文章目录
- 前情提要
- 第五章 嵌入式 Linux 驱动开发
- 1) 设备驱动的概念、抽象层次、分类、特点、安装方法
- 什么是驱动程序:
- 设备驱动分类
- 2) 设备驱动开发过程
- 直接编译进内核
- 编译成模块
- 3) 字符设备驱动结构,file_operations 里常用的接口函数、驱动的结构
- 驱动程序举例:
- 4) 所有的函数不需要背
前情提要
本人是一名大三学生,由于期末复习需要,所以按照老师的ppt总结整理此笔记,希望对你有所帮助
第五章 嵌入式 Linux 驱动开发
- 设备驱动的概念、抽象层次、分类、特点、安装方法
- 设备驱动开发过程
- 字符设备驱动结构,file_operations 里常用的接口函数、驱动的结构
- 所有的函数不需要背
1) 设备驱动的概念、抽象层次、分类、特点、安装方法
设备驱动程序介于操作系统和硬件之间,屏蔽了硬件设备的物理细节,并提供了访问各种硬件设备的统一接口。Linux内核源程序中占有60%以上。
应用程序 库 内核 驱动程序之间的关系
什么是驱动程序:
-
作为操作系统的一部分(OS = Kernel + Device Driver)
- 向上为Linux系统提供访问硬件统一调用接口
- 向下用于控制硬件:与Arm裸机程序一样,通过读写硬件寄存器达到控制硬件的目的
-
驱动程序的运行是被动的
- 驱动只是告诉内核”我在这里,我能做这些工作”:向内核注册
- 这些工作何时开始,取决于应用程序:应用触发驱动
设备驱动分类
-
字符设备驱动
设备以字节流方式访问(以字节为单位读写)
字符设备驱动实现了open、close 、read、write等系统调用
应用程序通过设备文件(如/dev/ttySAC0)访问设备
字符设备文件的第一个标志是前面的“c”标志。
$ ls –l /dev
crw-rw---- 1 root uucp 4, 64 08-30 22:58 ttyS0 /*串口设备, c表示字符设备*/
crw-rw---- 1 root uucp 3, 4 08-30 22:58 ttyS0 /* 主设备号3,此设备号4 */
- 块设备驱动
设备上的数据以块的方式存放(如NAND Flash上的数据以页为单位)
块设备驱动程序也向用户层提供open、close
应用通过设备文件(如/dev/sda1)来访问设备
块设备驱动特别之处:
(1)操作硬件的实现方式不一样:先将数据组织成块,再操作设备
(2)数据块上的数据按照一定的格式组织:存放文件系统,实现mount
例如,在系统中的块设备IDE硬盘的主设备号是3,而多个IDE硬盘及其各个分区分别赋予次设备号1、2、3……
$ ls –l /dev
brw-r----- 1 root floppy 2, 0 08-30 22:58 fd0/*软盘设备,b表示块设备*/
-
网络设备驱动
设备上的数据以不固定大小的帧输入与输出
没有/dev上对应的设备文件,不通过open、read、write操作
系统为网络设备访问分配唯一接口(如eth0)
为应用层提供一套数据包传输函数访问接口(SOCKET)
2) 设备驱动开发过程
- 查看原理图、数据手册,了解设备的操作方法
- 在内核中找到相近的驱动程序,以它为模板开发
- 实现驱动程序的初始化,并向内核注册
- 按照内核规定的驱动框架,实现相关操作函数(如open、read、write)
- 编译驱动程序到内核中,或者编译成模块并挂载(insmod)到内核
直接编译进内核
-
将驱动模块源码合入内核源码
- 设备驱动程序应包含在drivers子目录
- 首先确认是否存在于设备驱动程序特性相似的目录名
- 存在则插入相应目录,否则字符类型插入char目录,块类型插入block目录,网络类型插入net目录
-
修改内核编译选项文件
- Linux内核支持使用内核编译选项包含到内核中的功能
- make menuconfig读入这些内核编译选项文件来配置内核
- 2.6内核编译选项文件为KConfig
- 进入加入了驱动模块的目录,修改目录下的KConfig,使得加入的驱动能在配置项中显示
-
修改内核源码中的Makefile
- Makefile指定了驱动程序的编译规则,使得驱动程序能包含到内核image中
- Makefile根据make menuconfig配置设定的编译条件变量,决定是要把特定源代码编译成模块还是包含到内核中,或者是清除。
- 进入合入了驱动模块的目录,修改改目录下的Makefile,使得合入的驱动源码能编译进内核
-
确认合入内核的驱动在内核启动时自动运行
- 重新编译并启动新内核,dmesg=>确认“hello world”已打印出来
- 带**__int标志**的函数被放入初始化代码段,内核会依次调用初始化代码段的
- 函数,并在初始化完成后释放 init 区段.
编译成模块
-
模块加载函数(必需)
安装模块时被系统自动调用的函数,通过module_init宏来指定 -
模块卸载函数(必需)
卸载模块时被系统自动调用的函数,通过module_exit宏来指定
加载 insmod (insmod hello.ko)
卸载 rmmod (rmmod hello)
查看 lsmod
加载 modprobe (modprobe hello)
modprobe 如同 insmod, 也是加载一个模块到内核。它的不同
之处在于它会根据/lib/modules/<$version>/modules.dep
来查看要加载的模块, 看它是否还依赖于其他模块,如果是,
modprobe 会首先找到这些模块, 把它们先加载到内核。
3) 字符设备驱动结构,file_operations 里常用的接口函数、驱动的结构
总体结构:
3、调用关系
(1)当应用程序使用open打开某个设备(/dev的设备文件)
设备驱动程序file_operations结构中open成员函数被调用
(2)当应用程序使用使用read、write、ioctl等函数读写、控制设备
设备驱动程序file_operations结构中read、write、ioctl等成员函数被调用
4、字符设备驱动程序目的
为具体硬件设备file_operations结构实现操作设备所需的成员函数
5、设备文件与驱动程序file_operations结构的对应关系
主设备号用来标识与设备文件相连的驱动程序,次编号背驱动程序来辨别操作的是那个设备
主设备号用来反映设备类型
次设备号用来区分同类型的设备
6、设备驱动注册
在驱动中init 函数中实现的。
//老注册接口
register_chrdev(DEV_MAJOR, DEV_NAME, &dev_ops);
//2.6内核使用cdev来描述一个字符设备
cdev_init(&s_cdev, &dev_ops); //初始化cdev
ret = cdev_add(&s_cdev, s_dev, DEV_COUNT); //注册cdev
注册完成后,应用程序操作设备文件时,系统就会根据
主设备号找到内核中注册file_operations结构
驱动程序举例:
字符驱动程序框架
- 包含头文件
编写字符驱动程序时需要的内核头文件 - 定义常量
定义模块的主设备号和名称,它们将在字符设备的初始化的注册函数中使用。 - 函数声明
声明需要使用的函数,将被注册到文件操作数据结构struct file_operations中。 - 文件操作数据结构的指针
static struct file_operations virtual_char_fops={
owner: THIS_MODULE,
llseek: virtual_char_llseek,
read: virtual_char_read,
write: virtual_char_write,
ioctl: virtual_char_ioctl,
open: virtual_char_open,
release: virtual_char_close,
}
这是驱动程序的核心部分,定义了一个静态文件操作数据结构,将数据结构中几个成员赋值为驱动程序中函数的指针
5. 函数中的各种操作
- module_init()注册的函数执行设备文件的注册
- module_exit()注册的函数执行设备文件的卸载。
- open中增加引用计数;close中减少引用计数。
- write/read中执行定义的读写操作,内容传递主要通过缓冲区的指针buf。
- ioctl中执行驱动程序自定义的命令,根据cmd选择要执行的命令
- 应用程序
linux的应用程序与驱动程序调用关系
应用程序使用驱动程序主要有以下几个步骤:
- 使用open打开设备文件
- 在使用驱动程序的时候,根据需要调用write/read/ioctl等操作
- 使用close关闭设备文件