這篇文章給大家分享的是有關SHELL運行流程是怎么樣的的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
創(chuàng)新互聯(lián)專注于企業(yè)成都全網營銷、網站重做改版、梁溪網站定制設計、自適應品牌網站建設、H5響應式網站、商城系統(tǒng)網站開發(fā)、集團公司官網建設、外貿營銷網站建設、高端網站制作、響應式網頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為梁溪等各大城市提供網站開發(fā)制作服務。
shell.c
是shell主函數(shù)main所在文件。因此shell的啟動可以認為從shell.c
文件開始。main函數(shù)完成的主要工作流程是包括:檢查啟動的運行環(huán)境(是否通過sshd啟動,是否運行于emacs環(huán)境下,是否運行于cgywin環(huán)境下,是否是交互式shell,是否是login shell等,對系統(tǒng)進行內存泄露檢查,是否是受限shell),讀取配置文件(順序為/etc/profile and
( ~/.bash_profile OR ~/.bash_login OR ~/.profile
)前面的存在不會讀后面的),設置運行需要的全局變量的值(當前環(huán)境變量、shell的名稱、啟動時間、輸入輸出文件描述符、語言本地化的相關設置),處理參數(shù)和選項(即帶有-c -s --debugger
等參數(shù)和選項),設置參數(shù)和選項的值(run_shopt_alist ()
函數(shù)調用shopt_setopt
函數(shù)設置選項的值;綁定$位置參數(shù)的值),然后根據(jù)不同的啟動參數(shù)進入以下不同分支:
如果是只進行參數(shù)擴展而不執(zhí)行命令,調用run_wordexp
函數(shù)擴展參數(shù),然后調用exit_shell
(last_command_exit_value
)函數(shù)以上次命令執(zhí)行的返回值為返回值退出。
如果是以-c參數(shù)模式啟動shell,分為兩種情況:一:如果是附帶了字符串參數(shù)作為要執(zhí)行的命令,則調用run_one_command (command_execution_string)
執(zhí)行-c附帶的命令,參數(shù)command_execution_string
保存-c后面附帶的字符串命令值。執(zhí)行完畢后調用exit_shell (last_command_exit_value)
退出。二:如果是期待用戶輸入要執(zhí)行的命令,則跳轉到分支3。
將shell_initialized
置為1表示shell初始化完成。調用eval.c
中定義的函數(shù)reader_loop()
不斷的讀取和解析用戶輸入,如果reader_loop
函數(shù)返回,則調用exit_shell
、(last_command_exit_value)
退出shell。
Eval.c Command.h Copy_cmd.c Execute_cmd.c Make_cmd.c
shell中用如下結構體來表示一個命令。
typedef struct command { enum command_type type; /* 命令的類型 */ int flags; /* 標記位,將影響命令的執(zhí)行環(huán)境 */ int line; /* 命令從哪一行開始 */ REDIRECT *redirects; /*關聯(lián)的重定向操作*/ union {/*以下是一個聯(lián)合value,保存具體的“命令體”,可能是for循環(huán),case條件, while循環(huán)等,union結構體的特征是只有一個值是有效的,因此以下命令種類是并列的,后 面有每一種命令類型的注釋*/ 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;
其中一個很關鍵的成員是聯(lián)合union類型value,它指出了該命令的類型,也給出了保存命令具體內容的指針。從該結構的可選值來看,shell定義的命令共有for循環(huán)、case條件、while循環(huán)、函數(shù)定義、協(xié)同異步命令等14種。
其中,經過對所有命令執(zhí)行路徑的分析,確定類型為simple的command是經過命令替換后的最原子的命令操作,其余類型的命令都是由若干simple command構成的。
在shell啟動之后,無論是進入上面的2和3兩個分支中的哪一個,最后解析命令所用到的函數(shù)都是execute_cmd.c
中定義的函數(shù)。分支1不涉及到命令的解析,所以不在這里分析。
run_one_command (command_execution_string) 執(zhí)行的過程中調用parse_and_execute
(在evalstring.c中定義)解析與執(zhí)行命令,parse_and_execute
中實際調用execute_command_internal
函數(shù)進行命令的執(zhí)行。
reader_loop
函數(shù)調用read_command
函數(shù)解析命令,read_command
函數(shù)調用parse_command()
函數(shù)進行語法分析,parse_command()
調用語法分析器y.tab.c中的yyparse()(該函數(shù)由yyac自動生成,因此不再往函數(shù)內部跟進),將解析結果的命令字符串保存在全局變量GLOBAL_COMMAND
中,然后執(zhí)行execute_command
函數(shù)(定義在execute_cmd.c
中),execute_command
函數(shù)再調用execute_command_internal
函數(shù)進行命令的執(zhí)行。至此分支2和分支3的情況又合并到execute_command_internal
的執(zhí)行上。
該函數(shù)是shell源碼中執(zhí)行命令的實際操作函數(shù)。他需要對作為操作參數(shù)傳入的具體命令結構的value成員進行分析,并針對不同的value類型,再調用具體類型的命令執(zhí)行函數(shù)進行具體命令的解釋執(zhí)行工作。
具體來說:如果value是simple,則直接調用execute_simple_command
函數(shù)進行執(zhí)行,execute_simple_command
再根據(jù)命令是內部命令或磁盤外部命令分別調用execute_builtin
和execute_disk_command
來執(zhí)行,其中,execute_disk_comman
d在執(zhí)行外部命令的時候調用make_child
函數(shù)fork子進程執(zhí)行外部命令。
如果value是其他類型,則調用對應類型的函數(shù)進行分支控制。舉例來說,如果是value是for_commmand
,即這是一個for循環(huán)控制結構命令,則調用execute_for_command
函數(shù)。在該函數(shù)中,將枚舉每一個操作域中的元素,對其再次調用execute_command
函數(shù)進行分析。即execute_for_command
這一類函數(shù)實現(xiàn)的是一個命令的展開以及流程控制以及遞歸調用execute_command
的功能。
因此,從main函數(shù)啟動到命令執(zhí)行的主要流程圖可以表現(xiàn)為下圖所示:
括號內為函數(shù)定義所在的文件。
variables.c variables.h
BASH中主要通過變量上下文和變量兩個結構體來描述一個變量結構。以下分別介紹。
變量上下文:上下文又可以理解為作用域,可以比照C語言中的函數(shù)作用域,全局作用域來理解。一個上下文中的變量都是在這個上下文中可見的。
變量上下文結構定義:
typedef struct var_context { char *name; /* name如果為空則表示它存儲的是bash全局上下文,否則表示名為name的函數(shù)的局部上下文*/ int scope; /*上下文在調用棧中的層數(shù),0代表全局上下文 ,每深入一層函數(shù)調用scope遞增1*/ int flags; /*標志位集合flags記錄該上下文是否為局部的、是否屬于函數(shù)、是否屬于內部命令,或者是不是臨時建立的等信息*/ struct var_context *up; /* 指向函數(shù)調用棧中上一個上下文*/ struct var_context *down; /*指向函數(shù)調用棧中下一個上下文*/ HASH_TABLE *table; /* 同一上下文中的所有變量集合hash表,即名值對 */ } VAR_CONTEXT;
描述一個變量的作用域的結構體。一個上下文中的所有變量,存放在var_context的table成員中。
變量:bash中的變量不強調類型,可以認為都是字符串。其存儲結構如下
typedef struct variable { char *name; /*指向變量的名 */ char *value; /*指向變量的值*/ char *exportstr; /*指向一個形如“名=值”的字符串*/ sh_var_value_func_t *dynamic_value; /* 如果是要返回一個動態(tài)值的函數(shù),比如$SECONDS 或者$RANDOM,則函數(shù)指針指向生成該值的函數(shù)。*/ sh_var_assign_func_t *assign_func; /* 如果是特殊變量被賦值時需要調用的回調函數(shù),則其函數(shù)指針值保存在這里 */ int attributes; /* 只讀,可見等屬性*/ int context; /*記錄該上下文變量屬于可訪問的作用域內局部變量 棧的哪一層*/ } SHELL_VAR;
由于所有變量籠統(tǒng)的由字符串來表示,因此提供了attributes屬性成員來修飾變量的特性,比如屬性可以是att_readonly
表示只讀,att_array
表示是數(shù)組變量,att_function
表示是個函數(shù),att_integer
表示是整型類變量等等。
shell程序的執(zhí)行伴隨著一個個上下文的切換,shell源碼中的變量控制也是基于這一點。將變量綁定于一個一個的上下文中。
舉例來說,一開始默認存在的是全局上下文,這里稱為global,其中包含有由main函數(shù)的參數(shù)或者配置文件傳入的變量值。如果這時進入了一個函數(shù)foo的執(zhí)行中,則foo先從全局上下文獲取要導出的變量,加上自己新增的變量,構成foo的上下文局部變量,將foo的上下文壓入調用棧。這時調用??雌饋砣缦滤尽?/p>
棧頂 :foo上下文(包含foo上下文的所有局部變量)
棧底:global全局上下文(包含所有全局變量)
為了解釋更詳細的情況,假設在foo中又調用了fun函數(shù),則fun先從foo中獲取要導出的變量,加上自己新增的變量,構成fun的上下文局部變量,然后將fun的上下文壓入調用棧的棧頂
。這是調用??雌饋砣缦滤尽?/p>
棧頂 :fun上下文(包含fun上下文的所有局部變量)
棧中 :foo上下文(包含foo上下文的所有局部變量)
棧底:global全局上下文(包含所有全局變量)
此時假設fun函數(shù)執(zhí)行完畢,則將fun上下文從棧中pop出,局部變量全部失效。調用棧又變成如下所示。
棧頂 :foo上下文(包含foo上下文的所有局部變量)
棧底:global全局上下文(包含所有全局變量)
變量的查找順序:從棧頂往棧底,即如果棧頂上下文中沒有要查找的變量,則查找其在棧中的下一個上下文,如果整個調用棧查找完畢也沒有找到,則查找失敗。舉例來說,如果在棧頂上下文中有PWD變量(當前工作路徑),就不會去查找全局的PWD變量,這保證了局部變量覆蓋的正確語義。
bash中定義了若干特殊變量,特殊變量的意思是在該變量被修改后需要做一些額外的連貫工作。比如表示時區(qū)的變量TZ被修改了之后需要調用tzset函數(shù)修改系統(tǒng)中相應的時區(qū)設置。bash給這一類變量提供了一個回調函數(shù)接口,供其值發(fā)生改變的情況下來調用該回調函數(shù)。這可以類比數(shù)據(jù)庫中的觸發(fā)器機制。在bash中,特殊變量保存在一個全局數(shù)組special_vars
中。其定義如下:
struct name_and_function { char *name;/*變量名*/ sh_sv_func_t *function;/*變量值修改時要觸發(fā)的回調函數(shù)的函數(shù)指針*/ };
該結構表示一個特殊變量結構,用于生成specialvars數(shù)組。回調函數(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 } };
感謝各位的閱讀!關于“SHELL運行流程是怎么樣的”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!