目录
一、字符设备简介
字符设备就是一个一个字节,按照字节流进行读写的设备,读写数据分先后顺序的。常用的led、按键,i2c,spi,lcd等都是字符设备。
应用程序调用驱动程序如图所示:
当编好的驱动加载成功后,就会在/dev目录下生成一个相应的文件,如/dev/xxx,应用程序对这个文件进行相应的操作即可实现对硬件的操作。
应用程序运行在用户空间,驱动运行在内核空间,因为用户空间不能对内核空间进行操作,必须使用一个叫做“系统调用”的方法实现从用户空间陷入到内核空间,C库中提供的open、close、write、read等函数就属于这种“系统调用”。具体的流程如下:
在linux内核文件include/linux/fs.h中file_operations的结构体,就是内核驱动操作函数的集合。file_operation常用的重要函数:
owner | 拥有该结构体的模块的指针,一般是THIS_MODULE |
llseek | 修改文件当前的读写位置 |
read | 读取设备文件 |
write | 向设备文件写入数据 |
poll | 查询设备是否可以进行非阻塞的读写 |
unlocked_ioctl | 提供对设备的控制功能,应用程序中使用ioctl函数 |
compat_ioctl | 与unlocked_ioctl函数功能一样 |
mmap | 用于将设备的内存映射到用户空间,一般帧缓冲设备会使用此函数 |
open | 打开设备文件 |
release | 关闭设备文件,与应用中的close函数一致 |
fasync | 用于刷新待处理的数据,将缓冲区中的数据刷新到磁盘 |
aio_fsync | 与fsync功能类似,属于异步刷新待处理的数据 |
字符设备驱动开发中,最主要的工作就是实现这个结构体中的函数,具体实现哪些看具体的硬件要求,一般的open、close、write、read等是要实现的。
二、字符设备驱动开发步骤
在linux中,驱动开发要按照规定的框架来编写,一般有:
①、驱动的加载和卸载
②、设备注册和注销
③、实现设备的具体操作
④、添加license信息
2.1、驱动模块的加载和卸载
linux驱动有两种运行模式:将驱动编译至内核中,内核启动后自动运行该驱动程序;将驱动编译成模块(扩展名为.ko),内核启动后用“insmod”加载驱动模块。
推荐使用modprobe命令加载模块,因为insmod命令不能解决依赖关系,modprobe会分析模块的依赖关系,然后将所有依赖模块都加载到内核中,更智能一点。
在调试阶段,一般是先编译成模块,没问题后再编译进内核,这样做的好处是不用编译整个linux代码,也不用重启整个系统,只需要加载卸载驱动模块即可。
模块的加载和卸载函数如下,
module_init(xxx_init); //注册模块加载函数
modele_exit(xxx_exit); //注册模块卸载函数
module_init函数用来向linux内核注册一个模块加载函数,参数xxx_init就是需要注册的具体函数,当使用“insmod”命令加载驱动的时候,xxx_init就会被调用。module_exit函数用来向linux内核注册一个模块卸载函数,参数xxx_exit就是需要注册的具体函数,当使用“rmmod”命令卸载具体的驱动时候,xxx_exit就会被调用。
模板如下:
/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容,需实现 */
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 出口函数具体内容,需实现 */
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);
2.2、字符设备的注册和注销
驱动模块加载成功后需要注册字符设备,卸载驱动模块的时候需要注销字符设备,函数原型如下:
/*
@major:主设备号
@name:设备名字
@fops:设备操作函数集合
*/
static inline int register_chrdev(unsigned int major,
const char *name,
const struct file_operations *fops);
/*
@major:主设备号
@name:设备名字
*/
static inline void unregister_chrdev(unsigned int major,
const char *name);
设备号要用系统中没用掉的,用cat /proc/devices可以查看当前已经使用掉的设备号。
设备号链接如下:
一般字符设备的注册在驱动模块的入口函数中进行,注销在出口函数中进行, 模板如下:
static struct file_operations fops;
static int __init xxx_init(void)
{
register_chrdev(200, "chrtest", &fops);
}
static void __exit xxx_exit(void)
{
unregister_chrdev(200, "chrtest");
}
module_init(xxx_init);
module_exit(xxx_exit);
2.3、实现设备的具体操作函数
实现file_operations机构体中的函数 ,具体实现哪些看需求。一般包括open、close、write、read。
模板如下:
static int chrtest_open(struct inode *inode, struct file *filp)
{
//用户代码
}
static ssize_t chrtest_read(struct file *filp, __user char *buf, size_t count,loff_t *ppos)
{
//用户代码
}
static ssize_t chrtest_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
//用户代码
}
static int chrtest_release(struct inode *inode, struct file *filp)
{
//用户代码
}
static const struct file_operations chrtest_fops = {
.owner = THIS_MODULE,
.open = chrtest_open,
.read = chrtest_read,
.write = chrtest_write,
.close = chrtest_release,
};
static int __init xxx_init(void)
{
int ret = 0;
ret = register_chrdev(200, "chrtest", &chrtest_fops);
if (ret < 0)
{
//注册失败处理
}
return 0;
}
static void __exit xxx_exit(void)
{
unregister_chrdev(200, "chrtest");
}
module_init(xxx_init);
module_exit(xxx_exit);
2.4、添加license信息
licence是必须添加的,否则编译会报错,作者信息可以不添加。
MODULE_LICENSE(); //添加模块license信息
MODULE_AUTHOR(); //添加模块作者信息
//eg:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZK");