嵌软面试每日一阅----Linux驱动(一)

张开发
2026/4/13 17:30:21 15 分钟阅读

分享文章

嵌软面试每日一阅----Linux驱动(一)
一、Linux驱动分类按设备类型分类 (最核心的分类)这种分类方式决定了应用程序如何与硬件进行交互是理解Linux驱动架构的基石。设备类型访问方式特点典型设备与设备节点示例字符设备 (Character Device)按字节流顺序访问数据读写不经过系统缓存直接传输支持随机访问。键盘、鼠标、串口 (/dev/ttyS*)、LED (/dev/led)、I2C/SPI设备 (/dev/i2c*)块设备 (Block Device)以固定大小的“块”为单位访问数据读写经过系统缓存支持随机访问可以挂载文件系统。硬盘 (/dev/sda)、SSD (/dev/nvme0n1)、U盘 (/dev/sdb)、SD卡 (/dev/mmcblk0)网络设备 (Network Device)通过网络协议栈如Socket访问没有对应的设备节点文件数据传输以“数据包”为单位。以太网卡 (eth0)、无线网卡 (wlan0) 按加载方式分类这决定了驱动是如何被集成到内核中的。静态驱动 (Built-in)直接编译进内核镜像vmlinuz中随系统启动时自动加载无法在运行时卸载。适用场景系统启动所必需的关键硬件驱动如主板芯片组、根文件系统所在的存储设备驱动。动态驱动 (Loadable Kernel Module, LKM)以.ko(Kernel Object) 文件形式独立存在可以通过insmod、modprobe等命令在系统运行时动态加载或卸载。优点灵活性高便于更新和维护不占用常驻内存。这是最常见的驱动形式。 按硬件总线分类这种分类方式与硬件的物理连接方式直接相关驱动需要适配特定的总线协议。PCI/PCIe 驱动用于显卡、高速网卡、NVMe固态硬盘等。USB 驱动用于U盘、摄像头、打印机等支持热插拔的设备。I2C/SPI 驱动常用于嵌入式系统中的传感器、EEPROM、触摸屏等。平台总线 (Platform Bus) 驱动用于SoC片上系统内部集成的、无法被精确探测的设备如GPIO控制器、PWM、片内ADC等。️ 按功能层次分类硬件抽象层 (HAL) 驱动直接与硬件寄存器打交道进行最底层的硬件操作。协议驱动实现特定的通信协议如TCP/IP协议栈、USB HID协议等。虚拟设备驱动在内核中模拟出一个硬件设备如loop设备将文件模拟为块设备、KVM虚拟化驱动。二、什么是 Linux 驱动驱动的作用是什么 驱动的核心作用驱动的作用远不止“初始化硬件”那么简单它贯穿了硬件使用的整个生命周期。识别并初始化硬件在系统启动或设备插入时驱动负责给硬件上电、复位、配置寄存器和通信参数把硬件从“不能用”的状态变成“能用”。长期管理和控制硬件驱动是硬件的“终身代理”。它不仅负责启动还要处理运行中的所有事务例如数据读写在应用程序和硬件之间传输数据。中断处理响应硬件发出的紧急信号如按键按下、数据接收完成。电源管理控制硬件的休眠与唤醒以节省能耗。错误恢复处理硬件运行中可能出现的异常情况。向上提供统一接口这是驱动最重要的价值。它将复杂的硬件操作如操作特定寄存器、遵循严格的时序协议隐藏起来向上层暴露简单的标准接口。对应用程序而言它们只需要调用open()、read()、write()等标准系统调用或者访问/dev/下的设备文件就能使用硬件功能完全不需要知道底层是哪种芯片、通过什么总线连接。举个例子当你想让一个 LED 灯亮起时你的程序只需向对应的设备文件写入一个“1”。驱动接收到这个简单的指令后会自动完成所有复杂的底层操作比如通过 I2C 或 SPI 总线向特定的寄存器发送精确的指令序列。 为什么不能让应用直接操作硬件直接让应用程序操作硬件是不可行且非常危险的主要原因有硬件差异巨大不同厂商、不同型号的硬件其寄存器地址和操作方式千差万别。如果让应用直接操作将为每一款硬件重写整个应用毫无通用性可言。安全性与稳定性硬件操作需要极高的权限。如果允许用户态的应用程序直接访问硬件一个编写不当的程序就可能导致系统崩溃Kernel Panic或硬件损坏。驱动运行在内核态由内核统一管理和保护。资源协调复杂多个程序可能同时访问同一个硬件。驱动负责处理并发访问、加锁、DMA直接内存访问等复杂问题确保系统稳定有序地运行。⚡ 核心结论总而言之Linux 驱动的本质是硬件抽象层。它负责初始化、管理硬件并将硬件能力以标准、安全的方式提供给上层使用是 Linux 系统能够支持海量硬件设备的关键。三、内核态 vs 用户态的区别在 Linux 驱动开发的语境下内核态与用户态的界限不仅是权限的高低更是性能瓶颈与系统稳定性的分水岭。️ 核心区别速览表维度用户态 (User Mode)内核态 (Kernel Mode)CPU 权限Ring 3(受限不可执行特权指令)Ring 0(最高可操作硬件/寄存器)地址空间独立的虚拟空间(0 ~ 3GB)进程间隔离共享的内核空间(3GB ~ 4GB)全局可见上下文切换极高开销(需保存/恢复现场、刷新 TLB)无切换开销 (直接执行)异常/崩溃Segmentation Fault(仅杀死当前进程)Kernel Panic(导致整机死机/重启) 深度解析1.权限硬件操作的“通行证”用户态Ring 3受限执行CPU 处于低特权级。如果你的驱动代码尝试执行特权指令如关闭中断cli、修改页表基址寄存器CR3、直接操作 I/O 端口CPU 会立即触发通用保护异常内核会介入并终止你的程序。硬件访问无法直接访问硬件。必须通过系统调用如ioctl请求内核代为操作。内核态Ring 0上帝视角拥有对所有硬件资源的完全控制权。驱动程序在此模式下可以直接读写硬件寄存器、配置 DMA、响应中断。特权指令可以执行任何 CPU 指令包括那些能影响系统全局状态的指令。2.地址空间内存的“隔离墙”Linux 将虚拟内存空间一分为二以 32 位系统为例用户空间0 ~ 3GB私有性每个进程都有自己独立的 3GB 空间。进程 A 的指针如0x12345678在进程 B 中可能指向完全不同的数据甚至无效。驱动影响用户态驱动无法直接访问内核缓冲区数据传输必须经过拷贝。内核空间3GB ~ 4GB共享性所有进程共享同一块内核空间。内核驱动分配的内存如kmalloc对所有进程在内核态下都是可见的。直接映射内核空间通常与物理内存有简单的线性映射关系方便直接操作物理地址。3.上下文切换性能的“隐形杀手”这是驱动开发中最需要关注的性能点。当用户态应用调用驱动系统调用时会发生以下昂贵的过程切换流程保存现场将用户态的寄存器EIP, ESP 等压入内核栈。切换页表修改 CR3 寄存器切换到内核页表这会刷新TLB导致 CPU 缓存命中率下降。执行内核运行驱动代码。数据拷贝通常需要将数据从用户缓冲区拷贝到内核缓冲区copy_from_user处理完后再拷回copy_to_user。恢复现场恢复寄存器切回用户页表。代价频繁的“用户态 - 内核态”切换会消耗大量 CPU 周期。对于高频小包的数据传输如高速串口通信这种开销可能比实际数据处理还要大。4.异常处理崩溃的“代价”用户态异常如果用户态驱动访问了非法地址空指针CPU 触发异常内核捕获后发送SIGSEGV信号。结果是程序崩溃退出但操作系统和其他应用安然无恙。内核态异常如果内核驱动访问了非法地址如解引用空指针由于此时 CPU 处于 Ring 0且内核没有像用户态那样的保护机制来“杀掉”自己结果通常是Kernel Panic。此时系统会立即停止运行并打印堆栈信息必须硬重启。 总结与建议在 Linux 驱动设计中理解这些区别至关重要能少切就少切尽量减少系统调用的频率使用mmap将内核内存映射到用户空间可以避免数据拷贝和频繁的上下文切换。内核态要极度小心内核驱动代码必须经过严格测试因为一个微小的指针错误就能让整个服务器宕机。用户态驱动是趋势对于非实时性要求极高、逻辑复杂的设备如打印机、部分 USB 设备利用UIO或VFIO框架将驱动逻辑放在用户态虽然牺牲了一点点性能但换来了极高的系统稳定性。一句话总结在 Linux 驱动开发中内核态是为了追求极致性能和直接硬件控制承担系统崩溃的风险而用户态则是为了开发便捷和系统稳定牺牲了一定的性能和直接访问权。四、Linux 设备号是什么主设备号、次设备号作用在 Linux 驱动开发中设备号Device Number是内核用来标识和管理设备的“身份证号”。Linux 遵循“一切皆文件”的设计理念硬件设备在系统中表现为/dev目录下的设备文件如/dev/sda或/dev/ttyS0。当你操作这些文件时内核并不关心文件名是什么它只认文件背后关联的设备号。设备号是一个 32 位的数值dev_t类型由两部分组成主设备号Major Number高 12 位。次设备号Minor Number低 20 位。你可以把设备号想象成快递地址主设备号是“小区名”决定由哪个物业/驱动接管次设备号是“门牌号”决定具体送到哪一户。主设备号 (Major Number)核心作用标识驱动程序设备类别。驱动身份证主设备号主要用于告诉内核“这个设备属于哪一类”或者更准确地说“应该调用哪个驱动程序来处理它”。关联驱动内核通过主设备号找到对应的file_operations结构体即驱动提供的 open/read/write 等函数集合。示例主设备号1通常对应mem驱动内存设备。主设备号4通常对应tty驱动终端设备。主设备号8通常对应sd驱动SCSI 磁盘。注意在传统的 Linux 模型中一个主设备号对应一个驱动程序。虽然现代内核支持一个主设备号对应多个驱动通过设备号范围但“主设备号标识驱动类型”依然是核心逻辑。次设备号 (Minor Number)核心作用标识具体的物理设备设备实例。区分实例当一个驱动程序管理多个相同的硬件时次设备号用来区分它们。驱动程序内部通过次设备号来判断用户操作的是哪一个具体的硬件实例。驱动内部索引内核把请求交给驱动后就不再管次设备号的具体含义了它完全由驱动程序自己解释。示例对于磁盘驱动主设备号 8次设备号0可能代表第一块硬盘/dev/sda。次设备号1可能代表第一块硬盘的第一个分区/dev/sda1。对于串口驱动主设备号 4次设备号64可能代表/dev/ttyS0。次设备号65可能代表/dev/ttyS1。 它们如何协同工作当你在应用程序中执行open(/dev/mydev, ...)时内核的工作流程如下查找设备号内核查看/dev/mydev文件关联的设备号例如200, 5。定位驱动查主号内核根据主设备号 200在内核驱动列表中查找注册了该主号的驱动程序。调用函数找到驱动后内核调用该驱动的open函数。区分设备查次号内核将设备号传递给驱动驱动程序通过解析次设备号 5知道用户想要打开的是它管理的第 6 个假设从0开始具体设备从而进行针对性的初始化或操作。️ 常用操作与查看方法1. 查看设备号你可以使用ls -l命令查看/dev下的设备文件。输出中的两个数字即为设备号。$ ls -l /dev/sda brw-rw---- 1 root disk 8, 0 Oct 28 10:00 /dev/sda这里8是主设备号0是次设备号。你也可以查看/proc/devices文件这里列出了当前系统已注册的主设备号及其对应的驱动名称$ cat /proc/devices Character devices: 1 mem 4 tty 4 ttyS ... Block devices: 8 sd websourcesource_group_web_12/websource #### 2. 代码中的操作宏 在编写驱动时内核提供了一组宏来处理 dev_t 类型的设备号websourcesource_group_web_13/websource | 宏定义 | 功能描述 | | :--- | :--- | | MKDEV(major, minor) | 将主、次设备号组合成一个 dev_t 数值websourcesource_group_web_14/websource。 | | MAJOR(dev) | 从 dev_t 中提取主设备号websourcesource_group_web_15/websource。 | | MINOR(dev) | 从 dev_t 中提取次设备号websourcesource_group_web_16/websource。 | ### 总结 * **主设备号** **驱动程序的代号**决定由谁来处理。 * **次设备号** **具体设备的编号**决定处理哪一个。 * 两者结合构成了 Linux 设备文件的唯一标识实现了从用户空间文件操作到内核空间硬件控制的精准映射。 [(video_note_list_1)]注文章随手记录如有错误评论区交流

更多文章