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。