|
在 Linux 系统中,字符设备驱动由如下几个部分组成。
1、字符设备驱动模块加载与卸载函数
在字符设备驱动模块加载函数中应该实现设备号的申请(也可以事先指定)和 cdev 的注册,而在卸载函数中实现设备号的释放和 cdev 的注销。
通常的,习惯将设备定义为一个设备相关的结构体,其包含该设备所涉及的 cdev、私有数据及信号量等信息。常见的设备结构体、模块加载和卸载函数形式如下代码所示:
/*设备结构体*/
struct xxx_dev_t {
struct cdev cdev;
...
}
/*设备驱动模块加载函数*/
static int __init xxx_init(void) {
...
/*初始化 cdev*/
cdev_init (&xxx_dev.cdev, &xxx_fops);
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);
...
} 2、字符设备驱动的 file_operations 结构体中的成员函数
file_operations 结构体中成员函数是字符设备驱动与内核的接口,是用户空间对 Linux 进行系统调用的最终落实者。大多数字符设备驱动会实现 read()、write() 和 ioctl() 函数,常见的字符设备驱动的这 3 个函数形式如下代码所示:/*读设备*/
ssize_t xxx_read (struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{
...
copy_to_user (buf, ..., ...);
...
}
/*写设备*/
ssize_t xxx_write (struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
...
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_ops 是读的位置相对于文件开头的偏移。
写函数与读函数类似。
由于内核空间与用户空间的内存不能直接互访,因此借助函数 copy_from_user() 完成用户空间到内核空间的复制,函数 copy_to_user() 完成内核空间到用户空间的复制。 |
|