从实践中学嵌入式Linux应用程序开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.3 Linux内核与移植

Linux内核是Linux操作系统的核心,也是整个Linux功能体现的核心,它是用C语言编写的,符合Posix准。Linux最早是由芬兰黑客Linus Torvalds为尝试在英特尔X86架构上提供自由免费的类UNIX操作系统而开发的。该计划开始于1991年,这里有一份Linus Torvalds当时在Usenet新闻组comp.os.minix所登载的帖子,这份著名的帖子标志着Linux计划的正式开始。在计划的早期有一些Minix黑客提供了协助,而今天全球无数程序员正在为该计划无偿提供帮助。

现今Linux是一个一体化内核(Monolithic Kernel)系统,设备驱动程序可以完全访问硬件。Linux内的设备驱动程序可以方便地以模块化(Modularize)的形式设置,并在系统运行期间可直接装载或卸载。

Linux内核主要功能包括进程管理、内存管理、文件管理、设备管理、网络管理等。

  • 进程管理:进程是在计算机系统中资源分配的最小单元。内核负责创建和销毁进程,而且由调度程序采取合适的调度策略,实现进程间的合理且实时的处理器资源的共享。从而内核的进程管理活动实现了多个进程在一个或多个处理器上的抽象。内核还负责实现不同进程间、进程和其他部件之间的通信。
  • 内存管理:内存是计算机系统中最主要的资源。内核使得多个进程安全而合理地共享内存资源,为每个进程在有限的物理资源上建立一个虚拟地址空间。内存管理部分代码可分为硬件无关部分和硬件有关部分:硬件无关部分实现进程和内存之间的地址映射等功能;硬件有关部分实现不同体系结构上的内存管理相关功能并为内存管理提供与硬件无关的虚拟接口。
  • 文件管理:在Linux系统中的任何一个概念几乎都可以看做一个文件。内核在非结构化的硬件上建立了一个结构化的虚拟文件系统,隐藏了各种硬件的具体细节,从而在整个系统的几乎所有机制中使用文件的抽象。Linux在不同物理介质或虚拟结构上支持数十种文件系统。例如,Linux支持磁盘的标准文件系统ext3和虚拟的特殊文件系统。
  • 设备管理:Linux系统中几乎每个系统操作最终都映射到一个或多个物理设备上。除了处理器、内存等少数的硬件资源之外,任何一种设备控制操作都由设备特定的驱动代码来进行。内核中必须提供系统中可能要操作的每一种外设的驱动。
  • 网络管理:内核支持各种网络标准协议和网络设备。网络管理部分可分为网络协议栈和网络设备驱动程序。网络协议栈负责实现每种可能的网络传输协议(TCP/IP协议等);网络设备驱动程序负责与各种网络硬件设备或虚拟设备进行通信。

1.3.1 Linux内核结构

Linux内核源代码非常庞大,随着版本的发展不断增加。它使用目录树结构,并且使用Makefile组织配置、编译。

初次接触Linux内核,最好仔细阅读顶层目录的readme文件,它是Linux内核的概述和编译命令说明。readme的说明侧重于X86等通用的平台,对于某些特殊的体系结构,可能有些特殊的说明。

顶层目录的Makefile是整个内核配置编译的核心文件,负责组织目录树中子目录的编译管理,还可以设置体系结构和版本号等。

内核源码的顶层有许多子目录,分别组织存放各种内核子系统或者文件。具体的目录说明如表1.7所示。

表1.7 Linux内核源码顶层目录说明

1.3.2 Linux内核配置与编译

1.内核配置

编译内核之前要先配置。为了正确、合理地设置内核编译配置选项,从而只编译系统需要的功能的代码,主要有以下4个方面需要考虑。

  • 尺寸小。自己定制内核可以使代码尺寸减小,运行将会更快。
  • 节省内存。由于内核部分代码永远占用物理内存,定制内核可以使系统拥有更多的可用物理内存。
  • 减少漏洞。不需要的功能编译进入内核可能会增加被系统攻击者利用的机会。
  • 动态加载模块。根据需要动态地加载模块或者卸载模块,可以节省系统内存。但是,将某种功能编译为模块方式会比编译到内核内的方式速度要慢一些。

Linux内核源代码支持20多种体系结构的处理器,还有各种各样的驱动程序。因此,在编译前必须根据特定平台配置内核源代码。Linux内核有上千个配置选项,配置相当复杂。所以,Linux内核源代码组织了一个配置系统。

Linux内核配置系统可以生成内核配置菜单,方便内核配置。配置系统主要包含Makefile、Kconfig和配置工具,可以生成配置接口。配置接口是通过工具来生成的,工具通过Makefile编译执行,选项则是通过各级目录的Kconfig文件定义的。

Linux内核配置命令有make config、make menuconfig和make xconfig,它们分别是字符接口、ncurses光标菜单和X-window图形窗口的配置接口。字符接口配置方式需要回答每一个选项提示,逐个回答内核上千个选项几乎是行不通的;图形窗口的配置接口很好,光标菜单也方便实用。例如,执行make xconfig,主菜单接口如图1.5所示。

图1.5 配置内核

2.内核编译

(1)下载内核源码。

从http://www.kernel.org/pub/Linux/kernel/v2.6/Linux-2.6.14.tar.bz2下载Linux-2.6.14内核(或者更高的版本)至/source/kernel目录。解开压缩包,并进入内核源码目录,具体过程如下:

$ tar jxvf Linux-2.6.14.tar.bz2
$ cd Linux-2.6.14

(2)修改内核目录树根下的Makefile,指明交叉编译器:

$ vim Makefile

找到ARCH和CROSS_COMPILE,修改:

ARCH = arm
CROSS_COMPILE = arm-linux-gcc

(3)设置环境变量:

$ export PATH=$PATH:/usr/local/arm/3.3.2/bin

(4)配置内核产生.config文件:

$ cp arch/arm/configs/smdk2410_defconfig .config

(5)输入内核配置命令,进行内核选项的选择,命令如下:

$ make menuconfig

命令执行成功以后,会看到如图1.6所示的界面。其实我们在图1.5中看到过同样功能的界面,那个图也是内核选项配置界面,只不过那个界面在X-window下才能执行。

图1.6 内核选项配置界面

在各级子菜单项中,选择相应的配置时,有3种选择,它们代表的含义分别如下。

  • Y:将该功能编译进内核。
  • N:不将该功能编译进内核。
  • M:将该功能编译成可以在需要时动态插入到内核中的模块。

如果使用的是make xconfig,使用鼠标就可以选择对应的选项。如果使用的是make menuconfig,则需要使用回车键进行选取。

在每一个选项前都有个括号,有的是中括号,有的是尖括号,还有的是圆括号。用空格键选择时可以发现,中括号中要么是空,要么是“*”;而尖括号中可以是空、“*”和“M”。这表示前者对应的项要么不要,要么编译到内核中;后者则多一样选择,可以编译成模块。而圆括号的内容是要你在所提供的几个选项中选择一项。

在编译内核的过程中,最麻烦的事情就是配置这步工作了。初次接触Linux内核的开发者往往弄不清楚该如何选取这些选项。实际上,在配置时,大部分选项可以使用其默认值,只有小部分需要根据用户不同的需要选择。选择的原则是将与内核其他部分关系较远且不经常使用的部分功能代码编译成为可加载模块,这有利于减小内核的长度,减少内核消耗的内存,简化该功能相应的环境改变时对内核的影响;不需要的功能就不要选;与内核关系紧密而且经常使用的部分功能代码直接编译到内核中。

(6)执行下面的命令开始编译:

$ make zImage

在编译过程中会出现一些错误,可以看到错误发生在/drivers/video/console中。有时是因为我们选择了“VGA text console”选项,去掉这个选项即可。这个选项在“Device Driver”→“Graphics Support”→“console display driver support”下。

总之,这类错误是由于内核配置不当引起的,不需要修改内核源码。

如果按照默认的配置,没有改动的话,编译后系统会在arch/arm/boot目录下生成一个zImage文件,这个文件就是刚刚生成的内核文件。我们需要把它加载到开发板中运行,加以验证。

(7)下载Linux内核。加载到开发板的方式是通过U-Boot提供的网络功能,直接下载到开发板的内存中。首先把内核复制到tftp服务器的根目录下(见tftp配置文件说明)。在我们的实验中,这个目录在/tftpboot下,所以我们在内核源码目录中直接执行下面命令:

$ cp arch/arm/boot/zImage /tftpboot

启动开发板,在U-Boot界面下输入下面一组命令:

FS2410# printenv (查看当前开发板的环境变量)
FS2410# setenv ipaddr 192.168.1.134 (设置开发板的IP地址为192.168.1.134)
FS2410# setenv serverip 192.168.1.23 (设置开发主机的IP地址为192.168.1.23)
FS2410# setenv bootargs console=ttySAC0,115200 (设置终端为串口1,波特率115200)
FS2410# saveenv (保存环境变量)
FS2410# ping 192.168.1.23 (测试网络是否畅通)

如果网络畅通,执行下面的命令下载内核:

FS2410#tftp 30008000 zImage (把Linux内核下载到开发板内存的30008000地址处)
FS2410#go 30008000 (启动内核)

此时可以在超级终端中观察到内核的启动现象,不过内核在此时还不会成功启动,因为还需要做一些其他的移植工作。

1.3.3 Linux内核移植的简介

所谓移植就是把程序代码从一种运行环境转移到另一种运行环境。对于内核移植来说,主要是从一种硬件平台转移到另一种硬件平台上运行。

在一个目标板上Linux内核的移植包括3个层次,分别为体系结构级别的移植、SoC级别的移植和主板级别的移植。

体系结构级别的移植是指在不同体系结构平台上Linux内核的移植,例如,在ARM、MIPS、PPC等不同体系结构上分别都要对每个体系结构进行特定的移植工作。一个新的体系结构出现就需要进行这个层次上的移植。

SoC级别的移植是指在具体的SoC处理器平台上Linux内核的移植,例如,ARM920T IP核的两个处理器S3C2410和AT91RM9200等平台都分别要进行SoC特定的移植工作。

主板级别的移植是指在具体的目标主板上Linux内核的移植,例如,在FS2410目标板上,需要进行主板特定的移植工作。

在这里讨论主板级别的移植,主要是添加开发板初始化和驱动程序的代码。这部分代码大部分是与体系结构相关的,在arch目录下按照不同的体系结构管理。

Linux 2.6内核已经支持S3C2410处理器的多种硬件板,例如,SMDK2410、Simtec-BAST、IPAQ-H1940、Thorcom-VR1000等。我们可以参考SMDK2410参考板来移植开发板的内核。

S3C2410属于片上系统,处理器芯片具备串口、LCD等外围接口的控制器。这样,参考板上的设备驱动程序多数可以直接使用。但并不是所有的外部设备都相同,不同的开发板可以使用不同的SDRAM、Flash、以太网接口芯片等。这就需要根据硬件修改或者开发驱动程序。

例如,串口驱动程序是最典型的设备驱动程序之一,这个驱动程序几乎不需要任何改动。然而,如果用2.4内核的配置使用方式,是不能得到串口控制台信息的。在2.6的内核中,串口设备在/dev目录下对应的设备节点为/dev/ttySAC0、/dev/ttySAC1等。所以,再使用过去的串口设备ttyS0,就得不到控制台打印信息了。现在可以很简单地解决这个问题,把内核命令行参数的控制台设置修改为console = ttySAC0,115200。

在内核已经支持S3C2410处理器以后,基本上无须改动代码就可以让内核运行起来。但是,在有些情况下,我们必须针对不同的设备进行驱动级的移植,至少硬件地址和中断号可能会不同。例如,有时需要移植网络芯片和Nand Flash芯片等外设的驱动程序。