Chapter 18

Mini Shell

Chapter 18: Build a Mini Shell in C

Every programmer who truly understands Linux should build their own shell. It doesn't have to be perfect, but it will make you genuinely understand the difference between fork() and exec(), why pipes require closing unused fds, and how signals propagate across process groups. This chapter implements a mini shell supporting pipes, redirection, and built-in commands in roughly 400 lines of C, then uses GDB debugging to deepen understanding.

1. Why Build Your Own Shell

A shell is one of the thinnest wrappers over the OS. It does almost nothing "magical" โ€” most functionality is a composition of a few syscalls: fork() to create a child, execvp() to replace its image, waitpid() to reap it, and pipe() + dup2() to implement pipes and redirection. Once you understand these, bash/zsh behavior stops being mysterious.

2. Shell REPL Loop

The core of a shell is a REPL (Read-Eval-Print-Loop): read user input, parse the command, execute it, display the result, repeat forever.

/* repl.c โ€” ๆœ€็ฎ€ REPL ้ชจๆžถ */
#include 
#include 
#include 
#include 
#include 

int main(void) {
    char *line;

    /* readline ๆไพ›่กŒ็ผ–่พ‘ๅ’Œๅކๅฒ่ฎฐๅฝ• */
    while ((line = readline("mysh$ ")) != NULL) {
        if (*line) {
            add_history(line);      /* ๅŠ ๅ…ฅๅކๅฒ่ฎฐๅฝ•๏ผˆไธŠไธ‹็ฎญๅคดๅฏ็ฟป๏ผ‰ */
            /* TODO: ่งฃๆžๅนถๆ‰ง่กŒ line */
            printf("got: %s\n", line);
        }
        free(line);                 /* readline ๅˆ†้…๏ผŒ่ฐƒ็”จ่€…่ดŸ่ดฃ้‡Šๆ”พ */
    }

    printf("\n");                   /* Ctrl+D ้€€ๅ‡บๆ—ถๆข่กŒ */
    return 0;
}

readline: The readline library provides line editing (arrow keys, Ctrl+A/E), history (up/down arrows), and Tab completion hooks. Install: apt install libreadline-dev (Debian) or yum install readline-devel (RHEL). Compile with -lreadline.

3. fork + execvp + waitpid

Unix process creation uses the "fork-then-exec" pattern: fork() duplicates the current process (Copy-on-Write โ€” physical memory is shared until a write occurs), the child calls execvp() to replace its address space with the new program, and the parent calls waitpid() to wait for and reap the child (preventing zombie processes).

/* exec_cmd.c โ€” fork/execvp/waitpid ๅŸบ็ก€ๅ‘ฝไปคๆ‰ง่กŒ */
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* ๆ‰ง่กŒๅ•ๆกๅ‘ฝไปค๏ผˆๆ— ็ฎก้“ๆ— ้‡ๅฎšๅ‘๏ผ‰
 * argv: NULL็ป“ๅฐพ็š„ๅ‚ๆ•ฐๆ•ฐ็ป„๏ผŒๅฆ‚ {"ls", "-la", NULL}
 * ่ฟ”ๅ›žๅ€ผ๏ผšๅญ่ฟ›็จ‹้€€ๅ‡บ็Šถๆ€๏ผŒๅคฑ่ดฅ่ฟ”ๅ›ž-1
 */
int exec_simple(char **argv) {
    if (argv == NULL || argv[0] == NULL)
        return 0;

    pid_t pid = fork();
    if (pid 
  
## 4. Built-in Commands


  
Built-in commands must execute inside the shell process itself โ€” they cannot be forked โ€” because they need to modify the shell's own state (current directory, environment variables, etc.).


  
```c
/* builtins.c โ€” ๅ†…็ฝฎๅ‘ฝไปคๅฎž็Žฐ */
#include 
#include 
#include 
#include 
#include 

#define HISTORY_MAX 100
static char *history[HISTORY_MAX];
static int   history_count = 0;

/* ๆทปๅŠ ๅˆฐๅކๅฒ่ฎฐๅฝ• */
void history_add(const char *line) {
    if (history_count 
  
## 5. Signal Handling


  
Shell signal handling follows one core principle: **the shell ignores SIGINT (Ctrl+C), but child processes (foreground programs) do not ignore it**. This makes Ctrl+C interrupt only the foreground program, not the shell. This is achieved via `sigaction()` and process groups.


  
```c
/* signals.c โ€” Shell ไฟกๅทๅค„็† */
#include 
#include 
#include 
#include 

/* Shell ่‡ช่บซๅˆๅง‹ๅŒ–๏ผšๅฟฝ็•ฅไบคไบ’ๅผไฟกๅท */
void shell_init_signals(void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));

    /* SIG_IGN: Shell ่‡ช่บซๅฟฝ็•ฅ SIGINT๏ผˆCtrl+C๏ผ‰ๅ’Œ SIGQUIT๏ผˆCtrl+\๏ผ‰ */
    sa.sa_handler = SIG_IGN;
    sigaction(SIGINT,  &sa, NULL);
    sigaction(SIGQUIT, &sa, NULL);

    /* SIGTSTP๏ผˆCtrl+Z๏ผ‰๏ผšShell ไนŸๅฟฝ็•ฅ๏ผŒ่ฎฉๅญ่ฟ›็จ‹ๅค„็† */
    sigaction(SIGTSTP, &sa, NULL);

    /* SIGTTOU/SIGTTIN๏ผšๅŽๅฐ่ฟ›็จ‹่ฏปๅ†™็ปˆ็ซฏไบง็”Ÿ๏ผŒShell ๅฟฝ็•ฅ */
    sigaction(SIGTTOU, &sa, NULL);
    sigaction(SIGTTIN, &sa, NULL);
}

/* fork ๅŽ๏ผŒๅญ่ฟ›็จ‹ๆขๅค้ป˜่ฎคไฟกๅทๅค„็†ๅนถ่ฎพ็ฝฎ่ฟ›็จ‹็ป„ */
void child_init_signals(pid_t pgid) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = SIG_DFL;   /* ๆขๅค้ป˜่ฎค่กŒไธบ๏ผˆSIGINT โ†’ ็ปˆๆญข๏ผ‰ */

    sigaction(SIGINT,  &sa, NULL);
    sigaction(SIGQUIT, &sa, NULL);
    sigaction(SIGTSTP, &sa, NULL);
    sigaction(SIGTTOU, &sa, NULL);
    sigaction(SIGTTIN, &sa, NULL);

    /* ๅฐ†ๅญ่ฟ›็จ‹ๆ”พๅ…ฅ่‡ชๅทฑ็š„่ฟ›็จ‹็ป„๏ผˆpgid==0 โ†’ ไฝฟ็”จ่‡ช่บซ PID๏ผ‰ */
    setpgid(0, pgid);

    /* ๅฐ†ๅ‰ๅฐๆŽงๅˆถๆƒ่ฝฌ็งป็ป™ๅญ่ฟ›็จ‹็š„่ฟ›็จ‹็ป„ */
    tcsetpgrp(STDIN_FILENO, getpgrp());
}

/* sigaction vs signal ็š„ๅ…ณ้”ฎๅŒบๅˆซ๏ผš
 * signal():   ่กŒไธบๅœจไธๅŒ Unix ๅฎž็Žฐ้—ดไธไธ€่‡ด๏ผŒไฟกๅทๅค„็†ๆœŸ้—ดไธ่‡ชๅŠจๅฑ่”ฝ
 * sigaction(): POSIXๆ ‡ๅ‡†๏ผŒ่กŒไธบไธ€่‡ด๏ผŒๆ”ฏๆŒ SA_RESTART๏ผˆ่‡ชๅŠจ้‡ๅฏ่ขซไธญๆ–ญ็š„็ณป็ปŸ่ฐƒ็”จ๏ผ‰
 *
 * SA_RESTART ๅพˆ้‡่ฆ๏ผšๆฒกๆœ‰ๅฎƒ๏ผŒไฟกๅทไผšๅฏผ่‡ด read()/write() ่ฟ”ๅ›ž EINTR๏ผŒ
 * ้œ€่ฆๆ‰‹ๅŠจ้‡่ฏ•ใ€‚ๆœ‰ไบ† SA_RESTART๏ผŒ็ณป็ปŸ่ฐƒ็”จไผš่‡ชๅŠจ้‡ๆ–ฐๆ‰ง่กŒใ€‚
 */

6. Redirection Implementation

Redirection is fundamentally about using dup2() after fork but before exec to replace the child's standard fds (0/1/2). dup2(newfd, oldfd) duplicates newfd as oldfd, closing the original newfd; after this, the program reads/writes through oldfd but actually operates on the file pointed to by newfd.

/* redirect.c โ€” ้‡ๅฎšๅ‘ๅฎž็Žฐ */
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct {
    char **argv;
    char  *redir_in;      /* " file" ็š„ๆ–‡ไปถๅ */
    char  *redir_append;  /* ">> file" ็š„ๆ–‡ไปถๅ */
    int    stderr_to_stdout; /* "2>&1" ๆ ‡ๅฟ— */
} Cmd;

/* ๅœจๅญ่ฟ›็จ‹ไธญๆ‰ง่กŒ้‡ๅฎšๅ‘๏ผˆfork ไน‹ๅŽ่ฐƒ็”จ๏ผ‰ */
void apply_redirects(Cmd *cmd) {
    int fd;

    /* ่พ“ๅ…ฅ้‡ๅฎšๅ‘๏ผšredir_in) {
        fd = open(cmd->redir_in, O_RDONLY);
        if (fd redir_in, strerror(errno));
            exit(1);
        }
        dup2(fd, STDIN_FILENO);   /* ๆ ‡ๅ‡†่พ“ๅ…ฅ โ†’ ๆŒ‡ๅ‘ๆ–‡ไปถ */
        close(fd);                /* ๅ…ณ้—ญๅŽŸ fd๏ผˆๅทฒ่ขซ dup2 ๅคๅˆถ๏ผ‰ */
    }

    /* ่พ“ๅ‡บ้‡ๅฎšๅ‘๏ผš> file๏ผˆๆˆชๆ–ญๆจกๅผ๏ผ‰ */
    if (cmd->redir_out) {
        fd = open(cmd->redir_out,
                  O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd redir_out, strerror(errno));
            exit(1);
        }
        dup2(fd, STDOUT_FILENO);  /* ๆ ‡ๅ‡†่พ“ๅ‡บ โ†’ ๆŒ‡ๅ‘ๆ–‡ไปถ */
        close(fd);
    }

    /* ่ฟฝๅŠ ้‡ๅฎšๅ‘๏ผš>> file */
    if (cmd->redir_append) {
        fd = open(cmd->redir_append,
                  O_WRONLY | O_CREAT | O_APPEND, 0644);
        if (fd redir_append, strerror(errno));
            exit(1);
        }
        dup2(fd, STDOUT_FILENO);
        close(fd);
    }

    /* 2>&1๏ผšๅฐ† stderr ้‡ๅฎšๅ‘ๅˆฐ stdout ๅฝ“ๅ‰ๆŒ‡ๅ‘็š„็›ฎๆ ‡ */
    if (cmd->stderr_to_stdout)
        dup2(STDOUT_FILENO, STDERR_FILENO);
}

7. Pipe Implementation

pipe(pipefd[2]) creates an anonymous pipe: pipefd[0] is the read end, pipefd[1] is the write end. Implementing ls | grep txt requires two child processes: the left child redirects its stdout to pipefd[1]; the right child redirects its stdin from pipefd[0]. Critical: both sides must close the end they don't use, or the reader will never see EOF.

/* pipe_demo.c โ€” "ls | grep txt" ็š„ๅฎŒๆ•ด C ๅฎž็Žฐ */
#include 
#include 
#include 
#include 

int main(void) {
    int pipefd[2];

    /* ๅˆ›ๅปบ็ฎก้“๏ผšpipefd[0]=่ฏป็ซฏ  pipefd[1]=ๅ†™็ซฏ */
    if (pipe(pipefd)  **Most Common Pipe Bug:**     Forgetting to close the write end of the pipe in the parent process (or in any child that doesn't use it). The result is that the reader's `read()` blocks forever โ€” as long as any process holds the write end open, the kernel will not send EOF. Debug with `lsof | grep pipe` to find lingering pipe fds.


  
  
## 8. Complete Mini Shell Code


  
Below is the complete mini shell implementation (~230 lines of core code), supporting: simple commands, single-level pipes, input/output/append redirection, built-in commands (cd/pwd/export/history/exit), and Ctrl+C signal handling.


  
```c
/* mysh.c โ€” ๅฎŒๆ•ด่ฟทไฝ  Shell
 * ็ผ–่ฏ‘๏ผšgcc -Wall -Wextra -g -o mysh mysh.c -lreadline
 * ่ฟ่กŒ๏ผš./mysh
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๅธธ้‡ไธŽๆ•ฐๆฎ็ป“ๆž„ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
#define MAX_ARGS    64
#define MAX_CMDS    16     /* ็ฎก้“ไธญๆœ€ๅคšๅ‘ฝไปคๆ•ฐ */
#define HIST_MAX   100

typedef struct {
    char *argv[MAX_ARGS];  /* ๅ‚ๆ•ฐๅˆ—่กจ๏ผŒNULL็ป“ๅฐพ */
    char *redir_in;        /*  file */
    char *redir_append;    /* >> file */
    int   stderr_redir;    /* 2>&1 */
} Cmd;

typedef struct {
    Cmd  cmds[MAX_CMDS];   /* ็ฎก้“ไธญ็š„ๅ„ไธชๅ‘ฝไปค */
    int  count;            /* ๅ‘ฝไปคๆ•ฐ้‡ */
} Pipeline;

static int last_exit_code = 0;

/* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ไฟกๅทๅˆๅง‹ๅŒ– โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
static void init_signals(void) {
    struct sigaction sa = {0};
    sa.sa_handler = SIG_IGN;
    sigaction(SIGINT,  &sa, NULL);
    sigaction(SIGQUIT, &sa, NULL);
    sigaction(SIGTSTP, &sa, NULL);
    sigaction(SIGTTOU, &sa, NULL);
    sigaction(SIGTTIN, &sa, NULL);
}

/* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ่ฏๆณ•ๅˆ†ๆž๏ผˆtokenize๏ผ‰ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
/* ๅฐ†่พ“ๅ…ฅ่กŒๅˆ†ๅ‰ฒไธบ token ๆ•ฐ็ป„๏ผŒ่ฟ”ๅ›ž token ๆ•ฐ้‡ */
static int tokenize(char *line, char **tokens, int max_tokens) {
    int n = 0;
    char *tok = strtok(line, " \t\n");
    while (tok != NULL && n redir_in = tokens[++i];
        } else if (strcmp(t, ">") == 0) {
            if (i + 1 redir_out = tokens[++i];
        } else if (strcmp(t, ">>") == 0) {
            if (i + 1 redir_append = tokens[++i];
        } else if (strcmp(t, "2>&1") == 0) {
            cmd->stderr_redir = 1;
        } else {
            if (argc argv[argc++] = t;
        }
        i++;
    }
    cmd->argv[argc] = NULL;
    return argc;
}

/* ๅฐ† token ๆ•ฐ็ป„ๆŒ‰็ฎก้“็ฌฆ'|'ๅˆ†ๅ‰ฒ๏ผŒๅกซๅ…… Pipeline ็ป“ๆž„ */
static void parse_pipeline(char **tokens, int ntokens, Pipeline *pl) {
    pl->count = 0;
    int start = 0;

    for (int i = 0; i count cmds[pl->count++]);
            }
            start = i + 1;
        }
    }
}

/* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๅ†…็ฝฎๅ‘ฝไปค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
static int builtin_cd(char **argv) {
    const char *dir = argv[1] ? argv[1] : (getenv("HOME") ? getenv("HOME") : "/");
    if (chdir(dir) != 0) {
        fprintf(stderr, "cd: %s: %s\n", dir, strerror(errno));
        return 1;
    }
    return 0;
}

static int builtin_pwd(char **argv) {
    (void)argv;
    char buf[4096];
    if (!getcwd(buf, sizeof(buf))) { perror("getcwd"); return 1; }
    puts(buf);
    return 0;
}

static int run_builtin(Cmd *cmd) {
    char **av = cmd->argv;
    if (!av[0]) return -1;
    if (strcmp(av[0], "cd")      == 0) return builtin_cd(av);
    if (strcmp(av[0], "pwd")     == 0) return builtin_pwd(av);
    if (strcmp(av[0], "history") == 0) {
        HIST_ENTRY **list = history_list();
        if (list) for (int i = 0; list[i]; i++)
            printf("%4d  %s\n", i + 1, list[i]->line);
        return 0;
    }
    if (strcmp(av[0], "exit") == 0) exit(av[1] ? atoi(av[1]) : 0);
    return -1;   /* ไธๆ˜ฏๅ†…็ฝฎๅ‘ฝไปค */
}

/* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๅญ่ฟ›็จ‹๏ผšๅบ”็”จ้‡ๅฎšๅ‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
static void apply_redirects(Cmd *cmd) {
    int fd;
    if (cmd->redir_in) {
        fd = open(cmd->redir_in, O_RDONLY);
        if (fd redir_in); exit(1); }
        dup2(fd, STDIN_FILENO); close(fd);
    }
    if (cmd->redir_out) {
        fd = open(cmd->redir_out, O_WRONLY|O_CREAT|O_TRUNC, 0644);
        if (fd redir_out); exit(1); }
        dup2(fd, STDOUT_FILENO); close(fd);
    }
    if (cmd->redir_append) {
        fd = open(cmd->redir_append, O_WRONLY|O_CREAT|O_APPEND, 0644);
        if (fd redir_append); exit(1); }
        dup2(fd, STDOUT_FILENO); close(fd);
    }
    if (cmd->stderr_redir)
        dup2(STDOUT_FILENO, STDERR_FILENO);
}

/* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๆ‰ง่กŒ Pipeline โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
static int exec_pipeline(Pipeline *pl) {
    /* ๅ•ๆกๅ‘ฝไปค๏ผšๅ…ˆๅฐ่ฏ•ๅ†…็ฝฎๅ‘ฝไปค */
    if (pl->count == 1) {
        int ret = run_builtin(&pl->cmds[0]);
        if (ret >= 0) return ret;
    }

    int n = pl->count;
    int pipes[MAX_CMDS - 1][2];   /* n-1 ไธช็ฎก้“ */
    pid_t pids[MAX_CMDS];

    /* ้ข„ๅ…ˆๅˆ›ๅปบๆ‰€ๆœ‰็ฎก้“ */
    for (int i = 0; i  0) {
                dup2(pipes[i-1][0], STDIN_FILENO);
            }
            /* ่ฟžๆŽฅ็ฎก้“๏ผšๅ†™ๅ…ฅไธ‹ไธ€ไธชๅ‘ฝไปค */
            if (i cmds[i]);

            /* exec */
            execvp(pl->cmds[i].argv[0], pl->cmds[i].argv);
            fprintf(stderr, "mysh: %s: %s\n",
                    pl->cmds[i].argv[0], strerror(errno));
            exit(127);
        }
    }

    /* ็ˆถ่ฟ›็จ‹๏ผšๅ…ณ้—ญๆ‰€ๆœ‰็ฎก้“ fd */
    for (int i = 0; i  0 && tokens[0][0] == '#') {
            free(line); continue;
        }

        /* ่งฃๆž็ฎก้“ */
        Pipeline pl;
        parse_pipeline(tokens, ntok, &pl);

        /* ๆ‰ง่กŒ */
        if (pl.count > 0 && pl.cmds[0].argv[0] != NULL)
            last_exit_code = exec_pipeline(&pl);

        free(line);
    }

    return last_exit_code;
}

9. Makefile Build

# Makefile โ€” ่ฟทไฝ  Shell ๆž„ๅปบๆ–‡ไปถ
/* ไฟๅญ˜ไธบ Makefile๏ผˆๆณจๆ„๏ผš้…ๆ–น่กŒๅฟ…้กป็”จ Tab ็ผฉ่ฟ›๏ผŒไธ่ƒฝ็”จ็ฉบๆ ผ๏ผ‰ */
CC      = gcc
CFLAGS  = -Wall -Wextra -Wpedantic -g -std=c11
LDFLAGS = -lreadline
TARGET  = mysh
SRC     = mysh.c

.PHONY: all clean install bear

all: $(TARGET)

$(TARGET): $(SRC)
	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

# bear ็”Ÿๆˆ compile_commands.json๏ผˆไพ› clangd/LSP ไฝฟ็”จ๏ผ‰
bear: $(SRC)
	bear -- $(CC) $(CFLAGS) -o $(TARGET) $(SRC) $(LDFLAGS)

# ๅฎ‰่ฃ…ๅˆฐ ~/bin
install: $(TARGET)
	install -m 755 $(TARGET) $(HOME)/bin/$(TARGET)

clean:
	rm -f $(TARGET) compile_commands.json

# ่ฟ่กŒ๏ผˆๅธฆ ASAN ๅ†…ๅญ˜ๆฃ€ๆต‹๏ผ‰
asan:
	$(CC) $(CFLAGS) -fsanitize=address,undefined \
	    -o $(TARGET)-asan $(SRC) $(LDFLAGS)
	./$(TARGET)-asan

compile_commands.json: Generated via bear -- make, this compilation database enables clangd (the LSP server for VSCode/Neovim) to provide accurate code completion, go-to-definition, and diagnostics โ€” standard practice in modern C development.

10. GDB Debugging

Debugging a shell especially requires attention to child process behavior after fork. GDB defaults to following the parent after fork, but set follow-fork-mode child switches it to follow the child instead.

# ๅฏๅŠจ GDB
gdb ./mysh

# ๅŸบๆœฌๅ‘ฝไปค้€ŸๆŸฅ
(gdb) b main              # ๅœจ main ๅ‡ฝๆ•ฐ่ฎพๆ–ญ็‚น
(gdb) b mysh.c:120        # ๅœจ็ฌฌ 120 ่กŒ่ฎพๆ–ญ็‚น
(gdb) b exec_pipeline     # ๅœจๅ‡ฝๆ•ฐ่ฎพๆ–ญ็‚น
(gdb) r                   # ่ฟ่กŒ็จ‹ๅบ๏ผˆrun๏ผ‰
(gdb) n                   # ๅ•ๆญฅๆ‰ง่กŒ๏ผˆnext๏ผŒไธ่ฟ›ๅ…ฅๅ‡ฝๆ•ฐ๏ผ‰
(gdb) s                   # ๅ•ๆญฅๆ‰ง่กŒ๏ผˆstep๏ผŒ่ฟ›ๅ…ฅๅ‡ฝๆ•ฐ๏ผ‰
(gdb) c                   # ็ปง็ปญ่ฟ่กŒ๏ผˆcontinue๏ผ‰
(gdb) p pid               # ๆ‰“ๅฐๅ˜้‡ pid ็š„ๅ€ผ
(gdb) p *cmd              # ๆ‰“ๅฐ็ป“ๆž„ไฝ“ๅ†…ๅฎน
(gdb) p cmd->argv[0]      # ๆ‰“ๅฐๆŒ‡้’ˆๆˆๅ‘˜
(gdb) x/s buf             # ไปฅๅญ—็ฌฆไธฒๆ ผๅผๆ˜พ็คบๅ†…ๅญ˜
(gdb) bt                  # ๆ˜พ็คบ่ฐƒ็”จๆ ˆ๏ผˆbacktrace๏ผ‰
(gdb) frame 2             # ๅˆ‡ๆขๅˆฐ่ฐƒ็”จๆ ˆ็ฌฌ2ๅธง
(gdb) info locals         # ๆ˜พ็คบๅฝ“ๅ‰ๅ‡ฝๆ•ฐๆ‰€ๆœ‰ๅฑ€้ƒจๅ˜้‡
(gdb) info registers      # ๆ˜พ็คบๅฏ„ๅญ˜ๅ™จๅ€ผ
(gdb) watch pids[0]       # ็›‘่ง†ๅ˜้‡๏ผˆๅ€ผๆ”นๅ˜ๆ—ถๅœไธ‹๏ผ‰

# ่ฐƒ่ฏ• fork ๅŽ็š„ๅญ่ฟ›็จ‹
(gdb) set follow-fork-mode child    # fork ๅŽ่ทŸ่ธชๅญ่ฟ›็จ‹
(gdb) set follow-fork-mode parent   # fork ๅŽ่ทŸ่ธช็ˆถ่ฟ›็จ‹๏ผˆ้ป˜่ฎค๏ผ‰
(gdb) set detach-on-fork off        # fork ๅŽไธคไธช่ฟ›็จ‹้ƒฝ่ฐƒ่ฏ•๏ผˆๅคๆ‚๏ผ‰

# ๅฎžไพ‹๏ผš่ฐƒ่ฏ•็ฎก้“ๆŒ‚่ตท้—ฎ้ข˜
(gdb) b exec_pipeline
(gdb) r
# ่พ“ๅ…ฅ "ls | grep txt" ่งฆๅ‘ๆ–ญ็‚น
(gdb) n  # ๅ•ๆญฅๅˆฐ pipe() ่ฐƒ็”จ
(gdb) p pipes[0][0]  # ๆŸฅ็œ‹็ฎก้“่ฏป็ซฏ fd ๅท
(gdb) p pipes[0][1]  # ๆŸฅ็œ‹็ฎก้“ๅ†™็ซฏ fd ๅท
# ็กฎ่ฎค fork ๅŽ็ˆถ่ฟ›็จ‹ๆญฃ็กฎๅ…ณ้—ญไบ†ๆ‰€ๆœ‰็ฎก้“ fd

# ่ฐƒ่ฏ•ๅ†…ๅญ˜้—ฎ้ข˜๏ผˆ้…ๅˆ AddressSanitizer๏ผ‰
# ็ผ–่ฏ‘๏ผšgcc -fsanitize=address -g -o mysh-asan mysh.c -lreadline
./mysh-asan
# ASAN ไผšๅœจๅ‡บ็Žฐ use-after-free/buffer-overflow ๆ—ถๆ‰“ๅฐๅฎŒๆ•ดๆŠฅๅ‘Š

11. Extension Exercises

After completing the basic version, try these extensions:

Chapter Summary: The mini shell covers the core patterns of Unix systems programming: fork-exec-wait (process creation), pipe+dup2 (inter-process communication), sigaction (signal handling), open+dup2 (I/O redirection). Master these four syscall combinations and you understand 80% of bash's core implementation โ€” and have a solid foundation for the kernel contributions in Chapter 19.

  Previous
  โ† Ch17: Syscalls


  Next
  Ch19: Kernel Contrib โ†’
Rate this chapter
4.7  / 5  (11 ratings)

๐Ÿ’ฌ Comments