1. 程式人生 > >busybox(一)淺析

busybox(一)淺析


title: busybox(一)淺析
tag: arm
date: 2018-11-13 23:02:33
---

busybox淺析

原始碼包在busybox-1.7.0.tar.bz2,一個命令對應著一個c檔案,執行init命令,則是有init.c,有函式init_main

int init_main(int argc, char **argv);

最終的目的是啟動客戶的應用程式,需要指定具體的環境

1. 配置檔案讀取
2. 解析配置檔案
3. 執行使用者程式

help

相關的幫助可以搜尋下/examples下的檔案,比如搜尋inittab,裡面有相關說明

#define SYSINIT     0x001       //執行一次等待結束後從連結串列刪除
#define RESPAWN     0x002       //while迴圈執行
#define ASKFIRST    0x004       //while迴圈執行,會列印資訊,等待回車
#define WAIT        0x008       //執行一次等待結束後從連結串列刪除,在SYSINIT後
#define ONCE        0x010       //與SYSINIT 區別在於不等待其執行結束
#define CTRLALTDEL  0x020       //輸入訊號後執行
#define SHUTDOWN    0x040       //輸入訊號後執行
#define RESTART     0x080       //輸入訊號後執行

流程圖

mark

引入

init_main函式入口分析,Linux 是按照run_init_process("/sbin/init");形式呼叫,沒有傳遞引數,所以執行else分支,解析配置表

if (argc > 1
 && (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))
) {
    /* Start a shell on console */
    new_init_action(RESPAWN, bb_default_login_shell, "");
} else {
    /* Not in single user mode -- see what inittab says */

    /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
     * then parse_inittab() simply adds in some default
     * actions(i.e., runs INIT_SCRIPT and then starts a pair
     * of "askfirst" shells */
    parse_inittab();
}

讀取inittab

parse_inittab();讀取inittab配置表,可以搜尋下example下檢視例子幫助,查閱如下格式

Format for each entry: <id>:<runlevels>:<action>:<process>
<id>: WARNING: This field has a non-traditional meaning for BusyBox init!
<runlevels>: The runlevels field is completely ignored.
<action>: Valid actions include: sysinit, respawn, askfirst, wait, once,restart, ctrlaltdel, and shutdown.
<process>: Specifies the process to be executed and it's command line.
標識 作用
id 自動加上/dev/的字首,用作終端,stdin,stdout,stderr:printf,scanf,err 可以省略
runlevels 可以忽略
action 指示何止執行
process 應用程式或者可執行指令碼

最終執行new_init_action執行指令碼程式

建立執行指令碼連結串列

for (a = actions; a->name != 0; a++) {
    if (strcmp(a->name, action) == 0) {
        if (*id != '\0') {
            if (strncmp(id, "/dev/", 5) == 0)   //這裡為id加上/dev/的字首
                id += 5;
            strcpy(tmpConsole, "/dev/");
            safe_strncpy(tmpConsole + 5, id,
                sizeof(tmpConsole) - 5);
            id = tmpConsole;
        }
        new_init_action(a->action, command, id);
        break;
    }
}

當不存在這個配置表的時候也會有一個預設配置檔案,這裡以預設的其中一個指令碼解析

new_init_action(ASKFIRST, bb_default_login_shell, VC_2);

# define VC_2 "/dev/tty2"
#define ASKFIRST    0x004
const char bb_default_login_shell[] ALIGN1 = LIBBB_DEFAULT_LOGIN_SHELL;
#define LIBBB_DEFAULT_LOGIN_SHELL      "-/bin/sh"

也就是最終執行

new_init_action(ASKFIRST, "-/bin/sh", "/dev/tty2");
ASKFIRST 執行的時機
-/bin/sh 指令碼程式
/dev/tty2 id終端,加上了/dev/,符合上述描述

分析下new_init_action函式內部,

  1. 建立 init_action結構,包含inittab中的資訊
  2. 加入到init_action_list連結串列中
for (a = last = init_action_list; a; a = a->next) {
    /* don't enter action if it's already in the list,
     * but do overwrite existing actions */
    if ((strcmp(a->command, command) == 0)
     && (strcmp(a->terminal, cons) == 0)
    ) {
        a->action = action;
        return;
    }
    last = a;
}

struct init_action {
    struct init_action *next;
    int action;     
    pid_t pid;                              //對應程序id, process id
    char command[INIT_BUFFS_SIZE];          //對應應用程式 
    char terminal[CONSOLE_NAME_SIZE];       //對應終端
};

由此,可以理解當配置檔案不存在的時候,會去建立配置表

    if (file == NULL) {
        /* No inittab file -- set up some default behavior */
        /* 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, "");
        return;
    }
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>↓
建立類似的inittatb             ↓
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>↓
::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

執行指令碼

指令碼有多種型別,不同型別執行方式與時機不同

#define SYSINIT     0x001       //執行一次等待結束後從連結串列刪除
#define RESPAWN     0x002       //while迴圈執行
#define ASKFIRST    0x004       //while迴圈執行,會列印資訊,等待回車
#define WAIT        0x008       //執行一次等待結束後從連結串列刪除,在SYSINIT後
#define ONCE        0x010       //與SYSINIT 區別在於不等待其執行結束
#define CTRLALTDEL  0x020       //輸入訊號後執行
#define SHUTDOWN    0x040       //輸入訊號後執行
#define RESTART     0x080       //輸入訊號後執行
run_actions(SYSINIT);
                waitfor(a, 0);//執行a,等待執行結束
                    run(a);//執行建立process子程序
                     waitpid(runpid, &status, 0);
                delete_init_action(a);//刪除連結串列
    /* Next run anything that wants to block */
    run_actions(WAIT);
                    waitfor(a, 0);//執行a,等待執行結束
                    run(a);//執行建立process子程序
                     waitpid(runpid, &status, 0);
                delete_init_action(a);//刪除連結串列
    /* Next run anything to be run only once */
    run_actions(ONCE);
                    run(a);
                    delete_init_action(a);
        /* Now run the looping stuff for the rest of forever */
    while (1) {//重新執行pid已經退出的子程序
        run_actions(RESPAWN);
                if (a->pid == 0) {  //預設pid為0
                    a->pid = run(a);}
                    
        run_actions(ASKFIRST);
                if (a->pid == 0) {
                    a->pid = run(a);}
                    //列印"\nPlease press Enter to activate this console. ";,
                    //等待輸入回車
                    //建立子程序
        wpid = wait(NULL);//等待子程序退出
        while (wpid > 0) {
                a->pid--;//推出後pid=0
            }
        }
    }

小結

  1. 開啟終端 dev/console
  2. 開啟dev/null,用作不設定終端id的時候的定位
  3. 讀取配置檔案etc/inittab,需要存在配置檔案的可執行程式或者指令碼
  4. 執行指令碼

所以一個最小的根檔案系統必備的一些資源

dev/console
dev/null 
sbin/init-------------busybox提供,至少需要這個應用程式,這是linux啟動的第一個應用程式
etc/inittab-----------配置檔案,定義了一些應用程式 
配置檔案制定的應用程式----配置檔案指定的應用程式
C庫--------------------應用程式的C庫