Linux 信号的处理、含义、发送和定时信号

Linux 信号的处理、含义、发送和定时信号

1. 信号的基本概念

  • 软中断
  • 目的:
    • 让进程知道发生了某种事件
    • 根据该事件执行相应的动作,即执行它自己代码中的信号处理程序 。
  • 来源:内核
  • 请求方:
    • 进程:通过系统调用 kill 给另一个进程发送信号。进程之间可通过信号通信。
    • 内核:进程 执行出错 时,内核向进程发送一个信号,例如非法段访问、浮点数溢出等,也可通知进程特定事件的发生。
    • 用户:通过输入 Ctrl-C 、 Ctrl-\ 等请求内核产生信号
  • 信号的状态
    • delivery:当进程对信号 采取动作(执行信号处理函数或忽略)时称为递送。
    • pending: 信号产生和递送之间的时间间隔内称信号是未决的
    • block: 进程可指定对某个信号采用递送阻塞。若此时信号处理为默认或者捕捉的,该信号就会处于未决的状态。

2. 信号的分类

  • 根据来源

    • 同步信号:由进程的某个操作产生的信号称为 同步信号 ,例如被零除。

    • 异步信号:用户击键这样的进程 的事件引起的信号称为异步信号 ,该信号产生的事件进程是不可控。

  • 根据处理情况:

    • 不可靠信号
      • 信号值小于 SIGRTMIN。
      • 当同时有多个信号产生时,无法及时处理,造成信号的丢失
      • 早期 Unix 系统中的信号机制比较简单和原始,把那些建立在早期机制上的信号叫做不可靠信号
      • 收到信号的速度超过进程处理的速度的时候,不可靠信号将多余的丢弃掉
    • 可靠信号:
      • 在SIGRTMIN-SIGRTMAX之间
      • 可靠信号将来不及处理的信号就会排入进程的队列
  • 是否支持排队:

    • 实时信号:实时信号都支持排队,都是可靠信号。后 32 种为非实时信号
    • 非实时信号:非实时信号都不支持排队,都是不可靠信号。前 32 种为实时信号。

3. 常见信号

信号名称信号说明默认处理
SIGABRT调用abort 时产生该信号,程序异常结束进程终止并且产生core 文件
SIGALRM由alarm 或者 setitimer 设置的 定时器 到期进程终止
SIGBUS总线错误,地址没对齐等,取决于具体硬件进程终止并产生core 文件
SIGCHLD子进程停止或者终止时, 父进程收到 该信号忽略该信号
SIGCONT让停止的进程继续执行进程终止并且产生core 文件
SIGFPE算术运算异常,除 0 等进程终止
SIGHUP进程的控制终端关闭时产生这个信号进程终止并且产生core 文件
SIGILL代码中有非法指进程终止
SIGINT终端输入了CTRL+c 信号 下面用 ^c 表示进程终止
SIGIO异步I/O ,跟 SIGPOLL 一样进程终止
SIGIOT执行I/O 时产生 硬件错进程终止并且产生core 文件
SIGKILL该信号用户不能去捕捉和忽略它进程终止

4. 信号处理

基本信号处理 signal()

目标简单的信号处理
头文件signal.h
函数原型int signal(int signum, void (*action)(int));
参数signum 需响应的信号
action 如何响应(特殊值:SIG_IGN 忽略信号;SIG_DFL 恢复为默认处理)
返回值-1 遇到错误
prevaction 返回之前的处理函数指针
  • 不可靠信号:这是早期不可靠信号处理机制造成的。当执行完一次信号处理函数之后,系统的信号处理就恢复为默认处理,如果想让信号处理函数继续有效,必须重新设置

    void sigHandler(int signalNum)
    {
        printf("The sign no is:%d n", signalNum
        signal(SIGINT, sigHandler); //重新设置
    }
    
  • 面临的问题:

    1. 信号处理函数正在执行,没结束时,又产生一个同类型的信号,这时该怎么处理;

    2. 信号处理函数正在执行,没结束时,又发生了一个不同类型的信号,这时该怎么处理;

    3. 进程执行一个 阻塞系统调用如 read() 时,发生了一个信号 ,这时是让该阻塞系统调用返回错误再接着进入信号处理函数,还是先跳转到信号处理函数,等信号处理完毕后,系统调用再返回。

    • 例子
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <signal.h>
    #define INPUTLEN    20
    
    char input[INPUTLEN];
    void inthandler(int s) {
        printf("I have Received signal %d .. waiting\n", s);
        sleep(2);
        printf("I am leaving inthandler \n");
        signal(SIGINT, inthandler);
    }
    
    void quithandler(int s) {
        printf("I have Received signal %d .. waiting\n", s);
        sleep(3);
        printf("I am leaving quithandler \n");
        signal(SIGQUIT, quithandler);
    }
    
    int main(){
        signal(SIGINT, inthandler);
        signal(SIGQUIT, quithandler);
        do {
            printf("Please input a message\n");
            int nchars = read(0, input, (INPUTLEN - 1));
            if (nchars == -1) {
                perror("read returned an error");
            }
            else {
                input[nchars] = '\0';
                printf("You have inputed: %s\n", input);
            }
        }while(strncmp(input, "quit", 4) != 0);
    }
    
    
    • 结果
    1. 信号处理函数正在执行,没结束时,又产生一个同类型的信号
      a

    2. 信号处理函数正在执行,没结束时,又发生了一个不同类型的信号
      b

    3. 进程执行一个 阻塞系统调用如 read() 时,发生了一个信号
      c

指定信号处理 signaction()

目标指定信号的处理函数
头文件signal.h
函数原型int signaction(int signum, const struct sigaction *action, struct sigaction *prevaction);
参数signum 需处理的信号
action 指向描述操作的结构的指针
prevaction 指向描述被替换操作的结构指针
返回值-1 遇到错误
0 成功
  • sigation 结构体

    struct sigaction{
    	void (*sa_handler)();
    	void (*sa_sigaction)(int,siginfo_t *,void *);
    	sigset_t sa_mask;
    	int sa_flags;
    } 
    
  • sa_flags的标志

    标记含义
    SA_RESETHAND当处理函数被调用时重置,即捕鼠器模式
    SA_NODEFER处理信号时关闭“信号自动阻塞”(sa_mask无效),因此 允许递归调用 信号处理函数
    SA_RESTART当阻塞于系统调时收到信号,如果本标志置位,则信号处理函数返回后,系统调用返回失败而需要重新开始,否则系统调用成功返回。主要用于低速设备相关的系统调用。
    SA_SIGINFO指明使用sa_sigaction 的处理函数值。如果它未设置,则使用旧处理机制, 若设置 ,则 传给处理函数 的包括 信号编号、信号产生的原因和条件等信息

进程的阻塞信号 sigprocmask()

目标修改当前的信号挡板
头文件signal.h
函数原型int sigprocmask (int how, const sigset_t *sigs,sigset_t prev;
参数how 如何修改信号挡板:SIG_BLOCK, SIG_UNBLOCK,SIG_SET
sigs 指向使用的信号列表的指针
prev 指向之前的信号挡板列表的指针或者为 null
返回值-1 遇到错误
0 成功
  • 例子

    #include <stdio.h>
    #include <signal.h>
    
    void sig_handler(int signum, siginfo_t *info, void *myact) {
        if (signum == SIGINT)
            printf("GOT a common signal.\n");
        else
            printf("GOT a real time signal\n");
    }
    
    int main() {
        struct sigaction act;
        sigset_t newmask, oldmask;
        int rc;
    
        sigemptyset(&newmask);
        sigaddset(&newmask, SIGINT);
        sigaddset(&newmask, SIGRTMIN);
        sigprocmask(SIG_BLOCK, &newmask, &oldmask);
        act.sa_sigaction = sig_handler;
        act.sa_flags = SA_SIGINFO;
    
        if (sigaction(SIGINT, &act, NULL) < 0)
            printf("install signal error\n");
        if (sigaction(SIGRTMIN, &act, NULL) < 0)
            printf("install signal error\n");
        printf("pid = %d\n", getpid());
    
        sleep(60);
        sigprocmask(SIG_SETMASK, &oldmask, NULL);
        return 0;
    }
    
    
  • 结果

    image-20221119184910772
    image-20221119184026530

5. 信号发送

  • kill

    目标向一个进程发送信号
    头文件signal.h
    函数原型int kill (pid_t pid , int);
    参数pid 目标进程
    sig 要被发送的信号
    返回值-1 遇到错误
    0 成功
  • raise

    目标向自身进程发送信号
    头文件signal.h
    函数原型int raise(intsig);
    参数sig 要被发送的信号
    返回值-1 遇到错误
    0 成功
  • sigqueue

    目标向进程发送信号
    头文件signal.h
    函数原型int sigqueue(pid_t pid,int sig,const union sigval value)
    参数pid 目标进程的 pid
    sig 被发送信号
    参数
    value 为一整型与指针类型的联合体:
    unionsigval{ int sival_int; void* sival_ptr;}
    返回值-1 遇到错误
    0 成功

6. 父子进程的信号处理

  • 父进程创建子进程时, 子进程继承了父进程信号处理方式, 直到子进程调用 exec 函数

  • 子进程调用 exec 函数后, exec 将父进程中设置为捕捉的信号变为默认处理方式 。

  • 防止僵尸进程产生:

    • 子进程在退出程序时,会向父进程发送 SIGCHLD 信号
    • 父进程在该信号的处理函数中调用 wait 或者 waitpid 获取子进程的退出状态
    • 默认情况下,父进程是忽略该信号的。
  • 例子

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    void intsig_handler(int signum, siginfo_t *siginfo, void *empty) {
        printf("int_handler, my pid=%d\n", getpid());
    }
    
    int main() {
        int pid;
        struct sigaction act;
        act.sa_sigaction = intsig_handler;
        act.sa_flags = SA_SIGINFO;
    
        if (sigaction(SIGINT, &act, NULL) < 0) 
            printf("install signal error\n");
        printf("The parent pid = %d\n", getpid());
        pid = fork();
        if (pid < 0) {
            perror("fork failed.\n"); 
            exit(0);
        }
    
        printf("The return fork = %d\n", pid);
        if (pid == 0) execlp("ls", "ls", NULL);	// if delete
        else									// if delete
            while(1);
    }
    
    • 结果

      image-20221119181842838

    • 若删除上述代码倒数三、四行后

      image-20221119182037854

7. 系统定时信号

alarm()

  • unsigned int alarm(unsigned int seconds);
  • 函数说明 : 用来设置 信号 SIGALRM 在经过参数 seconds 指定的秒数后传送给目前的进程。如果参数 seconds 为 0 ,则之前设置的闹钟会被取消,并将剩下的时间返回。
  • 返回值 : 返回之前闹钟的剩余秒数 ,如果之前未设闹钟则返回 0
  • alarm() 执行后,进程将继续执行,在后期 (alarm 以后)的执行过程中将会在 seconds 秒后收到信号 SIGALRM 并执行其处理函数。

sleep()

  • unsigned int sleep(unsigned int seconds);
  • sleep() 是在库函数中实现的,它是通过调用 alarm() 来设定报警时间, 调用sigsuspend将进程挂起

usleep()

  • unsigned int usleep (unsigned int useconds
  • usleep 的时间单位为 us ,肯定不是由 alarm 实现的,但都是 linux用的,而 window 下不能用,因为都是 sleep 和 usleep 都是在unistd.h 下定义的。
  • 可能被其他信号打断。
  • return :若进程暂停到参数 seconds 所指定的时间,成功则返回 0;若有信号中断则返回剩余微秒数 。

setitimer()

int setitimer (int which, const struct itimerval *value, struct itimerval ovalue));
struct itimerval {
    struct timeval it_interval ; // next value
    struct timeval it_value ; // current value
};
struct timeval {
    long tv_sec ; //seconds 时间的秒数
    long tv_usec ; //micro seconds 时间的微秒数
}
  • which 可选项

    • ITIMER_REAL : 以 系统真实的时间 来计算,它送出 SIGALRM 信号。
    • ITIMER_VIRTUAL : 以该进程在 用户态下花费的时间 来计算,它送出SIGVTALRM 信号。
    • ITIMER_PROF : 以该进程 在用户态下和内核态下所费的时间来计算,它送出 SIGPROF 信号。
  • setitimer 调用成功返回 0 ,否则返回 1

  • 例子

    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    #include <sys/time.h>
    
    int i = 0;
    
    void timeChange(int ms, struct timeval *ptVal) {
        ptVal->tv_sec = ms / 1000;
        ptVal->tv_usec = (ms % 1000) * 1000;
    }
    
    void alarmsign_handler(int SignNo){
        printf("%d seconds\n", ++i);
    }
    
    int main(){
        struct itimerval tval;
        signal(SIGALRM, alarmsign_handler);
        timeChange(1, &(tval.it_value));
        timeChange(1000, &(tval.it_interval));
        setitimer(ITIMER_REAL, &tval, NULL);
        while(getchar() != '#');
        return 0;
    }
    
  • 结果

    image-20221119182128225

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GaspardR

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值