字符驱动
系统整体工作原理:
(1)应用层->API->设备驱动->硬件;
(2)API:open、read、write、close等;
(3)驱动源码中提供真正的open、read、write、close等函数实体
file_operations结构体(另外一种为attribute方式后面再讲):(1)元素主要是函数指针,用来挂接实体函数地址;(2)每个设备驱动都需要一个该结构体类型的变量;(3)设备驱动向内核注册时提供该结构体类型的变量。
register_chrdev注册函数
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
(1)作用,驱动向内核注册自己的file_operations结构体,注册的过程其实主要是将要注册的驱动的信息存储在内核中专门用来存储注册的字符设备驱动的数组中相应的位置
(2)参数:设备号major--major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备,内核如果成功分配就会返回分配的主设备号;如果分配失败会返回负数
(3)inline和static
inline:当把函数定义在头文件里面的时候,如果你这个头文件被两个及两个以上的函数包含的时候,在链接的时候就会出错。inline的作用就是解决这个问题,原地展开并能够实现静态检查。另外一个原因是函数本身就比较短。
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> // file_operations 没写会报错:xxx has initializer but incomplete type
#define MYNMAJOR 200
#define MYNAME "test_chrdev"
//file_operations结构体变量中填充的函数指针的实体,函数的格式要遵守
static int test_chrdev_open(struct inode *inode, struct file *file)
{
//这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
//但是现在我们暂时写不了那么多,所以就就用一个printk打印个信息来做代表
printk(KERN_INFO "test_module_open\n");
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
return 0;
}
//自定义一个file_operations结构体变量,并填充
static const struct file_operations test_module_fops = {
.owner = THIS_MODULE, //惯例,所有的驱动都有这一个,这也是这结构体中唯一一个不是函数指针的元素
.open = test_chrdev_open, //将来应用open打开这个这个设备时实际调用的函数
.release = test_chrdev_release, //对应close,为什么不叫close呢?详见后面release和close的区别的讲解
};
/*********************************************************************************/
// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
//在module_init宏调用的函数中去注册字符设备驱动
int ret = -1; //register_chrdev 返回值为int类型
ret = register_chrdev(MYNMAJOR, MYNAME, &test_module_fops);
//参数:主设备号major,设备名称name,自己定义好的file_operations结构体变量指针,注意是指针,所以要加上取地址符
//完了之后检查返回值
if(ret){
printk(KERN_ERR "register_chrdev fial\n"); //注意这里不再用KERN_INFO
return -EINVAL; //内核中定义了好多error number 不都用以前那样return -1;负号要加 !!
}
printk(KERN_ERR "register_chrdev success...\n");
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
//在module_exit宏调用的函数中去注销字符设备驱动
//实验中,在我们这里不写东西的时候,rmmod 后lsmod 查看确实是没了,但是cat /proc/device发现设备号还是被占着
unregister_chrdev(MYNMAJOR, MYNAME); //参数就两个
//检测返回值
......
return 0;
}
module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");
应用程序如何调用驱动
驱动设备文件的创建:(1)何为设备文件:用来索引驱动;(2)设备文件的关键信息是:设备号 = 主设备号 + 次设备号;(3)使用mknod创建设备文件:mknod /dev/xxx c 主设备号 次设备号
(c表示要创建的设备文件类型为字符设备);(4)使用ls xxx -l
去查看设备文件,就可以得到这个设备文件对应的主次设备号。
注:不可能总用mknod来创建设备文件,能否自动生成和删除设备文件?linux内核有一种机制--udev(嵌入式中用的是mdev)后面细讲
一个简单的应用程序示例app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //man 2 open 查看头文件有哪些
#define FILE "/dev/test" // 刚才mknod创建的设备文件名 双引号不要漏
int main(void)
{
int fd = -1;
fd = open(FILE, O_RDWR);
if (fd < 0){
printf("open %s error.\n", FILE);
return -1;
}
printf("open %s success..\n", FILE);
// 读写文件
...
// 关闭文件
close(fd);
return 0;
}
最后编辑:SteveChen 更新时间:2025-05-12 10:07