u-boot、kernel和filesystem 执行过程分析

 

标题:

  Uboot -kerne-root 启动流程

内容:

  ※uboot启动流程

  ※Kernel启动流程

  ※Root启动流程

  ※构建根文件系统

 

/*********************************

*u-boot:   u-boot2012.04.01

*kernel:    linux-2.6.22

*busybox:    busybox-1.1.6

*********************************/ 

 

 

 

 

一、uboot启动流程

1.初始化硬件

2.把内核从NAND读到SDRAM

3.设置内核启动参数

4.跳转执行内核

 

执行第一个代码:/cpu/arm920t/start.S

 

 

第一阶段(start.S)

reset:

   set the cpu to SVC32 mode          //管理模式

   turn off the watchdog               //关看门狗

   mask all IRQs                         //屏蔽中断

   cpu_init_crit

  flush v4 I/D caches              //关闭caches

  disable MMU stuff and caches     //关闭MMU

  lowlevel_init  //配置SDRAM

   Relocate                          //重定位

   stack_setup:                       //设置堆栈

   clear_bss:                         //清楚bss段

   start_armboot                      //跳转执行第二阶段

 

第二阶段(board.c)

start_armboot (void)

  init_sequence

  cpu_init,                      /* basic cpu dependent setup */

  board_init,                   /* basic board dependent setup */

  interrupt_init,              /* set up exceptions */

  env_init,                      /* initialize environment */

  init_baudrate,               /* initialze baudrate settings */

  serial_init,                   /* serial communications setup */

  console_init_f,             /* stage 1 init of console */

  display_banner,                        /* say that we are here */

  flash_init            初始化NOR

  nand_init()           初始化NAND

  env_relocate ()        将环境变量读入指定位置

  cs8900_get_enetaddr     初始化网络设备

  main_loop ()            死循环

    s = getenv ("bootdelay")

      bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

    s = getenv ("bootcmd");

    Bootcmd=nand read.jffs2 0x30007fc0 kernel;bootm 0x30007fc0  //环境变量

    run_command (s, 0);

    continue;

 

 

Uboot命令的实现

①串口输入命令(字符串)

②动作(函数),命令对应于名字

因而根据命令找到对应函数,来执行。Uboot里面有一个结构体

包含1.名

2.函数

struct cmd_tbl_s {

          char                  *name;              /* Command Name                                 */

          int                    maxargs;           /* maximum number of arguments           */

          int                    repeatable;         /* autorepeat allowed?                */

                                                          /* Implementation function         */

          int                    (*cmd)(struct cmd_tbl_s *, int, int, char *[]);

          char                  *usage;              /* Usage message           (short)   */

#ifdef  CFG_LONGHELP

          char                  *help;               /* Help  message            (long)   */

#endif

#ifdef CONFIG_AUTO_COMPLETE

          /* do auto completion on the arguments */

          int                    (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);

#endif

};

 

此时run_command函数根据名字匹配该结构体

 

Run_command

命令的提取

while (*str) {

 

                      /*

                       * Find separator, or string end

                       * Allow simple escape of ';' by writing "\;"

                       */

                      for (inquotes = 0, sep = str; *sep; sep++) {

                                  if ((*sep=='\'') &&

                                      (*(sep-1) != '\\'))

                                              inquotes=!inquotes;

 

                                  if (!inquotes &&

                                      (*sep == ';') &&         /* separator                    */

                                      ( sep != str) &&         /* past string start           */

                                      (*(sep-1) != '\\'))        /* and NOT escaped        */

                                              break;

                      }

parse_line()   //解析命令

Cmdtp=find_cmd()      //匹配命令  cmdtp就是指向上面的结构体cmd_tbl_s

for (cmdtp = &__u_boot_cmd_start;

               cmdtp != &__u_boot_cmd_end;

               cmdtp++)

 

 

 

 

以上:

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

比如启动命令:

Bootcmd=nand read.jffs2 0x30007fc0 kernel;bootm 0x30007fc0以bootm 0x30007fc0为例

U_BOOT_CMD(

     bootm,  CFG_MAXARGS,         1,         do_bootm,

     "bootm   - boot application image from memory\n",

     "[addr [arg ...]]\n    - boot application image stored in memory\n"

     "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"

     "\t'arg' can be the address of an initrd image\n"

#ifdef CONFIG_OF_FLAT_TREE

     "\tWhen booting a Linux kernel which requires a flat device-tree\n"

     "\ta third argument is required which is the address of the of the\n"

     "\tdevice-tree blob. To boot that kernel without an initrd image,\n"

     "\tuse a '-' for the second argument. If you do not pass a third\n"

     "\ta bd_info struct will be passed instead\n"

#endif

);

U_boot_cmd的定义:

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \

cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

以上可以展开为;

cmd_tbl_t __u_boot_cmd_bootm  __attribute__ ((unused,section (".u_boot_cmd")))

= {bootm, CFG_MAXARGS, 1, do_bootm, "bootm   - boot application image from memory\n", help}

以上解释为:bootm命令定义了__u_boot_cmd_bootm结构体,该结构体类型是cmd_tbl_t,并且强制为.u_boot_cmd段属性

 

Uboot启动内核

内核启动依赖nand read.jffs2 0x30007fc0 kernel;bootm 0x30007fc0

nand read.jffs2 0x30007fc0 kernel把内核读到0x30007fc0

从哪里读? --kernel分区在配置文件里面写死了smdk2410.h

读到哪里? --0x30007fc0

Nand命令分析:

"nand read[.jffs2]     - addr off|partition size\n"

 

bootm 0x30007fc0从bootm 0x30007fc0启动它

Bootm命令分析:

do_bootm

image_header_t *hdr = &header;

typedef struct image_header {

   uint32_t            ih_magic;          /* Image Header Magic Number  */

   uint32_t            ih_hcrc; /* Image Header CRC Checksum */

   uint32_t            ih_time;            /* Image Creation Timestamp      */

   uint32_t            ih_size; /* Image Data Size                     */

   uint32_t            ih_load; /* Data  Load  Address               */

   uint32_t            ih_ep;               /* Entry Point Address                */

   uint32_t            ih_dcrc; /* Image Data CRC Checksum     */

   uint8_t              ih_os;                /* Operating System                   */

   uint8_t              ih_arch; /* CPU architecture                    */

   uint8_t              ih_type; /* Image Type                            */

   uint8_t              ih_comp;           /* Compression Type                  */

   uint8_t              ih_name[IH_NMLEN];   /* Image Name              */

} image_header_t;

memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);

//如果当前地址不是入口地址,移到加载地址0x30008000,由于头部占用64字节,所以此时地址是0x30007fc0

do_bootm_linux  (cmdtp, flag, argc, argv,

                                       addr, len_ptr, verify); //启动内核

setup_start_tag (bd);

setup_memory_tags (bd);

setup_commandline_tag (bd, commandline);

setup_end_tag (bd);

printf ("\nStarting kernel ...\n\n");

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

 

 

 

二、Kernel启动流程

 

处理uboot传入的参数

①判断是否支持这个CPU

②判断是否支持这个单板

③建立页表

④使能MMU

⑤跳到start_kernel函数

 

内核执行的第一个代码文件:/arch/arm/kernel/head.S  和 /arch/arm/boot/bootp/init.S

 

Start_kernel分析

1.输出内核版本信息

2.Setup_arch($command_line)

3.Setup_command_line(command_line)

 

 

调用关系:

Start_kernel

            printk(linux_banner);   //输出版本信息

            setup_arch(&command_line); //匹配ID,arch-type

                        setup_processor();

                                    list = lookup_processor_type(processor_id);

                        mdesc = setup_machine(machine_arch_type);

parse_cmdline(cmdline_p, from); //解析命令

            setup_command_line(command_line);

                        strcpy (saved_command_line, boot_command_line);

Rest_init

  Kernel_thread()    //线程

  // kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

    kernel_init()

    Prepare_namespace()

    Mount_root()

    Init_post()

      Run_init_process()  //执行文件系统第一个程序

      ...

 

三、Root启动流程

init过程

1.读取配置文件

2.解析配置文件

3.执行客户程序

 

 

配置文件:1.指定程序;2.何时执行

 

busybox调用过程

busybox -> init_main

               parse_inittab

                           file = fopen(INITTAB, "r");//打开配置文件/etc/inittab

                          (没有配置文件时使用默认配置文件)

                           new_init_action()

                           #1.创建init_action结构,填充

                           #2.把这个结构放入init_action_list链表里面

               run_actions(SYSINIT);

                           waitfor(a, 0); //执行程序,等待执行完毕

                                       run(a); //系统调用,创建precess子进程

                                       waitpid(runpid, &status, 0) //等待结束

                           delete_init_action(a);//在init_action_list里面删掉应用程序

               run_actions(WAIT);

                           waitfor(a, 0);

                           delete_init_action(a);

               run_actions(ONCE);

                           run(a);//不会等待子进程结束

                           delete_init_action(a);//在init_action_list里面删

               while (1) {

                     run_actions(RESPAWN);

                                       run(a)  

                           run_actions(ASKFIRST);

                                       run(a)

                                                   打印:"\nPlease press Enter to activate this console. ";

                                                   等待回车

                                                   创建子进程

                           wpid = wait(NULL);//等待子进程退出

                           while (wpid > 0) {

                                       a->pid = 0;//退出后,设置pid=0

                           }

               }

              

             

 

 

init_action_list里面有id,action,process等等

 

 

 static void new_init_action(int action, const char *command, const char *cons)

struct init_action *new_action, *a, *last;

 

init_action里面有:

struct init_action *next;

   int action;

   pid_t pid;    //进程号

   char command[INIT_BUFFS_SIZE];  //应用程序

   char terminal[CONSOLE_NAME_SIZE];  //终端

 

 

new_init_action做了什么

1.创建init_action结构,填充

2.把这个结构放入init_action_list链表里面          

 

现在假设没有配置文件,根据

/* No inittab file -- set up some default behavior */

#endif

               /* Reboot on Ctrl-Alt-Del */

               new_init_action(CTRLALTDEL, "reboot", "");

               /* Umount all filesystems on halt/reboot */

               new_init_action(SHUTDOWN, "umount -a -r", "");

               /* Swapoff on halt/reboot */

               if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");

               /* Prepare to restart init when a HUP is received */

               new_init_action(RESTART, "init", "");

               /* Askfirst shell on tty1-4 */

               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);

               /* sysinit */

               new_init_action(SYSINIT, INIT_SCRIPT, "");

              

来反推出默认配置项

#inittab格式:(文件在example/inittab文件)

 #<id>:<runlevels>:<action>:<process>

 

 #<id>         :/dev/id,最终用作终端:stdin,stdout,stderrer:printf,scanf,err

 #<runlevels>  :忽略

 #<action>     :何时执行的

 <action>: Valid actions include: sysinit, respawn, askfirst, wait, once,

                                  restart, ctrlaltdel, and shutdown.

 

 #<process>    :应用程序或者脚本

 

 

 

 new_init_action(ASKFIRST, -/bin/sh, /dev/tty2);    

 

 

 new_init_action(CTRLALTDEL, "reboot", "");

 id=null

 runlevels=null

 action=ctrlaltdel

 那么上面就是:

 ::ctrlaltdel:reboot

 同理可知其他的配置项是:

 ::shutdown:umount -a -r

 ::restart:init

 ::askfirst:-/bin/sh

 tty2:askfirst:-/bin/sh

 tty3:askfirst:-/bin/sh

 tty4:askfirst:-/bin/sh

 ::sysinit:/etc/init.d/rcS

 

 

 

 

 

 其实:

 main里面

 signal(SIGHUP, exec_signal);

   signal(SIGQUIT, exec_signal);

   signal(SIGUSR1, shutdown_signal);

   signal(SIGUSR2, shutdown_signal);

   signal(SIGINT, ctrlaltdel_signal);

   signal(SIGTERM, shutdown_signal);

   signal(SIGCONT, cont_handler);

   signal(SIGSTOP, stop_handler);

   signal(SIGTSTP, stop_handler);

      

当用户按下 ctrl + alt +del时会产生一个信号,然后执行ctrlaltdel_signal函数

static void ctrlaltdel_signal(int sig ATTRIBUTE_UNUSED)

{

   run_actions(CTRLALTDEL);

}

 

 

 

 

 

总结:应用程序所需要的文件

 

1./dev/console /dev/null

2.配置文件/etc/inittab

3.配置文件里面指定的应用程序

4.库文件

5.init本身,即busybox

以上就是最小根文件系统所需要的项

 

构建根文件系统

 

 

read  INSTALL  rirst.

 

 

 

 

总结:应用程序所需要的文件

 

1./dev/console /dev/null

2.配置文件/etc/inittab

3.配置文件里面指定的应用程序

4.库文件

5.init本身,即busybox

以上就是最小根文件系统所需要的项

 

 

编译busybox

先阅读install文件可知道如何编译它

 

make menuconfig

make

make install(这里安装要注意)

安装到我们指定的某个文件里面

使用make CONFIG_PREFIX=/path/from/root install

 

 

 

编译需要在makefile里面加上cross_compiler里面加上arm-linux-

 

 

一、创建console

 

 

现在上面编译后busybox目录是work/FL_fs/first_fs

在此目录下:ls /dev/console /dev/null -l

crw------- 1 root root 5, 1 2015-01-05 20:57 /dev/console

crw-rw-rw- 1 root root 1, 3 2015-01-05 20:30 /dev/null

那么根据它来创建console null等设备

 

#mkdir dev

#cd dev

#mknod console c 5 1

#mknod null c 1 3

#ls -l

显示:

 crw-r--r-- 1 root root 5, 1 2015-05-06 20:39 console

crw-r--r-- 1 root root 1, 3 2015-05-06 20:40 null

表示创建成功

 

 

 

二、配置项

#cd work/FL_fs/first_fs

#mkdir etc

#vim  etc/inittab

 

输入:

console::askfirst:-/bin/sh

 

 

三、安装c库

#cd lib

#cp /work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib *.so* /work/FL_fs/first_fs/lib -d

 

 

 

 end

 

 最小根文件系统已经完成。

 

 此时需要制作镜像文件以烧写到硬件。

 yaffs2和jffs文件系统

 

 1.制作yaffs2

 #cd work/system

 yaffs_source_util_large_smmall_page_nand.tar.bz2

 

 #tar xjf  yaffs_source_util_large_small_page_nand.tar.bz2

 #cd Developement_util_ok

 #cd yaffs2

 #cd utils

 #make

 #cp mkyaffs2image /usr/local/bin

 #chmod +x /usr/local/bin/mkyaffs2image

 

 

 

 #cd /work/FL_fs

 #mkyaffs2image first_fs first_fs.yaffs2

 

 此后烧写到开发板里面。

 可以启动,并能使用ls,cd等命令。

 

 

 

 如何改进?

 

 #cd ../first_fs

 #mkdir proc

 

 

 单板上有虚拟系统

 #mkdir proc

 #mount -t proc none /proc

 #ps

 可以看到哪些程序及其内容

   PID TTY          TIME CMD

  383 pts/2    00:00:00 ps

30564 pts/2    00:00:00 su

30573 pts/2    00:00:00 bash

383 进程号

#cd 383

如果不想 手动挂载 #mount -t proc none /proc

也可以写到配置文件里面

 

#vim inittab

++ ::sysinit:/etc/init.d/rcS  需要脚本

#mkdir etc/init.d

#vim rcS

##mount -t proc none /proc

#chmod +x /etc/init.d/rcS

就可以了

 

上面#mount -t proc none /proc

也可以使用#mount -a

 

#mount -a是什么意思?

读出/etc/fstab内容来挂载

 

怎么使用呢?

#mount -a会依赖fstab文件

fstab文件内容有哪些呢?

#device         mount-point    type     options     dump   fsck

proc        /proc        proc defaults    0       0

 

那么,在/etc/fstab里面输入:

proc   /proc    default 0 0

然后在脚本rcS里面使用#mount -a

 

 

然后再制作yaffs镜像文件就和第一种方法是一样的。

可以使用#cat /proc/mounts查看挂载了哪些文件系统

 

 

继续

 

 

上面的dev是手工创建

那么怎么自动创建dev下面的设备节点呢

方法:使用mdev

 

怎么使用mdev?  在/

可以查看mdev.txt

 

[1] mount -t sysfs sysfs /sys

[2] echo /bin/mdev > /proc/sys/kernel/hotplug

[3] mdev -s

[4] mount -t tmpfs mdev /dev

[5] mkdir /dev/pts

[6] mount -t devpts devpts /dev/pts

这6步就可以了。

 

 

所以:

1、#cd ../first_fs

#mkdir sys

[1] mount -t sysfs sysfs /sys

[4] mount -t tmpfs mdev /dev

应该怎么做?

#vim /etc/fstab

#device         mount-point    type     options     dump   fsck

proc        /proc        proc defaults    0        0

sysfs           /sys           sysfs    default     0      0

tmpfs           /dev           tmpfs    default     0      0

 

 

[2] echo /bin/mdev > /proc/sys/kernel/hotplug

[3] mdev -s

[5] mkdir /dev/pts

[6] mount -t devpts devpts /dev/pts

怎么做呢?

#cd /etc/init.d/rcS

mount -a

mkdir /dev/pts/

mount -t devpts devpts /dev/pts

echo /sbin/mdev > /proc/sys/kernel/hotplug

mdev -s

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2015-07-23 15:20  风_行者7  阅读(529)  评论(0编辑  收藏  举报

导航