浅谈linux字符设备注册
Linux中有兩種字符設(shè)備注冊的方法:
這里所提到的函數(shù)在文件:fs/char_dev.c中定義,在頭文件include/linux/cdev.h中聲明。
一、?????????????老方法:
如果你深入瀏覽?2.6?內(nèi)核的大量驅(qū)動(dòng)代碼,?你可能注意到有許多字符驅(qū)動(dòng)使用這種方法.?你見到的是還沒有更新到?2.6內(nèi)核接口的老代碼.?因?yàn)槟莻€(gè)代碼實(shí)際上能用,?這個(gè)更新可能很長時(shí)間不會(huì)發(fā)生. 。
注冊一個(gè)字符設(shè)備的經(jīng)典方法是使用:
????? int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);這里, major?是感興趣的主編號, name?是驅(qū)動(dòng)的名子(出現(xiàn)在?/proc/devices), fops?是缺省的?file_operations?結(jié)構(gòu).?一個(gè)對?register_chrdev?的調(diào)用為給定的主編號注冊?0 - 255?的次編號,?并且為每一個(gè)建立一個(gè)缺省的?cdev?結(jié)構(gòu).?使用這個(gè)接口的驅(qū)動(dòng)必須準(zhǔn)備好處理對所有?256?個(gè)次編號的?open?調(diào)用(?不管它們是否對應(yīng)真實(shí)設(shè)備?),?它們不能使用大于?255?的主或次編號.
如果你使用?register_chrdev,?從系統(tǒng)中去除你的設(shè)備的正確的函數(shù)是:
int unregister_chrdev(unsigned int major, const char *name);major?和?name?必須和傳遞給?register_chrdev?的相同,?否則調(diào)用會(huì)失敗.
二、???????????新方法
第一步、
內(nèi)核在內(nèi)部使用類型?struct cdev?的結(jié)構(gòu)來代表字符設(shè)備.?在內(nèi)核調(diào)用你的設(shè)備操作前,?你編寫分配并注冊一個(gè)或幾個(gè)這些結(jié)構(gòu).新方法就是利用我們這里的?cdev?接口。定義在?linux/cdev.h。
struct cdev {
??????? struct kobject kobj;
??????? struct module *owner;?? //所屬模塊
??????? const struct file_operations *ops;???
??? ??? ??? ??? //文件操作結(jié)構(gòu),在寫驅(qū)動(dòng)時(shí),其結(jié)構(gòu)體內(nèi)的大部分函數(shù)要被實(shí)現(xiàn)
??????? struct list_head list;
??????? dev_t dev;????????? //設(shè)備號,int?類型,高12位為主設(shè)備號,低20位為次設(shè)備號
??????? unsigned int count;
};
可以使用如下宏調(diào)用來獲得主、次設(shè)備號:
MAJOR(dev_t dev)
MINOR(dev_t dev)
有?2?種方法來分配和初始化一個(gè)這些結(jié)構(gòu),呵呵,這里又是兩種哦。
1、如果你想在運(yùn)行時(shí)獲得一個(gè)獨(dú)立的?cdev?結(jié)構(gòu),?你可以為此使用這樣的代碼:
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行的代碼可以看出:這個(gè)函數(shù)動(dòng)態(tài)申請結(jié)構(gòu)體struct cdev,并對其進(jìn)行初始化,最后將其指針返回。下面結(jié)合cdev_init進(jìn)行進(jìn)一步說明。
2、但是,?偶爾你會(huì)想將?cdev?結(jié)構(gòu)嵌入一個(gè)你自己的設(shè)備特定的結(jié)構(gòu);?在這種情況下,?你應(yīng)當(dāng)初始化你已經(jīng)分配的結(jié)構(gòu),使用:
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ū)別是:前者動(dòng)態(tài)申請結(jié)構(gòu)體struct cdev并對其進(jìn)行初始化,后者將通過參數(shù)傳進(jìn)來的結(jié)構(gòu)體struct cdev進(jìn)行初始化。
?
另一個(gè)主要區(qū)別是:cdev_alloc函數(shù)中沒有對struct cdev的ops域進(jìn)行初始化,需要在cdev_alloc函數(shù)調(diào)用之后有專門的代碼對struct cdev的ops域進(jìn)行初始化,而cdev_init函數(shù)中使用通過參數(shù)傳進(jìn)來的struct file_operations結(jié)構(gòu)體指針對struct cdev的ops域進(jìn)行初始化,所以在函數(shù)cdev_init調(diào)用之后不需要再對struct cdev的ops域進(jìn)行初始化。在這之前你就應(yīng)該做好。這兩個(gè)函數(shù)就是互斥關(guān)系哈。可知,任一方法,?你都得對?struct cdev?成員file_operations結(jié)構(gòu)初始化。
第二步、
函數(shù)cdev_alloc和cdev_init只是(申請)并初始化了(部分)結(jié)構(gòu)體struct cdev,此時(shí),struct cdev和內(nèi)核還沒有任何關(guān)系。
函數(shù)cdev_add就是將函數(shù)cdev_alloc和cdev_init初始化后的struct cdev結(jié)構(gòu)體注冊到內(nèi)核中(第461行),自此內(nèi)核就可以訪問設(shè)備了。注冊設(shè)備,通常發(fā)生在驅(qū)動(dòng)模塊的加載函數(shù)中。這里, dev?是?cdev?結(jié)構(gòu), dev?是這個(gè)設(shè)備響應(yīng)的第一個(gè)設(shè)備號, count?是應(yīng)當(dāng)關(guān)聯(lián)到設(shè)備的設(shè)備號的數(shù)目.?常常?count?是?1,?但是有多個(gè)設(shè)備號對應(yīng)于一個(gè)特定的設(shè)備的情形.
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 }
在調(diào)用cdev_add()函數(shù)向系統(tǒng)注冊字符設(shè)備之前應(yīng)該先調(diào)用:int register_chrdev_region(dev_t from,unsigned count,const char *name)函數(shù)為其分配設(shè)備號,此函數(shù)可用:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)函數(shù)代替,他們之間的區(qū)別在于:register_chrdev_region()用于已知起始設(shè)備號時(shí),而MKDEV(int major,int minor)?可通過主次設(shè)備號來生成dev_t即設(shè)備號。
另一個(gè)用于設(shè)備號未知,動(dòng)態(tài)申請,其優(yōu)點(diǎn)在于不會(huì)造成設(shè)備號重復(fù)的沖突。在注銷之后,應(yīng)調(diào)用:void unregister_chrdev_region(dev_t from,unsigned count)函數(shù)釋放原先申請的設(shè)備號。
他們之間的順序關(guān)系如下:
register_chrdev_region()-->cdev_add()???? //此過程在加載模塊中
cdev_del()-->unregister_chrdev_region()???? //此過程在卸載模塊中
?
cdev_del
本函數(shù)和函數(shù)cdev_add功能相反,從內(nèi)核中刪除設(shè)備。通常發(fā)生在驅(qū)動(dòng)模塊的卸載函數(shù)中
?
總結(jié)
以上是生活随笔為你收集整理的浅谈linux字符设备注册的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 逻辑地址与物理地址
- 下一篇: linux内核部件分析之——设备驱动模型