6.1 linux字符设备驱动结构
6.1.1 cdev结构体
- cdev结构体描述一个字符设备
struct cdev {
struct kobject kobj; //内嵌的kobject对象
struct module *owner; //所属模块
const struct file_operations *ops;//文件操作结构
struct list_head list;
dev_t dev; //设备号,int 类型
unsigned int count;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- cdev的dev_t成员定义了设备号,高12位为主设备号,低20位为次设备号
- 获取主、次设备号:
- MAJOR(dev_t dev)
- MINOR(dev_t dev)
- 主次设备号生成dev_t:MKDEV(int major,int minor)
- 获取主、次设备号:
- cdev结构体相关的操作函数
void cdev_init(struct cdev *, const struct file_operations *);//建立cdev和file_operation之间的连接
struct cdev *cdev_alloc(void); //动态申请一个cdev内存
void cdev_put(struct cdev *p); //释放
int cdev_add(struct cdev *, dev_t, unsigned); //注册设备
void cdev_del(struct cdev *);//注销设备
- 1
- 2
- 3
- 4
- 5
6.1.2 分配和释放设备号
- 在注册设备之前应该先申请设备号
- register_chrdev_region()用于已知设备号
- register_chrdev_region()用于动态申请
int register_chrdev_region(dev_t from,unsigned count,const char *name)
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)
- 1
- 2
- 3
- 释放设备号
void unregister_chrdev_region(dev_t from,unsigned count)
- 1
- 注册与注销字符设备顺序关系如下:
register_chrdev_region()-->cdev_add() //此过程在加载模块中
cdev_del()-->unregister_chrdev_region() //此过程在卸载模块中
- 1
- 2
6.1.3 字符设备驱动组成
- 字符设备的模块加载与卸载函数
- 字符设备驱动的file_operation结构体中的成员函数,大多数实现以下函数
- read()
- write()
- ioctl()
- copy_from_user():用户空间缓冲区到内核空间
- copy_to_user():内核空间到用户空间缓冲区
- 返回0:成功
- 返回正数:不能被复制的字节数
- 负数:操作失败
- 复制的内容是简单的类型,如int、char、long等,则可以用以下函数
- put_user、get_user
- 上述函数中_user是一个宏,表示其后的指针指向用户空间
- 内核空间访问用户空间缓存时需要合法性检查
- access_ok(type, add, size):确认缓存区确实属于用户空间
- ioctl函数
- 设备类型字段为一个幻数,可以使0-0xff的值,内核中的ioctl-number.txt有推荐的已经存在的幻数,新设备区应该避免与其冲突
- 方向字段表示数据传输的方向
- _IOC_NONE:无数据传输
- _IOC_READ:读
- _IOC_WRITE:写
- _IOC_READ|_IOC_WRITE:双向
设备类型 | 序列号 | 方向 | 数据尺寸 |
---|---|---|---|
8位 | 8位 | 2位 | 13/14位 |
预定义命令:内核中预定了一些I/O控制命令,如果某设备中抱哈没了与预定义命令一样的命令码,则这些命令会作为预定义命令被内核处理而不是被设备驱动处理
- FIOCLEX:通知内核当exec()系统调用发生时,自动关闭打开的文件
- FIONCLEX:与FIOCLEX相反,清除由FIOCLEX命令设置的标志
- FIOQSIZE:获得一个文件和目录的大小,当用于设备文件时返回一个ENOTTY错误
- FIONBIO:修改在filp->f_flags中的O_NONBLOCK标志