深入理解計算機系統第八章shell實驗

/*
 * unix shell with job control
 *內建命令是fg,bg,jobs,echo,quit,&
 * @copyright 官加文
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<ctype.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<errno.h>

/* 宏定義一些常數 */
#define MAXLINE     1024   //最大的行大小
#define MAXARGS     128    //最多的參數
#define MAXJOBS     16     //在任何時間內最多的作業
#define MAXJID      1<<16  //最大的作業ID,爲65536

/*
 * 1<<16
 * 左移16位
 * 0000000000000001(2)==1
 * 1000000000000000(2)==2^16==65536
 */

/* 定義作業的狀態  */
#define UNDEF 0   //undefined
#define FG    1   //在前臺運行
#define BG    2   //在後臺運行
#define ST    3   //stopped

/*
 * 作業狀態: FG(foreground), BG(background), ST(stopped)
 * 作業狀態變化說明(開始狀態 -> 變化狀態):
 *    FG -> ST  : ctrl-z
 *    ST -> FG  : fg command
 *    ST -> BG  : bg command
 *    BG -> FG  : fg command
 *    沒有FG -> BG的狀態,至多一個作業運行在前臺
 */

/* 全局變量 */
/* c標準庫定義了一個全局變量environ, 保存着環境變量
 * extern引入外部文件的全局變量
 */
extern char **environ;
char prompt[] = "tsh>";  //命令提示符
int verbose = 0;         //如果爲真,打印額外的輸出
int nextjid = 1;         //分配下一個作業ID[1,2,3....]
char sbuf[MAXLINE];      //for composing sprintf(把格式化的數據寫入某個字符串) messages

struct job_t {
    pid_t pid;              //作業的進程組ID
    int jid;                //作業ID,即作業標識符 [1,2, ...]
    int state;              //UNDEF, BG, FG, or ST
    char cmdline[MAXLINE];  //命令行
};

struct job_t jobs[MAXJOBS];   //作業列表


/* 函數原型 */
void eval(char *cmdline);       //解析命令行
int builtin_cmd(char **argv);   //判斷是否是內建命令
void do_bgfg(char **argv);      //實現bg,fg兩個內建命令
void do_echo(char **argv);
void waitfg(pid_t pid);         //等待前臺進程終止


/* 信號處理函數 */
void sigchld_handler(int sig);   //SIGCHLD
void sigtstp_handler(int sig);   //SIGTSTP
void sigint_handler(int sig);    //SIGINT
void sigquit_handler(int sig);   //SIGQUIT,進程在退出時會產生core文件,(相當於程序錯誤信號)

void mask_sigchld_signal(int how, int sig);

int parseline(const char *cmdline, char **argv);

/* 關於作業控制的函數 */
void clear_job(struct job_t *job);
void init_jobs(struct job_t *jobs);
int max_jid(struct job_t *jobs);
int add_job(struct job_t *jobs, pid_t pid, int state, char *cmdline);
int delete_job(struct job_t *jobs, pid_t pid);
pid_t fgpid(struct job_t *jobs);  //前臺進程組長ID
struct job_t *get_job_pid(struct job_t *jobs, pid_t pid);
struct job_t *get_job_jid(struct job_t *jobs, int jid);
int pid2jid(pid_t pid);
void list_jobs(struct job_t *jobs);

void usage(void);   //usage是用法的意思(for -h命令行參數)
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int);   //將void定義爲handler_t
handler_t *Signal(int signum, handler_t *handler);
void Kill(pid_t pid, int signum);


/* 主程序(main routine) */
int main(int argc, char **argv)
{
    char c;
    char cmdline[MAXLINE];
    int emit_prompt = 1;  //默認輸出命令提示符

    /* 重定向標準錯誤流(stderr 文件描述符爲2)
     * 到標準輸出流(stdout 文件描述符爲1)
     * 這樣驅動就能得到所有輸出在被連接到stdout的管道上
     * dup2(oldfd,newfd),被指向oldfd
     */
    dup2(1, 2);

    /* 解析(parse)命令行參數 */
    while ((c = getopt(argc, argv, "hvp")) != EOF) {
        switch (c) {
            case 'h':
                usage();
                break;
            case 'v':
                verbose = 1;   //輸出額外的診斷信息,如果命令行參數有-v時則verbose置爲1
                break;
            case 'p':
                emit_prompt = 0;
                break;
            default:
                usage();  //如果參數有誤,默認參數爲-h
        }
    }

    /* 安裝信號處理程序 */
    Signal(SIGINT, sigint_handler);    //ctrl-c,signum爲2
    Signal(SIGTSTP, sigtstp_handler);  //ctrl-z,signum爲20
    Signal(SIGCHLD, sigchld_handler);  //子進程終止或停止,比如SIGKILL殺死,signum爲17

    /* 和SIGINT類似, 但由QUIT字符(通常是Ctrl-\)來控制.
     * 進程在因收到SIGQUIT退出時會產生core文件,
     * 在這個意義上類似於一個程序錯誤信號,
     * 提供一個乾淨的方式殺死shell
     */

    /* This one provides a clean way to kill the shell */
    Signal(SIGQUIT, sigquit_handler);

    /* 初始化作業列表 */
    init_jobs(jobs);

    /* 讀取命令行 */
    while (1) {
        if (emit_prompt) {
            printf("%s ", prompt);
            fflush(stdout);   //清除緩衝區,爲了立刻看到輸出的東西
        }
        /*  ferror返回0表示未出錯,返回非零表示出錯 */
        if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
            app_error("fgets error\n");
        if (feof(stdin)) {    //ctrl-d 是文件結束
            fflush(stdout);   //清空緩衝區
            exit(0);
        }

        /* 求命令行的值 */
        eval(cmdline);
        fflush(stdout);
    }

    exit(0);   /* 控制永遠不會到達這裏 */
}

void eval(char *cmdline)
{
    char *argv[MAXARGS];
    char buf[MAXLINE];
    int bg;
    pid_t pid;
    strcpy(buf, cmdline);
    bg = parseline(cmdline, argv);
    if (argv[0] == NULL)
        return;
    if (!builtin_cmd(argv)){
        /* 解決經典父進程和子進程的競爭問題:
         * 在調用fork之前阻塞SIGCHLD,保證在子
         * 進程被添加到作業列表之後回收該子進程
         * 即處理add_job與子進程結束的競爭條件
         */
        mask_sigchld_signal(SIG_BLOCK, SIGCHLD);   //阻塞SIGCHLD信號
        if ((pid = fork()) == 0){
            //解除阻塞,因爲子進程繼承了父進程被阻塞信號的集合
            mask_sigchld_signal(SIG_UNBLOCK, SIGCHLD);
            /* After the fork, but before the execve,
             * the child process should call
             * setpgid(0, 0), which puts the child in
             * a new process group whose group ID is
             * identical to the child’s PID. This
             * ensures that there will be only one
             * process, your shell, in the
             * foreground process group.
            */
            if (setpgid(0, 0) < 0) { /* put the child in a new process group */
                unix_error("eval: setpgid failed");
            }
            if (execve(argv[0], argv, environ) < 0){
                printf("%s: command not found\n", argv[0]);
                exit(0);
            }

        }
        else {
           //父進程
            int status;
            if (!bg) {
                add_job(jobs, pid, FG, cmdline);
                //printf("success add FG process\n");
            }
            else {
                add_job(jobs, pid, BG, cmdline);
                //printf("success add BG process\n");
            }

            /* add_job之後解除阻塞 */
            mask_sigchld_signal(SIG_UNBLOCK, SIGCHLD);
            if (!bg) {
                 /* 等待前臺進程終止 */
                waitfg(pid);
            }else {
                printf("[%d]+ (%d) %s", pid2jid(pid), pid, cmdline);
            }
        }

    }

}

int parseline(const char *cmdline, char **argv)
{
    static char array[MAXLINE]; /* holds local copy of command line */
    char *buf = array;          /* ptr that traverses command line */
    char *delim;                /* points to first space delimiter */
    int argc;                   /* number of args */
    int bg;                     /* background job? */

    strcpy(buf, cmdline);
    buf[strlen(buf)-1] = ' ';  /* replace trailing '\n' with space */
    while (*buf && (*buf == ' ')) /* ignore leading spaces */
	buf++;

    /* Build the argv list */
    argc = 0;
    if (*buf == '\'') { //*buf是單個字符
        buf++;
        delim = strchr(buf, '\'');
    }
    else {
	    delim = strchr(buf, ' ');
    }

    while (delim) {
	argv[argc++] = buf; /* 以ls -l爲例,此處buf是"ls-l",argv[0]爲"ls -l"*/
	*delim = '\0';  /* delim從" -l"變爲 " ", buf 變爲"ls",argv[0]變爲"" */
	buf = delim + 1; /* buf 變爲 "-l" */
	while (*buf && (*buf == ' ')) /* ignore spaces */
	       buf++;

	if (*buf == '\'') {
	    buf++;
	    delim = strchr(buf, '\'');
	}
	else {
	    delim = strchr(buf, ' ');
	}
    }
    argv[argc] = NULL;

    if (argc == 0)  /* ignore blank line */
	return 1;

    /* should the job run in the background? */
    if ((bg = (*argv[argc-1] == '&')) != 0) {
	argv[--argc] = NULL;
    }
    return bg;
}

int builtin_cmd(char **argv)
{
    if (!strcmp("quit",argv[0]))
        exit(0);
    if (!strcmp("&",argv[0]))
        return 1;

    /* 增加jobs命令 */
    if (!strcmp("jobs", argv[0])) {
        list_jobs(jobs);
        return 1;
    }

    /* 增加echo命令 */
    if (!strcmp("echo", argv[0])) {
        do_echo(argv);
        return 1;
    }

    /* 增加fg,bg命令 */
    if (!strcmp(argv[0],"fg") || !strcmp(argv[0], "bg")) {
        //printf("bg/fg\n");
        do_bgfg(argv);
        return 1;
    }
    return 0;
}

/* 執行內建的fg和bg命令 */
void do_bgfg(char **argv)
{
    int i, jobid;
    char *id = argv[1];
    struct job_t *job;
    if (id == NULL) {
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return; //退出函數
    }

    if (id[0] == '%') {
        jobid = atoi(++id);   //是++id,不是id++
        if ((job = get_job_jid(jobs, jobid)) == NULL) {
            printf("%s: No such job\n", id);
            return;
        }
    }

    else {
        if (atoi(id) != 0) {
            if ((job = get_job_pid(jobs, atoi(id))) == NULL) {
                printf("(%s): No such process\n", id);
                return;
            }

        }
    }

    if (!strcmp(argv[0], "fg")) {
        job->state = FG;
        //喚醒job,SIGCONT讓終止的進程繼續,signum爲18
        Kill(-1 * job->pid, SIGCONT);  //乘-1是要發送給整個進程組的意思
        waitfg(job->pid);
    }

    if (!strcmp(argv[0], "bg")) {
        job->state = BG;
        //喚醒job,SIGCONT讓終止的進程繼續
        Kill(-1 * job->pid, SIGCONT);  //乘-1是要發送給整個進程組的意思
    }

    return;
}

void do_echo(char **argv)
{
    int i;
    for (i=1; argv[i] != NULL; i++) {
        printf("%s ", argv[i]);
    }
    printf("\n");
}

//一直阻塞,直到不再爲前臺進程組
void waitfg(pid_t pid)
{
    while (pid == fgpid(jobs)) { //死循環監聽
        sleep(0);
    }
    return;
}

/*****************
 * Signal handlers
 *****************/

/*
 * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
 * a child job terminates (becomes a zombie), or stops because it
 * received a SIGSTOP or SIGTSTP signal. The handler reaps all
 * available zombie(殭屍) children, but doesn't wait for any other
 * currently running children to terminate.
 */
/* 子進程成爲殭屍進程的方法有很多種*/
void sigchld_handler(int sig)
{
    int status, child_sig;
    pid_t pid;
    struct job_t *job;
    //WUNTRACED | WNOHANG很重要,不要傻等,不然遇到直接cat的命令會一直阻塞
    while ((pid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0) {
        //printf("Handler child %d\n", (int) pid);
        if (WIFSTOPPED(status)) {  //子進程處理停止信號(這裏假設所有的停止信號都是SIGTSTP,即ctrl-z)
            sigtstp_handler(WSTOPSIG(status));  //直接cat命令獲得信號21(後臺進程從終端讀),但是我也不知道怎麼來的
        }
        else if (WIFSIGNALED(status)) {   //子進程因爲未捕獲的信號而停止
            //printf("yes\n");
            child_sig = WTERMSIG(status);
            if (child_sig == SIGINT)
                sigint_handler(child_sig);
            else
                unix_error("sigchld_handler: unknown signal\n");
        }
        else {
            //printf("yes\n");
            //後臺作業也應該被刪除
            delete_job(jobs, pid);   //如果正常退出
        }
    }
    return;
}

/* SIGINT和SIGTSTP都是針對於前臺進程組,所以要獲得前臺進程組的 */
void sigint_handler(int sig)
{
    //printf("terminated sig\n");
    pid_t pid;

    pid = fgpid(jobs);   //獲得前臺進程ID,不考慮管道,即一個作業只有一個進程
    int jid = pid2jid(pid);
    if (pid != 0) {      //如果有前臺進程則不返回0
        printf("Job [%d] (%d) terminated by signal %d", jid, pid, sig);
        delete_job(jobs,pid);   /* 順序不能變 */
        Kill(-pid, sig);   //發送SIGINT信號
    }

    return;
}

void sigtstp_handler(int sig)
{
    //printf("stop sig\n");
    pid_t pid;
    pid = fgpid(jobs);
    int jid = pid2jid(pid);
    struct job_t *job;

    if (pid != 0) {
        printf("Job [%d] (%d) stopped by signal %d\n", jid, pid, sig);
        job = get_job_pid(jobs, pid);
        job->state=ST;
        Kill(-pid, sig);   //發送SIGTSTP信號
    }
}

void sigquit_handler(int sig)
{
    printf("Terminating after receipt of SIGQUIT signal(core dumped)\n");   //receipt是收到的意思
    exit(1);
}
/* 信號處理函數結束 */

void mask_sigchld_signal(int how, int sig)
{
    sigset_t signals;  //信號集
    if (sigemptyset(&signals) < 0)
        unix_error("sigemptyset failed\n");
    if (sigaddset(&signals, sig) < 0)
        unix_error("sigaddset failed\n");
    if (sigprocmask(how, &signals, NULL) < 0)
        unix_error("sigprocmask failed\n");
    return;
}

/* job函數開始   */
void clear_job(struct job_t *job)
{
    job->pid = 0;
    job->jid = 0;
    job->state = UNDEF;
    job->cmdline[0] = '\0';
}

void init_jobs(struct job_t *jobs)
{
    int i;
    for (i=0; i<MAXJOBS; i++)
        clear_job(&jobs[i]);
}

/* 返回最大分配的作業ID */
int max_jid(struct job_t *jobs)
{
    int i,max=0;

    for (i=0; i<MAXJOBS; i++)
        if (jobs[i].jid > max)
            max = jobs[i].jid;
    return max;
}

/* 增加作業到作業列表  */
int add_job(struct job_t *jobs, pid_t pid, int state, char *cmdline)
{
    int i;

    if (pid < 1)
        return 0;

    for (i=0; i<MAXJOBS; i++){
        if (jobs[i].pid == 0){
            jobs[i].pid = pid;
            jobs[i].state = state;
            jobs[i].jid = nextjid++;
            if (nextjid > MAXJOBS)
                nextjid = 1;   //如果作業ID超過了作業列表的大小,則重新置爲1
            strcpy(jobs[i].cmdline, cmdline);
            if (verbose){   //如果命令行參數有-v時則執行此語句塊
                printf("Added job [%d] %d %s\n",jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
            }
            return 1;
        }
    }
    printf("Tried to create too many jobs\n");  //當作業列表被佔滿時
    return 0;
}

/* 刪除作業 */
int delete_job(struct job_t *jobs, pid_t pid)
{
    //printf("delete job\n");
    int i;

    if (pid < 1)
        return 0;

    for (i=0; i<MAXJOBS; i++){
        if (jobs[i].pid == pid){
            clear_job(&jobs[i]);
            nextjid = max_jid(jobs)+1;  //很有技巧
            return 1;
        }
    }
    return 0;
}

/* 返回當前前臺進程的PID,如果沒有則爲0 */
pid_t fgpid(struct job_t *jobs)
{
    int i;

    for (i=0; i<MAXJOBS; i++)
        if (jobs[i].state == FG)
            return jobs[i].pid;
    return 0;
}

/* 根據PID返回所查詢的作業 */
struct job_t *get_job_pid(struct job_t *jobs, pid_t pid)
{
    int i;

    if (pid < 1)
        return NULL;
    for (i=0; i<MAXJOBS; i++)
        if (jobs[i].pid == pid)
            return &jobs[i];
    return NULL;
}

/* 根據JID返回所查詢的作業 */
struct job_t *get_job_jid(struct job_t *jobs, int jid)
{
    int i;

    if (jid < 1)
        return NULL;
    for (i=0; i<MAXJOBS; i++)
        if (jobs[i].jid == jid)
            return &jobs[i];
    return NULL;
}

/* 把進程ID映射到作業ID */
int pid2jid(pid_t pid)
{
    int i;

    if (pid < 1)
        return 0;
    for (i=0; i<MAXJOBS; i++)
        if (jobs[i].pid == pid)
            return jobs[i].jid;
    return 0;
}

void list_jobs(struct job_t *jobs)
{
    int i;

    for (i=0; i<MAXJOBS; i++){
        if (jobs[i].pid != 0){
            printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
            switch (jobs[i].state) {
                case BG:
                    printf("Running ");
                    break;
                case FG:
                    printf("Foreground ");
                    break;
                case ST:
                    printf("Stopped ");
                    break;
                default:
                    printf("listjobs: Internal error: job[%d].state=%d ",
                            i, jobs[i].state);
            }
            printf("%s\n", jobs[i].cmdline);
        }
    }
    //exit(0);  會直接退出整個程序
}

/* 打印幫助信息 */
void usage(void)
{
    printf("Usage:  shell [-hvp]\n");
    printf("   -h:  print this message\n");
    printf("   -v:  print additional diagnostic information\n");  //diagnostic是診斷的意思
    printf("   -p:  do not emit a cmmand prompt\n");
    exit(1);
}

void unix_error(char *msg)
{
    fprintf(stdout, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

void app_error(char *msg)
{
    fprintf(stdout, "%s\n", msg);
    exit(1);
}

/* sigaction的包裝函數  */
handler_t *Signal(int signum, handler_t *handler)
{
    struct sigaction action, old_action;

    action.sa_handler = handler;
    sigemptyset(&action.sa_mask); /* block sigs of type being handled */
    action.sa_flags = SA_RESTART; /* restart syscalls if possible */

    if (sigaction(signum, &action, &old_action) < 0)
	unix_error("Signal error");
    return (old_action.sa_handler);
}

void Kill(pid_t pid, int signum)
{
    int rc;
    if ((rc = kill(pid, signum)) < 0)
        unix_error("kill error");
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章