如何基于UART驱动编写内核层实现用户态数据包传输抽象?
自定义UART数据包内核模块实现方案与示例指导
核心方案选型
- UART字节读取:优先复用
usbserial模块,无需从零实现USB转串口逻辑。通过字符设备驱动封装ttyUSB设备,利用内核文件操作接口读取UART数据,比直接用户态文件系统读写效率更高,还能复用串口参数配置(波特率、奇偶校验等)的现有逻辑。
内核模块关键环节实现示例
1. 对接UART设备(基于ttyUSB)
注册自定义字符设备,在设备打开时关联目标UART设备,关闭时清理资源:
#include <linux/fs.h> #include <linux/uaccess.h> #include <linux/tty.h> #include <linux/usb/serial.h> #include <linux/wait.h> #include <linux/list.h> #include <linux/slab.h> static struct file *uart_filp; static struct cdev my_dev; static dev_t dev_num; static struct class *dev_class; #define PACKET_LEN 64 #define CRC_POLY 0x8005 // 自定义数据包结构 struct my_packet { unsigned char data[PACKET_LEN]; struct list_head list; }; static struct list_head pkt_queue; static wait_queue_head_t wait_q; // 打开自定义设备时初始化UART连接 static int my_open(struct inode *inode, struct file *filp) { uart_filp = filp_open("/dev/ttyUSB0", O_RDWR | O_NONBLOCK, 0); if (IS_ERR(uart_filp)) { return PTR_ERR(uart_filp); } return 0; } // 释放设备资源 static int my_release(struct inode *inode, struct file *filp) { if (uart_filp) { filp_close(uart_filp, NULL); uart_filp = NULL; } return 0; }
2. 数据包解析与CRC校验
维护接收缓冲区,攒够指定字节后校验,合法数据包加入队列:
// CRC16校验计算 static unsigned short calc_crc(unsigned char *data, int len) { unsigned short crc = 0xFFFF; int i, j; for (i = 0; i < len; i++) { crc ^= (unsigned short)data[i]; for (j = 0; j < 8; j++) { crc = (crc & 0x0001) ? ((crc >> 1) ^ CRC_POLY) : (crc >> 1); } } return crc; } // 异步处理UART数据(建议用工作队列执行) static void process_uart_data(struct work_struct *work) { unsigned char tmp_buf[32]; int ret; static unsigned char rx_buf[PACKET_LEN]; static int rx_cnt = 0; while (!IS_ERR(uart_filp)) { ret = kernel_read(uart_filp, tmp_buf, sizeof(tmp_buf), &uart_filp->f_pos); if (ret <= 0) break; for (int i = 0; i < ret; i++) { if (rx_cnt < PACKET_LEN) { rx_buf[rx_cnt++] = tmp_buf[i]; // 数据包长度达标,开始校验 if (rx_cnt == PACKET_LEN) { unsigned short pkt_crc = *(unsigned short*)(rx_buf + PACKET_LEN - 2); unsigned short calc_val = calc_crc(rx_buf, PACKET_LEN - 2); if (pkt_crc == calc_val) { struct my_packet *pkt = kmalloc(sizeof(*pkt), GFP_KERNEL); if (pkt) { memcpy(pkt->data, rx_buf, PACKET_LEN); list_add_tail(&pkt->list, &pkt_queue); wake_up_interruptible(&wait_q); } } rx_cnt = 0; } } } } // 重新调度工作,持续监听UART schedule_work(work); } DECLARE_WORK(uart_work, process_uart_data);
3. 支持用户态poll机制
实现poll回调,让用户态可以通过select/poll/epoll监听数据就绪:
// 用户态读取完整数据包 static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { struct my_packet *pkt; ssize_t ret = 0; if (list_empty(&pkt_queue)) { if (filp->f_flags & O_NONBLOCK) return -EAGAIN; ret = wait_event_interruptible(wait_q, !list_empty(&pkt_queue)); if (ret) return ret; } pkt = list_first_entry(&pkt_queue, struct my_packet, list); if (count >= PACKET_LEN) { if (copy_to_user(buf, pkt->data, PACKET_LEN)) { ret = -EFAULT; } else { ret = PACKET_LEN; } } else { ret = -EINVAL; // 强制读取完整数据包 } list_del(&pkt->list); kfree(pkt); return ret; } // poll回调实现 static unsigned int my_poll(struct file *filp, struct poll_table_struct *wait) { unsigned int mask = 0; poll_wait(filp, &wait_q, wait); if (!list_empty(&pkt_queue)) { mask |= POLLIN | POLLRDNORM; } return mask; } // 文件操作集合 static const struct file_operations my_fops = { .owner = THIS_MODULE, .open = my_open, .release = my_release, .read = my_read, .poll = my_poll, };
模块初始化与退出
static int __init my_module_init(void) { int ret; // 分配设备号 ret = alloc_chrdev_region(&dev_num, 0, 1, "uart_packet_dev"); if (ret) goto err_chrdev; // 初始化字符设备 cdev_init(&my_dev, &my_fops); my_dev.owner = THIS_MODULE; ret = cdev_add(&my_dev, dev_num, 1); if (ret) goto err_cdev; // 创建设备类,自动生成/dev节点 dev_class = class_create(THIS_MODULE, "uart_packet_class"); if (IS_ERR(dev_class)) { ret = PTR_ERR(dev_class); goto err_class; } device_create(dev_class, NULL, dev_num, NULL, "uart_packet_dev"); // 初始化队列与等待队列 INIT_LIST_HEAD(&pkt_queue); init_waitqueue_head(&wait_q); // 启动UART数据处理工作队列 schedule_work(&uart_work); return 0; err_class: cdev_del(&my_dev); err_cdev: unregister_chrdev_region(dev_num, 1); err_chrdev: return ret; } static void __exit my_module_exit(void) { cancel_work_sync(&uart_work); device_destroy(dev_class, dev_num); class_destroy(dev_class); cdev_del(&my_dev); unregister_chrdev_region(dev_num, 1); // 清理剩余数据包 struct my_packet *pkt, *tmp; list_for_each_entry_safe(pkt, tmp, &pkt_queue, list) { list_del(&pkt->list); kfree(pkt); } } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL");
用户态程序示例
通过poll监听并读取完整数据包:
#include <stdio.h> #include <fcntl.h> #include <poll.h> #include <unistd.h> #include <errno.h> #define PACKET_LEN 64 int main() { int fd = open("/dev/uart_packet_dev", O_RDWR | O_NONBLOCK); if (fd < 0) { perror("Failed to open device"); return 1; } struct pollfd fds = { .fd = fd, .events = POLLIN, }; unsigned char pkt_buf[PACKET_LEN]; while (1) { int ret = poll(&fds, 1, -1); if (ret < 0) { if (errno == EINTR) continue; perror("Poll failed"); break; } if (fds.revents & POLLIN) { ret = read(fd, pkt_buf, PACKET_LEN); if (ret == PACKET_LEN) { printf("Received packet: "); for (int i = 0; i < PACKET_LEN; i++) { printf("%02x ", pkt_buf[i]); } printf("\n"); } else if (ret != -EAGAIN) { perror("Read failed"); } } } close(fd); return 0; }
关键注意事项
- 异步处理:必须用工作队列/线程化中断处理UART数据,避免在
read回调中阻塞,影响内核调度。 - 数据包灵活性:示例用固定长度包,实际可扩展为带长度字段的可变包(先读1-2字节长度,再读取对应数据)。
- 异常处理:补充UART设备断开、内存分配失败等场景的错误处理,避免内核Oops。
- 权限配置:模块创建的设备节点默认权限可能为root,可通过
class_create时设置dev_attrs调整,或用户态手动chmod。
内容的提问来源于stack exchange,提问作者konradk
相关产品推荐
相关产品推荐

