《深入理解计算机系统》CSAPP_TshLab


TshLab

开始日期:22.1.20

操作系统:linux

调试工具:linux terminal

目录
  • TshLab
    • Preknowledge
    • Functions
      • Attention
      • eval()
      • do_bgfg()
      • waitfg()
      • builtin_cmd()
      • sigchld_handler()
      • sigint_handler()
      • sigstp_handler()
    • Put all together
      • trace15 & 16
    • Conclusion

Preknowledge

  • tshlab的意思是:tiny shell lab
  • EOF(第八章)这一章务必看到8.6,笔者是把里面可运行的源代码都敲了一遍,想要方便的可以参考如下两个链接:源代码,csapp.h的使用
  • write up务必认真看,里面的hint部分很重要。
  • trace01.txt - Properly terminate on EOF,告诉我们,tsh的主要任务是恰当的终止异常

Functions

Attention

  • 正式做实验时,要按照trace01 ~ 16的任务要求编写7个函数,一开始无从下手很正常,不用着急,多查查资料,多想想。本文主要借鉴了这两篇:,myk的CS学习之旅

  • 借鉴教材给出的相关函数。

  • 一个pg(进程组)中有一个或多个pg(进程),一个pg也称为一个job(工作),jobs是所有工作的集合

  • jid是一个job在jobs中的序列[1, 2, ....]

  • pid:如果是child(子程序)就代表child的进程编号
    如果是parent(父程序)既代表parent的进程编号也代表parent所在程序组的编号(pgid)

  • shell会分为foreground(前台)和background(后台)两项工作列表,其中foreground中只能有一个工作,但background中可以有多个工作

  • &代表后台任务,无&代表前台任务

  • %代表进程组,无%代表进程

  • 额外设置了全局变量flag,用来表明前台工作是否终止(或停止)。(遇到foreground都要使用flag

    volatile  sig_atomic_t flag; 
    /* equal 1 => jobs terminated or stopped in fg; equal 0 => jobs running in fg */
    
  • handler系列函数要设置erron复原

  • main()中,信号已经install(安装)完毕

  • 使用&取地址运算符来取得数据、状态的地址

eval()

  • 主要功能:

      • 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.

    • 可以知道,eval()是用来评估用户键入的指令,如果是内嵌指令就立刻执行;
      否则,就要创建并运行子程序,对于这个子程序,如果它运行在前台就要等待它终止或返回
      如果它运行在后台,那它就不能接收终止/停止信号SIGINT/SIGTSTP),这些信号可以通过用户键入ctrl-c/ctrl-z发送

  • trace04.txt - Run a background job. 要调用运行一个后台工作,然后printf()出相关信息(jid,pid,进程名字),将会用到已经提供的函数:pid2jid()

  • trace06.txt - Forward SIGINT to foreground job;
    trace07.txt - Forward SIGINT only to foreground job;
    trace08.txt - Forward SIGTSTP only to foreground job;
    trace11.txt - Forward SIGINT to every process in foreground process group;
    trace12.txt - Forward SIGTSTP to every process in foreground process group.

    • trace06, 07, 08, 11, 12涉及到信号,那就必须创建并运行child了(得写sigchld_handler()),由此便会产生child和parent之间的race(竞争),必须解决它。同时,因为涉及到SITINTSIGTSTP,得写sigint_handler()sigtstp_handler()

    • child和parent之间的race,按照书中方式,只需要在创建child之前block(阻塞)SIGCHLD在创建child并运行之后unblock(解除阻塞)即可。

    • 注意write up中给出的提示:

      • 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. When you type ctrl-c, the shell should catch the resulting SIGINT and then forward it to the appropriate foreground job (or more precisely, the process group that contains the foreground job).

      • 因此我们调用setpgid(0, 0)将foreground中进程组的pgid设置为child的pid

    • 对于parent,before add/delete job, we must block all sigs防止信号干扰到job的增删

      • 如果是前台,先设置flag = 1,然后addjob(),最后需要显式地等待工作终止即调用waitfg()
      • 如果是后台,直接addjob(),然后unblock即可。
  • 打印Command not found.(trace14)

void eval(char *cmdline) {
    char *argv[MAXARGS];
    char buf[MAXLINE];
    int bg;   
    pid_t pid;
    sigset_t mask_all, mask_one, prev;
	
    sigfillset(&mask_all); //fill all sigs to mask
    sigemptyset(&mask_one);
    sigaddset(&mask_one, SIGCHLD); //mask_one has one sig: SIGCHLD
    //initjobs(jobs); already use in main(), recalled will empty new lists of jobs

    strcpy(buf, cmdline);
    bg = parseline(buf, argv); //return true, if it is background 
    if(argv[0] == NULL) //if not any cmd
        return;
    //exeluate buliltin_cmd, if not buliltin_cmd(filepath, ./exe and so on), shell will create a child process and run it
    if(!builtin_cmd(argv)) { 
    	sigprocmask(SIG_BLOCK, &mask_one, &prev); //block SIGCHLD to avoid race
        if((pid = fork()) == 0){ //create a child process
        	setpgid(0, 0); //or call setpgrp()
        	sigprocmask(SIG_SETMASK, &prev, NULL); //unblock SIGCHLD
            if(execve(argv[0], argv, environ) < 0) { //run it and if it can't run
                printf("%s: Command not found.\n", argv[0]);
                exit(0);
            }
        }
     	
     	//before add/delete job, we must block all sigs
     	sigprocmask(SIG_BLOCK, &mask_all, NULL);  /* Parent: block all sigs */   
     	
     	//background: 
    	if(bg) {
     		addjob(jobs, pid, BG, cmdline); // add job to jobs(list) in background
     		sigprocmask(SIG_SETMASK, &prev, NULL); /* Unblock all sig */
     		// get jid by pid
     		printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);      	
     	}
     	
     	//foreground: If the job is running in the foreground, wait for it to terminate and then return.
     	else { 
    		flag = 0; //job running in foreground
     		addjob(jobs, pid, FG, cmdline);
     		//Still block until process pid is no longer the foreground process
     		waitfg(pid);
     	}
   }
   return;
}

do_bgfg()

  • 主要功能:Execute the builtin bg and fg commands.
  • trace09.txt - Process bg builtin command
    trace10.txt - Process fg builtin command.
    trace13.txt - Restart every stopped process in process group;
    trace14.txt - Simple error handling.
  • Add job to jobs(list) in bg will builds new jobs, so we can only change the state of job
    bg部分:改state为BG再打印即可;
    fg部分:改state为FG前,要注意信号的阻塞以及flag的设置,同时,前台的job必须等待终止或停止(waitfg)
  • 打印错误信息时,注意(job_ptr == NULL) 必须在 (job_ptr->state == UNDEF)之前
  • 满足restart,发送SIGCONT即可,注意区分发送至单个进程还是整个工作(进程组)
void do_bgfg(char **argv) 
{	
	if (argv[1] == NULL){
		printf("%s command requires PID or %%jobid argument\n", argv[0]);
		return;
	}
	
	//define argument, if argv[1] has value
	struct job_t *job_ptr = NULL;
	int isjob;
	int bg = 0;
	pid_t jid, pid;

	//printf error message
	if (sscanf(argv[1], "%d", &pid) > 0){
		isjob = 0;
		job_ptr = getjobpid(jobs, pid);
		//printf error if state of job is undefine or tan90°(non-being)
		//Note: (job_ptr == NULL) Before (job_ptr->state == UNDEF)
		if ((job_ptr == NULL) || (job_ptr->state == UNDEF)) {
			printf("(%d): No such process\n", pid);
			return;
		}	
	}
	else if (sscanf(argv[1], "%%%d", &jid) > 0){
		isjob = 1;
		job_ptr = getjobjid(jobs, jid);
		if ((job_ptr == NULL) || (job_ptr->state == UNDEF)) {
			printf("%%%d: No such job\n", jid);
			return;
		}	
	}
	else {
		printf("%s: argument must be a PID or %%jobid\n", argv[0]);
		return;
	}
	
	if(!strcmp(argv[0], "bg"))
		bg = 1;
	// bg command			
	if (bg){
		if(job_ptr->state == BG){
			printf("[%d] (%d) %s", jid, job_ptr->pid, job_ptr->cmdline);	
		}
		//restart
		if(job_ptr->state == ST){
			if (isjob)
				kill(-(job_ptr->pid), SIGCONT);
			else
				kill((job_ptr->pid), SIGCONT);
			job_ptr->state = BG;
			printf("[%d] (%d) %s", jid, job_ptr->pid, job_ptr->cmdline);
		}
	}
	//fg command
	else { 
			//block all sig, after return or terminated in fg
			sigset_t mask_all;
			sigfillset(&mask_all);
			sigprocmask(SIG_BLOCK, &mask_all, NULL);
			flag = 0; //job running in foreground
    
			//when "fg %1 "is typed a second time, will sents SIGCONT to restart job/process
			if (job_ptr -> state == ST){
				if (isjob)
					kill(-(job_ptr->pid), SIGCONT);
				else
					kill((job_ptr->pid), SIGCONT);
			}
			job_ptr->state = FG;
			waitfg(job_ptr->pid);
			return;
	}
	return;
}

waitfg()

  • 主要功能:Block until process pid is no longer the foreground process
  • eval()do_bgfg()的foreground部分需要使用此函数
void waitfg(pid_t pid)
{
	sigset_t empty;
	sigemptyset(&empty); //an empty set
	while (!flag) //flag == 0 => job running in foreground => !flag == 1
		sigsuspend(&empty); //suspend, we must wait job terminated in foreground explicitly 
	sigprocmask(SIG_SETMASK, &empty, NULL); /* Unblock all sig */	
	return;
}

builtin_cmd()

  • 主要功能:判断是不是内嵌指令(builtin_command),是就执行对应指令,否则返回0
  • trace02.txt - Process builtin quit command.
    执行quit指令,该指令的作用是退出tsh程序,调用exit(0)即可。该指令是前台任务(trace03.txt - Run a foreground job.)
  • strcmp()=> if equal, strcmp() return 0, so !strcmp() return 1
  • trace05.txt - Process jobs builtin command.
    执行jobs指令,该指令的作用是罗列所有工作,调用listjobs()即可。
int builtin_cmd(char **argv) 
{
    if(!strcmp(argv[0], "quit")) // if equal, strcmp() return 0, so !strcmp() return 1
        exit(0);
    if(!strcmp(argv[0], "jobs")){
        listjobs(jobs);
        return 1;
    }
    if(!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")){
        do_bgfg(argv);
        return 1;
    }
    if(!strcmp(argv[0], "&")) // ignore alone '&'
        return 1;
    return 0;     /* not a builtin command */
}

sigchld_handler()

  • 创建child就会调用sigchld_handler(),从而使shell发送SIGCHLD信号。

  • 由于sigchld_handler()就是根据信号来调用的,所以delete()回收zombie child时,不需要block信号

  • 以下是教材内容:

    • WNOHANG | WUNTRACED: Return immediately, with a return value of 0, if none of the children in the wait set has stopped or terminated, or with a return value equal to the PID of one of the stopped or terminated children.

      • WIFEXITED(status). Returns true if the child terminated normally, via a call to exit or a return.
      • WIFSIGNALED(status). Returns true if the child process terminated because of a signal that was not caught.
      • WTERMSIG(status). Returns the number of the signal that caused the child process to terminate. This status is only defined if WIFSIGNALED() returned true.
      • WIFSTOPPED(status). Returns true if the child that caused the return is currently stopped.
      • WSTOPSIG(status). Returns the number of the signal that caused the child to stop. This status is only defined if WIFSTOPPED() returned true.
    • WIFSIGNALED(status)的描述出错了,应该是:Returns true if the child process terminated because of a signal that was caught.

  • 调用waitpid()等待child pid,而后回收zombie child(实验要求这里使用if判断),然后根据status打印信息,最后删除(即回收)这个child或者修改child的state为ST。(注意:使用jid来修改是无效,必须用地址)

  • if pid == fg_pid, job terminated or stopped in foreground
    因为WNOHANG | WUNTRACED: or with a return value equal to the PID of one of the stopped or terminated children.

void sigchld_handler(int sig) 
{
        int olderrno = errno;
        int status;
        pid_t pid, fg_pid = fgpid(jobs); //Note: only one job in fg
	
        if ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { 
	    if(pid == fg_pid)
		flag = 1;
	    if (WIFEXITED(status)) //normally terminated (return/exit)
	    	deletejob(jobs, pid);
	    else if (WIFSIGNALED(status)){  //child terminated because SIGINT that was caught.
		printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
		deletejob(jobs, pid);
	    }
    	    else if (WIFSTOPPED(status)){
   	        struct job_t *job_ptr = getjobpid(jobs, pid);//getjobpid() return address of job
                job_ptr->state = ST; //stopped the job   			
		printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
    	    }          
         }
    
        errno = olderrno;
        return;
}

sigint_handler()

  • 处理信号SIGINT
  • kill(pid, SIG)是只发送信号给单个进程,kill(-pgid, SIG)是发送信号给整个进程组
void sigint_handler(int sig) 
{	
  int olderrno = errno;
  //get pid from jobs in 'fg'
  pid_t pgid = fgpid(jobs);  //process ground in fg: fg_pid == pgid == pid != 0
  
  //sent SIGINT to pgid in order to terminate the whole fg ground  
  if (pgid) {
      kill(-pgid, SIGINT); //or call killpg(-pgid, SIGINT)
  }
  errno = olderrno;
  return;
}

sigstp_handler()

  • 处理信号SIGTSTP
void sigtstp_handler(int sig) 
{	
    int olderrno = errno;
    pid_t pgid = fgpid(jobs);  //process ground in fg: fg_pid == pgid == pid != 0
  
    //sent SIGTSTP to pgid in order to stopped the whole fg ground 
    if (pgid) {
	kill(-pgid, SIGTSTP); 
    }
    errno = olderrno;
    return;
}

Put all together

/* 
 * 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 */
volatile  sig_atomic_t flag; /* equal 1 => jobs terminated or stopped in fg; equal 0 => jobs running in fg */

/* 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;   
    pid_t pid;
	sigset_t mask_all, mask_one, prev;
	
	sigfillset(&mask_all); //fill all sigs to mask
	sigemptyset(&mask_one);
	sigaddset(&mask_one, SIGCHLD); //mask_one has one sig: SIGCHLD
	//initjobs(jobs); already use in main(), recalled will empty new lists of jobs

    strcpy(buf, cmdline);
    bg = parseline(buf, argv); //return true, if it is background 
    if(argv[0] == NULL) //if not any cmd
        return;
    //exeluate buliltin_cmd, if not buliltin_cmd(filepath, ./exe and so on), shell will create a child process and run it
    if(!builtin_cmd(argv)) { 
    	sigprocmask(SIG_BLOCK, &mask_one, &prev); //block SIGCHLD to avoid race
        if((pid = fork()) == 0){ //create a child process
        	setpgid(0, 0); //or call setpgrp()
        	sigprocmask(SIG_SETMASK, &prev, NULL); //unblock SIGCHLD
            if(execve(argv[0], argv, environ) < 0) { //run it and if it can't run
                printf("%s: Command not found.\n", argv[0]);
                exit(0);
            }
        }
     	
     	//before add/delete job, we must block all sigs
     	sigprocmask(SIG_BLOCK, &mask_all, NULL);  /* Parent: block all sigs */   
     	
     	//background: 
    	if(bg) {
     		addjob(jobs, pid, BG, cmdline); // add job to jobs(list) in background
     		sigprocmask(SIG_SETMASK, &prev, NULL); /* Unblock all sig */
     		// get jid by pid
     		printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);      	
     	}
     	
     	//foreground: If the job is running in the foreground, wait for it to terminate and then return.
     	else { 
    		flag = 0; //job running in foreground
     		addjob(jobs, pid, FG, cmdline);
     		//Still block until process pid is no longer the foreground process
     		waitfg(pid);
     	}
   }
   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")) // if equal, strcmp() return 0, so !strcmp() return 1
        exit(0);
    if(!strcmp(argv[0], "jobs")){
        listjobs(jobs);
        return 1;
    }
    if(!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")){
        do_bgfg(argv);
        return 1;
    }
    if(!strcmp(argv[0], "&")) // ignore alone '&'
        return 1;
    return 0;     /* not a builtin command */
}

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) 
{	
	if (argv[1] == NULL){
		printf("%s command requires PID or %%jobid argument\n", argv[0]);
		return;
	}
	
	//define argument, if argv[1] has value
	struct job_t *job_ptr = NULL;
	int isjob;
	int bg = 0;
	pid_t jid, pid;

	//printf error message
	if (sscanf(argv[1], "%d", &pid) > 0){
		isjob = 0;
		job_ptr = getjobpid(jobs, pid);
		//printf error if state of job is undefine or tan90°(non-being)
		//Note: (job_ptr == NULL) Before (job_ptr->state == UNDEF)
		if ((job_ptr == NULL) || (job_ptr->state == UNDEF)) {
			printf("(%d): No such process\n", pid);
			return;
		}	
	}
	else if (sscanf(argv[1], "%%%d", &jid) > 0){
		isjob = 1;
		job_ptr = getjobjid(jobs, jid);
		if ((job_ptr == NULL) || (job_ptr->state == UNDEF)) {
			printf("%%%d: No such job\n", jid);
			return;
		}	
	}
	else {
		printf("%s: argument must be a PID or %%jobid\n", argv[0]);
		return;
	}
	
	if(!strcmp(argv[0], "bg"))
		bg = 1;
	// bg command			
	if (bg){
		if(job_ptr->state == BG){
			printf("[%d] (%d) %s", jid, job_ptr->pid, job_ptr->cmdline);	
		}
		//restart
		if(job_ptr->state == ST){
			if (isjob)
				kill(-(job_ptr->pid), SIGCONT);
			else
				kill((job_ptr->pid), SIGCONT);
			job_ptr->state = BG;
			printf("[%d] (%d) %s", jid, job_ptr->pid, job_ptr->cmdline);
		}
	}
	//fg command
	else { 
			//block all sig, after return or terminated in fg
			sigset_t mask_all;
			sigfillset(&mask_all);
			sigprocmask(SIG_BLOCK, &mask_all, NULL);
			flag = 0; //job running in foreground
    
			//when "fg %1 "is typed a second time, will sents SIGCONT to restart job/process
			if (job_ptr -> state == ST){
				if (isjob)
					kill(-(job_ptr->pid), SIGCONT);
				else
					kill((job_ptr->pid), SIGCONT);
			}
			job_ptr->state = FG;
			waitfg(job_ptr->pid);
			return;
	}
	return;
}
/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
	sigset_t empty;
	sigemptyset(&empty); //an empty set
	while (!flag) //flag == 0 => job running in foreground => !flag == 1
		sigsuspend(&empty); //suspend, we must wait job terminated in foreground explicitly 
	sigprocmask(SIG_SETMASK, &empty, NULL); /* Unblock all sig */	
	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;
	int status;
	pid_t pid, fg_pid = fgpid(jobs); //Note: only one job in fg
	
  	if ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { 
		if(pid == fg_pid)
			flag = 1;
	    if (WIFEXITED(status)) //normally terminated (return/exit)
	    	deletejob(jobs, pid);
	    else if (WIFSIGNALED(status)){  //child terminated because SIGINT that was caught.
			printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
			deletejob(jobs, pid);
	    }
    	else if (WIFSTOPPED(status)){
   			struct job_t *job_ptr = getjobpid(jobs, pid);//getjobpid() return address of job
        	job_ptr->state = ST; //stopped the job   			
			printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
    	}          
 	}
    
    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) 
{	
	int olderrno = errno;
	//get pid from jobs in 'fg'
	pid_t pgid = fgpid(jobs);  //process ground in fg: fg_pid == pgid == pid != 0
  
	//sent SIGINT to pgid in order to terminate the whole fg ground  
	if (pgid) {
		kill(-pgid, SIGINT); //or call killpg(-pgid, SIGINT)
	}
  errno = olderrno;
  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) 
{	
	int olderrno = errno;
	pid_t pgid = fgpid(jobs);  //process ground in fg: fg_pid == pgid == pid != 0
  
	//sent SIGTSTP to pgid in order to stopped the whole fg ground 
	if (pgid) {
		kill(-pgid, SIGTSTP); 
	}
  errno = olderrno;
  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);
}

trace15 & 16

Conclusion

  • 完成日期:22.1.25
  • 有效时间大概12小时,除了24号是下午晚上都在电脑前,其他时间都是中午,因为晚上要学c++,24号debug完毕,25号把博客写完
  • 本实验和教材关系极大,所以主要是理解难度大(进程,异常,信号)
  • c语言果然是用来和底层交流的最好语言
  • 要快点学完c++,然后用在算法上!
  • 《枕边童话》很好听,晚上可以看《开端》结局了!(英文名叫reset,restart?发送SIGCONT?)