关于用strace工具定位vrrpd进程有时会挂死的bug

只作工做总结备忘之用。linux

正在烧镜像,稍总结一下进来改bug遇到的问题。程序员

 

 

一个项目里要用到L3 switch的nat,vrrp功能,但实地测试中偶然出现write file挂死的状况,但不是必现。交付在即,因而加调试信息,反复跑配置的脚本,定位bug。shell

 

一,期初怀疑是vtysh与vrrpd进程通讯出现阻塞(现象便是系统挂死)。小程序

(1)由于在跑配置脚本时,出现了enable命令也挂死的状况,因此这么怀疑;vim

(2)在vrrpd与vtysh命令传输的关键点加打印信息(注意vrrpd不要-d daemon化,用& 来后台),跟踪结果是vtysh    确实把‘enable’命令发了出去,只是vrrpd进程不接收。命令通讯框架公用的,基本能够肯定没有问题,由      此,把bug收敛到vrrpd进程的问题;数组

(3)在vtysh进程中ctrl+c信号处理中加进入Linux shell的代码,当vrrpd致使系统挂死(现象是阻塞)时,能够进    入shell来top,top发现,vrrpd进程cpu利用率很高,并且sys:92.0%,因此肯定了是vrrpd进程使用了某个系    统调用,而该系统调用接口阻塞,致使了vrrpd阻塞,进而致使了vrrpd进程不接收vtysh进程发多来的命令。缓存

(4)接下来要肯定哪一个系统调用致使vrrpd阻塞,因而使用了strace来把vrrpd进程拉起来。安全

   关于strace的用法,网上一大堆,连接:点击打开连接,并附件一到文章尾部,以防原文删除。框架

   用的: strace -aef -p /usr/sbin/vrrpd -o /tmp/vrrpdstrace.logless

   当复现vrrpd进程阻塞时,查看vrrpdstrace.log文件,分析里边的各个系统调用,肯定进程阻塞在了ioctl系    统调用上,根据ioctl的参数,跟踪到内核,最终定位到博通删除mac表项的sdk接口。(该接口先后加打印)

   鉴于继续跟踪太难,bug暂告一段落........

 

2、跟踪博通sdk接口删除mac表项

(1)过程是曲折和痛苦的,不知道的地方随便加一个打印,而后就刷屏了,但已经明确了确定是内核处理函数中    有一个没有释放锁而致使的阻塞,接下来就是跟进锁的过程,因为该接口底层用的锁(不清楚什么锁)也不    像用户态的信号量同样有个semid的关键字,。但终于仍是找到了一个区分mem的宏来做为关键字,叫L2Xm,

   刷屏总算少一点了。而后就查找内核中使用L2Xm的锁,最后反复跑脚本,肯定了多是_soc_l2x_thread线程    中也使用了该锁。

(2)最后,肯定了是_soc_l2x_thread中 LOCK(L2Xm)与UNLOCK(L2Xm)之间的其余锁的阻塞,进而致使了该锁没有

   没有执行不到;进而发现了没有释放锁的地方,问题基本完结。

 

3、内核kmalloc的内存类型,可能致使内核panic。

(1)在内核接收报文bnet_rx_deferred中报文处理中申请了内存kmalloc,但类型是GFP_KERNEL,,巧合的是,正    好该处理报文中出现了内存泄露,致使时间一长(或者故意打该报文),就会内存不足,一内存不足就会导    致kmalloc出现申请不成功的现象,即不会当即申请的到内存,,内核会在其正在申请的时候把其切换出        去,由于GFP_kernel类型是可睡眠的,非原子的。因此,问题来了,把接收报文中断处理的kmalloc切换了出    去,而接收报文中断处理是不能再切换回来的。即,中断接收报文处理被内核认为应该是原子的,当其中出    现了不是原子的kmalloc(GFP_KERNEL,)时就会panic。

(2)由此,同时发现了内存泄露和GFP_KERNEL两个问题。

(3)关于kmalloc的类型问题,网上也一大堆,连接:点击打开连接,并附录二在文章末尾。 

   完毕。

 

附件一:strace的用法。

 

strace命令详解
strace 命令是一种强大的工具,它可以显示全部由用户空间程序发出的系统调用。
  strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,并且不须要以任何特殊的方式来构建内核。
  下面记录几个经常使用 option .
  1 -f -F选项告诉strace同时跟踪fork和vfork出来的进程
  2 -o xxx.txt 输出到某个文件。
  3 -e execve 只记录 execve 这类系统调用
  ---------------------------------------------------
  进程没法启动,软件运行速度忽然变慢,程序的"SegmentFault"等等都是让每一个Unix系统用户头痛的问题,
  本文经过三个实际案例演示如何使用truss、strace和ltrace这三个经常使用的调试工具来快速诊断软件的"疑难杂症"。
  
  
  truss和strace用来跟踪一个进程的系统调用或信号产生的状况,而 ltrace用来跟踪进程调用库函数的状况。truss是早期为System V R4开发的调试程序,包括Aix、FreeBSD在内的大部分Unix系统都自带了这个工具;
  而strace最初是为SunOS系统编写的,ltrace最先出如今GNU/DebianLinux中。
  这两个工具如今也已被移植到了大部分Unix系统中,大多数Linux发行版都自带了strace和ltrace,而FreeBSD也可经过Ports安装它们。
  
  你不只能够从命令行调试一个新开始的程序,也能够把truss、strace或ltrace绑定到一个已有的PID上来调试一个正在运行的程序。三个调试工具的基本使用方法大致相同,下面仅介绍三者共有,并且是最经常使用的三个命令行参数:
  
  -f :除了跟踪当前进程外,还跟踪其子进程。
  -o file :将输出信息写到文件file中,而不是显示到标准错误输出(stderr)。
  -p pid :绑定到一个由pid对应的正在运行的进程。此参数经常使用来调试后台进程。
  
   使用上述三个参数基本上就能够完成大多数调试任务了,下面举几个命令行例子:
  truss -o ls.truss ls -al: 跟踪ls -al的运行,将输出信息写到文件/tmp/ls.truss中。
  strace -f -o vim.strace vim: 跟踪vim及其子进程的运行,将输出信息写到文件vim.strace。
  ltrace -p 234: 跟踪一个pid为234的已经在运行的进程。
  
   三个调试工具的输出结果格式也很类似,以strace为例:
  
  brk(0) = 0x8062aa8
  brk(0x8063000) = 0x8063000
  mmap2(NULL, 4096, PROT_READ, MAP_PRIVATE, 3, 0x92f) = 0x40016000
  
  每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。 truss、strace和ltrace的工做原理大同小异,都是使用ptrace系统调用跟踪调试运行中的进程,详细原理不在本文讨论范围内,有兴趣能够参考它们的源代码。
  举两个实例演示如何利用这三个调试工具诊断软件的"疑难杂症":
  
  案例一:运行clint出现Segment Fault错误
  
  操做系统:FreeBSD-5.2.1-release
  clint是一个C++静态源代码分析工具,经过Ports安装好以后,运行:
  
  # clint foo.cpp
  Segmentation fault (core dumped)
   在Unix系统中碰见"Segmentation Fault"就像在MS Windows中弹出"非法操做"对话框同样使人讨厌。OK,咱们用truss给clint"把把脉":
  
  # truss -f -o clint.truss clint
  Segmentation fault (core dumped)
  # tail clint.truss
   739: read(0x6,0x806f000,0x1000) = 4096 (0x1000)
   739: fstat(6,0xbfbfe4d0) = 0 (0x0)
   739: fcntl(0x6,0x3,0x0) = 4 (0x4)
   739: fcntl(0x6,0x4,0x0) = 0 (0x0)
   739: close(6) = 0 (0x0)
   739: stat("/root/.clint/plugins",0xbfbfe680) ERR#2 'No such file or directory'
  SIGNAL 11
  SIGNAL 11
  Process stopped because of: 16
  process exit, rval = 139
  咱们用truss跟踪clint的系统调用执行状况,并把结果输出到文件clint.truss,而后用tail查看最后几行。
  注意看clint执行的最后一条系统调用(倒数第五行):stat("/root/.clint/plugins",0xbfbfe680) ERR#2 'No such file or directory',问题就出在这里:clint找不到目录"/root/.clint/plugins",从而引起了段错误。怎样解决?很简单: mkdir -p /root/.clint/plugins,不过此次运行clint仍是会"Segmentation Fault"9。继续用truss跟踪,发现clint还须要这个目录"/root/.clint/plugins/Python",建好这个目录后 clint终于可以正常运行了。
  
  案例二:vim启动速度明显变慢
  
  操做系统:FreeBSD-5.2.1-release
  vim版本为6.2.154,从命令行运行vim后,要等待近半分钟才能进入编辑界面,并且没有任何错误输出。仔细检查了.vimrc和全部的vim脚本都没有错误配置,在网上也找不到相似问题的解决办法,难不成要hacking source code?没有必要,用truss就能找到问题所在:
  
  # truss -f -D -o vim.truss vim
  
  这里-D参数的做用是:在每行输出前加上相对时间戳,即每执行一条系统调用所耗费的时间。咱们只要关注哪些系统调用耗费的时间比较长就能够了,用less仔细查看输出文件vim.truss,很快就找到了疑点:
  
  735: 0.000021511 socket(0x2,0x1,0x0) = 4 (0x4)
  735: 0.000014248 setsockopt(0x4,0x6,0x1,0xbfbfe3c8,0x4) = 0 (0x0)
  735: 0.000013688 setsockopt(0x4,0xffff,0x8,0xbfbfe2ec,0x4) = 0 (0x0)
  735: 0.000203657 connect(0x4,{ AF_INET 10.57.18.27:6000 },16) ERR#61 'Connection refused'
  735: 0.000017042 close(4) = 0 (0x0)
  735: 1.009366553 nanosleep(0xbfbfe468,0xbfbfe460) = 0 (0x0)
  735: 0.000019556 socket(0x2,0x1,0x0) = 4 (0x4)
  735: 0.000013409 setsockopt(0x4,0x6,0x1,0xbfbfe3c8,0x4) = 0 (0x0)
  735: 0.000013130 setsockopt(0x4,0xffff,0x8,0xbfbfe2ec,0x4) = 0 (0x0)
  735: 0.000272102 connect(0x4,{ AF_INET 10.57.18.27:6000 },16) ERR#61 'Connection refused'
  735: 0.000015924 close(4) = 0 (0x0)
  735: 1.009338338 nanosleep(0xbfbfe468,0xbfbfe460) = 0 (0x0)
  
  vim试图链接10.57.18.27这台主机的6000端口(第四行的connect()),链接失败后,睡眠一秒钟继续重试(第6行的 nanosleep())。以上片段循环出现了十几回,每次都要耗费一秒多钟的时间,这就是vim明显变慢的缘由。但是,你确定会纳闷:"vim怎么会平白无故链接其它计算机的6000端口呢?"。问得好,那么请你回想一下6000是什么服务的端口?没错,就是X Server。看来vim是要把输出定向到一个远程X Server,那么Shell中确定定义了DISPLAY变量,查看.cshrc,果真有这么一行:setenv DISPLAY ${REMOTEHOST}:0,把它注释掉,再从新登陆,问题就解决了。
  
  
  案例三:用调试工具掌握软件的工做原理
  
  操做系统:Red Hat Linux 9.0
  用调试工具实时跟踪软件的运行状况不只是诊断软件"疑难杂症"的有效的手段,也可帮助咱们理清软件的"脉络",即快速掌握软件的运行流程和工做原理,不失为一种学习源代码的辅助方法。下面这个案例展示了如何使用strace经过跟踪别的软件来"触发灵感",从而解决软件开发中的难题的。
  你们都知道,在进程内打开一个文件,都有惟一一个文件描述符(fd:file descriptor)与这个文件对应。而本人在开发一个软件过程当中遇到这样一个问题:
  已知一个fd,如何获取这个fd所对应文件的完整路径?无论是Linux、FreeBSD或是其它Unix系统都没有提供这样的API,怎么办呢?咱们换个角度思考:Unix下有没有什么软件能够获取进程打开了哪些文件?若是你经验足够丰富,很容易想到lsof,使用它既能够知道进程打开了哪些文件,也能够了解一个文件被哪一个进程打开。好,咱们用一个小程序来试验一下lsof,看它是如何获取进程打开了哪些文件。lsof: 显示进程打开的文件。
  
  /* testlsof.c */
  #include #include #include #include #include
  int main(void)
  {
   open("/tmp/foo", O_CREAT|O_RDONLY); /* 打开文件/tmp/foo */
   sleep(1200); /* 睡眠1200秒,以便进行后续操做 */
   return 0;
  }
  
  将testlsof放入后台运行,其pid为3125。命令lsof -p 3125查看进程3125打开了哪些文件,咱们用strace跟踪lsof的运行,输出结果保存在lsof.strace中:
  
  # gcc testlsof.c -o testlsof
  # ./testlsof &
  [1] 3125
  # strace -o lsof.strace lsof -p 3125
  
  咱们以"/tmp/foo"为关键字搜索输出文件lsof.strace,结果只有一条:
  
  
  # grep '/tmp/foo' lsof.strace
  readlink("/proc/3125/fd/3", "/tmp/foo", 4096) = 8
  
  原来lsof巧妙的利用了/proc/nnnn/fd/目录(nnnn为pid):Linux内核会为每个进程在/proc/创建一个以其pid为名的目录用来保存进程的相关信息,而其子目录fd保存的是该进程打开的全部文件的fd。目标离咱们很近了。好,咱们到/proc/3125/fd/看个究竟:
  
  # cd /proc/3125/fd/
  # ls -l
  total 0
  lrwx------ 1 root root 64 Nov 5 09:50 0 -> /dev/pts/0
  lrwx------ 1 root root 64 Nov 5 09:50 1 -> /dev/pts/0
  lrwx------ 1 root root 64 Nov 5 09:50 2 -> /dev/pts/0
  lr-x------ 1 root root 64 Nov 5 09:50 3 -> /tmp/foo
  # readlink /proc/3125/fd/3
  /tmp/foo
  
  答案已经很明显了:/proc/nnnn/fd/目录下的每个fd文件都是符号连接,而此连接就指向被该进程打开的一个文件。咱们只要用readlink()系统调用就能够获取某个fd对应的文件了,代码以下:
  
  
  #include #include #include #include #include #include
  int get_pathname_from_fd(int fd, char pathname[], int n)
  {
   char buf[1024];
   pid_t pid;
   bzero(buf, 1024);
   pid = getpid();
   snprintf(buf, 1024, "/proc/%i/fd/%i", pid, fd);
   return readlink(buf, pathname, n);
  }
  int main(void)
  {
   int fd;
   char pathname[4096];
   bzero(pathname, 4096);
   fd = open("/tmp/foo", O_CREAT|O_RDONLY);
   get_pathname_from_fd(fd, pathname, 4096);
   printf("fd=%d; pathname=%sn", fd, pathname);
   return 0;
  }
  
  出于安全方面的考虑,在FreeBSD 5 以后系统默认已经再也不自动装载proc文件系统,所以,要想使用truss或strace跟踪程序,你必须手工装载proc文件系统:mount -t procfs proc /proc;或者在/etc/fstab中加上一行:
  
  proc /proc procfs rw 0 0

 

附录二:kmalloc的类型用法

 

malloc内存分配和malloc类似,除非被阻塞不然他执行的速度很是快,并且不对得到空间清零。

Flags参数

#include<linux/slab.h>

Void *kmalloc(size_t size, int flags);

第一个参数是要分配的块的大小,第二个参数是分配标志(flags),他提供了多种kmalloc的行为。

最经常使用的GFP_KERNEL,他表示内存分配(最终老是调用get_free_pages来实现实际的分配,这就是,这就是GFP前缀的由来)是表明运行在内核空间的进程执行的。使用GFP_KERNEL允许kmalloc在分配空闲内存时候若是内存不足允许把当前进程睡眠以等待。所以这时分配函数必须是可重入的。若是在进程上下文以外如:中断处理程序、tasklet以及内核定时器中这种状况下current进程不应睡眠,驱动程序该使用GFP_ATOMIC.

GFP_ATOMIC

用来从中断处理和进程上下文以外的其余代码中分配内存. 从不睡眠.

GFP_KERNEL

内核内存的正常分配. 可能睡眠.

GFP_USER

用来为用户空间页来分配内存; 它可能睡眠.

GFP_HIGHUSER

如同 GFP_USER, 可是从高端内存分配, 若是有. 高端内存在下一个子节描述.

GFP_NOIO

GFP_NOFS

这个标志功能如同 GFP_KERNEL, 可是它们增长限制到内核能作的来知足请求. 一个 GFP_NOFS 分配不容许进行任何文件系统调用, 而 GFP_NOIO 根本不容许任何 I/O 初始化. 它们主要地用在文件系统和虚拟内存代码, 那里容许一个分配睡眠, 可是递归的文件系统调用会是一个坏注意.

上面列出的这些分配标志能够是下列标志的相或来做为参数, 这些标志改变这些分配如何进行:

__GFP_DMA

这个标志要求分配在可以 DMA 的内存区. 确切的含义是平台依赖的而且在下面章节来解释.

__GFP_HIGHMEM

这个标志指示分配的内存能够位于高端内存.

__GFP_COLD

正常地, 内存分配器尽力返回"缓冲热"的页 -- 可能在处理器缓冲中找到的页. 相反, 这个标志请求一个"冷"页, 它在一段时间没被使用. 它对分配页做 DMA 读是有用的, 此时在处理器缓冲中出现是无用的. 一个完整的对如何分配 DMA 缓存的讨论看"直接内存存取"一节在第 1 章.

__GFP_NOWARN

这个不多用到的标志阻止内核来发出警告(使用 printk ), 当一个分配没法知足.

__GFP_HIGH

这个标志标识了一个高优先级请求, 它被容许来消耗甚至被内核保留给紧急情况的最后的内存页.

__GFP_REPEAT

__GFP_NOFAIL

__GFP_NORETRY

这些标志修改分配器如何动做, 当它有困难知足一个分配. __GFP_REPEAT 意思是" 更尽力些尝试" 经过重复尝试 -- 可是分配可能仍然失败. __GFP_NOFAIL 标志告诉分配器不要失败; 它尽最大努力来知足要求. 使用 __GFP_NOFAIL 是强烈不推荐的; 可能从不会有有效的理由在一个设备驱动中使用它. 最后, __GFP_NORETRY 告知分配器当即放弃若是得不到请求的内存.

 

2.内存区段

__GFP_DMA和__GFP_HIGHMEM的使用与平台相关,Linux把内存分红3个区段:可用于DMA的内存、常规内存、以及高端内存。X86平台上ISA设备DMA区段是内存的前16MB,而PCI设备无此限制。

内存区后面的机制在 mm/page_alloc.c 中实现, 而内存区的初始化在平台特定的文件中, 经常在 arch 目录树的 mm/init.c。

Linux 处理内存分配经过建立一套固定大小的内存对象池. 分配请求被这样来处理, 进入一个持有足够大的对象的池子而且将整个内存块递交给请求者. 驱动开发者应当记住的一件事情是, 内核只能分配某些预约义的, 固定大小的字节数组.

若是你请求一个任意数量内存, 你可能获得稍微多于你请求的, 至可能是 2 倍数量. 一样, 程序员应当记住 kmalloc 可以处理的最小分配是 32 或者 64 字节, 依赖系统的体系所使用的页大小. kmalloc 可以分配的内存块的大小有一个上限. 这个限制随着体系和内核配置选项而变化. 若是你的代码是要彻底可移植, 它不能期望能够分配任何大于 128 KB. 若是你须要多于几个 KB, 可是, 有个比 kmalloc 更好的方法来得到内存

 

 

在设备驱动程序或者内核模块中动态开辟内存,不是用malloc,而是kmalloc ,vmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,vfree,或free_pages. kmalloc函数返回的是虚拟地址(线性地址). kmalloc特殊之处在于它分配的内存是物理上连续的,这对于要进行DMA的设备十分重要. 而用vmalloc分配的内存只是线性地址连续,物理地址不必定连续,不能直接用于DMA.

  注意kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。kmalloc用法参见khg.

  内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)通常占用F0000000以上的地址空间。在驱动程序中不能直接访问,要经过kernel函数vremap得到从新映射之后的地址。

  另外,不少硬件须要一块比较大的连续内存用做DMA传送。这块内存须要一直驻留在内存,不能被交换到文件中去。可是kmalloc最多只能开辟大小为32XPAGE_SIZE的内存,通常的PAGE_SIZE=4kB,也就是128kB的大小的内存。

相关文章
相关标签/搜索