此实验目的是为了更加熟悉进程控制和信号。该实验内容是实现一个简单的Unix Shell程序,其支持job control。
准备步骤及基础要求:
shell的具体要求:
首先解析命令行,调用builtin_cmd判断是否是内置命令
如果是内置命令,执行对应的内置命令
否则,fork一个子进程,execv 对应的代码,将其添加到 job 列表中
需要考虑信号的使用
void eval(char *cmdline)
{char *argv[MAXARGS];//存储解析后的参数char buf[MAXLINE];int bg;//job 是否 后台运行pid_t pid;//创建的子进程的pid,用于构建jobsigset_t mask_all, mask_chld, prev;strcpy(buf, cmdline);bg = parseline(buf, argv);//解析命令行,返回值为该任务是否为一个前台任务if(argv[0] == NULL){return; //空命令行,不执行操作}//如果不是内置命令,则fork子进程if(!builtin_cmd(argv)){//注意,参考书籍P542页,因为我们设置了SIGCHLD信号的回调//为了防止,父进程fork子进程之后,还未执行到addjobs的逻辑,子进程就执行完毕,发送了SIGCHLD信号//导致父进程处理信号,先调用了deletejobs而导致错误,此时应该先屏蔽SIGCHLD信号sigemptyset(&mask_chld);sigaddset(&mask_chld, SIGCHLD);// Signal(SIGCHLD, sigchld_handler);//注册SIGCHLD信号的处理函数,用于父进程回收子进程的资源sigprocmask(SIG_BLOCK, &mask_chld, &prev);//父进程阻塞SIGCHLD信号//创建子进程,environ为父进程的环境变量if((pid == fork()) == 0){//由于子进程完全的复制了父进程,所以需要先解除子进程对SIGCHLD信号的阻塞sigprocmask(SIG_SETMASK, &prev, NULL);if(execve(argv[0], argv, environ)){printf("%s: Command not found\n", argv[0]);exit(0);}}//此处父进程执行,子进程的逻辑已经被更改,会重新执行自己的main函数//父进程阻塞所有信号,防止被打断去执行别的处理sigfillset(&mask_all);sigprocmask(SIG_BLOCK, &mask_all, NULL);//添加任务,fg为1,bg为2addjob(jobs, pid, bg + 1, cmdline);//恢复原来的状态,不再阻塞SIGCHLDsigprocmask(SIG_SETMASK, &prev, NULL);if(!bg){//前台执行,父进程等待子进程执行完毕waitfg(pid);}else{printf("[%d] (%d) %s\n", pid2jid(pid), pid, cmdline);//返回main函数,等待下一个任务}}return;
}
int builtin_cmd(char **argv)
{if(!strcmp(argv[0], "quit")){exit(0); //quit 命令}else if(!strcmp(argv[0], "jobs")){listjobs(jobs);}else if(!(strcmp(argv[0], "fg")) || !(strcmp(argv[0], "bg"))){do_bgfg(argv);}return 0; /* not a builtin command */
}
volatile sig_atomic_t wait_pid = 0;
void waitfg(pid_t pid)
{sigset_t mask, prev;sigemptyset(&mask);while(1){sigprocmask(SIG_SETMASK, &mask, &prev);if(!fgpid(jobs)){return;}sleep(1);sigprocmask(SIG_SETMASK, &prev, NULL);}// sigprocmask(SIG_SETMASK, &mask, &prev);// if(wait_pid == -1){// wait_pid = 0;// return;// }// wait_pid = pid;// while(wait_pid != -1){// sleep(1);// //sigsuspend(&prev);// }// sigprocmask(SIG_SETMASK, &prev, NULL);return;
}
取出第二个参数,得到要挪到前台/后台的 job 的 pid或者 jid,判断有无 %前缀,如果是%5,则表示 jid = 5,没有%则表示pid=5
向该pid进程发送SIGCONT信号,然后更新该任务的状态
char* arg1 = argv[1];int pid;int jid;struct job_t *cur;//当前的任务if(arg1 == NULL){printf("%s command requires PID or %%jobid argument\n", argv[0]);return;}//参数为jidif(arg1[0] == '%'){jid = atoi(((char *)arg1 + 1));if(jid == 0 && strcmp(((char *)arg1 + 1),"0")){printf("%s argument must be a PID or %%jobid\n", argv[0]);return;}cur = getjobjid(jobs, jid);if(cur == NULL){printf("%s: No such job\n", arg1);return;}pid = cur->pid;}else{//参数为pidpid = atoi(arg1);if(pid == 0 && strcmp(arg1, "0")){printf("%s: argument must be a PID or %%jobid\n", argv[0]);return;}cur = getjobpid(jobs, pid);if(cur == NULL){printf("(%d): No such process\n", pid);return;}}//退出该进程,重新执行kill(-pid, SIGCONT);//匹配前台还是后台任务,strcmp匹配成功返回0if(strcmp(argv[0], "bg")){//前台任务cur->state = FG;waitfg(pid);}else{//后台任务cur->state = BG;printf("[%d] (%d) %s", pid2jid(pid), pid, cur->cmdline);}return;
子进程终止时,会向父进程发送SIGCHLD信号,父进程收到信号后,使用waitpid去检查是否有结束的子进程
然后根据子进程的状态,判断子进程是怎么结束的,更新对应的job状态
为了保证原子性,父进程在deletejob时,屏蔽所有信号,删除完之后再打开信号
void sigchld_handler(int sig)
{int olderrno = errno;pid_t pid;int status;//进程状态sigset_t mask_all, prev;sigfillset(&mask_all);//父进程进行处理时,屏蔽信息,保证原子性//设置WNOHANG选项,只等待所有的僵死进程while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){sigprocmask(SIG_BLOCK, &mask_all, &prev);//如果子进程正常结束if(WIFEXITED(status)){deletejob(jobs, pid);}else if(WIFSTOPPED(status)){//如果进程状态为停止,更新job状态printf("Job [%d] (%d) stopped by signal 20 \n", pid2jid(pid), pid);(*getjobpid(jobs, pid)).state = ST;}else if(WIFSIGNALED(status)){//进程状态为终止状态printf("Job [%d] (%d) terminated by signal %d \n", pid2jid(pid), pid, WTERMSIG(status));deletejob(jobs, pid);}if(pid == wait_pid){wait_pid = -1;}sigprocmask(SIG_SETMASK, &prev, NULL);}errno = olderrno;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)
{//终止前台子进程kill(-fgpid(jobs), sig);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)
{kill(-fgpid(jobs), sig);return;
}
/* * tsh - A tiny shell program with job control* * */
#include
#include
#include
#include
#include
#include
#include
#include
#include /* 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 */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);void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig);/* 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];//存储解析后的参数char buf[MAXLINE];int bg;//job 是否 后台运行pid_t pid;//创建的子进程的pid,用于构建jobsigset_t mask_all, mask_chld, prev;strcpy(buf, cmdline);bg = parseline(buf, argv);//解析命令行,返回值为该任务是否为一个前台任务if(argv[0] == NULL){return; //空命令行,不执行操作}//如果不是内置命令,则fork子进程if(!builtin_cmd(argv)){//注意,参考书籍P542页,因为我们设置了SIGCHLD信号的回调//为了防止,父进程fork子进程之后,还未执行到addjobs的逻辑,子进程就执行完毕,发送了SIGCHLD信号//导致父进程处理信号,先调用了deletejobs而导致错误,此时应该先屏蔽SIGCHLD信号sigemptyset(&mask_chld);sigaddset(&mask_chld, SIGCHLD);// Signal(SIGCHLD, sigchld_handler);//注册SIGCHLD信号的处理函数,用于父进程回收子进程的资源sigprocmask(SIG_BLOCK, &mask_chld, &prev);//父进程阻塞SIGCHLD信号//创建子进程,environ为父进程的环境变量if((pid = fork()) == 0){//由于子进程完全的复制了父进程,所以需要先解除子进程对SIGCHLD信号的阻塞sigprocmask(SIG_SETMASK, &prev, NULL);setpgid(0, 0);if(execve(argv[0], argv, environ) < 0){printf("%s: Command not found\n", argv[0]);exit(0);}}//此处父进程执行,子进程的逻辑已经被更改,会重新执行自己的main函数//父进程阻塞所有信号,防止被打断去执行别的处理sigfillset(&mask_all);sigprocmask(SIG_BLOCK, &mask_all, NULL);//添加任务,fg为1,bg为2addjob(jobs, pid, bg + 1, cmdline);//恢复原来的状态,不再阻塞SIGCHLDsigprocmask(SIG_SETMASK, &prev, NULL);if(!bg){//前台执行,父进程等待子进程执行完毕waitfg(pid);} else {printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);//返回main函数,等待下一个任务}}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")){exit(0); //quit 命令}else if(!strcmp(argv[0], "jobs")){listjobs(jobs);return 1;}else if(!(strcmp(argv[0], "fg")) || !(strcmp(argv[0], "bg"))){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)
{char* arg1 = argv[1];int pid;int jid;struct job_t *cur;//当前的任务if(arg1 == NULL){printf("%s command requires PID or %%jobid argument\n", argv[0]);return;}//参数为jidif(arg1[0] == '%'){jid = atoi(((char *)arg1 + 1));if(jid == 0 && strcmp(((char *)arg1 + 1),"0")){printf("%s argument must be a PID or %%jobid\n", argv[0]);return;}cur = getjobjid(jobs, jid);if(cur == NULL){printf("%s: No such job\n", arg1);return;}pid = cur->pid;}else{//参数为pidpid = atoi(arg1);if(pid == 0 && strcmp(arg1, "0")){printf("%s: argument must be a PID or %%jobid\n", argv[0]);return;}cur = getjobpid(jobs, pid);if(cur == NULL){printf("(%d): No such process\n", pid);return;}}//退出该进程,重新执行kill(-pid, SIGCONT);//匹配前台还是后台任务,strcmp匹配成功返回0if(strcmp(argv[0], "bg")){//前台任务cur->state = FG;waitfg(pid);}else{//后台任务cur->state = BG;printf("[%d] (%d) %s", pid2jid(pid), pid, cur->cmdline);}return;
}/* * waitfg - Block until process pid is no longer the foreground process*/
volatile sig_atomic_t wait_pid;
void waitfg(pid_t pid)
{sigset_t mask, prev;sigemptyset(&mask);while(1){sigprocmask(SIG_SETMASK, &mask, &prev);if(!fgpid(jobs)){return;}sleep(1);sigprocmask(SIG_SETMASK, &prev, NULL);}// sigprocmask(SIG_SETMASK, &mask, &prev);// if(wait_pid == -1){// wait_pid = 0;// return;// }// wait_pid = pid;// while(wait_pid != -1){// sleep(1);// //sigsuspend(&prev);// }// sigprocmask(SIG_SETMASK, &prev, NULL);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 olderrno = errno;pid_t pid;int status;//进程状态sigset_t mask_all, prev;sigfillset(&mask_all);//父进程进行处理时,屏蔽信息,保证原子性//设置WNOHANG选项,只等待所有的僵死进程while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){sigprocmask(SIG_BLOCK, &mask_all, &prev);//如果子进程正常结束if(WIFEXITED(status)){deletejob(jobs, pid);}else if(WIFSTOPPED(status)){//如果进程状态为停止,更新job状态printf("Job [%d] (%d) stopped by signal 20 \n", pid2jid(pid), pid);(*getjobpid(jobs, pid)).state = ST;}else if(WIFSIGNALED(status)){//进程状态为终止状态printf("Job [%d] (%d) terminated by signal %d \n", pid2jid(pid), pid, WTERMSIG(status));deletejob(jobs, pid);}if(pid == wait_pid){wait_pid = -1;}sigprocmask(SIG_SETMASK, &prev, NULL);}errno = olderrno;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)
{//终止前台子进程kill(-fgpid(jobs), sig);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)
{kill(-fgpid(jobs), sig);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);
}
下一篇:第三个字是七的成语