> 文章列表 > Linux驱动开发详细解析

Linux驱动开发详细解析

Linux驱动开发详细解析

Linux驱动开发详细解析

一、驱动概念

驱动与底层硬件直接打交道,充当了硬件与应用软件中间的桥梁。

  • 具体任务
    1. 读写设备寄存器(实现控制的方式)
    2. 完成设备的轮询、中断处理、DMA通信(CPU与外设通信的方式)
    3. 进行物理内存向虚拟内存的映射(在开启硬件MMU的情况下)
  • 说明:设备驱动的两个任务方向
    1. 操作硬件(向下)
    2. 将驱动程序通入内核,实现面向操作系统内核的接口内容,接口由操作系统实现(向上)
      驱动程序按照操作系统给出的独立于设备的接口设计应用程序使用操作系统统一的系统调用接口来访问设备)

Linux系统主要部分:内核、shell、文件系统、应用程序

  • 内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统
  • 分层设计的思想让程序间松耦合,有助于适配各种平台
  • 驱动的上面是系统调用下面是硬件
    Linux驱动开发详细解析

二、驱动分类

Linux驱动分为三个基础大类:字符设备驱动,块设备驱动,网络设备驱动

  1. 字符设备(Char Device)
    • 字符(char)设备是个能够像字节流(类似文件)一样被访问的设备。
    • 对字符设备发出读/写请求时,实际的硬件I/O操作一般紧接着发生。
    • 字符设备驱动程序通常至少要实现open、close、read和write系统调用。
    • 比如我们常见的lcd、触摸屏、键盘、led、串口等等,他们一般对应具体的硬件都是进行出具的采集、处理、传输。
  2. 块设备(Block Device)
    • 一个块设备驱动程序主要通过传输固定大小的数据(一般为512或1k)来访问设备。
    • 块设备通过buffer cache(内存缓冲区)访问,可以随机存取,即:任何块都可以读写,不必考虑它在设备的什么地方。
    • 块设备可以通过它们的设备特殊文件访问,但是更常见的是通过文件系统进行访问。
    • 只有一个块设备可以支持一个安装的文件系统。
    • 比如我们常见的电脑硬盘、SD卡、U盘、光盘等。
  3. 网络设备(Net Device)
    • 任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备。
    • 访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点。
    • 内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函(socket函数)而不是read、write等。
    • 比如我们常见的网卡设备、蓝牙设备。

三、驱动程序的功能

  1. 对设备初始化和释放
  2. 把数据从内核传送到硬件和从硬件读取数据
  3. 读取应用程序传送给设备文件的数据和回送应用程序请求的数据
  4. 检测和处理设备出现的错误

四、驱动开发前提知识

4.1 内核态和用户态

  • Kernel Mode(内核态)
    • 内核模式下(执行内核空间的代码),代码具有对硬件的所有控制权限。可以执行所有CPU指令,可以访问任意地址的内存
  • User Mode(用户态)
    • 在用户模式下(执行用户空间的代码),代码没有对硬件的直接控制权限,也不能直接访问地址的内存。
    • 只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址。
    • 程序是通过调用系统接口(System Call APIs)来达到访问硬件和内存

Linux利用CPU实现内核态和用户态

  • ARM:内核态(svc模式),用户态(usr模式)
  • x86 : 内核态(ring 0 ),用户态(ring 3)// x86有ring 0 - ring3四种特权等级

Linux实现内核态和用户态切换

  • ARM Linux的系统调用实现原理是采用swi软中断从用户态切换至内核态

  • X86是通过int 0x80中断进入内核态

Linux只能通过系统调用硬件中断从用户空间进入内核空间

  • 执行系统调用的内核代码运行在进程上下文中,他代表调用进程执行操作,因此能够访问进程地址空间的所有数据
  • 处理硬件中断的内核代码运行在中断上下文中,他和进程是异步的,与任何一个特定进程无关通常,一个驱动程序模块中的某些函数作为系统调用的一部分,而其他函数负责中断处理

4.2 Linux下应用程序调用驱动程序流程

Linux驱动开发详细解析

  • Linux下进行驱动开发,完全将驱动程序与应用程序隔开,中间通过C标准库函数以及系统调用完成驱动层和应用层的数据交换。
  • 驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对“/dev/xxx” (xxx 是具体的驱动文件名字) 的文件进行相应的操作即可实现对硬件的操作。
  • 用户空间不能直接对内核进行操作,因此必须使用一个叫做 “系统调用”的方法 来实现从用户空间“陷入” 到内核空间,这样才能实现对底层驱动的操作
  • 应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比如应用程序中调用了 open 函数,那么在驱动程序中也得有一个名为 open 的函数。
  • 每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合

Linux驱动开发详细解析

4.3 内核模块

Linux 驱动有两种运行方式

  • 驱动编译进 Linux 内核中,当 Linux 内核启动的时就会自动运行驱动程序。
  • 驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用相应命令加载驱动模块。
    • 内核模块是Linux内核向外部提供的一个插口
    • 内核模块是具有独立功能的程序,他可以被单独编译,但不能单独运行。他在运行时被链接到内核作为内核的一部分在内核空间运行
    • 内核模块便于驱动、文件系统等的二次开发

内核模块组成

  1. 模块加载函数

    module_init(xxx_init);
    
    • module_init 函数用来向 Linux 内核注册一个模块加载函数,
    • 参数 xxx_init 就是需要注册的具体函数(理解是模块的构造函数)
    • 当加载驱动的时, xxx_init 这个函数就会被调用
  2. 模块卸载函数

    module_exit(xxx_exit);
    
    • module_exit函数用来向 Linux 内核注册一个模块卸载函数,
    • 参数 xxx_exit 就是需要注册的具体函数(理解是模块的析构函数)
    • 当使用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用
  3. 模块许可证明

    MODULE_LICENSE("GPL") //添加模块 LICENSE 信息 ,LICENSE 采用 GPL 协议
    
  4. 模块参数(可选)
    模块参数是一种内核空间与用户空间的交互方式,只不过是用户空间 --> 内核空间单向的,他对应模块内部的全局变量

  5. 模块信息(可选)

    MODULE_AUTHOR("songwei") //添加模块作者信息
    

模块操作命令

  1. 加载模块
    • insmod XXX.ko
      • 为模块分配内核内存、将模块代码和数据装入内存、通过内核符号表解析模块中的内核引用、调用模块初始化函数(module_init)
      • insmod要加载的模块有依赖模块,且其依赖的模块尚未加载,那么该insmod操作将失败
    • modprobe XXX.ko
      • 加载模块时会同时加载该模块所依赖的其他模块
  2. 卸载模块
    • rmmod XXX.ko
  3. 查看模块信息
    • lsmod
      • 查看系统中加载的所有模块及模块间的依赖关系
    • modinfo (模块路径)
      • 查看详细信息,内核模块描述信息,编译系统信息

4.4 设备号

  • Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成
  • 主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。
  • Linux 提供了一个名为 dev_t 的数据类型表示设备号其中高 12 位为主设备号, 低 20 位为次设备
  • 使用"cat /proc/devices"命令即可查看当前系统中所有已经使用了的设备号(主)
MAJOR // 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
MINOR //用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
MKDEV //用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。

搞笑gif下载