Linux环境编程-信号管理

发布时间 2023-09-09 16:57:40作者: 冲他丫的

一、基本概念

1、中断

当进程接收到消息后中止当前正在进行进程,转而去执行其它任务,等其它任务执行结束后再返回刚刚中止的位置,可以继续往下运行,这种执行模式称为中断

中断分为硬件中断、软件中断,硬件中断是由硬件设备引发的、软件中断是执行了中断指令引发

2、信号

信号是一种软件中断,由操作系统发出,进程接收后会执行相应的操作

3、常见信号

kill -l 显示所有信号
SIGINT(2)     Ctrl+c      终止
SIGQUIT(3)    Ctrl+\      终止+core
SIGFPE(8)     除0         终止+core
SIGKILL(9)    终止信号     终止
SIGSEGV(11)   非法访问内存  终止+core          

4、不可靠信号和可靠信号

  • 建立在早期的信号处理机制上的信号称为不可靠信号(1-31):不支持排队,可能会丢失,如果同一个信号连续多次发送,很可能进程只接受到一次

  • 建立在新的信号处理机制上的信号称为可靠信号(34-64):支持排队,不会丢之

5、信号的来源

  • 硬件异常:除零、非法访问内存、未定义指令、总线错误

  • 软件异常:通过一些命令、函数产生信号

6、信号的处理方式

1、忽略

2、终止进程

3、终止进程并产生core文件

4、捕获并处理

在信号来之前先向内核注册一个信号处理函数与,要捕获的信号绑定当信号发生时进程会中止先转去执行信号处理函数

7、命令发送信号

kill 信号编号(宏名) 进程id(pid)

二、信号捕获signal

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
    功能:向内核注册一个信号处理函数
    signum:要绑定捕获的信号编号
    handler:函数指针 提供需要注册的信号处理函数
        亦可以使用以下参数
        SIG_IGN 忽略处理
        SIG_DFL 按默认方式处理
    返回值:
        之前的信号处理方式

实例1:

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

//    信号处理函数
void sigint(int num)
{
    printf("我就不死 %d\n",num);    
    signal(SIGINT,sigint);
}

int main(int argc,const char* argv[])
{
    signal(SIGINT,sigint);
    signal(SIGQUIT,sigint);
    signal(SIGKILL,sigint);
    for(;;);
}

实例2:

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

void sigsegv(int num)
{
    printf("非法访问内存了\n");    
    exit(0);
}

int main(int argc,const char* argv[])
{
    signal(SIGSEGV,sigsegv);

    int* p = NULL;
    printf("++++\n");
    *p = 100;
    printf("------\n");
}

注意:只有9 、19 号信号不能被捕获、也不能忽略

注意:有些系统通过signal注册的函数只能有效一次,第一次之后会还原成默认的处理方式,可以在信号处理函数的末尾重新signal注册

注意:信号处理函数结束后会返回信号产生时的代码处继续执行,如果导致信号产生的错误还在,例如段错误、除零等,可能会导致死循环反复调用信号处理函数,正确的处理方式是备份进程数据然后结束进程(exit)

注意:通过fork创建的子进程会继承父进程的信号处理方式,但是通过exec系列函数创建的子进程会恢复默认的信号处理方式

三、信号的产生kill/raise/abort/alarm

1、键盘

Ctrl+C  Ctrl+\  Ctrl+z暂停\挂起  fg继续

2、错误

段错误 除0 硬件故障

3、命令

kill -信号id 进程号
killall -信号id 进程名 向同进程名的所有进程发送信号

4、函数

  • kill
int kill(pid_t pid, int sig);
        功能:向进程号pid的进程发送信号sig
  • raise
int raise(int sig);
        功能:向自己发送信号sig
  • abort
void abort(void);
        功能:向自己发送SIGABRT信号
  • alarm
unsigned int alarm(unsigned int seconds);
        功能:让内核在seconds秒后向自己发送SIGALRM信号
        返回值:上一次alarm设置的剩余秒数

注意:再次调用alarm函数会重置闹钟时间,不会产生两次信号

四、进程休眠sleep/pause

  • sleep
unsigned int sleep(unsigned int seconds);
    功能:让调用进程进入指定的休眠秒数,秒数到了会唤醒,并且在休眠中遇到了正常的信号都会提前唤醒返回
    返回值:剩余没睡的秒数
  • pause
int pause(void);
    功能:让进程进入休眠,直到遇到正常的信号才唤醒
    返回值:要么一直休眠不返回,要么返回-1表示信号来了

实例:

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

void sigalrm(int num)
{
    printf("时间到啦\n");    
}

int main(int argc,const char* argv[])
{
    signal(SIGALRM,sigalrm);
    printf("%u\n",alarm(3));
    sleep(20);
    printf("%u\n",alarm(10));
    for(;;);
}

五、信号集set和信号阻塞sigprocmask

信号集:

是一种数据类型,定义变量可以存储多个信号

sigset_t 128位二进制数 用每一位代表一个信号       

相关的函数:

  • sigemptyset
int sigemptyset(sigset_t *set);
        功能:清空信号集set
  • sigfillset
int sigfillset(sigset_t *set);
        功能:填满信号集set
  • sigaddset
int sigaddset(sigset_t *set, int signum);
        功能:向信号集set中添加信号signum
  • sigdelset
int sigdelset(sigset_t *set, int signum);
        功能:从信号集set中删除信号signum
  • sigismember
int sigismember(const sigset_t *set, int signum);
        功能:测试信号集set中是否存在信号signum
        返回值:
            1   存在
            0   不存在
            -1  非法信号

实例:

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

int main(int argc,const char* argv[])
{
    sigset_t set;
    sigemptyset(&set);

    sigfillset(&set);

    sigdelset(&set,10);
    for(int i=1; i<=128; i++)
    {
        printf("signum=%d is=%d\n",i,sigismember(&set,i));
    }
}

信号阻塞:

当进程正在执行一些关键性的特殊操作时,不适合暂停去处理信号,此时可以让内核先屏蔽信号,等特殊操作完成后再继续发送并处理

当信号产生时,内核会在其维护的一张信号表中给对应的进程设置一个该信号的标记,整个过程称为递送

从信号产生到完成递送的过程有一个时间间隔,处于该间隔中的信号状态称为未决

信号阻塞屏蔽是让信号处于未决状态,暂停递送,当解除阻塞时处于未决状态的信号就会继续递送

每个进程中都有一个信号集用于记录该进程中要屏蔽的信号

  • sigpromask
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
    功能:设置进程的信号屏蔽集
    how:  设置信号屏蔽的方式
        SIG_BLOCK   把set中的信号添加到本进程的信号屏蔽集中    
        SIG_UNBLOCK 从信号屏蔽集中删除set中的信号
        SIG_SETMASK 用set完全替换原来的信号屏蔽集
    set:想要设置的信号集
    oldset:获取设置前信号屏蔽集

实例:

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

void sigint(int num)
{
    printf("死不了\n");    
}

int main(int argc,const char* argv[])
{
    signal(SIGINT,sigint);

    //    准备信号集
    sigset_t set,oldset;

    sigemptyset(&set);
    sigemptyset(&oldset);

    sigaddset(&set,SIGINT);
    sigprocmask(SIG_BLOCK,&set,&oldset);

    sleep(10);
    printf("hehe\n");

    sigprocmask(SIG_UNBLOCK,&set,&oldset);
    for(;;);

}    

六、附带信息的信号处理函数sigaction

  • sigaction
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
    功能:向内核注册一个信号处理函数
    signum:想要绑定的信号
    act:要注册的信号处理函数
   struct sigaction {
              void  (*sa_handler)(int);     //不带信息的信号处理函数
              void  (*sa_sigaction)(int, siginfo_t *, void *);    //  附带额外信息的信号处理函数
              sigset_t   sa_mask;    //  信号屏蔽集,如果有想要屏蔽的信号,可以给该成员赋值
              int        sa_flags;   //  信号绑定的处理标志
                  SA_SIGINFO  使用sa_sigaction来绑定
                  SA_NODEFER  在信号处理过程中绑定的信号不会被屏蔽
                  SA_RESETHAND 该信号处理方式执行一次后,还原会默认的信号处理方式
                  SA_RESTART  绑定的信号执行完处理函数后,被中断的系统调用会重新启动
              void     (*sa_restorer)(void);  //保留NULL
          };   

实例:

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

void sigint(int num)
{
    printf("信号%d来了\n",num);    
}

int main(int argc,const char* argv[])
{
    struct sigaction act = {sigint};
    act.sa_flags = SA_NODEFER|SA_RESETHAND;
    sigaction(SIGINT,&act,NULL);
    for(;;);
}
  • sigqueue
int sigqueue(pid_t pid, int sig, const union sigval value);
    功能:向进程pid发送附带额外数据信息的信号sig
    union sigval {
        int   sival_int;    //  整数
        void *sival_ptr;    //  指针
    };

实例:

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

void sigint_data(int num, siginfo_t* info, void* ptr)
{
    printf("附带信息%s的信号%d来了 信号发送者是%u\n",(char*)info->si_ptr,num,info->si_pid);    
}

int main(int argc,const char* argv[])
{
    pid_t pid = getpid();

    struct sigaction act = {};
    act.sa_sigaction = sigint_data;
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGINT,&act,NULL);

    sleep(3);
    union sigval value;
    value.sival_ptr = "呵呵";
    sigqueue(pid,SIGINT,value);

    for(;;);
}

七、定时器

  • getitimer
int getitimer(int which, struct itimerval *curr_value);
    功能:获取当前进程的定时方案
    which:
        ITIMER_REAL 真实定时器 程序的总运行时间 SIGALRM
        ITIMER_VIRTUAL 虚拟定时器 用户态的运行时间 SIGVTALRM
        ITIMER_PROF 实际定时器 用户态+内核态的运行时间 SIGPROF
            真实定时器 = 实际定时器+切换时间+休眠时间
    curr_value:定时方案 
struct itimerval {
          struct timeval it_interval; //  定时间的间隔时间
          struct timeval it_value;    //  第一次启动定时器的时间
      };
struct timeval {
          time_t      tv_sec;         //  秒
          suseconds_t tv_usec;        //  微秒
      };    
  • setitmier
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
    功能:设置定时器
    new_value:想要设置的定时方案
    old_value:获取旧的定时方案

实例:

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

void sigalrm(int num)
{
    printf("定时器时间到啦\n");    
}

int main(int argc,const char* argv[])
{
    signal(SIGALRM,sigalrm);
    //    设置定时方案
    struct itimerval value = {{1,500000},{5,500000}};
    printf("%d\n",setitimer(ITIMER_REAL,&value,NULL));
    for(;;);
}