实验4 信号处理

发布时间 2023-03-26 19:58:34作者: Lumen3ever

Unix实验报告

实验:

实验4 信号处理

专业

计算机科学与技术

班级

1班

姓名

姚怀聿

学号

22920202204632

2022年12月19日

目 录

一、 实验内容描述 1

二、 实验构思 2

三、 实验结果 14

四、 体会和建议 15

五、 完成人姓名及完成时间 16

实验内容描述

本实验的目的是学习和掌握并发进程同步的概念和方法。

实验要求编写具有简单执行时间限制功能的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`:

  1. 输入ls等命令能正确执行:

  1. 执行sleep 10,大约5秒后终端上显示 “*** TIMEOUT ***”并退出sleep:

  1. 再次执行sleep 10,然后按Ctrl+\,终端上显示 “*** QUIT ***”,并退出sleep,且大约5秒后不会提示 “*** TIMEOUT ***”:

  1. 在等待输入命令时,按Ctrl+\,终端提示 “*** QUIT ***”,但不退出shell:

执行命令`./myshell`,不会受时间限制:

体会和建议

体会:通过本次实验,我对信号和中断有了更深的了解。“纸上得来终觉浅,绝知此事要躬行”。上午刚上完课下午就做实验,做的时候还是有一些吃力,将书上的例子都看懂了才勉强把实验做完,实现了实验要求的效果。感觉以后还是得提前预习,老师讲课的进度有点赶不上实验课的进度,这种课程还是写代码比上课重要。上课听着感觉懂了,但是真正做起来却有些云里雾里,摸不着头脑,不知从何下手。

建议:希望以后上机课之前,老师能提前说明实验课需要做什么,比如要用到哪些知识点,以及书上哪些程序对本次实验是比较有帮助的,我们可以通过看书本上的例程去更好的实现实验要求。以及可能要用到的不熟悉的函数能为我们做详细的解释。

完成人姓名及完成时间

完成人姓名

完成时间

姚怀聿

2022年12月16日