嵌入式Linux内核移植
姓名:成 炼 学号:081141
实验目的
l 了解Linux内核源代码的目录结构及各目录的相关内容
l 了解Linux内核各配置选项内容和作用
l 掌握Linux内核配置文件的作用
l 掌握Linux内核的编译过程
l 掌握将新增内核代码加入到Linux内核结构中的方法
实验指引
尽管目前Linux2.6版本内核已经增加了很多对ARM体系甚至是S3C2410CPU的支持,但仍然需要对内核作一些小的修改来适应我们的开发板,并且需要重新配置、编译和重新生成新的内核映像。本实验着重从NAND Flash分区到下载到开发板等一系列连贯的操作来进行嵌入式Linux内核的移植。本实验的内核版本为2.6.26。
1.NAND Flash分区
从Nand Flash启动时,S3C2410硬件会自动把Nand Flash前4K代码拷贝芯片内部RAM空间,CPU其实是从内部RAM开始执行代码的,所以vivi必须放到Nand Flash顶端。vivi开始执行后将初始化硬件设备、建立内存空间映射表,为调用内核做好准备;然后把压缩的内核映像加载到SDRAM中;最后跳转到内 核映像入口,启动内核。
内核MTD分区必须与vivi分区相一致。因为,vivi分区中的地址是引导程序、内核映像及文件系统下载到NandFlash的真正地址;而内核启动时,内核并不是去读vivi分区中的地址,而是去读内核MTD分区设定的地址;所以,如果内核MTD分区与vivi分区不相同,很可能导致不能正常启动内核及读取文件系统。
1.1 vivi的重新分区
根据开发板的Nand Flash大小及开发用途确定新的vivi分区,如表1.1。
表1.1 vivi的分区信息表
名称 |
offset |
size |
flag |
容量 |
vivi |
0x00000000 |
0x00020000 |
0 |
128K |
param |
0x00020000 |
0x00010000 |
0 |
64K |
kernel |
0x00030000 |
0x00400000 |
0 |
4M |
root |
0x00430000 |
0x00300000 |
4 |
3M |
yaffs |
0x00730000 |
0x03800000 |
8 |
56M |
ucos |
0x3f300000 |
0x000cc000 |
0 |
816K |
打开vivi源代码下的arch/s3c2410/smdk.c文件,在函 数:“mtd_partition_default_mtd_partitions[]={}”中可以看到vivi默认的Nand Flash分区信息。根据表2的新分区信息,在上述函数中以相同的格式修改原有分区信息即可完成vivi的重新分区。这里可以参考vivi的使用手册,《Getting started with vivi》。
1.2 内核MTD的重新分区
在给内核MTD重新分区之前,有一点应该注意,2.6.16(含)以前内核与2.6.17(含)以后内核的MTD重新分区方法是不一样的,前者是需要增加新的分区信息,而后者源代码初始文件中已含分区信息,需要的是修改分区信息。我们此次实验选用的内核为2.6.26, 其相应的修改如下:
在源代码linux-2.6.26/arch/arm/plat-s3c2410xx/common-smdk.c文件下的“mtd_partition smdk_default_nand_part[]={}”中,可以看到默认的MTD分区。根据表1.1,以相同的格式修改原分区信息即可完成MTD的重新分 区。
/* NAND parititon from 2.4.18-swl5 */
static struct mtd_partition smdk_default_nand_part[] = { [0] = { .name = "ViVi", .size = 0x00020000, .offset = 0x00000000, }, [1] = { .name = "param", .offset = 0x00020000, .size = 0x00010000, }, [2] = { .name = "kernel", .offset = 0x00030000, .size = 0x00400000, }, [3] = { .name = "root", .offset = 0x00430000, .size = 0x00300000, }, [4] = { .name = "yaffs", .offset = 0x00730000, .size = 0x03800000, }, [5] = { .name = "ucos", .offset = 0x3f300000, .size = 0x000cc000, } #if 0 [6] = { .name = "S3C2410 flash partition 6", .offset = 0x3f300000, .size = SZ_1M * 24, }, [7] = { .name = "S3C2410 flash partition 7", .offset = SZ_1M * 48, .size = SZ_16M, } #endif }; |
默认情况下分了8个区,而我们需要规划其中的6个分区,因此将后面的部分用#if 0和#endif包含起来,暂时不使用。在设置每个分区的大小(.size)和偏移量(.offset)时要小心,不要导致分区重叠,并且内核和跟文件系统的偏移量设置与BootLoader中的设置保持一致,其原因上面已经分析过了。
2. 内核的配置的基本结构
2.1 Linux内核的配置系统由四个部分组成
Makefile:分布在Linux内核源码中的Makefile,定义Linux内核的编译规则;顶层Makefile是整个内核配置、编译的总体控制文件;
配置文件(config.in):给用户提供配置选择的功能;config:内核配置文件,包括由用户选择的配置选项,用来存放内核配置后的结果;
配置工具:包括对配置脚本中使用的配置命令进行解释的配置命令解释器和配置用户界面(基于字符界面:make config;基于Ncurses图形界面:make menuconfig;基于xWindows图形界面:make xconfig)
Rules.make:规则文件,被所有的Makefile使用。
2.2 编译规则Makefile
利用 make menuconfig(或make config、make xconfig)对Linux内核进行配置后,系统将产生配置文件(.config)。在编译时,顶层 Makefile 将读取 .config 中的配置选择。
顶层 Makefile完成产生核心文件(vmlinux)和内核模块(module)两个任务,为了达到此目的,顶层 Makefile 递归进入到内核的各个子目录中,分别调用位于这些子目录中的 Makefile,然后进行编译。至于到底进入哪些子目录,取决于内核的配置。顶层Makefile中的include arch/$(ARCH)/Makefile指定特定 CPU 体系结构下的 Makefile,这个Makefile包含了特定平台相关的信息。
各个子目录下的 Makefile 同样也根据 配置文件(.config)给出的配置信息,构造出当前配置下需要的源文件列表,并在文件最后有 include $(TOPDIR)/Rules.make。
顶层 Makefile 定义并向环境中输出了许多变量,为各个子目录下的 Makefile 传递一些变量信息。有些变量,比如 SUBDIRS,不仅在顶层 Makefile 中定义并且赋初值,而且在 arch/*/Makefile 还作了扩充。
2.3编译内核的常用命令
精简Linux内核常用命令包括:make config、make dep、make clean、make mrproper、make zImage、make bzImage、make modules、make modules_install,用于配置内核,编译内核和编译安装模块。其中make zImage是我们本次实验用到的编译内核的命令。
3.内核编译过程
内核的配置主要通过make menuconfig或者是make xconfig这样的图形化界面来完成。不过在某些情况下特别是嵌入式环境下还需要手工配置文件或是修改makefile。
3.1 修改makefile文件
我们需要编译用于ARM开发板的内核,使用的编译器是arm-linux-gcc。Makefile文件修改如下:
ARCH ?= $(SUBARCH) CROSS_COMPILE ?= |
修改为:
ARCH ?= arm CROSS_COMPILE ?= arm-linux- |
这里ARCH=arm说明目标是ARM体系结构,默认的ARCH是指宿主机的体系,如i386;CROSS_COMPILE=arm-linux-说明使用交叉编译器前缀为arm-linux-,默认情况下为空。
3.2 添加devfs配置
devfs指的是设备文件系统(DeviceFilesystem),其作用在于提供一个更为方便的方式来管理通常位于/dev目录的所有块设备和字符设备等。Linux可以支持很多不同种类的硬件,这意味着/dev目录中可能需要数百个特殊文件来表示所有这些设备,而且往往对一些目前不存在的设备,也保留这些设备文件以备将来可以用到,这样是不方便的。Devfs用于自动管理这些设备文件,所有需要的设备节点都由内核自动创建,而不需要的则不会出现在/dev目录下。
Devfs文件系统从2.3内核开始支持,但是在2.6内核中已被逐渐舍弃,我们使用的2.6.26版本的内核中已经没有了支持devfs的配置选项(代码级别上仍然支持,仅仅是去掉了配置选项),而在较早的2.6内核中则该设置被标注为obsolete(过时的)。然而对于嵌入式Linux来说,devfs可提供较强的灵活性和紧缩而准确的/dev目录,确实是一项比较方便的配置,因此我们采用手动的方式把它恢复过来。
Devfs配置在Filesystem配置菜单下的Pseudo filesystems中,对应的配置文件是fs/Kconfig,我们可以在此fs/Kconfig的基础上做如下修改:
895 menu "Pseudo filesystems" 896 897 config DEVFS_FS 898 bool "/dev file system support(obsolete)" 899 default y 900 901 config DEVFS_MOUNT 902 bool "Automatically mount at boot" 903 default y 904 depends on DEVFS_FS 905 906 config PROC_FS 907 bool "/proc file system support" if EMBEDDED 908 default y 909 help |
其中粗体部分(897~890行)是添加的内容,其作用是让内核在编译时加入devfs文件系统的支持,在后面的make menuconfig时可以看到,加入这些内容之后出现了与devfs相关的配置选项。注意这里将默认的设置设为了default y,即默认被选中。
3.3 make menuconfig 配置内核
这是一种显示文本菜单的配置方式,使用最广泛。如果.config存在,则使用.config文件的默认配置。
make menuconfig |
此后就可以看到内核配置界面,如图4.1所示。
图 4.1 内核配置界面
3.4 读取已有的配置文件
2.6.26版本的内核已经为S3C2410 CPU准备了默认的配置文件,位置在arch/arm/configs/s3c2410_defconfig。一般情况下很多选择默认即可,但是特别要注意的是要修改启动参数。
Boot Options--->Default kernel command string,修改如下:
noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0,115200 mem=64M |
进入配置菜单后,可以通过Load an Alternate ConfigurationFile选项来读取这个默认配置,如图4.2所示。
图4.2 读取配置文件
或者直接将其拷贝过来,命名为.config,这样make menuconfig就会读取该.config文件作为默认配置。
cp arch/arm/configs/s3c2410_defconfig .config |
3.5 选择具体的配置选项
内核移植关键的步骤就在于配置哪些选项是必须选择,哪些选项是不用选的。实际上在配置时,大部分选项可以使用其默认值,只有少部分需要根据用户不同的需要选择。选择的原则,是将与内核其它部分关系较远且不经常使用的部分功能代码编译成可加载模块,有利于减小内核的大小,减小内核消耗的内存,简化该功能相应的环境改变时对内核的影响。不需要的功能就不选,与内核关系紧密而且经常使用的部分功能代码直接编译到内核中。
make dep |
注:如果是2.4内核,这一步是需要的,而对于2.6内核则不需要
3.6 生成zImage内核镜像文件
配置好内核选项并清除残留文件之后,就来编译内核zImage:
make zImage |
完成之后会出现以下的字样:
Kernel: arch/arm/boot/Image is ready AS arch/arm/boot/compressed/head.o GZIP arch/arm/boot/compressed/piggy.gz AS arch/arm/boot/compressed/piggy.o CC arch/arm/boot/compressed/misc.o LD arch/arm/boot/compressed/vmlinux OBJCOPY arch/arm/boot/zImage Kernel: arch/arm/boot/zImage is ready |
这表明在arch/arm/boot/目录下生成了Image和zImage两个内核映像文件,其中Image为正常大小的映像文件,而zImage为压缩后的映像文件。
注:在编译用于i386的2.6内核时,需要使用make bzImage命令而不是make zImage命令来编译内核映像,这是由于i386体系的CPU有保护寻址和实模式寻址的限制,内核启动时需要作一些调整。对于ARM体系的CPU则没有这样的限制,仍然可以采用make zImage命令来编译内核映像。
注意: 1.make dep应用在内核2.4或之前,在2.6内核中已取消该命令。 2.make clean删除前面留下的中间文件,该命令不会删除.config等配置文件。 3.make zImage 编译生成gzip压缩形成的image文件。 4.make bzImage编译生成较大一点的内核文件。 5.生成的zImage文件在arch/arm/boot/ 目录中。 |
3.7 编译和安装模块
在前面配置时选择了内核支持可加载模块,并且选择了一些需要的模块作为动态加载,这里来编译它们,并且在制作根文件系统时需要将它们复制到根文件系统中。
把在make menuconfig里面说选择为M的选项全部编译成模块,其命令格式如下:
make modules |
模块编译好以后,需要执行安装。默认情况下,模块会被安装到/lib/modules,这里是使用交叉编译器和目标板,所以需要设定目标路径,一般可通过INSTALL_MOD_PATH=$TARGETDIR来指定安装路径,其中TARGETDIR就是指定的安装路径。例如:
make modules_install INSTALL_MOD_PATH=./ |
这里指定安装路径是当前目录,在制作根文件系统时需要从这里复制模块文件到根文件系统的目录中。
4.下载内核到开发板
编译完Linux内核后,就可以将其下载到开发板上运行,在下载内核之前,需要开发板上已经有了BootLoader。在这次实验时已经将ViVi移植到了开发板上。本次实验我们只是测试内核,而不将其写入Flash中,在BootLoader的命令行输入一下命令:
load ram 0x30008000 x boot ram |
注:上述两行语句中的ttySAC0、0x00440000部分,跟第一部分内核移植是不同的。这里测试没有使用ramdisk文件系统,原因是前面移植的文件系统不能在这个内核下使用,需要移植更高版本的busybox才能使用。
或者使用xmodem协议将内核下载到flash中,其命令如下:
load flash kernel x |
然后在超级终端中选择,“发送->发送文件“,选择需要发送的内核即可,等待数分钟。
此时需要注意的是,需要将vivi传送给linux内核的参数设置正确,否则即使能够加载内核到内存,也不能顺利启动内核,在vivi中设置:
param set linux_cmd_line “noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0,115200” |
然后执行,
boot |
出现如下信息,就表明内核能够运行在开发板上了。
Uncompressing Linux.............................................................rder: 1, 8192 bytes)
.......................................... done, booting the kernel. Page-cache has Linux version 2.6.26 (chenglian@chenglian-desktop) (gcc version 3.4.1) #1 Fri Ma 。。。 Kernel command line: noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0,IC on S3C2410X, 281kHz clock 。。。 |
在vivi中设定的启动参数在这里起到了作用——“console=ttySAC0 “的配置使得内核启动时将信息打印在串口设备上。在启动过程中还可以看到Flash分区的信息,可以看到这和前面说修改的分区是一致的。
NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bit 0x00020000-0x00030000 : "param"tucos [{cmds}] 0x00030000-0x00430000 : "kernel" sy 0x00430000-0x00730000 : "root" lib mnt root t 0x00730000-0x03f30000 : "yaffs" -- Help about 0x03f300000-0x3f3cc000: "ucos" |
在本次实验中主要是内核的移植,还没有根文件系统,内核启动完毕之后去寻找根文件系统时会出现Kernel panic,即内核崩溃:
List of all partitions: 1f00 128 mtdblock0 (driver?) 1f01 64 mtdblock1 (driver?) 1f02 4096 mtdblock2 (driver?) 1f03 3072 mtdblock3 (driver?) 1f04 57344 mtdblock4 (driver?) No filesystem could mount root, tried: ext3 ext2 cramfs msdos vfat romfs Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(31,2) |
这是在终端设备上说看到的最后的信息,说明内核到这里就停止了——在启动参数中指定的/dev/mtdblock3分区上找不到可用的文件系统,也没有linuxrc。若果加上文件系统和启动脚本就可以正常运行。
5.小结
本实验主要做了如何移植Linux内核,使它可以工作在开发板上。移植过程中比较复杂的地方在于如何给Flash分区,如何根据自己的需要划分。配置和编译内核部分细节比较的多也比较繁琐,需要仔细操作。下载内核则更是一波三折,如xmodem选择等方面。总之,经过这次实验,我对内核移植的步骤更加清晰。