本文章就是在做ShellLab的源码以及对于内容的讲解,因为我自身比较菜
因此其中的解释对于小白来说还是比较的友好的,其中思路代码等都来自于郭郭
建议读的顺序是sigint sigstop -> sigchld_hanlder -> waitfg -> builtin_command -> do_fg -> eval
做之前需要通读一遍csapp第八章的内容,尤其是waitpid中的各种参数、sigprocmask进行信号屏蔽的例子要看懂,信号中断的处理。。。共勉,一起加油!
#include#include #include #include #include #include #include #include #include #define MAXLINE 1024 #define MAXARGS 128 #define MAXJOBS 16 #define MAXJID 1<<16 #define UNDEF 0 #define FG 1 #define BG 2 #define ST 3 extern char **environ; char prompt[] = "tsh> "; int verbose = 0; int nextjid = 1; char sbuf[MAXLINE]; struct job_t { pid_t pid; int jid; int state; char cmdline[MAXLINE]; }; struct job_t jobs[MAXJOBS]; void eval(char *cmdline); int builtin_cmd(char **argv); void do_bgfg(char **argv); void waitfg(pid_t pid); void sigchld_handler(int sig); void sigtstp_handler(int sig); void sigint_handler(int sig); int parseline(const char *cmdline, char **argv); void sigquit_handler(int sig); void clearjob(struct job_t *job); void initjobs(struct job_t *jobs); int maxjid(struct job_t *jobs); int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline); int deletejob(struct job_t *jobs, pid_t pid); pid_t fgpid(struct job_t *jobs); struct job_t *getjobpid(struct job_t *jobs, pid_t pid); struct job_t *getjobjid(struct job_t *jobs, int jid); int pid2jid(pid_t pid); void listjobs(struct job_t *jobs); void usage(void); void unix_error(char *msg); void app_error(char *msg); typedef void handler_t(int); handler_t *Signal(int signum, handler_t *handler); int main(int argc, char **argv) { char c; char cmdline[MAXLINE]; int emit_prompt = 1; dup2(1, 2); while ((c = getopt(argc, argv, "hvp")) != EOF) { switch (c) { case 'h': usage(); break; case 'v': verbose = 1; break; case 'p': emit_prompt = 0; break; default: usage(); } } Signal(SIGINT, sigint_handler); Signal(SIGTSTP, sigtstp_handler); Signal(SIGCHLD, sigchld_handler); Signal(SIGQUIT, sigquit_handler); initjobs(jobs); while (1) { if (emit_prompt) { printf("%s", prompt); fflush(stdout); } if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin)) app_error("fgets error"); if (feof(stdin)) { fflush(stdout); exit(0); } eval(cmdline); fflush(stdout); fflush(stdout); } exit(0); } void eval(char *cmdline) { //在原书中P543中,我们通过mask解决了子进程早于父进程执行从而出错的情况 //我们在子进程中解掉block 因为子进程同样继承了父进程的mask,但是如果子进程再fork的话,它的子进程不应该被block,因此子进程中应该解除block char * argv[MAXARGS]; int bg; //judge if is bg pid_t pid; sigset_t mask; bg = parseline(cmdline, argv); if (!builtin_cmd(argv)) { //掩码不在函数外面加是因为:我们上面函数如果是系统调用就直接执行就好了,不需要进行阻塞 //函数能执行到这里就代表不是系统调用了,那么我们就是应该进行阻塞,因为存在有sigchld_handler 在外面 sigemptyset(&mask); sigaddset(&mask, SIGCHLD); //阻塞回收子进程 sigprocmask(SIG_BLOCK, &mask, NULL); if ((pid = fork()) < 0) unix_error("forking error"); else if (pid == 0) { //shell 需要创建一个新的子进程执行我们锁输入的命令 sigprocmask(SIG_UNBLOCK, &mask, NULL); //上面讲述的,解除掉子进程中的block setpgid(0, 0); //创建新的进程组(进程组的ID就是目前进程的ID),不然如果前台有一个kill就将父进程和子进程都杀掉了 if (execvp(argv[0], argv) < 0) { //执行文件 argv[0], 后面的就作为参数进行传递 printf("%s command not foundn", argv[0]); exit(1); } }else { //parent process add job if (!bg) { addjob(jobs, pid, FG, cmdline); }else { addjob(jobs, pid, BG, cmdline); } sigprocmask(SIG_UNBLOCK, &mask, NULL); //上面的都是被mask进行包住了,比如说更改共享变量的时候我们就相当于上了一把锁一样 if (!bg) { waitfg(pid); }else { printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline); } } } } int parseline(const char *cmdline, char **argv) { static char array[MAXLINE]; char *buf = array; char *delim; int argc; int bg; strcpy(buf, cmdline); buf[strlen(buf)-1] = ' '; while (*buf && (*buf == ' ')) buf++; argc = 0; if (*buf == ''') { buf++; delim = strchr(buf, '''); } else { delim = strchr(buf, ' '); } while (delim) { argv[argc++] = buf; *delim = ' '; buf = delim + 1; while (*buf && (*buf == ' ')) buf++; if (*buf == ''') { buf++; delim = strchr(buf, '''); } else { delim = strchr(buf, ' '); } } argv[argc] = NULL; if (argc == 0) return 1; if ((bg = (*argv[argc-1] == '&')) != 0) { argv[--argc] = NULL; } return bg; } int builtin_cmd(char **argv) //判断是否是内置的函数quit,bg,&,jobs, 是的话进行处理 & return 1 { if (strcmp(argv[0], "quit") == 0) { exit(0); //return 1; }else if (strcmp(argv[0],"jobs") == 0) { //call function : listjobs listjobs(jobs); return 1; }else if (strcmp(argv[0], "&") == 0) { //试过了,只输入&的话 啥也不是 //bash: syntax error near unexpected token `&' return 1; }else if (strcmp(argv[0], "bg") == 0 | strcmp(argv[0], "fg") == 0 ) { //看看下一个函数就是do_bgfg do_bgfg(argv); return 1; } return 0; } void do_bgfg(char **argv) { struct job_t * job; pid_t pid; int jid; int status; char * tmp; tmp = argv[1]; if (tmp == NULL) { printf("%s command requires PID or %%jobid argumentn", argv[0]); return ; } if (tmp[0] == '%') { //input is a jobid jid = atoi(tmp + 1); job = getjobjid(jobs, jid); //通过jid找到那个需要执行的job if (job == NULL) { printf("%s: No such jobn", tmp); return ; }else { pid = job -> pid; } }else if (isdigit(atoi(&tmp))) { //只是数字的话那么就是输入的是进程ID了 pid = atoi(tmp); job = getjobpid(jobs, pid); if (job == NULL) { printf("(%d): No such processn", pid); return ; } }else { printf("%s: argument must be a PID or %%jobidn", argv[0]); } //现在拿到了需要执行的job kill(pid, SIGCONT); //不是kill,是继续执行的意思,本来就工作就继续,没有工作就开始工作 //已经处理完job了,下面看一下前台后台的处理 if (strcmp(argv[0], "fg") == 0) { job -> state = FG; //将任务的状态写成是FG:前台 waitfg(job -> pid); //在这个前台任务完成之前,都是一直block的 }else { printf("[%d] (%d) %s", job -> jid, job -> pid, job -> cmdline); job -> state = BG; } } void waitfg(pid_t pid) { //就是如果前台的程序是pid的话我们就一直等待着 //首先判断一下给的pid这个任务是否是存在的 struct jobs * job = getjobpid(jobs, pid); if (job == NULL) return ; //根本就没有这个程序,直接退出 while(fgpid(jobs) == pid) { //spin ..... until pid is not foreground } } //为什么处理信号的时候不需要进行block呢? //因为信号是不会进行计数的,如果本身存在了一个信号的话,你来多少信号我都只知道:现在存在一个信号执行,并不关心有多少个 //第二个同类型的信号是不会打断上一个正在处理的信号的:-) void sigchld_handler(int sig) { int olderrno = errno; pid_t pid; int status; while (waitpid(-1, &status, WNOHANG | WUNTRACED) > 0) { if (WIFEXITED(status)) { //通过正常的exit进行的退出 deletejob(jobs, pid); }else if (WIFSIGNALED(status)) { //通过被信号杀掉 int jid = pid2jid(pid); printf("Job [%d] (%d) terminated by signal %dn", jid, pid, WTERMSIG(status)); //返回导致被信号杀掉的pid,这两个函数是搭配使用的 deletejob(jobs, pid); }else { //子进程已经是stop了 struct job_t * job = getjobpid(jobs, pid); //获得指向已经退出子进程的结构体的指针,我们更改它的状态 job -> state = ST; //修改进程的状态为stop int jid = pid2jid(pid); printf("Job [%d] (%d) Stopped by signal %dn", jid, pid, WSTOPSIG(status)); } } errno = olderrno; } void sigint_handler(int sig) { pid_t pid = fgpid(jobs); if (pid == 0) //前台没有进程,直接return return; kill(-pid, sig); } void sigtstp_handler(int sig) { pid_t pid = fgpid(jobs); if (pid == 0) //前台没有进程,直接return return; kill(-pid, sig); } void clearjob(struct job_t *job) { job->pid = 0; job->jid = 0; job->state = UNDEF; job->cmdline[0] = ' '; } void initjobs(struct job_t *jobs) { int i; for (i = 0; i < MAXJOBS; i++) clearjob(&jobs[i]); } int maxjid(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 addjob(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) { //如果这个存放job的结构还没有被占用,就分配给当前的进程 jobs[i].pid = pid; jobs[i].state = state; jobs[i].jid = nextjid++; //golbal argument : nextjid 是下一个可以分配的jobid, 如果用完了就置为1 if (nextjid > MAXJOBS) nextjid = 1; strcpy(jobs[i].cmdline, cmdline); if(verbose){ //是否打印其他的信息 printf("Added job [%d] %d %sn", jobs[i].jid, jobs[i].pid, jobs[i].cmdline); } return 1; } } printf("Tried to create too many jobsn"); return 0; } int deletejob(struct job_t *jobs, pid_t pid) { int i; if (pid < 1) return 0; for (i = 0; i < MAXJOBS; i++) { if (jobs[i].pid == pid) { clearjob(&jobs[i]); nextjid = maxjid(jobs)+1; return 1; } } return 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; } struct job_t *getjobpid(struct job_t *jobs, pid_t pid) { //通过pid找到任务列表中的任务(返回的是结构体指针) int i; if (pid < 1) return NULL; for (i = 0; i < MAXJOBS; i++) if (jobs[i].pid == pid) return &jobs[i]; return NULL; } struct job_t *getjobjid(struct job_t *jobs, int jid) //通过jid找到任务,返回的是任务的结构体指针 { int i; if (jid < 1) return NULL; for (i = 0; i < MAXJOBS; i++) if (jobs[i].jid == jid) return &jobs[i]; return NULL; } int pid2jid(pid_t pid) //通过pid找到jobid { 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 listjobs(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", jobs[i].cmdline); } } } void usage(void) { printf("Usage: shell [-hvp]n"); printf(" -h print this messagen"); printf(" -v print additional diagnostic informationn"); printf(" -p do not emit a command promptn"); exit(1); } void unix_error(char *msg) { fprintf(stdout, "%s: %sn", msg, strerror(errno)); exit(1); } void app_error(char *msg) { fprintf(stdout, "%sn", msg); exit(1); } handler_t *Signal(int signum, handler_t *handler) { struct sigaction action, old_action; action.sa_handler = handler; sigemptyset(&action.sa_mask); action.sa_flags = SA_RESTART; if (sigaction(signum, &action, &old_action) < 0) unix_error("Signal error"); return (old_action.sa_handler); } void sigquit_handler(int sig) { printf("Terminating after receipt of SIGQUIT signaln"); exit(1); }



