嵌入式Linux入门7:kernel移植

kernel(内核)的移植在开始时给人的感觉是高大上,内容复杂,工作量大,技术难度高。后来发现内核的移植不过是改改参数,改改寄存器而已。其实,这是因为工作性质决定的。如linus、Alan Cox这样的一、二号功臣,我们无法望其项背。而芯片厂家的大门,也比较难进入,于是,很多人只是接近内核的边缘,做些修补的工作。——而这些人,占的比例很大,笔者就是其中一员。但即使如此,也有很多东西值得学习。

很多年前,笔者任职的公司召开代码评审大会,有位同事对Linux内核提出质疑:Linux内核代码都开源,还要做什么工作?当时笔者研究内核功底不深,不敢回答。笔者认为,不同公司产品不同,不同产品可能源自同一个芯片平台,比如eeprom、flash、ddr、屏幕等,这些适配工作,就需要内核工程师开发、维护。

一、内核编译步骤

一般地,获取内核有2个途径:从kernel.org官方下载;由厂家提供(或是厂家官网,或是直接发RDK包)。

编译内核一般分2步:

1、配置内核:make menuconfig

kernel官方或厂家都会有默认的配置文件,如没有,则可以找相近的配置文件(如经典的2440芯片,可从2410中找配置),然后修改。内核配置主要是选择外设、内核特性,等等。确定配置后,主要是修改源码,很少再会用make menuconfig了。另外,还可以使用make xconfig来配置,这个需要有图形界面,笔者比较少使用这个方式。

2、编译内核

编译内核一般是make zImage或make uImage(uboot专用)。注意,ARM平台需要进行交叉编译,可以在内核源码顶层Makefile中指定编译器和硬件平台(修改CROSS_COMPILE和ARM选项),如果不指定,则在编译命令中需要加上,如make zImage CROSS_COMPILE=arm-linux- ARCH=arm。

3、编译驱动模块

编译内核模块使用make modules,该命令将会编译内核所有的模块,如果编译单个驱动,可以使用SUBDIRS指定驱动目录,如make modules SUBDIRS=drivers/usb/gadget -j4 ARCH=arm CROSS_COMPILE=arm-linux-。

笔者之前的项目开发中,内核、根文件系统由一个部门负责,而应用层由另一部门负责,单独的驱动模块文件以.ko形式存在,需要放到根文件系统中,如果要更新该驱动,需要升级根文件系统分区,而根文件系统中存放应用层的程序文件,由于多部门协作的原因,操作上略显麻烦。因此建议将驱动编译到内核中,从另一方面,将内核、驱动全部由放在一个镜像,也是一个不错的做法。当然,这个建议仅从笔者经验得出的,仅供参考。但是,凡事无绝对,在某个项目中,厂家只提供ko文件,不提供源码,因此只能分别存放不同分区。

4、编译设备树

在3.x版本的内核中已经引入设备树,现在的内核版本已大量使用了,要编译设备树,使用make dtbs即可。设备树位于目录arch/arm/boot/dts中,由该目录的Makefile来指定编译哪些设备树源文件。

不同厂家编译方法可能稍有差异,但本质上是一样的,就笔者接触过的情况而言,有的直接使用一条命令编译打包(笔者弃用之),有的编译目标名称不同(如编译内核使用make kernel.img)。

最后一步是使用内核,涉及内容各式各样。有的厂家直接将镜像文件下载到指定分区即可,有的厂家需要用特定工作将内核镜像重新打包成另一种形式,再用指定工具下载到板子上。

二、内核移植要点

1、阅读厂家提供的手册,有的是驱动开发手册,有的是datasheet(数据手册),有的是RM(reference manual,参考手册),先对芯片平台有个印象。

2、从厂家默认的配置文件中找到涉及到哪些驱动,再找到这个驱动的目录。可以从Kconfig和Makefile来确定,也可以从启动信息中确定。

3、找到主入口文件,分析入口文件做了哪些初始化工作。

4、一般地,厂家已编写好部分驱动,直接使用即可,真正所做的工作,就是根据实际的板子硬件情况来修改,如修改GPIO,删除某些驱动,添加某些驱动,等等。

5、但内核的工作并非如此这般就行了,修改代码是一回事,懂得怎么修改是另一回事。因此,还是要继续学习,可以从简单的驱动入手,结合手册来分析驱动。建议从GPIO、RTC、WDT这些驱动下手。这些驱动,没有过多涉及时序,根据手册的介绍,对照源码,比较容易理解。

另外要指出的是,如果是公司与芯片厂家有合作,则拿资料会容易些(一般会签NDA协议)。如果是个人购买板子,资料一般在光盘或网盘中(但一般不免费公开)。如果都不是,则可以通过google(一定要google)搜索,一般会出现在官网某个地方。比如imx6q、imx6ul的RM可以免费下载。

三、内核学习要点

前面文章提过,(笔者认为)内核知识点分两部分,一是kernel本身的知识点,如内存管理机制(MMU)、时间管理、同步机制、网络协议栈,等等。二是外设驱动,如LED灯、GPIO、按键、mtd。第一部分则由Linux内核开发者那些大神维护,我们抱膜拜的态度去学习,偶有所得,对于自身技能的提高就有很大的帮助。实际工作中更关注第二部分,因为这才是最贴近项目的。用C语言来类比,第一部分可以认为是标准库的函数,不需要深入研究其原理,但可以系统了解一下,待需要时知道如何使用即可。第二部分就是如何使用这些函数完成实际工作。

3.1 学习准备

首先需要了解内核编译的过程,包括配置内核、编译内核、驱动。

然后了解一些内核语法,虽说内核使用C语言编写,但很多语法是GCC扩展的,在学校和教程中是没有学到的。

接着了解platform驱动模型(笔者文章有现成的模板,已经应用于很多个平台上)。此阶段,可以编写简单的字符驱动,并与应用层(用户空间)通信。比如,从用户空间拷贝字符串到内核空间,或反之。

这个阶段,不需要探究原理,能使用即可。比如,platform驱动模型,知道platform_driver如何填写,知道设备、驱动名称需要一致才能匹配,知道驱动探测函数(xx_probe)是真正初始化函数,等即可进入下一步的学习,至于platform驱动是如何与platform设备匹配的,探测函数是如何被调用的,则是后面的事了。

3.2 结合数据手册或参考手册

不结合板子是无法真正学习内核移植的,没有板子,使用qemu模拟亦可(用于学习可以,但毕竟不是实际硬件)。

然后从简单的驱动入手,笔者认为,结合硬件的第一个驱动应该是GPIO驱动,即编写platform驱动设置某个GPIO的方向、电平,如果这GPIO接有LED,则变成了LED驱动。不要一上来就分析MTD子系统、USB子系统,那些太复杂了。GPIO驱动离不开引脚复用,不同芯片平台,引脚复用的机制不同,需要结合代码、手册来学习。这样,会对硬件结构有一个初步认识。

3.3 接触复杂一点的子系统

内核中很多驱动都有其框架,或称子系统,哪怕简单的LED,也有LED子系统。另外还有RTC子系统、WDT子系统、SPI子系统、IIC子系统、MTD子系统、USB子系统,等等。这些子系统的代码已经编写好了,只要使用即可,但要提高技能,还得分析源码。以笔者为例,虽然socket编程很熟悉了,但涉及到底层的却没有了解过。偶然机会,因工作需要解决一些网络驱动问题,继而利用业余时间分析网络子系统(包括有线网络和无线网络),这些经历使笔者对网络通信有进一步的认识。

3.4 进阶

笔者推崇理论与实践结合,具象与抽象结合。如果有些代码太难理解,没关系,打印出来看。如:参数传递,代码执行到何处,某个数值,等,只要想看,都可以打印。对于前面提到的第一部分内核知识,可以研究一下container_of、双向链接,还有sysfs研究,这些算是入门级别的知识。熟悉之后,可以了解通知链、内存管理等等方面。

本文不涉及“非入门级别”的屏幕、摄像头等驱动。

李迟 2017.7.14 周五 晚

你可能感兴趣的:(嵌入式Linux,嵌入式Linux入门)