cdev描述字符设备。其中的dev_t成员定义了设备号,用下列宏可以获得主设备号和次设备号:
MAJOR(dev_t dev);
MINOR(dev_t dev);
使用下列宏可以通过主设备号和次设备号生成dev_t:
MKDEV(int major, int minor);
其中的成员file_operations定义了一组字符设备驱动提供给虚拟文件系统的接口函数。
内核提供了一组函数用于操作cdev结构体:cdev_init()用于初始化cdev成员,并建立cdev和file_operations之间的连接。cdev_alloc()用于动态申请一个cdev内存。cdev_add()和cdev_del()分别向系统添加和删除一个cdev。cdev_add()发生在模块加载函数中,dev_del()则发生在卸载函数中。
调用cdev_add()向系统注册字符设备之前要先调用register_chrdev_region()或alloc_chrdev_region()向系统申请设备号。前一个已知起始设备号,后一个则未知。相反,调用cdev_del()之后,要用unregister_chrdev_region释放设备号。
file_operations中的readdir()函数仅用于目录,设备节点不需要,设为null.
ioctl()提供设备相关控制命令的实现(既不是读也不是写操作)。调用成功时返回给调用程序一个非负值。内核本身可识别的部分命令,不用调用驱动中的ioctl()。如果有内核不识别且设备不提供的ioctl()函数,ioctl()系统调用将获得-EINVAL返回值。
mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行mmap()系统调用将获得-ENODEV返回值。此函数对帧缓冲等设备特别有意义。
poll()一般用于询问设备是否可被阻塞地立即读写。当询问的条件未触发时,用户空间进行select()和poll()系统调用将引起进程的阻塞。
aio_read(), aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。
字符设备的加载过程: 初始化cdev(用cdev_init) -> 获取字符设备号(register_chrdev_region()或alloc_chrdev_region()) -> 注册设备(cdev_add())
卸载过程: 注销设备(cdev_del()) -> 释放占有的设备号(unregister_chrdev_region())
字符设备的读、写、i/o控制函数的模板:
read函数用 copy_to_user;
write函数用 copy_from_user;
ioctl函数用 switch(cmd){ case ... ).
copy_to_user 与 copy_from_user如果复制成功返回0. 对于简单类型的内存复制,如char, int, long等,可以用简单的put_user(), get_user().
读写中__user是一个宏,表示其后的指针指向用户空间。
Ioctl()命令码组成为:8位设备类型(即幻数),8位序列号,2位方向字段, 13/或14位命令码的数据长度。内核定义了_IO(), _IOR(), _IOW()和_IOWR()4个宏来辅助生成命令。其作用是根据传入的type, nr(序列号字段), size(数据长度字段) 和宏名隐含的方向字段移位组合生成命令码。
内核预定义了一些I/O控制命令:FIOCLEX当exec()系统调用发生时自动关闭打开的文件;FIONCLEX清除前面设置的标志;FIOQSIZE获得一个文件或目录的大小,当用于设备文件时返回一个ENOTTY错误;FIONBIO修改在filp->flags中的O_NOBLOCK标志。这几个命令的幻数为"T".
结构体指针与结构体实例。
大多数linux驱动工程师都将文件的私有数据指向设备结构体。read()等函数通过private_data访问设备结构体。
container_of()通过结构体成员的指针找到对应结构体的指针,非常有用。第一个参数是结构体成员的指针(inode参数的i_cdev字段包含了先前设置的cdev结构(它在更大的一个封装中)),第二个参数为整个结构体的类型,第三个参数为传入的第一个参数即结构体成员的类型:contain_of(inode->i_cdev, struct globalmem_dev, cdev).
阅读(843) | 评论(0) | 转发(0) |