学习各种开源项目,已经成为很多朋友不可回避的工作内容了。笔者本人也是如此。在接触并学习了若干个开源项目之后,笔者试图对自己工作过程中的若干体会加以总结,以期对一些希望借鉴的朋友有所裨益。
需要说明的是,笔者本人接触的开源项目大多属于计算机系统领域,例如Linux kernel,KVM,QEMU,OpenStack等。因此,此处介绍的经验必定也有些局限。请读者们自行分辨,区别对待。
1. 学习分层和目标管理
对于一个开源项目,可以将与之相关的各种知识和技能的学习大致划分为如下五个层次:
第一层次:了解项目的基本概念、基本用途、逻辑结构、基本原理、产生背景、应用场景等基本知识。
这个层次的基本定位其实就是“科普”。如果对于一个项目只需要有些基本了解,且短期内并不需要上手进行实际技术工作,则学习到这个层次也就可以先应付一下了。
第二层次:掌握项目的基本安装流程和使用方法。
这个层次的基本定位是“入门”,以便对这个项目获得直观认识,对其安装和使用获得亲身体验。如果只是需要以as-is方式使用这个项目,则初步学习到这个层次即可。
第三层次:了解代码的组织,找到各个主要逻辑/功能模块与代码文件之间的对应关系,通过代码分析走通几个关键的、有代表性的执行流程。
这个层次的基本定位是“深入”,开始理解这个项目的实际实现,能够真正将项目的功能、工作原理和代码实现对应起来,获得对这个项目工作过程的直观认识。这个层次是学习开源项目代码的真正开始。如果希望基于这一项目进行应用开发,或者针对与这一项目密切相关的其他项目进行工作时,则对项目本身的代码进行这一层次的理解,会很有帮助。
第四层次:了解该项目所有代码模块、程序文件的作用,走通所有主要执行流程。
这个层次的基本定位是“掌握”,能够比较全面、系统地理解这个项目的设计和实现,并且熟悉项目各个部分的代码。如果希望对项目进行深度定制修改,或者对社区有所贡献,则应当以达到这个层次作为目标。
第五层次:钻研、领悟该项目的各种设计思想与代码实现细节。
这个层次的基本定位是“精通”,精益求精,学无止境。这是大神们追求的境界。如果希望成为项目社区的重要贡献者乃至核心贡献者,则应当以这个层次作为努力的目标。
综上,对于一个开源项目的学习过程可以大致分为五个层次。至于到底要学习到什么阶段,投入多少相关精力,则完全取决于学习的目的。
2. 知识基础
学习一个开源项目需要的知识基础主要包括:
1)该项目涉及的技术领域的背景知识。
举例而言,分析Linux Kenrel,则应该了解操作系统原理;学习OpenStack,则应该知道什么是云计算。如果没有这些背景知识作为基础,上来就死磕源代码,只能是事半功倍。
2) 该项目开发使用的语言及其各种开发调试工具
这个就无需多言了。
3) 英语
很遗憾,目前为止真正流行的开源项目大部分不是起源于国内。因此,除了学习个别极其流行、文档完备的项目之外,大家还是需要自行搜集阅读英文资料参考。学好英语很重要。
当然,到底需要准备多少知识基础,完全取决于学习的目的和层次。如果只是想科普一下,也就不必太过麻烦了。
3. 学习思路
学习一个项目的过程,其实就是由表及里了解分析它的过程。上述提及的五个学习层次便组成了这样一个逐渐深入的过程。在此基础之上,学习、分析代码的过程,也可以尝试做到由表及里、逐渐深入。
在刚开始接触一个项目的时候,我们看到的其实就是一个黑盒子。根据文档,我们一定会发现盒子上具有若干对外接口。通常而言,这些接口可以被分为三类:
- 配置接口:用于对盒子的工作模式、基本参数、扩展插件等等重要特性进行配置。这些配置往往是在盒子启动前一次性配好。在盒子的工作过程中,这些配置或者不变,或者只在少数的情况下发生改变。
- 控制接口:用于在盒子的工作过程中,对于一些重要的行为进行操纵。这是盒子的管理员对盒子进行控制命令注入和状态信息读取的通路。
- 数据接口:用于盒子在工作过程中读取外部数据,并在内部处理完成后向外输出数据。这是盒子的用户真正关心的数据通路。
因此,在分析一个开源项目的代码时,可以围绕重要的配置、控制、数据接口展开分析工作,特别应该注意理解一个关键的接口背后隐藏的操作流程。例如,针对数据接口,至少应当走通一条完整的数据输入输出流程,也即在代码中找到数据从输入接口进入盒子后,经过各种处理、转发步骤,最终从输出接口被传输出去的整个执行过程。一旦走通了这样一条流程,则可以将与数据处理相关的各个主要模块、主要步骤贯穿起来,并将逻辑模块图上和文档中的抽象概念对应到代码实现之中,可以有效推进对于项目的深入理解。
在实践这一思路的过程中,笔者建议可以优先从控制接口和数据接口中各自选择一二重要者进行背后的执行流程详细分析,力争找到其中每一步的函数调用及数据传递关系(对于一些系统、应用库提供的底层函数可以先行跳过以节省时间)。这一工作完成之后,则第1节中第三层次的学习目标即可初步达成。
配置接口在不同的项目中的重要程度不同。对于一些架构极为灵活、配置空间甚大的项目(如OpenStack的Ceilometer),则可以适当多花些时间加以研究,否则简单了解即可。
对于这个学习思路,下文中还将结合实例进行进一步的说明。
4. 若干小建议
以下是笔者的一些零散建议,供大家参考。
1)做好记录
在刚刚入手开始学习某个项目的源代码时,其实很有点破译密码的感觉。大量的数据结构和函数方法散落在代码的各个角落里,等待着学习者将它们贯穿到一个个重要的执行流程中。因此,在分析学习的过程中,无论有什么零散收获,都值得认真记录下来。珍珠自然会串成项链的。
2)不要过分纠缠于细节
立志搞懂一个项目的每行源代码是值得尊敬的,但至少在刚刚入手的时候是没有必要的。如果过于纠缠于代码的实现细节,则可能很快就被搞得头晕眼花不胜其烦了(看英文资料的时候,每遇到一个不认识的词都要立刻查词典么?)。不妨避免细节上的过度纠缠,还是先尽快走通关键的执行流程,将项目的骨干框架搭起来,然后再以此为参照,就可以清晰判断什么代码值得深入分析,什么地方可以简单略过了。
3)想像和联想很重要
如前所述,从零开始搞懂一个项目的代码,就像破译密码。因此,不妨展开合理的想象和联想,将各个零散的发现和理解联系起来,并加以分析印证。在这个过程中,对项目所在领域的背景知识、对项目本身的逻辑框架和工作原理等方面的理解,都是想像和联想的参照与指导。此外,一些关键的函数名、变量名等等都是联想的hint。本质上,编程语言也是语言,而程序代码就是说明文。在分析代码时,一定要超越语言和代码的细节去理解被说明的事物本身。
4)该搜就搜
分析代码的时候,很容易出现的情况就是,一个执行流程走到半截找不到下一步了。。。在这种情况下,当然首先还是推荐采用各种调试工具的单步执行功能加以跟踪。如果暂时不会,或者种种原因只能进行静态代码分析,那么该搜就搜吧。各种IDE工具的文本搜索都能用,哪怕是grep也行。至于到底以什么为搜索关键词,就需要琢磨琢磨了。
5)外事不决问google,内事不决问百度
如题,不解释。
5. 一个例子:OpenStack Cinder分析
此处将以OpenStack Cinder为例,并结合KVM/Qemu和Ceph,说明如何参考上述思路对一个开源项目进行分析。
可能有朋友奇怪为什么选这么个东东做例子。这个吧。。。写文章是忽发起想,举例子是随手抓来。木有原因。。。
首先,想对Cinder进行分析,一定要了解若干相关的基础知识。什么是云计算?什么是块存储?什么是OpenStack?Cinder在OpenStack里的作用?等等等等。如果对这些东西没有概念,则后续学习是很难开展下去的。
在此基础上,如果有条件,则最好能够亲自部署和实际操作一下Cinder(包括必要的其他OpenStack组件),以便对Cinder获得一个直观的认识和体验,为后续分析提供一些参考。此处假定Cinder使用的后端是Ceph,而OpenStack上运行的虚拟机是KVM。
然后,应该从概念上对我们要分析的系统的逻辑框架有个理解。从总体的范畴上讲,应该了解Horizon和Nova各自的逻辑模块结构,以及它们和Cinder的协同工作方式、关系。这部分与Cinder的控制接口及执行路径分析密切相关。此外,还应该了解Cinder和KVM/QEMU、Ceph之间的相互关系。这对于真正理解Cinder很有帮助。从Cinder自身而言,应该了解其内部逻辑模块构成、各自的功能、相互间的控制、数据连接关系等。
在完成上述准备之后,则可以开始对Cinder的代码进行分析了。如前所述,应该考虑在控制接口和数据接口中各自选择一两个关键的、有代表性的加以分析。至于配置接口,假定其实现了某一配置即可,暂时不需要过多花费时间。
Cinder的核心功能其实是OpenStack上的volume管理。至少在Cinder+Ceph方案中,Cinder自身并不在数据传输关键路径上。因此,控制接口的分析就是Cinder源代码分析的重中之重。就入手阶段而言,则有两个接口及其对应执行流程可以作为Cinder分析的起点,即volume的create和attach操作。如果能够彻底打通这两个操作的执行流程(至少要看到Cinder与Ceph通过librbd交互的层面),则对于真正理解Cinder的功能与实现大有帮助。
虽然基于KVM的虚拟机在通过QEMU访问Cinder创建的、Ceph提供的volume时并不通过Cinder,也即,这一部分的源代码其实已经超出了Cinder源代码学习的范畴,但是,如果希望真正彻底地理解Cinder,则对于这一部分知识还是应该有所涉猎,至少应该有概念上的了解。
在达到上述阶段之后,则可以根据自身的需求决定后续计划了。
以上就是笔者结合个人经验对开源项目学习方法给出的若干建议。见解粗浅,欢迎指正,非常感谢~