浅谈linux字符设备注册
Linux中有兩種字符設備注冊的方法:
這里所提到的函數(shù)在文件:fs/char_dev.c中定義,在頭文件include/linux/cdev.h中聲明。
一、?????????????老方法:
如果你深入瀏覽?2.6?內核的大量驅動代碼,?你可能注意到有許多字符驅動使用這種方法.?你見到的是還沒有更新到?2.6內核接口的老代碼.?因為那個代碼實際上能用,?這個更新可能很長時間不會發(fā)生. 。
注冊一個字符設備的經(jīng)典方法是使用:
????? int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);這里, major?是感興趣的主編號, name?是驅動的名子(出現(xiàn)在?/proc/devices), fops?是缺省的?file_operations?結構.?一個對?register_chrdev?的調用為給定的主編號注冊?0 - 255?的次編號,?并且為每一個建立一個缺省的?cdev?結構.?使用這個接口的驅動必須準備好處理對所有?256?個次編號的?open?調用(?不管它們是否對應真實設備?),?它們不能使用大于?255?的主或次編號.
如果你使用?register_chrdev,?從系統(tǒng)中去除你的設備的正確的函數(shù)是:
int unregister_chrdev(unsigned int major, const char *name);major?和?name?必須和傳遞給?register_chrdev?的相同,?否則調用會失敗.
二、???????????新方法
第一步、
內核在內部使用類型?struct cdev?的結構來代表字符設備.?在內核調用你的設備操作前,?你編寫分配并注冊一個或幾個這些結構.新方法就是利用我們這里的?cdev?接口。定義在?linux/cdev.h。
struct cdev {
??????? struct kobject kobj;
??????? struct module *owner;?? //所屬模塊
??????? const struct file_operations *ops;???
??? ??? ??? ??? //文件操作結構,在寫驅動時,其結構體內的大部分函數(shù)要被實現(xiàn)
??????? struct list_head list;
??????? dev_t dev;????????? //設備號,int?類型,高12位為主設備號,低20位為次設備號
??????? unsigned int count;
};
可以使用如下宏調用來獲得主、次設備號:
MAJOR(dev_t dev)
MINOR(dev_t dev)
有?2?種方法來分配和初始化一個這些結構,呵呵,這里又是兩種哦。
1、如果你想在運行時獲得一個獨立的?cdev?結構,?你可以為此使用這樣的代碼:
struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &my_fops;cdev_alloc源碼如下
504 /**
505??* cdev_alloc() - allocate a cdev structure
506??*
507??* Allocates and returns a cdev structure, or NULL on failure.
508??*/
509 struct cdev *cdev_alloc(void)
510 {
511?????struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
512?????if (p) {
513?????????p->kobj.ktype = &ktype_cdev_dynamic;
514?????????INIT_LIST_HEAD(&p->list);
515?????????kobject_init(&p->kobj);
516?????}
517?????return p;
518 }
?
從函數(shù)名稱和第511行的代碼可以看出:這個函數(shù)動態(tài)申請結構體struct cdev,并對其進行初始化,最后將其指針返回。下面結合cdev_init進行進一步說明。
2、但是,?偶爾你會想將?cdev?結構嵌入一個你自己的設備特定的結構;?在這種情況下,?你應當初始化你已經(jīng)分配的結構,使用:
void cdev_init(struct cdev *cdev, struct file_operations *fops);cdev_init源碼如下
520 /**
521??* cdev_init() - initialize a cdev structure
522??* @cdev: the structure to initialize
523??* @fops: the file_operations for this device
524??*
525??* Initializes @cdev, remembering @fops, making it ready to add to the
526??* system with cdev_add().
527??*/
528 void cdev_init(struct cdev *cdev, const struct file_operations *fops)
529 {
530?????memset(cdev, 0, sizeof *cdev);
531?????INIT_LIST_HEAD(&cdev->list);
532?????cdev->kobj.ktype = &ktype_cdev_default;
533?????kobject_init(&cdev->kobj);
534?????cdev->ops = fops;
535 }
?
cdev_alloc和cdev_init的主要區(qū)別是:前者動態(tài)申請結構體struct cdev并對其進行初始化,后者將通過參數(shù)傳進來的結構體struct cdev進行初始化。
?
另一個主要區(qū)別是:cdev_alloc函數(shù)中沒有對struct cdev的ops域進行初始化,需要在cdev_alloc函數(shù)調用之后有專門的代碼對struct cdev的ops域進行初始化,而cdev_init函數(shù)中使用通過參數(shù)傳進來的struct file_operations結構體指針對struct cdev的ops域進行初始化,所以在函數(shù)cdev_init調用之后不需要再對struct cdev的ops域進行初始化。在這之前你就應該做好。這兩個函數(shù)就是互斥關系哈。可知,任一方法,?你都得對?struct cdev?成員file_operations結構初始化。
第二步、
函數(shù)cdev_alloc和cdev_init只是(申請)并初始化了(部分)結構體struct cdev,此時,struct cdev和內核還沒有任何關系。
函數(shù)cdev_add就是將函數(shù)cdev_alloc和cdev_init初始化后的struct cdev結構體注冊到內核中(第461行),自此內核就可以訪問設備了。注冊設備,通常發(fā)生在驅動模塊的加載函數(shù)中。這里, dev?是?cdev?結構, dev?是這個設備響應的第一個設備號, count?是應當關聯(lián)到設備的設備號的數(shù)目.?常常?count?是?1,?但是有多個設備號對應于一個特定的設備的情形.
cdev_add
447 /**
448??* cdev_add() - add a char device to the system
449??* @p: the cdev structure for the device
450??* @dev: the first device number for which this device is responsible
451??* @count: the number of consecutive minor numbers corresponding to this
452??*?????????device
453??*
454??* cdev_add() adds the device represented by @p to the system, making it
455??* live immediately.??A negative error code is returned on failure.
456??*/
457 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
458 {
459?????p->dev = dev;
460?????p->count = count;
461?????return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
462 }
在調用cdev_add()函數(shù)向系統(tǒng)注冊字符設備之前應該先調用:int register_chrdev_region(dev_t from,unsigned count,const char *name)函數(shù)為其分配設備號,此函數(shù)可用:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)函數(shù)代替,他們之間的區(qū)別在于:register_chrdev_region()用于已知起始設備號時,而MKDEV(int major,int minor)?可通過主次設備號來生成dev_t即設備號。
另一個用于設備號未知,動態(tài)申請,其優(yōu)點在于不會造成設備號重復的沖突。在注銷之后,應調用:void unregister_chrdev_region(dev_t from,unsigned count)函數(shù)釋放原先申請的設備號。
他們之間的順序關系如下:
register_chrdev_region()-->cdev_add()???? //此過程在加載模塊中
cdev_del()-->unregister_chrdev_region()???? //此過程在卸載模塊中
?
cdev_del
本函數(shù)和函數(shù)cdev_add功能相反,從內核中刪除設備。通常發(fā)生在驅動模塊的卸載函數(shù)中
?
總結
以上是生活随笔為你收集整理的浅谈linux字符设备注册的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 逻辑地址与物理地址
- 下一篇: linux内核部件分析之——设备驱动模型