一、字符设备驱动概念
1. 什么是字符设备驱动?
字符设备是 Linux 驱动中最基本的一类设备驱动,按字节流进行读写操作,数据读写有先后顺序。常见的字符设备包括LED灯、按键、IIC、SPI、LCD等。字符设备驱动就是为这些设备编写的驱动程序。
2. Linux应用程序如何调用驱动程序
在 Linux 中,一切皆为文件,驱动加载成功后,会在 /dev
目录下生成一个相应的文件,应用程序通过操作该文件即可实现对硬件的操作。例如 /dev/led
是一个 LED 灯的驱动文件,应用程序可以使用 open
函数打开文件 /dev/led
,使用 close
函数关闭文件 /dev/led
。要点亮或关闭 LED,可以使用 write
函数向该驱动写入数据;要获取 LED 状态,可以使用 read
函数从驱动读取状态。
3. 系统调用与驱动函数
应用程序运行在用户空间,驱动运行在内核空间,用户空间不能直接操作内核空间。因此,通过系统调用的方式实现用户空间与内核空间的交互。每个系统调用在驱动中都有对应的驱动函数。驱动程序中的 file_operations
结构体定义了这些操作函数:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
// 其他函数省略
};
4. file_operations
结构体常用函数
owner
: 指向拥有该结构体的模块的指针,一般设置为THIS_MODULE
。read
: 用于读取设备文件。write
: 用于向设备文件写入数据。open
: 用于打开设备文件。release
: 用于释放(关闭)设备文件。
二、字符设备驱动开发步骤
1. 驱动模块的加载和卸载
Linux 驱动有两种运行方式:编译进内核或编译成模块。模块的加载和卸载注册函数如下:
module_init(xxx_init); // 注册模块加载函数
module_exit(xxx_exit); // 注册模块卸载函数
驱动模块加载和卸载模板:
static int __init xxx_init(void) {
// 入口函数的具体内容
return 0;
}
static void __exit xxx_deinit(void) {
// 出口函数的具体内容
}
module_init(xxx_init);
module_exit(xxx_deinit);