(1) 之前讲过,我们使用 2.6.35.7 版本的内核。
这个版本的内核有三种:
第一种,是 kernel.org 上的官方版本;
第二种,是三星移植过的;
第三种,是九鼎 X210 的移植版本。
我们讲课时使用第三种内核来讲解,后面的移植实验使用第二种内核来移植。
(2) 源码在开发板光盘中有。可以自己去 linux 下解压,然后 make distclean 清理,然后再次打包传输到 windows 下去解压分析。
(3) 解压后,最终在 windows 下得到了一个 kernel 的源码目录树,这个源码目录就是九鼎以三星移植过的内核为原材料,然后自己针对 X210 移植后的内核版本。
(1) Kbuild,Kbuild 是 kernel build 的意思,就是内核编译的意思。这个文件就是 linux 内核特有的内核编译体系需要用到的文件。
(2) Makefile,这个是 linux 内核的总 makefile,整个内核工程用这个 Makefile 来管理的。
(3) mk,是九鼎在移植时自己添加的,不是 linux 内核本身的东西。九鼎添加这个文件的作用是,用这个文件来整体管理 kernel 目录的配置和编译,也就是说这个文件有点类似于我们之前移植 uboot 时自己创建的那个 cp.sh 脚本。
(1) linux 内核很庞大,里面模块很多,而且可配置性非常高。所以 linux 源代码的配置是一个很复杂的事情,必须要有一套很复杂的机制,来保证 linux 内核可以被正确的配置。(对比一下 uboot,uboot 的配置项都是在 xxx.h 中,用宏定义来表示的。uboot 的这种方式很依赖于人的水平,因为 uboot 的配置体系很简单。)
(2 ) linux 内核本身配置项有上千个,光靠人眼睛去看脑袋去记根本不可能,所以内核发明了一种体系用来帮助人进行简单化的配置。这种体系就是我们本课程中重点要研究的东西。
(3) Kbuild、Kconfig 等文件,都是和内核的配置体系有关的。
(1) arch。arch 是 architecture 的缩写,意思是架构。arch 目录下是好多个不同架构的 CPU 的子目录,譬如 arm 这种 cpu 的所有文件都在 arch/arm 目录下,X86 的 CPU 的所有文件都在 arch/x86 目录下。
(2) block。英文是块的意思,在 linux 中,block 表示块设备(以块(多个字节组成的整体,类似于扇区)为单位来整体访问),譬如说 SD卡、iNand、Nand、硬盘等都是块设备。你几乎可以认为块设备就是存储设备。block 目录下放的是一些 linux 存储体系中关于块设备管理的代码。
(3) crypto。英文意思是加密。这个目录下放了一些各种常见的加密算法的C语言代码实现。譬如crc32、md5、sha1等。
(4) Documentation。里面放了一些文档。
(5) drivers。驱动目录,里面分门别类的列出了 linux 内核支持的所有硬件设备的驱动源代码。
(6) firmware。固件。什么是固件?固件其实是软件,不过这个软件是固化到 IC 里面运行的, 叫固件。就像 S5PV210 里的 iROM 代码。
(7) fs。fs 就是 file system,文件系统,里面列出了 linux 支持的各种文件系统的实现。
(8) include。头文件目录,公共的(各种 CPU 架构共用的)头文件都在这里。每种 CPU 架构特有的一些头文件,在 arch/arm/include 目录及其子目录下。
(9) init。init 是初始化的意思,这个目录下的代码,就是 linux 内核启动时初始化内核的代码。
(10) ipc。ipc 就是 inter process commuication,进程间通信,里面都是 linux 支持的 IPC 的代码实现。
(11) kernel。kernel 就是内核,就是 linux 内核,所以这个文件夹下放的就是内核本身需要的一些代码文件。
(12) lib。lib 是库的意思,这里面都是一些公用的有用的库函数。
注意这里的库函数,和 C 语言的库函数不一样的。在内核编程中,是不能用 C 语言标准库函数的;这里的 lib 目录下的库函数,就是用来替代那些标准库函数的。 譬如在内核中,要把字符串转成数字用 atoi,但是内核编程中只能用 lib 目录下的 atoi 函数,不能用标准 C 语言库中的 atoi。譬如在内核中要打印信息时,不能用 printf,而要用 printk,这个 printk 就是我们这个 lib 目录下的。
(13) mm。mm 是 memory management,内存管理,linux 的内存管理代码都在这里。
(14) net。该目录下是网络相关的代码,譬如 TCP/IP 协议栈等都在这里。
(15) scripts。脚本,这个目录下全部是脚本文件,这些脚本文件不是 linux 内核工作时使用的, 而是用来辅助对 linux 内核进行配置编译生产的。我们并不会详细进入分析这个目录下的脚本,而是通过外围来重点学会配置和编译 linux 内核即可。
(16) security。安全相关的代码。不用去管。
(17) sound。音频处理相关的。
(18) tools。linux 中用到的一些有用工具。
(19) usr。目录下是 initramfs 相关的,和 linux 内核的启动有关,暂时不用去管。
(20) virt。内核虚拟机相关的,暂时不用管。
总结:这么多目录,跟我们关系很紧密的就是:arch 和 drivers 目录,然后其他有点相关的还有:include、block、mm、net、lib 等目录。
(1) 主要是检查交叉编译工具链有没有设置对。
CROSS_COMPILE ?= /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
(2) 确认 ARCH = arm。主要目的是为了编译时能找到 arch/arm 目录。
(1) 最后只要出现:configuration written to .config 这句话,就证明我们的操作是正确的。如果没有出现这句话,就有错误。
(2) 可能出现的错误1:名字敲错了。名字是字符串匹配的,一定要正确。
注意:如果这一步配置没有得到 .config 文件,是不能进行到下一步的。实际测试时没有.config也可以 make menuconfig,但是这样做出来的内核编译和烧写运行应该是有问题的。
(1) 可能出现的错误1:ncurses 库没装
错误信息:
*** Unable to find the ncurses libraries or the
*** required header files.
*** 'make menuconfig' requires the ncurses libraries.
***
*** Install ncurses (ncurses-devel) and try again.
解决方案: apt-get install libncurses5-dev (参考了:http://blog.csdn.net/yao_qinwei/article/details/8805101)
root@ubuntu:/home/aston/workspace/uboot_bsp/qt_x210v3s_160307/kernel# cat /etc/network/interfaces
# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback
auto eth0
#iface eth0 inet static
iface eth0 inet dhcp
address 192.168.1.141
netmask 255.255.255.0
gateway 192.168.1.1
root@ubuntu:/home/aston/workspace/uboot_bsp/qt_x210v3s_160307/kernel# ifdown eth0
Internet Systems Consortium DHCP Client 4.2.4
Copyright 2004-2012 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Listening on LPF/eth0/00:0c:29:5f:95:ee
Sending on LPF/eth0/00:0c:29:5f:95:ee
Sending on Socket/fallback
DHCPRELEASE on eth0 to 192.168.101.94 port 67 (xid=0x38ecd765)
root@ubuntu:/home/aston/workspace/uboot_bsp/qt_x210v3s_160307/kernel# ifup eth0
Internet Systems Consortium DHCP Client 4.2.4
Copyright 2004-2012 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Listening on LPF/eth0/00:0c:29:5f:95:ee
Sending on LPF/eth0/00:0c:29:5f:95:ee
Sending on Socket/fallback
DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 3 (xid=0x2c9d7c18)
DHCPREQUEST of 192.168.30.128 on eth0 to 255.255.255.255 port 67 (xid=0x2c9d7c18)
DHCPOFFER of 192.168.30.128 from 192.168.30.254
DHCPACK of 192.168.30.128 from 192.168.30.254
bound to 192.168.30.128 -- renewal in 724 seconds.
root@ubuntu:/home/aston/workspace/uboot_bsp/qt_x210v3s_160307/kernel#
root@ubuntu:/home/aston/workspace/uboot_bsp/qt_x210v3s_160307/kernel#
root@ubuntu:/home/aston/workspace/uboot_bsp/qt_x210v3s_160307/kernel# apt-get install libncurses5-dev
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
libtinfo-dev
Suggested packages:
ncurses-doc
The following NEW packages will be installed:
libncurses5-dev libtinfo-dev
0 upgraded, 2 newly installed, 0 to remove and 706 not upgraded.
Need to get 237 kB of archives.
After this operation, 1,176 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://us.archive.ubuntu.com/ubuntu/ trusty/main libtinfo-dev i386 5.9+20140118-1ubuntu1 [71.2 kB]
Get:2 http://us.archive.ubuntu.com/ubuntu/ trusty/main libncurses5-dev i386 5.9+20140118-1ubuntu1 [166 kB]
Fetched 237 kB in 5s (40.6 kB/s)
Selecting previously unselected package libtinfo-dev:i386.
(Reading database ... 169342 files and directories currently installed.)
Preparing to unpack .../libtinfo-dev_5.9+20140118-1ubuntu1_i386.deb ...
Unpacking libtinfo-dev:i386 (5.9+20140118-1ubuntu1) ...
Selecting previously unselected package libncurses5-dev:i386.
Preparing to unpack .../libncurses5-dev_5.9+20140118-1ubuntu1_i386.deb ...
Unpacking libncurses5-dev:i386 (5.9+20140118-1ubuntu1) ...
Setting up libtinfo-dev:i386 (5.9+20140118-1ubuntu1) ...
Setting up libncurses5-dev:i386 (5.9+20140118-1ubuntu1) ...
root@ubuntu:/home/aston/workspace/uboot_bsp/qt_x210v3s_160307/kernel#
make menuconfig 的结果:
(2) 可能出现的错误 2 :屏幕太小
错误信息:
Your display is too small to run Menuconfig!
It must be at least 19 lines by 80 columns.
解决方案:全屏,或者是把字体调小。
总结:make menuconfig 是第二步配置,具体的用法和配置意义在后面课程讲。我们这里因为是九鼎已经移植过的,所以第二步配置是可以不做的,直接退出即可。
用键盘的向右方向键移动到 EXIT,按回车退出。
(1) 可能出现的错误1:莫名其妙的错误,可以试试先 make distclean。
(2) 代码本身的错误:具体问题具体分析。
(3) 编译完成后得到的内核镜像不在源码树的根目录下,在 arch/arm/boot 这个目录下。得到的镜像名是:zImage。
(1) .config 以 . 开头,是一个隐藏文件,因此平时是看不到的,需要 ls -a 来看。
(2) 当我们 make distclean 后(也就是说默认情况下)是没有 .config 文件的,我们配置的两步过程,就是为了得到内容合适的 .config 文件。
(3) .config 文件是 linux 内核在编译过程中很重要的一个文件,其作用类似于,uboot 中的 include/configs/x210_sd.h,内核在编译过程中会读取 .config 中的配置项,并且用这些配置项去指导整个编译链接过程。
(4) .config 文件的格式类似于脚本文件,其中内容为类似于:CONFIG_ARM=y 的一个一个的配置项。这些配置项就类似于脚本文件中定义的一个一个变量,所以这一行可以被理解为定义了一个变量 CONFIG_ARM,这个变量的值为 y。
(5) .config 文件中每一行都是一个配置项,从 .config 文件的规模可以看出,linux 内核的可配置项有两三千个。所以 linux 内核是高度可配置的,而且 linux 内核的所有配置项很难全部搞明白。 因为 linux 内核的配置项太多太繁杂,超出了人的大脑能够记忆和处理的数量级,因此 linux 内核不像 uboot 那样直接手工配置,而是发明了一个图形化的配置工具 menuconfig。
(1) 我们为了对 .config 文件中的两三千个配置项做逐一合适的配置,专门发明了两步结合的配置方式。
(2) 其实只要人的记忆足够好,大脑足够厉害,完全可以手工去书写/修改 .config 文件完成内核配置,最终只要 .config 中内容是正确的,就不影响编译过程。
(3) 第一步:make xxx_defconfig 解决的问题是大部分的配置项(这一步结束后,99% 的配置项就已经正确了);接下来就是对个别不同的、针对我们的开发板进行细节调整,细节调整就通过 make menuconfig 来完成。
(4) make xxx_defconfig 这一步其实是参考别人已经做好的,这样做有很多好处:减少很多工作量,避开了很多自己不懂的配置项(譬如对内存管理的、调度系统的等模块的配置项),我们只用管自己需要管的。
(5) make menuconfig 其实就是读取第一步得到的 .config,然后给我们一个图形化的界面,让我们可以更加容易的找到自己想要修改的配置项,然后更改配置它。
(1) make x210ii_qt_defconfig 其实相当于:
cp arch/arm/configs/x210ii_qt_defconfig .config
(2) arch/arm/configs 目录下的这么多个 xxx_defconfig 哪里来的?其实这些文件都是别人手工配置好适合一定的开发板的 .config 文件后,自己把 .config 文件保存过去的。譬如说,我们用 S5PV210 这个 SoC,针对这个 SoC 的开发板的最初配置,肯定是三星的工程师去做的。
(1) make menuconfig 中,本身自带的提示就有所有的用法,这里只要全部理解就可以了。
(2) menuconfig 中间的选择区中,有很多个选择项,每个选择项对应 .config 文件中的一个配置项,每一个选择项都可以被选择和配置操作,选择区中的每一项都是有子目录的,将光标放在选择项上按 Enter 键可以进入子目录(子目录可能还会有子目录)。选择区太短放不下所有的一个目录层级的选项,可以用箭头按键的向上箭头和向下箭头来上翻和下翻。
注:在 menuconfig 中操作相关的几个键盘按键,主要是:
Enter 键;
ESC 键;
四个方向箭头按键;
一些特殊字符按键,如:“/”、 “?”;
向上和向下箭头,主要用来在选择项菜单中目录浏览时上下翻;
回车,主要作用是选中并且执行 select/exit/help;
ESC,主要作用是返回上一层;
向左和向右箭头,主要作用是在菜单选项(select、exit、help)间切换。
(3) 用法翻译:
箭头按键 导航整个菜单,回车按键 选择子菜单(注意选项后面有 —> 的选项才是有子菜单的,没有这个标识的没有子菜单),高亮的字母是热键(快捷键),键盘按键 Y、N、M 三个按键的作用分别是,将选中模块 编入、去除、模块化。双击 ESC 表示退出,按下 ? 按键可以显示帮助信息,按下 / 按键可以输入搜索内容,来全局搜索信息(类似于 vi 中的搜索), [ ] 表示不可以模块化,< > 表示可以模块化。
注:linux 内核中,一个功能模块有三种编译方法:一种是编入、一种是去除、一种是模块化。
所谓编入就是,将这个模块的代码直接编译链接到 zImage 中去;
去除就是,将这个模块不编译链接到 zImage 中;
模块化是,将这个模块仍然编译,但是不会将其链接到 zImage 中,会将这个模块单独链接成一个内核模块 .ko 文件,将来 linux 系统内核启动起来后,可以动态的加载或卸载这个模块。
在 menuconfig 中选项前面的括号里,* 表示编入,空白表示去除,M 表示模块化。
(1) linux 为了实现图形化界面的配置,专门提供了一套配置工具 menuconfig。
(2) ncurses 库是 linux 中用来实现文字式的图形界面,linux 内核中使用了 ncurses 库来提供 menuconfig。
(3) scripts\kconfig\lxdialog 目录下的一些 c 文件,就是用来提供 menuconfig 的那些程序源代码。
(1) menuconfig 本身的软件只负责提供 menuconfig 工作的这一套逻辑(譬如在 menuconfig 中,通过上下左右箭头按键来调整光标,Enter、 ESC 键等按键按下的响应),而并不负责提供内容(菜单里的项目)。
(2) menuconfig 显示的菜单内容(一方面是菜单的目录结构,另一方面是每一个菜单项目的细节)是由内核源码树各个目录下的 Kconfig 文件来支持的。
Kconfig 文件中按照一定的格式,包含了一个又一个的配置项,每一个配置项在 make menuconfig 中都会成为一个菜单项目。而且 menuconfig 中显示的菜单目录结构,和源码目录中的 Kconfig 的目录结构是一样的。
(3) 在相应的 Kconfig 文件中删除一个 config 项,则再次 make menuconfig 时,这个项目已经看不到了。
(1) 刚才已经知道,menuconfig 的菜单内容来自于 Kconfig 文件,但是每一个菜单的选择结果(Y、N、M)却不是保存在 Kconfig 文件中的。
Kconfig 文件是不变的, Kconfig 文件只是决定有没有这个菜单项,并不管这个菜单项的选择结果。
(2) menuconfig 工作时,在我们 make menuconfig 打开时,他会读取 .config 文件,并且用 .config 文件中的配置选择结果,来初始化 menuconfig 中各个菜单项的选择值。
总结:菜单项的项目内容从 Kconfig 文件来,菜单项的选择值从 .config 文件来。
(3) 当我们每次退出 make menuconfig 时,menuconfig 机制会首先检查我们有没有更改某些配置项的值,如果我们本次没有更改过任意一个配置项目的值,那直接退出;如果我们有改动配置项的值,则会提示我们是否保存。
此时如果点保存,则会将我们更改过的配置重新写入 .config 文件中记录,下一次再次打开 make menuconfig 时,会再次加载 .config,最终去编译内核时,编译连接程序会考虑 .config 中的配置值,来指导整个编译链接过程。
总结:本节主要内容就是讲:menuconfig 和 Kconfig 和 .config 的关系。
(1) Kconfig 按照一定的格式来书写,menuconfig 程序可以识别这种格式,然后从中提取出有效信息,组成 menuconfig 中的菜单项。
(2) 将来在做驱动移植等工作时,有时需要自己添加 Kconfig 中的一个配置项,来将某个设备驱动添加到内核的配置项目中,这时候就需要对 Kconfig 的配置项格式有所了解,否则就不会添加。
(3) # 开头的行是注释行。
(4) menuconfig 表示菜单(menuconfig 本身属于一个菜单中的项目,但是它又有子菜单项目)、config 表示菜单中的一个配置项(本身并没有子菜单下的项目)。
(5) menuconfig 或者 config 后面空格隔开的大写字母表示的(类似于 NETDEVICES 的)就是这个配置项的配置项名字,这个字符串前面添加 CONFIG_ 后,就构成了 .config 中的配置项名字。
(6) 一个 menuconfig 后面跟着的所有 config 项,就是这个 menuconfig 的子菜单。这就是 Kconfig 中表示的目录关系。
(7) 内核源码目录树中,每一个 Kconfig 都会 source 引入其所有子目录下的 Kconfig,从而保证了所有的 Kconfig 项目都被包含进 menuconfig 中。
这个也告诉我们:如果你自己在 linux 内核中添加了一个文件夹,一定要在这个文件夹下创建一个 Kconfig 文件,然后在这个文件夹的上一层目录的 Kconfig 中,source 引入这个文件夹下的 Kconfig 文件。
(1) tristate 意思是三态( 3 种状态,对应 Y、N、M 三种选择方式),bool 是要么真要么假(对应 Y 和 N)。
所以 tristate 的意思就是,这个配置项可以被三种选择,bool 的意思是,这个配置项只能被 2 种选择。
(1) depends 中文意思是 “取决于” 或者 “依赖于”,所以 depends 在这里的意思是:本配置项依赖于另一个配置项。如果那个依赖的配置项为 Y 或者 M,则本配置项才有意义;如果依赖的那个配置项本身被设置为 N,则本配置项根本没有意义。
(2) depends 项目会导致 make menuconfig 的时候找不到一些配置项。所以你在 menuconfig 中如果找不到一个选项,但是这个选项在 Kconfig 中却是有的,则可能的原因就是这个配置项依赖的一个配置项是不成立的。
(3) depends 并不要求依赖的配置项一定是一个,可以是多个,而且还可以有逻辑运算。这种时候只要依赖项目运算式子的逻辑结果为真,则依赖就成立。
(1) 帮助信息,告诉我们这个配置项的含义,以及如何去配置它。
(1) 配置项被配置成 Y、N、M ,会影响 .config 文件中的 CONFIG_XXX 变量的配置值。
(2) 这个 .config 中的配置值(=y、=m、没有)会影响最终的编译链接过程。如果 =y, 则会被编入(built-in);如果 =m,会被单独连接成一个 ko 模块;如果没有,则对应的代码不会被编译。那么这么是怎么实现的?都是通过 makefile 实现的。
(3) obj-$(CONFIG_DM9000) += dm9000.o
如果 CONFIG_DM9000 变量值为 y,则 obj += dm9000.o,因此 dm9000.c 会被编译;
如果 CONFIG_DM9000 变量未定义,则 dm9000.c 不会被编译;
如果 CONFIG_DM9000 变量的值为 m,则会被连接成 ko 模块(这个是在 linux 内核的 Makefile 中定义的规则)
总结:把 menuconfig 中的菜单项、Kconfig 中的配置项、.config 中的一行、 Makefile 中的一行,这 4 个东西结合起来理解,则整个 linux 内核的配置体系就明了了。
(1) make menuconfig时,会读取 .config 中的配置值,来初始化 menuconfig 中的配置项。
验证:如果理论正确的,那么我自己手工修改了 .config 的配置后,再次 make menuconfig 时,看到的初始值就应该是我手工修改的。
(2) menuconfig 中修改了(按Y、N、M)配置项的值,然后退出时保存,则这个保存结果会修改 .config 文件中的相应行。
验证:如果结论是正确的,那么在 menucofig 中修改了配置后保存退出,再次去手工打开 .config 文件,则可以看到相应配置的一行内容被修改了。
(1) menuconfig 读取 Kconfig 的内容作为菜单项目内容。
验证1:在 Kconfig 中删除一个 config 项,则再次 make menuconfig 时,就看不到这个项目了。
验证2:在 Kconfig 中自己添加创建一个 config 项,则再次 make menuconfig 时,就能看到多了一个项目。
(1) 我找一个模块,把它配制成 y,然后去 make 编译链接,最后得到的 zImage 中,这个模块就应该被编译链接进去到 zImage 中了。
验证:
方法一:去这个模块对应的源代码目录看一下这个源码有没有被编译。
方法二:去 zImage 对应的 elf 格式的 vmlinux 中查看符号。
readelf -Ws vmlinux | grep dm9000
方法三:将 vmlinux 反编译(objdump)后得到的文件中找模块对应的符号。
arm-linux-objdump -D vmlinux > vmlinux_symble
cat vmlinux_symble | grep dm9000
方法四:将 zImage下载到开发板中启动,启动后看你的模块能不能工作。
源自朱有鹏老师.