系统相关
首页 > 系统相关> > linux之信号

linux之信号

作者:互联网

目录

linux ---- 信号的机制

信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。
信号是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。
信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

信号的产生

服务器程序必须处理(或至少忽略)一些常见的信号,以免异常终止。

信号相关命令

查看系统定义的信号列表:kill -l(前31个信号为常规信号,其余为实时信号)
查看信号的详细信息:man 7 signal
列出所有的参数选项:ulimit -a

常用信号

  1. SIGHUP:本信号在用户终端结束时发出,通常是在终端的控制进程结束时,通知同一会话期内的各个作业,这时他们与控制终端不在关联。比如,登录Linux时,系统会自动分配给登录用户一个控制终端,在这个终端运行的所有程序,包括前台和后台进程组,一般都属于同一个会话。当用户退出时,所有进程组都将收到该信号,这个信号的默认操作是终止进程。此外对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
  2. SIGINT:程序终止信号。当用户按下CRTL+C时通知前台进程组终止进程。
  3. SIGQUIT:Ctrl+\控制,进程收到该信号退出时会产生core文件,类似于程序错误信号。
  4. SIGILL:执行了非法指令。通常是因为可执行文件本身出现错误,或者数据段、堆栈溢出时也有可能产生这个信号。
  5. SIGTRAP:由断点指令或其他陷进指令产生,由调试器使用。
  6. SIGABRT:调用abort函数产生,将会使程序非正常结束。
  7. SIGBUS:非法地址。包括内存地址对齐出错。比如访问一个4个字长的整数,但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法地址的非法访问触发。
  8. SIGFPE:发生致命的算术运算错误。
  9. SIGKILL:用来立即结束程序的运行。不能被捕捉、阻塞或忽略,只能执行默认动作。
  10. SIGUSR1:留给用户使用,用户可自定义。
  11. SIGSEGV:访问未分配给用户的内存区。或操作没有权限的区域。
  12. SIGUSR2:留给用户使用,用户可自定义。
  13. SIGPIPE:管道破裂信号。当对一个读进程已经运行结束的管道执行写操作时产生。
  14. SIGALRM:时钟定时信号。由alarm函数设定的时间终止时产生。
  15. SIGTERM:程序结束信号。shell使用kill产生该信号,当结束不了该进程,尝试使用SIGKILL信号。
  16. SIGSTKFLT:堆栈错误。
  17. SIGCHLD:子进程结束,父进程会收到。如果子进程结束时父进程不等待或不处理该信号,子进程会变成僵尸进程。
  18. SIGCONT:让一个停止的进程继续执行。
  19. SIGSTOP:停止进程执行。不能被捕捉、阻塞或忽略,只能执行默认动作。
  20. SIGTSTP:停止终端交互运行,可以被忽略。按下Ctrl+z发出这个信号。
  21. SIGTTIN:当后台进程需要从终端接收数据时,所有进程会收到该信号,暂停执行。
  22. SIGTTOU:与SIGTTIN类似,在后台的进程向终端输出数据时产生。
  23. SIGURG:套接字上出现紧急情况时产生。向当前正在运行的进程发出些信号,报告有紧急数据到达,如网络带外数据到达。
  24. SIGXCPU:超过CPU时间资源限制时产生的信号。
  25. SIGXFSZ:当进程企图扩大文件以至于超过文件大小资源限制时产生。
  26. SIGVTALRM:虚拟使用信号。计算的是进程占用CPU调用的时间。
  27. SIGPROF:包括进程使用CPU的时间以及系统调用的时间。
  28. SIGWINCH:窗口大小改变时。
  29. SIGIO:文件描述符准备就绪,表示可以进行输入输出操作。
  30. SIGPWR:电源失效信号,即关机。
  31. SIGSYS:非法的系统调用。

信号处理

信号处理方式

信号的几种状态:产生、未决、抵达。

信号的5种默认处理动作
1. Term 终止进程
2. Ign 当前进程忽略此信号
3. Core 终止进程,并生成一个Core文件
4. Stop 暂停当前进程
5. Cont 继续执行当前被暂停的进程

信号处理相关函数

int kill (pid_t pid, int sig);

kill(getppid(), 9); 	// 给父进程发送9号信号
kill(getpid(), 0); 		// 给本进程发送9号信号

int raise (int sig);

void abort (void);

信号捕捉
sighandler_t signal (int signum, sighandler_t handler);

注意:信号 SIGKILL 和 SIGSTOP 不能被捕获或忽略。


定时器

unsigned int alarm (unsigned int seconds);

int setitimer (int which, const struct itimerval *new_value, struct itimerval *old_value);


信号集

许多信号相关的系统调用都需要能表示一组不同的信号,Linux使用数据结构信号集sigset_t来表示一组信号。

在PCB中有两个非常重要的信号集:“阻塞信号集”、“未决信号集”。“未决”是一种状态,指的是从信号产生到信号被处理前的这一段时间。“阻塞”是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

信号集相关函数

头文件: #include <signal.h>

int sigfillset (sigset_t *_set)

int sigemptyset (sigset_t *set);

int sigaddset (sigset_t *set, int signum);

int sigdelset (sigset_t *set, int signum);

int sigismember (const sigset_t *set, int signum);

int sigprocmask (int how, const sigset_t *set, sigset_t *oldset);

int sigpending (sigset_t *set);

案例:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
	int num = 0; // 计数
    // 1、创建信号集
    sigset_t set;

    // 2、将2 3 号信号添加到信号集中
    sigemptyset(&set);    // 清空信号集
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);
    // 3、修改内核中信号集的阻塞状态
    sigprocmask(SIG_BLOCK, &set, NULL);

    while (num < 10) {
    	num++;
        // 获取当前未决信号集数据
        sigset_t pendingset;
        sigemptyset(&pendingset);    // 清空信号集
        // 将未决信号集中的数据放入pendingset中
        sigpending(&pendingset);

        // 遍历前32位
        for (int i = 1; i <= 31; i++) {
            if (sigismember(&pendingset, i) == 1) {
                // i号信号在未决信号集中
                printf("1");
            }
            else if (sigismember(&pendingset, i) == 0) {
                printf("0");
            }
            else {
                perror("sigismember");
                exit(-1);
            }
        }
        printf("\n");
        sleep(1);
        // if(num == 10) {
        //     // 解除阻塞
        //     sigprocmask(SIG_UNBLOCK, &set, NULL);
        // }
    }
    return 0;
}

运行结果:

无输入时,内核中前31个信号标志位均位0

输入Ctrl + C,信号SIGINT处于未决状态

输入Ctrl + \,信号SIGQUIT处于未决状态

添加计数变量num后,当num=10,sigprocmask()函数解除信号集set阻塞,Ctrl + C产生的信号SIGINT结束当前进程。


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;                                      // 使用哪一个信号处理对捕捉到的信号进行处理
                                                       // 0,表示使用sa_handler; SA_SIGINFO表示使用sa_sigaction
    void (*sa_restorer)(void);                         // 被废弃掉了
};

案例:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

void myalarm(int num) {
    printf("捕捉到了信号的编号是:%d\n", num);
    printf("xxxxxxx\n");
}

// 过3秒以后,每隔2秒钟定时一次
int main() {

    struct sigaction act;
    act.sa_flags = 0;             // 使用sa_handler处理
    act.sa_handler = myalarm;     // 使用回调函数
    sigemptyset(&act.sa_mask);    // 清空默认临时阻塞信号集

    // 捕捉 SIGALRM 信号
    sigaction(SIGALRM, &act, NULL);

    struct itimerval new_value;
    // 设置延迟的时间, 即3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;
    // 设置间隔的时间, 即每次倒计时2秒
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL);    // 真实时间、非阻塞的
    printf("定时器开始了...\n");

    if (ret == -1) {
        perror("setitimer");
        exit(0);
    }

    while (1)
        ;

    return 0;
}

运行结果:

SIGCHLD信号

SIGCHLD信号产生的3个条件:
1. 子进程结束
2. 子进程暂停
3. 子进程继续运行
都会给父进程发送该信号,父进程默认忽略该信号。
可以使用SIGCHLD信号解决子进程退出产生僵尸进程的问题。

案例:

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

void myFun(int num) {
    printf("捕捉到的信号 :%d\n", num);
    // 回收子进程PCB的资源,彻底结束子进程
    while (1) {
        // 回收所有子进程资源,设置为非阻塞
        int ret = waitpid(-1, NULL, WNOHANG);
        if (ret > 0) {
            // 返回值 >0, 已回收ret子进程资源
            printf("chiled die, pid = %d\n", ret);
        }
        else if (ret == 0) {
            // options=WNOHANG, 表示还有子进程存在
            break;
        }
        else if (ret == -1) {
            // 调用失败
            break;
        }
    }
}
int main() {
    // 防止出现子进程运行结束,父进程还没有注册完信号捕捉
    // 提前设置好阻塞信号集,阻塞SIGCHLD
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_BLOCK, &set, NULL);
    // 创建一些子进程
    pid_t pid;
    for (int i = 0; i < 5; i++) {
        pid = fork();
        if (pid == 0) {
            // 子进程
            // break防止子进程再去进入循环生成子进程,保证只有5个子进程
            break;
        }
    }

    if (pid > 0) {
        // 父进程
        // 捕捉子进程死亡时发出的SIGCHLD信号
        struct sigaction act;
        act.sa_flags = 0;
        act.sa_handler = myFun;
        sigemptyset(&act.sa_mask);
        sigaction(SIGCHLD, &act, NULL);
        // 信号捕捉注册完成,解除SIGCHLD阻塞
        sigprocmask(SIG_UNBLOCK, &set, NULL);

        while (1) {
            printf("parent process pid = %d\n", getpid());
            sleep(1);
        }
    }
    else if (pid == 0) {
        // 子进程
        printf("child process pid = %d\n", getpid());
    }
    return 0;
}

运行结果:

标签:set,int,阻塞,信号,linux,进程,include
来源: https://www.cnblogs.com/happinesspills/p/16542146.html