问题:应用程序如何通过一个字符设备文件找到对应的字符设备?
本文主要分析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 }
该函数在系统初始化启动时被调用。该函数首先分配一个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 };
该结构主要包含一个类型为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 }
向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 }
当完成上述两项工作后,当系统调用打开设备时通过查找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 };
该结构是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 }
其它与字符设备结构相关的函数说明如下:
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 }
以上描述了与字符设备结构相关的处理操作与功能,另外字符设备驱动还需要与设备号相关的功能,包括设备号的申请分配,释放及管理。以下描述主要与设备号相关功能的代码。
首先看下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];
设备号相关功能代码如下:
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 }
总结下,通过上面的代码分析可以知道,字符设备驱动源码主要实现两个方面的内容,其一、对设备号占位哈希链表数组的管理;其二、对设备结构cdev映射表(同样是一个哈希链表数组)的管理。同时还可以了解到,相同设备驱动程序的各个设备可以共用一个字符设备结构cdev,设备个数用cdev的成员count表示。相同的主设备号在满足一定条件下(即次设备号之间不相互重叠)可以表示不同的实际设备。