天天看點

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怎樣進入指令行,怎樣接收指令,下次再分析了