1.Linux驱动(驱动程序开发、驱动框架代码编译和测试)
Linux驱动(驱动程序开发、驱动框架代码编译和测试)
驱动就是对底层硬件设备的操作进行封装,并向上层提供函数接口。
Linux系统将设备分为3类:字符设备、块设备、苹果 源码街网络设备。
先看一张图,图中描述了流程,有助了解驱动。
用户态:
内核态:
驱动链表:管理所有设备的驱动,添加或查找, 添加是发生在我们编写完驱动程序,加载到内核。查找是在调用驱动程序,由应用层用户空间去查找使用open函数。驱动插入链表的顺序由设备号检索。
字符设备驱动工作原理:
在Linux的世界里一切皆文件,所有的硬件设备操作到应用层都会被抽象成文件的操作。当应用层要访问硬件设备,它必定要调用到硬件对应的网站首页布局源码驱动程序。Linux内核有那么多驱动程序,应用怎么才能精确的调用到底层的驱动程序呢?
当open函数打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备),还会分配一个struct file结构体。
根据struct inode结构体里面记录的设备号,可以找到对应的驱动程序。在Linux操作系统中每个字符设备都有一个struct cdev结构体。此结构体描述了字符设备所有信息,其中最重要的单点登录源码分析一项就是字符设备的操作函数接口。
找到struct cdev结构体后,linux内核就会将struct cdev结构体所在的内存空间首地址记录在struct inode结构体i_cdev成员中,将struct cdev结构体中的记录的函数操作接口地址记录在struct file结构体的f_ops成员中。
任务完成,VFS层会给应用返回一个文件描述符(fd)。这个fd是和struct file结构体对应的。接下来上层应用程序就可以通过fd找到struct file,然后在struct file找到操作字符设备的函数接口file_operation了。
其中,cdev_init和cdev_add在驱动程序的中山建站模板源码入口函数中就已经被调用,分别完成字符设备与file_operation函数操作接口的绑定,和将字符驱动注册到内核的工作。
驱动程序开发步骤:
Linux 内核就是由各种驱动组成的,内核源码中有大约 %是各种驱动程序的代码。内核中驱动程序种类齐全,可以在同类驱动的基础上进行修改以符合具体单板。
编写驱动程序的难点并不是硬件的具体操作,而是弄清楚现有驱动程序的框架,在这个框架中加入这个硬件。
一般来说,lua源码包编译编写一个 linux 设备驱动程序的大致流程如下:
下面以一个简单的字符设备驱动框架代码来进行驱动程序的开发、编译等。
基于驱动框架的代码开发:
上层调用代码
驱动框架代码
驱动开发的重点难点在于读懂框架代码,在里面进行设备的添加和修改。
驱动框架设计流程:
1. 确定主设备号
2. 定义结构体 类型 file_operations
3. 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
4. 实现驱动入口:安装驱动程序时,就会去调用这个入口函数,执行工作:
① 把 file_operations 结构体告诉内核:注册驱动程序register_chrdev.
② 创建类class_create.
③ 创建设备device_create.
5. 实现出口:卸载驱动程序时,就会去调用这个出口函数,执行工作:
① 把 file_operations 结构体从内核注销:unregister_chrdev.
② 销毁类class_create.
③ 销毁设备结点device_destroy.
6. 其他完善:GPL协议、入口加载
驱动模块代码编译和测试:
编译阶段:
驱动模块代码编译(模块的编译需要配置过的内核源码,编译、连接后生成的内核模块后缀为.ko,编译过程首先会到内核源码目录下,读取顶层的Makefile文件,然后再返回模块源码所在目录。)
将该驱动代码拷贝到 linux-rpi-4..y/drivers/char 目录下 文件中(也可选择设备目录下其它文件)
修改该文件夹下Makefile(驱动代码放到哪个目录,就修改该目录下的Makefile),将上面的代码编译生成模块,文件内容如下图所示:(-y表示编译进内核,-m表示生成驱动模块,CONFIG_表示是根据config生成的),所以只需要将obj-m += pin4drive.o添加到Makefile中即可。
回到linux-rpi-4..y/编译驱动文件
使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules进行编译生成驱动模块。
加载内核驱动:
加载内核驱动(相当于通过insmod调用了module_init这个宏,然后将整个结构体加载到驱动链表中)。 加载完成后就可以在dev下面看到名字为pin4的设备驱动(这个和驱动代码里面static char *module_name="pin4"; //模块名这行代码有关),设备号也和代码里面相关。
lsmod查看系统的驱动模块,执行上层代码,赋予权限
查看内核打印的信息,如下图所示:表示驱动调用成功
在装完驱动后可以使用指令:sudo rmmod +驱动名(不需要写ko)将驱动卸载。
驱动调用流程:
上层空间的open去查找dev下的驱动(文件名),文件名背后包含了驱动的主设备号和次设备号。此时用户open触发一个系统调用,系统调用经过vfs(虚拟文件系统),vfs根据文件名背后的设备号去调用sys_open去判断,找到内核中驱动链表的驱动位置,再去调用驱动里面自己的dev_open函数。
为什么生成驱动模块需要在虚拟机上生成?树莓派不行吗?
生成驱动模块需要编译环境(linux源码并且编译,需要下载和系统版本相同的Linux内核源代码)。也可以在树莓派上面编译,但在树莓派里编译,效率会很低,要非常久。