linuxbingc(进程信号)

Posted 月屯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linuxbingc(进程信号)相关的知识,希望对你有一定的参考价值。

上一篇

信号

一个软中断(只是有这样的一个信号,具体怎么做还是有进程决定,硬中断与之相反)

种类

kill -l 可以罗列信号
非实时信号(非可靠信号)特点:信号可能会丢失1一31
实时信号(可靠信号)
特点:信号不会丢失33一64

总共定义了62个信号

产生

硬件产生:

ctrl+c :2号信号SIGINT,键盘当中按下ctrl+c结束一个进程的时候,其实是进程收到了2号信号。2号信号导致了进程的退出。
ctrl+z : 20号信号SIGTSTP
ctrl+| :3号信号SIGQUIT

kill命令向进程发送信号

kill-[信号值][pid]

软件产生

kill函数

#include <signal.h>
int kill(pid_t pid,int sig) :
pid :进程号,要给那个进程发送信号,则填写那个进程的进程号sig :要发送信号的值

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
  kill(getpid(),2);
  while(1)
    printf("test***");
    sleep(1);
  
  return 0;

raise函数

int raise(int sig);(自己个自己发送)
sig :要发送的信号值,该函数的实现当中是调用kill函数

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
  raise(2);
  while(1)
    printf("test***");
    sleep(1);
  
  return 0;


扩展

使用gdb查看

ulimit -c unlimited//修改coredump政策
gdb sigtest core.31339


内存访问越界 11信号
除0 8信号
double free 6信号(free函数清除两次指针)

信号处理

  • 默认处理方式:SIG_DFL,操作系统当中已经定义信号的处理方式了:2-》终止进程;11 -》终止进程,并且产生核心转储文件
  • 忽略处理方式:SIG_IGN,该信号为忽略处理(僵尸进程)
    进程收到忽略处理方式的信号后,是不进行处理的。如:SIGCHLD信号,子进程先于父进程退出,子进程退出的时候会给父进程发送SICCHLD,而父进程接收到这个信号之后,是忽略处理的,导致了父进程并没有回收子进程的退出状态信息,从而子进程变成了僵尸进程。
  • 自定义处理方式:程序员可以更改信号的处理方式,定义一个函数,当进程收到该信号的时候,调用程序猿自己写的函数。

操作系统定义的信号处理方式

man 7 singal

注册

一个进程收到一个信号,这个过程称之为注册,信号的注册和注销并不是一个过程是两个独立的过程。内核中信号注册位图以及sigqueue队列的的了解
task_struct结构体内部
struct sigpending pending;
siget_t

位图更改为1,添加sigqueue节点到sigqueue队列
信号注册的时候,会将信号对应的比特位从0修改为1,表示当前进程收到了该信号。还需要在sigqueue队列当中添加一个sigqueue节点,队列在操作系统内核当中本质上就是一个双向链表(先进先出的特性)
区别:

  • 非实时信号的注册
    第一次注册:
    修改sig位图(0-1),修改sigqueue队列
    第二次注册相同信号值的信号:
    修改sig位图(1-1),并不会添加sigqueue节点
  • 实时信号的注册
    第一次注册:
    修改sig位图(0-1) ,修改sigqueue队列
    第二次注册相同信号值的信号:
    修改sig位图(1-1),添加siaqueue节点到sigqueue队列当中

注销

  • 非可靠信号
    1.将信号对应的sig位图当中的比特位置为0 ( 1-0)
    2.将对应的信号的sigqueue节点进行出队操作
  • 可靠信号
    1.将对应的信号的sigqueue节点进行出队操作
    2.判断sigqueue队列当中还有相同信号的sigqueue节点吗
    如果有:则比特位不变
    如果没有:则比特位改变位0

信号自定义

自定义处理方式,就是让程序猿自己定跌某一个信号的处理方式

函数

signal

sighandler_t signal(int signum,sighandler_t handler);
signum:信号值
handler:更改为哪一个函数处理,接受一个函数地址,函数指针typedef void (*sighandler_t)(int);
返回值
成功:之前的handler
失败:SIG_ERR
9号信号(强杀)就是不能被程序员自定义处理的信号。
注意回调
在调用signal函数的时候,给第二个参数传递函数地址的时候并没有调用传递的函数。而是,等到进程收到了某个信号之后,才回调刚刚注册的函数

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void signalback(int sig)
printf("recv signal is:%d\\n",sig);

int main()
  //自定义2号信号的处理方式
  signal(2,signalback);
  while(1)
    printf("process\\n");
    sleep(1);
  
  return 0;

sigaction

int sigaction(int signum,const struct sigaction *act/*输入性函数*/,
struct sigaction*oldact/*输出型函数*/)

signum:信号值
act:将信号的处理方式更改为act
oldact:原来信号的处理方式

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigcallback(int sig)
  printf("sig num %d\\n",sig);

int main()
 //1.调用sigaction函数, 自定义2号信号的处理方式;    /
 ///1.1 定义 struct sigaction 结构体对象
struct sigaction act;
act.sa_handler=sigcallback;
sigemptyset(&act.sa_mask);
 //将act.sa_mask的内存修改为全0 , 防止后续误导进程收到了某些信号。    /
 ///int sigemptyset(sigset_t *set);
struct sigaction oldact;
sigaction(2,&act,&oldact);
 //2.程序进入死循环, 等待进程收到2号信号, 触发调用自定义的函数
while(1)
  sleep(1);


  return 0;


原理

查看源码

信号的捕捉流程

信号的处理时机

当从内核态切换会用户态的时候,会调用do_signal函数处理信号

  • 有,就处理信号(信号的处理方式(默认,忽略,自定义) ) ;判断当前信号是否被阻塞, 信号的注销过程,调用信号的处理方式函数;
  • 没有,就直接返回会用户态

处理信号

  • 默认,忽略:直接在内核就处理结束。
  • 自定义处理:调用程序猿自己定义的处理函数进行处理
    1.执行用户自定义的处理函数(用户空间)
    2.调用sigreturn()再次回到操作系统内核(内核空间)
    3.再次调用会调用do_signal函数处理信号
    4.调用sys_sigreturn函数回到用户空间,继续执行代码

常见的进入到内核的方式:

调用系统调用函数,
内存访问越界,
访问空指针调用库函数

信号的阻塞

信号的阻塞并不会干扰信号注册
而是说进程收到这个信号之后,由于阻塞,暂时不处理该信号。

加上信号阻塞之后,理解信号的处理;

·进入内核,返回之前,会调用do_signal函数处理信号
·有信号要处理,则先判断该信号是否阻塞,如果没阻塞,在处理信号。如果阻塞,则不处理。

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

how:想让sigprocmask做什么事情

  • SG_BLOCK:设置某个信号为阻塞状态
  • SIG_UNBLOCK :设置某个信号为非阻塞状态
  • SIG_SETMASK :用第二个参数“set”,替换原来的阻塞位图。(替换的意思)
    set :新设置的阻塞位图
    oldset :原来老的阻塞位图
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
	//1.阻塞全部信号
  sigset_t set;
  //将 set 的所有比特位全部设置为1 : int sigfillset(sigset_t *set);
  sigfillset(&set);
  sigprocmask(SIG_BLOCK,&set,NULL);
  //死循环,验证进程收到信号的场景
  while(1)
    sleep(1);
  
  
  return 0;



测试

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main()
    for(int i = 1; i <= 64; i++)
        if(i == 32 || i == 33  || i == 19)
            continue;
        
        kill(12803, i);
    
    return 0;

结论:
9号信号和19号信号是不能被阻塞的

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


void sigcallback(int sig)
    printf("recv sig num is %d\\n", sig);


int main()

    //1.将40号/2号信号自定义信号的处理方式
    signal(40, sigcallback);
    signal(2,  sigcallback);

    //2. 阻塞"全部"信号(40/2)
    sigset_t set;
    //将 set 的所有比特位全部设置为1 : int sigfillset(sigset_t *set);
    sigfillset(&set);

    sigprocmask(SIG_BLOCK, &set, NULL);


    //3.人为给该进程发送多次, 40/2号信号

    //4.解除所有信号的阻塞(40/2)
    //getchar 就是让程序等待到这里, 给用户发送信号争取时间
    getchar();
    sigprocmask(SIG_UNBLOCK, &set, NULL);

    return 0;


其他扩展内容

父子进程+进程等待+自定义信号处理方式

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

void sigcallback(int sig)
    //1.当能调用到该函数, 那么一定说明子进程向父进程发送了SIGCHLD信号
    //  隐含的含义就是, 子进程现在退出了。
    printf("recv sig num is %d\\n", sig);

    //2. 基于上面子进程的情况, 进行wait等到的时候, 一定不会阻塞, 因为子进程一定退出了
    int status;
    wait(&status);


int main()
    pid_t pid = fork();
    if(pid < 0)
        perror("fork");
        return 0;
    else if(pid == 0)
        //child
        sleep(5);
        exit(1);
    else
        //father
        //1.这样做就解放了父进程的生产力, 不需要阻塞, 或者循环调用waitpid了
        signal(SIGCHLD, sigcallback);

        while(1)
            printf("i am father process\\n");
            sleep(1);
        

    
    return 0;

volatile关键字-作用:保证内存可见性

每次CPU要计算的数据都是从内存当中获取,拒绝编译时优化的方案(从寄存器当中获取)
gcc/g++的编译选项“-O0, -O1,-O2,-O3,优化级别时越来越高。(理解优化级别越高,程序可能执行的越快)


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

volatile int g_val = 1;

void sigcallback(int sig)
    printf("recv sig num is %d\\n", sig);
    g_val = 0;


int main()
    signal(2, sigcallback);

    while(g_val)

    
    return 0;

以上是关于linuxbingc(进程信号)的主要内容,如果未能解决你的问题,请参考以下文章

linuxbingc(多线程)

linuxbingc(多线程)

linuxbingc(多线程)

linux内核—进程调度时机

进程信号

linux 信号处理