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

Linux内核模块使用SOCK_RAW读取CAN接口数据阻塞问题求助

内核态CAN原始套接字sock_recvmsg阻塞问题排查

我正在开发Linux内核模块,目标是通过SOCK_RAW原始套接字从CAN接口读取数据。已完成可正常运行的用户态版本(参考candump实现),但内核模块中sock_recvmsg始终阻塞无法返回——尽管用户态执行candump can1能正常接收该接口的数据。

模块已成功创建套接字、绑定CAN接口并进入读取循环,但卡在sock_recvmsg调用。使用内核版本6.1.21-v8+,运行环境为树莓派4。

核心代码

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <uapi/linux/sched/types.h>
#include <linux/delay.h>

#include <linux/net.h>
#include <linux/can.h>
#include <linux/ioctl.h>
#include <linux/can/dev.h>
#include <linux/can/raw.h>
#include <linux/can/core.h>
#include <linux/netdevice.h>

struct task_struct *task;
#define THREAD_NAME "canopener_thread"

int canopener_thread(void *data)
{
    struct task_struct *TSK;
    struct net_device *net_dev;
    struct sockaddr_can addr;
    int err;
    bool found_can;
    int canfd_on = 1;
    char* canif;
    struct socket* can_sock;
    sockptr_t sock_ptr;
    char ctrlmsg[72];
    struct iovec iov;
    struct msghdr msg;
    struct canfd_frame frame;
    int nbytes;

    can_sock = NULL;
    found_can = false;
    canif = "can1";
    TSK = current;
    sched_set_fifo(TSK);
    allow_signal(SIGKILL);

    printk(KERN_INFO "Searching CAN interface: %s\n", canif);
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    for_each_netdev(&init_net, net_dev) {
        if (strcmp(net_dev->name,canif) == 0) {
        printk(KERN_INFO "Found CAN interface: %s (ifindex: %d)\n",
                net_dev->name, net_dev->ifindex);
        addr.can_ifindex = net_dev->ifindex;
        found_can = true;
        break;
        }
    }
    if(!found_can){
        printk(KERN_ERR "Failed to find CAN device!\n");
        return err;
    }

    printk(KERN_INFO "sock_create!\n");
    err = sock_create(PF_CAN, SOCK_RAW, CAN_RAW, &can_sock);
    printk(KERN_INFO "sock_create done!\n");
    if (err < 0) {
        printk(KERN_ERR "Failed to create CAN socket: %d\n", err);
        return err;
    }

    sock_ptr.kernel = &canfd_on;
    sock_ptr.is_kernel = 1;
    sock_setsockopt(can_sock, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, sock_ptr, sizeof(sockptr_t));

    can_sock->ops->bind(can_sock, (struct sockaddr *)&addr, sizeof(addr));
    if (err < 0) {
        printk(KERN_ERR "Failed to bind CAN socket: %d\n", err);
        return err;
    }


    iov.iov_base = &frame;
    iov.iov_len = sizeof(frame);
    msg.msg_name = &addr;
    msg.msg_control = ctrlmsg;

    while(!kthread_should_stop()) {
        iov_iter_init(&msg.msg_iter, READ, &iov, 1, sizeof(frame));
        msg.msg_namelen = sizeof(addr);
        msg.msg_controllen = sizeof(ctrlmsg);
        msg.msg_flags = 0;

        printk(KERN_INFO "Reading...\n");
        nbytes = sock_recvmsg(can_sock, &msg, 0);
        printk(KERN_INFO "Done reading %d...\n", nbytes);
    }

    sock_release(can_sock);

    printk("canopener_thread exiting\n");
    return 0;
}


static int __init modcanopener_init(void)
{
    printk(KERN_INFO "canopener thread: starting...\n");
    task = kthread_run(canopener_thread, NULL, THREAD_NAME);

    printk(KERN_INFO "canopener thread: starting done.\n");
    return 0;
}

static void __exit modcanopener_exit(void)
{
    printk(KERN_INFO "canopener thread: stopping...\n");
    kthread_stop(task);
    printk(KERN_INFO "canopener thread: stopping done.\n");
}

module_init(modcanopener_init);
module_exit(modcanopener_exit);

dmesg输出

[  448.319191] canopener thread: starting...
[  448.319538] canopener thread: starting done.
[  448.319606] Searching CAN interface: can1
[  448.319627] Found CAN interface: can1 (ifindex: 5)
[  448.319641] sock_create!
[  448.319695] sock_create done!
[  448.319728] Reading...

问题分析与修复建议

  • 绑定操作错误检查失效:代码中调用can_sock->ops->bind后,未将返回值赋值给err就进行错误判断,导致绑定失败的问题无法被发现。正确写法应捕获返回值:

    err = can_sock->ops->bind(can_sock, (struct sockaddr *)&addr, sizeof(addr));
    if (err < 0) {
        printk(KERN_ERR "Failed to bind CAN socket: %d\n", err);
        goto cleanup_sock;
    }
    
  • 套接字选项参数错误:调用sock_setsockopt时,最后一个参数应为选项值的长度sizeof(canfd_on),而非sizeof(sockptr_t),错误的长度会导致CAN FD模式设置失败,影响帧接收:

    err = sock_setsockopt(can_sock, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, sock_ptr, sizeof(canfd_on));
    if (err < 0) {
        printk(KERN_ERR "Failed to set CAN_RAW_FD_FRAMES: %d\n", err);
        goto cleanup_sock;
    }
    
  • 内核线程调度与信号处理问题:sched_set_fifo(TSK)未检查返回值,FIFO调度可能引发优先级冲突;另外,kthread无需通过allow_signal(SIGKILL)处理信号,依赖kthread_should_stop()响应退出即可。

  • msghdr结构初始化问题:循环内可将msg.msg_name设为NULL(绑定后无需获取源地址),同时可尝试添加MSG_DONTWAIT标记测试非阻塞读取,验证是否为唤醒机制问题:

    msg.msg_flags = MSG_DONTWAIT;
    
  • 资源泄漏风险:当前代码缺少错误路径的套接字清理逻辑,需添加goto cleanup_sock跳转,确保失败时调用sock_release(can_sock)释放资源。

  • CAN过滤器显式配置:默认情况下CAN原始套接字接收所有帧,但显式配置全通过滤器可避免潜在的过滤规则影响:

    struct can_filter filter = {.can_id = 0, .can_mask = 0};
    sock_ptr.kernel = &filter;
    err = sock_setsockopt(can_sock, SOL_CAN_RAW, CAN_RAW_FILTER, sock_ptr, sizeof(filter));
    if (err < 0) {
        printk(KERN_ERR "Failed to set CAN filter: %d\n", err);
        goto cleanup_sock;
    }
    

内容的提问来源于stack exchange,提问作者nerdbeere

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 14:48:10