有關Linux kernel 字符設備分析:linux
參考:http://blog.jobbole.com/86531/網絡
一.linux kernel 將設備分爲3大類,字符設備,塊設備,網絡設備.併發
字符設備是指只能一個字節一個字節讀寫的設備, 常見的外設基本上都是字符設備.app
塊設備:常見的存儲設備,硬盤,SD卡都歸爲塊設備,塊設備是按一塊一塊讀取的.dom
網絡設備:linux 將對外通訊的一個機制抽象成一個設備, 經過套接字對其進行相關的操做.ide
每個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序經過設備文件(或稱設備節點)來使用驅動程序操做字符設備和塊設備。函數
2、字符設備、字符設備驅動與用戶空間訪問該設備的程序三者之間的關係。ui
三.字符設備的模型this
四.下面講一下字符設備驅動的編寫流程,linux 內核爲字符設備的建立提供了一套接口.3d
首先介紹一下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 };
複製代碼