---
# System prepended metadata

title: Homework Report - Simple Shell
tags: [OS Homework & Learning]

---

# Homework Report - Simple Shell

## (1) 開發環境
在ubuntu上使用CodeBlocks，並用C語言來撰寫。

## (2) 程式邏輯
### 1. 
(1) 印出父子程序的return值:
```clike=
    pid_t pid = fork();
    if (pid == 0){
        printf("Child return : %d\n", pid);
        exit(0);
    }
    else{
        printf("Parent return : %d\n", pid);
        wait(NULL);
    }
```
利用fork()創造出子程序後，在各自印出來。子程序會return 0，而父程序會印出大於零的數字。
  (2) 控制父子程序的執行順序:
```clike=
    int num;
    pid_t pid = fork();
    if (pid == 0) {
        printf("Child run first.\n");
        printf("Please input a number: \n");

    }
    else {
        wait(NULL);
        printf("------------------------");
        printf("\nChild completed and Input in parent process :");
        scanf(" %d", &num);
        printf("Output in parent process : %d\n", num);
    }

```
在父程序中使用wait()來確保子程序完成後再執行父程序的指令。我用了很簡單的應用:在子程序中印出輸入提示，但最後輸入是在父程序來完成，並且印出輸入。

### 2. 
(1) 寫出基本的shell，且能執行各種linux指令及參數。
- 控制整個shell的流程
```clike=
main :

while (1){
        type_prompt();
        read_command(input);
        if (input[0] == '\n')
            continue;
        parse = parse_input(input);

        char *cmd = strtok(parse, " ");
        int i = 0;
        int check = 1;
        if(vfork() != 0){
            wait(NULL);
            if (check == 0)
                break;
        }
        //if the command which is not exit, should say something.
        else{
            int redirection_check = 0 , pipe_check = 0;
            while(cmd){
                if (*cmd == '>'){
                    cmd = strtok(NULL, " ");
                    red_to_file(cmd);
                    if (pipe_check == 1)
                        run_pipe(pipe_cmd);
                    command[i] = NULL;
                    if (fork() == 0){
                        execvp(command[0], command);
                        exit(0);
                    }
                    else{
                        wait(NULL);
                    }
                    redirection_check = 1;
                }
                else if (*cmd == '<'){
                    cmd = strtok(NULL, " ");
                    red_from_file(cmd);
                    if (pipe_check == 1)
                        run_pipe(pipe_cmd);
                    command[i] = NULL;
                    if (fork() == 0){
                        execvp(command[0], command);
                        exit(0);
                    }
                    else{
                        wait(NULL);
                    }
                    redirection_check = 1;
                }
                else if (*cmd == '|'){
                    command[i] = NULL;
                    run_pipe(command);
                    pipe_check = 1;
                    int j = 0;
                    while (command[j]){
                        pipe_cmd[j] = command[j];
                        j++;
                    }
                    pipe_cmd[j] = NULL;
                    i = 0;
                }
                else{
                    command[i] = cmd;
                    i++;
                }
                 cmd = strtok(NULL, " ");
            }

            if (redirection_check == 1)
                exit(0);
            command[i] = NULL;
            built_in_cmd(command);
            execvp(command[0], command);
        }

        if (strcmp(command[0], "exit") == 0){
            check = 0;
            exit(0);
            break;
        }
    }

```
這是我主要控制整個shell的流程，首先type_prompt()是印出每次的輸入提示，之後在read_command()中輸入input指令，輸入完得到完整字串，透過parse_line()來處理子傳為分隔的token以便判斷linux指令及參數。分隔完的token會存到parse字元陣列中並以token與token用空格隔開的方式儲存，所以之後在內部的while迴圈中都是用空格(" ")來做一次token的更新判斷，每執行完一次while就用strtok()函數來找到下一個token，值到為NULL。其中因為要有redirection和pipe的功能所以要判斷他們的執行字符，並呼叫各自的執行函數。其中pipe_check和redirection_check變數是用來處理連續重導入時所用，因為多個導入需要由一開始的指令去個別操作要倒入的對象，所以需要加入一個check變數來確保每個獨立的導入對象都可以被執行，且最後要避免離開while迴圈時多執行一遍。

- 讀取input
```clike=
void read_command(char input[]){
    int count = 0;

    while (1){
        int c = fgetc(stdin);    //fgetc returns the character read as an int  //stdin is standard input.
        input[count++] = (char) c;
        if (c == '\n')
            break;
    }
    input[count] = '\0';
}
```
利用fegtc來一次接收一個字元，並搭配while來接收完整字串，讀到'\n'及輸入中止。

- 處理input字串成為一個個token

```clike=
char *parse_input(char input[]){
    int k = 0;
    char *parse_arr = malloc(500*sizeof(char));

    for (int i = 0; i < strlen(input); i++){
        if (input[i] != '>' && input[i] != '<' && input[i] != '|'){
            parse_arr[k++] = input[i];
        }
        else{
            parse_arr[k++] = ' ';
            parse_arr[k++] = input[i];
            parse_arr[k++] = ' ';
        }
    }
    parse_arr[k-1] = '\0';
    return parse_arr;
}
```
逐一將token放入parse_arr中，並以空格隔開每個token，關鍵字元的前後也需要是空格才可判斷。最後將字串末端設為NULL。

- 執行pipe
```clike=
void run_pipe(char *command[]){
    int fd[2];
    pid_t p1;

    if (pipe(fd) < 0){
        printf("Pipe Error\n");
        return;
    }
    p1 = fork();
    if (p1 < 0){
        printf("Fork Error\n");
        return;
    }
    if (p1 == 0){
        close(fd[0]);
        dup2(fd[1], 1);  // 1 represent STDOUT_FILENO which is an integer file descriper.
        close(fd[1]);

        if (execvp(command[0], command) < 0){  //execuse command and the stdout send to pipe fd[1]
            printf("command1 run error....\n");
            exit(0);
        }
    }
    else{
        close(fd[1]);
        dup2(fd[0], 0); // 0 represent STDIN_FILENO  // receive fd[1] and take it as input to parent process
        close(fd[0]);
        }
    wait(NULL);
}
```
串建pipe，fd[1]為寫入端，fd[0]為讀端，第19行將指令執行後寫入fd[1]管道，寫完close後作為fd[0]的input到父程序接收。

- 執行Redireciton
```clike=
void red_from_file(char *file_name){
    int in =  open(file_name, O_RDONLY);
    dup2(in, 0); //0 is stdin value
    close(in);
}
void red_to_file(char *file_name){
    int out = open(file_name, O_WRONLY | O_TRUNC | O_CREAT, 0600);
    dup2(out, 1); //1 stdout value
    close(out);
}
```
做Redirection，遇到'<'字元，呼叫red_from_file function，打開其後所接的file後將其開啟並用dup2()讀入(複製到)standard input。反之則寫到檔案中。

- 執行基本內部指令
```clike=
    if (strcmp(command[0], "cd") == 0){
        chdir(command[1]);
    }
    else if (strcmp(command[0], "help") == 0){
        printf("GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)\n");
        printf("These shell commands are defined internally.  Type `help' to see this list.\n");
        printf("cd [-L|[-P [-e]] [-@]] [dir].\n");
        printf("pwd [-LP].\n");
        printf("echo [-neE] [arg ...] .\n\n");
        printf("exit [n].\n\n");
    }
```
我只寫最基本的cd和help功能，而exit功能我是寫在mian裡面，其中cd的功能只要透過chdir即可，而help我就印出一些簡單的指令提示。

## (3) 函式功能說明
1. fork() : 能夠從父程序中複製出完全一樣的程序，此程序稱為子程序。父程序與子程序有各自獨立的memory space，但變數中指向的外部檔案人為同一份，此外，子程序並不會繼承父程序原有的lock、timer、及signal處理。子程序終止後會回傳SIGCHLD的信號給父程序，父程序可以透過此訊號得知子程序的中止狀態。而若父程序在子程序完成前中止則子程序會成為orphan process;若子程序完成後父程序卻沒收到SIGCHLD則子程序會成為zombie process。
2. vfork() : 與fork()最大差別在於能夠共享資料片段，且保證子程序先執行，在呼叫 exec 或exit 之前與父程序資料是共享的,在它呼叫exec或exit之後父程序才可能被排程執行。
3. wait() : 會暫時停止目前程序的執行, 直到有訊號來到或子程序結束。如果在呼叫wait()時子程序已經結束，則wait()會立即返回子程序結束狀態值。子程序的結束狀態值會由引數status返回，如果不在意結束狀態值，則引數status 可以設成NULL。
4. strtok(): 第一個參數放待切割字串，第二則放切割字符。切割完第一次後要繼續切則把第一個參數改為NULL，因為每次切完會將切割字符改為'\0'。
5. fgetc() : 用於單個字符寫入，返回值為讀取的字符的unsigned char並轉為int 或 EOF文件結束。
6. excvp() : 用來執行執行檔，第一個參數是即將被執行的執行檔名字，要的是const char*，而第二個是傳給file作為其引數，且其結尾必須是NULL。有error的話回傳-1，沒有error則不會回傳任何值。
7. pipe() : 用來做linux IPC，回傳值為0則建立成功，-1則建立失敗。將一端的標準輸出導入另一端作為標準輸入。其參數為接收整數值陣列，並建立出一組雙向管道，假如此陣列為pfd[2]，那pfd[0] (讀的端口)進入的東西會從pfd[1] (寫的端口)出來，反之亦然。pipe的write end傳完畢後，會傳送EOF給read end。若pipe的read end接收到EOF，就會關閉此read end。
8. dup2() :  int dup2(int oldfd, int newfd)，通過oldfd複製出一個新的文件描述符newfd，如果成功，newfd 和函數返回值是同一個返回值，最終 oldfd 和新的文件描述符 newfd 都指向同一個文件。其中newfd可以指定一個合法數字(0123..)，如果指定的數字已被占用，函數會自動關閉 close() 斷開這個數字和某個文件的關聯，再來使用這個合法數字。若成功執行返回newfd，反之為-1。與舊的文件都是指向同一個file結構體。
(STDIN_FILENO,Standard input value, stdin. Its value is 0.)
(STDOUT_FILENO,Standard output value, stdout. Its value is 1.)



###### tags: `OS Homework & Learning`