一、整体关系
在典型嵌入式 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 总线协议发送地址和数据。
驱动负责处理硬件相关细节,并向上层提供相对统一的接口。常见工作包括:
- 设备初始化和资源申请。
- 配置硬件寄存器。
- 设置 DMA 和数据缓冲区。
- 处理中断。
- 与 I2C、SPI、USB 等总线或设备子系统协作。
- 将硬件操作封装成内核接口。
驱动可能通过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", ...)为例,典型过程:
- 应用程序调用用户空间的 open() 接口。
- C 库中的 open() 封装触发系统调用,进入内核。
- 内核中的VFS(虚拟文件系统)处理路径查找。
- 内核找到设备节点及其关联的设备对象。
- VFS 或设备子系统调用驱动注册的相应操作函数。
- 驱动执行设备相关操作。
- 内核把结果返回给应用程序,例如返回文件描述符 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 流程:
- open("/dev/video0"):打开视频设备。
- VIDIOC_QUERYCAP:查询设备能力。
- VIDIOC_ENUM_FMT:枚举支持的像素格式。
- VIDIOC_S_FMT:设置分辨率和像素格式。
- VIDIOC_REQBUFS:向驱动申请视频缓冲区。
- mmap():把驱动提供的缓冲区映射到用户空间。
- VIDIOC_QBUF:把空闲缓冲区交给驱动排队。
- VIDIOC_STREAMON:启动采集。
- VIDIOC_DQBUF:取回已经采集完成的缓冲区。
- 应用程序处理图像,例如显示、编码或推流。
- VIDIOC_STREAMOFF,最后 munmap()、close(),释放资源。
V4L2 是 Linux 内核的视频设备子系统/框架。具体硬件驱动接入 V4L2,应用程序通过 V4L2 规定的 ioctl 接口操作设备。
复杂 MIPI 摄像头链路可能同时涉及图像传感器子设备驱动、CSI 接收端驱动、视频采集节点等多个组件,不一定只有一个驱动。
八、串口例子:应用程序如何使用 UART
假设串口设备为/dev/ttyS1,应用程序通常:
- open("/dev/ttyS1", O_RDWR | O_NOCTTY)。
- tcgetattr()获取串口配置。
- 设置波特率、数据位、停止位、校验位等termios参数。
- tcsetattr()应用配置。
- read()接收数据。
- write()发送数据。
- close()关闭设备。
应用程序通过 termios 和文件 I/O 接口操作串口;底层 UART 驱动负责配置 UART 控制器、处理中断或 DMA、收发 FIFO 数据等。
注意:
- /dev/ttyS1是设备节点;
- UART 驱动是内核代码;
- UART 控制器才是硬件。
九、不同设备的典型访问方式
| 设备类型 | 用户空间常见入口 | 常见操作 |
|---|---|---|
| 串口 | /dev/ttyS1 | open/read/write/termios |
| 摄像头 | /dev/video0 | V4L2 ioctl/mmap |
| 输入设备 | /dev/input/event0 | read,解析输入事件 |
| GPIO | /dev/gpiochipN | GPIO character device API |
| 网络设备 | socket | socket/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 框架和具体驱动负责控制摄像头采集硬件。这样可以把业务逻辑和硬件细节分离,提高程序的通用性和可维护性。
十三、应用层开发人员需要掌握什么
- 能区分用户空间、内核空间和设备驱动。
- 知道 /dev 下的设备节点是什么,不能把节点和驱动混为一谈。
- 理解open/read/write/ioctl/mmap/close的作用和返回值。
- 能解释 open("/dev/xxx") 如何进入内核并到达驱动。
- 能使用termios配置串口。
- 能使用V4L2完成摄像头打开、格式设置、缓冲区申请和取帧。
- 知道驱动负责硬件细节,应用程序负责业务逻辑。
- 设备异常时,会检查设备节点、权限、驱动加载情况、dmesg 内核日志和/sys 设备信息。
对于应用层岗位,通常不要求一开始就能独立编写完整内核驱动,但要理解驱动向应用程序提供了什么接口,以及应用程序如何正确使用它。
笔记结束