溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SHELL運(yùn)行流程是怎么樣的

發(fā)布時(shí)間:2021-12-18 09:57:46 來源:億速云 閱讀:137 作者:小新 欄目:云計(jì)算

這篇文章給大家分享的是有關(guān)SHELL運(yùn)行流程是怎么樣的的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。

一.啟動過程

shell.c是shell主函數(shù)main所在文件。因此shell的啟動可以認(rèn)為從shell.c文件開始。main函數(shù)完成的主要工作流程是包括:檢查啟動的運(yùn)行環(huán)境(是否通過sshd啟動,是否運(yùn)行于emacs環(huán)境下,是否運(yùn)行于cgywin環(huán)境下,是否是交互式shell,是否是login shell等,對系統(tǒng)進(jìn)行內(nèi)存泄露檢查,是否是受限shell),讀取配置文件(順序?yàn)?code>/etc/profile and( ~/.bash_profile OR ~/.bash_login OR ~/.profile)前面的存在不會讀后面的),設(shè)置運(yùn)行需要的全局變量的值(當(dāng)前環(huán)境變量、shell的名稱、啟動時(shí)間、輸入輸出文件描述符、語言本地化的相關(guān)設(shè)置),處理參數(shù)和選項(xiàng)(即帶有-c -s --debugger等參數(shù)和選項(xiàng)),設(shè)置參數(shù)和選項(xiàng)的值(run_shopt_alist ()函數(shù)調(diào)用shopt_setopt函數(shù)設(shè)置選項(xiàng)的值;綁定$位置參數(shù)的值),然后根據(jù)不同的啟動參數(shù)進(jìn)入以下不同分支:

  1. 如果是只進(jìn)行參數(shù)擴(kuò)展而不執(zhí)行命令,調(diào)用run_wordexp函數(shù)擴(kuò)展參數(shù),然后調(diào)用exit_shell (last_command_exit_value)函數(shù)以上次命令執(zhí)行的返回值為返回值退出。

  2. 如果是以-c參數(shù)模式啟動shell,分為兩種情況:一:如果是附帶了字符串參數(shù)作為要執(zhí)行的命令,則調(diào)用run_one_command (command_execution_string)執(zhí)行-c附帶的命令,參數(shù)command_execution_string保存-c后面附帶的字符串命令值。執(zhí)行完畢后調(diào)用exit_shell (last_command_exit_value)退出。二:如果是期待用戶輸入要執(zhí)行的命令,則跳轉(zhuǎn)到分支3。

  3. shell_initialized置為1表示shell初始化完成。調(diào)用eval.c中定義的函數(shù)reader_loop()不斷的讀取和解析用戶輸入,如果reader_loop函數(shù)返回,則調(diào)用exit_shell、(last_command_exit_value)退出shell。

二.命令解析和執(zhí)行流程

1. 主要相關(guān)文件
Eval.c

Command.h

Copy_cmd.c

Execute_cmd.c

Make_cmd.c
2. shell命令結(jié)構(gòu):

shell中用如下結(jié)構(gòu)體來表示一個(gè)命令。

typedef struct command {

  enum command_type type;   /* 命令的類型 */

  int flags;                /* 標(biāo)記位,將影響命令的執(zhí)行環(huán)境 */

  int line;                 /* 命令從哪一行開始 */

  REDIRECT *redirects;      /*關(guān)聯(lián)的重定向操作*/


  union {/*以下是一個(gè)聯(lián)合value,保存具體的“命令體”,可能是for循環(huán),case條件,

while循環(huán)等,union結(jié)構(gòu)體的特征是只有一個(gè)值是有效的,因此以下命令種類是并列的,后

面有每一種命令類型的注釋*/

    struct for_com *For;

    struct case_com *Case;

    struct while_com *While;

    struct if_com *If;

    struct connection *Connection;

    struct simple_com *Simple;

    struct function_def *Function_def;

    struct group_com *Group;

#if defined (SELECT_COMMAND)

    struct select_com *Select;

#endif

#if defined (DPAREN_ARITHMETIC)

    struct arith_com *Arith;

#endif

#if defined (COND_COMMAND)

    struct cond_com *Cond;

#endif

#if defined (ARITH_FOR_COMMAND)

    struct arith_for_com *ArithFor;

#endif

    struct subshell_com *Subshell;

    struct coproc_com *Coproc;

  } value;

} COMMAND;

其中一個(gè)很關(guān)鍵的成員是聯(lián)合union類型value,它指出了該命令的類型,也給出了保存命令具體內(nèi)容的指針。從該結(jié)構(gòu)的可選值來看,shell定義的命令共有for循環(huán)、case條件、while循環(huán)、函數(shù)定義、協(xié)同異步命令等14種。

其中,經(jīng)過對所有命令執(zhí)行路徑的分析,確定類型為simple的command是經(jīng)過命令替換后的最原子的命令操作,其余類型的命令都是由若干simple command構(gòu)成的。

在shell啟動之后,無論是進(jìn)入上面的2和3兩個(gè)分支中的哪一個(gè),最后解析命令所用到的函數(shù)都是execute_cmd.c中定義的函數(shù)。分支1不涉及到命令的解析,所以不在這里分析。

3. 分支2的第一種情況:

run_one_command (command_execution_string) 執(zhí)行的過程中調(diào)用parse_and_execute (在evalstring.c中定義)解析與執(zhí)行命令,parse_and_execute中實(shí)際調(diào)用execute_command_internal函數(shù)進(jìn)行命令的執(zhí)行。

4. 分支2的第二種情況和分支3:

reader_loop函數(shù)調(diào)用read_command函數(shù)解析命令,read_command函數(shù)調(diào)用parse_command()函數(shù)進(jìn)行語法分析,parse_command()調(diào)用語法分析器y.tab.c中的yyparse()(該函數(shù)由yyac自動生成,因此不再往函數(shù)內(nèi)部跟進(jìn)),將解析結(jié)果的命令字符串保存在全局變量GLOBAL_COMMAND中,然后執(zhí)行execute_command函數(shù)(定義在execute_cmd.c中),execute_command函數(shù)再調(diào)用execute_command_internal函數(shù)進(jìn)行命令的執(zhí)行。至此分支2和分支3的情況又合并到execute_command_internal的執(zhí)行上。

5. execute_command_internal內(nèi)部流程:

該函數(shù)是shell源碼中執(zhí)行命令的實(shí)際操作函數(shù)。他需要對作為操作參數(shù)傳入的具體命令結(jié)構(gòu)的value成員進(jìn)行分析,并針對不同的value類型,再調(diào)用具體類型的命令執(zhí)行函數(shù)進(jìn)行具體命令的解釋執(zhí)行工作。

具體來說:如果value是simple,則直接調(diào)用execute_simple_command函數(shù)進(jìn)行執(zhí)行,execute_simple_command再根據(jù)命令是內(nèi)部命令或磁盤外部命令分別調(diào)用execute_builtinexecute_disk_command來執(zhí)行,其中,execute_disk_command在執(zhí)行外部命令的時(shí)候調(diào)用make_child函數(shù)fork子進(jìn)程執(zhí)行外部命令。

如果value是其他類型,則調(diào)用對應(yīng)類型的函數(shù)進(jìn)行分支控制。舉例來說,如果是value是for_commmand,即這是一個(gè)for循環(huán)控制結(jié)構(gòu)命令,則調(diào)用execute_for_command函數(shù)。在該函數(shù)中,將枚舉每一個(gè)操作域中的元素,對其再次調(diào)用execute_command函數(shù)進(jìn)行分析。即execute_for_command這一類函數(shù)實(shí)現(xiàn)的是一個(gè)命令的展開以及流程控制以及遞歸調(diào)用execute_command的功能。

因此,從main函數(shù)啟動到命令執(zhí)行的主要流程圖可以表現(xiàn)為下圖所示:

6. 從啟動到命令解釋的函數(shù)級流程圖:

括號內(nèi)為函數(shù)定義所在的文件。

三. 變量控制

1. 主要相關(guān)文件
variables.c

variables.h
2. 重要數(shù)據(jù)結(jié)構(gòu)

BASH中主要通過變量上下文和變量兩個(gè)結(jié)構(gòu)體來描述一個(gè)變量結(jié)構(gòu)。以下分別介紹。

變量上下文:上下文又可以理解為作用域,可以比照C語言中的函數(shù)作用域,全局作用域來理解。一個(gè)上下文中的變量都是在這個(gè)上下文中可見的。
變量上下文結(jié)構(gòu)定義:

typedef struct var_context {

  char *name;           /* name如果為空則表示它存儲的是bash全局上下文,否則表示名為name的函數(shù)的局部上下文*/

  int scope;         /*上下文在調(diào)用棧中的層數(shù),0代表全局上下文 ,每深入一層函數(shù)調(diào)用scope遞增1*/

  int flags;  /*標(biāo)志位集合flags記錄該上下文是否為局部的、是否屬于函數(shù)、是否屬于內(nèi)部命令,或者是不是臨時(shí)建立的等信息*/

  struct var_context *up; /* 指向函數(shù)調(diào)用棧中上一個(gè)上下文*/

  struct var_context *down;   /*指向函數(shù)調(diào)用棧中下一個(gè)上下文*/

  HASH_TABLE *table;    /* 同一上下文中的所有變量集合hash表,即名值對 */

} VAR_CONTEXT;

描述一個(gè)變量的作用域的結(jié)構(gòu)體。一個(gè)上下文中的所有變量,存放在var_context的table成員中。

變量:bash中的變量不強(qiáng)調(diào)類型,可以認(rèn)為都是字符串。其存儲結(jié)構(gòu)如下

typedef struct variable {

  char *name;                  /*指向變量的名 */

  char *value;                  /*指向變量的值*/

  char *exportstr;            /*指向一個(gè)形如“名=值”的字符串*/

  sh_var_value_func_t *dynamic_value;    /* 如果是要返回一個(gè)動態(tài)值的函數(shù),比如$SECONDS       或者$RANDOM,則函數(shù)指針指向生成該值的函數(shù)。*/

  sh_var_assign_func_t *assign_func; /* 如果是特殊變量被賦值時(shí)需要調(diào)用的回調(diào)函數(shù),則其函數(shù)指針值保存在這里

*/

  int attributes;         /* 只讀,可見等屬性*/

  int context;                    /*記錄該上下文變量屬于可訪問的作用域內(nèi)局部變量
棧的哪一層*/

} SHELL_VAR;

由于所有變量籠統(tǒng)的由字符串來表示,因此提供了attributes屬性成員來修飾變量的特性,比如屬性可以是att_readonly表示只讀,att_array表示是數(shù)組變量,att_function表示是個(gè)函數(shù),att_integer表示是整型類變量等等。

3. 作用機(jī)理

shell程序的執(zhí)行伴隨著一個(gè)個(gè)上下文的切換,shell源碼中的變量控制也是基于這一點(diǎn)。將變量綁定于一個(gè)一個(gè)的上下文中。

舉例來說,一開始默認(rèn)存在的是全局上下文,這里稱為global,其中包含有由main函數(shù)的參數(shù)或者配置文件傳入的變量值。如果這時(shí)進(jìn)入了一個(gè)函數(shù)foo的執(zhí)行中,則foo先從全局上下文獲取要導(dǎo)出的變量,加上自己新增的變量,構(gòu)成foo的上下文局部變量,將foo的上下文壓入調(diào)用棧。這時(shí)調(diào)用??雌饋砣缦滤?。

  • 棧頂 :foo上下文(包含foo上下文的所有局部變量)

  • 棧底:global全局上下文(包含所有全局變量)

為了解釋更詳細(xì)的情況,假設(shè)在foo中又調(diào)用了fun函數(shù),則fun先從foo中獲取要導(dǎo)出的變量,加上自己新增的變量,構(gòu)成fun的上下文局部變量,然后將fun的上下文壓入調(diào)用棧的棧頂

。這是調(diào)用??雌饋砣缦滤?。

  • 棧頂 :fun上下文(包含fun上下文的所有局部變量)

  • 棧中 :foo上下文(包含foo上下文的所有局部變量)

  • 棧底:global全局上下文(包含所有全局變量)

此時(shí)假設(shè)fun函數(shù)執(zhí)行完畢,則將fun上下文從棧中pop出,局部變量全部失效。調(diào)用棧又變成如下所示。

  • 棧頂 :foo上下文(包含foo上下文的所有局部變量)

  • 棧底:global全局上下文(包含所有全局變量)

變量的查找順序:從棧頂往棧底,即如果棧頂上下文中沒有要查找的變量,則查找其在棧中的下一個(gè)上下文,如果整個(gè)調(diào)用棧查找完畢也沒有找到,則查找失敗。舉例來說,如果在棧頂上下文中有PWD變量(當(dāng)前工作路徑),就不會去查找全局的PWD變量,這保證了局部變量覆蓋的正確語義。

4. 特殊變量:

bash中定義了若干特殊變量,特殊變量的意思是在該變量被修改后需要做一些額外的連貫工作。比如表示時(shí)區(qū)的變量TZ被修改了之后需要調(diào)用tzset函數(shù)修改系統(tǒng)中相應(yīng)的時(shí)區(qū)設(shè)置。bash給這一類變量提供了一個(gè)回調(diào)函數(shù)接口,供其值發(fā)生改變的情況下來調(diào)用該回調(diào)函數(shù)。這可以類比數(shù)據(jù)庫中的觸發(fā)器機(jī)制。在bash中,特殊變量保存在一個(gè)全局?jǐn)?shù)組special_vars中。其定義如下:

struct name_and_function {

            char *name;/*變量名*/

            sh_sv_func_t *function;/*變量值修改時(shí)要觸發(fā)的回調(diào)函數(shù)的函數(shù)指針*/

};

該結(jié)構(gòu)表示一個(gè)特殊變量結(jié)構(gòu),用于生成specialvars數(shù)組?;卣{(diào)函數(shù)一般是sv變量名的命名方式。

static struct name_and_function special_vars[] = {

  { "BASH_XTRACEFD", sv_xtracefd },

#if defined (READLINE)

#  if defined (STRICT_POSIX)

  { "COLUMNS", sv_winsize },

#  endif

  { "COMP_WORDBREAKS", sv_comp_wordbreaks },

#endif

  { "FUNCNEST", sv_funcnest },

  { "GLOBIGNORE", sv_globignore },

#if defined (HISTORY)

  { "HISTCONTROL", sv_history_control },

  { "HISTFILESIZE", sv_histsize },

  { "HISTIGNORE", sv_histignore },

  { "HISTSIZE", sv_histsize },

  { "HISTTIMEFORMAT", sv_histtimefmt },

#endif

#if defined (__CYGWIN__)

  { "HOME", sv_home },

#endif

#if defined (READLINE)

  { "HOSTFILE", sv_hostfile },

#endif

  { "IFS", sv_ifs },

  { "IGNOREEOF", sv_ignoreeof },

  { "LANG", sv_locale },

  { "LC_ALL", sv_locale },

  { "LC_COLLATE", sv_locale },

  { "LC_CTYPE", sv_locale },

  { "LC_MESSAGES", sv_locale },

  { "LC_NUMERIC", sv_locale },

  { "LC_TIME", sv_locale },

#if defined (READLINE) && defined (STRICT_POSIX)

  { "LINES", sv_winsize },

#endif

  { "MAIL", sv_mail },

  { "MAILCHECK", sv_mail },

  { "MAILPATH", sv_mail },

  { "OPTERR", sv_opterr },

  { "OPTIND", sv_optind },

  { "PATH", sv_path },

  { "POSIXLY_CORRECT", sv_strict_posix },

#if defined (READLINE)

  { "TERM", sv_terminal },

  { "TERMCAP", sv_terminal },

  { "TERMINFO", sv_terminal },

#endif /* READLINE */

  { "TEXTDOMAIN", sv_locale },

  { "TEXTDOMAINDIR", sv_locale },

#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)

  { "TZ", sv_tz },

#endif

#if defined (HISTORY) && defined (BANG_HISTORY)

  { "histchars", sv_histchars },

#endif /* HISTORY && BANG_HISTORY */

  { "ignoreeof", sv_ignoreeof },

  { (char *)0, (sh_sv_func_t *)0 }

};

感謝各位的閱讀!關(guān)于“SHELL運(yùn)行流程是怎么樣的”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI