驱动知识点归纳一
1. 什么是内核模块,他的作用是什么
- 内核模块是指可以动态加载到操作系统内核中的代码块,通常是为了扩展内核的功能而使用的。它们是Linux内核的重要组成部分,允许用户在不需要重新编译整个内核的情况下,向内核添加功能,或者卸载不再需要的功能。
- 用途:设备驱动程序,文件系统支持,网络协议栈
2. 简述字符设备的作用,如何实现一个字符设备,有那些步骤?
- 字符设备(Character Device)是 Linux 中设备驱动的一种类型,它允许设备和操作系统之间通过字符流的方式进行交互。字符设备是以字符流(字节流)的方式处理数据的,通常是逐字节地读写数据,适合处理按顺序传输的数据。
- 主要作用
- 数据流的逐字节处理
- 提供设备接口
- 用户态与内核态交互
实现一个字符设备驱动
- 向内核申请设备号
- 创建字符设备驱动对象
- 实现文件操作接口
- 创建设备类
- 创建设备节点
- 模块退出函数,逆序释放以上步骤
3. 如何解决内核中同步/并发的问题?他们之间的区别是什么?
- 为了处理内核中的同步和并发问题,Linux 提供了多个机制来确保共享资源在多线程或多进程环境中的正确性。
- 常见的同步机制
- 原子操作(Atomic Operations)
- 自旋锁(Spinlocks)
- 读写锁(Read-Write Locks)
- 信号量(Semaphores)
- 互斥锁(Mutexes)
同步和并发的区别是什么?
- 同步指的是按照一定的顺序执行多个任务,确保这些任务在访问共享资源时不会发生冲突。同步的核心问题是:如何保证在同一时间只有一个任务访问临界区,防止数据竞争和不一致的情况出现。
- 并发指的是多个任务在同一时间段内可以“同时”执行。并发的核心问题是:如何管理多个任务在同一时间段内交替执行,并且有效共享系统资源(如 CPU、内存、文件等)。
- 例如读写锁
4. IO模型大概有几种,区别是什么?
- 阻塞 I/O
在阻塞 I/O 模型中,进程发起 I/O 操作后会阻塞,直到 I/O 操作完成(即数据全部读入或写出)时,进程才继续执行其他任务。整个操作过程中,进程处于等待状态,不会进行其他任务。 - 非阻塞 I/O(轮询访问)
在非阻塞 I/O 模型中,进程发起 I/O 操作时,即使数据未准备好,系统也会立即返回,不会阻塞进程。进程可以周期性地检查 I/O 操作是否完成(通常使用轮询),从而避免了长时间阻塞。 - I/O 复用
I/O 复用模型允许一个进程同时等待多个文件描述符(如多个 socket)的就绪状态。一旦某个文件描述符就绪,进程会被通知并开始处理该 I/O 操作。常见的 I/O 复用函数有 select、poll 和 epoll。 - 信号驱动 I/O
信号驱动 I/O 模型利用操作系统的信号机制。当 I/O 操作可以进行时,操作系统向进程发送一个信号,通知它可以开始处理 I/O 操作。这种模型不需要进程阻塞或轮询文件描述符的状态。 - 异步 I/O
在异步 I/O 模型中,进程发起 I/O 请求后,不需要等待 I/O 操作完成,而是继续执行其他任务。操作系统负责完成 I/O 操作,并在完成后通知进程。
I/O 模型 | 描述 | 进程状态 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|---|
阻塞 I/O | 进程发起 I/O 后阻塞,直到 I/O 完成 | 阻塞,进程等待 I/O 完成 | 简单直观 | 效率低,等待期间无法处理其他任务 | 单任务程序,低并发场景 |
非阻塞 I/O | I/O 操作立即返回,进程需要轮询检查 I/O 是否就绪 | 非阻塞,但需要反复检查 | 进程不会阻塞,可以同时处理其他任务 | 轮询消耗 CPU,编程复杂 | 等待时间短的 I/O 操作 |
I/O 复用 | 允许一个进程同时等待多个 I/O 就绪(select 、poll 、epoll ) | 阻塞等待 I/O 就绪,但可以等待多个 I/O | 能处理多个 I/O 请求,提高并发性能 | select 和 poll 效率低,epoll 较好 | 高并发网络服务器 |
信号驱动 I/O | 进程通过信号处理 I/O 就绪事件 | 非阻塞,通过信号通知 | 不需要轮询,进程可以继续处理其他任务 | 编程复杂,信号处理要求高 | 实时性要求高,异步 I/O 操作 |
异步 I/O | I/O 操作由操作系统完成,I/O 完成后通知进程 | 非阻塞,I/O 完成时被通知 | 完全异步,进程无需等待 I/O 完成 | 实现复杂,依赖操作系统支持 | 高性能网络服务器,高并发场景 |
5. 中断将任务推迟的方法有哪些? 他们的区别是什么?
- 软中断
软中断是 Linux 内核中用于处理高优先级的可延迟任务的机制。软中断有固定的数量,并且通常用于处理非常频繁的操作,如网络包处理。 - 任务队列(Tasklet)
任务队列是基于软中断机制实现的一种更简单的方式,用于将一些较轻量的中断任务推迟执行。任务队列通常用于较少发生的、相对轻量的任务。 - 工作队列(Workqueue)
工作队列是在内核中以线程的形式执行延迟任务的机制。与软中断和任务队列不同,工作队列是在内核线程中运行的,因此可以进行阻塞和睡眠操作,适合处理较复杂或耗时的任务。
机制 | 执行上下文 | 是否可阻塞 | 优先级 | 适用场景 |
---|---|---|---|---|
软中断 | 中断上下文 | 否 | 高 | 频繁发生的高优先级事件处理 |
任务队列 | 中断上下文 | 否 | 较高 | 较少发生的、轻量的任务 |
工作队列 | 线程上下文 | 是 | 低 | 长时间运行的复杂任务、I/O 操作 |
- 上半部分
上半部分是在中断发生时立即执行的部分,处理紧急的硬件响应任务。
不能执行耗时操作,尽快完成任务。
不能进行阻塞或睡眠操作。 - 下半部分(Bottom Half)
下半部分是将非紧急的中断处理推迟执行的一部分,用于执行耗时的任务。
通过软中断、任务队列或工作队列实现
可以避免中断长时间占用 CPU,提高系统的响应效率。
6. 驱动模型的工作过程
Linux 驱动模型主要由以下几个关键部分组成:总线(Bus)、设备(Device) 和 驱动(Driver)。它们协同工作,帮助内核在启动时识别硬件并绑定合适的驱动程序。
- 设备的注册
当硬件设备(如 PCI 设备、USB 设备)被发现时,内核中的总线子系统会检测到这些设备的存在,并调用相应的设备驱动模型接口注册这些设备。
注册时,每个设备都会被封装为一个 struct device 结构,并附加到对应的总线(如 PCI 总线、USB 总线)上。
设备被注册后,内核会将其加入设备列表中,表示这个设备已经被内核识别 - 驱动的注册
当设备驱动程序被加载时,它会通过 struct driver 结构注册到内核中,并挂载到相应的总线子系统上。
驱动程序注册后,内核会将其加入驱动程序列表中,表示该驱动已经可以为某些设备服务。
每个驱动程序都会指定自己支持的设备类型,通常通过设备的 ID 或设备树来标识驱动与设备的对应关系。 - 设备与驱动的匹配
设备和驱动注册完成后,总线子系统会尝试在设备和驱动之间进行匹配。内核通过比较设备和驱动的 ID 或者设备特定属性(如 PCI ID、USB ID)来进行匹配
如果找到合适的驱动程序,内核会调用该驱动的 probe 函数,绑定驱动与设备,完成设备初始化并开始与硬件交互 - 驱动的加载与卸载
驱动程序加载时,内核通过驱动模型框架注册驱动,并将驱动与对应的设备绑定
总结
- 设备注册 → 设备添加到设备列表中。
- 驱动注册 → 驱动程序添加到驱动列表中。
- 设备与驱动匹配 → 匹配成功后,调用 probe 函数进行设备初始化。
- 设备驱动加载完成 → 设备驱动开始与硬件设备交互。
7. 进程的启动和终止方式
- 在操作系统中,进程的启动可以分为两种主要类型:手动启动和调度启动
- 手动启动
./my_program //前台启动一个进程
./my_program & //后台启动一个进程
- 调度启动:系统根据系统资源和进程占用资源情况,事先进行调度安排, 制定任务运行的时间、场合,到时候系统自动完成该任务
终止方式
- 自然终止
- 用户手动终止
- 异常终止
- 系统调度终止
8. I2C、SPI和UART通信协议
- I2C 是一种 同步传输协议,使用两根线来进行通信
- SDA(数据线):传输数据
- SCL(时钟线):提供时钟信号,保证同步传输
- 工作原理
- I2C 是主从式架构,主设备通过 SDA 和 SCL 与从设备通信。在通信过程中,主设备发送 7 位地址(或 10 位)来指定目标从设备。
- 主机开始信号后,先发送7bit的地址位和1bit的读写位,每个从机都有 自己的I2C地址,当发现该条指令是发给自己的,就会拉低SDA线(即回复ACK信号),然后主机发送或接受数据,结束时主机发送停止消息
- I2C 常用于需要控制多个外设(如传感器、显示器、存储器等)的场景,特别适合通信速率要求不高的嵌入式系统
- SPI 是另一种同步通信协议,使用四根线进行数据传输
- CS(片选):选择与哪个从设备进行通信。
- MOSI(主机发,从机收):主设备向从设备发送数据。
- MISO(从机发,主机收):从设备向主设备发送数据。
- CLK(时钟):提供同步时钟信号。
- 工作原理
- SPI 使用 主从模式,但与 I2C 不同,SPI 的从设备是通过 片选信号(CS) 来进行选择的,而不是通过地址
- 主设备将 CS 线拉低,开始通信,传输时数据通过 MOSI 和 MISO 线进行交换,时钟信号(CLK)用于同步数据传输。
- 数据传输完成后,主设备将 CS 线拉高,结束通信
- 优缺点
- 高速、全双工、简单协议
- 不支持多主设备,线缆需求高
- SPI 常用于高速数据传输的场景,如闪存、液晶显示器、音频设备和存储设备等
- UART 是一种 异步传输协议,不使用时钟信号进行同步传输
- TXD(发送数据):数据发送线。
- RXD(接收数据):数据接收线。
- GND(地线):用于参考电压。
- 工作原理
- UART 是异步通信,发送和接收端通过约定的 波特率(双方都必须配置相同的波特率)来确定数据传输速率
- 数据通过帧结构(起始位、数据位、校验位和停止位)来保证传输的完整性
- UART 的主从设备可以同时自由地发送和接收数据,不需要像 I2C 和 SPI 那样进行主从同步协调
- 优缺点
- 硬件实现简单、没有时钟线、双向通信
- 速率不高、数据传输不确定性
- 在一对多通信时I2C更具优势,但是I2C总线的速度要低于SPI
- UART 常用于点对点的串行通信,如微控制器与传感器、调试接口或简单的串口外设之间的通信
特点 | I2C | SPI | UART |
---|---|---|---|
通信模式 | 同步传输 | 同步传输 | 异步传输 |
线数 | 2 根(SDA、SCL) | 4 根(CS、MOSI、MISO、CLK) | 3 根(TXD、RXD、GND) |
设备选择方式 | 通过地址进行选择 | 通过片选信号(CS)进行选择 | 无主从概念,双方自由收发数据 |
速率 | 较低,标准模式 100 kbps,高速模式 400 kbps | 较高,通常可达几十 Mbps | 低,通常几 Mbps 以下 |
优点 | 简化了多设备通信,只需两根线 | 传输速度快,全双工传输 | 硬件简单,易于实现 |
缺点 | 传输速度较慢,复杂环境下抗干扰能力差 | 每增加一个从设备需增加一根 CS 线 | 需要双方严格同步波特率,速率较低 |
应用场景 | 低速外围设备,如传感器、EEPROM、ADC 等 | 高速通信设备,如显示器、音频设备、闪存等 | 串行通信,如调试接口、点对点传感器通信 |
9. 内核调试方法
点灯法、printk 调试、日志系统(dmesg)、/proc 和 sysfs 文件系统
10.怎么操作寄存器
在内核中操作硬件寄存器时,需要通过映射物理地址到虚拟地址,然后使用相关函数进行读写操作。
- ioremap:将物理地址映射为虚拟地址
- 将硬件设备的物理地址映射为内核可以访问的虚拟地址,方便后续对寄存器的读写
- writel/readl:读写寄存器
- 通过读写寄存器的方式与硬件设备进行交互,比如控制设备的启动、数据传输等
- iounmap:使用完映射的虚拟地址后,必须通过 iounmap 释放
11.Linux有哪些设备/区别
- 字符设备
- 特点:按字节顺序读取或写入,不能随机访问,数据流式处理。
- 例子:串口、键盘、鼠标等
- 块设备
- 可以随机访问,按块读取或写入数据
- 硬盘、光驱、U 盘等
- 网络设备
- 特点:通过网络接口与其他主机进行数据交换。
- 例子:以太网卡、Wi-Fi 网卡等。
12. Kmalloc/vmalloc的区别
- Kmalloc
- 特点:分配物理地址和虚拟地址都连续的内存。
- 大小限制:适合分配小块内存,通常 32B 到 128KB。
- 效率:效率高,适合高性能需求的场景。
- vmalloc:
- 特点:分配的虚拟地址连续,但物理地址可能不连续。
- 大小限制:适合分配大块内存,没有严格的大小限制。
- 效率:效率较低,因为需要处理不连续的物理内存。
- kmalloc 适合小块内存的高效分配,vmalloc 适合需要分配较大块内存的场景,尽管效率较低。
13. 中断下半部分的类型及区别
- 软中断:优先级最高,内核管理。
- Tasklet:驱动层使用,不能睡眠。基于软中断实现,优先级低于软中断
- 工作队列:可以睡眠,适合耗时任务,灵活性高
14. Linux 系统启动流程
- 加载内核:引导程序(如 GRUB)加载内核并启动。
- 启动初始化:内核初始化硬件和启动第一个进程 init。
- 确定运行级别:init 读取配置文件,确定运行级别或目标(如多用户模式)。
- 加载启动程序:启动与运行级别对应的系统服务。
- 用户登录:显示登录界面,用户输入用户名和密码。
- 进入 login shell:用户登录后,启动登录 shell。
- 打开 non-login shell:用户在终端或远程会话中打开非登录 shell。
15. 内核同步机制及区别
同步机制 | 特点 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
原子变量 | 原子性操作,常用于计数 | 高效、开销小 | 仅适合简单的并发操作 | 计数器、简单变量操作 |
自旋锁 | 资源不可用时轮询,不会进入睡眠 | 响应快,适合短期操作 | 占用 CPU 资源,长时间锁定浪费 CPU | 短期临界区,频繁锁定解锁操作 |
互斥锁 | 资源不可用时进程睡眠,等待资源释放 | 不浪费 CPU 资源 | 进程上下文切换开销 | 长时间临界区操作 |
中断屏蔽 | 禁止中断打断关键代码,但无法阻止高优先级中断 | 确保中断不干扰关键代码 | 不能阻止优先级高的中断发生 | 关键代码执行期间避免中断干扰 |
16. 用户与内核的通信方式
- API(应用程序接口):通过标准的函数接口,用户程序可以调用内核提供的服务
- 常用于执行系统调用和硬件访问
- proc 文件系统
- 通过读写 /proc 下的文件,用户可以与内核交互,获取系统状态
- sysfs 文件系统
- 提供设备驱动与内核参数的接口,便于用户态程序调整系统行为
- 系统调用
- 通过映射内核空间地址到用户空间,用户进程可以调用内核功能。
- 信号
- 内核向进程发送异步信号,通知进程某些事件发生。
17. 多路复用
机制 | 特点 | 优点 | 缺点 |
---|---|---|---|
select | 监控文件描述符数量有上限(通常为 1024) | 简单易用,广泛兼容 | 监控上限限制,无法直接获取有事件的 fd |
poll | 监控文件描述符数量无上限 | 支持任意数量的文件描述符 | 无法直接获取有事件的 fd ,遍历效率较低 |
epoll | 监控文件描述符数量无上限 | 监控效率高,能够直接获取有事件的 fd | 仅适用于 Linux 平台,编程复杂度较高 |
18.进程间通信方式及它们的区别
通信方式 | 特点 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
无名管道 | 半双工(数据单向流动),只能用于具有亲缘关系的进程(如父子进程)。 | 简单、易于使用。 | 仅限父子进程,单向通信,数据无格式。 | 父子进程间的数据传递 |
有名管道(FIFO) | 半双工,允许无亲缘关系的进程间通信,通过文件系统命名管道。 | 无亲缘关系的进程间也能通信。 | 单向通信,数据无格式,需维护管道文件。 | 无亲缘关系进程间的简单通信 |
消息队列 | 内核中维护的消息链表,通过消息队列标识符进行标识,支持有格式的数据传输。 | 支持格式化数据传输,灵活性高,支持并发。 | 内核维护的消息队列大小有限,操作复杂。 | 需要发送格式化消息的多进程通信 |
信号量 | 用于控制多个进程对共享资源的访问,常用于实现进程间的同步和互斥,基于计数器机制。 | 解决进程同步和互斥问题。 | 不能传递数据,仅用于进程间同步和互斥控制。 | 需要进程同步和资源竞争的场景 |
信号 | 用于向进程发送异步通知,进程接收到信号后可以采取特定的处理方式。 | 机制简单,支持异步通知。 | 信息量少,复杂度高,处理信号需谨慎。 | 进程终止、事件通知等异步操作 |
共享内存 | 一段被多个进程访问的内存空间,允许多个进程直接访问同一块内存。 | 速度快,内存共享效率高。 | 需要使用同步机制来协调内存访问。 | 高速数据共享,配合同步机制使用 |
套接字(Socket) | 支持同一台或不同机器之间的进程通信,既可以用于本地进程通信,也可以用于网络通信。 | 支持跨网络通信,灵活性高。 | 需要维护网络连接,通信复杂度较高。 | 网络通信,跨主机进程间通信 |