发新话题
打印

aka-kernel大会-走入内核

aka-kernel大会-走入内核


1013日,aka组织了一次kernel大会,本篇为我的发言简稿。


摘要  曾穿越于Linux内核2.0~2.6,并没有熟悉所有的代码,但在Linux内核这样一个庞大的森林面前,几经周折,找到了那个入口,并尽力穿过主要岔路口。本文将分享作者在Linux内核分析方面的经验和体会。

支持各种体系结构的Linux源代码(2.6内核),有14000多个C语言和汇编语言文件,它们存放在大约1000个子目录中。大约由6百万行代码组成,占230MB以上的磁盘空间。如何在这庞大而复杂的世界中抓住主要内容,如何找到进入Linux内部的突破口,又如何能把Linux的源代码变为自己的需要,这是很多人困惑,也是我一直思索的问题。关于Linux内核,尽管我翻译和编写了一些书,尽管也阅读了不少源代码,但依然有不少迷茫,希望与大家共同探讨。
1.    操作系统的本质-虚拟性
纵观操作系统的主要管理功能,虚拟无处不在。进程管理-实际上是把一个CPU虚拟成多个CPU;内存管理-实际上是把有限的内存虚拟成无限大的内存。文件系统-Linux的虚拟文件系统,把设备和各种文件系统都拉入自己的框架之下。至于虚拟设备,虚拟存储,以至于热点话题虚拟机,都是虚拟概念的延伸。
虚拟的本质是以时间换空间。在此,以难度较大的虚拟内存实现机制为入口点。
2.    内核的入口-硬件平台
     操作系统的设计者必须在硬件相关的代码与硬件无关的代码之间划出清楚的界限,以便于一个操作系统很容易地移植到不同的平台。Linux的设计就做到了这点,它把与硬件相关的代码全部放在arch(architecture一词的缩写,即体系结构相关)的目录下,在这个目录下,你可以找到Linux目前版本支持的所有平台,例如,Linux2.6支持的平台有armalpha,i386m68kmips等十多种。在这众多的平台中,大家熟悉的就是i386
在80386的保护模式下,核心问题是虚拟地址到物理地址的转换。不管编译的内核,还是用户程序,其形成的地址都是虚拟地址。这样的虚地址不是被直接送到内存总线,而是被送到内存管理单元(MMU)。MMU由一个或一组芯片组成,其功能是把逻辑地址映射为物理地址,即进行地址转换,如图一所示。












图一  MMU的位置和功能


从图一可以看出,MMU是一种硬件电路,它包含两个部件,一个是分段部件,一个是分页部件。分段机制把一个逻辑地址转换为线性地址;接着,分页机制把一个线性地址转换为物理地址,如图二所示。







               图二 MMU把逻辑地址转换为物理地址
因为IA32规定,段机制是必选,而分页机制是可选,因此,复杂的段机制无法绕过。但是,现代操作系统的实现又大多主要采用分页机制,对于段机制无需过多关注。在这种情况下,如何取舍。在《Linux操作系统原理与实现》(清华大学出版社)这本书中给于简化,并说明如何在Linux中得以应用。
   体会之一,阅读源代码,原理先行。
3.    语言基础——GNU C和AT&T 汇编
   在阅读Linux源代码时,你可能碰到一些不熟悉的C语言代码,或汇编语言片段,或C与汇编混杂在一起的代码片段,它们在一定程度上都成为阅读源代码的拦路虎。
   关于 GNU C,要系统地阅读相关的资料,例如,GNU C 提供了大量的内建函数,其中很多是标准 C 库函数的内建版本,例如memcpy,它们与对应的C 库函数功能相同。其他内建函数的名字通常以 __builtin 开始。
* __builtin_return_address (LEVEL)
内建函数 __builtin_return_address 返回当前函数或其调用者的返回地址,参数
LEVEL
0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址,依此类推。例如:
kernel/sched.c
437:                 printk(KERN_ERR "schedule_timeout: wrong timeout "
438:                        "value %lx from %p\n", timeout,
439:                        __builtin_return_address(0));

请看给出的资料或查阅 GNU C Language Extensions
另外,在Linux的源代码中,有很多C语言的函数中嵌入一段汇编语言程序段,这就是gcc提供的“asm”功能,例如在include/asm-i386/system.h中定义的,读控制寄存器CR0的一个宏read_cr0()

#define read_cr0() ({ \
         unsigned int __dummy; \
         __asm__( \
                 "movl %%cr0,%0\n\t" \
                 :"=r" (__dummy)); \
         __dummy; \
})

关于AT&T 的汇编,在我们的内核之旅网站,“深入分析Linux内核源代码”一书的第二章有针对性的介绍。
  例如:
.globl SYMBOL_NAME(idt)
     .globl SYMBOL_NAME(gdt)
     定义idtgdt为全局符号。
  体会之二:阅读源代码,扫清语言障碍。
4.      源代码结构简析
  Linux内核源代码位于/usr/src/linux目录下,其结构分布如图三所示:




     图三   Linux源代码的分布结构
图三显示了八个主要目录,即 init, kernel, mm, ipc, drivers, fs, arch net ,其包含文件都在"include/" 目录下。在Linux内核中包含了  drivers, fs, arch net 模块,这就使得Linux内核既不是一个层次式结构,也不是一个微内核结构,而是一个“整体式”结构。因为系统调用可以直接调用内核层,因此,该结构使得整个系统具有较高的性能,其缺点是内核修改起来比较困难。
  Linux内核这样庞大而复杂的程序看起来确实让人望而生畏,它象一个很大的球,没有起点和终点。在读源代码的过程中,你会遇到这样的情况,当读到内核的某一部分时又会涉及到其它更多的文件,当返回到原来的地方想继续往下读时,又忘了原来读的内容。在internet上,很多人为此付出了很大的努力,制作出了源代码导航器,这为源代码阅读提供了良好的条件,站点为:http://lxr.linux.no/source。另外一种与source insight类似的工具叫kscope
  从何处读,我认为从kernel目录开始。读原理性较强的sched.c
  体会之三:阅读源代码,掌握全局,从熟悉的原理入手。
5.      核心数据结构
最核心的数据结构当属进程控制块task_struct,在2.6内核中,被725个文件引用,引用次数达几千次。
  以此结构为线索,可以找到与内存管理,文件系统,进程通信以及网络等各个子系统相关的主要数据结构:
   如:mm_struct, vm_area_struct, inode, fs_struct, signal_struct, sysv_sem
  内核中的数据结构数以千计,对每一个数据结构,首先搞清楚其主要功能,要描述什么,主要的字段的含义,主要数据结构之间的关系:
  Interactive Linux kernel maphttp://www.linuxdriver.co.il/kernel_map)这个在线工具的出现,有助于大家理清各种数据结构之间的关系。

6. 内核动态跟踪和探测工具                                                                                             
  第一个工具为kprobe,它是一个动态地收集调试和性能信息的工具,从Dprobe项目派生而来,是一种非破坏性工具,用户用它几乎可以跟踪任何函数或被执行的指令以及一些异步事件(如timer)。它的基本工作机制是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的关联函数被执行,然后继续执行正常的代码路径。
  另一个基于kprobes实现的工具为systemtapsystemtap是一个内核trace工具,用它来研究内核,跟踪内核执行非常方便。Systemtap提供了一种简单的脚本语言,用户使用脚本语言就可以跟踪内核执行。Systemtap把脚本语言转换成C语言,编译成内核模块,内核模块通过kprobes实现对内核的反射,动态修改函数的行为,增加trace功能,这样用户就不必写C代码,也不必编译内核模块了。   
透析真谛,似拨云穿雾;共享智慧,如春风沐浴
http://www.kerneltravel.net
发新话题