文章目录
通过我们上篇博文,内核的讲解,在看下面这个图就不难理解了。
内核的讲解

驱动认知
1. 什么是驱动
驱动就是对底层硬件设备的操作进行封装,并向上层提供函数接口。
设备分类:
linux系统将设备分为3类:字符设备、块设备、网络设备。
- 字符设备:指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等,字符设备驱动程序通常至少要实现open、close、read和write的系统调用,字符终端(/dev/console)和串口(/dev/ttyS0以及类似设备)就是两个字符设备,它们能很好的说明“流”这种抽象概念。
- 块设备: 指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。
- 网络设备: 网络设备可以是一个硬件设备,如网卡; 但也可以是一个纯粹的软件设备,
比如回环接口(lo).一个网络接口负责发送和接收数据报文。
我们来举一个例子来说一下整体的调用过程
- 在上层我们调用 c语言 open函数
open("/dev/pin4",O_RDWR);
调用/dev下的pin4以可读可写的方式打开,对于上层open调用到内核时会发生一次软中断中断号是0X80,从用户空间进入到内核空间 - open会调用到
system_call
(内核函数),system_call会根据/dev/pin4设备名,去找出你要的设备号。 - 再调到虚拟文件VFS (为了上层调用到确切的硬件统一化),调用VFS里的
sys_open
,sys_open会找到在驱动链表里面,根据主设备号和次设备号找到引脚4里的open函数,我们在引脚4里的open是对寄存器操作
我们写驱动无非就是做添加驱动:
添加驱动做哪些事呢?
- 设备名
- 设备号
- 设备驱动函数 (操作寄存器 来驱动 IO口)
综上所述
如果想要打开dev
下面的pin4
引脚,过程是:用户态调用open(“/de/pin4”,O_RDWR
),对于内核来说,上层调用open函数会触发一个软中断(系统调用专用,中断号是0x80,0x80代表发生了一个系统调用),系统进入内核态,并走到system_call
,可以认为这个就是此软中断的中断服务程序入口,然后通过传递过来的系统调用号来决定调用相应的系统调用服务程序(在这里是调用VFS
中的sys_open
)。sys_open
会在内核的驱动链表里面根据设备名和设备号查找到相关的驱动函数(每一个驱动函数是一个节点
),驱动函数里面有通过寄存器操控IO口的代码,进而可以控制IO口实现相关功能。
2. 各分态的详解
用户态:
- 是指用户编写程序、运行程序的层面,用户态在开发时需要C的基础和C库,C库讲到文件,进程,进程间通信,线程,网络,界面(GTk)。C库(是linux标准库一定有):就是Clibary,提供了程序支配内核干活的接口,调用的
open,read,write,fork,pthread,socket
由此处封装实现,由写的应用程序调用,C库中的各种API调用的是内核态,支配内核干活。
内核态:
-
用户要使用某个硬件设备时,需要内核态的设备驱动程序,进而驱动硬件干活,就比如之前文章里面所提到的
wiringPi库
,就是提供了用户操控硬件设备的接口,在没有wiringPi库时就需要自己实现wiringPi库的功能,就是自己写设备驱动程序。这样当我们拿到另一种类型的板子时,同样也可以完成开发。 -
在linux中一切皆文件,各种的文件和设备(比如:鼠标、键盘、屏幕、flash、内存、网卡、如下图所示:)都是文件,那既然是文件了,就可以使用文件操作函数来操作这些设备。
-
有一个问题,open、read等这些文件操作函数是如何知道打开的文件是哪一种硬件设备呢?
①在open函数里面输入对应的文件名,进而操控对应的设备。
②通过 设备号(主设备号和次设备号) 。除此之外我们还要了解这些驱动程序的位置,和如何实现这些驱动程序,每一种硬件设备对应不同的驱动(这些驱动有我们自己来实现)。 -
Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为 设备文件。
应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。
为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号 和 次设备号(如下图所示:)。
主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。
对于常用设备,Linux有约定俗成的编号,如硬盘的主设备号是3。
一个字符设备或者块设备都有一个主设备号和次设备号。
主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。
次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。
驱动链表
管理所有设备的驱动,添加或查找
添加
是发生在我们编写完驱动程序,加载到内核。
查找
是在调用驱动程序,由应用层用户空间去查找使用open函数。驱动插入链表的顺序由设备号检索,就是说主设备号和次设备号除了能区分不同种类的设备和不同类型的设备,还能起到将驱动程序加载到链表的某个位置,在下面介绍的驱动代码的开发无非就是添加驱动(添加设备号、设备名和设备驱动函数)和调用驱动。
- system_call函数是怎么找到详细的系统调用服务例程的呢?
通过系统调用号查找系统调用表sys_call_table! 软中断指令INT 0x80运行时,系统调用号会被放入 eax寄存器中,system_call函数能够读取eax寄存器获取,然后将其乘以4,生成偏移地址,然后以sys_call_table为基址。基址加上偏移地址,就能够得到详细的系统调用服务例程的地址了!然后就到了系统调用服务例程了。
补充:
- 每个系统调用都对应一个系统调用号,而系统调用号就对应内核中的相应处理函数。
- 所有系统调用都是通过中断0x80来触发的。
- 使用系统调用时,通过eax 寄存器将系统调用号传递到内核,系统调用的入参通过ebx、ecx……依次传递到内核
- 和函数一样,系统调用的返回值保存在eax中,所有要从eax中取出
3. 字符设备驱动工作原理
字符设备驱动工作原理
在linux的世界里一切皆文件,所有的硬件设备操作到应用层都会被抽象成文件的操作。我们知道如果应用层要访问硬件设备,它必定要调用到硬件对应的驱动程序。Linux内核有那么多驱动程序,应用怎么才能精确的调用到底层的驱动程序呢?
必须知道的知识:
-
在Linux文件系统中,每个文件都用一个
struct inode
结构体来描述,这个结构体记录了这个文件的所有信息,例如文件类型,访问权限等。 -
在linux操作系统中,每个驱动程序在应用层的
/dev
目录或者其他如/sys
目录下都会有一个文件与之对应。 -
在linux操作系统中, 每个驱动程序都有一个设备号。
-
在linux操作系统中,每打开一次文件,Linux操作系统会在VFS层分配一个
struct file
结构体来描述打开的文件。