csapp实验,一个简单的shell. Lab Assignment L5: Writing Your Own Unix Shell
来源:互联网 发布:js 对象作为参数 编辑:程序博客网 时间:2024/04/30 08:02
实验指导书 http://csapp.cs.cmu.edu/3e/shlab.pdf
该知道的在实验指导书都有了,以下是感觉这个实验重要的地方
- 清楚前台和后台的概念,这是shell创造的概念,有外部命令时我们直接去运行的,只不过如果是前台命令会去等待执行完毕,而后台不管。所以信号处理函数都只是对shell而言,按下ctrl+z和ctrl+c都是发给shell的。(这里有一个进程组的概念,信号默认是发给前台进程组的,所以现在发的就是我们自己写的shell了,为了不让外部命令创建的子进程接收到这些信号,要在创建子进程时设置单独的进程组号)
- 要清楚什么时候要阻塞什么信号,为什么要阻塞。像创建子进程时阻塞SIGCHLD是因为可能子进程运行完了,父进程才往jobs里add一个job,为了防止这种情况,要阻塞这个信号,add完再取消阻塞。但不能阻塞全部信号,因为可能收到SIGTSTP或SIGINT。
- 而在SIGCHLD的处理函数中,我阻塞了所有信号,现在看来似乎没有必要,因为另外两个信号处理函数没有对全局的jobs进行操作。不过在这个程序里也没所谓了。信号处理函数最好写得简单一点,waitpid写的地方也少一点,不然就会很乱。下面列下书上说的保守的好习惯吧
- 处理程序尽可能简单。处理程序可能只是简单地设置全局标志并立即返回,所有与接收信号相关的处理都由主程序执行,主程序周期性检查并重置这个标志。
- 在处理程序中只调用异步信号安全函数。这里的异步信号安全函数指的是:要么函数可重入(如至访问局部变量),要么不能被信号处理程序后中断。
- 保存和恢复errno。防止干扰errno
- 阻塞所有信号。保护对共享全局数据结构的访问。像我们这里只有一个处理函数对jobs进行访问,其实可以补阻塞所有信号的。不过这也是一个好习惯吧,毕竟如果哪一天改了,却忘了这里有个隐患呢?
- 用volatile声明全局变量,volatile声明的会告诉编译器补要缓存这个变量,总是从内从读取。
- 使用sig_atomic_t标志,这可以保证对标量的读写是不可中断的(原子的)。
- 哦,还有就是正确使用waitpid的参数,waitpid是默认阻塞的。摄者WNOHANG可以立即返回,设置WUNTRACED可以检查已终止和被停止的子进程,这在前台等待那里有用。
- 还有就是shell在发信号到前台是发到前台整个进程组的,所以kill的时候记得要负数。
- 信号处理函数和收到信号前正在运行的函数是并发的,如果把waitpid这种相对耗时的操作不放在信号处理函数,会不符合实验指导书要求。而且似乎把waitpid放在信号处理函数也更合理。
因为电脑是Windows的,所以敲代码在sublime,然后放到CLion上看看有没有warning,没有就上传到云服务器的Linux下跑,出错的话都是先去思考,想不到才去gdb调试,gdb调试没有IDE那么爽,到断点不能同时看到几个变量的值(也许是不会用),只能一个一个去p。感觉按键盘也没有点鼠标那么快捷。不过还是要多点熟悉Linux下调试吧。打算重装个图形化虚拟机,然后用CLion调试。
把代码放到sublime上会发现好看很多哈,感觉比在CLion上还舒服,而且跳转也方便不少。
在sublime下
在CLion下
/* * tsh - A tiny shell program with job control * * <wrc> */#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>/* Misc manifest constants */#define MAXLINE 1024 /* max line size */#define MAXARGS 128 /* max args on a command line */#define MAXJOBS 16 /* max jobs at any point in time */#define MAXJID 1<<16 /* max job ID *//* Job states */#define UNDEF 0 /* undefined */#define FG 1 /* running in foreground */#define BG 2 /* running in background */#define ST 3 /* stopped *//* * Jobs states: FG (foreground), BG (background), ST (stopped) * Job state transitions and enabling actions: * FG -> ST : ctrl-z * ST -> FG : fg command * ST -> BG : bg command * BG -> FG : fg command * At most 1 job can be in the FG state. *//* Global variables */extern char **environ; /* defined in libc */char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */int verbose = 0; /* if true, print additional output */int nextjid = 1; /* next job ID to allocate */char sbuf[MAXLINE]; /* for composing sprintf messages *///是否正在等待的前台完成volatile sig_atomic_t waiting =0;volatile sig_atomic_t waitingPid =-1;//正在等待的前台的pid,仅当waiting为1时有效struct job_t { /* The job struct */ pid_t pid; /* job PID */ int jid; /* job ID [1, 2, ...] */ int state; /* UNDEF, BG, FG, or ST */ char cmdline[MAXLINE]; /* command line */};struct job_t jobs[MAXJOBS]; /* The job list *//* End global variables *//* Function prototypes *//* Here are the functions that you will implement */void eval(char *cmdline);int builtin_cmd(char **argv);void do_bgfg(char **argv);void waitfg(pid_t pid, sigset_t *set);void sigchld_handler(int sig);void sigtstp_handler(int sig);void sigint_handler(int sig);//包装函数void Sigfillset(sigset_t *);void Sigemptyset(sigset_t *);void Sigaddset(sigset_t *, int);void Sigprocmask(int how, const sigset_t *set, sigset_t *oldsest);/* Here are helper routines that we've provided for you */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);/* * main - The shell's main routine */int main(int argc, char **argv) { char c; char cmdline[MAXLINE]; int emit_prompt = 1; /* emit prompt (default) */ /* Redirect stderr to stdout (so that driver will get all output * on the pipe connected to stdout) */ dup2(1, 2); /* Parse the command line */ while ((c = getopt(argc, argv, "hvp")) != EOF) { switch (c) { case 'h': /* print help message */ usage(); break; case 'v': /* emit additional diagnostic info */ verbose = 1; break; case 'p': /* don't print a prompt */ emit_prompt = 0; /* handy for automatic testing */ break; default: usage(); } } /* Install the signal handlers */ /* These are the ones you will need to implement */ Signal(SIGINT, sigint_handler); /* ctrl-c */ Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */ Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */ /* This one provides a clean way to kill the shell */ Signal(SIGQUIT, sigquit_handler); /* Initialize the job list */ initjobs(jobs); /* Execute the shell's read/eval loop */ while (1) { /* Read command line */ if (emit_prompt) { printf("%s", prompt); fflush(stdout); } if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin)) app_error("fgets error"); if (feof(stdin)) { /* End of file (ctrl-d) */ fflush(stdout); exit(0); } /* Evaluate the command line */ eval(cmdline); fflush(stdout); fflush(stdout); } exit(0); /* control never reaches here */}/* * eval - Evaluate the command line that the user has just typed in * * If the user has requested a built-in command (quit, jobs, bg or fg) * then execute it immediately. Otherwise, fork a child process and * run the job in the context of the child. If the job is running in * the foreground, wait for it to terminate and then return. Note: * each child process must have a unique process group ID so that our * background children don't receive SIGINT (SIGTSTP) from the kernel * when we type ctrl-c (ctrl-z) at the keyboard.*/void eval(char *cmdline) { char *argv[MAXARGS + 1]; int bg = parseline(cmdline, (char **) &argv); if (!builtin_cmd((char **) &argv)) { pid_t pid; sigset_t mask_one, prev_one; //其实好像不需要阻塞全部信号 //Sigfillset(&mask_all); Sigemptyset(&mask_one); Sigaddset(&mask_one, SIGCHLD); //这里要显示阻塞CHILD信号,防止子进程提早执行完但父进程还未addjob Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); if ((pid = fork()) < 0) { unix_error("Fork error"); } if (pid == 0) {//Child runs here Sigprocmask(SIG_SETMASK, &prev_one, NULL);//unblock 子进程的SIGCHLD setpgid(pid, 0); if (execve(argv[0], argv, environ) < 0) { printf("%s: Command not found\n", argv[0]); exit(1); } } //Sigprocmask(SIG_BLOCK, &mask_all, NULL); addjob(jobs, pid, ((bg == 1) ? BG : FG), cmdline); //Sigprocmask(SIG_SETMASK, &mask_one, NULL); //这里是不是有点多此一举?应该不是,毕竟ctrl+z和ctrl+c的信号不应该在这里阻塞的,不然就死锁了 if (!bg) { waiting=1; waitingPid=pid; waitfg(pid, &prev_one); } else { printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline); } Sigprocmask(SIG_SETMASK, &prev_one, NULL); } return;}/* * parseline - Parse the command line and build the argv array. * * Characters enclosed in single quotes are treated as a single * argument. Return true if the user has requested a BG job, false if * the user has requested a FG job. */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++; delim = strchr(buf, '\''); } else { delim = strchr(buf, ' '); } while (delim) { argv[argc++] = buf; *delim = '\0'; buf = delim + 1; 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;}/* * builtin_cmd - If the user has typed a built-in command then execute * it immediately. */int builtin_cmd(char **argv) { if (!strcmp(argv[0], "quit")) { //是不是还需要回收子进程? while (waitpid(-1, NULL, 0) > 0); exit(0); } if (!strcmp(argv[0], "jobs")) { listjobs(jobs); return 1; } if (!strncmp(argv[0], "bg", 2) || !strncmp(argv[0], "fg", 2)) { do_bgfg(argv); return 1; } return 0; /* not a builtin command */}/* * do_bgfg - Execute the builtin bg and fg commands */void do_bgfg(char **argv) { int bOrf, isJob, id; char *temp; if (!strncmp(argv[0], "bg", 2)) { bOrf = 0; } else { bOrf = 1; } if(argv[1]==NULL){ if(!bOrf){ printf("bg command requires PID or %%jobid argument\n"); }else{ printf("fg command requires PID or %%jobid argument\n"); } return; } struct job_t *job; if (!strncmp(argv[1], "%%", 1)) { isJob = 1; temp = argv[1]+1; id = atoi(temp); job = getjobjid(jobs, id); } else { isJob = 0; temp = argv[1]; id = atoi(temp); job = getjobpid(jobs, id); } if(id==0){ if(bOrf){ printf("fg: argument must be a PID or %%jobid\n"); }else{ printf("bg: argument must be a PID or %%jobid\n"); } return; } if (job == NULL) { if (!isJob) { printf("(%d): No such process\n",id); } else { printf("%%%d: No such job\n",id); } return; } else { if (0 == bOrf) {//bg job->state = BG; kill(-job->pid, SIGCONT); printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline); } else {//fg //waitfg job->state = FG; sigset_t mask, prev; Sigemptyset(&mask); Sigaddset(&mask, SIGCHLD); //这里要显示阻塞CHILD信号,防止子进程唤醒,执行完,回收完了,父进程才去判断,就会一直等 Sigprocmask(SIG_BLOCK, &mask, &prev); waiting=1; waitingPid=job->pid; kill(-job->pid, SIGCONT); waitfg(job->pid, &prev); Sigprocmask(SIG_SETMASK, &prev, NULL); } } return;}/* * waitfg - Block until process pid is no longer the foreground process */void waitfg(pid_t pid, sigset_t *set) { while (waiting==1) { sigsuspend(set); } 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 temp = errno; pid_t cpid; int status; sigset_t mask_all, prev_one; Sigfillset(&mask_all); while ((cpid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0) { //这里应该判断是挂起还是终止,根据不同来更新或删除job Sigprocmask(SIG_BLOCK, &mask_all, &prev_one); if(cpid==waitingPid) waiting=0; if (WIFEXITED(status)) { deletejob(jobs, cpid); } else if (WIFSIGNALED(status)) { printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(cpid), cpid, WTERMSIG(status)); deletejob(jobs, cpid); } else if (WIFSTOPPED(status)) { struct job_t *fjob = getjobpid(jobs, cpid); fjob->state = ST; printf("Job [%d] (%d) stopped by signal 20\n", pid2jid(cpid), cpid); } Sigprocmask(SIG_SETMASK, &prev_one, NULL); } errno = temp; return;}/* * sigint_handler - The kernel sends a SIGINT to the shell whenver the * user types ctrl-c at the keyboard. Catch it and send it along * to the foreground job. */void sigint_handler(int sig) { int temp = errno; pid_t fpid; fpid = fgpid(jobs); if (fpid > 0) { kill(-fpid, SIGINT);//向整个进程组发 } errno = temp; return;}/* * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever * the user types ctrl-z at the keyboard. Catch it and suspend the * foreground job by sending it a SIGTSTP. */void sigtstp_handler(int sig) { pid_t fpid; fpid = fgpid(jobs); if (fpid > 0) { kill(-fpid, SIGTSTP);//向整个进程组发 } return;}/********************* * End signal handlers *********************//*********************************************** * Helper routines that manipulate the job list **********************************************//* clearjob - Clear the entries in a job struct */void clearjob(struct job_t *job) { job->pid = 0; job->jid = 0; job->state = UNDEF; job->cmdline[0] = '\0';}/* initjobs - Initialize the job list */void initjobs(struct job_t *jobs) { int i; for (i = 0; i < MAXJOBS; i++) clearjob(&jobs[i]);}/* maxjid - Returns largest allocated job ID */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;}/* addjob - Add a job to the job list */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) { jobs[i].pid = pid; jobs[i].state = state; jobs[i].jid = nextjid++; if (nextjid > MAXJOBS) nextjid = 1; strcpy(jobs[i].cmdline, cmdline); if (verbose) { 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;}/* deletejob - Delete a job whose PID=pid from the job list */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;}/* fgpid - Return PID of current foreground job, 0 if no such job */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;}/* getjobpid - Find a job (by PID) on the job list */struct job_t *getjobpid(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;}/* getjobjid - Find a job (by JID) on the job list */struct job_t *getjobjid(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;}/* pid2jid - Map process ID to job 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;}/* listjobs - Print the job list */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); } }}/****************************** * end job list helper routines ******************************//*********************** * Other helper routines ***********************//* * usage - print a help message */void usage(void) { printf("Usage: shell [-hvp]\n"); printf(" -h print this message\n"); printf(" -v print additional diagnostic information\n"); printf(" -p do not emit a command prompt\n"); exit(1);}/* * unix_error - unix-style error routine */void unix_error(char *msg) { fprintf(stdout, "%s: %s\n", msg, strerror(errno)); exit(1);}/* * app_error - application-style error routine */void app_error(char *msg) { fprintf(stdout, "%s\n", msg); exit(1);}/* * Signal - wrapper for the sigaction function */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);}/* * sigquit_handler - The driver program can gracefully terminate the * child shell by sending it a SIGQUIT signal. */void sigquit_handler(int sig) { printf("Terminating after receipt of SIGQUIT signal\n"); exit(1);}void Sigfillset(sigset_t *set) { if (sigfillset(set) < 0) { unix_error("sigfillset error"); }}void Sigemptyset(sigset_t *set) { if (sigemptyset(set) < 0) { unix_error("sigemptyset error"); }}void Sigaddset(sigset_t *set, int num) { if (sigaddset(set, num) < 0) { unix_error("sigaddset error"); }}void Sigprocmask(int how, const sigset_t *set, sigset_t *oldsest) { if (sigprocmask(how, set, oldsest) < 0) { unix_error("sigprocmask error"); }}
阅读全文
0 0
- csapp实验,一个简单的shell. Lab Assignment L5: Writing Your Own Unix Shell
- 【CSAPP】Shell Lab 外壳实验
- CSAPP: shell lab
- CSAPP: Shell Lab
- CSAPP shell-lab
- csapp-lab5 shell-lab
- unix shell 脚本 (最简单的一个)
- Writing Your Own Packer
- CSAPP LAB---MALLOC实验
- Writing your own netfilter match
- csapp bomb lab:csapp lab2 炸弹实验
- 【CSAPP】proxy Lab代理实验
- CSAPP实验2:Bomb Lab
- 一个简单的shell
- 一个简单的shell
- UNIX程序设计实验一:实现带参数的简单的shell
- Linux and Unix Shell -- 一些简单的Shell脚本
- unix shell(壳)的简单实现
- 初识lct——洛谷p3203 bzoj2002 [HNOI2010]BOUNCE 弹飞绵羊
- DIV+CSS
- C语言--双链表
- Android 多线程
- 给初学者的RxJava2.0教程(七)
- csapp实验,一个简单的shell. Lab Assignment L5: Writing Your Own Unix Shell
- assert 的理解
- 进程组 会话 作业
- 深入学习Hibernate4_02 hibernate.cfg.xml文件和xxx.hbm.xml文件详解
- gdb调试多进程和多线程
- 算法 排序算法之插入排序--直接插入排序和希尔排序
- elf动态链接实例
- MQTT--HIVEMQ
- 13.activiti工作流-用户任务(userTask,即用户操作的任务)