Linux字符设备驱动之概述篇

.概述:

1.Linux中有一句哲学“Linux下皆文件”。

设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。

但是设备文件和普通文件还是又差别的。

那么设备和普通文件之间又有什么区分呢?

先看看两个图:

普通文件:

-rw-r--r--  1 stella stella   3699 2011-05-10 16:02 my_USBTMCAPP.c

-rwxr-xr-x  1 stella stella   8763 2011-05-08 11:27 tiger

-rw-r--r--  1 stella stella    441 2011-05-08 11:27 tiger.c

设备文件:

crw-------   1 root root    252,   4 2011-05-11 16:42 usbmon4

crw-------   1 root root    252,   5 2011-05-11 16:42 usbmon5

crw-rw----   1 root tty       7,   0 2011-05-11 16:42 vcs

crw-rw----   1 root tty       7,   1 2011-05-11 16:42 vcs1

1>访问权限之前的字母是bc,分别表示块设备和字符设备。

2>设备文件没有文件长度,而增加了另外的两个值,分别是主设备号和从设备号。二者共同形成一个唯一的号码,内核可由此查找对应的设备驱动程序。

3>之所以给设备文件分配名称,是因为用户更容易记忆符号名而不是数字,但名称无法表示设备文件的实际功能,这主要是通过主从设备号表示一个设备的,设备文件所处的目录也与其功能不相干。

尽管如此,命名设备文件时仍然采用了一中标准方法:

mknod 用于创建设备文件

2.内核采用主从设备号来标识匹配的驱动程序

为什么要采用两个号码来标识驱动程序呢?

1>首先,系统可能包含几个同样类型的设备,由同一个设备驱动程序管理(将同样的代码多次加载到内核也没有意义)

2>其次,可以将同类设备合并起来,便于插入到内核的数据结构中进行管理

3.Linux设备驱动程序的分类:

1>Linux设备驱动程序分为字符设备驱动(无缓冲且只能顺序存取),块设备驱动程序(有缓冲且可以随机存取)。每个字符设备和块设备都必须有主次设备号主设备号相同的设备是同类设备(使用同一驱动程序)

(1)块设备:系统中能够随机(不需要按顺序)访问固定大小数据片(chunk)的设备被称作块设备;他们都是以安装文件系统的方式使用的

(2)字符设备:字符设备按照字符流动的方式被有序访问

2>这些设备中,有些设备是对实际物理硬件的抽象,而有些设备则是内核自身提供的功能(不依赖于特定的物理硬件,又称为“虚拟设备”)

(1)每个设备在/dev目录下都有一个对应的文件(节点)

可以用下面的命令进行查看

cd  /dev

ls  -al

日期的前两列给出了对应设备的主设备号和次设备号

(2)可以通过cat /proc/devices命令查看当前已经加载的设备驱动程序的主设备号,第一列为主设备号,第二列为设备名

3>块设备和字符设备的主设备号可能是相同的。因此,除非同时指定设备号和设备类型(块设备/字符设备),否则找到的驱动程序可能不是唯一的

4.主从设备号

1>在内核中,dev_t类型用来保存设备编号(包括主设备号和次设备号),dev_t是一个32位的数,12位表示主设备号,20为表示次设备号

(1)主设备号 = MAJORdev_t dev

(2)次设备号 = MINORdev_t dev

(3)设备编号 = MKDEVint major,int minor

2>主设备号是与驱动对应的概念,同一类设备一般使用相同的主设备号,不同类的设备一般使用不同的主设备号。因为同一驱动可支持多个同类设备,因此用次设备号来描述使用该驱动的设备的序号,序号一般从0开始

5.MKDEV()宏的实现

#define MKDEV(ma,mi)   (((ma) << MINORBITS)|(mi))

#define MKDEV(ma,mi)   ((ma)<<8 | (mi))

获取设备在设备表中的位置

6.dev_t是无符号长整型号

1>typedef u_long dev_t;

2>typeddef unsigned long u_long;

7.分配和释放设备号:

(1)int register_chrdev_region(dev_t first,unsigned int count,char *name);

(2)int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);

(3)void unregister_chrdev_region(dev_t first,unsigned int count);

1>register_chardev_region函数用于已知起始设备号的情况;在静态申请时,如果主设备号没有和系统中某个字符设备的主设备号 重复,则此函数肯定会成功返回;如果系统中某个已注册的字符设备的主设备号与申请的主设备号相同,则内核还需要检查这两个设备的次设备号范围有没有重合,若有重合部分,则register_chrdev_region返回-EBUSY,表示系统正忙,申请的设备号已被占用;若无重合部分,则register_chrdev_region返回0,表示成功

2>alloc_chrdev_region()用于设备号未知,向系统动态申请设备号的情况。动态分配设备号,是指在驱动程序中通过调用此函数,系统将为驱动程序动态分配一个主设备号,将分配到的主设备号与参数baseminor组合成一个设备号,通过输出参数dev返回给用户

3>alloc_chrdev_region()register_chrdev_region()对比的优点在于它会自动避开设备号重复的冲突,但是当用alloc_chrdev_region动态申请设备号时,在申请完后,要通过major=MMOR(dev)获取主设备号;并且如果用户使用静态申请设备号,只要用户申请的主设备号在0~2^12-1之间,次设备号在0~2^20-1之间,并且设备号不冲突,内核就会成功注册该设备号;而如果用户使用了动态分配设备号,内核为用户分配的主设备号只会在1~254之间,如果这254个设备号全部被占用,则动态分配设备号会失败,但是常用的设备只有三十多个,所以基本上不用担心动态分配的时候内核会返回失败

4>在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用以释放原先申请的设备号

-----------------------------------------------------------------------------------------

关于register_chrdev_region()函数系列的具体实现可以参看《Linux字符设备驱动之register_chrdev_region

-----------------------------------------------------------------------------------------

8. 注册字符设备

在获得了设备号范围之后,需要将设备添加到字符设备数据库中,以激活设备。这需要用cdev_init函数初始化一个struct cdev的实例,然后调用cdev_add函数

(1)void cdev_init(struct cdev *cdev,struct file_operation *fops);

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

(3)void  cdev_del(stuct cdev *dev)

1> cdev_init()函数用于初始化cdev的成员,并建立cdevfile_operations之间的连接

2>cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。

3>先要申请设备号,才能注册字符设备,顺序不能乱

-----------------------------------------------------------------------------------------

关于cdev_init ()函数系列的具体实现可以参看《Linux字符设备驱动之cdev_init ()

-----------------------------------------------------------------------------------------

9.在内核空间和用户空间之间拷贝数据使用

(1)unsigned long copy_to_user(void _user* to,const void *from,unsigned long count);

(2)unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);

由于内核空间与用户空间的内存不能直接互访问,因此借助函数copy_from_user()完成用户空间到内核空间的复制。函数copy_to_user()完成内核空间到用户空间的复制