当前位置:首页>Linux>阿里笔试常考题:嵌入式Linux下的串口通信,从驱动到应用的全流程实现

阿里笔试常考题:嵌入式Linux下的串口通信,从驱动到应用的全流程实现

  • 2026-09-04 03:03:02
阿里笔试常考题:嵌入式Linux下的串口通信,从驱动到应用的全流程实现

大家好,我是一个爱分享的牛马程序员,工作中碰到,加上自己理解,很高兴给大家分享。

-begin-

题目:在嵌入式Linux系统中,编写一个串口通信程序,实现以下功能:

1.配置串口(波特率115200,数据位8位,无奇偶校验,停止位1位,无流控);
2.主线程循环从标准输入读取字符串,通过串口发送;
3.创建一个子线程,循环从串口接收数据,收到数据后打印“收到:XXX”;
4.输入“exit”时,程序退出并释放资源。

详细分析过程:

一、核心考点拆解

本题是阿里嵌入式Linux岗位笔试的常见题,主要考察:

•串口设备的打开与配置(termios结构体的使用);
•多线程在串口读写中的应用(主线程发送、子线程接收);
•串口数据的读写操作(read/write系统调用)及异常处理;
•程序退出时的资源释放(关闭文件描述符、线程回收)。

二、关键设计思路

5.串口配置流程:
◦打开串口设备(通常为/dev/ttyS0或/dev/ttyUSB0),使用O_RDWR | O_NOCTTY | O_NDELAY标志,确保非阻塞且不成为控制终端;
◦通过termios结构体配置参数:波特率设置(cfsetispeed/cfsetospeed)、数据位(CS8)、校验位(PARENB清零)、停止位(CSTOPB清零);
◦启用串口(tcsetattr的TCSANOW参数,立即生效)。
6.多线程分工:
◦主线程:负责从标准输入读取用户输入,若输入“exit”则设置退出标志,否则调用write发送数据到串口;
◦子线程:循环调用read从串口接收数据(设置非阻塞超时,避免一直阻塞),收到数据后打印,检测到退出标志时退出。
7.同步与退出机制:
◦使用全局变量exit_flag标记程序是否退出,主线程与子线程通过该变量同步退出状态;
◦子线程读取串口时设置超时(VTIME),避免因无数据一直阻塞,确保能及时响应退出指令。

三、代码实现

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <fcntl.h>

#include <pthread.h>

#include <termios.h>

#include <errno.h>

#define SERIAL_DEV "/dev/ttyS0"  // 串口设备路径

#define BAUDRATE B115200         // 波特率

#define BUF_SIZE 1024            // 缓冲区大小

int exit_flag = 0;               // 退出标志

int serial_fd;                   // 串口文件描述符

// 子线程:接收串口数据

void *recv_thread(void *arg) {

   char buf[BUF_SIZE];

   ssize_t len;

   while (!exit_flag) {

       // 读取串口数据(超时设置生效,无数据时返回-1,errno=EAGAIN)

       len = read(serial_fd, buf, BUF_SIZE - 1);

       if (len > 0) {

           buf[len] = '\0';

           printf("收到:%s\n", buf);

       } else if (len == -1 && errno != EAGAIN) {

           perror("串口接收失败");

           break;

       }

       usleep(100000);  // 短暂休眠,降低CPU占用

   }

   printf("接收线程退出\n");

   return NULL;

}

// 配置串口函数

int serial_config(int fd) {

   struct termios opt;

   // 获取当前配置

   if (tcgetattr(fd, &opt) != 0) {

       perror("tcgetattr failed");

       return -1;

   }

   // 设置波特率

   cfsetispeed(&opt, BAUDRATE);

   cfsetospeed(&opt, BAUDRATE);

   // 配置数据位、校验位、停止位

   opt.c_cflag &= ~PARENB;  // 无奇偶校验

   opt.c_cflag &= ~CSTOPB;  // 1位停止位

   opt.c_cflag &= ~CSIZE;   // 清除数据位设置

   opt.c_cflag |= CS8;      // 8位数据位

   // 禁用流控

   opt.c_cflag &= ~CRTSCTS;

   // 启用接收器,设置本地模式

   opt.c_cflag |= (CLOCAL | CREAD);

   // 禁用规范模式,不处理特殊字符(如回车换行)

   opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

   // 禁用输入处理(不转换回车换行)

   opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);

   // 禁用输出处理(不转换换行)

   opt.c_oflag &= ~OPOST;

   // 设置超时:读取时等待10个十分之一秒(1秒),最小读取1字节

   opt.c_cc[VTIME] = 10;

   opt.c_cc[VMIN] = 0;

   // 应用配置

   if (tcsetattr(fd, TCSANOW, &opt) != 0) {

       perror("tcsetattr failed");

       return -1;

   }

   return 0;

}

int main() {

   pthread_t tid;

   char buf[BUF_SIZE];

   // 打开串口

   serial_fd = open(SERIAL_DEV, O_RDWR | O_NOCTTY | O_NDELAY);

   if (serial_fd == -1) {

       perror("打开串口失败");

       return -1;

   }

   // 配置串口

   if (serial_config(serial_fd) != 0) {

       close(serial_fd);

       return -1;

   }

   // 创建接收子线程

   if (pthread_create(&tid, NULL, recv_thread, NULL) != 0) {

       perror("创建线程失败");

       close(serial_fd);

       return -1;

   }

   // 主线程:发送数据

   printf("请输入要发送的内容(输入exit退出):\n");

   while (1) {

       // 读取标准输入

       if (fgets(buf, BUF_SIZE, stdin) == NULL) {

           perror("读取输入失败");

           break;

       }

       // 去除换行符

       buf[strcspn(buf, "\n")] = '\0';

       // 检查退出条件

       if (strcmp(buf, "exit") == 0) {

           exit_flag = 1;

           break;

       }

       // 发送数据到串口

       if (write(serial_fd, buf, strlen(buf)) == -1) {

           perror("发送失败");

           break;

       }

       printf("已发送:%s\n", buf);

   }

   // 回收子线程

   pthread_join(tid, NULL);

   // 关闭串口

   close(serial_fd);

   printf("程序退出\n");

   return 0;

}

四、代码解析与考点总结

8.串口配置核心:termios结构体是配置串口的关键,需要重点理解c_cflag(控制模式)、c_lflag(本地模式)、c_iflag(输入模式)、c_oflag(输出模式)的作用,尤其是禁用规范模式(ICANON)确保数据原始传输。
9.多线程协同:子线程采用非阻塞读取(结合VTIME超时),避免因串口无数据导致无法响应退出指令;主线程与子线程通过全局变量exit_flag同步,确保退出时资源正确释放。
10.易错点:
◦串口设备路径需根据实际硬件修改(如/dev/ttyUSB0对应USB转串口);
◦打开串口时必须添加O_NOCTTY,否则程序可能接收不到信号(如Ctrl+C);
◦发送数据时若包含换行符,需注意是否在接收端处理,本题简化为去除输入的换行符后发送。
11.阿里考点延伸:面试中可能追问“如何处理串口数据的粘包问题?”——可通过添加结束符(如\r\n)或固定长度帧头解决,接收时按规则解析;或问“如何实现更高效率的串口通信?”——可使用select/poll实现I/O多路复用,避免线程休眠带来的延迟。

掌握串口通信是嵌入式Linux开发的基础技能,阿里等大厂尤其看重对底层设备操作和多线程协同的理解,本题覆盖了从配置到应用的全流程,值得深入掌握。

-end-

如果文章对你有提升,帮忙点赞,分享,关注。十分感谢

最新文章

随机文章