有關Linux kernel 字符設備分析:
參考:http://blog.jobbole.com/86531/
一.linux kernel 將設備分為3大類,字符設備,塊設備,網絡設備.
字符設備是指只能一個字節一個字節讀寫的設備, 常見的外設基本上都是字符設備.
塊設備:常見的存儲設備,硬盤,SD卡都歸為塊設備,塊設備是按一塊一塊讀取的.
網絡設備:linux 將對外通信的一個機制抽象成一個設備, 通過套接字對其進行相關的操作.
每一個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序通過設備文件(或稱設備節點)來使用驅動程序操作字符設備和塊設備。
二、字符設備、字符設備驅動與用戶空間訪問該設備的程序三者之間的關系。
三.字符設備的模型
四.下面講一下字符設備驅動的編寫流程,linux 內核為字符設備的創建提供了一套接口.
首先介紹一下dev_t , 他是主設備號和次設備號的結構體生成,他就代表了一個主次設備號
通過函數MKDEV (MAJ , MINOR) ; 生成.我們注冊一個字符設備可以通過動態注冊也可以靜態注冊 , linux kernel 為我們提供了所需要的接口
首先講一下靜態注冊的方法
1 //分配主設備號為200 次設備號從5開始分配5個 設備名字叫MONEY 2 int ret ; 3 DeviceId = MKDEV(MAJ , BASEMINOR); 4 ret = register_chrdev_region(DeviceId , MINORCNT , "MONEY"); 5 if(ret < 0) 6 { 7 return ret ; 8 } 9 10 //******************************************** 11 //方法一 12 //1> 初始化 13 cdev_init(&device, &fops); 14 //2> 添加 domain->probes HASH表上 15 cdev_add(&device,DeviceId , MINORCNT);
通過函數register_chrdev_region() , 我們可以注冊主設備號為MAJ , 次設備號BASEMINOR 開始 , 一共注冊MINORCNT 個次設備號 , 名字為MONEY 的字符設備.
第二種方法是動態申請主次設備號:
1 //動態分配一個主設備號 2 int ret ; 3 ret = alloc_chrdev_region(&DeviceId , BASEMINOR , MINORCNT,"TONY"); 4 if(ret < 0) 5 { 6 return ret ; 7 } 8 9 printk("major:%d \n" , MAJOR(DeviceId)); 10 11 //******************************************** 12 //方法二 13 //1> 分配空間 14 device = cdev_alloc();
我們可以通過linux kernel 提供的alloc_chrdev_region () 的方法 , 申請一個主設備號和基礎設備號 , 一共申請 MINORCNT , 名字為 TONY 的一個字符設備.
這里涉及一個結構體:
1 struct cdev { 2 struct kobject kobj; 3 struct module *owner;
4 const struct file_operations *ops; 5 struct list_head list; 6 dev_t dev; 7 unsigned int count; 8 };
這里的話還要申請一個cdev 結構體的空間
通過cdev_alloc() ;
搞定了主次設備號的問題 , 接下來就是涉及到了初始化和添加到設備列表 .
linux kernel 為我們提供了以下的方法:
1 //2> 初始化 2 cdev_init(device, &fops); 3 //3> 添加 domain->probes HASH表上 4 cdev_add(device,DeviceId , MINORCNT); 5
這里面涉及到了一個&fops 的文件操作結構體
1 static struct file_operations fops = { 2 .owner = THIS_MODULE, 3 .open = myopen, 4 .read = myread , 5 .write = mywrite, 6 .release = myclose, 7 .unlocked_ioctl = myioctl, 8 };
上層的open read write 等函數經過一系列的轉換都會到對應的函數
相對應的, 釋放主次設備號, 刪除在設備列表的節點, linux kernel 為我們提供了一下接口:
1 cdev_del(device); 2 3 unregister_chrdev_region(DeviceId , MINORCNT);
下面的代碼是想深入了解里面的代碼的看看就行了 , 如果只是向了解接口的 , 上面的就行了
從靜態申請注冊開始跟起吧
1 #define MKDEV(ma,mi) ((ma)<<8 | (mi))
上面這個是制作一個主次設備號的結構體
1 extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *); 2 extern int register_chrdev_region(dev_t, unsigned, const char *); 3 extern int __register_chrdev(unsigned int major, unsigned int baseminor, 4 unsigned int count, const char *name, 5 const struct file_operations *fops); 6 extern void __unregister_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name); 7 extern void unregister_chrdev_region(dev_t, unsigned);
這是幾個將要用的函數的函數聲明 , 它在 include/linux/fs.h 文件中
首先看一下 register_chrdev_region() 函數
1 /** 2 * register_chrdev_region() - register a range of device numbers 3 * @from: the first in the desired range of device numbers; must include 4 * the major number. 5 * @count: the number of consecutive device numbers required 6 * @name: the name of the device or driver. 7 * 8 * Return value is zero on success, a negative error code on failure. 9 */
注釋說明: 注冊一個范圍的設備號 , 原型如下:
1 int register_chrdev_region(dev_t from, unsigned count, const char *name) 2 { 3 struct char_device_struct *cd; 4 dev_t to = from + count; 5 dev_t n, next; 6 7 for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); 8 if (next > to) 9 next = to; 10 cd = __register_chrdev_region(MAJOR(n), MINOR(n), 11 next - n, name); 12 if (IS_ERR(cd)) 13 goto fail; 14 } 15 return 0; 16 fail: 17 to = n; 18 for (n = from; n < to; n = next) { 19 next = MKDEV(MAJOR(n)+1, 0); 20 kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); 21 } 22 return PTR_ERR(cd); 23 }
它是調用了
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
進里面看看
1 /* 2 * Register a single major with a specified minor range. 3 * 4 * If major == 0 this functions will dynamically allocate a major and return 5 * its number. 6 * 7 * If major > 0 this function will attempt to reserve the passed range of 8 * minors and will return zero on success. 9 * 10 * Returns a -ve errno on failure. 11 */
還是看注釋: 注冊一個指定的主設備號 和一個指定的次設備號范圍
判斷 主設備號是不是為零 , 如果是零的話 就動態申請一個主設備號 , 這就是后面要講的那個動態申請 , 它也是調用了這個.
代碼如下
1 static struct char_device_struct * 2 __register_chrdev_region(unsigned int major, unsigned int baseminor, 3 int minorct, const char *name) 4 { 5 struct char_device_struct *cd, **cp; 6 int ret = 0; 7 int i; 8
這里面涉及一個結構體沒講:
1 static struct char_device_struct { 2 struct char_device_struct *next; 3 unsigned int major; 4 unsigned int baseminor; 5 int minorct; 6 char name[64]; 7 struct cdev *cdev; /* will die */ 8 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
9 cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); //動態申請了一個char_device_struct 結構體 10 if (cd == NULL) 11 return ERR_PTR(-ENOMEM); 12 13 mutex_lock(&chrdevs_lock); //加一個互斥鎖 , 防止其他進程並發 14 15 /* temporary */ 16 if (major == 0) { 17 for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) { //這里其實就是做了一個動態申請主設備號的功能 18 if (chrdevs[i] == NULL) 19 break; 20 } 21 22 if (i == 0) { //沒有申請到主設備號, 直接退出 23 ret = -EBUSY; 24 goto out; 25 } 26 major = i; 27 ret = major; 28 } 29 30 cd->major = major; //對結構體進行初始化 31 cd->baseminor = baseminor; 32 cd->minorct = minorct; 33 strlcpy(cd->name, name, sizeof(cd->name)); 34 35 i = major_to_index(major); // 哈希表的下標生成 36 37 for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //進入chdevs[i] 哈希表快速進入 38 if ((*cp)->major > major || 39 ((*cp)->major == major && 40 (((*cp)->baseminor >= baseminor) || 41 ((*cp)->baseminor + (*cp)->minorct > baseminor)))) 42 break; 43 44 /* Check for overlapping minor ranges. */ // 檢查次設備號會不會重疊 45 if (*cp && (*cp)->major == major) { 46 int old_min = (*cp)->baseminor; 47 int old_max = (*cp)->baseminor + (*cp)->minorct - 1; 48 int new_min = baseminor; 49 int new_max = baseminor + minorct - 1; 50 51 /* New driver overlaps from the left. */ 52 if (new_max >= old_min && new_max <= old_max) { 53 ret = -EBUSY; 54 goto out; 55 } 56 57 /* New driver overlaps from the right. */ 58 if (new_min <= old_max && new_min >= old_min) { 59 ret = -EBUSY; 60 goto out; 61 } 62 } 63 64 cd->next = *cp; 65 *cp = cd; 66 mutex_unlock(&chrdevs_lock); //解鎖 67 return cd; 68 out: 69 mutex_unlock(&chrdevs_lock); 70 kfree(cd); 71 return ERR_PTR(ret); 72 }
到這里一個主次設備號就搞定了 , 並申請一個char_device_struct 結構體, 並對其進行賦值初始化.
第二步就是對cdev 進行init :
1 void cdev_init(struct cdev *, const struct file_operations *);
這里又涉及到一個struct cdev 的結構體:
1 struct cdev { 2 struct kobject kobj; 3 struct module *owner; 4 const struct file_operations *ops; 5 struct list_head list; 6 dev_t dev; 7 unsigned int count; 8 };
進初始化代碼一看究竟:
1 /** 2 * cdev_init() - initialize a cdev structure 3 * @cdev: the structure to initialize 4 * @fops: the file_operations for this device 5 * 6 * Initializes @cdev, remembering @fops, making it ready to add to the 7 * system with cdev_add(). 8 */ 9 void cdev_init(struct cdev *cdev, const struct file_operations *fops) 10 { 11 memset(cdev, 0, sizeof *cdev); 12 INIT_LIST_HEAD(&cdev->list); 13 kobject_init(&cdev->kobj, &ktype_cdev_default); 14 cdev->ops = fops; 15 }
看一段代碼之前我們盡可能的先看注釋, 這樣會讓我們跟代碼輕松很多 , 我們可以順着代碼的作者的思路走
注釋: 初始化一個cdev 結構體
進kobject_init() 看看:
1 /** 2 * kobject_init - initialize a kobject structure 初始化一個內核項目結構體 3 * @kobj: pointer to the kobject to initialize 4 * @ktype: pointer to the ktype for this kobject. 5 * 6 * This function will properly initialize a kobject such that it can then 7 * be passed to the kobject_add() call. 8 * 9 * After this function is called, the kobject MUST be cleaned up by a call 10 * to kobject_put(), not by a call to kfree directly to ensure that all of 11 * the memory is cleaned up properly. 12 */
1 void kobject_init(struct kobject *kobj, struct kobj_type *ktype) 2 { 3 char *err_str; 4 5 if (!kobj) { 6 err_str = "invalid kobject pointer!"; 7 goto error; 8 } 9 if (!ktype) { 10 err_str = "must have a ktype to be initialized properly!\n"; 11 goto error; 12 } 13 if (kobj->state_initialized) { 14 /* do not error out as sometimes we can recover */ 15 printk(KERN_ERR "kobject (%p): tried to init an initialized " 16 "object, something is seriously wrong.\n", kobj); 17 dump_stack(); 18 } 19 20 kobject_init_internal(kobj); 21 kobj->ktype = ktype; 22 return; 23 24 error: 25 printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str); 26 dump_stack(); 27 } 28 EXPORT_SYMBOL(kobject_init);
1 struct kobject { 2 const char *name; 3 struct list_head entry; 4 struct kobject *parent; 5 struct kset *kset; 6 struct kobj_type *ktype; 7 struct sysfs_dirent *sd; 8 struct kref kref; 9 unsigned int state_initialized:1; 10 unsigned int state_in_sysfs:1; 11 unsigned int state_add_uevent_sent:1; 12 unsigned int state_remove_uevent_sent:1; 13 unsigned int uevent_suppress:1; 14 };
下一部就是 cdev_add () ;
1 int cdev_add(struct cdev *, dev_t, unsigned);