Linux - 信号

信号,将内核和进程联系起来,是操作系统内部交互的机制。

通过kill -l指令查看所有信号

注意,这里没有32,33号信号,也是从这里将信号分为两类,1~31位普通信号,34~64为实时信号

信号产生原因 
  • 通过键盘按键产生信号
ctrl-c:2号信号(SIGINT),终止当前运行进程,注意:ctrl-c 产生的信号只发给前台进程
ctrl-\:3号信号(SIGQUIT),终止进程并且产生coredump文件

  • 通过硬件异常产生信号
CPU :0作为除数时,CPU运算单元会产生异常,产生8号信号(SIGFPE)
MMU:访问内存越界或者非法的时候,MMU发现当前内存不合法,就会通知CPU向该进程发送一个11号信号(SIGSEGV),使终止进程(段错误)

  • 系统调用
kill 给某个进程发送某个信号

  • 软件条件产生信号
管道读端关闭,尝试写,会触发13号信号(SIGPIPE)
alarm,若干秒后,操作系统会发送14号闹钟信号(SIGALRM)

关于信号处理的三种方式
  • 忽略 利用signal给信号注册一个函数
  • 默认 通常情况下是让信号异常终止
  • 捕捉 利用signal,有一个函数指针,在函数指针中进行处理(无穷大符号,用户和内核如何切换)
//代码功能:利用MyHnadler函数将产生的SIGINT(ctrl-c)信号捕捉,一旦产生SIGINT信号,就打印对应的值
#include <stdio.h>
#include <signal.h>

void MyHandler(int sig)
{
    printf("sig = %d\n",sig);
}

int main()
{
    signal(SIGINT,MyHandler);
    while(1)
    {}
    return 0;
}

下图展示信号捕捉的时候,用户和内核的几次交互

  • 执行顺序,一个正无穷符号。
  • main和MyHandler函数有各自的执行流,各自有各自的调用栈。
  • MyHandler函数结束以前,main函数需等待,这也限制了信号捕捉的使用场景,比如7*24小时的服务器程序时不允许main函数停止的。
  • 本次过程,内核态和用户态共交互4次,途中红圈表示。

信号的阻塞

PCB中维护了两个位图,分别表示已经收到但是没来得及处理的信号(未决信号集),和要去阻塞的信号(信号屏蔽字)
信号集操作函数

对于未决信号集和信号屏蔽字的操作其实就是在操作一个位图,所以我们需要一个位图结构体(sigset_t)
每个信号只用一个bit位表示,非0即1,不表示多少,只表示信号的有效无效状态。
在未决信号集中,‘有效’和‘无效’状态分别表示是否处于未决状态。
在信号屏蔽字中,‘有效’和‘无效’状态分别表示信号是否被阻塞。

int sigemptyset(sigset_t *set); //初始化位图,并将所有位置为0
int sigfillset(sigset_t *set); //将所有位置为1
int sigaddset (sigset_t *set, int signo);//给位图中某一位置为1 
int sigdelset(sigset_t *set, int signo); //给位图中某一位置为0
int sigismember(const sigset_t *set, int signo);//判断某一位是0/1

返回值 
    前四个函数,成功返回0,失败返回-1;
    sigismember函数,若包含返回1,不包含返回0,失败返回-1

sigpromask - 读取或更改进程的信号屏蔽字,指定某一位信号被阻塞

int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
参数
    how : 将哪一位进行改变
    set : 输入型参数,通过设置set中的位
    oset : 输出型参数,设置新的信号屏蔽字之前把旧的备份下来,以便于恢复

返回值:若成功则为0,若出错则为-1

sigpending - 读取未决信号集

int sigpending(sigset_t* set)
参数
    通过set参数传出。
返回值
调⽤成功则返回0,出错则返回-1。 下⾯⽤刚学的⼏个函数做个 实验。程序如下:

代码演示
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void MyHandler(int sig)
{
    printf("sig = %d\n",sig);
}

void PrintSigSet(sigset_t* set)
{
    int i = 1;
    for(; i < 32; ++i) //只考虑普通信号
    {
        if(sigismember(set,i))
        {
            printf("1");
        }
        else
        {
            printf("0");
        }
    }
    printf("\n");
}

int main()
{
    //1.捕捉 SIGINT 信号
    signal(SIGINT,MyHandler);
    //2.把 SIGINT 信号屏蔽掉
    sigset_t set;
    sigset_t oset;
    sigemptyset(&set);
    sigaddset(&set,SIGINT);
    sigprocmask(SIG_BLOCK,&set,&oset);
    //3.循环读取未决信号集
    int count = 0;
    while(1)
    {
        ++count;
        if(count >= 5)
        {
            count = 0;
            //解除信号屏蔽字,再设置上
            printf("解除信号屏蔽字\n");
            sigprocmask(SIG_SETMASK,&oset,NULL);
            sleep(1);
            printf("再次设置信号屏蔽字\n");
            sigprocmask(SIG_BLOCK,&set,&oset);
        }
        sigset_t pending_set;
        sigpending(&pending_set);
        PrintSigSet(&pending_set);
        sleep(1);
    }
    //循环若干次,自动解除屏蔽
    return 0;
}

结果演示:

起始时,未决信号集中没有未决信号,所以全0
当键入ctrl-c时,未决信号集的2号位置(从1开始)被设置为1
到了第五秒的时候,程序自动解除信号屏蔽字
这时SIGINT信号被我们所定义的MyHandler函数所捕捉,打印了一句 sig = %d
回到main函数的时候,重新将信号屏蔽字集置为全0







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值