信号


kill()

  • 给指定进程发送指定信号
       #include <sys/types.h>
       #include <signal.h>

       int kill(pid_t pid, int sig);

  • pid 进程编号
  • sig 信号
/*************************************************************************
        > File Name: callKill.c
        > Author:
        > Mail:
        > Created Time: Sat 29 Jan 2022 09:48:15 PM CST
 ************************************************************************/

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>


int main() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    }

    if (pid) {
        sleep(5);
        if (kill(pid, SIGQUIT) < 0) {
            perror("kill");
            exit(1);
        }
        int status;
        wait(&status);
        if (WIFSIGNALED(status)) {
            printf("child terminate by signal %d\n", WTERMSIG(status));
        } else {
            printf("child exit with other reason %d\n", WTERMSIG(status));
        }
    } else {
        while (1) {
            sleep(1);
            printf("父进程你快点杀我~~~\n");
        }
    }

    return 0;
}

raise()

  • 给当前进程发送信号

  • 等价 == kill(getpid(), sig);

       #include <signal.h>

       int raise(int sig);
/*************************************************************************
        > File Name: callKill.c
        > Author:
        > Mail:
        > Created Time: Sat 29 Jan 2022 09:48:15 PM CST
 ************************************************************************/

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>


int main() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    }

    if (pid) {
        int status;
        wait(&status);
        if (WIFSIGNALED(status)) {
            printf("child terminate by signal %d\n", WTERMSIG(status));
        } else {
            printf("child exit with other reason %d\n", WTERMSIG(status));
        }
    } else {
        int n = 10;
        while (n--) {
            sleep(1);
            printf("父进程我还有%d秒就要死了~~~\n", n);
        }
        raise(9); //发送信号9,杀死子进程
    }

    return 0;
}

abort()

  • 使当前进程接收SIGABRT信号而异常终止
       #include <stdlib.h>

       void abort(void);

alarm()

  • 调用alarm()函数可以设定一个闹钟,也就是告诉内核在secondes秒之后给当前进程发SIGALRM信号。
       #include <unistd.h>

       unsigned int alarm(unsigned int seconds);

/*************************************************************************
        > File Name: callAlarm.c
        > Author:
        > Mail:
        > Created Time: Sat 29 Jan 2022 10:37:22 PM CST
 ************************************************************************/

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>


int main() {

    alarm(5);
    sleep(2);
    size_t n = alarm(5); //剩余3s
    printf("剩余 %lds\n", n);

    char i;
    for (i = 0; i > -1; i++) {
        printf("i = %d\n", i);
    }

    printf("i == %d\n", i);


    return 0;
}

阻塞信号

实际执行信号处理的动作称为信号递达(delivery),信号从产生到递达之间的状态,称为信号未决(pending)。
进程可以选择阻塞(block)某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才解除该标志。

阻塞信号集

  • 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?
    • Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。
  • 阻塞信号集也叫做当前进程的信号屏蔽字。(signal mask)
API
       #include <signal.h>

       int sigemptyset(sigset_t *set); //信号集置空

       int sigfillset(sigset_t *set); // 初始化设置为full,包括所有信号。

       int sigaddset(sigset_t *set, int signum); //往信号集set增加信号signum

       int sigdelset(sigset_t *set, int signum); //往信号集set除去信号signum

       int sigismember(const sigset_t *set, int signum); //测试signum是否是信号集的成员。
sigprocmask()
  • 该函数可以读取或更改进程的信号屏蔽字
       #include <signal.h>

       /* Prototype for the glibc wrapper function */
       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

       /* Prototype for the underlying system call */
       int rt_sigprocmask(int how, const kernel_sigset_t *set,
                          kernel_sigset_t *oldset, size_t sigsetsize);

       /* Prototype for the legacy system call (deprecated) */
       int sigprocmask(int how, const old_kernel_sigset_t *set,
                       old_kernel_sigset_t *oldset);
  • set 是你要增加或去除的信号集
  • oldset是原来的阻塞信号集
  • how有三个宏:
    • SIG_BLOCK : 阻塞信号集是当前集合oldset和集合set参数的并集。也就是
    • SIG_UNBLOCK : 集合中的信号从当前阻塞信号集合中移除。允许尝试解除未被阻断的信号。
    • SIG_SETMASK : 阻塞信号集被设置为参数set。
  • 如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
代码
/*************************************************************************
        > File Name: sigMask.c
        > Author:
        > Mail:
        > Created Time: Sun 30 Jan 2022 03:01:10 PM CST
 ************************************************************************/

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

int main() {

    sigset_t set, oldset;
    //oldset表示当前信号集
    sigemptyset(&set); //清空信号集合
    sigaddset(&set, SIGINT); //信号加入集合

    sigprocmask(SIG_BLOCK, &set, &oldset); //宏SIG_BLOCK表示阻塞信号
    int n = 10;
    while (n--) {
        sleep(1);
        printf("在%ds内ctrl + c 杀不死沃~\n", n);
    }

    sigprocmask(SIG_SETMASK, &oldset, NULL); //恢复原来信号集

    while (1) {
        sleep(1);
        printf("按下ctrl + c就能结束程序,不要结束沃~~\n");
    }

    return 0;
}

未决信号集

  • sigpending读取当前进程的未决信号集,通过set参数传出。

  • #include <signal.h>
    int sigpending(sigset_t *set);
    
  • 调用成功返回0,否则返回-1.

代码
/*************************************************************************
        > File Name: sigMask.c
        > Author:
        > Mail:
        > Created Time: Sun 30 Jan 2022 03:01:10 PM CST
 ************************************************************************/

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

//打印未决信号集
void outSig(const sigset_t* set) {
    for (int i = 1; i < 32; i++) {
        if(sigismember(set, i)) {
            putchar('1');
        } else putchar('0');
    }
    putchar(10);

    return ;
}

int main() {

    sigset_t set, oldset, pendSet;
    //oldset表示当前信号集
    sigemptyset(&set); //清空信号集合
    sigaddset(&set, 2); //信号加入集合
    //sigaddset(&set, SIGSEGV);

    sigprocmask(SIG_BLOCK, &set, &oldset); //宏SIG_BLOCK表示增加阻塞信号

    //sigpending(&pendSet); //取出未决信号集


    //raise(SIGSEGV); //给当前进程发送段错误信号



    int n = 10;

    while (n--) {
        sleep(1);
        printf("在%ds内ctrl + c 杀不死沃~\n", n);
        sigpending(&pendSet);
        outSig(&pendSet);
    }

    sigprocmask(SIG_SETMASK, &oldset, NULL); //恢复原来信号集

    while (1) {
        sleep(1);
        printf("按下ctrl + c就能结束程序,不要结束沃~~\n");
    }

    return 0;
}

捕捉信号

  • 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
  • 由于信号处理函数的代码是在用户空间(没有捕捉信号的话,是在内核),处理过程比较复杂。
流程

QQ图片20220130163103

sigaction()
       #include <signal.h>

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };
  • sa_handler指定要与signum关联的操作,可能是de-fault操作的SIG_DFL、忽略此信号的SIG_IGN或指向信号处理函数的指针。此函数接收信号号作为其唯一参数。
  • sa_mask指定一个信号掩码,在信号处理程序执行期间,该掩码应被阻塞(即,添加到调用信号处理程序的线程的信号掩码中)。此外,触发处理器的信号将被阻止,除非使用SA_NODEFER标志。
/*************************************************************************
        > File Name: sigMask.c
        > Author:
        > Mail:
        > Created Time: Sun 30 Jan 2022 03:01:10 PM CST
 ************************************************************************/

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>


void sigHandler(int sigmember) {
    printf("略略略,sigmemer = %d,你不行\n", sigmember);
    return ;
}

int main() {

    struct sigaction newac, oldac;
    //oldac保存当前状态
    newac.sa_handler = &sigHandler;
    newac.sa_flags = 0;
    sigemptyset(&newac.sa_mask);

    sigaction(SIGINT, &newac, &oldac);

    int n = 10;
    while (n) {
        sleep(1);
        n--;
    }

    sigaction(SIGINT, &oldac, NULL); //不需要保存当前状态,置NULL

    return 0;
}
pause()
  • 阻塞进程
#include <unistd.h>
int pause(void);
  • 如果信号的处理动作是终止进程,则进程终止,没有返回值;
  • 如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;
  • 如果信号的处理动作是捕捉,则调用信号的处理函数之后pause返回-1,error设置为EINTR,表示”被信号中断“,所以pause只有出错的返回值。
实现sleep()函数
/*************************************************************************
        > File Name: mySleep.c
        > Author:
        > Mail:
        > Created Time: Sun 30 Jan 2022 05:16:54 PM CST
 ************************************************************************/

#include<stdio.h>
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

void sigAlarm(int sigmember) { return ; }

unsigned int mySleep(unsigned int seconds) {
    struct sigaction newac, oldac;
    newac.sa_handler = &sigAlarm;
    newac.sa_flags = 0;
    sigemptyset(&newac.sa_mask);

    sigaction(SIGALRM, &newac, &oldac); //替换SIGALRM的默认处理动作,因为它的默认处理动作是alarm结束后结束进程。
    alarm(seconds);

    pause();
    sigaction(SIGALRM, &oldac, NULL);
    return alarm(0); //返回剩余时间
}



int main() {
    int n = 5;
    while (n) {
        printf("hello my sleep 1s\n");
        mySleep(1);
        --n;
    }

    return 0;
}

sigsuspend()

  • 该函数包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend函数而不是pause。
       #include <signal.h>

       int sigsuspend(const sigset_t *mask);

简单实现sleep
/*************************************************************************
        > File Name: mySleep.c
        > Author:
        > Mail:
        > Created Time: Sun 30 Jan 2022 05:16:54 PM CST
 ************************************************************************/

#include<stdio.h>
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

void sigAlarm(int sigmember) { return ; }

unsigned int mySleep(unsigned int seconds) {
    struct sigaction newac, oldac;
    newac.sa_handler = &sigAlarm;
    newac.sa_flags = 0;
    sigemptyset(&newac.sa_mask);


    sigset_t newmask, oldmask, susmask;
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGALRM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);
    //阻塞闹钟的信号


    sigaction(SIGALRM, &newac, &oldac); //替换SIGALRM的默认处理动作,因为它的默认处理动作是alarm结束程序
    alarm(seconds);

    susmask = oldmask;
    sigdelset(&susmask, SIGALRM);

    //临时设置信号屏蔽字为susmask,并且立刻挂起程序等待信号(原子操作)
    sigsuspend(&susmask);

    unsigned int t = alarm(0);
    sigaction(SIGALRM, &oldac, NULL); //还原信号动作
    sigprocmask(SIG_SETMASK, &oldmask, NULL); //恢复原来信号屏蔽字

    return t;
}



int main() {
    int n = 5;
    while (n) {
        printf("hello my sleep 1s\n");
        mySleep(1);
        --n;
    }

    return 0;
}

事实上,想要不产生僵尸进程的另一种方法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。此方法对Linux可用,不保证在其它UNIX系统可用。


文章作者: Axieyun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Axieyun !
评论
评论
  目录