Unix实验报告
实验: |
实验4 信号处理 |
专业: |
计算机科学与技术 |
班级: |
1班 |
姓名: |
姚怀聿 |
学号: |
22920202204632 |
2022年12月19日
目 录
实验内容描述
本实验的目的是学习和掌握并发进程同步的概念和方法。
实验要求编写具有简单执行时间限制功能的shell:
命令语法为:
./myshell [-t <time>]
具体要实现的功能如下:
1.程序的功能类似实验3,但是具有系统shell的全部功能。<time>是测试程序允许用户命令执行的时间限制,默认值为无限制。当用户命令的执行时间限制到达时,测试程序终止用户命令的执行,转而接收下一个用户命令。
2.myshell只在前台运行。
3.按Ctrl-\键不是中断myshell程序的运行,而是中断当前用户命令的接收或终止当前用户命令的执行,转而接收下一个用户命令。
4.注意信号SIGALRM和SIGQUIT之间嵌套关系的处理。
实验构思
本实验要求利用可靠信号机制解决信号处理时可能出现的时间窗口,以及非局部转移等问题,学习使用sigaction,alarm,sigpending,sigsetjmp和siglongjmp等函数解决在处理信号时遇到的问题。
可以直接在程序清单1-8的基础上进行修改,直接利用系统shell:execl(“/bin/sh”, “sh”, “-c”, buf, (char *) 0); 这样程序就具有系统shell的全部功能。如果命令带 “-t”选项,则在创建执行上面函数的子进程时,必须先调用alarm函数设置闹钟;在子进程结束时,必须将闹钟清零。
需要处理的信号:
因为需要使用闹钟,所以实验需要处理两个信号:SIGALRM和SIGQUIT。如果当前程序正在执行用户命令,则信号处理函数必须“杀死”用户命令进程:
kill(pid, SIGKILL); // pid为用户命令进程的ID
对于信号SIGQUIT还有一种可能;正在接收用户输入的字符串。此时需要放弃当前输入,重新开始接收输入。解决方法是调用函数sigsetjmp()和siglongjmp()。
对于信号SIGALRM和SIGQUIT之间嵌套关系的处理:
如果这两个信号同时发生,或者在处理信号SIGALRM时产生信号SIGQUIT(或者反过来),那么应当如何处理?无论何时,如果存在多个未决信号,系统总是第一个信号处理完了,再处理下一个信号,但是在处理完全部未决信号之前,不会返回被中断的信号或系统调用。因此合理的解决方案是,无论以上两个信号哪个先处理,另一个未决信号就应该忽略(清除未决信号);在处理其中一个信号时,屏蔽另一个信号(如果发生了,就是未决信号)。
具体代码及解释如下:
(1)引用头文件及函数的声明
因为没有引用 “apue.h”,所以我自己定义了函数返回的类型typedef void Sigfunc(int);
print_prompt()函数用于打印输入命令前的提示字。
(2)main函数中:
首先处理程序传参是否正确,不正确报错:
默认情况下执行时间是不受限制的,如果执行命令的时候有设置参数,则调用alarm()函数设置闹钟:
接下来注册信号,Ctrl+\会产生SIGQUIT信号。闹钟超时时会产生SIGALRM信号:
在调用打印提示字的函数之前设置跳转点:
接下来处理每一行的输入,与实验三相同,将最后一个换行符换为字符串结束符。
在fork子进程之前先调用fflush函数,清空缓冲区,防止输出时换行出现奇怪的现象;在子进程中调用execlp(),直接利用系统shell,同时处理报错信息:
在父进程中调用waitpid(),等待子进程的结束,在子进程结束时清空闹钟:
print_prompt()函数的实现与实验三相同:
接下来是signal函数的可靠实现,该函数的实现参考了实验指导,首先定义两个结构体类型变量act和oact,在这个函数中实现当在处理一个信号的时候,屏蔽另一个信号的功能:
下面是实现对接收到超时信号时调用的handler函数,首先杀死用户命令进程,并打印超时提示字,接下来实现对屏蔽信号的处理(即对SIGQUIT信号的处理),代码如下:
接收到SIGQUIT信号的处理与SIGALRM信号的处理相似,代码如下:
完整代码如下:
#include <stdio.h> #include <stdlib.h> #include <setjmp.h> #include <sys/wait.h> #include <string.h> #include <signal.h> #include <unistd.h> #define MAXLINE 256 typedef void Sigfunc(int); // 定义Sigfunc函数返回值 static volatile pid_t pid; // 存放子进程id, 非0表示正在执行用户命令 static sigjmp_buf jmpbuf; void print_prompt(); Sigfunc *signal(int, Sigfunc *); // reliable version of signal() static void sig_alrm(int); // signal-catching function static void sig_quit(int); // signal-catching function int main(int argc, char* argv[]) { char buf[MAXLINE]; int status; int time = 0; // do input if(argc != 1 && argc != 3) fprintf(stderr, "Usage : myshell [-t <time>]\n"); if(argc == 3) { if(strcmp(argv[1], "-t") != 0) { fprintf(stderr, "Usage : myshell [-t <time>]\n"); } else { time = atoi(argv[2]); } } // register for signal if (signal(SIGALRM, sig_alrm) == SIG_ERR) { perror("Signal Error: "); exit(1); } if(signal(SIGQUIT, sig_quit) == SIG_ERR) { perror("Signal Error: "); exit(1); } sigsetjmp(jmpbuf, 1); // set jump point print_prompt(); // print the prompt while(fgets(buf, MAXLINE, stdin) != NULL) { if(buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = 0; // replace new line with null if(time) alarm(time); // set alarm before user executes the command fflush(NULL); if((pid = fork()) < 0) { perror("fork error: "); } else if(pid == 0) { // child execlp("/bin/sh", "sh", "-c", buf, (char *)0); // use system shell fprintf(stderr, "couldn't execute: %s", buf); exit(127); } // parent if((pid == waitpid(pid, &status, 0)) < 0) { perror("waitpid error"); } if(time) alarm(0); // clear clock print_prompt(); } exit(0); } void print_prompt() { char prompt[MAXLINE] = "[Myshell "; if(getcwd(prompt + 9, MAXLINE) == NULL) { perror("getcwd: "); exit(1); } strncat(prompt, "]$", MAXLINE - 1); fprintf(stdout, "%s", prompt); } Sigfunc* signal(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; // when handle one of the signal, mask another if(signo == SIGALRM) { sigaddset(&act.sa_mask, SIGQUIT); } else if(signo == SIGQUIT) { sigaddset(&act.sa_mask, SIGALRM); } if(signo == SIGALRM) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; #endif } if(sigaction(signo, &act, &oact) < 0) return(SIG_ERR); return(oact.sa_handler); } void sig_alrm(int signo) { struct sigaction act; if(pid > 0) { kill(pid, SIGKILL); pid = 0; } fprintf(stdout, "\n*** TIMEOUT ***\n"); sigset_t pendmask; if(sigemptyset(&pendmask) < 0) { perror("sigemptyset(): "); } if(sigpending(&pendmask) < 0) { perror("sigpending(): "); } if(sigismember(&pendmask, SIGQUIT)) { signal(SIGQUIT, SIG_IGN); sigaction(SIGQUIT, &act, NULL); } siglongjmp(jmpbuf, 1); } void sig_quit(int signo) { struct sigaction act; if(pid > 0) { kill(pid, SIGKILL); pid = 0; } fprintf(stdout, "\n*** QUIT *** \n"); alarm(0); // clear clock sigset_t pendmask; if(sigemptyset(&pendmask) < 0) { perror("sigemptyset(): "); } if(sigpending(&pendmask) < 0) { perror("sigpending(): "); } if(sigismember(&pendmask, SIGALRM)) { signal(SIGALRM, SIG_IGN); sigaction(SIGALRM, &act, NULL); } siglongjmp(jmpbuf, 1); } |
实验结果
源程序名 |
可执行程序名 |
myshell.c |
myshell |
编译生成可执行文件:
使用如下指令:
`make myshell`,即可得到可执行文件`myshell`
运行程序:
执行命令`./myshell -t 5`:
- 输入ls等命令能正确执行:
- 执行sleep 10,大约5秒后终端上显示 “*** TIMEOUT ***”并退出sleep:
- 再次执行sleep 10,然后按Ctrl+\,终端上显示 “*** QUIT ***”,并退出sleep,且大约5秒后不会提示 “*** TIMEOUT ***”:
- 在等待输入命令时,按Ctrl+\,终端提示 “*** QUIT ***”,但不退出shell:
执行命令`./myshell`,不会受时间限制:
体会和建议
体会:通过本次实验,我对信号和中断有了更深的了解。“纸上得来终觉浅,绝知此事要躬行”。上午刚上完课下午就做实验,做的时候还是有一些吃力,将书上的例子都看懂了才勉强把实验做完,实现了实验要求的效果。感觉以后还是得提前预习,老师讲课的进度有点赶不上实验课的进度,这种课程还是写代码比上课重要。上课听着感觉懂了,但是真正做起来却有些云里雾里,摸不着头脑,不知从何下手。
建议:希望以后上机课之前,老师能提前说明实验课需要做什么,比如要用到哪些知识点,以及书上哪些程序对本次实验是比较有帮助的,我们可以通过看书本上的例程去更好的实现实验要求。以及可能要用到的不熟悉的函数能为我们做详细的解释。
完成人姓名及完成时间
完成人姓名 |
完成时间 |
姚怀聿 |
2022年12月16日 |
完整代码链接:https://gitee.com/i-rong/huaiyuyao/tree/master/unix/homework
标签:SIGQUIT,信号处理,pid,实验,信号,act,SIGALRM From: https://www.cnblogs.com/i-rong/p/17259314.html