Linux串口读取深度实践指南
在嵌入式系统开发、工业控制及物联网设备调试领域,串口通信扮演着不可替代的角色,Linux系统因其强大的驱动支持和灵活的配置能力,成为串口应用的首选平台,本文将深入探讨Linux环境下串口读取的核心技术与实践策略。

Linux串口设备管理与基础操作
Linux系统将串口设备抽象为字符设备文件,通常位于/dev/目录下:
- 设备文件标识:
/dev/ttyS0,/dev/ttyS1:物理串口(COM1, COM2)/dev/ttyUSB0,/dev/ttyUSB1:USB转串口设备/dev/ttyAMA0:树莓派等平台的硬件串口
操作前关键步骤:
-
权限配置(避免使用root权限):
sudo usermod -aG dialout $USER # 将用户加入dialout组 sudo chmod 666 /dev/ttyUSB0 # 临时权限方案(重启失效)
-
设备验证:
dmesg | grep tty # 查看内核识别的串口设备 stty -F /dev/ttyUSB0 # 检查当前配置
串口参数配置核心技术
通过termios结构体精细控制通信行为(需包含<termios.h>):

struct termios serial; tcgetattr(fd, &serial); // 获取当前配置 // 设置波特率 (标准值:9600, 115200等) cfsetispeed(&serial, B115200); cfsetospeed(&serial, B115200); // 数据位与校验 serial.c_cflag &= ~CSIZE; // 清除数据位掩码 serial.c_cflag |= CS8; // 8位数据 serial.c_cflag &= ~PARENB; // 无校验 serial.c_cflag &= ~CSTOPB; // 1位停止位 // 原始模式配置 (关键!) serial.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); serial.c_oflag &= ~OPOST; // 超时控制 (单位:0.1秒) serial.c_cc[VTIME] = 5; // 500ms超时 serial.c_cc[VMIN] = 0; // 非阻塞读取 tcsetattr(fd, TCSANOW, &serial); // 立即生效
串口读取方法对比表
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
read() |
简单阻塞读取 | 实现简单 | 无法同时监听多源 |
select() |
多路复用超时控制 | 高效管理多文件描述符 | 代码复杂度较高 |
poll() |
大规模设备监听 | 无文件描述符数量限制 | 性能略低于epoll |
| 多线程+队列 | 高实时性数据处理 | 避免阻塞主线程 | 需处理线程同步 |
独家实践案例:工业传感器数据采集
在某水质监测项目中,需通过RS-485串口(Linux下仍为tty设备)读取多传感器数据:
挑战:传感器响应延迟差异大(200ms~2s),需精确超时控制。
解决方案:
fd_set readset;
struct timeval timeout = {.tv_sec = 2, .tv_usec = 0};
while (1) {
FD_ZERO(&readset);
FD_SET(fd, &readset);
int ret = select(fd+1, &readset, NULL, NULL, &timeout);
if (ret > 0 && FD_ISSET(fd, &readset)) {
uint8_t buffer[256];
ssize_t len = read(fd, buffer, sizeof(buffer));
if (len > 0) {
// 自定义协议解析 (示例:Modbus RTU)
if (validate_modbus_crc(buffer, len)) {
process_sensor_data(buffer);
}
}
} else if (ret == 0) {
log_timeout_error(); // 记录超时事件
}
}
关键优化:
- 使用
select()实现精确超时控制,避免永久阻塞 - 协议层添加CRC校验,过滤线路干扰导致的错误数据
- 超时事件记录帮助诊断传感器故障
高频问题与进阶技巧
Q1:如何解决read()返回EAGAIN错误?
此错误表明当前无数据且设置为非阻塞模式,正确处理方式:

- 若使用
select()/poll():应确保在返回可读状态后再调用read() - 纯非阻塞模式:需重试机制(如usleep短暂延迟后重试)
Q2:高波特率(1Mbps+)下如何避免数据丢失?
- 启用内核缓冲:
setserial /dev/ttyS1 low_latency减小延迟 - 优化读取策略:
fcntl(fd, F_SETFL, O_RDWR | O_NONBLOCK); // 非阻塞 while (read(fd, buf, BUF_SIZE) > 0); // 一次性读完缓冲
- 提升进程优先级:
nice -n -20 ./serial_app
深度问答 FAQ
Q:Linux下读取串口时出现字节错位(如0x0A变成0x0D 0x0A)如何解决?
A:这是终端模式转换导致的典型问题,需彻底禁用ICRNL和ONLCR选项:
serial.c_iflag &= ~(ICRNL | INLCR | IGNCR); serial.c_oflag &= ~(ONLCR | OCRNL); tcsetattr(fd, TCSANOW, &serial);
Q:多线程环境下操作同一串口描述符有何风险?如何规避?
A:并行read/write会导致数据交叉污染,必须采用:
- 互斥锁保护:所有读写操作前加锁
- 单读写线程+队列:专用线程负责I/O,通过队列与其他线程交互
- EPOLLEXCLUSIVE标志(Linux 4.5+):避免惊群效应
国内权威文献参考
- 毛德操, 胡希明. 《Linux内核源代码情景分析》. 浙江大学出版社
- 宋宝华. 《Linux设备驱动开发详解》. 人民邮电出版社
- 杨铸, 张欢. 《嵌入式Linux应用开发完全手册》. 清华大学出版社
- 中国电子技术标准化研究院. 《工业自动化系统与集成 串行通信技术规范》GB/T 19582-2008















