学习Linux内核,每个人都有一套自己的方法,算是见仁见智吧。一般从事嵌入式Linux驱动和内核开发工作,工程师主要分为两种:一种是做产品研发的,另外一种是做开源社区。
内核学习
对内核的认知是一个反复的过程,一开始并不完善,可能需要反复纠正。不要陷入这种纠错中;而是以后继续使用和学习过程中,发现了没有弄清楚的地方再深入,毕竟 Linux 内核是不断变化的。
还有一个很好的方式是,从系统调用入手,现在这方面的数据不少,而且对系统调用的语义都有讲解,这样可以间接了解 Linux 系统的一些概念。对系统调用熟悉了,可以根据系统调用的执行过程,来大体了解内核的一个运作过程;但是跟踪系统调用的时候要注意抓主线,现在内核系统很复杂,一些 code path 上可能会涉及多个子系统,可以从名字上猜测它们是干什么的,不需要深入,否则会发现精力完全被分散掉了。
即便是子系统,也是很庞大的。一个省力的方式是网上搜一些相关的文章,便于快速了解这个子系统的运作;然后结合代码,形成自己的认知,最后做一下总结。如果仅仅是快速了解某一子系统的运作,可以参考一些早期代码的注解书籍,再深入的时候看看最新的代码实现。
对内核的认知是一个反复的过程,一开始并不完善,可能需要反复纠正。不要陷入这种纠错中;而是以后继续使用和学习过程中,发现了没有弄清楚的地方再深入,毕竟 Linux 内核是不断变化的。
还有一个很好的方式是,从系统调用入手,现在这方面的数据不少,而且对系统调用的语义都有讲解,这样可以间接了解 Linux 系统的一些概念。对系统调用熟悉了,可以根据系统调用的执行过程,来大体了解内核的一个运作过程;但是跟踪系统调用的时候要注意抓主线,现在内核系统很复杂,一些 code path 上可能会涉及多个子系统,可以从名字上猜测它们是干什么的,不需要深入,否则会发现精力完全被分散掉了。
学习 Linux 内核,一个很重要的是抽象的能力,所谓的抽象这里仅仅是指分清接口和接口的实现。因为 Linux内核子系统很多,有很多子系统相互渗透,这样 code path 看上去很复杂。阅读代码的时候,为了排除干扰,需要分清哪些是自己需要看的,哪些是其它子系统的接口,对于其它子系统的接口,先当作它们功能完善不会出问题好了,这样可以关注重点;打个比方,一个应用程序的代码可能量很大,比如一个 apache 项目,它包含很多组件,有时候阅读代码的时候会看到不同组件的 API,深入看相关组件实现并不现实,这时候分清主次对于代码的阅读就很有帮助了,总不能看到了 malloc 就要先把它的实现弄清楚吧,系统调用多者呢。
Linux内核子系统要怎么学?
Linux内核有上百个驱动子系统,你是否都研究过内核各种驱动子系统的共性,层次结构设计?对于Linux内核子系统的经验,例如USB、I2C、HID等driver的设计经验,你是否都已经具备了。
每一个子系统都巨大无比,而且涉及各种硬件规范,很难去搞明白所有。只能是遇到问题的时候,能对某一部分深入下去。之前了解过 SCSI 的架构,最上层的抽象,中间层的桥梁,最底层的设备驱动控制;如果仅仅是做 driver 的工作,可以把精力放在设备特性上,Linux 内核部分只需要了解驱动
接口和同步、内存管理等基本功能即可。
Linux内核驱动的设计思想
Linux 设备驱动模型是从分类的角度来看待设备,分类是多维的,所以 /sys 下也是多个目录。另外,设备驱动模型给出了系统中设备布局信息,比如:根据总线地址,可以定位对应的设备目录等。
Linux 内核驱动可以都是遵循一个逐层抽象的架构:最上层的抽象层便于系统软件的访问,中间层的实现硬件协议细节,同时提供上下两层连接的接口,对于最下层的 driver 来说,就是要定义底层驱动要实现的接口和实际的设备控制。
由于 Linux 内核各类驱动的框架支持,driver 可以更加关注设备本身的特性。
在Linux嵌入式开发的过程中,我们经常会遇到很多棘手的问题,比如内存泄漏,内核栈溢出,网络丢包等等问题,在定位这类问题时如果没有有效的方法往往会消耗大量的时间去定位排查。