Linux内核源代码分析方法
一、内核源代码之我见
Linux内核代码的庞大令不少人“望而生畏”,也正由于如此,使得人们对Linux的了解仅处于泛泛的层次。假设想透析Linux,深入操作系统的本质,阅读内核源代码是最有效的途径。我们都知道,想成为优秀的程序猿,须要大量的实践和代码的编写。编程固然重要,可是往往仅仅编程的人非常easy把自己局限在自己的知识领域内。假设要扩展自己知识的广度,我们须要多接触其它人编写的代码,尤其是水平比我们更高的人编写的代码。通过这样的途径,我们能够跳出自己知识圈的束缚,进入他人的知识圈,了解很多其它甚至我们一般短期内无法了解到的信息。Linux内核由无数开源社区的“大神们”精心维护,这些人都能够称得上一顶一的代码高手。透过阅读Linux内核代码的方式,我们学习到的不光是内核相关的知识,在我看来更具价值的是学习和体会它们的编程技巧以及对计算机的理解。
我也是通过一个项目接触了Linux内核源代码的分析,从源代码的分析工作中,我受益颇多。除了获取相关的内核知识外,也改变了我对内核代码的过往认知:
1....Linux内核代码在“外界”看来多少有些神奇感,并且它非常庞大,猛地摆在面前可能感觉无法下手。比方可能来源于一个非常细小的原因——找不到main函数。对于简单的demo程序,我们能够从头至尾的分析代码的含义,可是分析内核代码这招就彻底失效了,由于没有人能把Linux代码从头到尾看上一遍(由于确实没有必要,用到时看就能够了)。
2.不少人也接触过大型软件的代码,但多数属于应用型项目,代码的形式和含义都和自己常接触的业务逻辑相关。而内核代码不同,它处理的信息多数和计算机底层密切相关。比方操作系统、编译器、汇编、体系结构等相关的知识的欠缺,也会让阅读内核代码障碍重重。
3.分析内核代码的方法不够合理。面对大量的并且复杂的内核代码,假设不从全局的角度入手,非常easy陷入代码细节的泥淖中。内核代码尽管庞大,可是它也有它的设计原则和架构,否则维护它对不论什么人来说都是一个噩梦!假设我们理清代码模块的总体设计思路,再去分析代码的实现,可能分析源代码就是一件轻松快乐的事情了。
针对这些问题,我个人是这样理解的。假设没有接触过大型软件项目,可能分析Linux内核代码是一个非常好的积累大型项目经验的机会(确实,Linux代码是我眼下接触到的最大的项目了!)。假设你对计算机底层了解的不够透彻,那么我们能够选择边分析边学习的方式去积累底层的知识。可能刚開始分析代码的进度会稍显迟缓,可是随着知识的不断积累,我们对Linux内核的“业务逻辑”会逐渐明朗起来。最后一点,怎样从全局的角度把握分析的源代码,这也是我想与大家分享的经验。
三、内核源代码分析方法
第一步:资料搜集.CPUFreq机制。
2.performance、powersave、userspace、ondemand、conservative调频策略。
3./driver/cpufreq/。
4./documention/cpufreq。
5.P state和C state。
……
分析Linux内核代码假设能搜集到这些信息,应该说是非常“幸运”了。毕竟有关Linux内核的资料确实不如.NET和JQuery那么丰富,只是这相比于十数年前,没有强大的搜索引擎,没有相关的研究资料的时期应该称得上是“大丰收”时代了!我们通过简单的“搜索”(可能会花费一到两天的时间吧),甚至找到了这部分代码所在的源代码文件文件夹,不得不说这样的信息简直是“价值连城”!
第二步:源代码定位.基本弄清了源代码中代码元素存在的含义。
2.找出了该模块所涉及的基本上全部的关键源代码文件。
结合之前搜集到的信息和资料对该待分析代码的总体或者架构描写叙述,我们能够将分析的结果和资料对照,以确定和修正我们对代码的理解。这样,通过一遍的简单凝视,我们就能够从总体上把握了源代码模块的主要结构。这也达到了我们简单凝视的基本目的。
第四步:详细凝视.变量定义在何时被使用。
2.宏定义的代码何时被使用。
3.函数的參数和返回值的含义。
4.函数的运行流程和调用关系。
5.结构体字段的详细含义和使用条件。
我们甚至能够把这一步称为函数详细凝视,由于函数之外的代码元素的含义基本上在简单凝视中已经比較明白了。而函数本身的运行流程、算法等是这部分凝视和分析的主要任务。
比方cpufreq_ondemand策略的实现算法(函数dbs_check_cpu中)是怎样实现的。我们须要逐步分析该函数使用的变量和调用的函数等信息,弄清算法的来龙去脉。最好的结果,我们须要这些复杂函数的运行流程图和函数调用关系图,这是最直观的表达方式。
通过这一步的凝视,我们基本上能全然把握待分析代码总体的实现机制了。而全部的分析工作能够认为完毕了80%。这一步工作尤其关键,我们必须尽量让凝视的信息足够的准确,才干更好的理解待分析代码的内部模块的划分。尽管Linux内核中使用了宏语法“module_init”和“module_exit”声明模块文件,可是对模块内部子功能的划分是建立在充分了解模块的功能基础上的。仅仅有正确划分好模块,我们才干弄清模块提供了哪些外部函数和变量(使用EXPORT_SYMBOL_GPL或者EXPORT_SYMBOL导出的符号)。才干继续下一步的模块内标识符依赖关系分析。
第五步:模块内部标识符依赖关系
通过第四步对代码模块的划分,我们就能够非常“轻松”地逐个对模块进行分析。一般的,我们能够从文件底部的模块出入口函数開始(“module_init”和“module_exit”声明的函数,一般都在文件最后),依据它们调用的函数(自定义的或者其它模块的函数)和使用的关键变量(本文件内的全局变量或者其它模块的外部变量)画出“函数-变量-函数”依赖关系图——我们称为标识符依赖关系图。
当然,模块内标识符依赖关系并不是是单纯的树形结构,非常多情况是错综复杂的网络关系。这时候,我们对代码的详细凝视的作用就体现出来了。我们依据函数本身的含义,将模块进行子功能划分,抽取出每一个子功能的标识符依赖树。
通过标识符依赖关系分析,能够非常清楚的展示模块定义的函数调用了那些函数,使用了哪些变量,以及模块子功能之间的依赖关系——公用了哪些函数和变量等。
第六步:模块间相互依赖关系
一旦将全部的模块内部标识符依赖关系图整理完毕,依据模块使用的其它模块的变量或函数,能够非常easy得到模块之间的依赖关系。
cpufreq代码的模块依赖关系能够表示为例如以下关系。
第七步:模块架构图
透过模块间的依赖关系图,能够非常清楚的表达模块在整个待分析代码中的地位和功能。基于此,我们能够将模块分类,整理出代码的架构关系。
如cpufreq的模块依赖关系图所看到的,我们能够非常清楚的看到全部的调频策略模块都是依赖于核心模块cpufreq、cpufreq_stats和freq_table的。假设我们把被依赖的三个模块抽象为代码的核心框架的话,这些调频策略模块都是建立在这个框架之上的,它们负责和用户层交互。而核心模块cpufreq提供了驱动等相关的接口负责与系统底层交互。因此,我们能够得到例如以下的模块架构图。
当然,架构图并不是模块的无机拼接,我们还须要结合查阅的资料去丰富架构图的含义。因此,这里的架构图的细节会随着不同的人的理解有所偏差。可是架构图主体的含义非常基本一致的。至此,我们完毕了待分析的内核代码的全部分析工作。
四、总结
正如文章開始所说,我们不可能对全部的内核代码进行分析。因此,通过对待分析的代码进行信息搜集,然后依照上述的流程分析出代码的原本始末是了解内核本质的有效手段。这样的依照详细须要分析内核代码的方式,为高速进入Linux内核的世界提供了可能。通过这样的方式,不断的对内核的其它模块分析,最后综合得到自己对Linux内核的理解,也就达到了我们学习Linux内核的目的。
最后向大家推荐两本学习内核的參考书。一本是《Linux内核的设计与实现》,该书为读者高速精简的介绍了Linux内核的主要功能和实现。但不会把读者带入Linux内核代码的深渊中,是了解内核架构和入门Linux内核代码的非常好的參考书,同一时候该书会提高读者对内核代码的兴趣。还有一本是《深入理解Linux内核》,该书的经典我不必多说。我仅仅是建议,假设想更好的学习本书,最好是结合着内核代码一起阅读。由于这本书对内核代码描写叙述的十分详细,所以结合代码进行阅读能够帮助我们更好的理解内核代码。同一时候,在分析内核代码的过程中,也能够在本书中找到具有參考价值的资料。最后,愿大家早日进入内核的世界,体验Linux带给我们的惊喜!