字符设备驱动将会分为三个部分,Part1:宏观上了解驱动模型,Part2:具体分析其中重要的三个数据结构,文件操作集file_operations,文件file以及inode结构。Part3:用一个简单的实例来演示字符设备驱动的实现
字符设备是面向字节流的方式,一个字节一个字节传输数据的设备,通常是按照先后顺序传递,常见的字符设备有鼠标、键盘、串口等设备。
对字符设备的访问是通过文件系统内的设备名称进行的。设备文件为文件系统树的节点,通常位于/dev目录下。
1.主设备号与次设备号
主设备号标示设备对应的驱动程序,例如/dev/rtc0由驱动程序251管理
次设备号由内核使用,用于正确确定设备文件所指的设备
2.设备号的内部表达
在内核中,dev_t类型(在<linux/types.h>中定义)用来保存设备号——包括主设备号和次设备号
typedef unsigned int __u32;
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
在<linux/kdev_t.h>中定义的宏,用于获取dev_t的主设备号和次设备号
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
dev_t是一个32位的数,其中的12位用来表示主设备号,其余20位用来标示次设备号。如果知道主设备号和次设备号可以转换成dev_t类型
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
下面我们整体上认识一下字符设备驱动模型组成结构,随后再一个个分析
3.分配和释放设备号
在<linux/fs.h>中声明,静态申请设备号的方式:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
函数参数说明
@from: the first in the desired range of device numbers; must include the major number.
@count: the number of consecutive device numbers required
@name: the name of the device or driver.
函数返回值:
Return value is zero on success, a negative error code on failure.
在<linux/fs.h>中声明,动态分配备号的方式:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
函数参数说明:
@dev: output parameter for first assigned number
@baseminor: first of the requested range of minor numbers
@count: the number of minor numbers required
@name: the name of the associated device or driver
函数返回值:
Allocates a range of char device numbers. The major number will be chosen dynamically, and returned (along with the first minor number)
in @dev. Returns zero or a negative error code.
不论采用哪种方式分配的设备号,都应该在不再使用的时候释放这些设备号,及释放设备号函数
void unregister_chrdev_region(dev_t from, unsigned count)
函数参数说明:
@from: the first in the range of numbers to unregister
@count: the number of device numbers to unregister
函数返回值:
This function will unregister a range of @count device numbers, starting with @from. The caller should normally be the one who allocated those numbers in the first place...
4.字符设备的注册
在用户空间可访问上述设备号之前,驱动程序需要将设备号和内部函数连接起来,这些内部函数用来实现设备的操作。内核使用struct cdev结构来表示字符设备。在<linux/cdev.h>中使用下面代码来获取一个cdev 结构
struct cdev *cdev_alloc(void)
这时,初始化已分配到的cdev结构,将cdev与file_operations关联起来
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
函数参数说明:
@cdev: the structure to initialize
@fops: the file_operations for this device
cdev结构设置后,需要添加到系统,告诉内核该结构的信息:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
函数参数说明:
@p: the cdev structure for the device
@dev: the first device number for which this device is responsible
@count: the number of consecutive minor numbers corresponding to this device
函数返回值:
cdev_add() adds the device represented by @p to the system, making it live immediately. A negative error code is returned on failure.
注意:这个调用可能失败,如果它返回一个负的错误码,则设备不会添加到系统中。
最后,将cdev结构从系统中删除:
void cdev_del(struct cdev *p)
函数参数说明:
@p: the cdev structure to be removed
本章小结:
在Linux字符设备驱动中:
a.模块加载通过register_chrdev_region()或alloc_chrdev_region()来静态或动态获取设备号
b.通过cdev_alloc()获取设备结构
通过cdev_init()初始化cdev结构,建立cdev与file_operations间的连接
通过cdev_add()注册,向内核注册一个cdev结构,建立cdev与dev_t间的联系
c.模块卸载cdev_del()来注销cdev,通过unregister_chrdev_region()来释放设备号
用户空间访问该字符设备:
通过系统调用,如open、read、write来实现字符设备驱动在file_operations的接口