七,移植linux-3.19内核
文档时间:2018-08-18
交叉编译器:arm-linux-gcc-4.3.2
Ubuntu版本:16.04
kernel版本:linux-3.19
1,分析 uboot 如何启动内核
通过之前对环境变量保存的分析可知,uboot是通过 bootcmd 来启动内核的,在 include/configs/jz2440.h 中我们有定义:
#define CONFIG_BOOTCOMMAND "nand read 0x30000000 kernel; bootm 0x30000000" //bootcmd
有以下图片可知:执行 bootcmd 时,会将内核代码从nand 读到内存30000000 处,然后开始执行。
由于要执行 bootm 命令,所以我们需要打开与 bootm 命令相关的文件进行分析,从名字可知,需要打开 cmd_bootm.c (位于 common 目录下)文件,找到对应的 do_bootm 函数:
(PS:一般与 xxx 命令相关的文件都在 common 目录下,名为cmd_xxx.c)
如上图,可以看出,do_bootm 会执行 boot_os 里的所有函数,进入到 boot_os 结构体中:
发现,虽然函数很多,但是与启动内核有关的只有 do_bootm_linux 函数,进入到 do_bootm_linux 函数 (位于 arch/arm/lib/bootm.c 文件中):
发现 do_bootm_linux 函数最终会 跳转执行 boot_prep_linux 和 boot_jump_linux 函数,首先分析 boot_prep_linux 函数(位于 bootm.c 文件中):
static void boot_prep_linux(bootm_headers_t *images) { char *commandline = getenv("bootargs"); //从环境变量中获取 bootargs 的值 。。。。。。。 setup_board_tags(¶ms); setup_end_tag(gd->bd); //将 tag 参数保存在指定位置 } else { printf("FDT and ATAGS support not compiled in - hanging\n"); hang(); } do_nonsec_virt_switch(); }
从代码可以看出来,boot_prep_linux,主要功能是将 tag 参数保存到指定位置,比如 bootargs 环境变量 tag,串口 tag,接下来分析 boot_jump_linux 函数(位于 bootm.c 文件中):
static void boot_jump_linux(bootm_headers_t *images, int flag) { unsigned long machid = gd->bd->bi_arch_number; //获取机器id (在 board/samsung/jz2440/jz2440.c 中设置,为 MACH_TYPE_SMDK2410(193)) char *s; void (*kernel_entry)(int zero, int arch, uint params); unsigned long r2; int fake = (flag & BOOTM_STATE_OS_FAKE_GO); kernel_entry = (void (*)(int, int, uint))images->ep; //获取 kernel的入口地址,此处应为 30000000 s = getenv("machid"); //从环境变量里获取机器id (本例中还未在环境变量里设置过机器 id) if (s) { //判断环境变量里是否设置机器id strict_strtoul(s, 16, &machid); //如果设置则用环境变量里的机器id printf("Using machid 0x%lx from environment\n", machid); } debug("## Transferring control to Linux (at address %08lx)" \ "...\n", (ulong) kernel_entry); bootstage_mark(BOOTSTAGE_ID_RUN_OS); announce_and_cleanup(fake); if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) r2 = (unsigned long)images->ft_addr; else r2 = gd->bd->bi_boot_params; //获取 tag参数地址,gd->bd->bi_boot_params在 setup_start_tag 函数里设置
if (!fake) kernel_entry(0, machid, r2); } //进入内核
通过分析可以看出,最终进入内核的函数为 :
kernel_entry(0, machid, r2)
该函数向内核传递了三个参数,分别为:
(1),0 (2),机器id (3),参数列表地址,即 tag 地址
到此 uboot 的任务就结束了,接下来内核代码便开始执行
2,移植 kernel-3.19
1),下载 3.19内核源码
下载地址为:https://mirrors.edge.kernel.org/pub/linux/kernel/,然后拷贝到 Ubuntu 中(可以用FileZilla Client工具拷贝),输入以下命令进行解压:
tar -zxvf linux-3.19.tar.gz
2),Linux 内核目录结构
目录名 | 描述 |
arch | 体系结构相关的代码,对于么个架构的CPU,arch 目录下都有一个对应的子目录,如 arch/arm/、arch/x86等 |
block | 块设备相关的通用函数 |
crypto | 常用加密和散列算法(如 AES、SHA等),还有一些压缩和CRC校验算法 |
drivers |
所有的设备驱动程序,里面每一个子目录对应一类驱动程序,比如 drivers/block/ 为块设备驱动程序,drivers/char/为字符设备驱动程序, drivers/mtd/为nor flash、nand flash 等存储设备的驱动程序 |
firmware | 设备相关的固件程序 |
fs | Linux 支持的文件系统的代码,每个子目录对应一种文件系统,比如 fs/jffs2/、fs/ext2/、fs/ext4/等 |
include |
内核头文件,有基本头文件(存放在 include/linux/目录下)、各种驱动或功能部件的头文件(如 include/media/、include/video/、include/net等)、 各种体系相关的头文件(如 include/asm-generic/等) |
init | 内核的初始化代码(不是系统的引导代码),其中的 main.c 文件中的 start_kernel 函数是内核引导后运行的第一个函数 |
ipc | 进程间通信的代码 |
kernel | 内核管理的核心代码 |
lib | 内核用到的一些库函数代码,如 crc32.c、string.c、shal.c等 |
mm | 内存管理代码 |
net | 网络支持代码,每个子目录对应子网络的一个方面 |
samples | 一些示例程序,如断点调试,功能测试等 |
scripts | 用于配置、编译内核的脚本文件 |
security | 安全、密钥相关的代码 |
sound | 音频设备驱动程序 |
tools | 工具类代码,比如 USB 传输等,通常会将 u-boot 下生成的mkimage工具放到此目录下,同事修改Linux的Makefile支持生成uImage |
usr | 一般不会用到 |
virt | 一般不会用到 |
Documentation | Linux内核的使用帮助文档 |
3),配置,编译内核
修改顶层的 Makefile,打开 Makefile 文件,找到下面语句:
ARCH ?= $(SUBARCH) CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
修改为:
ARCH ?= arm CROSS_COMPILE ?= arm-linux-
然后选择默认配置,单板的默认配置文件在 arch/arm/configs 目录下,由于没有2440相关的默认配置,所以我们选择比较相近的 2410 的配置,选择s3c2410 和mini2410 都可以,这里我们选择 s3c2410_defconfig,在linux-3.19 目录下输入以下命令编译内核:
make s3c2410_defconfig
make uImage
出现如下错误:
说明缺少 mkimage ,有两种解决办法:(1),利用uboot生成mkimage工具,然后拷贝到/usr/bin 目录下 (2),输入 sudo apt-get install u-boot-tools 命令在线安装,本文选择第二种方法,输入命令:
sudo apt-get install u-boot-tools
然后 make uImage 编译内核,编译成功,在 arch/arm/boot 目录下生成 uImage,将生成的 uImage 烧写到板子上,然后启动内核:
tftp 30000000 uImage bootm 30000000
发现程序卡死在 booting the kernel,查找问题,在 Ubuntu 中输入命令 make menuconfig 进入可视化配置界面,发现出现如下错误:
缺少 curses.h 文件,在网上查找原因发现是因为缺少一个套件 ncurses devel,把此套件安装下来即可,输入以下命令进行安装:
apt-get install libncurses5-dev
然后就可以使用make menuconfig 进入可视化配置界面,发现在可视化配置界面中串口选项已经配置,继续查找问题,发现可能是机器id 不匹配,在 uboot 下输入 print machid,显示如下错误:
说明环境变量中未定义机器 id,所以启动失败(因为在内核自解压完成以后内核会首先会进入 bl __lookup_machine_type函数(在arch/arm/kernel/head.S中),检查machine_type是否匹配,如果不匹配会跳入__error_a函数(在arch/arm/kernel/head-common.S中),导致启动失败),我们先随便设置一个 id,如下所示:
set machid 123456
然后再启动内核,打印如下:
可以发现我们随便设置的机器id并没有被支持,内核打印出来所支持的机器id,与2440有关的有:MINI2440(7CF),SMDK2440(16A),我们先把机器id设置为 0x7cf:
set machid 0x7cf
save
然后启动内核,打印如下:
打印乱码,怀疑可能是波特率不对,在之前设置的 bootargs 参数里加上波特率:
set bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 save
然后下载启动内核,发现可以正常启动,说明内核支持MINI2440机器id 可用,接下来把机器id修改为 0x16a:
set machid 0x16a save
然后下载启动内核,打印如下:
打印乱码,由于之前我们已经设置过波特率,所以出现乱码可能是时钟设置不对,打开 mach-smdk2440.c (位于arch/arm/mach-s3c24xx目录下)文件,定位到如下位置:
可见设置时钟时用的是16934400,而我们板子用的晶振是 12M,所以修改代码如下(红色部分为修改代码):
static void __init smdk2440_init_time(void) { s3c2440_init_clocks(12000000); samsung_timer_init(); }
在Ubuntu 下输入 make uImage 重新编译,然后烧写测试,打印信息如下:
说明内核启动正常,但是未能成功挂载文件系统,原因是我们在uboot中设置了4个分区,把文件系统放在了 block3上,而如上图内核设置了8个分区,因此不可能挂载成功
3),修改内核分区
搜索 S3C2410 flash partition 1 字段,找到:
打开 arch/arm/mach-s3c24xx/common-smdk.c 文件,并定位到119行,仿照uboot的分区,修改代码如下:
static struct mtd_partition smdk_default_nand_part[] = { //changed by zhyy [0] = { .name = "u-boot", .size = SZ_512K, .offset = 0, }, [1] = { .name = "params", .offset = MTDPART_OFS_APPEND, .size = SZ_128K, }, [2] = { .name = "kernel", .offset = MTDPART_OFS_APPEND, .size = SZ_4M, }, [3] = { .name = "rootfs", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, }, };
上面部分宏的定义位于 include/linux/mtd/partitions.h 文件中,如下所示:
#define MTDPART_OFS_RETAIN (-3) #define MTDPART_OFS_NXTBLK (-2) #define MTDPART_OFS_APPEND (-1) #define MTDPART_SIZ_FULL (0)
如果设置的是MINI2440的机器id,则需要将mach-mini2440.c 文件中的分区改成与上面一样,代码如下所示:
static struct mtd_partition mini2440_default_nand_part[] __initdata = { [0] = { .name = "u-boot", .size = SZ_512K, .offset = 0, }, [1] = { .name = "params", .offset = MTDPART_OFS_APPEND, .size = SZ_128K, }, [2] = { .name = "kernel", .offset = MTDPART_OFS_APPEND, .size = SZ_4M, }, [3] = { .name = "rootfs", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, }, };
修改完之后,编译内核,烧写新内核,烧写板子自带的 .jffs2 和 .yaffs2文件系统,看看能否挂接成功,命令如下:
烧写新内核:
tftp 30000000 uImage nand erase.part kernel nand write 30000000 kernel
烧写.jffs2 文件系统:
tftp 30000000 fs_mini_mdev.jffs2 nand erase.part rootfs nand write.jffs2 30000000 4a0000 $filesize
运行.jffs2 文件系统要重新设置 bootargs:
set bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 rootfstype=jffs2
烧写.yaffs2文件系统:
tftp 30000000 fs_mini_mdev.yaffs2 nand erase.part rootfs nand write.yaffs 30000000 4a0000 $filesize
set bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 rootfstype=yaffs2
运行结果是,可以正常运行.jffs2 文件系统,不能运行 .yaffs2 文件系统,原因是当前内核不支持yaffs文件系统,下一节移植yaffs2 文件系统
如果在运行.jffs2 文件系统是出现以下错误:
exitcode=0x00000004
则需要执行 make menuconfig,在kernel feature中增加以下选项: