1. U-boot与linux的关系是什么给个详细点的说明
uboot 是 Universal BootLoad 。一个就算是“通用”的启动代码载入器。
Linux 本身不能自己把自己读取到内存中并且运行,所以他需要一个 loader (载入器)读入内存并且运行。
uboot 类似台式机的 BIOS + grub 启动 Linux 的组合。
嵌入式系统因为构造很特殊,所以他的系统启动一般都是要在 nor flash (不是我们常见的 NAND flash 存储器,NOR flash 可以直接运行程序,NAND 不能直接运行程序,需要读取到内存运行,和 BIOS 很类似,nor flash 成本很高)。这个前期启动需要做一些初始化工作,以及因为环境限制,程序运行有很大的功能限制。这使得原本就不支持自己启动的 Linux 内核更需要一个 loader 来提供前期的准备,这就是 bootloader 的主要用处。
uboot 就是针对某个嵌入式环境特别编译特别准备的一级引导程序。用来初始化环境,并且读取启动 Linux 内核的东西。
当然一个在 Linux 内核启动之前就启动的 bootloader 不光有这些功能。不过主要的用处就是启动它后面的系统,不光是 Linux ,WinCE 也需要这么一个东西的。
2. 怎样移植u-boot和linux到s3c2440开发板
uboot最主要的功能就是能够引导内核启动。本文就介绍如何实现该功能,并组成一个最简单的系统,这不仅要移植uboot,还要移植linux内核及创建一个根文件系统。
首先我们对nandflash进行分区,规划好每个文件存放在nandflash的位置。下面是nandflash的分区:
第0分区:0x000000000000-0x000000080000为uboot区
第1分区:0x000000080000-0x000000100000为参数区
第2分区:0x000000200000-0x000000600000为linux内核区
第3分区:0x000000800000-0x000001000000为根文件系统区
规划好分区后,我们就可以依次完成uboot的移植,linux内核的移植,及创建一个根文件系统。我们选择cramfs作为根文件系统。
一、uboot移植
1.修改机器码,要保证uboot与linux内核的机器码一致,这样才能启动内核。
修改board/samsung/zhaocj2440/zhaocj2440.c文件中的第116行内容,把SMDK2410改为SMDK2440,即:
gd->bd->bi_arch_number = MACH_TYPE_SMDK2440;
因为我们的uboot移植是以uboot自带的SMDK2440开发板为模板的,所以我们还是按照SMDK2440的机器码来移
植,MACH_TYPE_SMDK2440的具体数值在arch/arm/include/asm/mach-types.h文件的第1013行已有定
义:
#define MACH_TYPE_SMDK2440 1008
2.添加bootcmd和bootargs参数。其中bootcmd是为了引导内核,而bootargs是为了在加载根文件系统时,给根文件系统传递必要的参数。
可以有两种方法来设置这两个参数:
第一种方法是在uboot的提示符下直接设置bootcmd和bootargs这两个参数:
ZHAOCJ2440 # setenv bootcmd ' nand read 31000000 200000 400000; bootm 31000000 '
ZHAOCJ2440 # setenv bootargs ' root=/dev/mtdblock3 ro noinitrd
init=/linuxrc console=ttySAC , 115200 rootfstype=cramfs mem=64M'
ZHAOCJ2440 # saveenv
在这里bootcmd的含义是从nandflash中读取kernel,然后利用命令bootm启动。bootargs的含义是在
nandflash中的第3个分区内存放着根文件系统,它的格式是cramfs。最后还要应用saveenv命令来保存这两个变量。这时,如果你在提示符
下敲入printenv命令,则会看到uboot的环境参数多了两项,如:
bootargs=root=/dev/mtdblock3 ro noinitrd init=/linuxrc console=ttySAC,115200 rootfstype=cramfs mem=64M
bootcmd=nand read 31000000 200000 400000 ; bootm 31000000
第二种方法是在include/configs/zhaocj2440.h内定义CONFIG_BOOTARGS和CONFIG_BOOTCOMMAND这两个宏定义:
#define CONFIG_BOOTARGS " root=/dev/mtdblock3 ro noinitrd init=/linuxrc console=ttySAC , 115200 rootfstype=cramfs mem=64M"
#define CONFIG_BOOTCOMMAND " nand read 31000000 200000 400000 ; bootm 31000000"
3.把移植好的uboot烧写到nandflash中的0x00000000至0x000000080000内。
二、linux内核移植
这里我们实现的是最简单的移植,即能够启动即可。
1.在下列网址下载linux内核,linux-3.4.6.tar.bz2
www.kernel.org/pub/linux/kernel/v3.x/
解压到当前目录:
tar -xvjf linux-3.4.6.tar.bz2
2.修改主目录下的Makefile文件,第195行和第196行改写为:
ARCH ?=arm
CROSS_COMPILE ?= arm-linux-
3.添加机器码,使uboot与linux机器码一致,并改变内核时钟
在arch/arm/tools/mach-types文件的第207行添加下列代码:
smdk2440 MACH_SMDK2440 SMDK2440 1008
在arch/arm/mach-s3c24xx/mach-smdk2440.c文件内
第165行中的16934400改为12000000,即
s3c24xx_init_clocks(12000000);
第178行中的S3C2440改为SMDK2440,即
MACHINE_START(SMDK2440,"SMDK2440")
4.修改内核中的分区,使其与我们事先定义的分区一致
在arch/arm/mach-s3c24xx/common-smdk.c文件内
第111行中的smdk_default_nand_part结构体改为:
static struct mtd_partition smdk_default_nand_part[ ] = {
[0]= {
.name = "UBoot",
.size = SZ_512K,
.offset = 0,
},
[1]= {
.name = "Para",
.offset= SZ_512K,
.size = SZ_512K,
},
[2]= {
.name = "Kernel",
.offset= SZ_2M,
.size = SZ_4M,
},
[3]= {
.name = "rootfs",
.offset = SZ_8M,
.size = SZ_8M,
}
};
5.改变内核的ECC类型
在drivers/mtd/nand/s3c2410.c文件内
第846行中的NAND_ECC_SOFT改为NAND_ECC_NONE,即:
chip->ecc.mode = NAND_ECC_NONE;
此处如果不改,虽然能够启动linux内核,但无法加载根文件系统。
6.编译内核
退回到linux-3.4.6的根目录下,复制配置文件:
cp arch/arm/configs/s3c2410_defconfig .config
使用menuconfig来配置内核:
make menuconfig
在KernelFeatures下选上两项内容,即
Kernel Features --->
[*]Use the ARM EABI to compile the kernel
[*]Allow old ABI binaries to run with this kernel (EXPERIMENTAL)
如果不选择这两项,则在内核启动完,挂载根文件系统时会出现kernel panic:attempted to kill init的错误。
menuconfig的其他内容可以不需要改变,选择默认即可。
最后执行下面两个命令:
make clean
make zImage
等待一段时间后,在arch/arm/boot/目录下会生成zImage文件。
7.制作内核镜像
在上一步虽然我们已经生成了zImage文件,但它还不能被uboot正确引导,我们还需要给zImage文件加上64个字节的数据头,这部分内容
包括CPU架构(A)、操作系统(O)、镜像类型(T)、压缩类型(C)、镜像名称(n)、镜像加载地址(a)、镜像入口(e)、源文件(d)。只有加上
这些内容uboot才能正确引导内核。
mkimage工具就是uboot用来制作完成上述内容的工具。编译过uboot后,会在tools目录下生成mkimage。为了更方便地应用该工具,我们需要完成下列操作,进入tools目录,以根用户的身份执行下列命令:
cp mkimage /usr/bin
chmod 777 /usr/bin/mkimage
进入linux-3.4.6目录下的arch/arm/boot/目录,执行下列命令:
mkimage -n 'linux' -A arm -O linux -T kernel -C none -a 0x31000000 -e 0x31000040 -d zImage uImage.img
uImage.img为最终我们需要烧写到nandflash中的文件。在这里,我们是把镜像加载到内存0x31000000地址内的。
8.最后,我们把uImage.img文件烧写到nandflash中的0x200000至0x600000中。
3. ubootlinux
缂栬瘧uboot鏃跺嚭鐜/bin/sh:1:/opt/buildroot-gcc342/bin/mipsel-linux-as:notfound锛
杩欎釜閿欒搴旇ユ槸鍛婅瘔浣狅紝浣犵殑缂栬瘧鍣ㄦ病鎵惧埌銆備綘鍘荤‘璁や笅杩欎釜鐩褰曞瓨涓嶅瓨鍦锛
/opt/buildroot-gcc342/bin/mipsel-linux-as
濡傛灉涓嶅瓨鍦锛岄偅鑲瀹氭槸缂栬瘧鍣ㄦ病瀹夎呫
濡傛灉瀛樺湪锛屾垜鐪嬬綉涓婃湁浜哄湪璇达細鏄鍥犱负绯荤粺鏄64浣嶇殑锛岃岀紪璇戝櫒鏄32浣嶇殑锛屼笉鍏煎广備綘鎹涓32浣嶇郴缁熻瘯璇曘
linuxbootbiosboot鍖哄埆锛
boot鏄涓閫氱敤璇嶈锛岃〃绀哄惎鍔ㄧ瓑鐩稿叧杩囩▼銆倁boot鏄涓涓撴湁鍚嶈瘝锛岃〃绀虹殑鏄涓涓杞浠跺悕鍙玼boot銆
uboot寮鍙戞湁鍓嶆櫙鍚楋紵
uboot寮鍙戞湁鍓嶆櫙鐨勩
uboot鐨勬牳蹇冮儴鍒嗗嚑涔庢病鎬庝箞鍙樺寲,瓒婃柊鐨勭増鏈鏀鎸佺殑寮鍙戞澘瓒婂氳屽凡,瀵逛簬涓涓鑰佺増鏈鐨勮姱鐗囨潵璇,鏂版棫鐗堟湰鐨剈boot骞舵病鏈夊樊寮傘
uboot瀹樼綉锛氭槸婧愬ご锛屼絾鏄鑲瀹氭病鏈夋垜浠瀵瑰簲鐨勫紑鍙戞澘鐨剈boot銆
涓嶅皯U-Boot婧愮爜灏辨槸鐩稿簲鐨凩inux鍐呮牳婧愮▼搴忕殑绠鍖栵紝灏ゅ叾鏄涓浜涜惧囩殑椹卞姩绋嬪簭锛岃繖浠嶶-Boot婧愮爜鐨勬敞閲婁腑鑳戒綋鐜拌繖涓鐐广
uboot鍚鍔ㄨ︾粏璁茶В锛
uboot鏄閬靛惊GPL鏉℃剧殑寮鏀炬簮鐮侀」鐩銆
uboot鐨勪綔鐢ㄦ槸绯荤粺寮曞笺
uboot浠嶧ADSROM銆8xxROM銆丳PCBOOT閫愭ュ彂灞曟紨鍖栬屾潵銆
鍏舵簮鐮佺洰褰曘佺紪璇戝舰寮忎笌Linux鍐呮牳寰堢浉浼硷紝浜嬪疄涓婏紝涓嶅皯uboot婧愮爜灏辨槸鏍规嵁鐩稿簲鐨凩inux鍐呮牳婧愮▼搴忚繘琛岀畝鍖栬屽舰鎴愮殑锛屽挨鍏舵槸涓浜涜惧囩殑椹卞姩绋嬪簭锛岃繖浠巙boot婧愮爜鐨勬敞閲婁腑鑳戒綋鐜拌繖涓鐐广
uboot涓嶄粎浠呮敮鎸佸祵鍏ュ紡Linux绯荤粺鐨勫紩瀵硷紝瀹冭繕鏀鎸丯etBSD,VxWorks,QNX,RTEMS,ARTOS,LynxOS,android宓屽叆寮忔搷浣滅郴缁熴
鍏剁洰鍓嶈佹敮鎸佺殑鐩鏍囨搷浣滅郴缁熸槸OpenBSD,NetBSD,FreeBSD,4.4BSD,Linux,SVR4,Esix,Solaris,Irix,SCO,Dell,NCR,VxWorks,LynxOS,pSOS,QNX,RTEMS,ARTOS,android銆
杩欐槸uboot涓璘niversal鐨勪竴灞傚惈涔夛紝鍙﹀栦竴灞傚惈涔夊垯鏄痷boot闄や簡鏀鎸丳owerPC绯诲垪鐨勫勭悊鍣ㄥ栵紝杩樿兘鏀鎸丮IPS銆亁86銆丄RM銆丯IOS銆乆Scale绛夎稿氬父鐢ㄧ郴鍒楃殑澶勭悊鍣ㄣ
杩欎袱涓鐗圭偣姝f槸uboot椤圭洰鐨勫紑鍙戠洰鏍囷紝鍗虫敮鎸佸敖鍙鑳藉氱殑宓屽叆寮忓勭悊鍣ㄥ拰宓屽叆寮忔搷浣滅郴缁熴
灏辩洰鍓嶆潵鐪嬶紝uboot瀵筆owerPC绯诲垪澶勭悊鍣ㄦ敮鎸佹渶涓轰赴瀵岋紝瀵筁inux鐨勬敮鎸佹渶瀹屽杽銆
鍏跺畠绯诲垪鐨勫勭悊鍣ㄥ拰鎿嶄綔绯荤粺鍩烘湰鏄鍦2002骞11鏈圥PCBOOT鏀瑰悕涓簎boot鍚庨愭ユ墿鍏呯殑銆
浠嶱PCBOOT鍚憉boot鐨勯『鍒╄繃娓★紝寰堝ぇ绋嬪害涓婂綊鍔熶簬uboot鐨勭淮鎶や汉寰峰浗DENX杞浠跺伐绋嬩腑蹇僕olfgangDenk鏈浜虹簿婀涗笓涓氭按骞冲拰鎵х潃涓嶆噲鐨勫姫鍔涖
褰撳墠锛寀boot椤圭洰姝e湪浠栫殑棰嗗啗涔嬩笅锛屼紬澶氭湁蹇椾簬寮鏀炬簮鐮丅OOTLOADER绉绘嶅伐浣滅殑宓屽叆寮忓紑鍙戜汉鍛樻e傜伀濡傝嵓鍦板皢鍚勪釜涓嶅悓绯诲垪宓屽叆寮忓勭悊鍣ㄧ殑绉绘嶅伐浣滀笉鏂灞曞紑鍜屾繁鍏ワ紝浠ユ敮鎸佹洿澶氱殑宓屽叆寮忔搷浣滅郴缁熺殑瑁呰浇涓庡紩瀵笺
51鍗曠墖鏈哄彲浠ユ湁鍑犱釜绋嬪簭锛
鍙浠ユ槸锛氣滀竴涓鍗曠墖鏈哄埢鎰忕儳涓涓绋嬪簭锛屸濓紝杩欑嶆槸鏈鍏稿瀷鐨勫簲鐢锛屾瘮濡51鍗曠墖鏈猴紝涓鑸灏辨槸寮鍙戝畬绋嬪簭鐢熸垚涓涓狧EX锛屼竴涓狧EX瀵瑰簲鐑у啓鍏ヤ竴涓狹CU鍗曠墖鏈恒
涔熷彲浠ユ槸鈥滀竴涓鍗曠墖鏈哄埢鎰忕儳鍐欎换鎰忓氫釜绋嬪簭锛岀洿鍒颁綘鐨凢LASH/ROM琚浣犲啓婊♀濓紝涓涓鍏稿瀷鐨勪緥瀛愭槸涓涓狪AP绋嬪簭+涓涓狝PP绋嬪簭鐨勫簲鐢ㄥ満鏅锛屽綋鐒舵牴鎹姝ゅ彲浠ユ敼缂栨垚涓涓狪AP+N涓猘pp鐨勫満鏅銆傚彟涓涓鍏稿瀷鐨勪緥瀛愭槸楂樼鍗曠墖鏈虹Щ妞嶇被浼紆boot+linux鐨勬搷浣滀娇鐢锛宭inux鍐呭彲浠ュ紑鍙戝緢澶欰PP绋嬪簭銆
4. uboot如何启动内核
1.uboot启动内核的代码缩减如下:
Uboot 1.16/lib_arm/board.c中start_armboot()函数调用/common/main.c中main_loop()函数,在main_loop()中有uboot启动内核的代码:
s = getenv ("bootcmd");
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s :"<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay))
{
run_command(s, 0);
}
2.假设bootcmd = nandread.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
<1> nandread.jffs2 0x30007FC0 kernel
从nand读出内核:
从哪里读? :kernel分区
读到哪里去?:0x30007FC0
何为分区?
简单的说就是将nand划分为几个区域,一般如下:
bootloader->params->kernel->root
这些分区划分穗余在/include/configs/100ask24x0.h中写死的:
#define MTDPARTS_DEFAULT"mtdparts=nandflash0:256k@0(bootloader)," \
"128k(params)," \
"2m(kernel)," \
"-(root)"
进入uboot执行mtd ,可以查看已有分区:
# name 大小 在nand上的猜裤滚起始地址
0 bootloader 0x00040000 0x00000000
1 params 0x00020000 0x00040000
2 kernel 0x00200000 0x00060000
3 root 0xfda00000 0x00260000
上面的nand read.jffs2 0x30007FC0 kernel等价于:
nand read.jffs20x30007FC0 0x00060000 0x00200000
注:read.jffs2并不是指定特定的格式,仅表示不需要块/页对齐,所以kernel的分区大小可以随意定。纯岩
<2> bootm0x30007FC0
关键函数do_bootm()
flash上存的内核:uImage
uImage =头部+真正的内核
头部的定义如下:
typedef struct image_header {
uint32_t ih_magic;
uint32_t ih_hcrc;
uint32_t ih_time;
uint32_t ih_size;
uint32_t ih_load;
uint32_t ih_ep;
uint32_t ih_dcrc;
uint8_t ih_os;
uint8_t ih_arch;
uint8_t ih_type;
uint8_t ih_comp;
uint8_t ih_name[IH_NMLEN];
} image_header_t;
我们需要关心:
uint32_t ih_load;
uint32_t ih_ep;
ih_load是加载地址,即内核运行是应该位于的地方
ih_ep是入口地址,即内核的入口地址
这与uboot类似,uboot的加载地址是TEXT_BASE = 0x33F80000;入口地址是start.S中的_start。
从nand读出来的内核可以放在ram中的任意地方,如0x31000000,0x32000000等等,只要它不破坏uboot所占用的内存空间就可以
既然设定好了加载地址和入口地址,为什么内核还能随意放?
因为uImage有一个头部!头部里有加载地址和入口地址,当我们用bootm xxx时,
do_bootm先去读uImage的头部以获取该uImage的加载地址和入口地址,当发现该uImage目前所处的内存地址不等于它的加载地址时,会将uImage移动到它的加载地址上,代码中体现如下:
uboot 1.16/common/cmd_bootm.c中的bootm_load_os()函数
case IH_COMP_NONE::
if (load != image_start)
{
memmove_wd((void *)load, (void *)image_start, image_len, CHUNKSZ);
}
另外,当内核正好处于头部指定的加载地址,便不用uboot的do_bootm函数来帮我们搬运内核了,可以缩短启动时间。这就是为什么我们一般都下载uImage到0x30007FC0的原因。
内核加载地址是0x30008000,而头部的大小64个字节,将内核拷贝到0x30007FC0,加上头部的64个字节,内核正好位于0x30008000处。
总结bootm做了什么:
1.读取头部
2.将内核移动到加载地址
3.启动内核
具体如何启动内核?
使用在/lib_arm/bootm.c定义的do_bootm_linux(),我们已经知道入口地址,只需跳到入口地址就可以启动linux内核了,在这之前需要做一件事———— uboot传递参数(启动参数)给内核。
启动代码在do_bootm_linux()函数:
void (*theKernel)(int zero, int arch,uint params); //定义函数指针theKernel
theKernel = (void (*)(int, int, uint))images->ep; //先是将入口地址赋值给theKernel
theKernel (0, bd->bi_arch_number, bd->bi_boot_params); //然后是调用thekernel,以0,bd->bi_arch_number,bd->bi_boot_params为参数
下面分析这三个参数:
1. 0—相当于mov,ro #0
2.bd->bi_arch_number:uboot机器码,这个在/board/100ask24x0.c设置:gd->bd->bi_arch_number = MACH_TYPE_S3C2440,MACH_TYPE_S3C2440在/arch/arm/asm/mach-types.h定义:362,内核机器码和uboot机器码必须一致才能启动内核
2. bd->bi_boot_parmas--- 启动参数地址
也是在在/board/100ask24x0.c设置:gd->bd->bi_boot_params = 0x30000100;
启动参数(tag)在哪里设置?
在lib_arm/armlinux.c设置:
setup_start_tag (bd);
setup_revision_tag (parmas);
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_initrd_tag (bd, images->rd_start, images->rd_end);
setup_videolfb_tag ((gd_t *) gd);
setup_end_tag (bd);
每一个启动参数对应一个tag结构体,所谓的设置传递参数其实就是初始化这些tag的值,想了解这个结构体以及这些tag的值是如何设置的请看嵌入式Linux应用开发完全手册的uboot章节
我们来看setup_start_tag(bd)函数:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
再看setup_commandline_tag (bd , commandline):
static void setup_commandline_tag (bd_t *bd, char*commandline)
{
// commandline就是我们的bootargs
char *p;
if (!commandline)
return;
for (p = commandline; *p == ' '; p++);
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof(struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
}
内核启动时会读取这些tag(参数)并跳转启动。
更多uboot启动内核的细节观看毕业班视频自己写uboot。