# 2021q1 期末專題 (quiz7A - shell) contributed by < `robertlin0401` > ###### tags: `linux2021` > [2021 年第 7 週測驗題:測驗 1](https://hackmd.io/@sysprog/H1l5yStSu) > [GitHub](https://github.com/robertlin0401/Linux-Kernel-Internals-quiz7) --- ## 專題簡介 :::warning TODO: 此處補上本程式的功能、設計考量,和實作議題,在實際逐行分析程式碼之前。 一如所有事物的解說,應從高階總覽到個別子系統模組的順序。 :notes: jserv ::: * 此實作旨在模擬 Unix shell 的運作方式,並且透過解讀此實作的過程去理解 Unix shell 的運作原理是此專題的一大目標 * 目前支援功能包含 * 執行簡單的命令,如結構為 `{command} {argument list}` 的命令 * 命令之間可使用 pipeline * 檔案重新導向 * 未支援的功能包含但不限於 * `>>` (append), `2>` (導向到 stderr), `&` (背景執行) 等語法 * 上述語法的支援將在後續嘗試進行擴充 <!-- * <span class="todo">TBC</span> --> ## 程式運作原理 ### 相關函式說明 * 建立命令列起始符號 ```c static void prompt() { write(2, "$ ", 2); } ``` * 對應的命令列介面如下 ```shell > ./picosh $ _ ``` * 錯誤處理 ```c static void fatal(int retval, int leave) { if (retval >= 0) return; write(2, "?\n", 2); if (leave) exit(1); } ``` * `retval` 為系統呼叫之回傳值,若為負值則表示有錯誤,須進行處理 * 此處系統呼叫包含 `chdir`, `fork`, `open`, `creat`, `execvp` * 錯誤處理如下 * 印出 `?` 符號告知使用者錯誤發生 * 判斷錯誤類型,在需要之情況終止該行程,至於何謂需要之情況將於[後續段落](#執行命令1)中說明 * <span id="特殊字元符號之類型判斷">特殊字元符號之類型判斷</span> ```c static inline int is_delim(int c) { return c == 0 || c == '|'; } static inline int is_redir(int c) { return c == '>' || c == '<'; } static inline int is_blank(int c) { return c == ' ' || c == '\t' || c == '\n'; } static int is_special(int c) { return is_delim(c) || is_redir(c) || is_blank(c); } ``` ### main 函式說明 ```c int main() { while (1) { prompt(); char buf[512] = {0}; /* input buffer */ char *c = buf; if (!fgets(c + 1, sizeof(buf) - 1, stdin)) exit(0); for (; *++c;) /* skip to end of line */ ; run(c, 0); } return 0; } ``` * 使用一迴圈,重複下列工作 1. 呼叫 `prompt` 函式生成命令列起始符號 2. 讀取一行命令 3. 呼叫 `run` 函式解析並執行命令 * 讀取之命令將存入長度為 512 的字元陣列 `buf` 內,值得一提的是 * 最大讀取長度是 511 個字元,而非 512 個 * 儲存的方式為留一格空格在最前方,從第二格(`buf[1]` 的位置)開始儲存 * 若輸入命令為 `echo hello world`,則儲存方式如下圖所示 ```graphviz digraph { graph [rankdir=LR]; c [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">c</td></tr> <tr><td port="start"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="358"></td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td port="end"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="60"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; content [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="head"></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">c</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">w</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">r</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">d</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="tail"></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="40">...</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; c:start -> content:head; { rank="same"; c; content; }; } ``` * 呼叫 `run` 函式執行前,會將指向 `buf` 開頭之指標移到該命令之結尾字元的下一個位址 * 因為解析命令時將會從命令字串的尾端開始解析,詳見[後續段落](#命令字串解析)說明 * 同樣以 `echo hello world` 為例,其結果如下圖所示 ```graphviz digraph { graph [rankdir=LR]; c [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td port="start"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="350"></td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">c</td></tr> <tr><td port="end"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="60"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; content [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="head"></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">c</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">w</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">r</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">d</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="tail"></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="40">...</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; c:end -> content:tail; { rank="same"; c; content; }; } ``` ### 執行命令(run 函式)說明 以下分為數個部分說明 * 參數 ```c static void run(char *c, int t) ``` * c - 指向一命令字串之結尾字元的下一個位址的指標,詳見[上一段](#main-函式說明) * t - 用於指定輸出位置之 file descriptor,若為 0 則表示使用 stdout * <span id="命令字串解析">命令字串解析</span> ```c char *redir_stdin = NULL, *redir_stdout = NULL; int pipefds[2] = {0, 0}, outfd = 0; char *v[99] = {0}; char **u = &v[98]; /* end of words */ for (;;) { c--; if (is_delim(*c)) /* if NULL(start of string) or pipe */ break; if (!is_special(*c)) { /* Copy word of regular chars into previous u */ u--; int count = 0; while (!is_special(*c)) { count++; c--; } *u = strndup(c + 1, count); } if (is_redir(*c)) { /* if < or > */ if (*c == '<') redir_stdin = *u; else redir_stdout = *u; if ((u - v) != 98) u++; } } if ((u - v) == 98) /* empty input */ return; ``` * `v` 為長度 99 的字元指標的陣列,`u` 則為指向陣列 `v` 的最後一個空間(`v[98]` 的位置)的指標 * 使用迴圈從命令的尾端開始解析字元,若遇到下列 3 種情況則進行對應處理,各情況所代表之意義請見[這裡](#特殊字元符號之類型判斷) 1. `is_delim` 成立 * 表示解析完畢或是遇到 pipe 符號(`|`),此時的處理為離開迴圈 * 故命令字串儲存的方式為留一格空格在最前方,將之作為命令解析完畢的判斷依據 * 且由此可知,<span class="highlight">最後的命令</span>將優先解析,並且也是優先開始執行的,但透過 pipe 的機制,最終會是<span class="highlight">最前的命令</span>率先執行完畢 :::info 受限於 pipe 的機制,若前方的命令未執行完成並產生輸出,後方的命令無法完成執行 :::warning 節錄自 [pipe(7) — Linux manual page](https://man7.org/linux/man-pages/man7/pipe.7.html): > If a process attempts to read from an empty pipe, then read(2) will block until data is available. ::: 2. `is_special` 不成立 * 表示解析到命令內容的一般字元,此時的處理為將此字元所屬的整段文字的複本的指標,存入當前 `u` 所指向的位置的前一個位置 * 承上,以 `echo hello world` 為例,則處理過程如下圖所示 ```graphviz digraph { graph [rankdir=LR]; subgraph cluster_level2 { label="after "; u2 [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="48"></td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">u</td></tr> <tr><td port="ptr"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="60"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; v2 [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="50">v</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">0</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="40">...</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="end"></td></tr> <tr><td width="20" port="echo"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20" port="hello"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20" port="world"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="start"></td></tr> <tr><td width="20">0</td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="60"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; world [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="190"></td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="30" port="echo"></td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="26" port="hello"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="world"></td></tr> <tr><td width="20">w</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">r</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">d</td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="80"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; hello [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="163"></td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="50" port="echo"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="hello"></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="95"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; echo [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="115"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="echo"></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">c</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="100"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; c2 [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">c</td></tr> <tr><td port="end"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="358"></td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td port="start"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="60"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; content2 [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="head"></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="echo"></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">c</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="hello"></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="world"></td></tr> <tr><td width="20">w</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">r</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">d</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="tail"></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="40">...</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; c2:end -> content2:head [arrowhead=normal; constraint=false]; u2:ptr -> v2:end [arrowhead=normal; constraint=false]; v2:echo -> world:echo [arrowhead=none; constraint=false]; world:echo -> hello:echo [arrowhead=none; constraint=false]; hello:echo -> echo:echo [arrowhead=normal; constraint=false]; v2:hello -> world:hello [arrowhead=none; constraint=false]; world:hello-> hello:hello [arrowhead=normal; constraint=false]; v2:world -> world:world [arrowhead=normal; constraint=false]; } subgraph cluster_level1 { label="before "; u1 [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="180"></td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">u</td></tr> <tr><td port="ptr"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="60"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; v1 [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="50">v</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">0</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="40">...</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="end"></td></tr> <tr><td width="20" port="echo">0</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20" port="hello">0</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20" port="world">0</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="start"></td></tr> <tr><td width="20">0</td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="60"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; c1 [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td port="end"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="350"></td></tr> <tr><td></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">c</td></tr> <tr><td port="start"></td></tr> </table> </td> <td> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="60"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; content1 [shape=none; margin="0"; label=< <table border="0" cellspacing="0" cellpadding="0"> <tr> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="head"></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="echo"></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">c</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="hello"></td></tr> <tr><td width="20">h</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">e</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="world"></td></tr> <tr><td width="20">w</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">o</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">r</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">l</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20">d</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td port="tail"></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="40">...</td></tr> <tr><td></td></tr> </table> </td> <td border="1"> <table border="0" cellspacing="0"> <tr><td></td></tr> <tr><td width="20"></td></tr> <tr><td></td></tr> </table> </td> </tr> </table>> ]; c1:start -> content1:tail [constraint=false]; u1:ptr -> v1:start [constraint=false]; } } ``` * 值得注意的是,根據上述處理可知,陣列 `v` 的最後一個位置之值保留為 0,不會被更動,作為一命令之 argument list 的結尾標記,詳見[這裡](#execvp說明) 3. `is_redir` 成立 * 表示解析到重新導向符號(`<` 或 `>`),此時的處理為記錄欲重新導向的路徑 * 因為是由右至左解析命令,故解析到重新導向符號時,當前的 `u` 所指向的字元指標所指向的字串即為欲重新導向的路徑 * 將其指標存入 `redir_stdin` 或 `resir_stdout` 後,便執行 `u++`,使後續解析的命令將其覆蓋掉,可視為將其指標從陣列 `v` 中移除 * 解析完成後,判斷 `v`(即指向 `v[0]` 的指標)與 `u`(原本為指向 `v[98]` 的指標)之差,若正好為 98 則表示 `u` 在該次解析前後一致,故可將其視為一個空命令 * <span id="執行命令1">執行內建命令</span> 「內建命令」(bultin) 指不需要透過 execve 系統呼叫執行程式而獲得功能的操作。 ```c if (!strcmp(*u, "cd")) { fatal(chdir(u[1]), 0); return; } ``` * 若目前 `u` 所指向的字元指標所指向的字串為 `cd`,則 `u[1]` 便應是欲切換的路徑 * `cd` 為一 builtin command,故直接使用系統呼叫 `chdir(u[1])` 來實現 :::warning 節錄自 GNU [bash 手冊內容](https://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html): > Builtin commands are contained within the shell itself. When the name of a builtin command is used as the first word of a simple command, the shell executes the command directly, without invoking another program. ::: * 正因為無新增子行程來執行其他程式,故當此系統呼叫發生錯誤時,無需將當前行程終止,至於其他命令的作法之說明詳見[後續段落](#執行命令2) * pipe 處理 ```c if (*c) { pipe(pipefds); outfd = pipefds[1]; /* write end of the pipe */ } ``` * 若離開解析命令的迴圈後,指標 `c` 所指向的空間仍有內容,則代表是因為遇到 pipe 符號而離開迴圈的 * 故使用 `pipe` 系統呼叫來建立一個 pipe,並將其 read、write end 的 file descriptors 分別記錄在整數變數 `pipefds[0]` 與 `pipefds[1]` 中 * <span id="執行命令2">執行命令:其他命令</span> ```c pid_t pid = fork(); if (pid) { /* parent or error */ fatal(pid, 1); if (outfd) { run(c, outfd); /* parse the rest of the cmdline */ close(outfd); /* close output fd */ close(pipefds[0]); /* close read end of the pipe */ } wait(0); return; } if (outfd) { dup2(pipefds[0], 0); /* dup read fd to stdin */ close(pipefds[0]); /* close read fd */ close(outfd); /* close output */ } if (redir_stdin) { /* replace stdin with redir_stdin */ close(0); fatal(open(redir_stdin, 0), 1); } if (t) { dup2(t, 1); /* replace stdout with t */ close(t); } if (redir_stdout) { /* replace stdout with redir */ close(1); fatal(creat(redir_stdout, 438), 1); } fatal(execvp(*u, u), 1); ``` * 使用 `fork` 系統呼叫建立子行程 (process),負責執行該輪解析出來的命令,此作法為模擬非 builtin command 的行為。父行程則在命令未解析完的狀況(即存在 pipe 的狀況),遞迴呼叫 `run` 來繼續解析 * 父行程內容詳述 * 若建立子行程失敗,則程式無法正常運作,故將此行程終止 * 遞迴呼叫 `run` 來繼續解析命令,須將先前建立的 pipe 的 write end file descriptor 傳入,使前方命令的輸出寫入 pipe 中的 buffer,作為後方命令的輸入 * 子行程內容詳述 * 首先,在有需要的情況下,進行一系列輸入、輸出重新導向,且若在開檔的系統呼叫過程出現錯誤,皆須將目前行程終止。此部分應考慮以下情況 1. 存在 pipe,則使用 `dup2` 系統呼叫,將原本代表 stdin 的 file descriptor,改寫成代表先前建立的 pipe 的 read end,使其能夠讀取前方命令的輸出 :::info ```c int dup2(int oldfd, int newfd); ``` :::warning 節錄自 [dup(2) - Linux manual page](https://man7.org/linux/man-pages/man2/dup.2.html): > The file descriptor `newfd` is adjusted so that it now refers to the same open file description as `oldfd`. ::: 2. 命令內容有輸入重新導向符號(`<`),則關閉 file decriptor 為 0 者(stdin 或先前建立的 pipe 的 read end),並使用 `open` 系統呼叫開啟指定檔案,使其 file descriptor 成為 0,因此被當作新的輸入位置 3. 呼叫 `run` 時有傳入 file descriptor,則將 stdout 以 `dup2` 系統呼叫做更改 4. 命令內容有輸出重新導向符號(`>`),則關閉 file decriptor 為 1 者(stdout、先前建立的 pipe 的 write end 或其他檔案位置),並使用 `creat` 系統呼叫開啟指定檔案。其中,`438` 為檔案 mode,轉換為二進位為 `110110110`,表示對所有使用者而言此檔案可讀可寫 :::warning 用八進位 (對! C 語言內建八進位就為了檔案權限的描述) 來解讀上述數值的作用。見: [CHMOD Calculator](https://clickcalculators.com/chmod/CHMOD~438~r---wxrwx) :notes: jserv ::: :::info 更新: * `438` 為檔案 mode(以十進位表示),將其轉換為八進位為 `666` * 八進位的三個位數從左至右分別代表 User(檔案擁有者)、Group(擁有者所屬群組)與 Other(其他使用者)對此檔案的操作權限 * 以單獨一個位數來看,其由 3 個 bits 組成,每個 bit 由右至左分別代表該類別的使用者是否擁有讀取、寫入與執行的權限 * 雖然也能夠以二進位來解讀,但<span class="highlight">無法使用二進位來操作</span> ::: * 若重新導向部分未出錯,則接著使用 `execvp` 系統呼叫執行當前指令,而若系統呼叫出錯亦將此行程終止 :::info <span id="execvp說明"></span> ```c int execvp(const char *file, char *const argv[]); ``` :::warning 節錄自 [exec(3) — Linux manual page](https://man7.org/linux/man-pages/man3/exec.3.html): > The `char *const argv[]` argument is an array of pointers to null-terminated strings that represent the argument list available to the new program. > The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a null pointer. --- ## 提升開發便利性之修改 ### 支援輸入重新導向自檔案 * 每當修改程式碼,需要將所有命令輸入一輪進行測試,以確認程式是按照預期來運作 * 而每次手動輸入所有指令相當沒有效率,故將其寫成檔案 [test.cmd](https://github.com/robertlin0401/Linux-Kernel-Internals-quiz7/blob/main/test.cmd),並使用輸入重新導向的方式輸入 * 為此將程式碼做少量更動,使用 `isatty(0)` 判斷是否有做輸入重新導向,若有,則無須輸出如命令列起始符號等資訊至螢幕 ```dif - prompt(); + if (isatty(0)) + prompt(); ``` ### 支援輸出位置選擇 * 使用 argument list 來指定輸出位置的類別,格式如下 ```shell > ./picosh {output_class} ``` * 該引數可寫在輸入、輸出重新導向之前或之後 * 舉例而言,以下格式皆正確 ```shell > ./picosh < {input_dir} {output_class} > ./picosh {output_class} < {input_dir} ``` * 引數 `output_class` 說明 * 0 表示輸出至 stdout,1 表示將輸出寫入 output.log 檔案 * 預設值為 0 * 實作如下 ```c int t = open("./output.log", O_WRONLY | O_TRUNC | O_CREAT); int out_class = 0; if (argc > 1 && !(argc % 2)) { for (int i = 0; i < argc / 2; ++i) { if (!strncmp(argv[1 + 2 * i], "<", 1) || !strncmp(argv[1 + 2 * i], ">", 1)) { continue; } out_class = atoi(argv[1 + 2 * i]); break; } } ... run(c, out_class ? t : 0); ``` ### 引入 linenoise * 透過 linenoise 可提供 `tab` 鍵自動補其功能與 `↑`、`↓` 鍵瀏覽命令輸入歷史紀錄功能 * 其目的性有二 * 增進開發過程中的測試效率 * 模擬如 Bash 的介面操作方式 <!-- * <span class="todo">進行中...</span> --> <!-- customized style --> <style> .highlight { background-color: yellow; } .todo { background-color: red; } </style>