1. Shell运行的基本逻辑
Shell一般按照如下的步骤运行:等待状态-获取命令行输入-处理-创建子进程-等待子进程运行结束-等待状态。
分别来说:
- 获取命令行输入可以使用
readline()
,scanf()
之类的函数。
- 处理输入,首先要做的是将输入按照分隔符分开,得到一个二维数组。接着,我们要对获得的命令进行判断,判断其是否为内置命令,如果为内置命令,需要执行我们自己编写函数,如果不是内置命令,就要通过
fork()
创建一个子进程,在子进程中执行非内置命令,父进程等待子进程的结束。
2. 获取输入
读取输入的函数大概如下,一个字符一个字符读取,方便我们控制对应的行为,比如读取到EOF就直接返回等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int ReadLine(char* buffer) { int buffer_pivot = 0;
while(1) { if(buffer_pivot == BUFFER_SIZE) { printf("The expression is too long to process.\n"); return ERR_EXPR_TOO_LONG; }
char current_char = getchar(); if(current_char == '\n') { buffer[buffer_pivot++] = '\0'; break; } else if(current_char == EOF){ return INPUT_EOF; } else { buffer[buffer_pivot++] = current_char; } }
return strlen(buffer); }
|
3. 处理输入
使用strtok()
函数,可以将字符串根据设定的分隔符,分割成各部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| char** ProcessLine(char* command) { char** command_tokens = (char**)SafeMalloc(sizeof(char) * BUFFER_SIZE); char* token; int command_tokens_index = 0;
token = strtok(command, TOKEN_DELIMITER); while(token != NULL) { command_tokens[command_tokens_index++] = token; token = strtok(NULL, TOKEN_DELIMITER); }
command_tokens[command_tokens_index] = NULL; return command_tokens; }
|
4. 进程处理
大致如此。之前CSAPP的课设里,执行程序使用的是execve()
,用execvp()
倒也无所谓,它们的功能都差不多。exec函数族属于”进程替换”的函数族,执行它们不创建新进程,而是将当前进程覆盖掉,执行要执行的程序,这也是我们要创建子进程执行命令的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| int ExecuteCommand(char **token_list) { pid_t command_pid; int status = 0; if(token_list[0] == NULL) { return SUCCESS; } else {if(strcmp(token_list[0], "cd") == 0) { return Command_cd(token_list); } else if(strcmp(token_list[0], "exit") == 0) { return Command_exit(token_list); } } command_pid = fork(); if(command_pid == 0) { if(execvp(token_list[0], token_list) == -1) { perror("Execute Error"); exit(0); return ERR_FAILED_TO_EXEC; } } else if(command_pid > 0) { wait(&status); } else { perror("Fork Error"); exit(0); } free(token_list); return SUCCESS; }
|