Linux内核模块使用SOCK_RAW读取CAN接口数据阻塞问题求助
我正在开发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

