# qtest 命令直譯器的實作 ###### tags: `Linux Kernel 2022 spring` ## Brief 根據 [K01:lab0](https://hackmd.io/@sysprog/linux2022-lab0#-%E4%BD%9C%E6%A5%AD%E8%A6%81%E6%B1%82) `qtest`命令直譯器實做章節 >在 `qtest` 執行後,會見到 `cmd>` 命令提示訊息,當輸入一則命令後,會經過以下函式呼叫,一路到 `parse_arg`,後者解析使用者的輸入,呼叫流程是: >>main → run_console → cmd_select → interpret_cmd → parse_args >之後,`interpret_cmda` 會在 `cmd_list` 鏈結串列中找對應的命令。`cmd_list` 會在 `main` 裡面的 `init_cmd` 跟 `console_init` 呼叫時初始化。 >找到後,`struct CELE` 這個結構體中有個 `operation成員`,儲存每個命令的結構體中的 `operation` 包裝著待測試的函式: ## 檢視位於 `qtest.c` 的 `main` function ```c while ((c = getopt(argc, argv, "hv:f:l:")) != -1) { switch (c) { case 'h': usage(argv[0]); break; case 'f': strncpy(buf, optarg, BUFSIZE); buf[BUFSIZE - 1] = '\0'; infile_name = buf; break; case 'v': { char *endptr; errno = 0; level = strtol(optarg, &endptr, 10); if (errno != 0 || endptr == optarg) { fprintf(stderr, "Invalid verbosity level\n"); exit(EXIT_FAILURE); } break; } case 'l': strncpy(lbuf, optarg, BUFSIZE); buf[BUFSIZE - 1] = '\0'; logfile_name = lbuf; break; default: printf("Unknown option '%c'\n", c); usage(argv[0]); break; } } ``` 藉由 `getopt` 函式進行==判斷程式參數==,根據 [C getopt usage](http://blog.carlcarl.me/11/c-getopt-usage/) >`getopt` 是用來判斷程式參數的函式。像是` ./a.out -s `之類的, `getopt` 能對後面的 `-s` 這類參數做判斷處理。 >`getopt` 有三個參數 `(int argc, char* argv[], const char* optsting)`,第一個和第二個就直接把 main 裡頭的參數丟進去就好了,第三個參數就是用來處理前面講到的 `-s` 的這類參數。 >舉個例子來講,像是 `optstring = "abf"` 的話,就能夠抓到這三個參數值,如: `./a.out -a` 或 `./a.out -b` 或 `./a.out -abf` 都 OK。 而如果你的 `f` 需要一個字串值,像是後面需要檔案名稱的話,就可以這樣設 `abf:`,`f: `表示 `f` 之後需要有個額外的參數, 在處理的時候可以透過 `optarg` 來使用這額外的參數。 ## 檢視位於 `console.c` 的 `init_cmd()` 函式 其中有一段寫到 ```c void init_cmd() { cmd_list = NULL; param_list = NULL; err_cnt = 0; quit_flag = false; ADD_COMMAND(help, " | Show documentation"); ADD_COMMAND(option, " [name val] | Display or set options"); ADD_COMMAND(quit, " | Exit program"); ADD_COMMAND(source, " file | Read commands from source file"); ADD_COMMAND(log, " file | Copy output to file"); ADD_COMMAND(time, " cmd arg ... | Time command execution"); add_cmd("#", do_comment_cmd, " ... | Display comment"); add_param("simulation", &simulation, "Start/Stop simulation mode", NULL); add_param("verbose", &verblevel, "Verbosity level", NULL); add_param("error", &err_limit, "Number of errors until exit", NULL); add_param("echo", &echo, "Do/don't echo commands", NULL); init_in(); init_time(&last_time); first_time = last_time; } ``` 在預設 `cmd` 中已經有了 `help`, `option` , `quit` 等等指令 ## 檢視位於`console.c` 中的 `cmd_select()` ``` /* Handle command processing in program that uses select as main control loop. * Like select, but checks whether command input either present in internal * buffer * or readable from command input. If so, that command is executed. * Same return as select. Command input file removed from readfds * * nfds should be set to the maximum file descriptor for network sockets. * If nfds == 0, this indicates that there is no pending network activity */ ``` [file descriptor](https://zh.wikipedia.org/wiki/%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6) 寫到 >在Linux系列的操作系统上,由于Linux的设计思想便是把一切设备都视作文件。因此,文件描述符为在该系列平台上进行设备相关的编程实际上提供了一个统一的方法。 ## 檢視 `console.c` 中的 `interpret_cmd` `/* Execute a command from a command line */` ```c= static bool interpret_cmd(char *cmdline) { if (quit_flag) return false; int argc; char **argv = parse_args(cmdline, &argc); bool ok = interpret_cmda(argc, argv); for (int i = 0; i < argc; i++) free_string(argv[i]); free_array(argv, argc, sizeof(char *)); return ok; } ``` 在 `line 7` 中呼叫 `parse_args` 對輸入的 `cmdline` 進行處理 ## 檢視 `console.c` 中的 `parse_args()` 在這裡對 `cmdline` 進行處理 ## 檢視 `console.c` 中的 `interpret_cmda()` 經過 `parse_args()` 回傳的 `**argv` 作為 `interpret_cmda` 參數傳入 ```c= /* Execute a command that has already been split into arguments */ static bool interpret_cmda(int argc, char *argv[]) { if (argc == 0) return true; /* Try to find matching command */ cmd_ptr next_cmd = cmd_list; bool ok = true; while (next_cmd && strcmp(argv[0], next_cmd->name) != 0) next_cmd = next_cmd->next; if (next_cmd) { ok = next_cmd->operation(argc, argv); if (!ok) record_error(); } else { report(1, "Unknown command '%s'", argv[0]); record_error(); ok = false; } return ok; } ``` 在 `line 9` 的 `while` 迴圈中,在 `cmd_list` 鍊結串列中找對應命令。`cmd_list` 會在 `main` 的 `init_cmd` 和 `console_init` 呼叫時初始化。 ==重要結構體== ```c /* Each command defined in terms of a function */ typedef bool (*cmd_function)(int argc, char *argv[]); /* Information about each command */ /* Organized as linked list in alphabetical order */ typedef struct CELE cmd_ele, *cmd_ptr; struct CELE { char *name; cmd_function operation; char *documentation; cmd_ptr next; }; ``` `struct CELE` 這個結構體中有個 `operation` 成員, ==儲存每個命令的結構體中的 `operation` 包裝著待測試的函式== ## 檢視在 `qtest.c` 中的 `console_init()` ```c static void console_init() { ADD_COMMAND(new, " | Create new queue"); ADD_COMMAND(free, " | Delete queue"); ADD_COMMAND( ih, " str [n] | Insert string str at head of queue n times. " "Generate random string(s) if str equals RAND. (default: n == 1)"); ADD_COMMAND( it, " str [n] | Insert string str at tail of queue n times. " "Generate random string(s) if str equals RAND. (default: n == 1)"); ADD_COMMAND( rh, " [str] | Remove from head of queue. Optionally compare " "to expected value str"); ADD_COMMAND( rt, " [str] | Remove from tail of queue. Optionally compare " "to expected value str"); ADD_COMMAND( rhq, " | Remove from head of queue without reporting value."); ADD_COMMAND(reverse, " | Reverse queue"); ADD_COMMAND(sort, " | Sort queue in ascending order"); ADD_COMMAND( size, " [n] | Compute queue size n times (default: n == 1)"); ADD_COMMAND(show, " | Show queue contents"); ADD_COMMAND(dm, " | Delete middle node in queue"); ADD_COMMAND( dedup, " | Delete all nodes that have duplicate string"); ADD_COMMAND(swap, " | Swap every two adjacent nodes in queue"); add_param("length", &string_length, "Maximum length of displayed string", NULL); add_param("malloc", &fail_probability, "Malloc failure probability percent", NULL); add_param("fail", &fail_limit, "Number of times allow queue operations to return false", NULL); } ``` 內含的都是在 `queue.c` 中對佇列進行的操作,故 `shuffle` 也需要照這個流程進行並準備 `do_shuffle` 的函式 ## 檢視在 `console.h` 中的 `#define ADD_COMMAND` ```c #define ADD_COMMAND(cmd, msg) add_cmd(#cmd, do_##cmd, msg) ``` ## 檢視在 `harness.c` 中的 `error_check()` ```c bool error_check() { bool e = error_occurred; error_occurred = false; return e; } ``` 一樣在 `harness.c` 中, 若其他地方有檢查到錯誤,則會呼叫 ```c error_occurred = true; ``` `error_check()` 用來判團當前的 `bool e` 是否有 `error` 發生 ## 檢視在 `harness.c` 中的 `exception_setup()` 參考 [K01:lab0](https://hackmd.io/@sysprog/linux2022-lab0#-%E8%87%AA%E5%8B%95%E6%B8%AC%E8%A9%A6%E7%A8%8B%E5%BC%8F)、[sigsetjmp](https://linux.die.net/man/3/sigsetjmp) >setjmp() and sigsetjmp() return 0 if returning directly, and nonzero when returning from longjmp(3) or siglongjmp(3) using the saved context. ```c bool exception_setup(bool limit_time) { if (sigsetjmp(env, 1)) { /* Got here from longjmp */ jmp_ready = false; if (time_limited) { alarm(0); time_limited = false; } if (error_message) report_event(MSG_ERROR, error_message); error_message = ""; return false; } /* Got here from initial call */ jmp_ready = true; if (limit_time) { alarm(time_limit); time_limited = true; } return true; } ``` 此部份更多應用延伸至 [K01:lab0 - Signal 處理和應用](https://hackmd.io/@sysprog/linux2022-lab0#-%E8%87%AA%E5%8B%95%E6%B8%AC%E8%A9%A6%E7%A8%8B%E5%BC%8F) ## 檢視 `harness.c` 中的 `set_noallocate_mode()` **definition** ```c static bool noallocate_mode = false; ``` ```c void set_noallocate_mode(bool noallocate) { noallocate_mode = noallocate; } ``` ## 撰寫新的命令 `shuffle` 觀察其他 `do_*` 系列函式,開頭都含有 ```c if (argc != 1) { report(1, "%s takes no arguments", argv[0]); return false; } bool ok = true; if (!l_meta.l) report(3, "Warning: Calling free on null queue"); error_check(); ``` 查找結構體 ```c /* List being tested */ typedef struct { struct list_head *l; /* meta data of list */ int size; } list_head_meta_t; static list_head_meta_t l_meta; ``` 故在進行佇列操作時可以呼叫 `l_mete.l` 使用 `list_head *` ### 觀察其他`do_*`系列函式 有設定 `bool ok` 的函式 - `do_new` - `do_ih` - `do_it` - `do_remove` - `do_rhq` - `do_dedup` - `do_size` - `do_sort` - `do_dm` 沒有設定 `bool ok` 的函式 - `do_reverse` - `do_swap` - `do_show` 觀察到 `ok` 都用在佇列長度有更改或者是需要判斷是否為空佇列,而在 `reverse` ,`swap` ,中不需要 `ok` 的原因是佇列長度不會改變且若為空佇列在 `q_reverse` 以及 `q_swap` 函式中會有相對應的 `if(!head)` 作處理,因此判斷在 `do_shuffle` 也不需要有 `ok`,且 `q_shuffle` 應為 `void`