一、前言
字符设备是Linux 驱动中最重要的设备之一,其能够像字节流一样被访问,也就是说对它的操作以字节为单位来实现读写等操作。比如最常见的点灯、按键、IIC、SPI、LCD等都是字符设备,这些设备的驱动就叫做字符设备驱动。字符设备的驱动程序实现了open、close、read、write等系统调用,应用程序可以通过设备文件(/dev/xxx,其中xxx为该字符设备文件,例如/dev/led)来访问字符设备。在Linux中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。
二、目的
本文简单的介绍Linux字符设备驱动的开发步骤与流程。如果学过裸机或者STM32都知道对某个设备模块就是要初始化相关的外设寄存器等。在Linux内核下开发字符设备驱动仍是按初始化相关外设寄存器的步骤流程或者说框架来编写驱动。重点是要掌握其驱动开发框架。
三、开发流程与介绍
Linux的设备驱动程序大致可以分为如下几个部分:驱动模块的加载与卸载、驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理等。以字符设备驱动开发为例,其开发流程模型与工作关联分别如下图所示:
1.Linux内核中
①使用cdev结构体来描述字符设备。
②通过cdev的成员dev_t来定义设备号(主、次设备号)来确定字符设备的唯一性。
③通过cdev的成员file_operations来定义字符设备驱动提供的VFS的函数接口,如open( ),read( ),write( )等。
2.Linux字符设备驱动中
①模块加载函数通过register_chrdev_region( )或alloc_chrdev_region( )来静态或动态获取设备号。
②通过cdev_init( )建立cdev与file_opreations之间的联系,通过cdev_add( )向系统添加一个cdev以完成注册。
③模块卸载通过cdev_del( )来注销cdev,通过unregister_chrdev_region( )来释放设备号。
3.字符设备驱动加载
当字符设备驱动被添加到内核后,用户空间的应用程序通过系统调用可以间接访问到字符设备驱动的read、write、ioctl等底层驱动函数。
4.用户空间访问该设备程序
通过Linux系统调用,如open( ),read( ),write( ),来调用file_opreations来定义字符设备提供给VFS的接口函数。
5.file_operations对设备的读写操作
由于用户空间不能直接访问内核空间的内存,因此借助了函数**copy_from_user( )来完成用户空间缓冲区到内核空间的复制,以及copy_to_user( )**来完成内核空间到用户空间缓冲区的复制。
四、驱动模块的加载与卸载
Linux驱动有两种运行方式,第一种就是将驱动编译到内核中去,这样当Linux内核在启动运行过程的时候自动加载驱动程序。第二种将驱动程序编译成模块(在linux下模块的扩展名为.ko),该驱动模块需要在Linux内核启动以后使用“insmod”命令加载。一般在调试驱动的时候都选择将其编译成模块,这样的好处是当修改代码后只需要编译一下驱动代码即可,从而不需要编译整个内核代码。
模块有加载和卸载两种操作,需要编写这两个部分的代码。函数如下:
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
1.module_init函数
用来向 Linux内核注册一个模块加载函数,参数 xxx_init就是需要注册的具体函数,当使用“ insmod”命令加载驱动的时候 xxx_init这个函数就会被调 用。
2.module_exit()函数
用来向 Linux内核注册一个模块卸载函数,参数 xxx_exit就是需要注册的具体函数,当使用“ rmmod”命令卸载具体驱动的时候 xxx_exit函数就会被调用。
3.驱动模块加载和卸载模板
驱动模块加载和卸载模板实现如下所示:
/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容 */
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 出口函数具体内容 */
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);
/* 添加LICENSE和作者信息 */
MODULE_LICENSE("GPL"); //必须添加模块LICENSE信息,否则的话编译的时候会报错
MODULE_AUTHOR("Yimning"); //添加模块作者信息
五、字符设备的申请与注销设备号
当字符设备驱动模块加载的时候,往往也需要初始化或者注册字符设备号,同样卸载驱动模块的时候也需要注销字符设备号。
1.申请设备号的注册函数原型
/*
* @description : 函数用于静态申请字符设备号
* @params :
* unsigned int major : 主设备号
* const char *name : 设备名字,指向一串字符串
* const struct file_operations *fops : 结构体file_operations类型指针,指向设备的操作函数集合变量
* @return : int
*/
static inline int register_chrdev(unsigned int major, const char *name, co