Linuxコマンドシェルを書いてみましょう

挨拶

みなさん、こんにちは!PosixAPIを使用して独自のLinuxシェルを作成した経験を共有したいと思います。しばらくお待ちください。





最終結果
最終結果

私たちのシェルができるべきこと

  1. フォアグラウンドモードとバックグラウンドモードでプロセスを実行する





  2. バックグラウンドプロセスの終了





  3. ディレクトリナビゲーションのサポート





シェルのしくみ

  1. 標準入力から行を読み取る





  2. 文字列をトークンに分割する





  3. システム呼び出しを使用して子プロセスを作成する fork







  4. exec







  5. ( foreground )





fork()

fork



, , . . pid



.





:





#include <stdio.h>
#include <unistd.h>
#include <wait.h>

int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        printf("I'm child process!\n");    
    } else {
        printf("I'm parent process!\n");
        wait(NULL);
    }
    
    return 0;
}
      
      



:

I'm parent process!

I'm child process!





? fork



, . . .





. fork



0



- , - . , id



0



.





, . . wait(NULL)



, .





exec()

exec



, , .





exec



. , .





:





#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>

int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        execlp("ls", "ls", "-l", NULL);
        exit(1);
    } else {
        waitpid(pid, NULL, 0);
    }
    
    return 0;
}
      
      



total 16

-rwxr-xr-x 1 runner runner 8456 Jan 13 07:33 main

-rw-r--r-- 1 runner runner 267 Jan 13 07:33 main.c





?





. exec



ls



, .





, .





1.

. , .





char* readline() {
    char*   line = NULL;
    size_t  size = 0;
    ssize_t str_len;

    // Reading line from stdin
    if ((str_len = getline(&line, &size, stdin)) == -1) {
        // Logging all errors except Ctrl-D - terminal shutdown
        if (errno != 0) {
            printf("[ERROR] Couldn't read from stdin\n");
        }
        free(line);
        printf("\n");
        return NULL;
    }

    // Remove useless \n symbol if exists
    if (line[str_len - 1] == '\n') {
        line[str_len - 1] = '\0';
    }

    return line;
}
      
      



getline



. , , .





, , - ctrl-D



. , .





2.

.





#define DEFAULT_BUFF_SIZE 16
#define TOKENS_DELIMITERS " \t"
      
      



.





char** split(char* line) {
    size_t position  = 0;
    size_t buff_size = DEFAULT_BUFF_SIZE;

    char* token;

    // Allocate memory for tokens array
    char** tokens = (char**)malloc(sizeof(char*) * buff_size);
    if (tokens == NULL) {
        printf("[ERROR] Couldn't allocate buffer for splitting!\n");
        return NULL;
    }

    // Tokenize process
    token = strtok(line, TOKENS_DELIMITERS);
    while (token != NULL) {
        // Emplace token to array
        tokens[position++] = token;

        // If array free space ended - increase array
        if (position >= buff_size) {
            buff_size *= 2;

            tokens = (char**)realloc(tokens, buff_size * sizeof(char*));
            if (tokens == NULL) {
                printf("[ERROR] Couldn't reallocate buffer for tokens!\n");
                return NULL;
            }
        }

        // Getting next token
        token = strtok(NULL, TOKENS_DELIMITERS);
    }

    // Place NULL to the end of tokens array
    tokens[position] = NULL;

    return tokens;
}
      
      



, .





strtok



. . , 2 .





NULL



, . . exec() .





3.

foreground background , fg_task



bg_task



. tasks



.





// Struct of background task
struct bg_task_t {
    pid_t  pid;           // Process id
    bool   finished;      // Process state
    char*  timestamp;     // Process state
    char*  cmd;           // Command cmd
};
typedef struct bg_task_t bg_task;

// Struct of foreground task
struct fg_task_t {
    pid_t pid;     // Process id
    bool finished; // Process state
};
typedef struct fg_task_t fg_task;

// Struct of all tasks
struct tasks_t {
    fg_task  foreground; // Process id of foreground bg_task
    bg_task* background; // Background task list
    size_t   cursor;     // Cursor of background tasks
    size_t   capacity;   // Background array capacity
};
typedef struct tasks_t tasks;
      
      



tasks



, .





// Global variable for storing active tasks
tasks t = {
    .foreground = {
        .pid = -1,
        .finished = true
    },
    .background = NULL,
    .cursor = 0,
    .capacity = 0
};
      
      



foreground .





void set_foreground(pid_t pid) {
    t.foreground.pid = pid;
    t.foreground.finished = 0;
}
      
      



background .





int add_background(pid_t pid, char* name) {
    // Temp background task variable
    bg_task* bt;

    // If end of free space in background array - increase size
    if (t.cursor >= t.capacity) {
        t.capacity = t.capacity * 2 + 1;
        t.background = (bg_task*)realloc(t.background, sizeof(bg_task) * t.capacity);
        if (t.background == NULL) {
            printf("[ERROR] Couldn't reallocate buffer for background tasks!\n");
            return -1;
        }
    }

    // Print info about process start
    printf("[%zu] started.\n", t.cursor);

    // Save task in temp variable
    bt = &t.background[t.cursor];

    // Save process info in array
    bt->pid = pid;
    bt->finished = false;

    time_t timestamp = time(NULL);
    bt->timestamp = ctime(&timestamp);

    bt->cmd = strdup(name);

    // Move cursor right
    t.cursor += 1;

    return 0;
}
      
      



. . - 2 . bg_task



.





-1



.





foreground . kill SIGTERM id .





void kill_foreground() {
    if (t.foreground.pid != -1) {
        // Kill process
        kill(t.foreground.pid, SIGTERM);

        // Set finished flag
        t.foreground.finished = true;

        printf("\n");
    }
}
      
      



background .





int term(char** args) {
    char* idx_str;      // Cursor in index arg
    int   proc_idx = 0; // Converted to int index arg

    if (args[1] == NULL) {
        printf("[ERROR] No process index to stop!\n");
    } else {
        // Set cursor in index arg
        idx_str = args[1];

        // Convert string index arg to int
        while (*idx_str >= '0' && *idx_str <= '9') {
            proc_idx = (proc_idx * 10) + ((*idx_str) - '0');

            // Move cursor to right
            idx_str += 1;
        }

        // Kill process if process index not bad
        // and target process not finished
        if (*idx_str != '\0' || proc_idx >= t.cursor) {
            printf("[ERROR] Incorrect background process index!\n");
        } else if (!t.background[proc_idx].finished) {
            kill(t.background[proc_idx].pid, SIGTERM);
        }
    }

    return CONTINUE;
}
      
      



{"term", "<bg task index>", NULL}



. background . background kill



.





is_background



, . &



.





int is_background(char** args) {
    // Current position in array
    int last_arg = 0;

    // Finding last arg in array
    while (args[last_arg + 1] != NULL) {
        last_arg += 1;
    }

    // Checking if task is background`
    if (strcmp(args[last_arg], "&") == 0) {
        // Remove '&' token for future executing
        args[last_arg] = NULL;

        // Return true
        return 1;
    }

    // Return false if: '&' wasn't founded
    return 0;
}
      
      



launch



background &



, foreground .





int launch(char** args) {
    pid_t pid;        // Fork process id
    int   background; // Is background task

    // Checking if task is background
    background = is_background(args);

    // Create child process
    pid = fork();

    // If created failure log error
    if (pid < 0) {
        printf("[ERROR] Couldn't create child process!\n");
    }
    // Child process
    else if (pid == 0) {
        // Try launch task
        if (execvp(args[0], args) == -1) {
            printf("[ERROR] Couldn't execute unknown command!\n");
        }
        exit(1);
    }
    // Parent process
    else {
        if (background) {
            // Try add background task to array
            if (add_background(pid, args[0]) == -1) {
                // Kill all processes and free
                // memory before exit
                quit();
            }
        } else {
            // Set foreground task to store
            set_foreground(pid);

            // Wait while process not ended
            if (waitpid(pid, NULL, 0) == -1) {
                // Logging error if process tracked with error
                // Except when interrupted by a signal
                if (errno != EINTR) {
                    printf("[ERROR] Couldn't track the completion of the process!\n");
                }
            }
        }
    }

    return CONTINUE;
}
      
      



, .





  1. fork







  2. exec











  3. - bacground





  4. -





quit



. .





.

execute



, .





int execute(char** args) {
    if (args[0] == NULL) {
        return CONTINUE;
    } else if (strcmp(args[0], "cd") == 0) {
        return cd(args);
    } else if (strcmp(args[0], "help") == 0) {
        return help();
    } else if (strcmp(args[0], "quit") == 0) {
        return quit();
    } else if (strcmp(args[0], "bg") == 0) {
        return bg();
    } else if (strcmp(args[0], "term") == 0) {
        return term(args);
    } else {
       return launch(args);
    }
}
      
      



, NULL



. , cd



. , help



. , quit



. background , bg



. , term



.





.





#define CONTINUE 1
#define EXIT     0
      
      



CONTINUE



. EXIT



.





int cd(char** args) {
    if (args[1] == NULL) {
        printf("[ERROR] Expected argument for \"cd\" command!\n");
    } else if (chdir(args[1]) != 0) {
        printf("[ERROR] Couldn't change directory to \"%s\"!\n", args[1]);
    }

    return CONTINUE;
}
      
      



int help() {
    printf(
        "Simple shell by Denis Glazkov.                               \n\n"

        "Just type program names and arguments, and hit enter.          \n"
        "Run tasks in background using '&' in the end of command.     \n\n"

        "Built in functions:                                           \n"
        "  cd   <path>        - Changes current working directory      \n"
        "  term <bg_task_idx> - Kill background process by index       \n"
        "  help               - Prints info about shell                \n"
        "  bg                 - Prints list of background tasks        \n"
        "  quit               - Terminates shell and all active tasks\n\n"

        "Use the man command for information on other programs.         \n"
    );

    return CONTINUE;
}
      
      



int quit() {
    // Temp background task variable
    bg_task* bt;

    // Disable logging on child killed
    signal(SIGCHLD, SIG_IGN);

    // Kill foreground process
    if (!t.foreground.finished) {
        kill_foreground();
    }

    // Kill all active background tasks
    for (size_t i = 0; i < t.cursor; i++) {
        // Place background task to temp variable
        bt = &t.background[i];

        // Kill process if active
        if (!bt->finished) {
            kill(bt->pid, SIGTERM);
        }

        // Free memory for command name
        free(bt->cmd);
    }

    return EXIT;
}
      
      



quit



callback SIGCHLD



- . . , . .





#define PRIMARY_COLOR   "\033[92m"
#define SECONDARY_COLOR "\033[90m"
#define RESET_COLOR     "\033[0m"
      
      



.





int bg() {
    // Temp background task variable
    bg_task* bt;

    for (size_t i = 0; i < t.cursor; i++) {
        // Store background task in temp variable
        bt = &t.background[i];

        // Print info about task
        printf(
            "[%zu]%s cmd: %s%s;%s pid: %s%d; %s"
            "state: %s%s;%s timestamp: %s%s", i,
            SECONDARY_COLOR, RESET_COLOR, bt->cmd,
            SECONDARY_COLOR, RESET_COLOR, bt->pid,
            SECONDARY_COLOR, RESET_COLOR, bt->finished ? "finished" : "active",
            SECONDARY_COLOR, RESET_COLOR, bt->timestamp
        );
    }

    return CONTINUE;
}
      
      



4.

#include <stdlib.h>
#include <signal.h>

#include "include/shell.h"

int main() {
    char*  line;   // User input
    char** args;   // Tokens in user input
    int    status; // Status of execution

    // Add signal for killing foreground child on ctrl-c
    signal(SIGINT, kill_foreground);

    // Add signal for handling end of child processes
    signal(SIGCHLD, mark_ended_task);

    // Shell is running while
    // status == CONTINUE
    do {
        // Printing left shell info
        display();

        // Reading user input
        line = readline();
        if (line == NULL) {
            exit(1);
        }

        // Parse line to tokens
        args = split(line);
        if (args == NULL) {
            free(line);
            exit(2);
        }

        // Try execute command
        status = execute(args);

        // Free allocated memory
        free(line);
        free(args);

    } while (status);

    return 0;
}
      
      



. . signal



callback .





// Add signal for killing foreground child on ctrl-c
signal(SIGINT, kill_foreground);

// Add signal for handling end of child processes
signal(SIGCHLD, mark_ended_task);
      
      



SIGINT



- ctrl-C



, . foreground .





SIGCHLD



- y fork



. mark_ended_task



.





void mark_ended_task() {
    // Temp background task variable
    bg_task* bt;

    // Get process id of ended process
    pid_t pid = waitpid(-1, NULL, 0);

    // Handle foreground process
    if (pid == t.foreground.pid) {
        t.foreground.finished = true;
    }
    // Handle background process
    else {
        // Search and remove process form background tasks array
        for (size_t i = 0; i < t.cursor; i++) {
            // Place task to temp variable
            bt = &t.background[i];

            if (bt->pid == pid) {
                // Print info about process end
                printf("[%zu] finished.\n", i);

                // Set new state for background process
                bt->finished = 1;

                break;
            }
        }
    }
}
      
      



:





  1. display















  2. execute



    , .





display



. getcwd



getpwuid



.





void display() {
    // Try get and print username with color
    uid_t uid = geteuid();
    struct passwd *pw = getpwuid(uid);
    if (pw != NULL) {
        printf("%s%s%s:", PRIMARY_COLOR, pw->pw_name, RESET_COLOR);
    }

    // Try get and print current directory with color
    char cwd[MAX_DIRECTORY_PATH];
    if (getcwd(cwd, MAX_DIRECTORY_PATH) != NULL) {
        printf("%s%s%s", SECONDARY_COLOR, cwd, RESET_COLOR);
    }

    // Print end of shell info
    printf("# ");
}
      
      



5.

最終結果

これがお役に立てば幸いです。この資料を補足するものがあれば、コメントであなたの考えを聞いてうれしいです。





プロジェクトのソースコードは、このリンクにあります。








All Articles