linux字符设备驱动程序源码(char_dev.c)分析

     问题:应用程序如何通过一个字符设备文件找到对应的字符设备?

     本文主要分析linux-2.6.28内核版本的字符设备抽象层源码文件char_dev.c。该文件代码量不大,但其为linux应用程序访问实际字符型硬件设备搭建了桥梁,进一步限定了linux字符设备驱动的设计框架。

 1 // 初始化kobj_map结构时填充的回调函数成员
 2 static struct kobject *base_probe(dev_t dev, int *part, void *data)
 3 {
 4     if (request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev)) > 0)
 5         /* Make old-style 2.4 aliases work */
 6         request_module("char-major-%d", MAJOR(dev));
 7     return NULL;
 8 }
 9 
10 void __init chrdev_init(void)
11 {
12     cdev_map = kobj_map_init(base_probe, &chrdevs_lock);
13     bdi_init(&directly_mappable_cdev_bdi);
14 }
chrdev_init

     该函数在系统初始化启动时被调用。该函数首先分配一个kobj_map类型结构,并初始化其成员,返回该结构指针保存在类型为kobj_map结构的cdev_map变量中。接下来初始化BDI(backing device information)备份设备信息结构体。它用于描述备份存储设备相关信息,BDI结构应该主要是用来处理数据存储系统相关的功能,其应该与文件系统处理,存储管理及缓存机制相关性比较大,对理解字符驱动设备框架影响不大,再者本人目前对BDI不甚了解,暂时不去讨论相关内容。主要描述下与cdev_map相关的内容。

      kobj_map结构定义如下:

 1 struct kobj_map {
 2     struct probe {
 3         struct probe *next;
 4         dev_t dev;
 5         unsigned long range;
 6         struct module *owner;
 7         kobj_probe_t *get;
 8         int (*lock)(dev_t, void *);
 9         void *data;
10     } *probes[255];
11     struct mutex *lock;
12 };
kobj_map结构定义

      该结构主要包含一个类型为probe结构的probes哈希链表数组。系统启动后,该数组便永久驻留在内存中,用来存储字符设备的kobject对象、设备号等相关信息。字符设备驱动在系统初始化时最终会关联到probes数组,并填充与之对应的具体成员。当系统调用通过设备号打开设备时首先就是从该数组中搜索与设备号匹配的字符设备信息,从而找到相应字符设备。

      以下为字符设备结构cdev初始化相关的代码:

 1 // 下面两个函数为kobj_type结构成员函数,用于解除cdev链表上文件结点inode与该cdev结构的关联
 2 static void cdev_default_release(struct kobject *kobj)
 3 {
 4     struct cdev *p = container_of(kobj, struct cdev, kobj);
 5     cdev_purge(p);
 6 }
 7 
 8 static void cdev_dynamic_release(struct kobject *kobj)
 9 {
10     struct cdev *p = container_of(kobj, struct cdev, kobj);
11     cdev_purge(p);
12     kfree(p);
13 }
14 
15 // 下面两个结构体用于kobject对象的初始化
16 static struct kobj_type ktype_cdev_default = {
17     .release    = cdev_default_release,
18 };
19 
20 static struct kobj_type ktype_cdev_dynamic = {
21     .release    = cdev_dynamic_release,
22 };
23 
24 /**
25  * cdev_alloc() - allocate a cdev structure
26  *
27  * Allocates and returns a cdev structure, or NULL on failure.
28  */
29  // 该函数为向下层提供的接口函数,用于动态分配并初始化cdev结构
30 struct cdev *cdev_alloc(void)
31 {
32     struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
33     if (p) {
34         INIT_LIST_HEAD(&p->list);
35         kobject_init(&p->kobj, &ktype_cdev_dynamic);
36     }
37     return p;
38 }
39 
40 /**
41  * cdev_init() - initialize a cdev structure
42  * @cdev: the structure to initialize
43  * @fops: the file_operations for this device
44  *
45  * Initializes @cdev, remembering @fops, making it ready to add to the
46  * system with cdev_add().
47  */
48  // 该函数为向下层提供的接口函数,用于初始化字符设备驱动分配的cdev结构
49 void cdev_init(struct cdev *cdev, const struct file_operations *fops)
50 {
51     memset(cdev, 0, sizeof *cdev);
52     INIT_LIST_HEAD(&cdev->list);
53     // 初始化字符设备结构对应的kobject对象结构,kobject结构构成了linux设备模型的基础
54     kobject_init(&cdev->kobj, &ktype_cdev_default);
55     cdev->ops = fops;
56 }
cdev init

      向kobj_map结构的哈希链表数组中添加cdev结构:

 1 // 下面两个函数为调用kobj_map函数时候传递的回调函数
 2 static struct kobject *exact_match(dev_t dev, int *part, void *data)
 3 {
 4     struct cdev *p = data;
 5     return &p->kobj;
 6 }
 7 
 8 static int exact_lock(dev_t dev, void *data)
 9 {
10     struct cdev *p = data;
11     return cdev_get(p) ? 0 : -1;
12 }
13 
14 /**
15  * cdev_add() - add a char device to the system
16  * @p: the cdev structure for the device
17  * @dev: the first device number for which this device is responsible
18  * @count: the number of consecutive minor numbers corresponding to this
19  *         device
20  *
21  * cdev_add() adds the device represented by @p to the system, making it
22  * live immediately.  A negative error code is returned on failure.
23  */
24  // 该函数为向下层提供的接口函数,将初始化后的cdev结构添加到cdev_map指向的哈希链表数组中。
25 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
26 {
27     p->dev = dev;
28     p->count = count;
29     return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
30 }
cdev_add

      当完成上述两项工作后,当系统调用打开设备时通过查找probes哈希链表数组便可以找到对应设备结构。

      注销字符设备时相关函数:

 1 // 删除指定设备在哈希链表数组中的信息
 2 static void cdev_unmap(dev_t dev, unsigned count)
 3 {
 4     kobj_unmap(cdev_map, dev, count);
 5 }
 6 
 7 /**
 8  * cdev_del() - remove a cdev from the system
 9  * @p: the cdev structure to be removed
10  *
11  * cdev_del() removes @p from the system, possibly freeing the structure
12  * itself.
13  */
14  // 向下层提供的接口函数,设备注销时调用该函数
15 void cdev_del(struct cdev *p)
16 {
17     cdev_unmap(p->dev, p->count);
18     // 减小kobject对象引用计数,当引用计数为0时删除kobject对象。
19     kobject_put(&p->kobj);
20 }
注销设备函数

      通过inode节点打开字符设备时用到如下文件操作结构:

 1 /*
 2  * Dummy default file-operations: the only thing this does
 3  * is contain the open that then fills in the correct operations
 4  * depending on the special file...
 5  */
 6  // 该结构关联设备文件结点与字符设备结构
 7 const struct file_operations def_chr_fops = {
 8     .open = chrdev_open,
 9     .llseek = noop_llseek,
10 };
file_operations

      该结构是inode节点搜索cdev_map哈希链表数组中字符设备结构的真正桥梁,主要是open成员函数的实现,该函数功能如下:

 1 /*
 2  * Called every time a character special file is opened
 3  */
 4  // 系统调用打开字符设备的桥梁函数
 5 static int chrdev_open(struct inode *inode, struct file *filp)
 6 {
 7     struct cdev *p;
 8     struct cdev *new = NULL;
 9     int ret = 0;
10     // 加锁,控制系统并发打开该字符设备时的资源竞争。
11     spin_lock(&cdev_lock);
12     // 首次打开设备时inode结点的cdev结构为空
13     p = inode->i_cdev;
14     if (!p) {
15         struct kobject *kobj;
16         int idx;
17         spin_unlock(&cdev_lock);
18         // 搜索cdev_map哈希链表数组,查找inode->i_rdev设备号指定的设备是否在该数组中,
19         // 如果存在则返回字符设备关联的kobject对象结构。
20         kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
21         if (!kobj)
22             return -ENXIO;
23         // 获取字符设备结构
24         new = container_of(kobj, struct cdev, kobj);
25         spin_lock(&cdev_lock);
26         /* Check i_cdev again in case somebody beat us to it while
27            we dropped the lock. */
28         p = inode->i_cdev;
29         if (!p) {
30             // 关联查找到的字符设备结构到inode设备文件结点
31             inode->i_cdev = p = new;
32             // 将inode添加到字符设备链表头结构中
33             list_add(&inode->i_devices, &p->list);
34             new = NULL;
35         } else if (!cdev_get(p))
36             ret = -ENXIO;
37     } else if (!cdev_get(p))
38         ret = -ENXIO;
39     spin_unlock(&cdev_lock);
40     // new为真时说明处理过程出错,减小字符设备引用计数,并释放相关资源。
41     cdev_put(new);
42     if (ret)
43         return ret;
44 
45     ret = -ENXIO;
46     // 使用实际字符设备注册的文件操作结构替换默认的文件操作结构(def_chr_fops)
47     filp->f_op = fops_get(p->ops);
48     if (!filp->f_op)
49         goto out_cdev_put;
50     // 调用字符设备注册的open函数进行字符设备相关的处理操作
51     if (filp->f_op->open) {
52         ret = filp->f_op->open(inode,filp);
53         if (ret)
54             goto out_cdev_put;
55     }
56 
57     return 0;
58 
59  out_cdev_put:
60     cdev_put(p);
61     return ret;
62 }
chrdev_open

      其它与字符设备结构相关的函数说明如下:

 1 // 增加与该字符设备结构相关的kobject对象,module的引用计数
 2 static struct kobject *cdev_get(struct cdev *p)
 3 {
 4     struct module *owner = p->owner;
 5     struct kobject *kobj;
 6 
 7     if (owner && !try_module_get(owner))
 8         return NULL;
 9     kobj = kobject_get(&p->kobj);
10     if (!kobj)
11         module_put(owner);
12     return kobj;
13 }
14 // 减小与该字符设备结构相关的kobject对象,module的引用计数,当计数减小为0时应当会删除kobject对象
15 void cdev_put(struct cdev *p)
16 {
17     if (p) {
18         struct module *owner = p->owner;
19         kobject_put(&p->kobj);
20         module_put(owner);
21     }
22 }
23 
24 // 将指定的与该字符设备关联的inode结点从字符设备结构的链表头中删除
25 void cd_forget(struct inode *inode)
26 {
27     spin_lock(&cdev_lock);
28     list_del_init(&inode->i_devices);
29     inode->i_cdev = NULL;
30     spin_unlock(&cdev_lock);
31 }
32 // 注销字符设备时解除与该字符设备关联的文件结点inode
33 static void cdev_purge(struct cdev *cdev)
34 {
35     spin_lock(&cdev_lock);
36     // cdev链表头不空时将与该字符设备关联的所有inode结点从该链表头结构中删除
37     while (!list_empty(&cdev->list)) {
38         struct inode *inode;
39         inode = container_of(cdev->list.next, struct inode, i_devices);
40         list_del_init(&inode->i_devices);
41         inode->i_cdev = NULL;
42     }
43     spin_unlock(&cdev_lock);
44 }
others

      以上描述了与字符设备结构相关的处理操作与功能,另外字符设备驱动还需要与设备号相关的功能,包括设备号的申请分配,释放及管理。以下描述主要与设备号相关功能的代码。

      首先看下char_device_struct结构,该结构描述了字符设备的设备号使用情况,其同样为一个哈希链表数组,以主设备号为主键。

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];
char_device_struct

      设备号相关功能代码如下:

  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  */
 12 // 实际的设备号注册函数
 13 static struct char_device_struct *
 14 __register_chrdev_region(unsigned int major, unsigned int baseminor,
 15                int minorct, const char *name)
 16 {
 17     struct char_device_struct *cd, **cp;
 18     int ret = 0;
 19     int i;
 20     // 动态分配设备号结构
 21     cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
 22     if (cd == NULL)
 23         return ERR_PTR(-ENOMEM);
 24 
 25     mutex_lock(&chrdevs_lock);
 26 
 27     /* temporary */
 28     if (major == 0) {
 29         // 动态分配设备号时,从后向前搜索未占用的设备号结构数据项,并且不使用第一项数据项
 30         for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
 31             if (chrdevs[i] == NULL)
 32                 break;
 33         }
 34 
 35         if (i == 0) {
 36             ret = -EBUSY;
 37             goto out;
 38         }
 39         major = i;
 40         ret = major;
 41     }
 42 
 43     // 初始化设备号结构数据项
 44     cd->major = major;
 45     cd->baseminor = baseminor;
 46     cd->minorct = minorct;
 47     strlcpy(cd->name, name, sizeof(cd->name));
 48 
 49     // 通过主设备号获取在设备号哈希链表数组中的索引
 50     i = major_to_index(major);
 51     // 搜索设备号哈希链表数组,对其按照主设备号major从小到大排序,并将新的设备号插入到链表
 52     for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
 53         if ((*cp)->major > major ||
 54             ((*cp)->major == major &&
 55              (((*cp)->baseminor >= baseminor) ||
 56               ((*cp)->baseminor + (*cp)->minorct > baseminor))))
 57             break;
 58 
 59     /* Check for overlapping minor ranges.  */
 60     // 检查设备号是否有重叠
 61     // 重叠分以下几种情况: 
 62     // case1: |new_min|----|old_min|----|new_max|-----|old_max|
 63     // case2: |old_min|----|new_min|----|old_max|-----|new_max|
 64     // case3: |old_min|----|new_min|----|new_max|-----|old_max|
 65     // case4: |new_min|----|old_min|----|old_max|-----|new_max|
 66     // 下面的逻辑包含了上面的前三种情况,但对于case4并没有包含在内,感觉第四种情况也是一种重叠情况,
 67     // 目前还不知道为什么没有进行处理。
 68     if (*cp && (*cp)->major == major) {
 69         int old_min = (*cp)->baseminor;
 70         int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
 71         int new_min = baseminor;
 72         int new_max = baseminor + minorct - 1;
 73 
 74         /* New driver overlaps from the left.  */
 75         if (new_max >= old_min && new_max <= old_max) {
 76             ret = -EBUSY;
 77             goto out;
 78         }
 79 
 80         /* New driver overlaps from the right.  */
 81         if (new_min <= old_max && new_min >= old_min) {
 82             ret = -EBUSY;
 83             goto out;
 84         }
 85     }
 86 
 87     cd->next = *cp;
 88     *cp = cd;
 89     mutex_unlock(&chrdevs_lock);
 90     return cd;
 91 out:
 92     mutex_unlock(&chrdevs_lock);
 93     kfree(cd);
 94     return ERR_PTR(ret);
 95 }
 96 
 97 // 实际的设备号注销函数
 98 static struct char_device_struct *
 99 __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
100 {
101     struct char_device_struct *cd = NULL, **cp;
102     // 通过主设备号获取在设备号哈希链表数组中的索引
103     int i = major_to_index(major);
104 
105     mutex_lock(&chrdevs_lock);
106     // 在设备号哈希链表数组中搜索匹配的数据项,如果找到则在链表中删除该项,并返回搜索到数据项,
107     // 否则返回NULL
108     for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
109         if ((*cp)->major == major &&
110             (*cp)->baseminor == baseminor &&
111             (*cp)->minorct == minorct)
112             break;
113     if (*cp) {
114         cd = *cp;
115         *cp = cd->next;
116     }
117     mutex_unlock(&chrdevs_lock);
118     return cd;
119 }
120 
121 /**
122  * register_chrdev_region() - register a range of device numbers
123  * @from: the first in the desired range of device numbers; must include
124  *        the major number.
125  * @count: the number of consecutive device numbers required
126  * @name: the name of the device or driver.
127  *
128  * Return value is zero on success, a negative error code on failure.
129  */
130 // 注册某个范围的字符设备号
131 int register_chrdev_region(dev_t from, unsigned count, const char *name)
132 {
133     struct char_device_struct *cd;
134     dev_t to = from + count;
135     dev_t n, next;
136     // 如果字符设备号占用多个主设备号,则按照主设备号分别进行注册
137     for (n = from; n < to; n = next) {
138         next = MKDEV(MAJOR(n)+1, 0);
139         if (next > to)
140             next = to;
141         cd = __register_chrdev_region(MAJOR(n), MINOR(n),
142                    next - n, name);
143         if (IS_ERR(cd))
144             goto fail;
145     }
146     return 0;
147 fail:
148     to = n;
149     for (n = from; n < to; n = next) {
150         next = MKDEV(MAJOR(n)+1, 0);
151         kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
152     }
153     return PTR_ERR(cd);
154 }
155 
156 /**
157  * alloc_chrdev_region() - register a range of char device numbers
158  * @dev: output parameter for first assigned number
159  * @baseminor: first of the requested range of minor numbers
160  * @count: the number of minor numbers required
161  * @name: the name of the associated device or driver
162  *
163  * Allocates a range of char device numbers.  The major number will be
164  * chosen dynamically, and returned (along with the first minor number)
165  * in @dev.  Returns zero or a negative error code.
166  */
167 // 动态分配并注册某个范围的字符设备号,并通过第一个参数返回分配的第一个设备号
168 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
169             const char *name)
170 {
171     struct char_device_struct *cd;
172     cd = __register_chrdev_region(0, baseminor, count, name);
173     if (IS_ERR(cd))
174         return PTR_ERR(cd);
175     *dev = MKDEV(cd->major, cd->baseminor);
176     return 0;
177 }
178 
179 /**
180  * __register_chrdev() - create and register a cdev occupying a range of minors
181  * @major: major device number or 0 for dynamic allocation
182  * @baseminor: first of the requested range of minor numbers
183  * @count: the number of minor numbers required
184  * @name: name of this range of devices
185  * @fops: file operations associated with this devices
186  *
187  * If @major == 0 this functions will dynamically allocate a major and return
188  * its number.
189  *
190  * If @major > 0 this function will attempt to reserve a device with the given
191  * major number and will return zero on success.
192  *
193  * Returns a -ve errno on failure.
194  *
195  * The name of this device has nothing to do with the name of the device in
196  * /dev. It only helps to keep track of the different owners of devices. If
197  * your module name has only one type of devices it's ok to use e.g. the name
198  * of the module here.
199  */
200 // 动态创建字符设备结构,并注册字符设备号
201 int __register_chrdev(unsigned int major, unsigned int baseminor,
202               unsigned int count, const char *name,
203               const struct file_operations *fops)
204 {
205     struct char_device_struct *cd;
206     struct cdev *cdev;
207     int err = -ENOMEM;
208     // 注册字符设备号
209     cd = __register_chrdev_region(major, baseminor, count, name);
210     if (IS_ERR(cd))
211         return PTR_ERR(cd);
212     // 动态分配字符设备结构
213     cdev = cdev_alloc();
214     if (!cdev)
215         goto out2;
216     // 初始化字符设备结构
217     cdev->owner = fops->owner;
218     cdev->ops = fops;
219     kobject_set_name(&cdev->kobj, "%s", name);
220 
221     // 添加字符设备到字符设备映射表(kobj_map哈希链表数组)
222     err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
223     if (err)
224         goto out;
225 
226     cd->cdev = cdev;
227 
228     return major ? 0 : cd->major;
229 out:
230     kobject_put(&cdev->kobj);
231 out2:
232     kfree(__unregister_chrdev_region(cd->major, baseminor, count));
233     return err;
234 }
235 
236 /**
237  * unregister_chrdev_region() - return a range of device numbers
238  * @from: the first in the range of numbers to unregister
239  * @count: the number of device numbers to unregister
240  *
241  * This function will unregister a range of @count device numbers,
242  * starting with @from.  The caller should normally be the one who
243  * allocated those numbers in the first place...
244  */
245 // 注销指定范围的字符设备号
246 void unregister_chrdev_region(dev_t from, unsigned count)
247 {
248     dev_t to = from + count;
249     dev_t n, next;
250     // 如果字符设备号占用多个主设备号,则每个主设备号分别释放
251     for (n = from; n < to; n = next) {
252         next = MKDEV(MAJOR(n)+1, 0);
253         if (next > to)
254             next = to;
255         kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
256     }
257 }
258 
259 /**
260  * __unregister_chrdev - unregister and destroy a cdev
261  * @major: major device number
262  * @baseminor: first of the range of minor numbers
263  * @count: the number of minor numbers this cdev is occupying
264  * @name: name of this range of devices
265  *
266  * Unregister and destroy the cdev occupying the region described by
267  * @major, @baseminor and @count.  This function undoes what
268  * __register_chrdev() did.
269  */
270 // 注销字符设备所占用的字符设备号资源并删除字符设备,释放动态分配的char_device_struct结构资源
271 void __unregister_chrdev(unsigned int major, unsigned int baseminor,
272              unsigned int count, const char *name)
273 {
274     struct char_device_struct *cd;
275 
276     cd = __unregister_chrdev_region(major, baseminor, count);
277     if (cd && cd->cdev)
278         cdev_del(cd->cdev);
279     kfree(cd);
280 }
chrdev_region

      总结下,通过上面的代码分析可以知道,字符设备驱动源码主要实现两个方面的内容,其一、对设备号占位哈希链表数组的管理;其二、对设备结构cdev映射表(同样是一个哈希链表数组)的管理。同时还可以了解到,相同设备驱动程序的各个设备可以共用一个字符设备结构cdev,设备个数用cdev的成员count表示。相同的主设备号在满足一定条件下(即次设备号之间不相互重叠)可以表示不同的实际设备。

posted @ 2014-08-17 17:33  Bennnyzhao  阅读(1899)  评论(0编辑  收藏  举报