一 简介
字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的 LED、按键、 IIC、SPI, LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。为了方便开发,做一个简单的字符型设备驱动模型供CV用(参考了原子开发文档)
Linux 应用程序对驱动程序的调用如下图所示:
在 Linux 中一切皆为文件,驱动加载成功以后会在“ /dev”目录下生成一个相应的文件,应用程序通过对这个名为“ /dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。比如现在有个叫做/dev/led 的驱动文件,此文件是 led 的驱动文件。应用程序使用 open 函数来打开文件/dev/led,使用完成以后使用 close 函数关闭/dev/led 这个文件。 open 和 close 就是打开和关闭 led 驱动的函数,如果要点亮或关闭led,那么就使用 write 函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开 led 的控制参数。如果要获取 led 灯的状态,就用 read 函数从驱动中读取相应的状态。
应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间陷入到内核空间,这样才能实现对底层驱动的操作。 open、 close、 write 和read 等这些函数是有 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。当我们调用 open 函数的时候流程如下图所示:
二 开发流程
我们在学习裸机或者 STM32 的时候关于驱动的开发就是初始化相应的外设寄存器,在Linux 驱动开发中肯定也是要初始化相应的外设寄存器,这个是毫无疑问的。只是在 Linux驱动开发中我们需要按照其规定的框架来编写驱动,所以说学 Linux 驱动开发重点是学习其驱动框架。
驱动模块的加载和卸载
字符设备注册与注销
实现设备的具体操作函数
1、 能够对 chrtest 进行打开和关闭操作
2、对 chrtest 进行读写操作
添加 LICENSE 和作者信息
三 设备号
为了方便管理, Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。
Linux 系统中主设备号范围为 0~4095,所以大在选择主设备号的时候一定不要超过这个范围。在文件 include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏),如下所示:
#define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
第 1 行,宏 MINORBITS 表示次设备号位数,一共是 20 位。
第 2 行,宏 MINORMASK 表示次设备号掩码。
第 3 行,宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
第 4 行,宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
第 5 行,宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。
有静态分配和动态分配两种方式
实例如下
#include "stdio.h" #include "string.h" #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> // static struct file_operations test_fops; //就是设备的操作函数集合,只是此时还没有初始化 test_fops 中的 open、 release 等这些成员变量,所以这个操作函数集合还是空的 /*cat /proc/devices可以查看设备号*/ #define CHRDEVBASE_MAJOR 200 // 主设备号 #define CHRDEVBASE_NAME "chrdevbase" // 设备名 static char readbuf[100]; // 读缓冲区 static char writebuf[100]; // 写缓冲区 static char kerneldata[] = {"kernel data!"}; /* * @description : 打开设备 * @param – inode : 传递给驱动的 inode * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量 * 一般在 open 的时候将 private_data 指向设备结构体。 * * @return : 0 成功;其他 失败 */ static int chrtest_open(struct inode *inode, struct file *filp) { /*实现具体功能*/ //printk("chrdevbase open!\r\n"); return 0; } /* * @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符) * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { /*实现具体功能*/ int retvalue = 0; /* 向用户空间发送数据 */ memcpy(readbuf, kerneldata, sizeof(kerneldata)); retvalue = copy_to_user(buf, readbuf, cnt);//因为内核空间不能直接操作用户空间的内存,因此需要借助 copy_to_user 函数来完成内核空间的数据到用户空间的复制 if(retvalue == 0) { printk("kernel senddata ok!\r\n"); } else { printk("kernel senddata failed!\r\n"); } //printk("chrdevbase read!\r\n"); return 0; } /* * @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符 * @param - buf : 要写给设备写入的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 写入的字节数,如果为负值,表示写入失败 */ static ssize_t chrtest_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt) { int retvalue = 0; /* 接收用户空间传递给内核的数据并且打印出来 */ retvalue = copy_from_user(writebuf, buf, cnt); if(retvalue == 0) { printk("kernel recevdata:%s\r\n", writebuf); } else { printk("kernel recevdata failed!\r\n"); } //printk("chrdevbase write!\r\n"); return 0; } /* * @description : 关闭/释放设备 * @param - filp : 要关闭的设备文件(文件描述符) * @return : 0 成功;其他 失败 */ static int chrtest_release(struct inode *inode, struct file *filp) { /*实现具体功能*/ //printk("chrdevbase release! \r\n"); return 0; } /* * 设备操作函数结构体 */ static struct file_operations test_fops = { .owner = THIS_MODULE, .open = chrtest_open, .read = chrtest_read, .write = chrtest_write, .release = chrtest_release, }; /* * @description : 驱动入口函数 * @param : 无 * @return : 0 成功;其他 失败 */ static int __init chrdevbase_init(void) { /* 入口函数具体内容 */ int retvalue = 0; /* 注册字符设备驱动 */ retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);//调用函数 register_chrdev 注册字符设备,主设备号为 200,设备名为“ chrtest” if(retvalue < 0) { printk("chrdevbase driver register failed\r\n"); /* 字符设备注册失败,自行处理 */ } printk("chrdevbase_init()\r\n"); return 0; } /* 驱动出口函数 */ static void __exit xxx_exit(void) { /* 注销字符设备驱动 */ unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);//调用函数 unregister_chrdev 注销主设备号为 200 的这个设备。 printk("chrdevbase_exit()\r\n"); } /* 将上面两个函数指定为驱动的入口和出口函数 */ module_init(xxx_init); module_exit(xxx_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("xin.han");