#主要開發流程介紹node
module_init宏和module_exit宏linux
當模塊裝載時須要調用module_init宏指定的函數,卸載時須要調用 module_exit宏指定的函數
如下是簡單的init流程:express
當卸載模塊時,須要釋放申請的設備號。api
#主設備號和次設備號 對字符設備的訪問是經過文件系統內的設備名稱進行的。那些名稱被稱爲特殊 文件、設備文件,或者簡單稱爲文件系統樹的節點,他們一般位於/dev目錄。安全
一般而言,主設備號表示設備對應的驅動程序。例如,/dev/null和/dev/zero 由驅動程序1管理,而虛擬控制檯和串口終端由驅動程序4管理。數據結構
現代的Linux內核容許多個驅動程序共享主設備號,但咱們看到的仍然按照」一 個主設備號對應一個驅動程序「的原則組織。架構
/proc/devices 能夠查看註冊的主設備號; /proc/modules 能夠查看正在使用模塊的進程數併發
#設備編號的內部表達 在內核中dev_t類型(在linux/types.h中定義)用來保存設備編號——包括主 設備號和次設備號。咱們的代碼不該該對設備編號的組織作任何假定,而應該始終 使用linux/kdev_t.h中定義的宏。app
MAJOR(dev_t dev); MINOR(dev_t dev);
相反,若是要將主設備號和次設備號轉換成dev_t類型,則使用:異步
MKDEV(int major, int minor);
#分配和釋放設備編號 在創建一個字符設備以前,咱們的驅動程序首先要作的事情就是得到一個或者多個 設備編號。完成該工做的必要函數是 register_chrdev_region,該函數在linux/fd.h 中聲明:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
其中 first 是要分配的設備編號範圍的起始值。first的次設備號常常被置爲0,但對 該函數不是必須的。count 是所請求的連續設備編號的個數。 name 是和該編號範圍 關聯的設備名稱,它將出如今/proc/devices和sysfs中。
若是咱們知道可用的設備編號,則 register_chrdev_region 會工做很好。可是 咱們進程不知道將要用哪些主設備號;應此提供瞭如下函數
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev 用於輸出參數,成功調用後保存已分配範圍的第一個編號。 firstminor 應該是 要使用的被請求的第一個次設備號,一般是0。 count 和 name 參數和 register_chrdev_region 相同。
不管使用哪一種方法分配設備號,都應該在再也不使用它們時釋放這些設備編號。
void unregister_chrdev_region(dev_t first, unsigned int count);
一般咱們在清除模塊中調用 unregister_chrdev_region 函數。
#一些重要的數據結構
##文件操做 迄今爲止,咱們已經爲本身保留了一些設備編號,但還沒有將任何驅動程序的操做鏈接到這些 編號。file_operations結構就是用來創建這種鏈接的。
struct file_operations { struct module *owner; loff_t(*llseek) (struct file *, loff_t, int); ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *); ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long); }; struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, }; //C99 syntax
##file結構 在<linux/fs.h>中定義的struct file是設備驅動程序所使用的第二個最重要的數據結構。
注意:file結構和用戶空間程序中的FILE沒有任何關聯。FILE是C庫中的定義的結構, 而struct file是一個內核結構,不會出如今用戶程序中(文件描述符應該是指向該結構體)。
struct file { mode_t f_mode; //文件模式 loff_t f_pos; //當前讀寫位置 unsigned int f_flags; //文件標誌,如O_RDONLY、O_NONBLOCK和O_SYNC。 struct file_operations *f_op; //與文件相關的操做。 void *private_data; //open系統調用在調用驅動程序的open方法前將這個 //指針置爲NULL。 struct dentry *f_dentry;//文件對應的目錄項(dentry)結構。 //filp->f_dentry->d_inode …… }
##inode結構 內核用inode結構在內部表示文件,所以它和file結構不一樣,後者表示打開的文件描述符。對 單個文件,可能會有多個表示打開的文件描述符的file結構,但它們都指向單個inode結構。
struct inode { dev_t i_rdev; //對錶示設備文件的inode結構, //該字段包含了真正的設備編號 struct cdev *i_cdev;//表示字符設備的內核內部結構。 …… }
i_rdev 的類型在2.5開發系列版本中發生了變化,爲了鼓勵編寫可移植性更強的代碼, 內核開發者增長了兩個新的宏。
unsigned int iminor(struct inode *inode); //獲取次設備號 unsigned int imajor(struct inode *inode); //獲取主設備號
#字符設備的註冊 內核內部使用struct cdev結構來表示字符設備。在內核調用設備的操做以前,必須分配 並註冊一個或者多個上述結構。爲此,咱們的代碼須要包含linux/cdev.h,其中定義 了這個結構以及與其相關的一些輔助函數。
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
有一個老的機制能夠避免使用cdev結構,可是新代碼應該使用新技術。
struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &my_fops;
咱們能夠將cdev結構嵌入到本身的設備特定結構中(有點相似派生的C版本)。 若是沒有經過 cdev_alloc 申請,則咱們須要用下面的代碼來初始化已分配的結構:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
還有一個字段須要初始化,和file_operations同樣,struct cdev也有一個全部者字段, 應設置爲THIS_MODULE。
在設置完cdev結構後,最後的步驟是告訴內核該結構的信息:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
num 是該設備對應的第一個設備編號,count是應該和該設備關聯的設備編號數量。 只要 cdev_add 成功返回,咱們的設備就要開始工做了,它的操做會被內核調用。
要從系統移除一個字符設備,作以下調用:
void cdev_del(struct cdev *dev);
在將cdev經過 cdev_del 移除後,就不該該再訪問cdev結構了。
##Scull中的設備註冊
static void scull_setup_cdev(struct scull_dev *dev, int index) { int err, devno = MKDEV(scull_major, scull_minor+index); cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; // this expression is redundancy ? err = cdev_add(&dev->cdev, devno, 1); if(err){ printk(KERN_NOTICE "Error %d adding scull%d", err, index); } }
由於cdev結構被內嵌到了strcut scull_dev中,所以必須調用cdev_init來執行該結構 的初始化。
##早期辦法
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
對 register_chrdev 的調用將 爲給定的主設備號註冊0~255做爲次設備號,併爲 每一個設備創建一個對應的默認cdev結構。使用這一接口的驅動程序必須可以處理全部 256個次設備號上的 open 調用。對應的移除函數:
int unregister_chrdev(unsigned int major, const char *name);
#open和release
##open方法
open 方法提供給驅動程序以初始化的能力(和module_init的不一樣),open應完成以下 工做:
open 方法的原型以下:
int (*open) (struct inode *inode, struct file *filp);
inode參數在i_cdev字段中包含了咱們須要的信息,即咱們先前設定的cdev結構。咱們一般須要 包含它的scull_dev結構,內核黑客爲咱們提供了此類技巧,它經過定義在linux/kernel.h 中的container_of宏實現:
container_of(pointer, container_type, container_field); struct scull_dev *dev; dev = container_of(inode->i_cdev, struct scull_dev, cdev); flip->private_data = dev;
另外一個肯定要打開的設備的方法是:檢查保存在inode中的次設備號。若是使用了 register_chrdev 註冊設備,則必須使用該技術。 通過簡化的 scull_open 代碼:
int scull_open(struct inode *inode, struct file *filp) { struct scutll_dev *dev; dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; if((filp->f_flags&O_ACCMODE)==O_WRONLY){ scull_trim(dev); } return 0; }
因爲咱們沒有維護scull的打開計數,只維護模塊的使用計數,所以 也就沒有相似"首次打開時初始化設備"這類動做。
##release方法 release 方法和 open 相反,有時這個方法被稱爲 device_close。
注: 後面scull_open爲每種設備都替換了不一樣的filp->f_op,因此不一樣的設備由 不一樣的函數關閉
當關閉一個設備文件的次數比打開它的次數多時,系統中會發生什麼? 答案很簡單:並非每一個close系統調用都會引發對release方法的調用。只有 真正釋放設備數據結構的 close 調用纔會調用這個方法。內核對每一個file 結構維護其被使用多少次的計數器。不管 fork 仍是 dup 都不會建立新 的數據結構(僅由open建立),他們只是增長已有結構中的計數。只有file結 構的計數歸零是,close系統調用纔會執行release方法,這隻在刪除這個結 構時纔會發生。保證了一次open只會看到一次release調用。
注意:flush方法在應用程序每次調用close時都會調用。
#read和write read 和 write 方法完成的任務是類似的,亦即,拷貝數據到應用程序空間, 或者反過來。
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp); ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
須要說明的是read和write方法的buff參數是用戶空間的指針。所以,代碼不能直接 引用其中內容。緣由以下:
爲確保安全,須要使用下面幾個函數(由linux/uaccess.h中定義)
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count); unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);
當內核空間運行的代碼訪問用戶空間的時候必須多加當心,由於被尋址的用戶頁面 可能不存在當前內存中,因而虛擬內存子系統經該進程轉入休眠,知道該頁面被加載 到指望位置。對驅動開發人員來講,這帶來的結果就是任何訪問用戶空間的函數都是 必須可重入的(異步信號安全),必須能和其餘驅動程序函數併發執行,更特別的是 必須處於可以合法休眠的狀態。
#readv和writev Unix系統很早就已支持兩個可選的系統調用:readv和writev。 若是驅動程序沒有提供用於處理向量操做的方法,readv和writev會經過對read和 write方法的屢次調用來實現。但在不少狀況下,直接在驅動程序中實現readv和writev 能夠得到更高的效率(收集分散的buffer直接傳給驅動讀寫,減小了內存拷貝次數)。
ssize_t (*readv) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos); ssize_t (*writev) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
iovec結構定義在<linux/uio.h>中:
struct iovec { void __user *iov_base; __kernel_size_t iov_len; }
每一個iovec結構都描述了一個用於傳輸的數據塊。函數中的count參數指明要操做多少 個iovec結構。 正確而有效率的操做常常須要驅動程序作一些更爲巧妙的事情。 例如,磁帶驅動程序的writev就應將全部iovec結構的內容做爲磁帶上的單個記錄寫入。 若是忽略他們,內核會經過read和write模擬它們。
#實例
/* * ===================================================================================== * * Filename: scull.c * * Description: this is a first driver from ldd3 * ignore mutithread race, data overflow, * just a simple example. * * Version: 1.0 * Created: 11/29/2014 09:00:04 PM * Revision: none * Compiler: gcc * * Author: firemiles(firemiles@163.com), * Organization: * * ===================================================================================== */ #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include<linux/slab.h> MODULE_LICENSE("GPL"); static long scull_ioctl (struct file *file, unsigned int cmd, unsigned long n); static ssize_t scull_write (struct file *file, const char __user *buff, size_t count, loff_t *f_ops ); static ssize_t scull_read (struct file *file, char __user *buff, size_t count, loff_t *f_ops ); static loff_t scull_llseek (struct file *file, loff_t f_ops, int count); static int scull_release (struct inode *inode, struct file *file ); static int scull_open (struct inode *inode, struct file *file ); static int scull_major = 0; static int scull_minor = 0; struct scull_dev { char *data; size_t maxlen; //buffer length size_t len; //data length struct cdev cdev; }scull_dev1; struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_read, .write = scull_write, .unlocked_ioctl = scull_ioctl, //new api .open = scull_open, .release= scull_release, }; /* * === FUNCTION ====================================================================== * Name: scull open * Description: * ===================================================================================== */ static int scull_open (struct inode *inode, struct file *filp ) { struct scull_dev *dev; dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; if(dev->data == NULL){ dev->data = (char *)kmalloc(1024, GFP_KERNEL); dev->maxlen = 1024; dev->len = 0; } return 0; } /* ----- end of function scull open ----- */ /* * === FUNCTION ====================================================================== * Name: scull_close * Description: * ===================================================================================== */ static int scull_release (struct inode *inode, struct file *filp ) { return 0; } /* ----- end of function scull_close ----- */ /* * === FUNCTION ====================================================================== * Name: scull_llseek * Description: * ===================================================================================== */ static loff_t scull_llseek (struct file *file, loff_t f_ops, int count) { return 0; } /* ----- end of function scull_llseek ----- */ /* * === FUNCTION ====================================================================== * Name: scull_read * Description: * ===================================================================================== */ static ssize_t scull_read (struct file *filp, char __user *buff, size_t count, loff_t *f_ops ) { int num; struct scull_dev *dev = filp->private_data; copy_to_user(buff, dev->data, dev->len); // ignore buff overflow; num = dev->len; dev->len = 0; return num; } /* ----- end of function scull_read ----- */ /* * === FUNCTION ====================================================================== * Name: scull_write * Description: * ===================================================================================== */ static ssize_t scull_write (struct file *filp, const char __user *buff, size_t count, loff_t *f_ops ) { struct scull_dev *dev = filp->private_data; copy_from_user(dev->data, buff, count); // ignore data overflow; dev->len = count; return count; } /* ----- end of function scull_write ----- */ /* * === FUNCTION ====================================================================== * Name: scull_ioctl * Description: * ===================================================================================== */ static long scull_ioctl (struct file *file, unsigned int cmd, unsigned long n) { return 0; } /* ----- end of function scull_ioctl ----- */ /* * === FUNCTION ====================================================================== * Name: scull_setup_cdev * Description: * ===================================================================================== */ static void scull_setup_cdev (struct scull_dev *dev, int index) { int err; dev_t devnum; char name[16]; sprintf(name,"scull1"); if(scull_major){ devnum = MKDEV(scull_major, scull_minor+index); err = register_chrdev_region(devnum, 1, name); }else{ err = alloc_chrdev_region(&devnum, scull_minor+index, 1, name); scull_major = MAJOR(devnum); } if(err<0){ printk(KERN_WARNING "scull1: can't get major %d\n", scull_major); return; } cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; //nessary? err = cdev_add(&dev->cdev, devnum, 1); if(err){ printk(KERN_NOTICE "Error %d adding scull%d", err, index); } } /* ----- end of function scull_setup_cdev ----- */ /* * === FUNCTION ====================================================================== * Name: scull_init * Description: * ===================================================================================== */ static int __init scull_init (void) { scull_setup_cdev(&scull_dev1, 1); printk(KERN_ALERT "scull init\n"); return 0; } /* ----- end of function scull_init ----- */ /* * === FUNCTION ====================================================================== * Name: scull_exit * Description: * ===================================================================================== */ static void __exit scull_exit (void) { kfree(scull_dev1.data); unregister_chrdev_region(scull_dev1.cdev.dev, 1); cdev_del(&scull_dev1.cdev); printk(KERN_ALERT "scull exit\n"); } /* ----- end of function scull_exit ----- */ /* this macro told to compiler. * that the two function are init function and exit function * */ module_init(scull_init); module_exit(scull_exit);