Linux:什么是信号

注意:Linux中的kill命令是像进程发送信号,kill不是杀死的意思,-9表示无条件退出,但由进程自行决定是否退出,这就是为什么liee -9终止不了系统进程和守护进程的原因

信号其实就是一个软件中断

1.输入命令,在Shell下启动一个前台进程

2.用户按下Ctrl-C,键盘输入产生一个硬件中断

3.如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断

4.终端驱动程序将Ctrl-C解释成一个SINGINT信号,记得该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)

5.当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行

在这个例子中,有Ctrl-C产生的硬件中断就是一个信号。Ctrl-C产生的信号只能发送给前台进程,命令后加&就可放到后台运行

Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接收到像CTRL+C这种控制按键产生的信号

 信号的种类:

使用这个命令查看:kill -l

非可靠信号:1~31号信号,信号可能会丢失

可靠信号:    34~64号信号,信号不可能会丢失

SIGHUP:1号信号,Hangup detected on controlling terminal or death of controlling process(在控制终端上挂起信号,或让进程结束),ation:term

SIGINT:2号信号,Interrupt from keyboard(键盘输入中断,ctrl + c ),action:term

SIGQUIT:3号信号,Quit from keyboard(键盘输入退出,ctrl+ | ),action:core,产生core dump文件

SIGABRT:6号信号,Abort signal from abort(3)(非正常终止,double free),action:core

SIGKILL:9号信号,Kill signal(杀死进程信号),action:term,该信号不能被阻塞、忽略、自定义处理

SIGSEGV:11号信号,Invalid memory reference(无效的内存引用,解引用空指针、内存越界访问),action:core

SIGPIPE:13号信号,Broken pipe: write to pipe with no readers(管道中止: 写入无人读取的管道,会导致管道破裂),action:term

SIGCHLD:17号信号,Child stopped or terminated(子进程发送给父进程的信号,但该信号为忽略处理的)

SIGSTOP:19号信号,Stop process(停止进程),action:stop

SIGTSTP:20号信号,Stop typed at terminal(终端上发出的停止信号,ctrl + z),action:stop

信号的产生

硬件产生:


1.Ctrl+C:SINGINT(2),发送给前台进程,&进程放到后台运行,fg把刚刚放到后台的进程,再放到前台来运行

2.Ctrl+Z:SIGSTP(20),一般不用,除非有特定场景

3.CTRL+|:SIGQUIT(3),产生core dump文件

产生core dump文件的条件:
1.当前OS一定不要限制core dump 文件的大小, ulimit -a

2.磁盘空间要足够

3.如何产生:

    3.1解引空指针,收到11号信号,产生core dump 文件

    3.2内存访问越界,程序一旦崩溃,就会收到11号信号,也就会产生core dump文件

    3.3double free ,收到6号信号,并产生core dump

    3.4free(NULL),不会崩溃

软件产生:

1.kill函数:

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid, int sig);

参数解释 :

pid:进程号

sig:要发送的信号值

返回值:成功返回0,失败返回-1,并设置错误

2.Kill命令:kill -[信号] pid

3.abort: void abort(void); 收到6号信号,谁调用该函数没睡就收到信号

4.alarm:unsigned int alarm(unsigned int seconds);收到14号信号,告诉内核在seconds秒后进程发送SIGALRM信号,该信号默认处理动作终止当前进程

4.信号的注册:

信号注册又分为可靠信号和非可靠信号的注册

信号注册实际上是一个位图和一个sigqueue队列 

 4.1非可靠信号的注册:

当进程收到非可靠信号时:

1.将非可靠信号对应的比特位置为1

2.添加sigqueue节点到sigqueue队列当中,但是,在添加sigqueue节点的时候,队列当中已然有了该信号的sigqueue节点,则不添加

4.2可靠信号的注册:

当进程锁收到的可靠信号时:

1.在sig位图中更改信号对应的比特位为1

2.不论之前sigqueue队列中是否存在该节点的sigqueue节点,都再次添加sigqueue节点到sigqueue队列当中去

5.信号的注销

5.1非可靠信号的注销:

1.信号对应的比特位从1置为0

2.0将该信号的sigqueue节点从sigqueue队列当中进行出队操作

5.2可靠信号的注销

1.将该信号的sigqueue节点从sigqueue队列当中进行出队操作

2.需要判断sigqueue队列当中是否还有先后同的sigqueue节点:

  1.没有了:信号比特位置为0;

  2.还有:不会更改sig位图中的比特位

6.信号阻塞

6.1信号是怎么阻塞的?

1.信号的阻塞,并不会 干扰信号的注册,信号能注册,但不能被立即处理

2.将block位图中对应的信号比特位置为1,表示阻塞该i新年好

2.进程收到该信号,还是一如既往的注册

4.当进程进入到内核空间,准备返回用户空间的时候,调用do_signal函数,就不会立即取处理该信号了

5.当该信号不被阻塞后,就可以进程处理了

6.2sigproamask

函数原型:int sigprocmask(int how,const sigset_t *set, sigset_t *oldset)

参数解释:

1.how:该做什么样的操作

SIG_BLOCK:设置信号为阻塞

SIG_UNBLOCK:接触信号阻塞

SIFG_SETMASK:替换阻塞位图

2.set:用来设置阻塞位图

SIG_BLOCK:设置某个信号为阻塞,block(new) = block(old) |set

SIG_UNBLOCK:接触某个信号阻塞,block(new) = block (old) & (~set)

SIG_SETMASK:替换阻塞位图,block (new) = set

3.oldset :原来的阻塞位图

下述例子,信号全部被阻塞,采用kill -9,将该进程结束掉

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

void signcallback(int signumber)
{
    printf("chage the signal %d\n",signumber);
}

int main()
{
    sigset_t set;
    sigset_t oldset;
    sigfillset(&set);//所有比特位全置为1,则信号全部会被阻塞
    sigprocmask(SIG_BLOCK,&set,&oldset);
    while(1)
    {
        sleep(1);
    }
    return 0;
}

结果:此时发送信号时不会有作用的,采用kill -9 强杀掉

7.信号未决

未决概念:

实际执行信号的处理动作称为信号递答,信号从产生到递答之间的状态,称为信号未决

进程可以选择阻塞某个信号。被阻塞的信号产生时将保持在未决状态,直到进程接触suicide吸纳后的阻塞,才执行递答的动作,注意。阻塞和忽略时不同的,只要信号被阻塞就不会递答,而忽略时,在递答之后可以选择的一种动作处理

信号的处理方式:

每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作

在上述例子中:

1.SIGHUP信号为阻塞也未产生过,当它递答时执行默认处理动作

2.SIGINT信号产生过,但正在被阻塞,所以暂时不能递达,虽然它的处理动作时忽略,但在没有接触阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再接触阻塞

3.SIGOUIT信号未产生过,一旦产生SIGOUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler 。

自定义信号处理的流程 

 信号的捕捉

如果信号的处理动作是用户自定义函数,再信号递达时就调用这个函数,这就称为信号捕捉

信号捕捉流程

内核态返回用户态会调用do_signal函数,两种情况:

 1.无信号:sys_return 函数,返回用户态

2.有信号:先处理信号,信号返回,再调用do_signal函数

常用信号集操作函数

int sigemptyset(sigset_t *set);://将比特位图全置为0

int sigfillset(sigset_t *set);//将比特位图全置为1

int sigaddset(sigset_t *set, int signum);//将该set位图,多少号信号置为1

int sigdelset(sigset_t *set, int signum);//将该set位图,多少号信号置为0

int sigismember(const sigset_t *set, int signum);//信号signum是否是set位图中的信号

SIGCHLD信号

该信号是子进程在结果是发送给父进程的信号,但是该信号的处理方式是默认处理的

父进程对子进程的发送过来的SIGCHLD信号进行了忽略处理,就会导致子进程称为僵尸进程

可以自定义该信号的处理方式

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

void signcallback(int signumber)
{
    printf("change signal %d\n",signumber);
    wait(NULL);
}

int main()
{
    signal(17,signcallback);
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("I am child\n");
        sleep(1);
        exit(12);
    }
    else
    {
        while(1)
        {
            sleep(1);
        }
    }
    return 0;
}

指令查看后台: ps aux | grep ./fork

#include <stdio.h>
#include <unised.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>

void signcallback(int signumber)
{
    printf("change signal %d\n",signumber);
    wait(NULL);
}

int main()
{
    signal(17,signcallback);
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("I am child\n");
        sleep(1);
        exit(12);
    }
    else
    {
        while(1)    
        {
            sleep(1);
        }
    }

    return 0;
}

  • 12
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值