CAN端口测试程序引发Linux内核OOM崩溃,请求问题定位
问题定位:CAN测试程序导致内核内存耗尽崩溃
编写了如下C程序用于测试CAN端口的发送速率:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <net/if.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <linux/can.h> #include <linux/can/raw.h> void send_candata(int s, unsigned int len, unsigned char *buf) { struct can_frame frame; memset(&frame, 0, sizeof(frame)); frame.can_id = 0x123; frame.can_dlc = len; memcpy(&frame.data[0], buf, 8); resend: if (write(s, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) goto resend; } int main(int argc, char *argv[]) { unsigned int cont = 0; unsigned int time = 0; unsigned int total = 0; unsigned char buf1[8]; unsigned char buf2[8]; unsigned char buf3[8]; unsigned char buf4[8]; unsigned char buf5[8]; unsigned char buf6[8]; unsigned char buf7[8]; unsigned char buf8[8]; unsigned char buf9[8]; int s; struct sockaddr_can addr; struct ifreq ifr; const char *ifname = "can0"; memset(buf1, 0x11, 8); memset(buf2, 0x22, 8); memset(buf3, 0x33, 8); memset(buf4, 0x44, 8); memset(buf5, 0x55, 8); memset(buf6, 0x66, 8); memset(buf7, 0x77, 8); memset(buf8, 0x88, 8); memset(buf9, 0x99, 8); if (argc < 3) { printf("please input delay time and total test times you want\r\n"); printf(" usage: \r\n"); printf(" ./testcan 200 1000\r\n"); return 0; } // 初始化CAN Socket,只执行一次 if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) { perror("socket create failed"); return -1; } strcpy(ifr.ifr_name, ifname); if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { perror("ioctl get ifindex failed"); close(s); return -1; } addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_index; // 直接用ioctl获取的索引,无需重复调用if_nametoindex if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind failed"); close(s); return -1; } time = atoi(argv[1]); printf("delay: %dus\r\n", time); total = atoi(argv[2]); printf("total frames: %d * 9 = %d\n", total, total * 9); while (cont < total) { cont++; usleep(time); send_candata(s, 8, buf1); usleep(time); send_candata(s, 8, buf2); usleep(time); send_candata(s, 8, buf3); usleep(time); send_candata(s, 8, buf4); usleep(time); send_candata(s, 8, buf5); usleep(time); send_candata(s, 8, buf6); usleep(time); send_candata(s, 8, buf7); usleep(time); send_candata(s, 8, buf8); usleep(time); send_candata(s, 8, buf9); } close(s); return 0; }
使用该程序测试CAN端口驱动时,发送约160万帧数据(数量取决于系统DDR容量)后,Linux内核因内存耗尽崩溃,内核日志如下:
[ 293.703838] [<ffffffff800049ba>] dump_backtrace+0x1c/0x24 [ 293.709259] [<ffffffff80c60a90>] dump_stack_lvl+0x40/0x58 [ 293.714665] [<ffffffff80c60abc>] dump_stack+0x14/0x1c [ 293.719721] [<ffffffff80c5f748>] dump_header+0x46/0x256 [ 293.724955] [<ffffffff800eef3a>] oom_kill_process+0x386/0x388 [ 293.730708] [<ffffffff800ef6f0>] out_of_memory+0x1a4/0x3ae [ 293.736201] [<ffffffff8012810c>] __alloc_pages+0xa74/0xb60 [ 293.741695] [<ffffffff80139db0>] new_slab+0x382/0x394 [ 293.746751] [<ffffffff8013c694>] ___slab_alloc.constprop.0+0x4c6/0x598 [ 293.753288] [<ffffffff8013c792>] __slab_alloc.constprop.0+0x2c/0x3e [ 293.759563] [<ffffffff8013c868>] __kmalloc_track_caller+0xc4/0x216 [ 293.765751] [<ffffffff80a13ee8>] __alloc_skb+0x7a/0x1b4 [ 293.770986] [<ffffffff80a18950>] alloc_skb_with_frags+0x48/0x160 [ 293.776997] [<ffffffff80a0deba>] sock_alloc_send_pskb+0x19a/0x1b2 [ 293.783095] [<ffffffff80a0dee6>] sock_alloc_send_skb+0x14/0x1c [ 293.788935] [<ffffffff80b68c3e>] raw_sendmsg+0x86/0x224 [ 293.794168] [<ffffffff80a09e98>] sock_write_iter+0x94/0xe8 [ 293.799663] [<ffffffff80145742>] new_sync_write+0xee/0x14c [ 293.805158] [<ffffffff80146f2c>] vfs_write+0x1aa/0x23e [ 293.810303] [<ffffffff80147138>] ksys_write+0x9c/0xc6 [ 293.815358] [<ffffffff80147170>] sys_write+0xe/0x16 [ 293.820243] [<ffffffff800030f6>] ret_from_syscall+0x0/0x2 [ 293.825662] Mem-Info: [ 293.827942] active_anon:35 inactive_anon:840 isolated_anon:0 [ 293.827942] active_file:45 inactive_file:110 isolated_file:0 [ 293.827942] unevictable:0 dirty:0 writeback:0 [ 293.827942] slab_reclaimable:3861 slab_unreclaimable:715005 [ 293.827942] mapped:102 shmem:69 pagetables:130 bounce:0 [ 293.827942] kernel_misc_reclaimable:0 [ 293.827942] free:264660 free_pcp:32 free_cma:261384
测试使用的Linux内核版本为5.15,该问题在4.19版本内核中同样存在。此代码参考自开源的cansend,问题定位及修复如下:
问题根源
- 频繁创建销毁CAN Socket是核心原因:
原代码中每次调用send_candata都会创建新的Socket、绑定、关闭,高频次的资源创建与销毁会导致内核slab分配器产生大量不可回收的内存碎片。从内核日志的slab_unreclaimable:715005可以看出,不可回收的slab内存持续增长,最终耗尽系统内存触发OOM。 - 代码逻辑冗余与错误:
argc判断错误:提示需要2个参数,但代码中写的是argc<4,实际应改为argc<3。- 重复获取网络接口索引:已经通过
ioctl(SIOCGIFINDEX)拿到了ifr.ifr_index,却又调用if_nametoindex重复获取,浪费系统资源。
修复方案
- 将Socket的创建、绑定逻辑移到
main函数中,仅初始化一次,复用同一个Socket完成所有发送操作。 - 修复
argc的判断错误,匹配实际参数需求。 - 移除多余的
if_nametoindex调用,直接使用ioctl获取的接口索引。
修改后的代码已在上方给出,复用Socket后可以避免内核资源的频繁分配与回收,从根本上解决内存耗尽问题。
内容的提问来源于stack exchange,提问作者Arvin
相关产品推荐
相关产品推荐

