返回

Linux UART 读取优化:降低 CPU 负载的 5 个技巧

Linux

Linux UART 读取优化:减少 read() 调用次数

在嵌入式Linux系统(例如i.MX6 ULL,Linux 6.1)中使用UART接收数据时,高波特率下频繁的 read() 调用会导致CPU负载过高。本文探讨如何减少 read() 调用次数,从而降低CPU负载,并提供几种优化方案。

问题

在非规范模式下,使用 termios::c_cc[VMIN] = 0termios::c_cc[VTIME] = 20 配置UART。read() 函数在接收到至少一个字节或超时200毫秒后返回。以921600波特率接收每秒100个200字节的数据包时,每个数据包 read() 会被调用6-12次;降低波特率到460800,调用次数增加到12-24次。频繁的上下文切换增加了CPU负载,尤其在资源有限的嵌入式系统中(例如i.MX6 ULL)。

目标是增加数据在内核中累积的时间,减少 read() 的调用次数,同时保证在数据停止发送时能够及时超时。

解决方案

以下几种方案可以减少 read() 的调用次数:

1. 调整 VTIME 值

增加 VTIME 的值可以延长超时时间,允许更多数据在返回前累积。但这需要在响应速度和 CPU 负载之间进行权衡。更大的 VTIME 值意味着更长的延迟,但在某些应用场景中,比如医疗设备的数据采集,一定的延迟是可以接受的。

示例代码(设置 VTIME 为 50,即 500 毫秒):

struct termios tty;
tcgetattr(fd, &tty);
tty.c_cc[VTIME] = 50; 
tcsetattr(fd, TCSANOW, &tty);

2. 使用更大的缓冲区

使用更大的缓冲区可以一次性读取更多数据。可以通过修改驱动程序或应用层代码来实现。在应用层,可以通过在 read() 函数中指定更大的读取字节数来实现。

示例代码(一次读取 200 字节):

char buffer[200];
int bytes_read = read(fd, buffer, 200);

如果驱动程序允许配置FIFO大小,则可以尝试增大硬件FIFO的大小。这需要修改设备树或驱动程序代码,具体方法取决于硬件平台。

3. 修改驱动程序(谨慎操作)

如果以上方法无法满足需求,可以考虑修改UART驱动程序。例如,可以在驱动程序中添加一个缓冲机制,将接收到的数据缓存到一定数量后再返回给用户空间。但这需要深入理解驱动程序的代码逻辑,并进行仔细的测试,确保不会引入新的问题。 修改驱动需谨慎,务必进行充分测试并评估潜在风险。

4. 异步 I/O

异步 I/O (aio) 允许在数据到达时接收通知,而无需轮询。这可以显著降低 CPU 负载,但实现相对复杂。aio_read() 函数可以用于异步读取数据。

需要配置信号处理函数,在数据准备好时接收通知。异步I/O可以避免忙等待,从而减少CPU负载。

5. 选择性读取 (poll/select)

poll()select() 可以用来监控文件符的可读状态,并在数据可读时进行读取操作。结合更大的 VTIME 值和更大的读取缓冲区,可以减少不必要的 read() 调用。

示例代码(使用 poll 监控文件描述符,超时时间 200ms):

struct pollfd fds[1];
fds[0].fd = fd;
fds[0].events = POLLIN;
int ret = poll(fds, 1, 200); // 200ms timeout

if (ret > 0 && (fds[0].revents & POLLIN)) {
    char buffer[200];
    int bytes_read = read(fd, buffer, 200);
    // ... process data ...
}

总结

选择合适的方案取决于具体应用场景的需求和系统资源。调整 VTIME 值和使用更大的缓冲区是最简单的方案。如果需要更精细的控制,可以考虑异步 I/O 或修改驱动程序。无论选择哪种方案,都建议进行充分的测试,确保满足应用的需求,并避免引入新的问题。 在修改内核或驱动代码时,务必进行全面测试,确保系统的稳定性和安全性。

选择合适的方案需要权衡响应速度、CPU 负载和实现复杂度。 通过仔细评估和测试,可以找到最佳的解决方案。