ARM-Linux內核移植之(一)——內核啓動流程分析
轉載請註明來自於衡陽師範學院08電2 K-Style http://blog.csdn.net/ayangke,QQ:843308498 郵箱:[email protected]
內核版本:2.6.22 爲什麼要採用這樣一個較低的版本進行移植了,因爲韋東山大牛說了,低版本的才能學到東西,越是高版本需要移植時做的工作量越少,學的東西越少。
內核啓動分爲三個階段,第一是運行head.S文件和head-common.S,第三個階段是允許運行main.c文件
對於ARM的處理器,內核第一個啓動的文件是arc/arm/kernel下面的head.S文件。當然arc/arm/boot/compress下面也有這個文件,這個文件和上面的文件略有不同,當要生成壓縮的內核時zImage時,啓動的是後者,後者與前者不同的是,它前面的代碼是做自解壓的,後面的代碼都相同。我們這裏這分析arc/arm/kernel下面的head.S文件。當head.S所作的工作完成後它會跳到init/目錄下的main.c的start_kernel函數開始執行。
第一階段:
首先截取部分head.S文件
ENTRY(stext)
msr cpsr_c,#PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ andirqs disabled
mrc p15,0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10,r5 @ invalidprocessor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8,r5 @ invalidmachine (r5=0)?
beq __error_a @ yes, error 'a'
bl __create_page_tables
/*
*The following calls CPU specific code in a position independent
*manner. See arch/arm/mm/proc-*.S fordetails. r10 = base of
*xxx_proc_info structure selected by __lookup_machine_type
*above. On return, the CPU will be readyfor the MMU to be
*turned on, and r0 will hold the CPU control register value.
*/
ldr r13,__switch_data @ address to jump toafter
@ mmuhas been enabled
adr lr,__enable_mmu @ return (PIC)address
第一步,執行的是__lookup_processor_type,這個函數是檢查處理器型號,它讀取你的電路板的CPU型號與內核支持的處理器進行比較看是否能夠處理。這個我們不關心它的具體實現過程,因爲現在主流處理器內核都提供了支持。
第二步,執行的是__lookup_machine_type,這個函數是來檢查機器型號的,它會讀取你bootloader傳進來的機器ID和它能夠處理的機器ID進行比較看是否能夠處理。內核的ID號定義在arch/arm/tool/mach_types文件中MACH_TYPE_xxxx宏定義。內核究竟就如何檢查是否是它支持的機器的呢?實際上每個機器都會在/arc/arm/mach-xxxx/smdk-xxxx.c[如具體 MTK平臺的在kernel-3.18\arch\arm\mach-mediatek\core.c]文件中有個描述特定機器的數據結構,如下
- MACHINE_START(S3C2440,"SMDK2440")
- /* Maintainer: Ben Dooks<ben@fluff.org> */
- .phys_io =S3C2410_PA_UART,
- .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
- .boot_params = S3C2410_SDRAM_PA + 0x100,
- .init_irq =s3c24xx_init_irq,
- .map_io =smdk2440_map_io,
- .init_machine = smdk2440_machine_init,
- .timer =&s3c24xx_timer,
- MACHINE_END
MACHINE_START和 MACHINE_END實際上被展開成一個結構體
- #defineMACHINE_START(_type,_name) \
- staticconst struct machine_desc __mach_desc_##_type \
- __used \
- __attribute__((__section__(".arch.info.init")))= { \
- .nr =MACH_TYPE_##_type, \
- .name =_name,
- #defineMACHINE_END \
- };
於是上面的數據結構就被展開爲
- staticconst struct machine_desc __mach_desc_S3C2440 \
- __used \
- __attribute__((__section__(".arch.info.init")))= { \
- .nr =MACH_TYPE_S3C2440, \
- .name =”SMDK2440”,};
- .phys_io = S3C2410_PA_UART,
- .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
- .boot_params = S3C2410_SDRAM_PA + 0x100,
- .init_irq =s3c24xx_init_irq,
- .map_io =smdk2440_map_io,
- .init_machine = smdk2440_machine_init,
- .timer =&s3c24xx_timer,
- }
每個機器都會有一個machine_desc__mach_desc結構,內核通過檢查每個machine_desc__mach_desc的nr號和bootloader傳上來的ID進行比較,如果相同,內核就認爲支持該機器,而且內核在後面的工作中會調用該機器的machine_desc__mach_desc_結構中的方法進行一些初始化工作。
對於MTK平臺,在文件kernel-3.18\arch\arm\include\asm\mach\arch.h中有machine_desc結構體:
struct machine_desc {
unsigned int nr; /* architecture number */
const char *name; /* architecture name */
unsigned long atag_offset; /* tagged list (relative) */
const char *const *dt_compat; /* array of device tree
* 'compatible' strings */
unsigned int nr_irqs; /* number of IRQs */
#ifdef CONFIG_ZONE_DMA
phys_addr_t dma_zone_size; /* size of DMA-able area */
#endif
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned char reserve_lp0 :1; /* never has lp0 */
unsigned char reserve_lp1 :1; /* never has lp1 */
unsigned char reserve_lp2 :1; /* never has lp2 */
enum reboot_mode reboot_mode; /* default restart mode */
unsigned l2c_aux_val; /* L2 cache aux value */
unsigned l2c_aux_mask; /* L2 cache aux mask */
void (*l2c_write_sec)(unsigned long, unsigned);
struct smp_operations *smp; /* SMP operations */
bool (*smp_init)(void);
void (*fixup)(struct tag *, char **);
void (*dt_fixup)(void);
void (*init_meminfo)(void);
void (*reserve)(void);/* reserve mem blocks */
void (*map_io)(void);/* IO mapping function */
void (*init_early)(void);
void (*init_irq)(void);
void (*init_time)(void);
void (*init_machine)(void);
void (*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
void (*handle_irq)(struct pt_regs *);
#endif
void (*restart)(enum reboot_mode, const char *);
};
在MTK平臺kernel-3.18\arch\arm\kernel\setup.c中:
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
setup_processor();
mdesc = setup_machine_fdt(__atags_pointer);
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
machine_desc = mdesc;
#ifdef CONFIG_OF
machine_name = of_flat_dt_get_machine_name();
#else
machine_name = mdesc->name; //###############################
#endif
if (mdesc->reboot_mode != REBOOT_HARD)
reboot_mode = mdesc->reboot_mode;
。。。。。。。。。。。。。。。。。。。。。。。
}
第三步,創建一級頁表。
第四步,在R13中保存__switch_data 這個函數的地址,在第四步使能mmu完成後會跳到該函數執行。
第五步,執行的是__enable_mmu,它是使能MMU,這個函數調用了__turn_mmu_on函數,然後在_turn_mmu_on在最後將第三步賦給R13的值傳給了PC指針 (mov pc, r13),於是內核開始跳到__switch_data這個函數開始執行。
我們再來看arch/arm/kenel/head-common.S這個文件中的__switch_data函數
- __switch_data:
- .long __mmap_switched
- .long __data_loc @ r4
- .long __data_start @ r5
- .long __bss_start @ r6
- .long _end @ r7
- .long processor_id @ r4
- .long __machine_arch_type @ r5
- .long cr_alignment @ r6
- .long init_thread_union+ THREAD_START_SP @ sp
- /*
- * The following fragment of code is executedwith the MMU on in MMU mode,
- * and uses absolute addresses; this is notposition independent.
- *
- * r0 =cp#15 control register
- * r1 = machine ID
- * r9 = processor ID
- */
- .type __mmap_switched,%function
- __mmap_switched:
- adr r3,__switch_data + 4
- ldmia r3!,{r4, r5, r6, r7}
- cmp r4,r5 @ Copy datasegment if needed
- 1: cmpne r5,r6
- ldrne fp,[r4], #4
- strne fp,[r5], #4
- bne 1b
- mov fp,#0 @ Clear BSS(and zero fp)
- 1: cmp r6,r7
- strcc fp,[r6],#4
- bcc 1b
- ldmia r3,{r4, r5, r6, sp}
- str r9, [r4] @ Save processor ID
- str r1, [r5] @ Save machine type
- bic r4,r0, #CR_A @ Clear 'A' bit
- stmia r6,{r0, r4} @ Save controlregister values
- b start_kernel
這個函數做的工作是,複製數據段清除BBS段,設置堆棧指針,然後保存處理器內核和機器內核等工作,最後跳到start_kernel函數。於是內核開始執行第二階段。
在MTK平臺kernel-3.18\arch\arm\kernel\head-common.S中:
__mmap_switched_data:
.long __data_loc @ r4
.long _sdata @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
#ifdef CONFIG_CPU_CP15
.long cr_alignment @ r7
#else
.long 0 @ r7
#endif
.long init_thread_union + THREAD_START_SP @ sp
.size __mmap_switched_data, . - __mmap_switched_data
/*
* This provides a C-API version of __lookup_processor_type
*/
ENTRY(lookup_processor_type)
stmfd sp!, {r4 - r6, r9, lr}
mov r9, r0
bl __lookup_processor_type
mov r0, r5
ldmfd sp!, {r4 - r6, r9, pc}
ENDPROC(lookup_processor_type)
__FINIT
.text
第二階段:
我們再來看init/目錄下的main.c的start_kernel函數,這裏我只截圖了部分。
- asmlinkage void __init start_kernel(void)
- {
- …………………….
- ……………………..
- printk(KERN_NOTICE);
- printk(linux_banner);
- setup_arch(&command_line);
- setup_command_line(command_line);
- parse_early_param();
- parse_args("Booting kernel",static_command_line, __start___param,
- __stop___param - __start___param,
- &unknown_bootoption);
- ……………………
- …………………………
- init_IRQ();
- pidhash_init();
- init_timers();
- hrtimers_init();
- softirq_init();
- timekeeping_init();
- time_init();
- profile_init();
- …………………………
- ……………………………
- console_init();
- ………………………………
- ………………………………
- rest_init();
- }
從上面可以看出start_kernel首先是打印內核信息,然後對bootloader傳進來的一些參數進行處理,再接着執行各種各樣的初始化,在這其中會初始化控制檯。最後會調用rest_init();
在MTK平臺kernel-3.18\init\main.c中:
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
/*
* Need to run as early as possible, to initialize the需要運行,儘可能早地初始化
* lockdep hash:
*/
lockdep_init(); //死鎖檢測模塊,於2006年引入內核
set_task_stack_end_magic(&init_task);
/*
* 當只有一個CPU的時候這個函數就什麼都不做,
* 但是如果有多個CPU的時候那麼它就返回在啓動的時候的那個CPU的號
*/
smp_setup_processor_id();
debug_objects_early_init();
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
cgroup_init_early();
local_irq_disable(); /* 關閉當前CPU的中斷 */
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them 中斷仍然是禁用的。做必要的設置去使能他們
*/
boot_cpu_init();
page_address_init(); /* 初始化頁地址,使用鏈表將其鏈接起來 */
pr_notice("%s", linux_banner); /* 顯示內核的版本信息 */
/*
* 每種體系結構都有自己的setup_arch()函數,是體系結構相關的,具體編譯哪個
* 體系結構的setup_arch()函數,由源碼樹頂層目錄下的Makefile中的ARCH變量決定
*/
setup_arch(&command_line);
mm_init_cpumask(&init_mm);
setup_command_line(command_line);
setup_nr_cpu_ids();
........................................................//中間省略
ftrace_init();
/* Do the rest non-__init'ed, we're now alive */
rest_init();//satrt kernel最後執行的初始化
}
我們再來看rest_init()函數
- static void noinline __init_refok rest_init(void)
- __releases(kernel_lock)
- {
- int pid;
- kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
- ............
- }
在MTK平臺kernel-3.18\init\main.c中:
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
smpboot_thread_init();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS); //啓動了kernel_init進程
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}
他啓動了kernel_init這個函數,再來看kerne_init函數
- static int __init kernel_init(void * unused)
- {
- ..............................
- if (!ramdisk_execute_command)
- ramdisk_execute_command = "/init";
- if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
- ramdisk_execute_command = NULL;
- prepare_namespace();
- }
- /*
- * Ok, we have completed the initial bootup, and
- * we're essentially up and running. Get rid of the
- * initmem segments and start the user-mode stuff..
- */
- init_post();
- return 0;
- }
在MTK平臺kernel-3.18\init\main.c中:
static int __ref kernel_init(void *unused)
{
int ret;
kernel_init_freeable(); //它調用了prepare_namespace(); 調用了mount_root掛接根文件系統
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
flush_delayed_fput();
#ifdef CONFIG_MTPROF
log_boot("Kernel_init_done");
#endif
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d). Attempting defaults...\n",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
kernel_init先調用了prepare_namespace();然後調用了init_post函數
- void __init prepare_namespace(void)
- {
- ..........................
- mount_root();
- .....................
- }
可以看出prepare_namespace調用了mount_root掛接根文件系統。接着kernel_init再執行init_post
- static int noinline init_post(void)
- {
- .......................................
- /*打開dev/console控制檯,並設置爲標準輸入、輸出*/
- if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
- printk(KERN_WARNING "Warning: unable to open an initial console.\n");
- (void) sys_dup(0);
- (void) sys_dup(0);
- if (ramdisk_execute_command) {
- run_init_process(ramdisk_execute_command);
- printk(KERN_WARNING "Failed to execute %s\n",
- ramdisk_execute_command);
- }
- /*
- * We try each of these until one succeeds.
- *
- * The Bourne shell can be used instead of init if we are
- * trying to recover a really broken machine.
- */
- //如果bootloader指定了init參數,則啓動init參數指定的進程
- if (execute_command) {
- run_init_process(execute_command);
- printk(KERN_WARNING "Failed to execute %s. Attempting "
- "defaults...\n", execute_command);
- }
- //如果沒有指定init參數,則分別帶sbin、etc、bin目錄下啓動init進程
- run_init_process("/sbin/init");
- run_init_process("/etc/init");
- run_init_process("/bin/init");
- run_init_process("/bin/sh");
- panic("No init found. Try passing init= option to kernel.");
- }
注意上面的run_init_process的會等待init進程返回才往後面執行,所以它一旦找到一個init可執行的文件它將一去不復返。
綜上,內核啓動的過程大致爲以下幾步:
1.檢查CPU和機器類型
2.進行堆棧、MMU等其他程序運行關鍵的東西進行初始化
3.打印內核信息
4.執行各種模塊的初始化
5.掛接根文件系統
6.啓動第一個init進程
ARM-Linux內核移植之(二)——Linux2.6.22內核移植
一、內核移植基本知識
移植內核也叫構建BSP(boardsupprot packet)。BSP的作用有兩個:一是爲內核運行提供底層支持,二是屏蔽與板相關的細節。
BSP的構建分三個層次
1、體系結構層次
對一些體系結構提供linux內核支持,比如說ARM,X86等芯片。這一類工作一般在arc/xxx/下面額除了palt-xxx和mach-xxx目錄的其它目錄完成。
2、SOC層次
對一些公司提供的SOC微處理器提供linux內核支持,比如說三星公司的 S3C2440。這一類工作一般在arch/xxx/plat-xxxxarch/xxx/mach-xxxx目錄下完成。我們可以看到在arch/arm/目錄下同時有plat-s3c24xx和mach-s3c2440兩個目錄,這樣做是因爲plat-s3c24xx目錄下存放了所有s3c24系列相同的代碼,mach-s3c2440則只存放了與S3C2440有關的代碼。【如mtk平臺:kernel-3.18\arch\arm\mach-mediatek】
3,板級層次
這是我們一般的菜鳥要做的,上面兩個層次一般有芯片公司的大牛完成了,但是不同的電路板的板級層次則需要由我們菜鳥完成的。這一類工作主要在mach-xxxx/目錄下面的板文件完成,比如說mach-s3c2440/smdk-s3c2440.c這個S3C2440標準板文件。很多文檔很多書籍都直接在這個文件裏面進行修改,這樣是不對的,對於不同的電路板應該建立不同的板文件,比如說我的是mini2440,就應該建立一個smdk-mini2440.c文件或者mach-mini2440.c文件在mach-s3c2440下面。如果直接在裏面修改是非常不規範的做法,這樣不是在移植內核,這樣是在破壞內核!(這一句是宋寶華說的)。
下面開始移植。
二、BSP構建
1.建立板文件支持
這一步我會重新建立一個板文件mach-mini2440.c,而不是直接在smdk-s3c2440.c裏面修改,這樣或許麻煩一些,但是爲了保持對內核尊重的態度和規範的做法,認爲應該這樣做。
如果我們重新建立一個空的板文件將會導致大量的工作量,幸運的是smdk-s3c2440.c文件已經幫我們做了大量的工作,我們直接拷貝過來命名爲mach-mini2440.c
cp arch/arm/mach-s3c2440/smdks3c2440.c arch/arm/mach-s3c2440/mach-mini2440.c
修改arch/arm/mach-s3c2440/mach-mini2440.c文件將MACHINE_START宏括號裏面的名字換成ID換成MINI2440,名字隨便取,我們取“MINI2440”,這個ID最終會被擴展爲MACH_TYPE_MINI2440,然後到arch/arm/tools/mach_types裏面找對應的ID號,所有做完以這一步我們要在mach_types添加我們機器的ID
MACHINE_START(MINI2440,"MINI2440")
/*Maintainer: Ben Dooks <[email protected]> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18)& 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
在kernel-3.18\arch\arm\mach-mediatek\core.c中:
DT_MACHINE_START(MT6735_DT, "MT6735")
.dt_compat = mt_dt_match,
MACHINE_END
static const char *mt6755_dt_match[] __initconst = {
"mediatek,MT6755",
NULL
};
然後在mach_types裏面添加我們機器的ID,再最後一行添加
mini2440 MACH_MINI2440 MINI2440 1999
第一個表示機器名字,這個也隨便取,第二個在Kconfig配置項裏面定義的宏名稱,下面一步我們會定義到,我們取名爲MACH_MINI2440,第三表示MACH_START第一個參數ID名字,第四個是ID號。ID號我們取爲1999。
修改arch/arm/mach-s3c2440/目錄下的Kconfig和Makefile,以建立內核對板文件的支持使其可以被配置和編譯進內核。
首先修改Kconfig,在endmenu之前加入下面的內容:
87 config MACH_MINI2440 // 開發板名稱宏定義
88 bool "mini2440" // 開發板名稱
89 select CPU_S3C2440 // 開發板使用的處理器類型
90 help
91 Say Y here if you are using the mini2440. // 幫助信息
再修改Makefile
obj-$(CONFIG_MACH_MINI2440)+= mach-mini2440.o
注意這一行要添加在obj-$(CONFIG_ARCH_S3C2440)+= smdk-s3c2440.o後面,否則會編譯錯誤。
這樣我們就可以通過makemenuconfig配置mini2440的板文件是否編譯進內核。
在 kernel-3.18\arch\arm\Kconfig中:
config ARCH_MT6735
bool "MediaTek MT6735"
select GENERIC_TIME
select GENERIC_CLOCKEVENTS
select ARCH_HAS_CPUFREQ
select ARM_HAS_SG_CHAIN
select ARM_AMBA
select CPU_V7
select HAVE_SMP
select NEED_MACH_MEMORY_H
select IRQ_DOMAIN
select IRQ_DOMAIN_DEBUG
select GENERIC_SCHED_CLOCK
select VFP_OPT
select MTK_SYSTRACKER
select MTK_SYS_CIRQ
select MTK_EIC
select MTK_GIC
select COMMON_CLK if !MTK_CLKMGR
select CPU_IDLE
select PINCTRL
select PINCTRL_MT6735
select MFD_SYSCON
select MTK_BASE_POWER
select KERNEL_MODE_NEON
select CRYPTO_AES_ARM32_CE
select CRYPTO_SHA2_ARM_CE
select ARM_ERRATA_836870
select MTK_IRQ_NEW_DESIGN
select MTK_IRQ_NEW_DESIGN_DEBUG
select MTK_POWER_GS
select MTK_CPU_STRESS
help
This enable support for MediaTek MT6735
在kernel-3.18\arch\arm\mach-mediatek\Kconfig中:有類似如下信息
config MACH_MT2701
bool "MediaTek MT2701 SoCs support"
default n
select MFD_SYSCON
select MTK_BASE_POWER
endif
在kernel-3.18\arch\arm\mach-mediatek\Makefile中:
obj-$(CONFIG_ARCH_MEDIATEK) += mediatek.o
我們再跳到linux-2.6.22目錄,執行makemenuconfig
執行加載默認配置文件後,可以開始配置新增加的菜單。進入System Types菜單項,打開S3C24XX Implementations菜單,出現一個目標開發板的列表:
[ ] Simtec ElectronicsBAST (EB2410ITX)
[ ] IPAQ H1940
[ ] Acer N30
[ ] SMDK2410/A9M2410
[ ] SMDK2440
[ ] AESOP2440
[ ] Thorcom VR1000
[ ] HP iPAQ rx3715
[ ] NexVision OTOM Board
[ ] NexVision NEXCODER2440 Light Board
[ ] mini2440
選中mini2440選項
然後執行makezImage,如果能夠正常編譯,已經能夠將mini2440板文件編譯進內核了。如果不行,請檢查上述步驟。
2.修改機器碼
將編譯在arch/arm/boot下面生成的zImage燒寫到nand的kernel分區,然後啓動。
Copylinux kernel from 0x00060000 to 0x30008000, size = 0x00500000 ... done
zImage magic = 0x016f2818
Setup linux parameters at 0x30000100
linux command line is: "console=ttySAC0 root=/dev/nfsnfsroot=192.168.1.101:/home/work/shiyan/rootfsip=192.168.1.102:192.168.1.101:192.168.1.1:255.255.255.0:mini2440:eth0:off"
MACH_TYPE = 362
NOW, Booting Linux......
UncompressingLinux.................................................................................................done, booting the kernel.
Error: unrecognized/unsupported machine ID (r1 = 0x0000016a).
內核提示不能識別的機器ID,於是修改bootloader的參數使其機器ID爲1999,我用的是supervivi使用命令set parammach_type 1999
3.修改時鐘源頻率
啓動內核,出現一系列的亂碼,這是因爲時鐘源設置的不對,我的開發板用的是12M的晶振,所以在arch/arm/mach-s3c2440.c的s3c24xx_init_clocks(16934400);處將16924400修改爲12000000。即改爲s3c24xx_init_clocks(12000000);
4.添加nand分區信息
再啓動,發現還是不能啓動,這是因爲內核中填寫的nand分區信息不對。於是修改nand分區信息,很多人的做法是直接修改arch/arm/plat-s3c24xx/Common-smdk.c文件裏面的smdk_default_nand_part數據結構,這樣是不提倡的做法,因爲還是那句話,破壞了內核。我們應該再arch/arm/mach-s3c2440/mach-mini2440.c文件中建立我們自己板文件的nand信息。我們在mach-mini2440.c的staticstruct platform_device *smdk2440_devices[]前面添加
static struct mtd_partition smdk_default_nand_part[] = {
/*這裏面填的是我用的mini2440分區信息*/
[0] = {
.name = "patition1 supervivi",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "patition2 param",
.offset =0x00040000,
.size = 0x00020000,
},
[2] = {
.name = "patition3 kernel",
.offset =0x00060000,
.size = 0x00500000,
},
[3] = {
.name = "patition4 root",
.offset = 0x00560000,
.size = 64*1024*1024,
},
[4] = {
.name = "patition5 nand",
.offset = 0,
.size = 64*1024*1024,
},
};
static struct s3c2410_nand_set smdk_nand_sets[] = {
[0] = {
.name = "NAND",
.nr_chips = 1,
.nr_partitions = ARRAY_SIZE(smdk_default_nand_part),
.partitions = smdk_default_nand_part,
},
};
再修改mach-mini2440.c的smdk2440_machine_init函數,將我們的nand傳給給nand設備
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
/*將我們的nand信息傳給nand設備*/
s3c_device_nand.dev.platform_data= &smdk_nand_info; //set nand infoto nand
platform_add_devices(smdk2440_devices,ARRAY_SIZE(smdk2440_devices));
//smdk_machine_init();
//smdk_machine_init()函數屏蔽,因爲他會將arch/arm/plat-s3c24xx/Common-smdk.c裏面的分區信息傳給nand,這樣我們的自己的nand信息就被覆蓋了
s3c2410_pm_init();//添加加這個函數是因爲smdk_machine_init()裏面調用了。
}
再修改mach-mini2440.c的smdk2440_devices
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_nand,//向內核添加nand設備
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
};
6.添加YAFFS文件系統支持
完成上述步驟工作後,還是不能正常掛載根文件系統,因爲內核還沒對yaffs文件系統進行支持。
下載cvs-root-yaffs.tar.gz補丁包文件,解壓,運行yaffs2文件夾裏面的腳本文件patch-ker.sh來給內核打補丁,用法如下
Usage: ./patch-ker.sh c/l kernelpath
if c/l is c,then copy, if l then link
如果是l則yaffs2源碼被鏈接到內核,如果是c則複製
我們運行./patch-ker.sh c work/kernel_make/linux2.6.22
給內核打上yaffs2補丁,然後使用makemenuconfig配置內核使其支持yaffs2文件系統
File systems --->
Miscellaneous filesystems --->
<*>YAFFS2 file system support
7.配置內核支持EABI接口
完成上面的步驟之後運行,內核會在輸出
VFS: Mounted root (yaffs filesystem) on device 31:2.
Freeing init memory: 132K
之後卡住,這個打印反應出內核實際上已經掛接上了根文件系統,之所以卡在這裏是因爲無法啓動根文件系統上的init進程。是由於內核和根文件系統的應用程序的接口不一致。所以在內核中使用make menuconfig配置EABI支持
Kernel Features --->
Memory split...--->
[ ]preemptible Kernel...
[*]Use the ARM EABI to compile thekernel
[*] Allow old ABI binaries to run......
Memory model(flatMemory)--->
[ ]Add lru list to tarcknon-evictable pages
我們通常使用Busybox來構建根文件系統的必要的應用程序。Busybox通過傳入的參數來決定執行何種操作。當init進程啓動時,實際上調用的是Busybox的init_main()函數,下面我們來分析這個函數,看init進程究竟是怎樣一個流程。我分析的Busybox源碼是1.7.0版本的,其它版本會略有不同。部分代碼省略我們只看關鍵性代碼。
首先看init_main函數
- int init_main(int argc, char **argv);
- int init_main(int argc, char **argv)
- {
- ……………………………..
- ……………………………..
- //初始化控制檯
- console_init();
- ………………………………
- if (argc > 1
- && (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))
- ) {
- new_init_action(RESPAWN, bb_default_login_shell, "");
- } else {
- //因爲我們啓動的init進程沒有任何參數,所有argc==1,執行的是這一句
- parse_inittab();
- }
- …………………………………………
- …………………………………………
- run_actions(SYSINIT); //運行inittab配置文件中acthion爲SYSINIT的進程
- run_actions(WAIT); //運行inittab配置文件中action爲WAIT的進程
- run_actions(ONCE); //運行inittba配置文件中action爲ONCE的進程
- ………………………………………………
- while (1) {
- /*
- 運行inittab配置文件中action爲RESPAWN和ASKFIRST的進程,一旦退出則重新啓動
- */
- run_actions(RESPAWN);
- run_actions(ASKFIRST);
- wpid = wait(NULL);
- while (wpid > 0) {
- a->pid = 0;
- }
- wpid = waitpid(-1, NULL, WNOHANG);
- }
- }
parse_inittab實際上對/etc/inittab文件裏面的配置進行解釋,如果沒有,則設置一些默認設置。
我們先來看看這個inittab這個文件裏面的配置格式,這個在busybox文件裏面的inittab文件裏面有說明
<id>:<runlevels>:<action>:<process>
id表示輸出輸入設備,這個不需要設置,因爲/etc/console已經設爲標準輸入輸出了,如不設置,則從控制檯輸入輸出。
runlevels 這個參數完全忽略
action 運行時機,它表示inittab解釋後的運行順序,它有sysinit, respawn, askfirst, wait, once,restart, ctrlaltdel, andshutdown.這個值可選擇。
process 就是要啓動的進程。
下面來看prase_inittab這個函數
- static void parse_inittab(void)
- {
- …………………………………………………
- …………………………………………………
- /*INITTAB是一個宏 #define INITTAB "/etc/inittab"
- 可以看得出來它打開了/etc/inittab這個文件*/
- file = fopen(INITTAB, "r");
- //如果沒有這個文件,則調用new_init_action進行一些默認的操作
- if (file == NULL) {
- new_init_action(CTRLALTDEL, "reboot", "");
- new_init_action(SHUTDOWN, "umount -a -r", "");
- if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");
- new_init_action(RESTART, "init", "");
- new_init_action(ASKFIRST, bb_default_login_shell, "");
- new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
- new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
- new_init_action(ASKFIRST, bb_default_login_shell, VC_4); new_init_action(SYSINIT, INIT_SCRIPT, "");
- return;
- }
- …………………………………………………
- …………………………………………………
- /*果inittab文件裏面有內容就將裏面的內容一行一行讀出來,然後調用new_init_action進行操作*/
- while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) {
- /* Ok, now process it */
- for (a = actions; a->name != 0; a++) {
- if (strcmp(a->name, action) == 0) {
- if (*id != '\0') {
- if (strncmp(id, "/dev/", 5) == 0)
- id += 5;
- strcpy(tmpConsole, "/dev/");
- safe_strncpy(tmpConsole + 5, id,
- sizeof(tmpConsole) - 5);
- id = tmpConsole;
- }
- new_init_action(a->action, command, id); //將inittab裏面的action相同的操作串成一個鏈表。
- break;
- }
- }
- …………………………………………………
- …………………………………………………
- }
- fclose(file);
- }
這個new_init_action函數,它實際上是將inittab裏面的action相同的操作串成一個鏈表。
下面我們再來分析init_main執行prase_inittab之後執行的操作
可以看出init_main執行prase_initab對inittab文件裏面的配置進行解釋之後,會先執行運行時機爲SYSINIT的進程,讓執行WAIT時機的,接着是ONCE的,然後在一個while(1)函數裏面運行RESPAWN和ASKFIRST時機的,一旦這兩個時機裏面的進程被殺死,就會把他們的pid賦爲0,然後跳到while(1)函數的開始處又去啓動他們。所有說運行時機爲RESPAWN和ASKFIRST的進程永遠無法殺死,除非reboot或者shutdown。
下面我們來總結一下init進程的啓動過程
1.初始化控制檯
2.解釋inittab
3.執行inittab運行時機爲SYSINIT的進程
4.執行inittab運行時機爲WAIT的進程
5.執行inittab運行時機爲ONCE的進程
6.執行inittab運行時機爲RESPAWN和ASKFRIST的進程,有退出的則重新執行。
ARM-Linux移植之(四)——根文件系統構建
相關工具版本:
busybox-1.7.0 arm-linux-4.3.2 linux-2.6.22
1.配置busybox並安裝。
在我們的根文件系統中的/bin和/sbin目錄下有各種命令的應用程序,而這些程序在嵌入式系統中都是通過busybox來構建的,每一個命令實際上都是一個指向busybox的鏈接,busybox通過傳入的參數來決定進行何種命令操作。
1)配置busybox
解壓busybox-1.7.0,然後進入該目錄,使用makemenuconfig進行配置。這裏我們這配置兩項
一是在編譯選項選擇動態庫編譯,當然你也可以選擇靜態,不過那樣構建的根文件系統會比動態編譯的的大。
->Busybox Settings
->BuildOptions
->Buildshared libbusybox
二是在性能微調選項選擇tab鍵補全功能。
->Busybox Settings
->Busyboxlibrary Tuning
->Commandline editing
->Tabcompletion
其他的都是一些命令配置,如果你想使你的根文件系統具備哪些命令就選擇那個命令。我選擇的是默認的配置,一般基本的命令都幫你選上了。
2)編譯busybox
修改Makefile,修改”ARCH?= arm” 和”CROSS_COMPILE?= arm-linux-“,然後使用make命令進行編譯。我在編譯的過程出現如下錯誤:
../arm-none-linux-gnueabi/libc/usr/include/linux/netfilter.h:44:
error: field ‘in’ has incomplete type
解決辦法:
修改arm-linux交叉編譯工具鏈
在usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/linux/netfilter.h 頭文件的開頭添加缺少的頭文件:#include<netinet/in.h>
3)安裝busybox
這裏我們先新建一個root_fs來構建根文件系統,
然後使用命令makeCONFIG_PREFIX=/home/y-kee/work/root_make/root_fs對busybox進行安裝。於是在root_fs下面就出現瞭如下目錄和文件,可以看出linuxrc是指向busybox的鏈接。
[root@localhost root_fs]# ls -l
total 12
drwxr-xr-x 2 root root 4096 Oct 19 05:41bin
lrwxrwxrwx 1 root root 11 Oct 22 11:17 linuxrc -> bin/busybox
drwxr-xr-x 2 root root 4096 Oct 22 18:43sbin
drwxr-xr-x 4 root root 4096 Oct 22 16:52usr
進入bin目錄,可以看出這些文件全部是指向busybox的鏈接(除了busybox本身)。
[root@localhostroot_fs]# ls bin -l
total 0
lrwxrwxrwx 1 root root 7 Oct 22 11:17addgroup -> busybox
lrwxrwxrwx 1 root root 7 Oct 22 11:17adduser -> busybox
lrwxrwxrwx 1 root root 7 Oct 22 11:17ash -> busybox
-rwxr-xr-x 1 root root 0 Oct 23 13:20busybox
lrwxrwxrwx 1 root root 7 Oct 22 11:17cat -> busybox
lrwxrwxrwx 1 root root 7 Oct 22 11:17catv -> busybox
2.安裝glibc庫。
在root_fs下新建lib目錄,再把arm-linux-交叉編譯鏈下的lib文件拷貝到我們root_fs下的lib目錄下。我使用
cp /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib/*root_fs/lib/* -df
使用-d選項表示鏈接文件按照原來的鏈接方式拷貝,否則鏈接文件拷貝過來是一個副本。
3.構建/etc,/dev,proc目錄
/etc和/dev是一個根文件系統必須的。/etc文件需包含init進程啓動所需的配置文件inittab.dev目錄下需包含init進程啓動需要的兩個設備文件console和null。proc目錄要進來掛載內核的虛擬proc文件系統。這樣對進程的一些命令如ps纔會有效。
1) 在dev目錄下執行mkdiretc dev proc
2) 在etc下新建文件inittab
inittab內配置信息的格式我已經在我的上一篇文章《linux移植(三)》裏解釋了。我們在裏面寫入兩行配置信息。
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
第一行是用來啓動腳本文件rcS,之所以這樣做,是因爲我們可以利用這個文件來引導系統啓動時爲我們做一個工作比如說掛載文件系統或者啓動一些其他的應用程序的。
第二個是啓動shell解釋器sh
3)配置腳本文件rcS
在etc下新建init.d目錄,然後在init.d目錄下新建rcS文件,再給rcS文件加上可執行權限。
在rcS目錄下寫入
#!bin/sh
mount –a
在rcS裏面執行mount –a命令,於是該命令就會根據etc下fstab文件來掛載相應的目錄。
4)配置fstab文件
在etc目錄下新建fstab文件,然後在該文件內寫入
# device mount-point type options dump fsck order
proc /proc proc defaults 0 0
第一個參數是設備,第二個是掛載點,第三個是設置。其餘的寫兩個0。
5)構建dev目錄下的設備文件。
由於console和null設備是init進程啓動所必須的,所以要建立這兩個設備文件,進入dev目錄,然後執行
mknod console c 5 1
mknod null c 1 3
如果這兩個設備文件沒有手工mknod創建就在內核啓動時會出現錯誤:
Warning: unable to open an initialconsole.
注意一定是在dev下創建這兩個設備文件。我因爲一時糊塗在etc目錄下創建了這兩個文件,調了大半天才找到原因。還有在cdetc或者cddev時千萬不要在etc和dev前面順手打上了斜槓了,我就是手賤,順手打了斜槓,結果進入的PC上的LINUX系統的etc目錄刪了一些文件,導致系統崩潰。
完成了上述步驟,將根文件系統製作成yaffs2鏡像燒到flash就能正常啓動了。
5.配置mdev來支持熱插拔
busybox使用sbin目錄下的一個mdev來支持熱插拔,什麼叫做支持熱插拔?就是你linux系統啓動時插入一個設備,則mdev會在dev目錄下自動給你創建設備文件。
在/busybox源碼中的mdev.txt有介紹。我截取部分如下
Mdev has two primary uses: initialpopulation and dynamic updates. Both
require sysfs support in the kernel andhave it mounted at /sys. For dynamic
updates, you also need to havehotplugging enabled in your kernel.
Here's a typical code snippet from theinit script:
[1] mount -t sysfs sysfs /sys
[2] echo /bin/mdev >/proc/sys/kernel/hotplug
[3] mdev -s
Of course, a more "full" setupwould entail executing this before the previous
code snippet:
[4] mount -t tmpfs mdev /dev
[5] mkdir /dev/pts
[6] mount -t devpts devpts /dev/pts
The simple explanation here is that [1]you need to have /sys mounted before
executing mdev. Then you [2] instruct the kernel to execute/bin/mdev whenever
a device is added or removed so that thedevice node can be created or
destroyed. Then you [3] seed /dev with all the devicenodes that were created
while the system was booting.
For the "full" setup, you wantto [4] make sure /dev is a tmpfs filesystem
(assumingyou're running out of flash). Then you want to [5] create the
/dev/pts mount point and finally [6]mount the devpts filesystem on it.
當我們在flash中使用使,則只需要前面[1][2][3]步就行了。即
[1] mount -t sysfs sysfs /sys
[2] echo /bin/mdev >/proc/sys/kernel/hotplug
[3] mdev -s
於是我們在etc/init.d/rcS文件改爲
mount –a
echo /bin/mdev >/proc/sys/kernel/hotplug
mdev -s
將ect/fstab文件改爲
# device mount-point type options dump fsck order
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
再在root_fs下新建一個sys目錄。
於是我們再做成一個yaffs2鏡像就可以支持自動創建設備文件了,注意上面說到的建立的console和null設備文件不能刪除,因爲它們在mdev工作之前就已經被使用了。
6.完善根文件系統。
1)將etc目錄下的inittab加上
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::restart:/sbin/init
來指定系統執行特殊操作命令(shultdown、restart、ctrlaltdel)時做的附加工作。
2)在root_fs下新建mnt、tmp、root目錄
注意:
在製作根文件系統的過程中不要去移動root_fs目錄下的由busybox創建的binsbin usr和linuxrc,因爲這些目錄和文件很多都是鏈接文件。移動可能會導致內核啓動時出現如下錯誤:
request_module:runaway loop modprobe binfmt-0000
我就被這個問題搞了好久!後來我是從一個好的根文件系統把這些文件和目錄拷貝過來才行。切記切記!