Linux系统移植的四大步骤
本文所测试的环境:虚拟机Ubuntu16.04版本
Linux所用内核:为最新4.17稳定版
工交叉编译工具:arm-Linux-gcc 4.64
uboot版本:win-fs4412.bin(低版本的Uboot 不能引导新版本内核)
开发板:FS4412,三星4412,crotex-A9
最近在学习移植Linux系统,遇到了很多问题总结下,在做每一个步骤的时候,首先问问自己,为什么要这样做,然后再问问自己正在做什么? 先对着学,通透了一遍之后再剁碎了慢慢消化,关注些细节问题。
嵌入式Linux系统移植主要由四大部分组成
一、搭建交叉开发环境
二、bootloader的选择和移植
三、kernel的配置、编译、和移植
四、根文件系统的制作
第一部分:搭建交叉开发环境
先介绍第一分部的内容:搭建交叉开发环境,首先必须得思考两个问题,什么是交叉环境? 为什么需要搭建交叉环境?
先回答第一个问题,在嵌入式开发中,交叉开发是很重要的一个概念,开发的第一个环节就是搭建环境,第一步不能完成,后面的步骤从无谈起,这里所说的交叉开发环境主要指的是:在开发主机上(通常是我的pc机)开发出能够在目标机(通常是我们的开发板)上运行的程序。嵌入式比较特殊的是不能在目标机上开发程序(狭义上来说),因为对于一个原始的开发板,在没有任何程序的情况下它根本都跑不起来,为了让它能够跑起来,我们还必须要借助pc机进行烧录程序等相关工作,开发板才能跑起来,这里的pc机就是我们说的开发主机,想想如果没有开发主机,我们的目标机基本上就是无法开发,这也就是电子行业的一句名言:搞电子,说白了,就是玩电脑! 然后回答第二个问题,为什么需要交叉开发环境?主要原因有以下几点:
原因1 :嵌入式系统的硬件资源有很多限制,比如cpu主频相对较低,内存容量较小等,想想让几百MHZ主频的MCU去编译一个Linux kernel会让我们等的不耐烦,相对来说,pc机的速度更快,硬件资源更加丰富,因此利用pc机进行开发会提高开发效率。
原因2:嵌入式系统MCU体系结构和指令集不同,因此需要安装交叉编译工具进行编译,这样编译的目标程序才能够在相应的平台上比如:ARM、MIPS、POWEPC上正常运行。
交叉开发环境的硬件组成主要由以下几大部分: 1.开发主机 2.目标机(开发板) 3.二者的链接介质,常用的主要有3中方式:(1)串口线 (2)USB线 (3)网线 对应的硬件介质,还必须要有相应的软件“介质”支持: 1.对于串口,通常用的有串口调试助手,putty工具等,工具很多,功能都差不多,会用一两款就可以; 2.对于USB线,当然必须要有USB的驱动才可以,一般芯片公司会提供,比如对于三星的芯片,USB下载主要由DNW软件来完成; 3.对于网线,则必须要有网络协议支持才可以,常用的服务主要两个 :
第一:TPFP服务
主要用于实现文件的下载,比如开发调试的过程中,主要用tftp把要测试的bootloader、kernel和文件系统直接下载到内存中运行,而不需要预先烧录到Flash芯片中,一方面,在测试的过程中,往往需要频繁的下载,如果每次把这些要测试的文件都烧录到Flash中然后再运行也可以,但是缺点是:过程比较麻烦,而且Flash的擦写次数是由限的;另外一方面:测试的目的就是把这些目标文件加载到内存中直接运行就可以了,而tftp就刚好能够实现这样的功能,因此,更没有必要把这些文件都烧录到Flash中去 。
第二:NFS服务
主要用于实现网络文件的挂载,实际上是实现网络文件的共享,在开发的过程中,通常在系统移植的最后一步会制作文件系统,那么这是可以把制作好的文件系统放置在我们开发主机PC的相应位置,开发板通过nfs服务进行挂载,从而测试我们制作的文件系统是否正确,在整个过程中并不需要把文件系统烧录到Flash中去,而且挂载是自动进行挂载的,bootload启动后,kernel运行起来后会根据我们设置的启动参数进行自动挂载,因此,对于开发测试来讲,这种方式非常的方便,能够提高开发效率。
具体的搭建细节:LINUX
第二部分:bootloader的选择和移植
一、Boot Loader 概念
就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境,他就是所谓的引导加载程序(Boot Loader)。

二、为什么系统移植之前要先移植BootLoader?
BootLoader的任务是引导操作系统,所谓引导操作系统,就是启动内核,让内核运行就是把内核加载到内存RAM中去运行,那先问两个问题:第一个问题,是谁把内核搬到内存中去运行?第二个问题:我们说的内存是SDRAM,大家都知道,这种内存和SRAM不同,最大的不同就是SRAM只要系统上电就可以运行,而SDRAM需要软件进行初始化才能运行,那么在把内核搬运到内存运行之前必须要先初始化内存吧,那么内存是由谁来初始化的呢?其实这两件事情都是由bootloader来干的,目的是为内核的运行准备好软硬件环境,没有bootloadr我们的系统当然不能跑起来。
三、bootloader的分类。
首先更正一个错误的说法,很多人说bootloader就是U-boot,这种说法是错误的,确切来说是u-boot是bootloader的一种。也就是说bootloader具有很多种类,大概的分类如下图所示:

由上图可以看出,不同的bootloader具有不同的使用范围,其中最令人瞩目的就是有一个叫U-Boot的bootloader,是一个通用的引导程序,而且同时支持X86、ARM和PowerPC等多种处理器架构。U-Boot,全称 Universal Boot Loader,是遵循GPL条款的开放源码项目,是由德国DENX小组开发的用于多种嵌入式CPU的bootloader程序,对于Linux的开发,德国的u-boot做出了巨大的贡献,而且是开源的。
四、u-boot的工作模式
U-Boot的工作模式有启动加载模式和下载模式。启动加载模式是Bootloader的正常工作模式,嵌入式产品发布时,Bootloader必须工作在这种模式下,Bootloader将嵌入式操作系统从FLASH中加载到SDRAM中运行,整个过程是自动的。下载模式就是Bootloader通过某些通信手段将内核映像或根文件系统映像等从PC机中下载到目标板的SDRAM中运行,用户可以利用Bootloader提供的一些令接口来完成自己想要的操作,这种模式主要用于测试和开发。
五、u-boot的启动过程
大多数BootLoader都分为stage1和stage2两大部分,U-boot也不例外。依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。 以三星Exynos4412为例:

具体细节请参考:UBOOT启动流程分析
其中uboot在实际运行中的内存分配情况大致如下:

编译后,将uboot烧录到SD卡中,拨码开关选择相应的位置,则从SD卡开始启动。上电后,配置好电脑的COM口利用串口工具,则可以看到UBOOT启动后的打印信息。
输入printenv查看环境变量
set serverip 192.168.2.132 你Ubuntu的ip(根据你的具体ip来设置)
set ipaddr 192.168.2.130 你板子的ip
set gatewayip 192.168.1.1 你网关的ip
setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 - 42000000
setenv bootargs root=/dev/nfs nfsroot=192.168.2.132:/home/linux/NFS/rootfs rw console=ttySAC2,115200 clk_ignore_unused init=/linuxrc ip=192.168.2.130
saveenv

参数解析:
setenv bootargs(传递给内核的启动参数) root=/dev/nfs(根文件挂载方式)
nfsroot=192.168.2.132:/home/linux/NFS/rootfs rw console=ttySAC2,115200(内核向串口输入信息)
init=/linuxrc(指定内核运行第一个程序(缺省则从/sbin/init启动))
ip=192.168.2.130(指定开发板ip) (超级终端显示如下图)
saveenv(保存到emmc中)一定要保存呐!!!
第三部分:kernel的配置、编译、和移植
一、将下载好的linux-4.17.tar.tar.bz2拷贝到主目录下解压
二、修改顶层目录下的Makefile,主要修改平台的体系架构和交叉编译器,代码如下:
ARCH ?= arm 体系架构是arm架构
CROSS_COMPILE ?= arm-none-linux-gnueabi- 交叉编译器是arm平台的
三、拷贝标准版配置文件,目的是得到跟我们开发板相关的配置信息。
在此我们已经配置好交叉编译工具,但是怎么样才能使内核能运行在我们的ARM芯片中呢?因为Linux支持的平台非常非常多,不仅仅是ARM处理器,当然我们编译的时候只需要编译跟我们平台相关的代码就可以了,平台相关的不需要编译,那么就有个问题,Linux系统中的源代码文件有一万多以个,面对这么庞大的文件数量,我们如何去选择呢? 其实,我们担心的问题也是写操作系统的那哥们早就担心过的问题了,只不过人家已经把这个问题帮我们解决了,因为新版本的Linux内核会更新我们使用的芯片,(Linux内核因为开源更新速度很快就是为了更好的支持新的芯片)我们只需进行很简单的操作,就可以选择出我们要编译的代码,具体的方法就是把相应平台的_deconfig直接make到顶层目录的.config文件中,这样.config文件中就记录了我们要移植平台的平台信息,因为在配置内核时,系统会把所有的配置信息都保存在顶层目录的.config文件中。注意在第一次,进行make menuconfig时,系统会根据我们选取的平台信息自动选取相关的代码和模块,因此我们只需要进入然后再退出,选择保存配置信息就行了,系统会把这些跟我们移植平台相关的所有配置信息全部保存在顶层目录的.config文件中。(新版本的Linux内核都采用了图形界面的方式来配置相关属性来更好更快的支持更多的平台)
下图为linux-4.17版本内核的目录结构:

四、配置内核
$make menuconfig 注意:第一次进去,不做任何操作,直接推出,在推出时提示是否保存配置信息,一定要保存配置信息,点击“YES”。这样我们的.config中就已经保存了我们开发平台的信息。 在这个环节,我们需要关心一个问题,make menuconfig时,系统到低都做了哪些事情?为什么会出现图形化的界面?图形化的界面中的相关内容是从哪里来的? 图形化的界面当然是由一个特殊的图形库来实现的,还记得第一次make menuconfig时,系统并没有出现图形化的界面,而是报错了,并且提示我们缺少 ncurses-devel ,此时只需要按照要求安装一个libncurses5-dev就行了,sudo apt-get install libncurses5-dev,有了这个图形化库的支持,我们才能够正常显示图形化界面。
好了,图形化界面的问题解决了,那还有另外一个问题就是图形化界面里面的内容是从哪里来的?要回答这个问题,我们就要提一下Linux内核的设计思想了,Linux 内核是以模块的方式来组织这个操作系统的,那么,为什么要用模块的方式来组织呢?模块的概念又是什么呢?
在此来一一回答这个问题。Linux2.6内核的源码树目录下一般都会有两个文件:Kconfig和Makefile。分布在各目录下的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文件相关的内核配置菜单。每个目录都会存放功能相对独立的信息,在每个目录中会存放各个不同的模块信息,比如在/dev/char/目录下就存放了所有字符设备的驱动程序,而这些程序代码在内核中是以模块的形式存在的,也就是说当系统需要这个驱动的时候,会把这个驱动以模块的方式编译到系统的内核中,编译分为静态编译和动态编译,静态编译内核体积比动态编译的体积要大,前面已经说了每个目录下面都会有一个Kconfig的文件,我们还会问,这个文件中都存放了什么信息?前面说了,每个目录的Kconfig文件描述了所属目录源文件相关的内核配置菜单,有其特殊的语法格式,图形化界面的文字正是从这个文件中读取出来的,如果把这个文件中的相应目录文件的信息全部删除,那么在图形化界面中将看不到该模块的信息,因此也不能进行模块的配置。
在内核配置make menuconfig(或xconfig等)时,系统会自动从Kconfig中读出配置菜单,用户配置完后保存到.config(在顶层目录下生成)中。在内核编译时,主Makefile调用这个.config,(.config的重要性就体现在,它保存了我们的所有的配置信息,是我们选取源代码并且进行编译源代码的最终依据!!!)就知道了用户对内核的配置情况。上面的内容说明:Kconfig就是对应着内核的配置菜单。假如要想添加新的驱动到内核的源码中,可以通过修改Kconfig来增加对我们驱动的配置菜单,这样就有途径选择我们的驱动,假如想使这个驱动被编译,还要修改该驱动所在目录下的Makefile。因此,一般添加新的驱动时需要修改的文件有两种,即:Kconfig 和相应目录的Makefile(注意不只是两个),系统移植的重要内容就是给内核添加和删除相应的模块,因此主要修改的内核文件就是Kconfig 和相应目录的Makefile这两个文件。
Exynos4412最接近的配置是:arch/arm/config/exynos_defconfig。
在顶层目录下输入命令:make ARCH=arm exynos_defconfig,这样便是Exynos的基础配置直接保存在顶层的Makefile中了,剩下来就根据我们需要什么服务,利用make ARCH=arm menuconfig或者make menuconfig(以配置好交叉环境),提一句,在第一次运行这个命令的时候可能会报错,原因是缺少一个相应的库文件,在命令行输入:sudo apt-get install libncurses5-dev安装后在尝试make即可。
输入命令后出现以下界面,提示要有合适的空间,全屏即可。

在此有的会出现以下问题,就是如果你没有配置好交叉编译环境,则make menuconfig默认的是进入X86环境的下界面,在配置好交叉环境之后,则是出现这样的标识,说明已经成功配置了arm。

在根据下面的提示,进入菜单选择需要配置的服务。(在需要的选项下敲击空格使【】中有【*】即可,或者直接Y,取消N。)
System Type --->
[*] Samsung EXYNOS --->
[*] SAMSUNG EXYNOS4
* EXYNOS SoCs *
-*- SAMSUNG EXYNOS4210
[*] SAMSUNG EXYNOS4412
再配置网卡,因为下载内核和挂载根文件系统是需要通过TFTP协议来将UBUNTU的文件传输到开发板上的。
[*] Networking support --->
Networking options --->
<*> Packet socket
<*>Unix domain sockets
[*] TCP/IP networking
[*] IP: kernel level autoconfiguration
Device Drivers --->
[*] Network device support --->
[*] Ethernet driver support (NEW) --->
<*> DM9000 support (开发板的网卡型号)
配置网络文件系统:
File systems --->
[*] Network File Systems (NEW) --->
<*> NFS client support
[*] NFS client support for NFS version 3
[*] NFS client support for the NFSv3 ACL protocol extension
[*] Root file system on NFS
配置设备树,在arch/arm/boot/dts/exynos4412-origen-dts。利用编辑工具打开它。
在任意地方添加以下配置,这是根据开发板所搭建的相关的硬件配置。
srom-cs1@5000000 {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x5000000 0x1000000>;
ranges;
ethernet@5000000 {
compatible = "davicom,dm9000";
reg = <0x5000000 0x2 0x5000004 0x2>;
interrupt-parent = <&gpx0>;
interrupts = <6 4>;
davicom,no-eeprom;
mac-address = [00 0a 2d a6 55 a2];
};
};
五、编译内核
$make zImage 通过上述操作我们能够在 arch/arm/boot 目录下生成一个 zImage 文件,这就是经过压缩的内核镜像。 内核的编译过程是非常复杂的,注意这里的编译是静态编译,此时会执行顶层目录下的Makefile中的zImage命令,在执行的过程中,会根据当前目录的.config文件去选择编译源代码。编译内核的具体步骤比较复杂,然后进入目录 linux-4.4.82/arch/arm/boot执行命令将 zImage 制作成 uImage
mkimage -A arm -O linux -T kernel -C none -a 0x41000000 -e 0x41000040 -n “内核名称” -d zImage uImage
或者直接在顶层目录 make uImage ,则也会在arch/arm/boot目录下生成内核文件。
六、通过tftp网络服务下载测试内核
根据文中之前提到的设置NFS和TFTP,设置保存好环境变量后,复位开发板,测试是否能够正常启动(注意:在此之前应设置好需要nfs挂载的文件系统,最后才能看到效果).内核测试和启动过程也是比较复杂的,利用串口工具查看打印信息,如果信息中出现错误,提示找不到根文件系统,大部分是因为网络服务没有启动或者没有配置好。
第四部分:根文件系统的介绍
由本文的第一张图:Flash存储中存放文件的分布图可知,文件系统的制作和移植是系统移植的最后一道工序了,在这里首先要提几个问题: 1.什么是文件系统? 2.如何实现文件系统? 3.常用的文件系统有哪些?为什么需要这些文件系统? 下面来一一回答这些问题:
文件系统我们在日常生活中则很少听说,但是它确实存在,只是名字不叫文件系统罢了,一般叫资料库。资料库里面的文件众多,我们如何快速准确的找到我们要的那份文件呢?资料库采用了分类索引的方法来实现快速查找。类似于我们学校图书馆的管理方式,一楼可能是哲学类,二楼是社科类的,三楼是电子类的,四楼是计算机类的…………等等,我们把这种进行了分类索引的资料库叫文件系统。 对于计算机而言,文件其实就是资料数据,只能存储在物理介质上面,比如:硬盘,但是我们人不可能自己读取物理介质上的文件,或者自己把文件写入物理介质,物理介质上文件的读写只能采用程序来完成,为了方便实现,程序又被分成了物理介质驱动程序、内容存储程序和文件内容存储程序。物理介质驱动程序专门用于从物理介质上存取数据;内容存储程序用于把文件内容和文件属性信息打包;文件内容存储程序用于把用户输入形成文件内容,或者取得文件内容显示出来。
我们可以把一个文件系统(倒树)分解成多个文件系统(倒树)分别存放到存储介质上,比如:一个存储到光盘里,一个存储到硬盘中,在使用时,我们把光盘里的文件系统的根目录挂到硬盘文件系统的一个目录下面,这样访问这个目录就相当于是访问光盘的根目录了,找到了根目录,我们也就可以访问整个光盘上的文件系统了。
“在Linux系统中一切皆是文件”这句话是我们学习Linux系统的时候常常听到的一句话。虽然有些夸张,但是它揭示了文件系统对于Linux系统的重要性;实际上文件系统对于所有的操作系统都很重要,因为它们把大部分的硬件设备和软件数据以文件的形式进行管理。Linux系统对设备和数据的管理框架图如下:
特有的文件系统类型:Ramdisk文件系统 在Linux系统中,内存经常用于存储文件系统,这种叫做Ramdisk,Ramdisk有两种,一种是完全把内存看成物理存储介质,利用内存模拟磁盘,运用磁盘的文件系统类型;另一种只是在内存中存储了文件系统逻辑结构,运用tmpfs & ramfs文件系统类型:tmpfs & ramfs
1.概述 用物理内存模拟磁盘分区,挂载这种分区后,就可以跟读写磁盘文件一样读写这里面的文件,但是操作速度要比磁盘文件快得多;所以一般应用在下面几个方面: 1)读写速度要求快的文件应该放在这种文件系统中 2)磁盘分区为flash的情况下,把需要经常读写的文件放在这种文件系统中,然后定期写回flash 3)系统中的临时文件,如/tmp、/var目录下的文件应该放在这种文件系统中 4)/dev设备文件(因为设备文件随驱动和设备的加载和卸载而变化),应该放在这种文件系统中
2. 特点 1)由于数据都存放在物理内存中,所以系统重启后,这个文件系统中的数据会全部丢失 2)ramfs在没有指定最大的大小值情况下,会自动增长,直到用掉系统中所有的物理内存为止,这时会导致系统的崩溃,建议挂载时最好限定其最大的大小值 3)tmpfs如果指定了大小值,自动增长至大小值后,系统会限定它的大小;这个文件系统占用的物理内存页可以背置换到swap分区,但是ramfs不行 。