Linux 内核模块使用 SOCK_RAW 处理 CAN 接口未接收数据

问题描述 投票:0回答:1

我正在尝试开发一个Linux内核模块,旨在使用原始套接字(SOCK_RAW)从CAN(控制器局域网)接口读取数据。 请注意,这是我第一次使用内核模块,所以我猜想有一些明显的错误,但我只是不知道它是什么。 作为这个起点,我研究了 candump 的实现,并且能够稍微分解代码以获得在用户空间中运行的工作副本。

模块成功创建socket,绑定CAN接口,并进入循环读取数据。然而,sock_recvmsg 函数似乎无限期地阻塞并且永远不会返回,即使我已经通过在用户空间中运行 candump can1 命令确认数据正在发送到 CAN 接口 (can1)。

以下是我的内核模块代码的相关部分的概述:

#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;
    /* Code below returns 88, user-space test code with struct timeval and struct timespec actually returns 72.
    char ctrlmsg[CMSG_SPACE(sizeof(struct old_timeval32)) +
    CMSG_SPACE(3 * sizeof(struct old_timespec32)) +
    CMSG_SPACE(sizeof(__u32))];*/
    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);

我仔细检查了错误处理,套接字创建、绑定或线程调度没有明显问题。然而,sock_recvmsg函数似乎永远不会返回。

我使用的内核版本是 6.1.21-v8+,一切都在树莓派 4 上运行。

我尝试使用“sudo insmod modcanopener_thread.ko”加载模块。 dmesg --follow 的输出如下

    [  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...

我还检查了 ifindex 5 是否正确。我不确定 iov_iter_init 是否正确,但我尝试以多种方式修改它以确保至少可以读取任何内容,但未能做到这一点。

任何有关可能导致 sock_recvmsg 函数无限期阻塞的原因的建议或见解将不胜感激。谢谢!

c linux kernel-module
1个回答
0
投票

因此,经过一些尝试和错误,我能够以有效的方式更改我的代码。我不确定为什么它突然起作用,但我仍然会为可能遇到此问题的每个人提供代码。请随意评论任何可能仍需改进的地方。 首先是工作代码:

struct kvec v;
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;
}

err = can_sock->ops->setsockopt(can_sock, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, KERNEL_SOCKPTR(&canfd_on), sizeof(int));
if (err < 0) {
    printk(KERN_ERR "Failed to sock_setsockopt CAN_RAW_FD_FRAMES: %d\n", err);
    return 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);
    return err;
}


v.iov_base = &frame;
v.iov_len = sizeof(struct canfd_frame);

msg.msg_name = &addr;
msg.msg_control = ctrlmsg;

while(!kthread_should_stop()) {
    msg.msg_namelen = sizeof(addr);
    msg.msg_controllen = sizeof(ctrlmsg);
    
    printk(KERN_INFO "Reading...\n");        
    nbytes = kernel_recvmsg(can_sock,&msg, &v, 1, sizeof(struct canfd_frame), 0);
    printk(KERN_INFO "Done reading %d...\n", nbytes);
}

我所做的步骤:

  • 将 sock_setsockopt 更改为 can_sock->ops->setsockopt
  • 我没有使用 sizeof(sockptr_t) 作为setsockopt,而是使用选项的大小,在本例中是一个 int。我还使用定义 KERNEL_SOCKPTR 创建了 sockptr。
  • setsockopt 更改后,我收到来自 recvmsg 的 -EFAULT 错误。为了解决这个问题,我用 kvec 替换了 iovec,突然一切都开始工作了。

希望有一天这可以帮助别人。如果有人知道为什么 iovec 不起作用,我很想知道。

© www.soinside.com 2019 - 2024. All rights reserved.