[置顶] 图解linux char驱动
来源:程序员人生 发布时间:2015-01-06 09:04:05 阅读次数:3126次
图解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;
}
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠