文章目录
Linux内核基础框架
- 对内核结构框图有个总体的把握,有助于理解为什么驱动要这样写,为什么写应用程序所用的C库接口(文件、进程、线程、网络等)能够完成相应的功能。
内核结构框图

名词解释:
-
【用户程序】:基础C + 函数库
-
【函数库】:利用linux C库提供的API去支配内核(也可以调用第三方库例如wiringPi库:树莓派的IO库)【是对系统调用接口的封装】
-
【系统调用接口】:会调用sys_call。linux C库与系统调用接口打交道,系统调用接口是最小功能函数
-
【虚拟文件VFS接口】:会调用sys_open、sys_read等函数
-
【硬件】可以分为磁盘或设备,虚拟文件系统会识别用户输入的指令是操作磁盘还是设备,若操作设备还会经过设备驱动程序,设备驱动里面会有硬件控制的代码
-
linux系统内核相当于是超级裸机,直接操作硬件,但会把硬件底层的东西抽象化,对用户来说只需要调API就好了,根本不需要管寄存器,协议,总线…(单片机会去直接操作),这些全部由操作系统做好。
linux系统架构

-
为了方便调用内核,Linux将内核的功能接口制作成系统调用(system call)。用户不需要了解内核的复杂结构,就可以使用内核。系统调用是操作系统的最小功能单位。一个操作系统,以及基于操作系统的应用,都不可能实现超越系统调用的功能。
-
系统调用提供的功能非常庞大,所以使用起来很麻烦。一个简单的给变量分配内存空间的操作,就需要动用多个系统调用。Linux定义一些库函数(library routine)来将系统调用组合成某些常用的功能。上面的分配内存的操作,可以定义成一个库函数,比如常用的malloc。
-
linux中有两百多个系统调用,在命令中输入
man 2 syscalls可以查询,2表示在2类(系统调用类)中查询,具体各类是什么可以在man man中查询
-
-
c库是对系统调用的封装,可以完成进程,线程,网络编程,文件IO等操作【进程、线程、文件IO可以统称“系统编程”】 -
设备驱动需要使用第三方库,比如wiringPi库,将引脚进行重新配置【wiringPi库中也有线程进程API等,但是不采用】
shell
- shell(壳)是一个特殊的
应用,也经常被称为命令行。可以理解为是一个命令解释器 - 一个shell对应一个终端,在终端输入的文本会给shell程序分析,shell程序可以运行库函数或系统调用等
本质就是提供和内核交互的程序。
驱动
为什么要学习写驱动
-
原来树莓派开发使用厂家提供的wiringPi库,开发简单。
-
但未来做开发时,不一定都是用树莓派,没有wiringPi库可以用。但只要能运行Linux,Linux的标准C库一定有。
-
学会根据标准C库编写驱动,只要能拿到linux内核源码,拿到芯片手册,电路等就能做开发。
什么是驱动:
- 驱动就是对底层硬件设备的操作进行封装,并向上层提供函数接口。
硬件设备分类:
linux系统将设备分为3类:字符设备、块设备、网络设备。
- 字符设备:指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
- 块设备: 指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。
- 网络设备: 网络设备可以是一个硬件设备,如网卡; 但也可以是一个纯粹的软件设备, 比如回环接口(lo).一个网络接口负责发送和接收数据报文。
文件名与设备号
linux一切皆为文件,其设备管理同样是和文件系统紧密结合,各种设备都以文件的形式存放在/dev目录下,称为设备文件。
硬件要有相对应的驱动,那么open怎样区分这些硬件对应的驱动程序
-
依靠文件名(设备名)与设备号
-
查询设备号:依靠文件名(设备名)与设备号。在/dev下ls -l可以看到

对于常用设备
- Linux有约定俗成的编号,如硬盘的主设备号是3。 一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。
设备号又分为:
- 主设备号:用于区别不同种类的设备;
- 次设备号:区别同种类型的多个设备。
设备号的作用:
-
区分不同种类的设备和不同类型的设备
-
驱动插入到链表的位置(顺序)由设备号检索
驱动结构框图的认知

字符驱动代码执行流程:
- 应用层调用C库函数open(“/dev/pin4”,O_RDWR);
触发软中断(系统调用专用,触发cpu异常,中断号是0x80,0x80代表发生了一个系统调用) ,进入系统内核- 内核层中系统调用接口sys_call(汇编语言,执行效率高),通过设备名找到设备号
- 内核层中虚拟文件系统 VFS会区分是操作磁盘还是设备,根据设备号调用指定链表节点(驱动程序)内的sys_open ,即在内核的驱动链表里面根据设备名和设备号查找到相关的设备驱动函数(每一个驱动函数是一个节点)
- 驱动函数里面有通过寄存器操控IO口的代码,进而可以控制IO口实现相关功能。
注意:
从用户态到内核态会携带参数进入,通过指令携带参数的方式,或在寄存器里面存值

内核根据用户态传递进入的值去判断调用什么函数, sys_call_table 是函数指针数组

内核的 sys_open 、sys_read 会做什么?

驱动程序开发步骤
驱动开发无非以下两件事:
- 编写完驱动程序,加载到内核 ,为其添加设备号和设备名、 设备驱动函数
- 用户空间open设备名后,调用驱动程序,驱动程序通过操作寄存器来驱动IO口
Linux 内核就是由各种驱动组成的,内核源码中有大约 85%是各种驱动程序的代码。内核中驱动程序种类齐全,可以在同类驱动的基础上进行修改以符合要求的驱动。
编写驱动程序的难点并不是硬件的具体操作,而是弄清楚现有【驱动程序的框架】,在这个框架中加入这个硬件。
一般来说,编写一个 linux 设备驱动程序的大致流程如下:
- 查看原理图、数据手册,了解设备的操作方法;
- 在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始;
- 实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序;
- 设计所要实现的操作,比如 open、close、read、write 等函数;
- 实现中断服务(中断并不是每个设备驱动所必须的);
- 编译该驱动程序到内核中,或者用 insmod 命令加载;
- 测试驱动程序;
字符设备驱动
下面就以一个简单的字符设备驱动框架代码来进行驱动程序的开发、编译等。
基于驱动框架的代码开发【不涉及硬件操作】
上层调用代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void main()
{
int fd,data;
fd = open("/dev/pin4",O_RDWR);
if(fd<0){
printf("open fail\n");
perror("reson:");
}
else{
printf("open success\n");
}
data = write(fd,'1',1);
}
驱动框架代码
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件
//确定主设备、变量定义
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno; //设备号
static int major = 231; //主设备号
static int minor = 0; //次设备号
static char *module_name = "pin4"; //模块名
//led_open函数
stat
1万+




被折叠的 条评论
为什么被折叠?



