You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 12:09:51