[置頂] 圖解linux char驅動
來源:程序員人生 發布時間:2015-01-06 09:04:05 閱讀次數:3308次
圖解Linux char 驅動
主題:
1.字符驅動模型。
2.字符裝備的裝備號。
3.文件系統中對字符裝備的訪問。
1:字符裝備框架
寫的驅動程序只是1個程序,
然后將底層硬件驅動抽象出來用1個結構體來表示cdev。
那末我們需要做的就是從用戶空間寫的open、write、read等系統調用來操作硬件,
通過系統調用進入內核,而內核的實現就是利用了1切皆文件的思想,
就是上層直接操作文件但實質底層就是在操作硬件,
這個轉換就是VFS。
2:大概的進程:
大概進程就是說open(“/dev/first_drv”,..)
會進入內核->創建1個file結構體->該結構體指向->inode
->通過主裝備號查找到cdev結構(即是驅動)->找到cdev指向的操作函數指針->找到.open->自己的open函數。
3:下面是相干的詳細說明底層如何實現:
相干數據結構:
25 struct cdev {
26 struct kobject kobj;
27 struct module *owner;
28 const struct file_operations *ops;
29 struct list_head list;
30 dev_t dev;
31 unsigned int count;
32 };
33
34 struct kobj_map {
35
36 struct probe {
37
38 struct probe *next;
39 dev_t dev;
40 unsigned long range;
41 struct module *owner;
42 kobj_probe_t *get;
43 int (*lock)(dev_t, void *);
44 void *data;
45 } *probes[255];
46 struct mutex *lock;
47 };
48
49 static struct char_device_struct {
50
51 struct char_device_struct *next;
52 unsigned int major;
53 unsigned int baseminor;
54 int minorct;
55 char name[64];
56 struct file_operations *fops;
57 struct cdev *cdev; /* will die */
58 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
59
60 #define CHRDEV_MAJOR_HASH_SIZE 255
1.字符裝備驅動模型。
每個字符設驅動有1個cdev結構提表示。
再裝備驅動模型那個(device driver model)中。使用(kobject mapping domain)
來記錄字符裝備文件驅動。這是由struct kobj_map結構體來表示。
它內嵌了255個struct probe指針數組。kobj_map由全局變量cdev_map援用。
static struct kobj_map *cdev_map; 這個全局變量的定義在char_dev.c中。
70 /-------------
71 | cdev_map |
72 -------------/
73 |
74 | /------> probe /------> probe
75 | | +---------+ | +---------+ /-------
76 | | | *next |----/ | *next |----->| NULL |
77 --->kobj_map | +---------+ +---------+ -------/
78 +-------------------+ | | dev | | dev |
79 | *probes[255] | | +---------+ +---------+
80 | +-----------+ | | | range | | range |
81 | | *probe | | | +---------+ +---------+
82 | +-----------+ | | | *owner | | *owner |
83 | | *probe |---------/ +---------+ +---------+
84 | +-----------+ | | *get | | *get |
85 | | .... | | +---------+ +---------+
86 | +-----------+ | | *lock | | *lock |
87 | | *probe | | +---------+ +---------+
88 | +-----------+ | | *data |------ | *data |------
89 | | *probe |--------- +---------+ | +---------+ |
90 | +-----------+ | | | |
91 +------------------- | | |
92 | | |
93 | | |
94 | /------- | |
95 ----->| NULL | | |
96 -------/ | |
97 | |
98 cdev<-----------/ cdev<----/
99 +---------+ +---------+
100 | *kobj | | *kobj |
101 +---------+ +---------+
102 | *owner | | *owner |
103 +---------+ +---------+
104 | *ops | | *ops |
105 +---------+ +---------+
106 | *list | | *list |
107 +---------+ +---------+
108 | dev | | dev |
109 +---------+ +---------+
110 | *count | | *count |
111 +---------+ +---------+
1:cdev_add()函數詳解。
1.
1般會使用kzalloc(size,GFP_KERNEL)給cdev分配1塊空間,然后初始化好,ops對應的結構體。
2.
再使用cdev_init把ops函數操作集和cdev綁定起來。
3.
使用cdev_add() 用來將cdev對象添加到驅動模型中,其主要是通過kobj_map()來實現的.
kobj_map() 會創建1個probe對象,然后將其插入cdev_map中的某1項中,并關聯probe->data 指向 cdev
4.
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
根據裝備號,在cdev_map中查找其cdev對象內嵌的kobject. (probe->data->kobj),返回的是cdev的kobject
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
133 struct module *module, kobj_probe_t *probe,
134 int (*lock)(dev_t, void *), void *data)
135 {
136
137 unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
138 unsigned index = MAJOR(dev);
139 unsigned i;
140 struct probe *p;
141
142 if (n > 255)
143 n = 255;
144 //為prob結構體分配空間。
145 p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
146 if (p == NULL)
147 return -ENOMEM;
148
149 //給結構體賦值。
150 for (i = 0; i < n; i++, p++) {
151 p->owner = module;
152 p->get = probe;
153 p->lock = lock;
154 p->dev = dev;
155 p->range = range;
156 p->data = data;
157 }
158 mutex_lock(domain->lock);
159
160 //把分配的結構體probe寄存到該hash表中。結構如上。
161 for (i = 0, p -= n; i < n; i++, p++, index++) {
162 struct probe **s = &domain->probes[index % 255];
163 while (*s && (*s)->range < range)
164 s = &(*s)->next;
165 p->next = *s;
166 *s = p;
167 }
168 mutex_unlock(domain->lock);
169 return 0;
170 }
2:字符裝備的裝備號。
174 0 1 2 253 254
175 +--------+--------+--------+--------+--------+--------
176 cdevs | | | | .... | | |
177 +--------+--------+--------+--------+--------+--------
178 | | | |
179 | | | |
180 | | | |
181 /------- | | | |
182 | NULL |<--/ | | |
183 -------/ | | | /-------
184 | | ------>| NULL |
185 | | -------/
186 /-----------------/ /-------
187 | | NULL |
188 | -------/
189 |
190 ------>char_devices_struct /----> char_devices_struct
191 +----------------+ | +----------------+ /-------
192 | *next |--------/ | *next |------>| NULL |
193 +----------------+ +----------------+ -------/
194 | major | | major |
195 +----------------+ +----------------+
196 | vaseminor | | vaseminor |
197 +----------------+ +----------------+
198 | *name[64] | | *name[64] |
199 +----------------+ +----------------+
200 | *fops | | *fops |
201 +----------------+ +----------------+
202 | *cdev | | *cdev |
203 +----------------+ +----------------+
字符裝備的主,次裝備號的分配:
1.
全局數組 chrdevs 包括了255(CHRDEV_MAJOR_HASH_SIZE 的值)個struct char_device_struct的元素.
每個對應1個相應的主裝備號.
2.
分配了1個裝備號,就會創建1個 struct char_device_struct 的對象,
并將其添加到chrdevs中.這樣,通過chrdevs數組,我們就能夠知道分配了哪些裝備號.
相干函數:
1. register_chrdev_region( ) 分配指定的裝備號范圍
2.alloc_chrdev_region( ) 動態分配裝備范圍他們都主要是通過調用函數__register_chrdev_region() 來實現的
要注意,這兩個函數僅僅是注冊裝備號! 如果要和cdev關聯起來,還要調用cdev_add()
register_chrdev( ) 申請指定的裝備號,并且將其注冊到字符裝備驅動模型中.
它所做的事情為:
1. 注冊裝備號, 通過調用 __register_chrdev_region() 來實現
2. 分配1個cdev, 通過調用 cdev_init()來實現
3. 將cdev添加到驅動模型中,這1步將裝備號和驅動關聯了起來.通過調用 cdev_add() 來實現
4. 將第1步中創建的 struct char_device_struct 對象的 cdev 指向第2步中分配的cdev. 由于register_chrdev()是老的接口,
這1步在新的接口中其實不需要.
236 /-------------------------------------->cdev<------------------------------------
237 | +---------+ |
238 | | *kobj | |
239 | +---------+ |
240 | | *owner | |
241 | +---------+ |
242 | | *ops | |
243 | +---------+ |
244 | /----------------------------->| *list |<--------------------------- |
245 | | +---------+ | |
246 | | | dev | | |
247 | | +---------+ | |
248 | | | *count | | |
249 | | +---------+ | |
250 | | | |
251 | | inode inode | |
252 | | +---------+ +---------+ | |
253 | | | i_rdev | | i_rdev | | |
254 | | +---------+ +---------+ | |
255 | ----------|i_devices|<----------.........--------->|i_devices|<------/ |
256 | +---------+ +---------+ |
257 --------------- |*i_cdev | |*i_cdev |------------/
258 +---------+ +---------+
259 |i_cindex | |i_cindex |
260 +---------+ +---------+
261 | ..... | | ..... |
262 +---------+ +---------+
系統調用open打開1個字符裝備的時候, 通過1系列調用,終究會履行到 chrdev_open.
(終究是通過調用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open. 這1系列的調用進程,本文暫不討論)
int chrdev_open(struct inode * inode, struct file * filp)
chrdev_open()所做的事情可以概括以下:
1. 根據裝備號(inode->i_rdev), 在字符裝備驅動模型中查找對應的驅動程序,
這通過kobj_lookup() 來實現, kobj_lookup()會返回對應驅動程序cdev的kobject.
2. 設置inode->i_cdev , 指向找到的cdev
3. 將inode添加到cdev->list的鏈表中.
4. 使用cdev的ops 設置file對象的f_op
5. 如果ops中定義了open方法,則調用該open方法
6. 返回.
履行完chrdev_open()以后,file對象的f_op指向cdev的ops,因此以后對裝備進行的read, write等操作,就會履行cdev的相應操作.
285 static int chrdev_open(struct inode *inode, struct file *filp)
286 {
287
288 struct cdev *p;
289 struct cdev *new = NULL;
290 int ret = 0;
291
292 spin_lock(&cdev_lock);
293 p = inode->i_cdev;
294 if (!p) {
295 struct kobject *kobj;
296 int idx;
297 spin_unlock(&cdev_lock);
298
299 // 1. 根據裝備號(inode->i_rdev), 在字符裝備驅動模型中查找對應的驅動程序,
300 //這通過kobj_lookup() 來實現, kobj_lookup()會返回對應驅動程序cdev的kobject.
301
302 kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
303 if (!kobj)
304 return -ENXIO;
305 new = container_of(kobj, struct cdev, kobj);
306 spin_lock(&cdev_lock);
307 /* Check i_cdev again in case somebody beat us to it while
308 we dropped the lock. */
309
310 //2. 設置inode->i_cdev , 指向找到的cdev.
311 p = inode->i_cdev;
312 if (!p) {
313 inode->i_cdev = p = new;
314
315 //3. 將inode添加到cdev->list的鏈表中.
316 list_add(&inode->i_devices, &p->list);
317
318 new = NULL;
319 } else if (!cdev_get(p))
320 ret = -ENXIO;
321 } else if (!cdev_get(p))
322 ret = -ENXIO;
323 spin_unlock(&cdev_lock);
324 cdev_put(new);
325 if (ret)
326 return ret;
327
328 ret = -ENXIO;
329
330 //4. 使用cdev的ops 設置file對象的f_op
331 filp->f_op = fops_get(p->ops);
332 if (!filp->f_op)
333 goto out_cdev_put;
334
335 if (filp->f_op->open) {
336 5. 如果ops中定義了open方法,則調用該open方法
337 ret = filp->f_op->open(inode, filp);
338 if (ret)
339 goto out_cdev_put;
340 }
341
342 return 0;
}
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈