Linux:信号(2):从内核看信号

想要有顺序地学习Linux,入口在这里哦:Linux:目录索引

一、信号在内核中的表示

1.信号在内核中的三种状态

①抵达态

执行信号的处理动作称之为信号抵达,即处理信号的三种方式:忽略、默认、执行自定义函数

②未决态

从信号的产生到信号抵达之间的状态被称为未决

③阻塞态(屏蔽态)

阻塞态是可以用户手动设置的,当信号处于阻塞态时,如果产生了该信号,那就是未决状态,直到用户解除信号的阻塞(屏蔽),该信号才会转成抵达状态,执行抵达动作

④三中状态的对比

属性 抵达态 未决态 阻塞态(屏蔽态)
如何设置 用户手动设置 系统根据阻塞态自动设置 用户手动设置

2.三种状态在内核中的存储

①使用结构体存储

这三种状态分别用三个结构体存储,结构体的内部是足够多的比特位,每一个比特位对应一个信号值。
如信号SIGINT的值是2,信号SIGALRM的值是14,假设阻塞态结构体中的比特位存储内容为:
01010000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
则代表SIGINT和SIGALRM信号处于阻塞态
这里写了64位,具体需要多少位取决于信号值个数,不同操作系统的信号值个数不同,Linux操作系统的信号值有64个

②结构体类型sigset_t

上面说到的三个结构体中的阻塞态和未决态在底层中被设计成类型为sigset_t的结构体,个人推测,假如某个操作系统中的信号值只有32个那么这个结构体可以被设计为:
typedef struct sigset_t
{
int sig;
}sigset_t

如果有80个信号值,也许又会被设计成这样:
typedef struct sigset_t
{
char sig[10];
}sigset_t

③操作结构体sigset_t的函数

#include <signal.h>
// { }内部全部都是伪代码,全都是进行位运算,看懂即可,只要会用这些函数就行
int sigemptyset(sigset_t *set)//清空sigset_t,将所有位,置0
{
memset(set,0cx00,sizeof(sigset_t));
}

int sigfillset(sigset_t *set)//填充,将所有位,置1
{
*set =1;
}

int sigaddset(sigset_t *set, int signum)//添加,第signum位,置为1,signum是信号值
{
*set |= 1<<(signum-1);
}

int sigdelset(sigset_t *set, int signum)//删除,第signum位,置为0,signum是信号值
{
*set &= ~(1<<(signum-1));
}

int sigismember(const sigset_t *set, int signum)//判断,判断signum这个信号在不在set中
{
return set & (1<<(signum-1));
}

3.sigprocmask()函数:设置阻塞态结构体

1.函数原型:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
2.功能描述:
经过上面的“操作结构体sigset的函数”的执行后,set的内容已经被修改成用户指定的内容,再通过sigprocmask函数,根据修改完成的set的内容修改内核的“存放阻塞状态的结构体”中对应比特位的信息
3.返回值:
成功执行时,返回0。失败返回-1,errno被设为EINVAL
4.参数解释:
how:用于指定信号修改的方式,可能选择有三种:
SIG_BLOCK //加入信号到进程屏蔽。
SIG_UNBLOCK //从进程屏蔽里将信号删除。
SIG_SETMASK //将set的值设定为新的进程屏蔽。
set:为指向信号集的指针,该信号集存放的是各个信号的阻塞状态,在此专指新设的信号集,如果仅想读取现在的屏蔽值,可将其置为NULL。
oldset:也是指向信号集的指针,在此存放原来的信号集。

4.sigpending()函数:获取未决态结构体中信息

1.函数原型:
#include <signal.h>
3. int sigpending(sigset_t *set);
2.功能描述:
将“存放未决信息的结构体内容”从内核空间拷贝到用户空间,实质就是拷贝到set这个局部变量里
3.返回值
成功返回0,错误返回-1
4.参数解释:
set:将“内核中储存各个信号未决态的信息”写入到set这个临时结构体变量,然后用户可以对set进行读取,从而实现对“内核中储存各个信号未决态的信息”的读取

5.代码验证

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void handler(int s)
{
    printf("recv %d\n", s); 
}
void handler_quit(int s)//解除信号阻塞态
{
    sigset_t set;//定义一个“存放阻塞态信息”的结构体变量
    sigemptyset(&set);//将结构体初始化为0,即每一个比特位为0
    sigaddset(&set, SIGINT);//SIGINT信号,值为2,将set中的第2位,置1
    sigprocmask(SIG_UNBLOCK, &set, NULL);
    //因为第一个参数是SIG_UNBLOCK,并且set中是第2个比特位为1
    //则将“内核中存储阻塞态信息的结构体的第2位,置0”,即将SIGINT的阻塞态解除
}
int main( void ) {
    signal(SIGINT, handler);//捕获ctl c的信号,执行handler函数
    signal(SIGQUIT, handler_quit);//捕获ctl \的信号,执行handler_quit函数
    sigset_t set;//定义结构体变量
    sigemptyset(&set);//将每一个比特位,置0
    sigaddset(&set, SIGINT);//SIGINT信号,值为2,将set中的第2位,置1
    sigprocmask(SIG_BLOCK, &set, NULL);
    //因为第一个参数是SIG_BLOCK,并且set中是第2个比特位为1
    //则将“内核中存储阻塞态信息的结构体的第2位,置1”,即将SIGINT设为阻塞态

    sigset_t pset;//定义结构体变量
    while ( 1 ) 
    {
        sigemptyset(&pset);//将每一个比特位,置0
        sigpending(&pset);
        //将内核中“存储个信号的未决态信息的结构体”中的信息写入pset
        for (int i=1; i<=_NSIG;i++) //_NSIG=64
        {
            //检测信号值为i的信号在不在未决态结构体中
            //换句话说,就是检测i信号处不处于未决态
            if ( sigismember(&pset, i) == 1 ) 
                putchar('1');//在,打印1
            else 
                putchar('0');//不在,打印0
        }
        //这个循环的作用就是将所有信号的未决态打印出来
        //即将内核中未决态结构体的内容打印出来

            printf("\n");
        sleep(1);
    }
}

这里写图片描述

二、可重入函数和不可重入函数

1.概念

可以写在“信号函数”中的函数,叫做可重入函数;
不可以写在“信号函数”中的函数,叫做不可重入函数;
signal函数捕获信号后执行的自定义函数叫做信号函数

2.满足不可重入函数的条件

函数中包括以下信息:
①malloc\free
②open 系统标准I/O
③使用了静态变量的函数

3.实例证明

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

typedef struct test {
    int a;
    int b;
    int c;
}test_t;

test_t g_data;

void unsafe( void )
{
    if ( g_data.a!=g_data.b 
            || g_data.a!=g_data.c 
            || g_data.b!=g_data.c) {
            printf("%d,%d,%d\n", g_data.a,g_data.b,g_data.c);
    }

}

void handler(int s)
{
    unsafe();
}

int main( void )
{
    test_t one = {1,1,1};
    test_t zero= {0,0,0};

    signal(SIGALRM, handler);

    struct itimerval it;    
    it.it_value.tv_sec = 1;
    it.it_value.tv_usec = 0;
    it.it_interval.tv_sec = 1;
    it.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &it, NULL);
    while ( 1 ) {
        g_data = one;
        g_data = zero;
    }
}

猜你喜欢

转载自blog.csdn.net/w_y_x_y/article/details/80288650