字符设备驱动模板
在字符设备驱动模块加载函数中应该实现设备号的申请和 cdev
的注册, 而在卸载函数中应实现设备号的释放和 cdev
的注销。
工程师通常习惯将设备定义为一个设备相关的结构体,其包含该设备所涉及的 cdev、私有数据及信号量等信息。常见的设备结构体、模块加载和卸载函数形式如代码清单所示
//设备结构体
struct xxx_dev_t
{
struct cdev cdev;
...
} xxx_dev;
//设备驱动模块加载函数
static int _ _init xxx_init(void)
{
...
cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化 cdev
xxx_dev.cdev.owner = THIS_MODULE;
//获取字符设备号
if (xxx_major)
{
register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
}
else
{
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
}
ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备
...
}
/*设备驱动模块卸载函数*/
static void _ _exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号
cdev_del(&xxx_dev.cdev); //注销设备
...
}
file_operations
结构体中成员函数是字符设备驱动与内核的接口,是用户空间对 Linux 进行系统调用最终的落实者。大多数字符设备驱动会实现 read()
、 write()
和 ioctl()
函数,常见的字符设备驱动的这 3 个函数的形式如代码清单所示
/* 读设备*/
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t*f_pos)
{
...
copy_to_user(buf, ..., ...);
...
}
/* 写设备*/
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
...
copy_from_user(..., buf, ...);
...
}
/* ioctl 函数 */
int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
...
switch (cmd)
{
case XXX_CMD1:
...
break;
case XXX_CMD2:
...
break;
default:
/* 不能支持的命令 */
return - ENOTTY;
}
return 0;
}
设备驱动的读函数中, filp 是文件结构体指针, buf 是用户空间内存的地址,该地址在内核空间不能直接读写, count 是要读的字节数, f_pos 是读的位置相对于文件开头的偏移
设备驱动的写函数中, filp 是文件结构体指针, buf 是用户空间内存的地址,该地址在内核空间不能直接读写, count 是要写的字节数, f_pos 是写的位置相对于文件开头的偏移
由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_from_user()
完成用户空间到内核空间的复制,函数 copy_to_user()
完成内核空间到用户空间的复制
I/O 控制函数的 cmd 参数为事先定义的 I/O 控制命令,而 arg 为对应于该命令的参数
在字符设备驱动中,需要定义一个 file_operations 的实例,并将具体设备驱动的函数赋值给file_operations 的成员
struct file_operations xxx_fops =
{
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
.ioctl = xxx_ioctl,
...
};
上述 xxx_fops 在代码第 11 行的 cdev_init(&xxx_dev.cdev, &xxx_fops)
的语句中被建立与 cdev的连接
下图所示为字符设备驱动的结构、字符设备驱动与字符设备以及字符设备驱动与用户空间访问该设备的程序之间的关系
上一篇: Android工具类: 基于Zxing的二维码生成和展示
下一篇: 字符设备驱动