我正在尝试开发一个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 函数无限期阻塞的原因的建议或见解将不胜感激。谢谢!
因此,经过一些尝试和错误,我能够以有效的方式更改我的代码。我不确定为什么它突然起作用,但我仍然会为可能遇到此问题的每个人提供代码。请随意评论任何可能仍需改进的地方。 首先是工作代码:
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);
}
我所做的步骤:
希望有一天这可以帮助别人。如果有人知道为什么 iovec 不起作用,我很想知道。