红联Linux门户
Linux帮助

ARM-Linux内核移植

发布时间:2014-12-01 21:54:03来源:linux网站作者:K-Style

内核版本: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

movsr10,r5@ invalidprocessor (r5=0)?

beq  __error_p@ yes, error 'p'

bl__lookup_machine_type @ r5=machinfo

movsr8,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号定义在arc/arm/tool/mach_types文件中MACH_TYPE_xxxx宏定义。内核究竟就如何检查是否是它支持的机器的呢?实际上每个机器都会在/arc/arm/mach-xxxx/smdk-xxxx.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_结构中的方法进行一些初始化工作。

第三步,创建一级页表。

第四步,在R13中保存__switch_data 这个函数的地址,在第四步使能mmu完成后会跳到该函数执行。

第五步,执行的是__enable_mmu,它是使能MMU,这个函数调用了__turn_mmu_on函数,让后在_turn_mmu_on在最后将第三步赋给R13的值传给了PC指针 (movpc, 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 
.longprocessor_id   @ r4 
.long__machine_arch_type@ r5 
.longcr_alignment   @ r6 
.longinit_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:cmpner5,r6 
ldrnefp,[r4], #4 
strnefp,[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} 
strr9, [r4]@ Save processor ID 
strr1, [r5]@ Save machine type 
bic   r4,r0, #CR_A @ Clear 'A' bit 
stmiar6,{r0, r4}@ Save controlregister values 
bstart_kernel 

这个函数做的工作是,复制数据段清楚BBS段,设置堆在指针,然后保存处理器内核和机器内核等工作,最后跳到start_kernel函数。于是内核开始执行第二阶段。


第二阶段:

我们再来看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();

我们再来看rest_init()函数

static void noinline __init_refok rest_init(void) 
 __releases(kernel_lock) 

 int pid; 
 
 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); 
 ............
}  

他启动了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; 

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进程


平台:mini2440  交叉工具链:arm-linux-gcc-4.3.2

一、内核移植基本知识

移植内核也叫构建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有关的代码。

2,板级层次

这是我们一般的菜鸟要做的,上面两个层次一般有芯片公司的大牛完成了,但是不同的电路板的板级层次则需要由我们菜鸟完成的。这一类工作主要在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.carch/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 <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

  然后在mach_types里面添加我们机器的ID,再最后一行添加

  mini2440 MACH_MINI2440 MINI24401999

  第一个表示机器名字,这个也随便取,第二个在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

91Say 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的板文件是否编译进内核。

  我们再跳到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); 
 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的进程,有退出的则重新执行。

相关工具版本:

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文件,然后在该文件内写入

# devicemount-pointtype  options dump  fsck order

proc/proc proc  defaults 00

第一个参数是设备,第二个是挂载点,第三个是设置。其余的写两个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文件改为

# devicemount-pointtype  options dump  fsck order

proc  /proc  proc   defaults 00

sysfs/sys   sysfsdefaults   00

再在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

我就被这个问题搞了好久!后来我是从一个好的根文件系统把这些文件和目录拷贝过来才行。切记切记!