BUG追踪

inline function, cache miss and performance
由 droplet 在 周六, 2007-04-28 11:42 提交 BUG追踪 | 内核研究这一周就为一件事头疼,在一个inline function里面加了一个宏,导致引用这个inline function的函数太大,造成cpu的cache miss增加,从而使得性能下降。
inline function的好处是可以减少函数入栈,出栈,以及跳转的开销,从而提高程序的性能。但是,CPU有I-cache和D-cache,如果一个函数的代码都在I-cache里面,性能当然会高,但是如果函数太大,不能全部load到cache里面,导致函数执行过程中,总是要更新cache,同样会导致性能下降。不过这个问题只在IA32的cpu上发现过,其他的CPU没有这个问题。
解决这个问题的时候,还怀疑是CPU的branch prediction有问题,因此还把代码中的条件判断语句人为地改成if后面的是最有可能的情况,else后面是不太常见的情况。效果并不是很明显。不过这个技巧也是一个常识,一般的CPU都会有branch prediction,把branch后面的语句提前load到cache里面,如果if后面的语句执行概率大,就会提高程序性能。

内存泄漏能表现出什么现象?
由 droplet 在 周一, 2007-01-22 20:49 提交 BUG追踪 | 内核研究内存泄漏或者是说,资源耗尽后,系统会表现出什么现象哪?
cpu资源耗尽:估计是机器没有反应了,键盘,鼠标,以及网络等等。这个在windows上经常看见,特别是中了毒。
进程id耗尽:没法创建新的进程了,串口或者telnet都没法创建了。
硬盘耗尽: 机器要死了,交换内存没法用,日志也没法用了,死是很正常的。
内存泄漏或者内存耗尽:新的连接无法创建,free的内存比较少。发生内存泄漏的程序很多,但是要想产生一定的后果,就需要这个进程是无限循环的,是个服务进程。当然,内核也是无限循环的,所以,如果内核发生了内存泄漏,情况就更加不妙。内存泄漏是一种很难定位和跟踪的错误,目前还没看到有什么好用的工具(当然,用户空间有一些工具,有静态分析的,也会动态分析的,但是找内核的内存泄漏,没有好的开源工具)
内存泄漏和对象的引用计数有很大的关系,再加上c/c++都没有自动的垃圾回收机制,如果没有手动释放内存,问题就会出现。如果要避免这个问题,还是要从代码上入手,良好的编码习惯和规范,是避免错误的不二法门。

心里时刻要有把锁
由 droplet 在 周一, 2007-01-22 20:48 提交 BUG追踪 | 内核研究今天追踪一个内核BUG,按以下步骤进行。
1:把崩溃信息拷贝下来,保存成文件aaa.txt
2:把发生崩溃机器上的/proc/ksyms,/proc/modules和内核的System.map保存下来
3:执行这个命令
ksymoops -k ksyms -l modules -m /home/droplet/project/kernel2/System.map aaa.txt 把出错的寄存器,栈,出错地址,代码等打印出来,看看哪一句出错了
4:根据错误提示,做下一步的动作
4.1:如果可以得到出错执行的语句,以及出错所在的函数以及偏移,找到相应的目标文件,执行objdump -D xxx.o > asm.txt得到目标文件的反汇编代码。
4.2:根据出错所执行的代码和出错所在函数的偏移,找到对应函数中的汇编代码(偏移是16进制的,可以用计算器来计算,或者用搜索来找到相应的汇编行)
4.3:根据汇编代码,在相应函数里推导相应的c语言代码。这里需要熟悉一般的c编译的模式,特别是gcc是如何编译代码的。一般什么语句对应什么汇编代码都有一定的模式可循,这个需要长期的积累才行。
4.4:如果开启了代码优化,或者有宏,内联函数等,找起来会麻烦一点
4.5:根据出错信息,推断是引用数据结构出错了,还是代码本身有问题。这里是显示功力的地方。
5:就是不断积累,不断学习,^_^
今天的这个错误,原因是一个变量,可以从用户空间配置,而且在软中断里面使用,但是没有使用锁,导致问题发生。最后的错误原因绝对想不到:
Code: Bad EIP value.
^_^,连出错的汇编代码都找不到,这个肯定是内存已经写坏了。找来找去,原来是锁的问题。还要这段代码不是我写的,否则,真实羞愧啊。
心里时刻要有把锁,如果是在内核里面写程序,这是一个最基本的常识。

内核中的缓冲区溢出,栈溢出
由 droplet 在 周六, 2007-01-20 16:11 提交 BUG追踪 | 内核研究在内核中拷贝内存时,如果源内存的大小超过目的内存的大小,将会导致目的缓冲区的溢出,这样会破坏内存,导致内核崩溃。
所以在使用strcpy,strncpy,memcpy等函数时,一定要确认源和目的缓冲区的大小。同时,还需要注意,如果是字符串,是以"\0"结束的,不能忽略了它的长度。
在计算缓冲区长度时,要注意整数的溢出,错误的长度参数,也会导致内存的拷贝或移动出现缓冲区溢出。
在内核中,常见的缓冲区溢出发生在复制网络包(skb)时,特别是分片重组和分片时。不同类型的网络包,其头部长度和负载长度是不一样的,而且,参数的类型也不同。所以在复制或移动这些数据时,要分清包的类型,这样,才能避免出现缓冲区溢出的错误。(分片重组和分片出现过很多bug,可以搜索一下,看看出错的具体原因)
内核栈发生溢出是因为内核栈的容量有限,而且缓冲区的溢出也有可能导致栈的溢出。栈溢出通常会导致内核崩溃,更严重的,是溢出被利用去做一些其它的事情,比如得到root权限等。

我修改过的LINUX内核BUG(4)
由 droplet 在 周六, 2007-01-20 16:10 提交 BUG追踪 | 内核研究内核版本:2.4.22
现象描述:内核出现空指针错误
现象分析:
这是在实现一个ALG时碰到的问题。在这个程序里,用了一个第三方的应用协议解析库。这个库是用户空间的,经过修改测试,可以在内核空间使用。
在使用这个库的时候,经常会出现空指针错误。代码跟踪发现,在我的代码里引用这个库中的数据结构时,会出现空指针错误。
经过分析代码,同时查看了库的代码。发现这个库所提供的数据结构都是动态分配的,而在我的代码里引用这些数据结构后,没有把这些数据结构释放掉。由于这是一段嵌入网络协议栈的代码,所有内存消耗的数量非常大(这些内存都是全局分配的,所以不会碰到超过内核堆栈的问题)。这样导致内核内存比较混乱。还有一个问题是,我释放了其中的一部分内存,而忘了释放其他的内存。最后,导致了上述的问题。
结论:
仔细释放了所有动态分配的内存后,程序恢复正常。
动态内存的申请和释放是一个非常重要的编程习惯。未释放的内存可能导致内存泄漏或者内存的错误引用。特别是在内核里面写程序,更加要注意这个问题(另外需要注意内核堆栈是8k,所以不要分配太大的局部变量)。

我修改过的LINUX内核BUG(3)
由 droplet 在 周六, 2007-01-20 16:10 提交 BUG追踪 | 内核研究内核版本:2.4.22
现象描述:虚拟vpn设备在经过某种配置之后,设备驱动无法卸载,卸载时,显示设备的计数是2
现象分析:内核中的共享数据结构,一般都会有一个引用计数,当其它数据结构或代码引用这个数据结构时,把引用计数加一;释放这个引用时,减一。引用计数被初始化为一。释放这个数据结构时,只有引用计数为1才能释放这个结构,否则,则会等待其它模块或代码释放它们的引用计数。或者,如果这个结构有相应的引用链表,它就会遍历引用链表,把引用链表上的引用计数都释放掉。
引用计数加一的接口一般是xxx_get或xxx_hold,而减一的接口是xxx_put。如果某段代码需要引用这个数据结构,它先调用 xxx_get,然后使用这个数据结构,然后xxx_put。如果没有用xxx_get,则当引用数据结构时,这个数据结构有可能被释放了,这时会碰到指针为空的错误;如果没有用xxx_put,则引用计数大于1,这个数据结构就不能被释放。
结论:在虚拟vpn设备的驱动程序的某个地方,调用了xxx_get,但是在使用完数据结构后,没有调用xxx_put,导致设备的引用计数大于1,所以不能被卸载。

我修改过的LINUX内核BUG(2)
由 droplet 在 周六, 2007-01-20 16:09 提交 BUG追踪 | 内核研究内核版本:2.2.19
现象描述:内核在某种情况下会崩溃,发生的频率是随机的,发生错误时,会打印出空指针的oops
现象分析:随机发生的错误,说明发生错误的执行流和其它执行流有交互,或者这个执行流会与其它执行流共享变量。
让我们来想象一下,如果是一个进程在执行,执行流的步骤和状态都是确定的。如果这个进程与其它进程有进程间通信,或者这个进程的执行与某个外部变量有关(比如说,网络包),这个进程的执行步骤和状态就不是确定的,而是与外部条件相关。
内核中,有很多种执行流。有进程的执行上下文(系统调用进入),软中断的执行上下文(软中断),中断的执行上下文(中断)。这些上下文是独立的,每一个,都可以单独执行。但是它们可能会共享某些代码或者数据,这种情况下,就会要求互斥或者信号量等。
共享的代码段不会引起问题,共享的数据会引起问题。特别是共享数据是链表结构,或者是整数加减乘除等操作,一定要注意互斥。
上下文的优先级从低到高依次是:进程上下文->软中断上下文->中断上下文。软中断上下文有很多形式,比如软中断,tasklet,task queue,bottom half等,这些都是在软中断上下文中执行的。
低优先级的上下文如果要保证自己在访问某个数据结构时,不会被高优先级的上下文打断,它需要阻止高优先级的上下文执行。一般是如stop_bh或者时 stop_irq之类的函数(具体的可以参考<
结论:最后的追踪发现,在包发送的过程中,本地发送的包从进程上下文进入,它会和转发的包(从软中断进入)有共享的数据结构。但是在进程上下文中,在访问数据结构时,没有禁止软中断上下文的访问,导致空指针的出现,这种错误的发生,与外部包的速率和cpu本身的处理速度有关。加上相应的锁之后,问题解决。

我修改过的LINUX内核BUG(1)
由 droplet 在 周六, 2007-01-20 16:08 提交 BUG追踪 | 内核研究内核版本: linux2.2.19
现象描述: 使用HTTP透明代理在连接很多的时候,吞吐量比较小,传输速度非常慢
现象分析: 内核中,影响执行速度的情况有哪些?
锁:锁会影响执行速度,如果错误的使用自旋锁,还有可能导致死锁,当然其他类型的锁也会导致死锁。死循环:如果程序里面有死循环,而没有进程退出机制,比如内核线程里面有死循环,但是没有把执行权交出去的函数,将会导致内核无法响应其他请求。长链表:如果哈希表的冲突链比较长,或者有一个很长的链表,这样,在查找时,就会消耗很长时间。哈希表的哈希函数如果设计的好的话,可以节省查找时间,如果不好,则会消耗很多时间。如果用树的数据结构,至少可以在最坏情况下,达到比较好的效果。
错误原因:最后发现透明代理在tcp层找连接的时候,用的哈希函数有问题,导致冲突链很长。
解决方法:用一个更好的哈希函数。

最新评论
13 小时 16 分钟 前
20 小时 59 分钟 前
1天 16 小时 前
2 天 14 小时 前
5 天 12 小时 前
5 天 12 小时 前
5 天 17 小时 前
1周 17 小时 前
1周 20 小时 前
2 周 1天 前