Linux内核源码阅读系列(3)-代码阅读的陷阱

在前几天些的博文中大概说了一下应该怎样索引代码来帮助阅读过程。但是那篇文章实在是粗略的很,粗略到几乎不能用的地步。于是想偷偷补充一下……

Cscope不能帮你的事情

Cscope本来是为了检索c语言的代码而设计的。这句话的意义就是,Cscope可以索引和帮助检索大部分内核源代码,但是却是有很多遗漏。被遗漏掉的索引理所当然的不能被检索到。所以,这是阅读源代码是必须注意的问题。Linux内核代码无法分析的部分形成原因在于:

  1. 汇编源码包括内迁汇编
  2. 汇编代码和C代码之间的调用关系
  3. 利用函数指针的函数调用
  4. 宏定义的“假函数”
  5. 利用宏在编译时动态生成的函数体

比如,第2点所说的情况,利用汇编语言调用c语言定义的函数,在i386结构的“调度器”dispatcher代码面就存在:

 18 #define switch_to(prev,next,last) do {                                  
 19         unsigned long esi,edi;                                          
 20         asm volatile("pushflnt"               /* Save flags */        
 21                      "pushl %%ebpnt"                                  
 22                      "movl %%esp,%0nt"        /* save ESP */          
 23                      "movl %5,%%espnt"        /* restore ESP */       
 24                      "movl $1f,%1nt"          /* save EIP */          
 25                      "pushl %6nt"             /* restore EIP */       
 26                      "jmp __switch_ton"                                
                          ^^^----------这里利用汇编代码跳转到__switch_to函数
 27                      "1:t"                                             
 28                      "popl %%ebpnt"                                   
 29                      "popfl"                                            
 30                      :"=m" (prev->thread.esp),"=m" (prev->thread.eip),  
 31                       "=a" (last),"=S" (esi),"=D" (edi)                 
 32                      :"m" (next->thread.esp),"m" (next->thread.eip),    
 33                       "2" (prev), "d" (next));                          
 34 } while (0)

第4点中提到的宏定义函数这种情况也比较常见,比如:

#define page_buffers(page)					
	({							
		BUG_ON(!PagePrivate(page));			
		((struct buffer_head *)page_private(page));	
	})

在这里利用宏定义事实上定义了一个函数,而代码索引工具是无法发现这个符号的。
第5点,利用宏定义动态生成的函数体,也是无法被代码索引工具识别的。比如:

 82 #define BUFFER_FNS(bit, name)                                           
...
 91 static inline int buffer_##name(const struct buffer_head *bh)           
 92 {                                                                       
 93         return test_bit(BH_##bit, &(bh)->b_state);                      
 94 }
...
130 BUFFER_FNS(Unwritten, unwritten)

在编译预处理阶段,##name将被替换为正确的缓冲名字,但是,源代码解析工具却不能看到这种变化。这类函数通常和是一些小的内嵌函数,即使是用kgdb调试的时候也是不容易被发现的,要精确发现它们只能靠你的大脑和字符搜索了。所以,在源代码阅读的之前,你必须很清楚的知道,自己究竟可能漏掉什么东西。

内核源码中的其他容易被遗漏的重要内容

除了上面提到的可能被源码索引工具漏掉的内容之外,还必须对下面这些内容保持警惕。

% export LNX=${WHERE YOUR KERNEL SRC LOCATED}

内核的链接器脚本(linker scripts)


find $LNX -name "*lds"

你可以在这里找到链接器脚本的说明。

Every link is controlled by a linker script. This script is written in the linker command language.

The main purpose of the linker script is to describe how the sections in the input files should be mapped into the output file, and to control the memory layout of the output file. Most linker scripts do nothing more than this.

内核中重要的宏定义文件


$LNX/include/linux/moduleparam.h
$LNX/include/linux/init.h

这两个头文件中定义的宏在很多情况吓都可能被用到(特别是驱动程序开发),所以,尽可能把大脑当硬盘用一下。

内核中的汇编文件


find $LNX -name "*.S"

内核中的Makefile


find $LNX -name "Makefile"

内核中的配置文件


find $LNX -name "*config*"

可能妨碍你理解内核行为的另外一个因素是内核设计的思想以及可能被它影响到的代码运行形式——也就是说虽然内核是使用c语言代码写的,但是并不证明她就是简单的结构化的顺序构成。这主要包含两个方面,1. 面向对象的部分实现,比如文件系统和设备模型——设备模型是内核2.5开发时的一个重要目标,目的在于统一驱动程序的动作以及分离procfs和sysfs;2. “发布——订阅”模型,比如 notification chain,主要思想类似于Design Pattern中提到的Observer Pattern,用于事件消息的传递过程。

OK.源码阅读是件不容易的事情,特别是像Linux内核这样规模庞大的源代码。所以,“长期奋战在”Linxu内核开发“第一线的广大”内核开发者,很难有人说清楚究竟应该怎样去读。但是,注意上面提到的陷阱尽管去读也就是了。 🙂

附送用来生成cscope使用db的脚本。Happy hacking!

#!/bin/sh
if [ "$1" = "" ]
then
	echo "Usage: `basename $0`(linux-src-dir) (architecture)";
	echo "e.g. `basename $0` ~/linux-src/ i386";
	exit;
fi

if [ "$2" = "" ]
then
	echo "Usage: `basename $0`(linux-src-dir) (architecture)";
	echo "e.g. `basename $0` ~/linux-src/ i386";
	exit;
fi

LNX=$1
ARCH=$2
mkdir -p ${HOME}/.cscope/
cd /
find $LNX 								
	-path "$LNX/arch/*" ! -path "$LNX/arch/${ARCH}*" -prune -o 	
	-path "$LNX/include/asm-*" ! -path "$LNX/include/asm-${ARCH}*" -prune -o 
	-path "$LNX/tmp*" -prune -o 					
	-path "$LNX/Documentation*" -prune -o 				
	-path "$LNX/scripts*" -prune -o 				
	-path "$LNX/drivers*" -prune -o 				
	-name "*.[chxsS]" -print > ${HOME}/.cscope/cscope.files

cd ${HOME}/.cscope/ #the directory with 'cscope.files'
cscope -b -q -k

—-
参考文献:
x86虚拟调试环境的建立

One thought on “Linux内核源码阅读系列(3)-代码阅读的陷阱

发表评论