如何学习Linux内核?

Linux内核经过30年的不断更新迭代,已经从当初的1万行代码猛增到近3000万行。代码量增加了近3000倍。

  • Linux-0.01:一共88个源文件,10239行代码,只支持X86平台
  • Linux-5.4:一共48815个文件,超过2500万行代码,支持X86、ARM、MIPS等20多个平台
  • Linux-5.10:一共52333个源文件,超过2700万行代码,支持X86、ARM、RISC-V、MIPS等20多种CPU架构平台

根据Linux内核官方网站统计,2019年,Linux内核一共有4249位开发者和提交者,全年平均每小时10.7次提交,平均每天新增数千行代码。

年份 版本工具 提交(commits) 开发人数
1991 - - 1
2002 Bitkeeper 15,474 497
2005 Bitkeeper/Git 23,553 1372
2008 Git 48,851 2304
2011 Git 56,405 2806
2014 Git 75,650 3580
2017 Git 80,826 4175
2019 Git 82,371 4249

以上统计的只是添加到内核主线的开发者人数。除此之外,还有更多的没有添加到内核主线(mainline)的内核模块,比如各大芯片公司开发的硬件驱动,各大互联网公司基于内核开发的内核功能模块等。

内核每天都在变化,以小时为单位不断更新迭代,时刻在变化。毫不夸张地说,你学习的速度,赶不上内核更新的速度。那我们该如何学习呢?

Linux内核的学习难点

Linux内核源码错综复杂,又在不断地更新迭代,学习难度大,笔者做了以下总结,主要体验在以下几个方面:

数据结构:

  • 数据结构之间关系错综复杂、成员巨多
  • 层层内嵌、相互指向、陷入代码的热带雨林中
  • 侵入式(intrusive)数据结构、内嵌双向链表

函数实现:

  • 单个函数最大行数可达1000行以上
  • 分支多:调用几十甚至上百个子函数:start_kernel()
  • 层次深:层层封装、调用层深5级起步
  • 不同版本的API接口变动

内核驱动开发者,在初次接触Linux内核源码时,跟踪代码很容易断篇。内核源码阅读困难主要体现在以下几个方面:

  • 语法障碍:Linux内核中大量应用了GCC编译器的扩展语法,在各种复杂的宏定义、底层编译链接、初始化的地方大量使用:如.section、__attribute__、语句表达式、可变参数宏、xxx_init、__init、initcall机制等,如果对GCC扩展的这块C语法不熟悉,就可能对代码的理解造成一定的困扰。(弥补这块知识,搜:嵌入式C语言自我修养,作者已经在网上分享了关于GCC扩展语法的教程)
  • 找不到函数的定义、调用链跟踪失败
  • 大量的重名函数,不知道调用哪一个,代码分析误入歧途
  • 一个接口多个实现:如xxx_ops->read()
  • 函数指针调用、函数指针作为函数参数
  • 链表的添加、删除、内嵌、offset
  • 各个子系统之间相互交叉调用

小结

Linux内核驱动的新手,在刚开始阅读内核驱动源码时,往往无法在宏观框架和代码细节的把握上达到平衡。但对于一个内核驱动开发者来说,任务压身,老板在催进度,到底对内核的理解到达什么程度,才能胜任内核驱动开发的岗位需求呢?笔者的建议是:要学会需求驱动、以解决问题为导向,不同的开发岗位,目标定位不同、学习层次不同,需求不同,关注点也就不一样,该掌握的知识点和技能也就有一定的差异。想要弄明白这个问题,不想学得糊里糊涂,我们需要先了解一下Linux内核驱动开发生态。

Linux内核驱动开发生态

内核开发岗位在行业中的分布如下图所示:

即使title同样是内核工程师,驱动工程师,在不同的公司,不同的层次,不同的产品线,干得活,或者说干的侧重点也是不一样的,要求的技能树也有一定差异。

在芯片原厂做驱动内核开发,主要工作就是基于自家芯片平台开发驱动,解决各种平台的bug,做各种性能优化。工作的输出不是一个具体的产品,而是一个稳定的BSP软件包,作为一个交钥匙方案,提供一个稳定的SDK给下游的产品开发者使用。

如果你所在的公司从事手机、平板、智能音箱等嵌入式产品的开发,嵌入式工程师主要做的就是根据产品定义、功能需求等因素选择合适的芯片平台,使用他们提供的SDK快速开发产品。当产品的硬件电路升级、或增加新的硬件模块,此时要能根据芯片手册、模块手册快速适配,集成到产品中。做到深一点,还需要根据自家产品的需求,在应用层、驱动层面、甚至内核层面进行优化。

如果你在互联网、云厂商,面对网站高并发访问、数据的分布式存储,在内核层面你需要重点关注的可能是进程的创建销毁效率快不快,CPU实时调度是否满足应用的需求,数据的存储是否高效,安全。内核开发工程师更多关注的是:进程、调度、内存管理、文件系统这一块。

掌握科学的学习方法

了解了Linux内核驱动开发,在行业领域不同职位、不同上下游的工作内容后,我们就可以有的放矢,来制定一套行之有效、更加高效的学习方法了。

正确的思想认识

首先你要确立一套正确的思想认识,从现实主义出发,承认个人能力有效,抛弃各种理想主义、21天速成等不切实际的想法。饭要一口一口吃,路要一步一步走,每个人都是这么走来的,只是你没有看到而已。切勿浮沙筑高台,根基不牢,地动山摇,基础不大好,学到后面往往感觉很吃力,后劲不足。

二是要从实用角度出发,一个人不可能掌握Linux内核的所有东西,不同的开发岗位,需要不同的技能树,要在工作需求、业务驱动下,有目的的去学习,这样学起来才不会迷茫,知道自己学的这个有啥用,为什么要学。

建立自己的思维模型

人类在学习一个复杂的知识时,无论是有意识还是无意识,经常会采用以下三种思维方式来学习:

  • 类比思维:看到鸟想起造飞机就是类比思维,学习一个新东西,没有类似的经验可以参考,只能从身边类似的事务中找出关联。学习内核驱动开发也是如此,从零开始摸索不太现实,参考其他平台、其他操作系统的方法,再结合要学习的对象,往往会让新手走很多弯路。
  • 经验思维:一门新的技术经过前期不断探索、摸索,已经开始渐渐普及,此时再去学习,可以参考他们分享的文档、手册,消化吸收,在此基础上再不断深化学习,借鉴前人的经验,踩在巨人的肩膀上,可以少走很多弯路,提高学习的效率,这种学习思维也称为经验思维。经验思维的弊端是前人的经验也有可能是片面的、不科学的,走了很多弯路,后来者如果再走一遍,也会绕一些圈子。
  • 演绎思维:为了解决经验思维的缺陷,目前更科学的一种学习思维叫演绎思维:基于已经成熟的技术,根据前人的经验,重新设计新的课程体系,进行系统化学习,构建完整的知识体系和技能树。

嵌入式已经是很成熟的技术了,只要我们掌握科学的学习方法,系统化地学习,就可以快速构建嵌入式内核驱动开发所需要的完整知识体系和技能树,为后续的进阶学习打下良好的基础。

软件分层与模块化设计

软件工程不仅仅是一种软件编程思想,更是一把学习Linux内核的利器,Linux内核中处处可以看到软件分层的思想:BSP、驱动、应用层。即使是一个驱动模块:硬件层、电气层、控制器层、协议层…

Linux内核虽然是个宏内核,但通过模块化的设计,可以把Linux划分为多个独立的模块,这些模块可以通过裁剪,从内核中删除,也可以通过配置,快速添加到内核中,甚至不需要重新编译内核,可以在一个正在运行的内核中加载一个或多个内核模块运行,动态加载,动态卸载。

面向对象编程思想

OOP是一种编程思想,跟具体的编程语言无关。使用C语言,同样也可以实现面向对象编程思想,通过结构体和函数指针的封装,我们同样也可以使用C语言来实现封装、继承和多态。

Linux内核虽然是用C语言开发的,但处处体现着面向对象的编程思想,学会使用OOP思想去分析内核,会让你对复杂的Linux内核子系统有一个更加深刻和直观的理解。

需要的基础理论知识

内核开发还是有一定门槛的,学员在学习内核驱动开发之前,最好补充以下基础知识:

  • Linux开发工具、vimMakefile、IDE、debug
  • 工具链:编译、链接、重定位、安装、运行
  • 程序主战场:内存管理、堆、栈、堆栈溢出
  • 处理器架构、指令集、CPU工作原理
  • C语言:结构体、指针、OOP编程思想、扩展语法
  • 软件工程:模块化编程、软件分层、框架
  • 操作系统概念:进程、线程、调度、临界区、同步
  • Linux系统调用:了解、会用、掌握、升华

工作的年限越高,职位越高,越能体会到基础知识的重要性。勿在浮沙筑高台,基础不牢,地动山摇。同样是做厨师,做一道菜,超市里有现成的速冻饺子,买来就可以煮了,为什么还要学习剁馅儿、擀面皮、配材料这些看起来很初级、很基础的呢,原因很简答,一流的厨子,差异往往就在这些基础之上,只有掌握了基本功,你才有能力做出有差异化、更好的饺子。学习内核驱动开发也是如此,现在很多开发板、SDK、开发平台越来越傻瓜化,人性化,拿来后马上就可以投入开发,快速开发出产品,但为什么还要学习上面这些基础呢?跟做饺子是一个道理,只有掌握了基础,你才有可能做出更好的产品,做出差异化的产品,可以随时升级迭代产品,否则,你的产品就寄托在第三方,浮在空中,无法掌握自主权。

嵌入式学习路线

为了夯实嵌入式开发所需要的核心理论知识和技能,笔者特地设计了前4套课程,由浅入深,步步为营,逐步推荐,针对嵌入式ARM+linux 设计的一套核心技能提升路线:

驱动开发核心理论,Linux内核开发入门实战视频教程:《Linux内核编程》,具有一线芯片原厂开发经验的驱动工程师录制,详情点击:王利涛老师个人淘宝店:Linux内核编程