Linux与单片机程序对比
本笔记为作者再学习嵌入式Linux的一些心得体会,如有不对的地方,请包涵与谅解!我主要是采用香橙派5来作为我们学习嵌入式Linux的环境。
------------by wsoz
单片机程序
本文这里讨论的是裸机单片机程序:没有通用操作系统支持,程序直接运行在硬件之上。
特点
-
裸机运行
- 程序直接控制硬件寄存器
- 没有操作系统的抽象层
- 启动后直接执行main函数或初始化代码
-
程序结构
cint main(void) { // 硬件初始化 GPIO_Init(); UART_Init(); Timer_Init(); // 主循环 while(1) { // 轮询处理 if (flag) { // 处理任务 } } } -
资源管理
- 内存直接分配,通常使用静态分配
- Flash和RAM资源有限(KB级别)
- 需要手动管理所有资源
-
中断驱动
- 通过中断响应外部事件
- 中断服务程序(ISR)直接操作硬件
- 上下文通常由硬件和启动代码处理,特殊场景才需要手动保存/恢复
-
实时性
- 响应时间可预测
- 适合硬实时系统
- 中断延迟通常在微秒级
注意:裸机开发通常不做严格的应用层/驱动层边界划分,可以直接操作硬件寄存器实现控制。
Linux应用程序
Linux应用程序运行在操作系统之上,通过系统调用与内核交互 ,内核负责硬件管理。
特点
-
操作系统支持
- 程序运行在用户空间
- 通过系统调用访问硬件
- 内核提供进程管理、内存管理、文件系统等服务
-
程序结构
cint main(int argc, char *argv[]) { // 使用系统调用 int fd = open("/dev/ttyS0", O_RDWR); write(fd, "Hello", 5); // 可以创建多进程/多线程 pthread_create(&tid, NULL, thread_func, NULL); // 事件驱动或循环 while(1) { // 使用select/poll/epoll等待事件 select(fd + 1, &readfds, NULL, NULL, NULL); } return 0; } -
资源管理
- 虚拟内存,动态分配(malloc/free)
- 内存资源丰富(MB/GB级别)
- 操作系统自动管理资源回收
-
并发处理
- 多进程/多线程支持
- 进程调度由内核完成
- 使用信号、管道、socket等IPC机制
-
实时性
- 标准Linux为分时系统,非硬实时
- 响应时间受调度影响
- 可使用RT-PREEMPT补丁提升实时性
Linux分层架构(从上到下)
应用层(用户态)
App / Shell / 服务进程
C库与系统调用接口
glibc / syscall
Linux内核核心子系统
进程调度 / 内存 / VFS / 网络
设备驱动层(内核态)
字符设备 / 块设备 / 总线驱动
硬件层
CPU / 寄存器 / 外设
Linux分层是怎么区分的
主要按下面几个维度区分:
-
运行权限(最核心)
- 用户态(User Mode):应用层代码运行在低权限级别,不能直接访问寄存器。
- 内核态(Kernel Mode):内核和驱动运行在高权限级别,可管理硬件和系统资源。
-
接口边界
- 应用层 -> 内核层:通过系统调用(open/read/write/ioctl/mmap等)。
- 内核核心 -> 驱动层:通过内核内部接口(VFS、网络栈、设备模型、总线框架)。
- 驱动层 -> 硬件层:通过寄存器映射、中断、DMA等机制操作硬件。
-
代码与产物形态
- 应用层:可执行文件(如
/usr/bin/*)、脚本、服务程序。 - 内核/驱动层:内核镜像、内核模块(
.ko)。 - 硬件描述:设备树(
.dts/.dtb)描述硬件资源,不属于应用层。
- 应用层:可执行文件(如
-
一个典型调用路径
- 应用调用
write(fd, buf, len) - 进入系统调用陷入内核
- 内核通过VFS/设备模型定位驱动
- 驱动访问寄存器或发起DMA
- 硬件执行并通过中断通知内核,再返回应用
- 应用调用
核心对比
| 对比项 | 单片机程序 | Linux应用程序 |
|---|---|---|
| 运行环境 | 裸机,无OS | 运行在Linux内核之上 |
| 硬件访问 | 直接操作寄存器 | 通过系统调用和驱动 |
| 内存管理 | 手动管理,静态分配为主 | 虚拟内存,动态分配 |
| 资源规模 | KB级RAM/Flash | MB/GB级内存/存储 |
| 并发模型 | 中断+主循环 | 多进程/多线程 |
| 实时性 | 硬实时,微秒级响应 | 软实时(PREEMPT_RT可进一步降低时延) |
| 开发复杂度 | 需要深入了解硬件 | 更高层次的抽象 |
| 调试方式 | JTAG/SWD,串口打印 | GDB,日志系统,丰富工具 |
| 文件系统 | 无(或简单的FAT) | 完整的文件系统(ext4等) |
| 网络协议栈 | 需要移植轻量级栈(lwIP) | 完整的TCP/IP协议栈 |
编程思维转变
从单片机到Linux开发,需要转变的思维:
-
从直接控制到抽象接口
- 单片机:
GPIO_PIN = 1; - Linux:优先使用
/dev/gpiochip+libgpiod(sysfs多用于兼容旧项目)
- 单片机:
-
从轮询到事件驱动
- 单片机:
while(1) { if(flag) {...} } - Linux:使用
select/poll/epoll等待事件
- 单片机:
-
从静态到动态
- 单片机:编译时确定所有资源
- Linux:运行时动态分配和配置
-
从单任务到多任务
- 单片机:主循环+中断
- Linux:多进程/线程并发执行
Linux应用层如何访问硬件
应用层不能直接访问寄存器,典型路径如下:
- 应用层调用
open/read/write/ioctl/mmap等系统调用。 - CPU从用户态陷入内核态(系统调用异常,不是外设中断)。
- 内核按系统调用号分发,再根据
fd定位到对应的file_operations。 - 驱动在内核态通过寄存器映射、中断、DMA等机制与硬件交互。
- 内核将结果返回用户态,应用继续执行。
总结
在单片机中我们可以直接操作底层硬件寄存器,本质上没有应用层和驱动层的区别(可以人为的进行编程划分)。
在Linux中通常分为应用层与内核/驱动层。隔离的核心是CPU特权级(用户态/内核态)和内核访问控制策略,MMU与页表提供地址空间隔离和内存保护,因此应用层不能直接操作底层寄存器。
完整流程:
- 用户层:应用程序调用open/read/write等函数(这些是glibc封装的系统调用)
- 触发异常:CPU执行特殊指令(ARM上是SVC指令),触发系统调用异常
- 切换特权级:CPU从用户态(低权限)切换到内核态(高权限)
- 内核分发:内核根据系统调用号,找到对应的内核函数(如sys_write)
- 定位驱动:内核通过VFS等子系统,根据文件描述符fd找到对应的驱动程序
- 驱动操作硬件:驱动程序在内核态执行,可以直接访问硬件寄存器(通过ioremap映射的虚拟地址)
- 返回用户态:操作完成后,内核将结果返回,CPU切换回用户态
关键点:
- 用户层代码永远运行在用户态,没有权限直接访问寄存器
- 只有内核和驱动运行在内核态,才能操作硬件
- 系统调用是用户态和内核态之间的唯一合法通道
- MMU确保用户态程序即使知道寄存器地址,也无法直接访问(会触发段错误)