天天看点

busybox 初始化

下载busybox 1.00 http://busybox.net/downloads/busybox-1.00.tar.bz2

# tar jxvf busybox-1.00.tar.bz2

# cd busybox-1.00

# make defconfig

# make menuconfig 配置

# make

# make install

需要cp到ramdisk的文件在_install目录中

下面主要分析一下内核到busybox的启动流程

在kernel/init/main.c的init函数中有代码

 if (execute_command)

  execve(execute_command,argv_init,envp_init);

 execve("/sbin/init",argv_init,envp_init);

一般启动命令行会给出 init=/linuxrc 这个参数,于是就有了

 execute_command = "/linuxrc"

busybox中_install目录下的 linuxrc 是busybox的一个软链接

而在我的ramdisk中已经被替换成一个shell脚本,其中的代码是

#!/bin/sh

exec /sbin/init

所以其实这个linuxrc基本没什么用处,我就可以把

if (execute_command)  execve(execute_command,argv_init,envp_init);

这句代码注释掉,于是内核就直接运行/sbin/init了

/sbin/init也是busybox的软链接,所以接着下来就要看busybox的源代码了

入口函数main在busybox-1.00/applets/busybox.c中

int main(int argc, char **argv)

{

 const char *s;

 bb_applet_name = argv[0];

 if (bb_applet_name[0] == '-') bb_applet_name++;

 for (s = bb_applet_name; *s != '/0';)

 {

  if (*s++ == '/')   bb_applet_name = s;

 }

#ifdef CONFIG_LOCALE_SUPPORT

#ifdef CONFIG_INIT

 if(getpid()!=1) 

#endif

 { setlocale(LC_ALL, ""); }

#endif

 run_applet_by_name(bb_applet_name, argc, argv);

 bb_error_msg_and_die("applet not found");

}

bb_applet_name = argv[0]

对于execve("/sbin/init",argv_init,envp_init)

bb_applet_name = argv_init[0] = "init"

在进入shell后,执行命令 ls -l 同样也会调用main函数

这是 argv[0]="ls"  argv[1]="-l"

接着是run_applet_by_name(bb_applet_name, argc, argv)

该函数的作用是找到bb_applet_name对应的主函数并执行,主要的代码如下

  if ((applet_using = find_applet_by_name (name)) != NULL)

  {

 bb_applet_name = applet_using->name;

 exit ((*(applet_using->main)) (argc, argv));

  }

 struct BB_applet * find_applet_by_name (const char *name)

 {

     return bsearch (name, applets, NUM_APPLETS,

  sizeof (struct BB_applet), applet_name_compare);

 }

可以看出find_applet_by_name从applets结构中寻找name对应的项。

  const struct BB_applet applets[] = {

  #define APPLET(a,b,c,d) {#a,b,c,d},

  #define APPLET_NOUSAGE(a,b,c,d) {a,b,c,d},

  #define APPLET_ODDNAME(a,b,c,d,e) {a,b,c,d},

#ifdef CONFIG_TEST

 APPLET_NOUSAGE("[", test_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER)

#endif

#ifdef CONFIG_ADDGROUP

 APPLET(addgroup, addgroup_main, _BB_DIR_BIN, _BB_SUID_NEVER)

#endif

#ifdef CONFIG_ADDUSER

 APPLET(adduser, adduser_main, _BB_DIR_BIN, _BB_SUID_NEVER)

#endif

 。。。。}

这样看来如果想要在busybox中添加相应的命令,就只需在这里添加一项并提供

相应的主函数即可。找到我关注的是init项

#ifdef CONFIG_INIT

 APPLET(init, init_main, _BB_DIR_SBIN, _BB_SUID_NEVER)

#endif

于是init_main函数被执行。

在busybox-1.00/init/init.c中找到init_main函数,分析一下其中的关键代码

parse_inittab();

run_actions(SYSINIT);

run_actions(ASKFIRST);

主要是这三个调用

parse_inittab函数分析inittab文件并执行其中的命令

方便一点可以把删除inittab文件,而我这里也是没有inittab文件的。

以下是没有inittab文件执行的代码

 file = fopen(INITTAB, "r");

 if (file == NULL) {

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

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

#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__)

  new_init_action(SHUTDOWN, "/sbin/swapoff -a", "");

#endif

  new_init_action(RESTART, "/sbin/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;

#ifdef CONFIG_FEATURE_USE_INITTAB

 }

只是调用了很多的new_init_action函数,这个函数其实是把这些init_action添加到

以init_action_list为头的链表里,这样便可以通过run_actions函数来调用。

run_actions(SYSINIT) 就会执行 INIT_SCRIPT 命令

#define INIT_SCRIPT  "/etc/init.d/rcS" 

于是就执行了/etc/init.d/rcS这个shell脚本,需要看一下run_actions这个函数

static void run_actions(int action)

{

  struct init_action *a, *tmp;

  for (a = init_action_list; a; a = tmp)

  {

    tmp = a->next;

    if (a->action == action)

    {

 if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART))

 {

    waitfor(a);

    delete_init_action(a);

  }

 else if (a->action & ONCE)

 {

   run(a);

   delete_init_action(a);

 }

 else if (a->action & (RESPAWN | ASKFIRST))

 {

   if (a->pid == 0)  a->pid = run(a);

 }

     }

  }

}

对于SYSINIT运行的是waitfor(a),而对于ASKFIRST运行的是run(a)

看waitfor函数的代码知道其实它也调用了run(a)建了一个子进程,只是

父进程会等待子进程运行结束。run函数是比较长的,只取其中的关键代码看看

strcpy(buf, a->command);

s = buf;

for (tmpCmd = buf, i = 0; (tmpCmd = strsep(&s, " /t")) != NULL;)

{

 if (*tmpCmd != '/0')

 {

  cmd[i] = tmpCmd;

  i++;

 }

}

cmd[i] = NULL;

cmdpath = cmd[0];

if (*cmdpath == '-') {

 ++cmdpath;

 s = bb_get_last_path_component(cmdpath);

 if ((cmd[0] = malloc(strlen(s) + 2)) == NULL) {

  message(LOG | CONSOLE, bb_msg_memory_exhausted);

  cmd[0] = cmdpath;

 } else {

  cmd[0][0] = '-';

  strcpy(cmd[0] + 1, s);

  }

}

execv(cmdpath, cmd);

其实就是处理a->command来获的要执行的文件路径和argv参数,调用execv函数执行它

对于run_actions(SYSINIT) 其a->command = "/etc/init.d/rcS"

处理后cmdpath = "/etc/init.d/rcS" ,cmd[0] = "/etc/init.d/rcS"

run_actions(ASKFIRST) 其a->command = bb_default_login_shell

const char * const bb_default_login_shell = LIBBB_DEFAULT_LOGIN_SHELL;

#define LIBBB_DEFAULT_LOGIN_SHELL      "-/bin/sh"

a->command = "-/bin/sh"

处理后cmdpath = "/bin/sh" ,cmd[0] = "-sh"

由于run_actions(SYSINIT)会一直等待子进程执行完,

/etc/init.d/rcS就执行完了,基本的初始化也完成了,那么就可以进入shell了

这里就是execv(cmdpath, cmd)去执行shell的

一般会要求按回车才进shell,可以把run函数中以下注释掉来取消回车直接进入

if (a->action & ASKFIRST)

{

 char c;

 messageD(LOG, "Waiting for enter to start '%s'(pid %d, terminal %s)/n",

  cmdpath, getpid(), a->terminal);

 bb_full_write(1, press_enter, sizeof(press_enter) - 1);

 while(read(0, &c, 1) == 1 && c != '/n')

 ;

}

/bin/sh同样是busybox的软链接,同样去执行main函数

这是的argv[0] = "-sh" 去掉-后,用"sh"去查找函数

#if defined(CONFIG_FEATURE_SH_IS_ASH) && defined(CONFIG_ASH)

 APPLET_NOUSAGE("sh", ash_main, _BB_DIR_BIN, _BB_SUID_NEVER)

#elif defined(CONFIG_FEATURE_SH_IS_HUSH) && defined(CONFIG_HUSH)

 APPLET_NOUSAGE("sh", hush_main, _BB_DIR_BIN, _BB_SUID_NEVER)

#elif defined(CONFIG_FEATURE_SH_IS_LASH) && defined(CONFIG_LASH)

 APPLET_NOUSAGE("sh", lash_main, _BB_DIR_BIN, _BB_SUID_NEVER)

#elif defined(CONFIG_FEATURE_SH_IS_MSH) && defined(CONFIG_MSH)

 APPLET_NOUSAGE("sh", msh_main, _BB_DIR_BIN, _BB_SUID_NEVER)

#endif

busybox中有四种shell,在配置的时候注意下,一般选择默认的为ash

这样执行的函数就是ash_main,ash_main怎样进入命令行,怎样接收命令,下次再分析了