1)实验平台:正点原子Linux
开发板
2)
摘自《正点原子I.MX6U嵌入式Linux驱动开发指南
》
关注官方微信号公众号,获取更多资料:正点原子
前几章我们重点讲解了如何移植uboot到I.MX6U-ALPHA开发板上,从本章开始我们就开始学习如何移植Linux内核。同uboot一样,在具体移植之前,我们先来学习一下Linux内核的顶层Makefile文件,因为顶层Makefile控制着Linux内核的编译流程。
35.1 Linux内核获取关于Linux的起源以及发展历史,这里就不啰嗦了,网上相关的介绍太多了!即使写到这里也只是水一下教程页数而已,没有任何实际的意义。有限的时间还是放到有意义的事情上吧,Linux由Linux基金会管理与发布,Linux官网为,所以你想获取最新的Linux版本就可以在这个网站上下载,网站界面如图35.1.1所示:
图35.1.1 linux官网
从图35.1.1可以看出最新的稳定版Linux已经到了5.1.4,大家没必要追新,因为4.x版本的Linux和5.x版本没有本质上的区别,5.x更多的是加入了一些新的平台、新的外设驱动而已。
NXP会从https://www.kernel.org下载某个版本的Linux内核,然后将其移植到自己的CPU上,测试成功以后就会将其开放给NXP的CPU开发者。开发者下载NXP提供的Linux内核,然后将其移植到自己的产品上。本章的移植我们就使用NXP提供的Linux源码,NXP提供Linux源码已经放到了开发板光盘中,路径为:1、例程源码-》4、NXP官方原版Uboot和Linux-》linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2。
35.2 Linux内核编译初次编译先看一下如何编译Linux源码,这里编译一下I.MX6U-ALPHA开发板移植好的Linux源码,已经放到了开发板光盘中,路径为:1、例程源码-》3、正点原子修改后的Uboot和Linux-》linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2。在Ubuntu中新建名为"alientek_linux"的文件夹,然后将linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2这个压缩包拷贝到前面新建的alientek_linux文件夹中并解压,命令如下:
tar -vxjf linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2
解压完成以后的Linux源码根目录如图35.2.1所示:
图35.2.1 正点原子提供的Linux源码根目录
以EMMC核心板为例,讲解一下如何编译出对应的Linux镜像文件。新建名为"mx6ull_alientek_emmc.sh"的shell脚本,然后在这个shell脚本里面输入如下所示内容:
示例代码35.2.1 mx6ull_alientek_emmc.sh文件内容
1 #
!/bin/sh
2 make ARCH
=arm CROSS_COMPILE
=arm
-linux
-gnueabihf
- distclean
3 make ARCH
=arm CROSS_COMPILE
=arm
-linux
-gnueabihf
- imx_v7_defconfig
4 make ARCH
=arm CROSS_COMPILE
=arm
-linux
-gnueabihf
- menuconfig
5 make ARCH
=arm CROSS_COMPILE
=arm
-linux
-gnueabihf
- all
-j16
使用chmod给予x6ull_alientek_emmc.sh可执行权限,然后运行此shell脚本,命令如下:
./mx6ull_alientek_emmc.sh
编译的时候会弹出Linux图形配置界面,如图35.2.3所示:
图35.2.3 Linux图形配置界面
Linux的图行界面配置和uboot是一样的,这里我们不需要做任何的配置,直接按两下ESC键退出,退出图形界面以后会自动开始编译Linux。等待编译完成,完成以后如图35.2.4所示:
图35.2.4 Linux编译完成
编译完成以后就会在arch/arm/boot这个目录下生成一个叫做zImage的文件,zImage就是我们要用的Linux镜像文件。另外也会在arch/arm/boo/dts下生成很多.dtb文件,这些.dtb就是设备树文件。
编译Linux内核的时候可能会提示"recipefortarget 'arch/arm/boot/compressed/piggy.lzo' failed",如图35.2.5所示:
图35.2.5 lzop未找到
图35.2.5中的错误提示lzop未找到,原因是没有安装lzop库,输入如下命令安装lzop库即可解决:
sudoapt-ge
tinstalllzop
lzop库安装完成以后在重新编译一下Linux内核即可。
看一下编译脚本mx6ull_alientek_emmc.sh的内容,文件内容如下:
示例代码35.2.1 mx6ull_alientek_emmc.sh文件内容
1 #
!/bin/sh
2 make ARCH
=arm CROSS_COMPILE
=arm
-linux
-gnueabihf
- distclean
3 make ARCH
=arm CROSS_COMPILE
=arm
-linux
-gnueabihf
- imx_v7_defconfig
4 make ARCH
=arm CROSS_COMPILE
=arm
-linux
-gnueabihf
- menuconfig
5 make ARCH
=arm CROSS_COMPILE
=arm
-linux
-gnueabihf
- all
-j16
第2行,执行"makedistclean",清理工程,所以mx6ull_alientek_emmc.sh每次都会清理一下工程。如果通过图形界面配置了Linux,但是还没保存新的配置文件,那么就要慎重使用mx6ull_alientek_emmc.sh编译脚本了,因为它会把你的配置信息都删除掉!
第3行,执行"makexxx_defconfig",配置工程。
第4行,执行"makemenuconfig",打开图形配置界面,对Linux进行配置,如果不想每次编译都打开图形配置界面的话可以将这一行删除掉。
第5行,执行"make",编译Linux源码。
可以看出,Linux的编译过程基本和uboot一样,都要先执行"makexxx_defconfig"来配置一下,然后在执行"make"进行编译。如果需要使用图形界面配置的话就执行"makemenuconfig"。
35.3 Linux工程目录分析将正点原子提供的Linux源码进行解压,解压完成以后的目录如图35.3.1所示:
图35.3.1未编译的Linux源码目录
图35.3.1就是正点原子提供的未编译的Linux源码目录文件,我们在分析Linux之前一定要先在Ubuntu中编译一下Linux,因为编译过程会生成一些文件,而生成的这些恰恰是分析Linux不可或缺的文件。编译完成以后使用tar压缩命令对其进行压缩并使用Filezilla软件将压缩后的uboot源码拷贝到Windows下。
编译后的Linux目录如图35.3.2所示:
图35.3.2 编译后的Linux目录
图35.3.2中重要的文件夹或文件的含义见表35.3.1所示:
表35.3.1 Linux目录
表35.3.1中的很多文件夹和文件我们都不需要去关心,我们要关注的文件夹或文件如下:
1、arch目录
这个目录是和架构有关的目录,比如arm、arm64、avr32、x86等等架构。每种架构都对应一个目录,在这些目录中又有很多子目录,比如boot、common、configs等等,以arch/arm为例,其子目录如图35.3.2所示:
图35.3.2 arch/arm子目录
图35.3.2是arch/arm的一部分子目录,这些子目录用于控制系统引导、系统调用、动态调频、主频设置等。arch/arm/configs目录是不同平台的默认配置文件:xxx_defconfig,如图35.3.3所示:
图35.3.3 配置文件
在arch/arm/configs中就包含有I.MX6U-ALPHA开发板的默认配置文件:imx_v7_defconfig,执行"make imx_v7_defconfig"即可完成配置。arch/arm/boot/dts目录里面是对应开发平台的设备树文件,正点原子I.MX6U-ALPHA开发板对应的设备树文件如图35.3.4所示:
图35.3.4 正点原子I.MX6U开发板对应的设备树
arch/arm/boot目录下会保存编译出来的Image和zImage镜像文件,而zImage就是我们要用的linux镜像文件。
arch/arm/mach-xxx目录分别为相应平台的驱动和初始化文件,比如mach-imx目录里面就是I.MX系列CPU的驱动和初始化文件。
2、block目录
block是Linux下块设备目录,像SD卡、EMMC、NAND、硬盘等存储设备就属于块设备,block目录中存放着管理块设备的相关文件。
3、crypto目录
crypto目录里面存放着加密文件,比如常见的crc、crc32、md4、md5、hash等加密算法。
4、Documentation目录
此目录里面存放着Linux相关的文档,如果要想了解Linux某个功能模块或驱动架构的功能,就可以在Documentation目录中查找有没有对应的文档。
5、drivers目录
驱动目录文件,此目录根据驱动类型的不同,分门别类进行整理,比如drivers/i2c就是I2C相关驱动目录,drivers/gpio就是GPIO相关的驱动目录,这是我们学习的重点。
6、firmware目录
此目录用于存放固件。
7、fs目录
此目录存放文件系统,比如fs/ext2、fs/ext4、fs/f2fs等,分别是ext2、ext4和f2fs等文件系统。
8、include目录
头文件目录。
9、init目录
此目录存放Linux内核启动的时候初始化代码。
10、ipc目录
IPC为进程间
通信,ipc目录是进程间通信的具体实现代码。
11、kernel目录
Linux内核代码。
12、lib目录
lib是库的意思,lib目录都是一些公用的库函。
13、mm目录
此目录存放内存管理相关代码。
14、net目录
此目录存放网络相关代码。
15、samples目录
此目录存放一些示例代码文件。
16、scripts目录
脚本目录,Linux编译的时候会用到很多脚本文件,这些脚本文件就保存在此目录中。
17、security目录
此目录存放安全相关的文件。
18、sound目录
此目录存放音频相关驱动文件,音频驱动文件并没有存放到drivers目录中,而是单独的目录。
19、tools目录
此目录存放一些编译的时候使用到的工具。
20、usr目录
此目录存放与initramfs有关的代码。
21、virt目录
此目录存放虚拟机相关文件。
22、.config文件
跟uboot一样,.config保存着Linux最终的配置信息,编译Linux的时候会读取此文件中的配置信息。最终根据配置信息来选择编译Linux哪些模块,哪些功能。
23、Kbuild文件
有些Makefile会读取此文件。
24、Kconfig文件
图形化配置界面的配置文件。
25、Makefile文件
Linux顶层Makefile文件,建议好好阅读一下此文件。
26、README文件
此文件详细讲解了如何编译Linux源码,以及Linux源码的目录信息,建议仔细阅读一下此文件。
关于Linux源码目录就分析到这里,接下来分析一下Linux的顶层Makefile。
35.4 VSCode工程创建在分析Linux的顶层Makefile之前,先创建VSCode工程,创建过程和uboot一样。创建好以后将文件.vscode/settings.json改为如下所示内容:
示例代码35.4.1.1 settings.json文件内容
1
{
2"search.exclude"
:{
3"**/node_modules"
: true
,
4"**/bower_components"
: true
,
5"**/*.o"
:true
,
6"**/*.su"
:true
,
7"**/*.cmd"
:true
,
8"Documentation"
:true
,
9
10/* 屏蔽不用的架构相关的文件 */
11"arch/alpha"
:true
,
12"arch/arc"
:true
,
13"arch/arm64"
:true
,
14"arch/avr32"
:true
,
15"arch/[b-z]*"
:true
,
16"arch/arm/plat*"
:true
,
17"arch/arm/mach-[a-h]*"
:true
,
18"arch/arm/mach-[n-z]*"
:true
,
19"arch/arm/mach-i[n-z]*"
:true
,
20"arch/arm/mach-m[e-v]*"
:true
,
21"arch/arm/mach-k*"
:true
,
22"arch/arm/mach-l*"
:true
,
23
24/* 屏蔽排除不用的配置文件 */
25"arch/arm/configs/[a-h]*"
:true
,
26"arch/arm/configs/[j-z]*"
:true
,
27"arch/arm/configs/imo*"
:true
,
28"arch/arm/configs/in*"
:true
,
29"arch/arm/configs/io*"
:true
,
30"arch/arm/configs/ix*"
:true
,
31
32/* 屏蔽掉不用的DTB文件 */
33"arch/arm/boot/dts/[a-h]*"
:true
,
34"arch/arm/boot/dts/[k-z]*"
:true
,
35"arch/arm/boot/dts/in*"
:true
,
36"arch/arm/boot/dts/imx1*"
:true
,
37"arch/arm/boot/dts/imx7*"
:true
,
38"arch/arm/boot/dts/imx2*"
:true
,
39"arch/arm/boot/dts/imx3*"
:true
,
40"arch/arm/boot/dts/imx5*"
:true
,
41"arch/arm/boot/dts/imx6d*"
:true
,
42"arch/arm/boot/dts/imx6q*"
:true
,
43"arch/arm/boot/dts/imx6s*"
:true
,
44"arch/arm/boot/dts/imx6ul-*"
:true
,
45"arch/arm/boot/dts/imx6ull-9x9*"
:true
,
46"arch/arm/boot/dts/imx6ull-14x14-ddr*"
:true
,
47
},
48"files.exclude"
:{
49"**/.git"
: true
,
50"**/.svn"
: true
,
51"**/.hg"
: true
,
52"**/CVS"
: true
,
53"**/.DS_Store"
: true
,
54"**/*.o"
:true
,
55"**/*.su"
:true
,
56"**/*.cmd"
:true
,
57"Documentation"
:true
,
58
59/* 屏蔽不用的架构相关的文件 */
60"arch/alpha"
:true
,
61"arch/arc"
:true
,
62"arch/arm64"
:true
,
63"arch/avr32"
:true
,
64"arch/[b-z]*"
:true
,
65"arch/arm/plat*"
:true
,
66"arch/arm/mach-[a-h]*"
:true
,
67"arch/arm/mach-[n-z]*"
:true
,
68"arch/arm/mach-i[n-z]*"
:true
,
69"arch/arm/mach-m[e-v]*"
:true
,
70"arch/arm/mach-k*"
:true
,
71"arch/arm/mach-l*"
:true
,
72
73/* 屏蔽排除不用的配置文件 */
74"arch/arm/configs/[a-h]*"
:true
,
75"arch/arm/configs/[j-z]*"
:true
,
76"arch/arm/configs/imo*"
:true
,
77"arch/arm/configs/in*"
:true
,
78"arch/arm/configs/io*"
:true
,
79"arch/arm/configs/ix*"
:true
,
80
81/* 屏蔽掉不用的DTB文件 */
82"arch/arm/boot/dts/[a-h]*"
:true
,
83"arch/arm/boot/dts/[k-z]*"
:true
,
84"arch/arm/boot/dts/in*"
:true
,
85"arch/arm/boot/dts/imx1*"
:true
,
86"arch/arm/boot/dts/imx7*"
:true
,
87"arch/arm/boot/dts/imx2*"
:true
,
88"arch/arm/boot/dts/imx3*"
:true
,
89"arch/arm/boot/dts/imx5*"
:true
,
90"arch/arm/boot/dts/imx6d*"
:true
,
91"arch/arm/boot/dts/imx6q*"
:true
,
92"arch/arm/boot/dts/imx6s*"
:true
,
93"arch/arm/boot/dts/imx6ul-*"
:true
,
94"arch/arm/boot/dts/imx6ull-9x9*"
:true
,
95"arch/arm/boot/dts/imx6ull-14x14-ddr*"
:true
,
96
}
97
}
创建好VSCode工程以后就可以开始分析Linux的顶层Makefile了。
35.5 顶层Makefile详解Linux的顶层Makefile和uboot的顶层Makefile非常相似,因为uboot参考了Linux,前602行几乎一样,所以前面部分我们大致看一下就行了。
1、版本号
顶层Makefile一开始就是Linux内核的版本号,如下所示:
示例代码35.5.1 顶层Makefile代码段
1 VERSION
=4
2 PATCHLEVEL
=1
3 SUBLEVEL
=15
4 EXTRAVERSION
=
可以看出,Linux内核版本号为4.1.15。
2、MAKEFLAGS变量
MAKEFLAGS变量设置如下所示:
示例代码35.5.2 顶层Makefile代码段
16 MAKEFLAGS
+=-rR
--include
-dir
=$
(CURDIR
)
3、命令输出
Linux编译的时候也可以通过"V=1"来输出完整的命令,这个和uboot一样,相关代码如下所示:
示例代码35.5.3 顶层Makefile代码段
69 ifeq
("$(origin V)"
,"command line"
)
70 KBUILD_VERBOSE
= $
(V
)
71 endif
72 ifndef KBUILD_VERBOSE
73 KBUILD_VERBOSE
=0
74 endif
75
76 ifeq
($
(KBUILD_VERBOSE
),1
)
77 quiet
=
78 Q
=
79
else
80 quiet
=quiet_
81 Q
= @
82 endif
4、静默输出
Linux编译的时候使用"make-s"就可实现静默编译,编译的时候就不会打印任何的信息,同uboot一样,相关代码如下:
示例代码35.5.4 顶层Makefile代码段
87 ifneq
($
(filter 4.
%,$
(MAKE_VERSION
)),) # make
-4
88 ifneq
($
(filter
%s
,$
(firstword x$
(MAKEFLAGS
))),)
89 quiet
=silent_
90 endif
91
else # make
-3.8x
92 ifneq
($
(filter s
%-s
%,$
(MAKEFLAGS
)),)
93 quiet
=silent_
94 endif
95 endif
96
97 export quiet Q KBUILD_VERBOSE
5、设置编译结果输出目录
Linux编译的时候使用"O=xxx"即可将编译产生的过程文件输出到指定的目录中,相关代码如下:
示例代码35.5.5 顶层Makefile代码段
116 ifeq
($
(KBUILD_SRC
),)
117
118 # OK
, Make called in directory where kernel src resides
119 # Do we want to locate output files in a separate directory
?
120 ifeq
("$(origin O)"
,"command line"
)
121 KBUILD_OUTPUT
:= $
(O
)
122 endif
6、代码检查
Linux也支持代码检查,使用命令"make C=1"使能代码检查,检查那些需要重新编译的文件。"make C=2"用于检查所有的源码文件,顶层Makefile中的代码如下:
示例代码35.5.6 顶层Makefile代码段
172 ifeq
("$(origin C)"
,"command line"
)
173 KBUILD_CHECKSRC
= $
(C
)
174 endif
175 ifndef KBUILD_CHECKSRC
176 KBUILD_CHECKSRC
=0
177 endif
7、模块编译
Linux允许单独编译某个模块,使用命令"make M=dir"即可,旧语法"make SUBDIRS=dir"也是支持的。顶层Makefile中的代码如下:
示例代码35.5.7 顶层Makefile代码段
179 # Use make M
=dir to specify directory of external module to build
180 # Old syntax make
... SUBDIRS
=$PWD is still supported
181 # Setting the environment variable KBUILD_EXTMOD take precedence
182 ifdef SUBDIRS
183 KBUILD_EXTMOD
?= $
(SUBDIRS
)
184 endif
185
186 ifeq
("$(origin M)"
,"command line"
)
187 KBUILD_EXTMOD
:= $
(M
)
188 endif
189
190 # If building an external module we
do not care about the all
: rule
191 # but instead _all depend on modules
192 PHONY
+= all
193 ifeq
($
(KBUILD_EXTMOD
),)
194 _all
: all
195
else
196 _all
: modules
197 endif
198
199 ifeq
($
(KBUILD_SRC
),)
200 # building in the source tree
201 srctree
:=.
202
else
203 ifeq
($
(KBUILD_SRC
)/,$
(dir $
(CURDIR
)))
204 # building in a subdirectory of the source tree
205 srctree
:=..
206
else
207 srctree
:= $
(KBUILD_SRC
)
208 endif
209 endif
210 objtree
:=.
211 src
:= $
(srctree
)
212 obj
:= $
(objtree
)
213
214 VPATH
:= $
(srctree
)$
(if $
(KBUILD_EXTMOD
),:$
(KBUILD_EXTMOD
))
215
216 export srctree objtree VPATH
外部模块编译过程和uboot也一样,最终导出srctree、objtree和VPATH这三个变量的值,其中srctree=.,也就是当前目录,objtree同样为"."。