csapp-shlab

371 阅读4分钟

官方参考链接

Linux信号

序号名称默认行为相应事件
1SIGHUP终止终端线挂断
2SIGINT终止来自键盘的终端
3SIGQUIT终止来自键盘的退出
4SIGILL终止非法指令
5SIGTRAP终止并转储内存跟踪陷阱
6SIGABRT终止并转储内存来自abort函数的终止信号
7SIGBUS终止总线错误
8SIGFPE终止并转储内存浮点异常
9SIGKILL终止杀死进程,这个信号既不能被捕获也不能被忽略
10SIGUSR1终止用户自定义的信号1
11SIGSEGV终止并转存内存无效的内存引用(段错误)
12SIGUSR2终止用户自定义的信号2
13SIGPIPE终止向一个没有读用户的管道做写操作。比如,如果一个 socket 在接收到了 RST packet 之后,程序仍然向这个 socket 写入数据,那么就会产生SIGPIPE信号。
14SIGALRM终止来自alarm函数的定时器信号
15SIGTERM终止软件终止信号,与SIGKILL不同的是该信号可以被阻塞和处理
16SIGSTKFLT终止协处理器上的栈故障
17SIGCHLD忽略一个子进程终止或者停止
18SIGCONT忽略继续进程,如果该进程停止
19SIGSTOP停止直到下一个SIGCONT不是来自终端的停止信号,不能被修改
20SIGTSTP停止直到下一个SIGCONT来自终端的停止信号
21SIGSTTIN停止直到下一个SIGCONT后台进程从终端读
22SIGSTTOU停止直到下一个SIGCONT后台进程向终端写
23SIGURG忽略套接字上的紧急通知
24SIGXCPU终止CPU事件限制超出
25SIGXFSZ终止文件大小限制超出
26SIGVTALRM终止虚拟定时器期满
27SIGPROF终止剖析定时器期满
28SIGWINCH忽略终端窗口大小变化
29SIGIO终止在某个描述符上可执行I/O操作
30SIGPWR终止电源故障

eval 命令解析

eval(char *cmdline){
	char* argv[MAXARGS]; //存放参数,供execve函数使用
    char buf[MAXLINE];  //限制行最大数据
    int bg; //后台运行or前端运行标记位
    pid_t pid, jid;
   	sigset_t mask_all, mask_one, prev_one;
    sigemptyset(&mask_one);
    sigaddset(&mask_one, SIGCHLD);
    sigfillset(&mask_all);
    
    strcpy(buf, cmdline);
    bg = parseline(buf, argv);
    if(argv[0] == NULL){ //忽略空行
    	return;
    }
    
    if(!builtin_cmd(argv)){ //是不是内置命令
    	sigprocmask(SIG_SETMASK, &mask_one, &prev_one); //阻塞SIGCHLD信号,防止deletejob和addjob的竞争
    	if((pid = fork()) == 0){
        	setpgid(0, 0);
            sigprocmask(SET_MASK, &prev_one, NULL); //子进程继承了父进程的阻塞信号,需要解除。
        	if(execve(argv[0], argv, environ) < 0){ //execve函数使得继承自父进程的信号处理函数恢复为默认
            	printf("command not found!");
                exit(0);
            }
        }
        sigprocmask(SIG_SETMASK, &mask_all, &prev_one); //阻塞所有信号,以便执行addjob
        if(!bg){
        	flag = 0;
            addjob(jobs, pid, FG, buf);
        	waitfg(pid);
		}else{
        	jid = addjob(jobs, pid, BG, buf);
            printf("[%d] (%d) %s", jid, pid, buf);
        }
        sigprocmask(SIG_SETMASK, &prev_one, NULL);
	}
    return;
}

builtin_cmd

builtin_cmd(char** argv){
	if(!strcmp(argv[0], "quit")){
    	exit(0);
    }else if(!strcmp(argv[0], "jobs"){
    	listjobs(jobs);
        return 1;
    }else if(!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")){
    	do_bgfg(argv);
        return 1;
    }else if(!strcmp(argv[0], "&"){
    	return 1;
    }
    return 0;
}

do_bgfg

do_bgfg(char** argv){
	int bg = 0;
	if(!strcmp(argv[0], "bg")){
    	bg = 1;
    }
    struct job_t *job_ptr = NULL;
    int pid, jid;
    if(sscanf(argv[1], "%d", &pid) > 0){ // 进程ID
    	job_ptr = getjobpid(jobs, pid); //根据pid查找job
        if(job_ptr == NULL || job_ptr == UNDEF){
        	printf("(%d): no such process", pid);
            return;
        }
        jid = job_ptr->jid;
    }else if(sscanf(argv[1], "%%%d", &jid) > 0){
    	job_ptr = getjobjid(jobs, jid); // 通过jid获得job
        if(job_ptr == NULL || job_ptr == UNDEF){
        	printf("[%d]: no such jid", pid);
            return;
        }
        pid = job_ptr->pid;
    }else{
    	printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }
    
    if(bg){
    	if(job_ptr->state == BG){
        	printf("[%d] (%d) %s", jid, pid, job_ptr->cmdline);
            return;
        }
        if(job_ptr->state == ST){
            kill(pid, SIGCONT); 
            job_ptr->state = BG;
            printf("[%d] (%d) %s", jid, pid, job_ptr->cmdline);
        }
    }else{
   		flag = 0;
        sigset_t mask_all, prev;
        sigprocmask(SIG_SETMASK, &mask_all, &prev);
    	if(job_ptr->state == ST){
        	kill(pid, SIGCONT);
        }
        job_ptr->state = FG; //确保这一行在waitfg前面
        waitfg(pid);
        sigprocmask(SIG_SETMASK, &prev, NULL);
    }
    return ;
}

waitfg 阻塞当前进程直到pid所指进程不再是前端进程(或者终止或者挂起)

waitfg(pid_t pid){
	sigset_t mask_empty;
    sigemptyset(&mask_empty);
    while(!flag){
    	sigsuspend(&mask_empty); //挂起进程,且不阻塞任何信号
	}
}

sigchld_handler SIGCHLD信号处理程序

void sigchld_handler(int sig){
	int olderrno = errno;
	struct job_t *job_ptr = NULL;
    pid_t child_pid, fg_pid = fgpid(jobs);
  	// WUNTRACED - 挂起调用进程,直到等待集合中的一个进程终止或停止
    // WNOHANG - 如果等待集合中的任何子进程都没有终止,那么立即返回
    while((child_pid = waitpid(-1, &status, WUNTRACED | WNOHANG) > 0){ 
    	if(fg_pid == child_pid){
        	flag = 1;
        }
        
        if(WIFEXITED(status)){ //由exit终止
        	deletejob(jobs, child_pid);
        }else if(WIFSIGNALED(status)){  // 由一个未被捕获的信号终止 比如SIGKILL
        	job_ptr = getjobpid(jobs, child_pid);
            printf("Job [%d] (%d) terminated by signal %d \n", job_ptr->jid, job_ptr->pid, WTERMSIG(status));
			deletejob(jobs, child_pid);
        }else if(WIFSTOPPED(status)){ // 引起返回的子进程是否停止
        	job_ptr = getjobpid(jobs, child_pid);
			job_ptr->status = ST;
            printf("Job [%d] (%d) stopped by signal %d \n", job_ptr->jid, job_ptr->pid, WTERMSIG(status));
        }
    }
    errno = olderrno;
}

sigint_handler Ctrl-c终止进程将触发SIGINT信号

void sigint_handler(int sig){
	int olderrno = errno;
	pid_t fg_pid = fgpid(jobd);
    if(fp_pid == 0){
    	return ;
    }
    kill(fg_pid, SIGINT);
    errno = olderrno;
    return;
}

sigtstp_handler Ctrl-z挂起进程触发SIGTSTP信号

void sigtstp_handler(int sig){
	int olderrno = errno;
	pid_t fg_pid = fgpid(jobs);
    if(fg_pid <= 0){
    	return;
    }
    sigset_t mask_all, prev;
    sigfillset(&mask_all);
    sigprocmask(SIG_SETMASK, $mask_all, &prev); // 屏蔽其他信号
    kill(fg_pid, SIGTSTP); //传递停止信号给前端程序
    struct job_t *job_ptr = getjobpid(jobs, fg_pid);
    job_ptr->state = ST;
    sigprocmask(SIG_SETMASK, &prev, NULL);
    errno = olderrno;
    return ;
}

fork与execve程序对信号处理函数的影响

如果在fork函数执行前,父进程已经注册了信号处理函数,那么fork函数执行后,子进程也将继承父进程注册的信号处理函数。特殊的是execve函数,如果在子进程中执行了execve函数,子进程的信号处理函数将恢复为默认。例如,对eval中的execve部分的代码修改为

if ((pid = fork()) == 0) {
	setpgid(0, 0);
	sigprocmask(SIG_SETMASK, &prev_one, NULL); //子进程继承了父进程的阻塞信号,需要解除。
    while(1);        
 }

此时,子进程收到的信号也会由从继承自父进程的信号处理函数处理。那么上述sigint_handler和sigtstp_handler函数将不能实现kill函数的效果。