Chinaunix首页 | 论坛 | 博客
  • 博客访问: 250374
  • 博文数量: 101
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 95
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-12 12:35
文章分类

全部博文(101)

文章存档

2016年(5)

2015年(16)

2014年(37)

2013年(32)

2012年(8)

2011年(3)

我的朋友

分类: LINUX

2013-12-03 17:39:58

原文地址:linux字符设备那些事 作者:wmmy2008

------------------------------------------------
#个人浅见,如有问题敬请谅解!
#kernel version: 2.6.26
#Author: andy wang
-------------------------------------------------
   字符设备是一种常见的设备类型,在linux中字符设备是一种特殊的文件,如果要操作控制字符设备需要先将它注册到内核中。
 
注册字符设备
下面就来研究一下字符设备是如何注册到linux内核中的.
2.6近期的版本中注册一个字符设备一般调用函数register_chrdev(); 而这个函数只能指定主设备号不能指定次设备号,所以用这个函数最多只能注册256个字符设备。
下面还是看看注册一个字符设备的软件流程:
register_chrdev()->__register_chrdev_region()->cdev_add();
跟踪一下register_chrdev()代码:
265 int register_chrdev(unsigned int major, const char *name,
266                     const struct file_operations *fops)
267 {
268         struct char_device_struct *cd;
269         struct cdev *cdev;
270         char *s;
271         int err = -ENOMEM;
272
273         cd = __register_chrdev_region(major, 0, 256, name);
274         if (IS_ERR(cd))
275                 return PTR_ERR(cd);
276
277         cdev = cdev_alloc();
278         if (!cdev)
279                 goto out2;
280
281         cdev->owner = fops->owner;
282         cdev->ops = fops;
283         kobject_set_name(&cdev->kobj, "%s", name);
284         for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
285                 *s = '!';
286
287         err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
288         if (err)
289                 goto out;
290
291         cd->cdev = cdev;
292
293         return major ? 0 : cd->major;
294 out:
295         kobject_put(&cdev->kobj);
296 out2:
297         kfree(__unregister_chrdev_region(cd->major, 0, 256));
298         return err;
299 }
 
273行,根据主设备号(major)的值,次设备号为0 ,计算出它的散列值, 找到未占用的chrdevs散列头;来看看struct char_device_struct的定义就会明白了:
static struct char_device_struct {
         struct char_device_struct *next;
         unsigned int major;
         unsigned int baseminor;
         int minorct;
         char name[64];
         struct cdev *cdev;                 
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
 
可见chrdevs被定义为结构数组指针,所有注册到内核中的字符设备由它组织成一个散列表,而散列值最大为CHRDEV_MAJOR_HASH_SIZE=255
277行,动态分配一个cdev结构。
281-282行,初始化cdev,其中有一个重要的操作是我们定义的字符设备操作方法fops赋值给cdev->ops ,后面会分析VFS层是如何得到cdev->ops读写字符设备的。
287行,把cdev添加到cdev_map中的,在后面打开字符设备的操作中会根据设备号找到这个cdev.
291好,关联cdevchrdevs.
 
下面继续跟踪函数__register_chrdev_region():
94 static struct char_device_struct *
 95 __register_chrdev_region(unsigned int major, unsigned int baseminor,
 96                            int minorct, const char *name)
 97 {
 98         struct char_device_struct *cd, **cp;
 99         int ret = 0;
100         int i;
101
102         cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
103         if (cd == NULL)
104                 return ERR_PTR(-ENOMEM);
105
106         mutex_lock(&chrdevs_lock);
107
108         /* temporary */
109         if (major == 0) {
110                 for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
111                         if (chrdevs[i] == NULL)
112                                 break;
113                 }
114
115                 if (i == 0) {
116                         ret = -EBUSY;
117                         goto out;
118                 }
119                 major = i;
120                 ret = major;
121         }
122
123         cd->major = major;
124         cd->baseminor = baseminor;
125         cd->minorct = minorct;
126         strncpy(cd->name,name, 64);
127
128         i = major_to_index(major);
129
130         for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
131                 if ((*cp)->major > major ||
132                     ((*cp)->major == major &&
133                      (((*cp)->baseminor >= baseminor) ||
134                       ((*cp)->baseminor + (*cp)->minorct > baseminor))))
135                         break;
136
137         /* Check for overlapping minor ranges.  */
138         if (*cp && (*cp)->major == major) {
139                 int old_min = (*cp)->baseminor;
140                 int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
141                 int new_min = baseminor;
142                 int new_max = baseminor + minorct - 1;
143
144                 /* New driver overlaps from the left.  */
145                 if (new_max >= old_min && new_max <= old_max) {
146                         ret = -EBUSY;
147                         goto out;
148                 }
149
150                 /* New driver overlaps from the right.  */
151                 if (new_min <= old_max && new_min >= old_min) {
152                         ret = -EBUSY;
153                         goto out;
154                 }
155         }
156
157         cd->next = *cp;
158         *cp = cd;
159         mutex_unlock(&chrdevs_lock);
160         return cd;
161 out:
162         mutex_unlock(&chrdevs_lock);
163         kfree(cd);
164         return ERR_PTR(ret);
165 }
 
109-121行,如果主设备号major0,意味着需要动态分配一个主设备号。这段代码就是遍历chrdevs散列头找到没有占用的散列头.
123-126行,初始化刚分配的散列单元cd .
128行,计算主设备号对应的散列值.
130-135行代码其实就是解决,当散列值冲突时会根据主设备以及次设备号的大小排列散列元素.
138-154行,当注册的设备号已经存在时返回错误,确保每个字符设备设备号的唯一性。
 
打开字符设备
linux下字符设备是一种特殊文件,但对与用户来说它的操作和普通文件是一样。那么是什么帮我们隐藏这种文件的差异性呢?这就是VFS的功劳了。
先回忆一下以前文章中分析VFS操作文件的流程。在VFS创建文件索引节点时有这么一段代码:
{       ………..
switch (mode & S_IFMT) {
                   default:
                            init_special_inode(inode, mode, dev);
                            break;
                   …………..
}
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
         inode->i_mode = mode;
         if (S_ISCHR(mode)) {
                   inode->i_fop = &def_chr_fops;
                   inode->i_rdev = rdev;
         } else if (S_ISBLK(mode)) {
                   inode->i_fop = &def_blk_fops;
                   inode->i_rdev = rdev;
         } else if (S_ISFIFO(mode))
                   inode->i_fop = &def_fifo_fops;
         else if (S_ISSOCK(mode))
                   inode->i_fop = &bad_sock_fops;
         else
                   printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n",
                          mode);
}
当我们用mknod创建一个字符设备节点时,我们可以看到字符设备文件默认操作方法是def_chr_fops ,而且设备号保存在inode->i_rdev中。
那我们就来看看def_chr_fops是如何定义的:
const struct file_operations def_chr_fops = {
         .open = chrdev_open,
};
 
到这里我们知道了,打开一个字符设备其实就是默认调用函数chrdev_open();那么我们在注册字符设备时定义的文件操作方法是如何被调用到的呢?要回答这个问题我们就需要来看看chrdev_open() 函数了:
359 static int chrdev_open(struct inode *inode, struct file *filp)
360 {
361         struct cdev *p;
362         struct cdev *new = NULL;
363         int ret = 0;
364
365         spin_lock(&cdev_lock);
366         p = inode->i_cdev;
367         if (!p) {
368                 struct kobject *kobj;
369                 int idx;
370                 spin_unlock(&cdev_lock);
371                 kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
372                 if (!kobj)
373                         return -ENXIO;
374                 new = container_of(kobj, struct cdev, kobj);
375                 spin_lock(&cdev_lock);
376                 p = inode->i_cdev;
377                 if (!p) {
378                         inode->i_cdev = p = new;
379                         inode->i_cindex = idx;
380                         list_add(&inode->i_devices, &p->list);
381                         new = NULL;
382                 } else if (!cdev_get(p))
383                         ret = -ENXIO;
384         } else if (!cdev_get(p))
385                 ret = -ENXIO;
386         spin_unlock(&cdev_lock);
387         cdev_put(new);
388         if (ret)
389                 return ret;
390         filp->f_op = fops_get(p->ops);
391         if (!filp->f_op) {
392                 cdev_put(p);
393                 return -ENXIO;
394         }
395         if (filp->f_op->open) {
396                 lock_kernel();
397                 ret = filp->f_op->open(inode,filp);
398                 unlock_kernel();
399         }
400         if (ret)
401                 cdev_put(p);
402         return ret;
403 }
366-383行,首先是判断inode->i_cdev是否定义,如果没有定义那么就需要根据inode->i_rdev字符设备号调用函数kobj_lookup()找到对应的kobj,然后调用函数container_of(kobj, struct cdev, kobj)找到cdev ,从上面注册字符设备的流程可以得知cdev->ops定义了字符设备的操作方法。所以找到了cdev我们就找到了这个字符设备的操作方法了。
390行,取出字符设备文件的操作方法赋给filp->f_op ,到这里这个文件的读写方法就可以找到了。
395-397行,如果定义了open操作,那么就调用open回调函数打开文件。也就是我们注册字符设备时定义的文件open回调函数了。
那么自然VFS读写文件操作时,也会调用我们定义的读写操作回调函数了.;如果不清楚VFS读写文件的流程,请参见以前的文章《VFS读写文件操作》。
 
最后我们来看看一个函数:
67 #ifdef CONFIG_PROC_FS
 68
 69 void chrdev_show(struct seq_file *f, off_t offset)
 70 {
 71         struct char_device_struct *cd;
 72
 73         if (offset < CHRDEV_MAJOR_HASH_SIZE) {
 74                 mutex_lock(&chrdevs_lock);
 75                 for (cd = chrdevs[offset]; cd; cd = cd->next)
 76                         seq_printf(f, "%3d %s\n", cd->major, cd->name);
 77                 mutex_unlock(&chrdevs_lock);
 78         }
 79 }
 80
 81 #endif
 
这个函数就是把内核中注册的所有字符设备都打印出来,其实我们平时在查看内核注册的设备时用 
cat /proc/devices时就会调用到这个函数,关于proc文件系统和devices文件的读写流程已经在以前的文章《procfs文件系统》中已经分析过了,这里就不多说了;现在只是回头望月。
 
  好了,到这里我们就已经把字符设备的注册和操作方法都介绍完了,是不是有点“拨开云雾现青天”的感觉呢!!!
 
 
阅读(492) | 评论(0) | 转发(0) |
0

上一篇:Linux设备模型之platform总线

下一篇:data

给主人留下些什么吧!~~