ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

嵌入式 Linux 笔记:应用程序、设备驱动与内核之间的关系

嵌入式 Linux 笔记:应用程序、设备驱动与内核之间的关系

一、整体关系

在典型嵌入式 Linux 系统中:

  • 应用程序运行在用户空间,负责实现产品业务功能。
  • 设备驱动通常运行在内核空间,负责控制具体硬件。
  • Linux 内核负责进程调度、内存管理、文件系统、网络协议栈、设备管理,并提供系统调用和设备子系统接口。
  • 硬件负责真正的数据采集、发送、显示、存储等工作。

可以记为:

应用程序提出请求 -> 内核接收并分发请求 -> 驱动操作硬件 -> 硬件完成工作。

注意:标准 Linux 设备驱动通常是内核的一部分,并不是与内核并列的独立软件层。

二、用户空间和内核空间

1. 用户空间(User Space)

普通应用程序运行的区域,例如串口通信程序、摄像头采集程序、Qt 界面程序、RTSP 视频流程序。

应用程序通常不能直接访问内核地址空间,也不能随意执行特权操作。需要通过系统调用或内核提供的接口请求内核完成工作。

2. 内核空间(Kernel Space)

Linux 内核运行在具有较高权限的执行环境中,包含:

  • 进程调度
  • 内存管理
  • 虚拟文件系统 VFS
  • 网络协议栈
  • 设备驱动
  • 中断处理
  • V4L2、ALSA、input 等设备子系统

3. 为什么区分两者?

  • 隔离应用程序和内核,降低普通程序错误破坏系统的风险。
  • 防止应用程序随意访问其他进程或内核内存。
  • 由内核统一管理硬件资源。
  • 向应用程序提供相对统一的接口,屏蔽硬件寄存器细节。

用户空间和内核空间不是两颗不同的 CPU,而是 Linux 对执行权限和地址空间进行的划分。

三、设备驱动是什么

设备驱动(Device Driver)是内核中负责管理特定设备的软件。

不同硬件的控制方式不同:

  • UART:波特率、数据位、停止位、收发 FIFO。
  • 摄像头:分辨率、像素格式、采集缓冲区。
  • GPIO:引脚方向、电平、复用配置。
  • I2C 设备:按照 I2C 总线协议发送地址和数据。

驱动负责处理硬件相关细节,并向上层提供相对统一的接口。常见工作包括:

  1. 设备初始化和资源申请。
  2. 配置硬件寄存器。
  3. 设置 DMA 和数据缓冲区。
  4. 处理中断。
  5. 与 I2C、SPI、USB 等总线或设备子系统协作。
  6. 将硬件操作封装成内核接口。

驱动可能通过MMIO(内存映射 I/O)、中断、DMA、总线控制器等方式与硬件交互。

四、设备节点是什么?和驱动有什么区别?

常见设备节点位于/dev:

  • /dev/ttyS1
  • /dev/ttyUSB0
  • /dev/video0
  • /dev/input/event0

设备节点是用户空间访问设备驱动的一种入口,不等于设备驱动本身。

三者区别:

  • 设备节点:用户空间访问设备驱动的一种入口。
  • 设备驱动:内核中负责处理设备操作的代码。
  • 硬件设备:实际的 UART 控制器、摄像头、GPIO 控制器等。

例如 /dev/video0 只是一个设备节点。应用程序打开它后,内核根据设备节点关联的信息找到对应设备对象和操作接口。

并不是所有设备或内核功能都必须通过 /dev 访问:

  • 网络通信通常使用socket。
  • 设备属性可能通过/sys暴露。
  • 进程和系统信息常见于/proc。

五、应用程序如何通过系统调用访问驱动

以open("/dev/xxx", ...)为例,典型过程:

  1. 应用程序调用用户空间的 open() 接口。
  2. C 库中的 open() 封装触发系统调用,进入内核。
  3. 内核中的VFS(虚拟文件系统)处理路径查找。
  4. 内核找到设备节点及其关联的设备对象。
  5. VFS 或设备子系统调用驱动注册的相应操作函数。
  6. 驱动执行设备相关操作。
  7. 内核把结果返回给应用程序,例如返回文件描述符 fd。

后续调用 read()、write()、ioctl() 时,内核根据 fd 找到对应的内核文件对象,再分发到相应操作。

必须区分:

  • 用户空间的 open():应用程序调用的库函数接口。
  • 系统调用入口:从用户态进入内核态的机制。
  • 驱动的 open 回调:驱动或设备子系统注册的内核操作函数。

它们不是同一个函数,而是在一次设备打开流程中前后衔接。

六、常见用户接口

1. open()

打开文件或设备节点。成功通常返回非负文件描述符;失败返回-1,并设置errno。

2. read()

读取数据。返回值可能小于请求长度,不保证一次读满。

3. write()

写入数据。返回值可能小于请求长度,需要根据具体场景处理部分写入。

4. ioctl()

执行设备相关控制操作,例如查询能力、设置参数、申请缓冲区。具体命令由设备子系统或驱动定义。

5. mmap()

将文件或设备提供的内存区域映射到用户进程地址空间。某些驱动会把 DMA 缓冲区映射给用户程序。mmap 并不意味着应用程序可以随意访问所有物理地址。

6. close()

关闭文件描述符,释放或减少相关资源引用。

七、摄像头例子:应用程序、V4L2、驱动、硬件如何协作

假设开发板接入摄像头,系统出现/dev/video0。

关系:

用户空间摄像头应用程序
-> open /dev/video0、ioctl、mmap
->内核 V4L2 框架
-> 视频采集控制器驱动、传感器子设备驱动等
->摄像头及 SoC 视频采集硬件

典型 V4L2 流程:

  1. open("/dev/video0"):打开视频设备。
  2. VIDIOC_QUERYCAP:查询设备能力。
  3. VIDIOC_ENUM_FMT:枚举支持的像素格式。
  4. VIDIOC_S_FMT:设置分辨率和像素格式。
  5. VIDIOC_REQBUFS:向驱动申请视频缓冲区。
  6. mmap():把驱动提供的缓冲区映射到用户空间。
  7. VIDIOC_QBUF:把空闲缓冲区交给驱动排队。
  8. VIDIOC_STREAMON:启动采集。
  9. VIDIOC_DQBUF:取回已经采集完成的缓冲区。
  10. 应用程序处理图像,例如显示、编码或推流。
  11. VIDIOC_STREAMOFF,最后 munmap()、close(),释放资源。

V4L2 是 Linux 内核的视频设备子系统/框架。具体硬件驱动接入 V4L2,应用程序通过 V4L2 规定的 ioctl 接口操作设备。

复杂 MIPI 摄像头链路可能同时涉及图像传感器子设备驱动、CSI 接收端驱动、视频采集节点等多个组件,不一定只有一个驱动。

八、串口例子:应用程序如何使用 UART

假设串口设备为/dev/ttyS1,应用程序通常:

  1. open("/dev/ttyS1", O_RDWR | O_NOCTTY)。
  2. tcgetattr()获取串口配置。
  3. 设置波特率、数据位、停止位、校验位等termios参数。
  4. tcsetattr()应用配置。
  5. read()接收数据。
  6. write()发送数据。
  7. close()关闭设备。

应用程序通过 termios 和文件 I/O 接口操作串口;底层 UART 驱动负责配置 UART 控制器、处理中断或 DMA、收发 FIFO 数据等。

注意:

  • /dev/ttyS1是设备节点;
  • UART 驱动是内核代码;
  • UART 控制器才是硬件。

九、不同设备的典型访问方式

设备类型用户空间常见入口常见操作
串口/dev/ttyS1open/read/write/termios
摄像头/dev/video0V4L2 ioctl/mmap
输入设备/dev/input/event0read,解析输入事件
GPIO/dev/gpiochipNGPIO character device API
网络设备socketsocket/bind/connect/send/recv
LED 属性/sys/class/leds/...读写 sysfs 属性(视驱动支持情况)

说明:

  • 现代 Linux GPIO 推荐了解GPIO character device接口,不要把旧式 sysfs GPIO 当作所有新系统的首选。
  • 网络接口通常通过socket和内核网络协议栈交互,不是简单地打开一个 /dev 网卡节点。
  • sysfs 属性是否可写以及具体含义,要看内核和驱动实现。

十、三者职责对比

应用程序

  • 实现产品业务逻辑。
  • 调用系统调用、设备子系统 API 或用户空间库。
  • 组织数据、处理协议、显示界面、管理线程。
  • 通常不直接操作硬件寄存器。

设备驱动

  • 处理具体硬件差异。
  • 配置设备、传输数据、处理中断和 DMA。
  • 向内核子系统注册设备和操作接口。

Linux 内核

  • 管理进程、线程、内存、文件系统和网络。
  • 管理设备及驱动。
  • 提供系统调用和内核子系统。
  • 协调 CPU、内存和硬件资源。

十一、常见误区

误区 1:/dev/video0 就是摄像头驱动。
正确:它是设备节点,是用户空间访问视频设备的入口;驱动代码在内核中。

误区 2:设备驱动和 Linux 内核是两个完全独立的软件。
正确:标准 Linux 内核驱动通常属于内核的一部分。

误区 3:应用程序调用 open() 就是直接调用驱动的 open 函数。
正确:应用程序先经过 C 库封装和系统调用进入内核,再由 VFS/设备子系统分发到驱动回调。

误区 4:所有硬件都通过 /dev 访问。
正确:网络通常使用 socket;设备属性可能通过 /sys;系统和进程信息常见于 /proc。

误区 5:应用程序使用 mmap() 就是在直接操作硬件。
正确:mmap 是内存映射机制。驱动可以把它管理的缓冲区映射给用户程序,但映射范围和权限由内核及驱动控制。

误区 6:所有摄像头功能都由一个驱动完成。
正确:复杂摄像头系统可能由传感器驱动、CSI 接收端驱动、视频采集驱动和 V4L2 框架共同完成。

十二、面试回答模板

问题:应用程序、设备驱动和 Linux 内核之间是什么关系?

回答:

在 Linux 系统中,应用程序一般运行在用户空间,设备驱动通常运行在内核空间,驱动也是内核的一部分。应用程序不会直接操作硬件寄存器,而是通过open、read、write、ioctl、mmap等接口访问设备。调用进入内核后,由 VFS 或对应的设备子系统找到相关驱动操作,驱动再完成硬件配置、数据传输、中断处理等工作。

例如摄像头应用打开 /dev/video0,通过 V4L2 ioctl 设置分辨率、申请缓冲区并启动采集;底层 V4L2 框架和具体驱动负责控制摄像头采集硬件。这样可以把业务逻辑和硬件细节分离,提高程序的通用性和可维护性。

十三、应用层开发人员需要掌握什么

  1. 能区分用户空间、内核空间和设备驱动。
  2. 知道 /dev 下的设备节点是什么,不能把节点和驱动混为一谈。
  3. 理解open/read/write/ioctl/mmap/close的作用和返回值。
  4. 能解释 open("/dev/xxx") 如何进入内核并到达驱动。
  5. 能使用termios配置串口。
  6. 能使用V4L2完成摄像头打开、格式设置、缓冲区申请和取帧。
  7. 知道驱动负责硬件细节,应用程序负责业务逻辑。
  8. 设备异常时,会检查设备节点、权限、驱动加载情况、dmesg 内核日志和/sys 设备信息。

对于应用层岗位,通常不要求一开始就能独立编写完整内核驱动,但要理解驱动向应用程序提供了什么接口,以及应用程序如何正确使用它。

笔记结束

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进