出处:本文作者是中国石油大学(山东)研究生。因为项目需要,他参与了Linux内核源代码的分析。在此之前,他从未深入接触过Linux内核源代码,但从导师那里了解到,他有精力碾压塔克这样的东西,所以被分配了一个有一定难度的CPU变频机制。三个月,他写了几十页的分析文档。无论是文字还是各种图表,对大家都很有说服力。更神奇的是,他反过来总结了一套分析Linux内核源代码的方法。至于Linux内核的分析方法,仁者见仁智者见智,不一定适合你,但可能对你都有启发。
他的毕业论文是从零开始构建编译器,并在此基础上写了一本书,名为《自己构建编译系统:编译、汇编、链接》。因为每行代码都是自己做的,所以这本书更具有可操作性。
他在研究生就业的时候,被推荐报考阿里。以他的能力和认真踏实的作风,我觉得他去阿里没问题。但是应聘的时候面试官问的是Linux内核调度器,他就是没看这部分代码。据说如果他讲分析CPU变频机制的过程,也能打动人,可能是因为紧张,不知道怎么回答。当他说自己是编译器的时候,面试官问他会不会为GCC提交代码,而且因为编译器是完全独立开发的,和GGC没有交集,估计答案没打动面试官,所以溜过阿里,后来去了腾讯。在腾讯工作两年,被挖进蚂蚁金服,转身又遇到了阿里。
分享一下范志东写的内核分析的方法。
首先,我对内核源代码的看法
很多人“害怕”庞大的Linux内核代码,正因为如此,人们对Linux的了解也只是一般水平。如果要分析Linux和操作系统的本质,阅读内核源代码是最有效的方法。众所周知,成为一名优秀的程序员需要大量的实践和代码编写。编程很重要,但对于只编程的人来说,很容易把自己局限在自己的知识领域。如果要拓展知识,就需要接触更多别人写的代码,尤其是比我们水平高的人写的代码。这样我们就可以跳出自己知识圈的束缚,进入别人的知识圈,在短时间内学到更多我们连自己都不会的信息。Linux内核是由开源界无数“大神”精心维护的,他们都堪称顶级的代码大师。通过阅读Linux内核代码,我们不仅学到了内核相关的知识,还学到了他们的编程技巧和对计算机的理解。
我也通过一个项目接触了Linux内核源代码的分析,从源代码的分析中受益匪浅。除了获取相关的内核知识,也改变了我之前对内核代码的理解:
1.内核源代码的分析并不是“高不可攀”。内核源代码分析的难点不在于源代码本身,而在于如何用更合适的方式和手段来分析代码。由于内核庞大,我们无法像分析一般的演示程序一样,从主函数开始一步一步的分析。我们需要一种从中间介入的方式来“分解”内核源代码。这种“按需”的方法让我们能够抓住源代码的主线,而不是纠结于具体的细节。
2.内核的设计很优雅。内核的特殊地位决定了内核的执行效率必须足够高,才能满足当前计算机应用的实时性要求。所以Linux内核采用C语言和汇编的混合编程。但是我们都知道软件执行的效率和软件的可维护性在很多情况下是背道而驰的。如何在保证其高效率的前提下提高内核的可维护性,取决于内核的“美观”设计。
3.惊人的编程技巧。在一般的应用软件设计领域,编码的地位可能不太被重视,因为开发者更看重的是软件的好的设计,而编码只是一个实现手段的问题——就像是用斧头劈柴一样,不需要太多思考。但在内核中却不是这样。好的编码设计带来的不仅仅是可维护性的提高,还有代码性能的提高。
每个人对内核的理解都不一样。随着我们对内核理解的加深,我们会对它的设计和实现有更多的思考和体会。因此,本文期望引导更多游走在Linux内核之外的人进入Linux世界,体验内核的神奇与伟大。而且我不是内核源代码方面的专家。只是想分享一下自己分析源代码的经验和体会,给有需要的人提供参考和帮助。说白了,可以算是对计算机行业的贡献,尤其是在操作系统内核方面。闲话少了(已经啰嗦了很多,不好意思~),我来分享一下我的Linix内核源代码分析方法。
二、内核源码难吗?
本质上,分析Linux内核代码和看别人的代码没什么区别,因为你面对的一般不是你写的代码。我们举个简单的例子。一个陌生人随机给你一个程序,让你看完源代码后解释程序功能的设计。我觉得很多觉得自己编程能力还行的人肯定觉得没什么。只要我耐心的从头到尾看完他的代码,我一定能找到答案,事实也确实如此。现在如果这个人是Linus,给你Linux内核一个模块的代码,你还会觉得那么轻松吗?很多人可能会犹豫。陌生人给你的代码(Linus认识你就不算了,呵呵~),为什么会给我们不一样的感受?我觉得有以下几个原因:
1.1。Linux内核代码在外界看来有些神秘,而且庞大,突然放在面前可能会觉得无从下手。比如可能来自一个很小的原因——主功能找不到。对于一个简单的演示程序,我们可以从头到尾分析代码的含义,但是分析内核代码是完全无效的,因为没有人可以从头到尾读完Linux代码(因为真的没有必要,用的时候看看就行)。
2.很多人都接触过大型软件的代码,但是大部分都属于面向应用的项目,代码的形式和意义都和他们经常接触的业务逻辑有关。与内核代码不同,它处理的大部分信息都与计算机底层密切相关。比如操作系统、编译器、汇编、架构等相关知识的缺乏。,还会使内核代码难以阅读。
3.分析内核代码的方法不合理。面对大量复杂的内核代码,如果不从全局的角度出发,很容易陷入代码的细节中。内核代码虽然庞大,但也有它的设计原则和架构,否则任何人维护都是噩梦!如果我们明确了代码模块的整体设计思路,然后分析代码的实现,那么分析源代码可能会很容易,也很开心。
针对这些问题,我个人理解这一点。如果你没有接触过大型软件项目,那么通过分析Linux内核代码,可能是一个积累大型项目经验的好机会(的确,Linux代码是我目前接触过的最大的项目!)。如果你对计算机底层了解不够,那么我们可以选择边分析边学习来积累底层的知识。可能一开始代码分析的进度会有点慢,但是随着知识的积累,我们对Linux内核的“业务逻辑”会逐渐清晰起来。最后,如何从全局角度把握分析的源代码,也是我的经验与大家分享。
三、内核源代码分析方法
第一步:数据收集
从人们对新事物的认识来看,在探索事物的本质之前,一定有一个认识新事物的过程,这给了我们对新事物的初步概念。比如我们要学钢琴,就需要知道弹钢琴需要我们学习乐理、记谱法、五线谱等基础知识,然后学习钢琴演奏技巧和指法,最后才能真正开始练琴。
分析内核代码也是如此。首先,我们需要定位要分析的代码的内容。进程同步和调度代码、内存管理代码、设备管理代码、系统启动代码等。内核庞大的规模决定了我们不能一次分析完所有的内核代码,需要给自己一个合理的分工。正如算法设计告诉我们的,要解决一个大问题,首先要解决它的子问题。
通过定位要分析的代码范围,我们可以使用手头的所有资源来充分理解代码的整体结构和一般功能。
这里所说的资源都是指百度、谷歌的大型网络搜索引擎、教材和别人提供的关于操作系统原理、经验和资料的专业书籍,甚至还有Linux源代码提供的文档、评论和源代码标识符的名称(不要小看代码中标识符的命名,有时候可以提供关键信息)。简而言之,这里的所有资源都是指你能想到的所有可用资源。当然,我们不太可能通过这种形式的信息收集获得我们想要的所有信息,我们只想尽可能全面。因为收集的信息越全面,代码分析过程中可以使用的信息就越多,分析过程的难度就越小。
这里有一个简单的例子,假设我们要分析Linux的变频机制实现的代码。到目前为止,我们只知道这个排名,通过字面意思大致可以猜测应该和CPU频率调整有关。通过信息收集,我们应该能够获得以下相关信息:
1.cpufreq机制。
2.性能、省电、用户空间、按需和保守频率调制策略。
3./driver/cpufreq/.
4./documention/cpufreq .
5.p状态和C状态。
……
如果能通过分析Linux内核代码收集到这些信息,应该说是非常幸运了。毕竟关于Linux内核的信息没有那么丰富。NET和JQuery,但是和十几年前相比,没有强大的搜索引擎,没有相关的研究数据,应该称之为“丰收”时代!通过简单的“搜索”(可能需要一到两天),我们甚至找到了这部分代码所在的源文件目录。不得不说,这些信息简直是“无价之宝”!
第二步:源代码定位
从数据收集中,我们“幸运”地找到了与源代码相关的源目录。但这并不意味着我们真的在分析这个目录下的源代码。有时候我们找到的目录可能比较分散,有时候我们找到的目录中有很多与特定机器相关的代码,我们更关心要分析的代码的主要机制,而不是与机器相关的专门代码(这样更有助于我们理解内核的本质)。因此,我们需要仔细选择与代码文件相关的信息。当然,这一步不太可能一次完成,也没有人能保证要分析的所有源文件都能一次选中。但我们不用担心,只要能掌握大部分模块相关的核心源文件,后期分析代码自然会全部找到。
回到上面的例子,我们仔细阅读了/documentation/cpufreq下的文档。目前Linux源代码会将模块相关文档保存在源目录的documentation文件夹中。如果要分析的模块没有文档,会增加定位关键源文件的难度,但不会导致我们找到想要分析的源代码。通过阅读文档,我们至少可以关注到源文件/driver/driver/cpufreq/cpufreq . c根据这个源文件的文档描述,结合之前收集的调频策略,我们可以很容易的关注到五个源文件:cpufreq_performance.c、cpufreq_powersave.c、cpufreq_userspace.c、cpufreq_ondemand、cpufreq_conservative.c你找到所有涉及到的文档了吗?放心吧,从他们开始,迟早会找到其他源文件的。如果我们使用sourceinsight来读取windows下的内核源代码,那么通过调用函数、查找符号引用等函数,可以很容易地找到其他文件freq_table.c、cpufreq_stats.c和/include/linux/cpufreq.h。
根据搜索到的信息流方向,可以定位需要分析的源文件。源代码定位的步骤不是很关键,因为我们不需要找到所有的源代码文件,可以把一些工作推迟到代码分析的过程。源代码的定位也很关键,找到一部分源代码文件是分析源代码的基础。
第三步:简单的评论
分析定位到的源文件中每个变量、宏、函数、结构等代码元素的一般含义和作用。之所以称之为这种简单的标注,并不是说这部分的标注工作很简单,而是说这部分的标注不需要过于详细,只要大致描述了相关代码元素的含义即可。相反,这里的工作其实是整个分析过程中最困难的一步。因为这是第一次深入内核代码,尤其是对于第一次分析内核源代码的人来说,大量不熟悉的GNU C语法和铺天盖地的宏定义会让人绝望。这时候只要静下心来,找出每一个关键的难点,就可以保证以后不会再有类似的难点被困住。此外,我们关于内核的其他知识将像树一样继续扩展。
比如cpufreq.c文件开头会出现宏“DEFINE_PER_CPU”,查阅数据基本就能找出这个宏的含义和作用。这里使用的方法与之前收集数据的方法基本相同。此外,我们还可以使用sourceinsight提供的“转到定义”功能来检查其定义,或者使用LKML(Linux内核邮件列表)来查找。如果真的不可能,我们也可以通过提问来寻求答案(想知道LKML和斯达克佛洛是什么?收集信息!)。总之,通过一切可能的手段,我们总能得到这个宏的意义——为每个CPU定义一个自变量。
我们也不强求注释可以一次准确描述(我们甚至不需要搞清楚每个函数的具体实现过程,只需要搞清楚一般的函数意义),我们结合收集到的数据和后面代码的分析不断完善注释的意义(源代码中的原始注释和标识符名称在这里很有用)。通过不断的注释,不断的获取信息,不断的修改注释的含义。
当我们简单地注释所有涉及的源文件时,我们可以获得以下结果:
1.基本明确了源代码中代码元素的含义。
2.找出本模块涉及的几乎所有关键源文件。
结合之前收集的信息和数据来描述要分析的代码的整体或架构,我们可以将分析结果与数据进行比较,以确定和纠正我们对代码的理解。这样,我们就可以通过简单的标注,从整体上把握源代码模块的主要结构。这也达到了我们简单标注的基本目的。
第四步:详细评论
对代码进行简单的标注后,可以认为模块的分析已经完成了一半,剩下的就是对代码的深入分析和透彻理解。简单的注释不能准确描述代码元素的具体含义,所以需要详细的注释。在这一步中,我们需要了解以下内容:
1.当使用变量定义时。
2.当使用宏定义的代码时。
3.参数和函数返回值的含义。
4.函数的执行流程和调用关系。
5.结构字段的具体含义和使用条件。
我们甚至可以把这一步函数称为详细注释,因为在简单的注释中,除了函数之外的代码元素的含义基本上是清楚的。函数本身的执行流程和算法是注释和分析的主要任务。
比如如何实现cpufreq_ondemand策略的算法(在dbs_check_cpu中)。我们需要分析函数使用的变量和一步一步调用的函数,找出算法的来龙去脉。为了得到最好的结果,我们需要这些复杂函数的执行流程图和函数调用图,这是最直观的表达。
通过这一步的标注,基本上可以完全掌握待分析代码的整体实现机制。而所有的分析工作都可以认为完成了80%。这一步尤为关键。我们必须使注释的信息足够准确,以便更好地理解要分析的代码的内部模块的划分。Linux内核虽然使用宏语法module_init和module_exit来声明模块文件,但是模块内部子功能的划分是基于对模块功能的充分理解。只有正确划分模块,才能找出模块提供了哪些外部函数和变量(使用EXPORT_SYMBOL_GPL或EXPORT_SYMBOL导出的符号)。只有这样,我们才能继续分析模块中的标识符依赖关系。
步骤5:模块的内部标识符依赖性
通过对第四步中的代码模块进行划分,我们可以轻松地对模块进行逐一分析。一般我们可以从文件底部的模块入口函数和出口函数开始(由“module_init”和“module_exit”声明的函数一般在文件的末尾),根据它们调用的函数(自定义的或其他模块函数)和使用的关键变量(本文件中的全局变量或其他模块的外部变量)绘制一个“函数-变量-函数”依赖图——我们称之为标识符
当然,模块内的标识符依赖关系不是简单的树形结构,在很多情况下是复杂的网络关系。这时,我们对代码的详细注释的功能就体现出来了。根据函数本身的含义,我们将模块划分为子函数,并提取每个子函数的标识符依赖树。
通过对标识符依赖关系的分析,我们可以清楚地显示模块定义的函数调用了哪些函数,使用了哪些变量,以及模块的子函数之间的依赖关系——哪些函数和变量是共享的。
步骤6:模块之间的相互依赖
一旦模块的所有内部标识符依赖图都被整理出来,就可以根据模块使用的其他模块的变量或函数很容易地获得模块之间的依赖关系。
cpufreq代码的模块依赖关系可以表示如下。
步骤7:模块架构图
通过模块之间的依赖图,我们可以清晰地表达模块在整个待分析代码中的位置和作用。在此基础上,我们可以对模块进行分类,梳理代码架构关系。
如cpufreq的模块依赖图所示,我们可以清楚地看到,所有FM策略模块都依赖于核心模块cpufreq、cpufreq_stats和freq_table。如果我们抽象出三个依赖模块作为代码的核心框架,那么这些FM策略模块都是建立在这个框架之上的,它们负责与用户层的交互。核心模块cpufreq提供驱动程序和其他相关接口,与系统底层交互。因此,我们可以得到下面的模块架构图。
当然,结构图不是模块的无机拼接,我们还需要结合查阅到的数据来丰富结构图的含义。所以这里架构图的细节会根据不同人的理解而有所不同。但是架构图主体的含义基本相同。至此,我们已经完成了待分析内核代码的所有分析工作。
四.总结
如文章开头所述,我们不可能分析所有的内核代码。因此,通过收集关于要分析的代码的信息,然后按照上述过程分析原始代码,是理解内核本质的有效手段。这种根据具体需求分析内核代码的方式,提供了快速进入Linux内核世界的可能性。这样我们不断地分析内核的其他模块,最终得到自己对Linux内核的理解,从而达到学习Linux内核的目的。
最后推荐两本学习内核的参考书。一个是《Linux内核的设计与实现》,快速简洁地向读者介绍Linux内核的主要功能和实现。但不会把读者带入Linux内核代码的深渊。对于理解内核架构和入门Linux内核代码来说,是一本非常好的参考书。同时,这本书也会增强读者对内核代码的兴趣。另一个是对Linux内核的深度理解。这本书的经典就不用我多说了。我只是建议,如果你想更好地学习这本书,最好用内核代码来读。因为这本书对内核代码描述的非常详细,结合代码阅读可以帮助我们更好的理解内核代码。同时,在分析内核代码的过程中,也可以找到这本书的参考资料。最后,希望大家早日进入内核世界,体验Linux带来的惊喜!
1.《linux源代码分析 【 陈老师推荐】Linux内核源码分析方法》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《linux源代码分析 【 陈老师推荐】Linux内核源码分析方法》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/caijing/1167744.html