《深入理解計算機系統》CSAPP_ShellLab(TshLab)


TshLab

開始日期:22.1.20

操作系統:linux

調試工具:linux terminal

Link:CS:APP3e

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個函數,一開始無從下手很正常,不用着急,多查查資料,多想想。本文主要借鑒了這兩篇:CS:APP3e 深入理解計算機系統_3e ShellLab(tsh)實驗 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()

  • When each child terminates, the kernel notifies the parent by sending it a SIGCHLD signal. The parent catches the SIGCHLD, reaps one child, does some additional cleanup work (modeled by the sleep statement), and then returns.

  • 當任意child終止時,kernel(shell)就會發送SIGCHLD給parent,sigchld_handler()就要開始處理,開始回收zombie child

  • 以下是教材內容:

    • 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
 * 
 * <duile programme>
 */
#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 */

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?)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM