Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1363323
  • 博文数量: 118
  • 博客积分: 3888
  • 博客等级: 中校
  • 技术积分: 2940
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-10 18:15
个人简介

一看二做三总结

文章分类

全部博文(118)

分类: LINUX

2012-01-05 20:31:19

本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接。内容可任意使用,但对因使用该内容引起的后果不做任何保证。
作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net

前言

只看了两天的源码,分析的过程是源码加想象,可能与实际实现有一定差别。因为目的只是为了大致了解字符设备的实现原理。更重要的是,要彻底分析,必须要对文件系统有深入了解才行,显然不是我目前能力可以办到的。kernel作为一个整体,单独学任何一块都比较困难。文件系统等很难绕过去,而单独分析文件系统又会因为涉及到太多东西而无法完成。只能采用这种农村包围城市的方法,先从外围下手。


第一节 如何使用驱动

Linux中万物都是文件,设备也是这样。要使用一个设备,首先要为它建立一个文件,然后通过下面的方法操作它。

int fd;

char buf[100];

fd =open(“/dev/TestChar”, O_RDONLY, 0);

if (-1 == fd)

    return ERROR;

 

read(fd, buf, 100);

close(fd);

 如何创建一个设备文件呢?作为一个标准的文件,可以用open创建(实际上调用的是create)。如果是一个设备,则要通过mknod创建。这是以为设备驱动的创建方式是非标准的。假如通过opencreate创建文件,kernel默认会调用该文件所在目录属于的设备的驱动。

什么意思呢?

如果我创建一个文件名为“/myfile”的文件,因为是在根目录下,所以会用根目录所在设备的驱动。一般是磁盘驱动ext2ext3。如果我通过mount命令把一个u盘关在了“/mnt”上,再创建文件“/mnt/myfile”,则会调用u盘的驱动。可是现在需要创建的“/dev/TestChar”是新的字符设备,显然不能用 /dev”的方法创建。

对于这种情况,这用mknod创建,由用户指定创建时使用的驱动。

 

第二节 什么是mknod

现在我们再进一步讨论下mknod

乍看mknod,似乎有点奇怪。既然是创建文件,为何不叫mkdevfile,而叫mknod呢?

原因很简单,它真的是make node,而不是make device file

前面提到Linux万物都是文件,这有点让人混淆,应该说linux中外物都是inodefile则是inode的一层外衣。我们在任何一个目录下,调用ls看到的所有条目,包括子目录与文件,都对应着一个inodeInode中保存着文件名、属性、驱动等信息。严格意义上讲,代表着一个文件的file结构体此时可能还没有,只有调用open才会生成file结构体。File结构体把操作一个文件或设备所需的信息,加上传入的flagsmode等,保存起来,以备驱动调用。

mknod 的作用是创建inode节点,这是我们就明白了,open之前必须先用mknod创建inode节点。创建完inode节点用我们就可以用ls命令查看它了。open该节点后,则可以对inode对应的设备进行读写等操作。

mknod调用格式如下:

mknod Name { b | c } Major Minor

具体含义不讲了,需要说明的是他的参数,Major对应着一组驱动,也就是后面我们要写的东西。

创建普通文件的inode使用的是create,其内部也是调用的mknod,其Major号默认就是存储设备驱动。而我们要创建的inode就没这么幸运了。需要我们来指定Major号,也就是主设备号。

至此,我们虽然什么都没做,但也明白了创建字符设备驱动的唯一需要的就是一个主设备号。如何得到一个主设备号呢?

 

第三节 如何获得主设备号

先来看看系统原有的设备号,查看“/proc/devices”文件的内容,一般是这样的:

Character devices:

 1 mem

 2 pty

 3 ttyp

180 usb

Block devices:

 2 fd

 8 sd

第二列是驱动名字,第一列就是主设备号。如何添加新的设备号呢?既然是在proc目录下,显然不能直接读写。在用户态我们是无能为力的,因为主设备号只能在kernel中操作。添加新的主设备号就是驱动初始化中首先要做的事情。

 

Kernel中添加新设备号的函数是register_chrdev_region(),它可以把一个设备号添加到内核中,但前提是我们知道哪个设备号没人用。不要以为“/proc/devices”中没有的设备号就是可用的,很多设备号是预留的。为了避免这一问题,最好的办法是让kernel为我们分配一个。对应函数是alloc_chrdev_region()

完成上面的操作我们就可以在“/proc/devices”中查看对应的设备号了。

 

第四节 如何添加驱动

有了设备号,似乎一切都具备了,再回到刚才的mknod上去,填上主设备号(先不考虑次设备号),执行。我没试过,但我猜应该是失败的。为什么呢?因为还缺点东西。

到现在为止,我们都做了哪些工作?申请主设备号,创建节点,等着打开设备。

设备…….

似乎还没有给设备写驱动。这就好像是我们报名参加考试,却没有去考,考试结果当然是failedalloc_chrdev_region()是报名,mknod是结果,而写驱动就是答题的过程。Kernel中使用cdev_add为某个设备号添加具体驱动。其原型如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

dev是设备号,count是指可以创建几个这种类型的设备,也就是次设备号的范围。简单来讲主设备号表示驱动类型,次设备号表示设备实例。

P是该设备的数据维护结构。这个结构也挺复杂,因此kernel提供单独的函数cdev_init()对它进行初始化。P可以是我们自己申请,也可以是通过cdev_alloc()获得。好处是系统会在卸载模块时自动释放它,比较方便。

Cdev的结构比较复杂,但只有两项需要关心。一个是owner,一般填THIS_MODULE,另一个是ops,填真正的设备驱动,如openread等。

至此,可以看到我们要做的就是实现ops的各个函数。通常只需要实现openreadwriteioctl等几个函数。

还有一个涉及到实现细节的问题。

通常我们维护一个设备驱动需要很多数据,只靠一个统一的cdev结构体是不够的,因此真正实现时会创建一个自定义的结构体,如TestChar_devcdev结构体作为其中一个元素。

后见面我们会看到,open等函数只能得到cdev结构体的指针,我们需要通过kernel提供的container_of()来获取TestChar_dev的指针。

 

实现一个字符设备驱动,最主要的是实现下列方法:

int (*open) (struct inode *, struct file *);

int (*release) (struct inode *, struct file *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

这些函数的大部分参数都很简单,复杂些的只有两个,fileinodeInode我们只会用到inode->i_cdev,用于获得cdev结构体。获得后用container_of得到真正的TestChar_dev结构体首地址,存储到file->中。之后用实现readwrite时只会用到参数file

 

第五节 开发过程整理

完成上面的分析后,我们整理一下开发过程。

驱动开发:

1.       使用alloc_chrdev_region()register_chrdev_region()添加一个主设备号;

2.       自己申请或使用cdev_alloc()获得一块cdev结构;

3.       实现ops中需要调用的方法;

4.       cdev_init()初始化cdev结构;

5.       使用cdev_add()cdev结构体与新添加的主设备号关联起来;

增加节点:

Mknod /dev/TestChar c major 0

 

第六节 整理数据结构

对整体的数据结构作个总结。


Chdevs是维护char型设备的数组。Cdev_map是维护cdev数据结构的数组。my_cdev0my_cdev1是两种设备驱动。驱动中调用alloc_chrdev_region()时,会在chrdevs中申请一个设备号。调用cdev_add时,会调用kobj_map把它映射到cdev_map中,并把刚申请到设备号写进去。最后调用mknod,在文件系统中创建一个设备节点,用来挂载设备驱动程序。

本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接。内容可任意使用,但对因使用该内容引起的后果不做任何保证。
作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net
阅读(6175) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~