【Linux】7.0 信号

news2024/12/19 11:28:28

文章目录

  • 信号的基本概念
  • kill -l 查看信号列表
  • 信号的处理方式
    • signal( ) 自定义处理信号
  • 信号的产生方式
    • 键盘产生
    • 进程异常(core dump)
    • 系统调用
    • 软件条件
  • 信号的发送(OS)
    • 信号常见相关名词解释
    • 进程接收处理信号原理
    • 信号集函数的使用
    • 打印pending表
  • 信号的捕捉
  • 可重入函数
  • volatile关键字
  • SIGCHLD信号

信号的基本概念

生活角度的信号
闹钟、红绿灯、信号强、鸡叫声… -> 都可以代表一种信号,这种信号是给人看的
当我们听到这些场景触发的时候,我们立马能想到什么,我立马就知道了接下来即将发生什么,我们该如何应对
对于信号的处理动作,我们早就知道了,甚至远远早于信号的产生
是不是只有这些场景在我们面前我们才知道该怎么做呢?其实和场景是否被触发,没有直接关联!
本质:我记住了现实生活中各种行为所代表的意义,并且知道看到这些行为我应该使用什么处理方式

计算机角度的信号
信号的产生->信号是给进程发的->进程要在合适的时候,执行相应的动作
进程在没有收到信号的时候,就知道应该识别哪些信号,以及对各种信号该如何处理
本质:曾经编写操作系统的工程师在写进程源代码的时候就已经设计好了所以进程具有识别并处理信号的能力,是远远早于信号产生的

在生活中,我们收到某种信号的时候,并不是一定处理的信号随时都可能产生(异步),但是我当前有更重要的事情进程收到信号的时候,并不是立即处理,而是在合适的时候进行处理既然信号不能被立即处理,已经来的信号,是不是就应该被暂时保存起来信号被保存在哪里呢?PCB (struct task_struct)
信号的本质也是:数据
信号的发送的本质:操作系统往进程task_struct内写入数据

结论:
1.信号是操作系统发送给进程的一种数据,信号发送的本质就是向进程的PCB内写入数据。
2.进程收到信号后并不是立即处理,而是在合适的时候对其进行处理,在此期间信号被暂时保存了起来
3.进程收到信号后会做出反应是因为编写操作系统的工程师在写进程源代码的时候就已经设计好了所以进程具有识别并处理信号的能力,是远远早于信号产生的

kill -l 查看信号列表

指令:kill -l 命令可以用于查看系统定义的信号列表
在这里插入图片描述
每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到
编号1 ~ 31的信号都是普通信号,剩下的都是实时信号。本章讨论的是普通信号,这些信号各自在什么条件下产生,默认处理动作是什么,在man 7 single 中有详细说明

信号的处理方式

一般而言,进程收到信号的处理方式有三种情况
1.默认动作 – 一部分是杀死自己,或者暂停等待
2.忽略动作 – 也是信号的处理方式之一,不过就是收到信号后什么也不干
3.自定义动作(信号的捕捉) – 使用signal等方法,修改进程收到信号的处理动作(默认 -> 自定义)

signal( ) 自定义处理信号

在这里插入图片描述
第一个参数 signum
需处理的信号的编号或者宏

第二个参数 handler
处理程序,用于修改进程收到信号的默认处理动作

小测试1

//前台进程运行时,输入Ctrl + C, OS会向前台进程发送2号信号(SIGINT),默认情况下进程会终止
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void handler(int signum)
{
  printf("get a signal !! signal number = %d, process pid = %d", signum, getpid());
}

int main()
{
  signal(2, handler);
  while (1){
    sleep(1);
    printf("I'm a process, my pid = %d\n", getpid());
  }
  return 0;
}

实验现象
在这里插入图片描述
运行程序,发现当我们输入Ctrl + c时进程并未终止反而打印了一句话,而这句话就是我们的自定义动作。此时我们可以通过
ps axj | grep + 可执行程序文件名来进行进程的查找运行进程的pid,然后使用kill -9 + pid来终止进程

小测试2
在这里插入图片描述
使用for循环对所有信号都进行捕获,修改进程收到信号的默认处理动作。我们发现发送2号、3号、20号信号都成功被捕获,但是发送9号信号的时候,进程被杀死了。这说明9号信号是不能被捕获的

基本结论:
1.信号的产生方式之一就是键盘
2.9号信号无法被捕获,所以也不能被自定义

信号的产生方式

1.键盘产生 2.进程异常 3.系统调用 4.软件条件
但是虽然信号产生的方式很多,但是无论信号产生的方式千差万别,最终一定是通过OS向目标进程发送信号的

键盘产生

在处理信号方式的实验中,我们向前台进程发送^C, ^Z, ^\ 以及使用指令kill -9 + pid 都是使用键盘产生信号,然后操作系统将信号发送给进程

进程异常(core dump)

程序运行的时候,若出现类似野指针访问,除以零等异常问题,与计算相关的软件或者硬件就会出现问题,而操作系统是软硬件的管理者,就要对软硬件的健康负责,所以操作系统会发送信号给产生异常的进程,让进程终止

当进程崩溃了,我们最想知道什么??
我们最想知道崩溃的原因 -> 收到了哪一个信号
在这里插入图片描述
我还想知道在哪里崩溃的,core dump标志就是来解决这个问题的
在Linux中,当一个进程退出的时候,它的退出码和退出信号都会被设置(正常情况)
当一个进程异常的时候,进程的退出信号会被设置,表明当前信号退出的原因
如果有必要,OS会设置退出信息中的core dump标志位,并将内存中的数据转储到磁盘中,方便调试

在云服务器中,core dump这项服务是被关掉的
指令:ulimit -a (查看系统资源)
指令:ulimit -c 10240(打开core dump 服务)
在这里插入图片描述
小测试

//打开core dump服务后,写一个存在异常的程序,并编译运行
[clx@VM-20-6-centos singal_blog]$ cat signal_test.c
#include <stdio.h>

int main()
{
  int a = 10;
  printf("%d", a / 0);
  return 0;
}
//运行后
[clx@VM-20-6-centos singal_blog]$ ll
total 168
-rw------- 1 clx clx 282624 Oct 26 08:32 core.14988
-rw-rw-r-- 1 clx clx     80 Oct 26 08:05 Makefile
-rwxrwxr-x 1 clx clx   8368 Oct 26 08:32 mytest
-rw-rw-r-- 1 clx clx     84 Oct 26 08:32 signal_test.c

我们发现系统生成了一个文件叫做core.14988,是一个二进制文件,这个文件并不是直接给我们读的,而是给调试器gdb看的

//更改Makefile 
[clx@VM-20-6-centos singal_blog]$ cat Makefile
mytest:signal_test.c
		gcc -o $@ $^ -std=c99 -g   //添加-g选项,提取调试信息
.PHONY:clean
clean:
		rm -f mytest

指令:core-file + core文件名(事后调试)
在这里插入图片描述

程序出现异常后,我们可以使用core dump服务获取其报错信息,然后使用gdb的core-file + 文件名的指令读取异常信息,就可以知道程序在哪出现异常,又收到了什么信号。这种方法叫做事后调试

关于core dump 标志位小测试

[clx@VM-20-6-centos singal_blog]$ cat signal_test.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>


void test1()
{
  if (fork() == 0){
    printf("I'm child\n");
    int a = 10;
    printf("%d", a / 0);
  }
  int status = 0;
  waitpid(-1, &status, 0);
  printf("child exit code = %d, child get signal = %d, core dump flag = %d\n", (status >> 8) & 0xff, status & 0x7f, (status >> 7) & 1);
  printf("father end\n");
}
int main()
{
  test1();
}

在这里插入图片描述
可以看到生成了core文件并且子进程收到了八号信号,core dump标志位被设置成1

进程并非收到所有信号都会生成core文件,比如收到SIGINT(2号信号)就不会生成,可以自行测试一下

系统调用

1.kill系统调用接口
在这里插入图片描述
小测试

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

void Usage(const char* proc){  //使用手册
  printf("Usage:\n\t %s signo who\n", proc);
}

int main(int argc, char *argv[])
{
  if (argc != 3){   //若输入的参数数量不对,则调用使用手册后退出程序
    Usage(argv[0]);
    return 1;
  }
  int who = atoi(argv[2]);       //使用atoi对输入的字符串转化为int类型       
  int signal = atoi(argv[1]);
  kill(who, signal);             //调用kill系统调用接口
  printf("signal = %d, who = %d\n", signal, who); //打印信息
}
//sleep.c
[clx@VM-20-6-centos singal_blog]$ cat sleep.c
#include <stdlib.h>
#include <unistd.h>

int main()
{
  sleep(1000);
  return 0;
}

在这里插入图片描述
可以看到我们通过自己写的程序杀死了一个正在运行的进程,kill接口的作用就是向任意进程发送任意一个信号

2.rasiz

在这里插入图片描述

向自己所在进程发送一个sig信号

3.about

![在这里插入图片描述](https://img-blog.csdnimg.cn/35f97f4aa8

向自己所在进程发送一个abort(八号)信号

软件条件

通过某种软件(OS),来触发信号的发送,系统层面设置定时器,或者某种操作而导致条件不就绪等场景下,触发信号发送

在进程间通信管道部分的学习中,当读端不光不读,并且关闭了读fd,写端一直在写,这是对操作系统资源的一种浪费,操作系统会向写端发送十三号信号来终止进程,这就是一种典型的软件条件触发信号发送

SIGPIPE信号是一种由软件条件产生的信号,在“管道学习"中已经介绍过了,本节主要介绍alarm函数和SIGAKRM信号

alarm 定时器函数
在这里插入图片描述
参数:seconds
等待seconds秒后会向当前进程发送alarm信号(14号)
返回值

设置成功返回0,若alarm函数还未发送信号,在执行下方代码过程中出现alarm(0)取消定时器的指令时,会返回剩余的时间

alarm(0)取消定时器,若距离alarm(5)两秒后收到取消定时器的命令,则函数返回3

小测试1

[clx@VM-20-6-centos singal_blog]$ cat signal_test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>

void handler(int signum)              //检验alarm函数发送几号信号
{
  printf("signum = %d\n", signum);
}
int main()
{

  for (int i = 1; i <= 31; i++){
    signal(i, handler);
  }
  alarm(3);
  while (1){
    sleep(1);
    printf("I'm a process !! pid = %d\n", getpid());
  }
  return 0;
}

//测试结果
[clx@VM-20-6-centos singal_blog]$ ./mytest
I'm a process !! pid = 2262
I'm a process !! pid = 2262
signum = 14
I'm a process !! pid = 2262

小测试2

 int count = 0;                                                                           
W>void handler(int signum)    
  {    
    printf("hello : %d\n", count);    
    exit(1);                          
  }             
       
  int main()    
  {             
    signal(14, handler);    
    alarm(1);               
    while (1)    
    {            
      count++;    
    }             
    return 0;    
  }              
//结果1
[clx@VM-20-6-centos singal_blog]$ ./mytest
hello : 434772462   //可以看到在1秒钟时间里count++执行了4亿多次

//修改我们的代码
int count = 0;    

int main()    
{    
  alarm(1);    
  while (1)  {    
    printf("hello : %d\n", count);    
    count++;    
  }    
  return 0;                                                                                 
}    

//执行结果
hello : 15389
hello : 15390
hello : 15391Alarm clock //一秒钟打印了一万五千次

结论:频繁的IO会严重影响效率,cpu有大量时间会等待数据从内存刷新到外设

信号的发送(OS)

进程PCB使用一个三十二位的位图结构(uit32_t )来存储信号信息
在这里插入图片描述

OS向进程发送信号的本质:OS向指定进程的PCB中的信号位图中对应比特位设置成1,即完成信号的发送

信号常见相关名词解释

实际信号的处理动作称为信号递达Delivery】(自定义捕捉, 默认, 忽略)

信号从产生到递达之间的状态,称为信号未决Pending】(本质是信号被暂存在PCB信号位图中)

进程可以选择阻塞Block】某个信号

阻塞的本质是OS,允许进程暂时屏蔽指定信号 1.该信号依然是未决的 2.该信号不会被递达,直到解除阻塞,方可递达

进程接收处理信号原理

在这里插入图片描述

在这里插入图片描述

注:block表也称信后屏蔽字.这些位图结构的类型在Linux系统下叫做sigset_t

信号集函数的使用

sigset_t
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的

信号集操作函数汇总

int sigemptyset(sigset_t *set);                   //将所有位置零
int sigfillset(sigset_t *set);                    //将所有位置一
int sigaddset (sigset_t *set, int signo);         //添加一个信号(将对应信号位置1)
int sigdelset(sigset_t *set, int signo);          //删除一个信号(将对应信号位置0)
int sigismember(const sigset_t *set, int signo); //判定一个信号是否在集合中

sigprocmask
在这里插入图片描述
第一个参数 how
在这里插入图片描述
可以传入上述三个宏
第二个参数 set
输入型参数,OS会根据set来对原有的位图结构进行操作
第三个参数 oldset
输出型参数,OS返回原来的位图结构

以上函数是对block 表,也就是信号屏蔽字进行操作。pending表的类型和block相同,但是pending表的修改是由操作系统完成的,我们只能获取pending表但是不能修改

小测试:定制block表

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

int main()
{
  sigset_t set;        //创建两个信号集数据结构
  sigset_t oldset;
  sigemptyset(&set);   //将两个信号集制空
  sigemptyset(&oldset);
  sigaddset(&set, 2);  //将2号信号设置为阻塞
  sigprocmask(SIG_SETMASK, &set, &oldset); //替换OS的block表,用set代替,oldset接收OS原block表
  while (1){
    sleep(1);
    printf("I'm a process, my pid = %d\n", getpid());
  }
  return 0;
}


//现象展示
[clx@VM-20-6-centos sigset_test]$ ll
total 20
-rw-rw-r-- 1 clx clx   91 Oct 26 18:34 Makefile
-rwxrwxr-x 1 clx clx 8632 Oct 26 18:37 mytest
-rw-rw-r-- 1 clx clx  340 Oct 26 18:37 mytest.c
[clx@VM-20-6-centos sigset_test]$ ./mytest   //运行程序
I'm a process, my pid = 10311        
I'm a process, my pid = 10311
^CI'm a process, my pid = 10311
^C^CI'm a process, my pid = 10311             //使用键盘手动发送2号信号
I'm a process, my pid = 10311                 //2号信号并未递达
^\Quit                                        //输入三号信号终止程序

九号信号是管理员信号,不会被阻塞,进程一旦受到9号信号会立马递达,杀死进程

打印pending表

#include <stdio.h>    
#include <stdlib.h>    
#include <signal.h>    
#include <unistd.h>    
    
void print_pending(sigset_t* pending){ //打印思路很简单,若标记位为1则打印1,若为0则打印0    
  for (int i = 1; i < 31; i++){    
    if (sigismember(pending, i)){    
      printf("1");    
    }    
    else {    
      printf("0");    
    }    
  }    
  printf("\n");    
}    
                                                                                            
int main()    
{    
  sigset_t block_set;      //创建block表
  sigset_t pending;        //创建一个信号集结构接收pending表
  sigemptyset(&pending);   
  sigemptyset(&block_set);    
  sigaddset(&block_set, 2); //将二号信号设置成阻塞状态    
  sigprocmask(SIG_SETMASK, &block_set, NULL);    //用我们的新block表替换系统的旧表
  while (1){    
    sigemptyset(&pending);   //将pending表制空
    sigpending(&pending);    //获取OS中的pending表
    print_pending(&pending);    //打印pending表
    sleep(1);    
  }    
}    

实验现象:
在这里插入图片描述
想进程发送2号信号,信号被pending表接收,打印出来的pending表2号位变成1,但是进程并未被终止,因为2号信号被阻塞了。最后发送9号信号,终止进程

若进程收到信号,但此信号属于阻塞状态。当我们修改block表接触这个信号的阻塞状态后,这个信号会被递达,接下来修改上面的代码,看一看这个情况

小测试:取消阻塞,信号递达

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

void print_pending(sigset_t* pending){
  for (int i = 1; i < 31; i++){
    if (sigismember(pending, i)){
      printf("1");
    }
    else {
      printf("0");
    }
  }
  printf("\n");
}

void handler(int signum)  //捕获二号信号,打印一句话
{
  printf("%d singal delivered\n", signum);
}

int main()
{
  int count = 0;
  signal(2, handler); //自定义2号信号递达后进程对其的处理
  sigset_t block_set, oldset;
  sigset_t pending;
  sigemptyset(&pending);
  sigemptyset(&block_set);
  sigaddset(&block_set, 2);
  sigprocmask(SIG_SETMASK, &block_set, &oldset);
  while (1){
    if (++count == 20){//计数器,20秒时取消对二号信号的阻塞
      sigprocmask(SIG_SETMASK, &oldset, NULL);   
    }
    sigemptyset(&pending);
    sigpending(&pending);                                                                   
    printf("count = %d  ", count);
    print_pending(&pending);                                                    
    sleep(1);                                                                   
  }                                                                             
}                                    

在这里插入图片描述

信号的捕捉

信号的延时处理取决于OS和进程。

信号的产生是异步的,进程会在合适的时候处理信号,因为当前进程可能在做更重要的事。

那么什么是合适的时候?
从内核态切换回用户态的时候,进行信号的检测和处理

在这里插入图片描述
用户态:执行用户的代码和数据时,计算机所处的状态叫用户态。用户的代码执行全是在用户态
内核态:执行 OS 的代码和数据时,计算机所处的状态叫内核态。0S的代码执行全是在内核态

在这里插入图片描述
用户的身份是以进程为代表的,CPU寄存器保存当前进程的状态

用户态使用的是,用户级页表,只能访问用户数据和代码
内核态使用的是,系统及页表,只能访问系统数据和代码

进程具有地址空间,虽然能够看到用户和内核的所有数据但是并不一定可以访问,会受权限的约束
用户的数据和代码一定要被加载到内存,那么0S的数据和代码呢?也必须要加载到内存中,OS的代码是怎么被执行到的呢?
内核页表被所有进程共享,通过内核页表所有进程都可以使用操作系统的代码和数据
所以进程不管如何切换,我们都能保证管理它们的是同一个操作系统,因为每个进程都使用同一张系统级页表

所谓的系统调用,实际就是将身份转化为内核,然后使用系统级页表找到系统函数进行执行就可以了
在大部分情况下,OS都可以在进程的上下文中直接运行的,因为所有进程公用一张系统级页表,访问的都是同一个操作系统的数据和代码

用户态和内核态本质区别:权限不同


可以将上图简化
在这里插入图片描述
sigaction
在这里插入图片描述
第一个参数 signum
需要捕获的信号

第二个参数 act
替换操作系统中的struct sigaction 结构体

第三个参数oldact
接收操作系统中的struct sigaction 结构体
在这里插入图片描述
对于struct sigaction 我们只需要认识它第一和第三个成员变量,其余设为0
第一个成员变量 sa_handler
singal函数中的handler,一个函数指针,指向我们的自定义方法

第三个成员变量 sa_mask
信号集,我们在处理比如2号信号的同时,会自动阻塞2号信号。在处理2号信号的同时,我们可能还想屏蔽3号、5号信号等,我们可以将3,5等标志位传给信号集,信号集再传给sigaction函数对这些信号进行屏蔽

使用这个函数编译时需要添加 -D_GNU_SOURCE 选项
gcc -D_GNU_SOURCE -o $@ $^ -std=c99

小测试1:测试信号处理时对其他信号的阻塞作用

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <signal.h>    
  #include <unistd.h>    
      
      
  void handler(int signum)       //自定义方法
  {    
    while (1){    
      printf("get a signal %d, process's pid = %d\n", signum, getpid());    
      sleep(1);    
    }    
  }    
      
  int main()    
  {    
    struct sigaction act;          //创建自己的struct sigaction 变量
    memset(&act, sizeof(act), 0);  //将变量内部都初始化成0   
    sigset_t mask;                 //自己的block表
    sigemptyset(&mask);            //清空block表
    sigaddset(&mask, 3);           //将三号信号假如block表中
    //act.sa_handler = SIG_IGN;    
        
    act.sa_handler = handler;      //将自定义方法函数指针传递给act
    act.sa_mask = mask;            //将block表传递给act
    sigaction(2, &act, NULL);      //调用sigaction函数
    while (1){    
      sleep(5);                                                                             
      printf("I'm running\n");                                          
    }                                                                   
                                                                        
    return 0;                                                           
  }                        

测试现象
在这里插入图片描述

信号2 的处理过程中,信号3收到阻塞无法递达

小测试2:测试信号处理结束,阻塞结束后的情况

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <signal.h>    
  #include <unistd.h>    
      
      
  void handler(int signum)    
  {    
    int count = 0;    
    while (++count <= 10){       //增加计时器,信号2 的处理时间为10s
      printf("get a signal %d, process's pid = %d\n", signum, getpid());    
      sleep(1);    
    }    
  }                                                                                         
  
  int main()
  {
    struct sigaction act;
    memset(&act, sizeof(act), 0);
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, 3);
    //act.sa_handler = SIG_IGN;
    
    act.sa_handler = handler;
    act.sa_mask = mask;
    sigaction(2, &act, NULL);
    while (1){
      sleep(5);
      printf("I'm running\n");                                          
    }                                                                   
                                                                        
    return 0;                                                           
  }                        

测试现象
在这里插入图片描述

信号2处理过程中发送3号信号,信号3阻塞无法递达。信号2 处理接收,阻塞消失,信号3 递达。

普通信号的标记使用的是位图结构,所以在阻塞过程中,不管发送几个信号在阻塞结束后都只执行一次,后pending位会被置0.
实时信号使用的是链表,每接受一次就增加一个结点。两种信号的处理方式不同是由其数据结构决定的

可重入函数

在这里插入图片描述
以上场景包含了两个执行流 1.main执行流 2.信号捕捉执行流。两个执行流的存在让insert函数被重复进入引发了错误。

某个函数一旦重入,有可能出现问题 – 该函数不可被重入 不可重入函数
某个函数一旦重入, 不会出现问题 – 该函数可以被重入 可重入函数

volatile关键字

小测试:编译器优化

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int flag = 0;
void handler(int signum)
{
    printf("chang flag 0 to 1\n");
    flag = 1;
}
int main()
{
  signal(3, handler);
  while (!flag);
  return 0;
}

1.运行程序,使用键盘发送2号信号程序打印chang flag 0 to 1后运行结束。
2.然后我们不修改代码,去Makefile中编译代码处加上-O3选项,进行一定程度的编译器优化
3.再次运行程序,使用键盘不断发送2号信号,程序不断打印chang flag 0 to 1 但是并未终止

导致这样的原因是,cpu和内存的硬件对数据的处理速度存在较大差异,在逻辑判断语句中flag并未被修改,编译器为优化程序的运行效率,就将flag的数值存在缓存中或者寄存器等更快的(距离CPU更近)硬件中,以提高效率。所以信号处理语句中修改的是内存中的flag,但优化后的程序执行逻辑语句cpu并不从内存读取flag了,就导致了上面的现象

解决方案
我们可以给全局变量flag 添加关键字volatile。

volatile的作用
保持内存可见性,不要对我的这个变量做任何优化,读取必须贯穿式读取内存,不要读取中间缓存区,寄存器内部数据

SIGCHLD信号

子进程退出会向父进程发送SIGCHLD信号,该信号的默认处理时忽略,所以之前实验进行waitpid接收子进程好像并未收到信号

#include <stdio.h>      
#include <signal.h>      
#include <stdlib.h>      
#include <unistd.h>      
int main()    
{    
  if (fork() == 0){    
    int count = 5;    
    while (count--){    
      printf("I'm child, mypid = %d\n", getpid());    
      sleep(1);    
    }    
    exit(0);
  }         
  //显式设置忽略17号信号,当进程退出后,自动释放僵尸进程    (只在linux下有效)              
  signal(SIGCHLD, SIG_IGN);                                                  
  while (1);                                                                   
  printf("father end\n");                                                      
  return 0;                
}                          

显式设置忽略17号信号,当进程退出后,自动释放僵尸进程 (只在linux下有效)

void handler(int signum){ 
      pid_t id = 0;   
      while ((id = waitpid(-1, NULL, WNOHANG)) > 0){
        printf("wait child success\n");                                                                                                                     
    }
  }

还可以自定义捕获信号,并在其中进行非阻塞式等待。使用while循环可以接收同时结束的进程发送来的数据。使用非阻塞等待可以在没有进程需要读取的时候父进程自己做自己的事情

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1833.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Redis】3.详解分布式锁

文章目录1. 什么是分布式锁2. 分布式锁的特点3. 常见的分布式锁4. 实现分布式锁5.解决分布式锁中的原子性问题5.1 Lua脚本5.2 使用Java代码调用Lua脚本实现原子性1. 什么是分布式锁 分布式锁是指分布式系统或者不同系统之间共同访问共享资源的一种锁实现&#xff0c;其是互斥的…

【Django框架】——20 Django视图 02 路由命名和反向解析

文章目录一、 路由命名二、reverse反向解析三、通过URL模板页面进行传参四、namespace1.reverse反向解析2.url模板标签在 Django 项⽬中&#xff0c;⼀个常⻅需求是获取最终形式的 URL&#xff0c;⽐如⽤于嵌⼊⽣成的内容中&#xff08;视图和资源⽹址&#xff0c;给⽤户展示⽹…

《网络安全笔记》第七章:注册表基础

一、注册表基础 1、概述 注册表是windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心“数据库”&#xff0c;也可以说是一个非常巨大的树桩分层结构的数据库系统注册表记录了用户安装在计算机上的软件和每个程序的相互关联信息&#xff0c;它包括了计算…

【UDS】ISO14229之0x2F服务

文章目录前言一、理论描述二、使用步骤1.请求2.响应总结->返回总目录<- 前言 简称&#xff1a; “InputOutputControlByIdentifier”&#xff0c;根据标识符控制输入输出 功能&#xff1a; 根据标识符控制输入输出服务用于替换输入信号的值、电控单元内部参数或控制电子…

Telnet连接

❤️人生没有白走的路&#xff0c;每一步都算数❤️ 你是否安装Telnet没毛病&#xff0c;但登录总报错&#xff1f; 巧了&#xff0c; 我也遇到了。 于是我打开浏览器尝试搜索&#xff0c;有许多说的并不详细。 所以呢就有了这篇文章&#xff01; 首先我们准备实验环境&#xf…

oracle中替换字符串的不同写法

replace函数 replace(原字段&#xff0c;“原字段旧内容“,“原字段新内容“) 例如将DEPTNO字段值中的0替换为1&#xff1a; TRANSLATE TRANSLATE(expr, from_string, to_string) from_string 与 to_string 以字符为单位&#xff0c;对应字符一一替换。 用法示例&#xf…

详解数据结构——二叉排序树

目录 二叉排序树 二叉排序树的查找 二叉排序树的插入 二叉排序树的删除 查找时间效率分析 二叉排序树 二叉排序树&#xff0c;又称二叉查找树&#xff08;BST&#xff0c;Binary Search Tree)一棵二叉树或者是空二叉树&#xff0c;或者是具有如下性质的二叉树: 左子树上所有结…

SpringBoot - SpringBoot整合i18n实现消息国际化

文章目录1. MessageSource源码2. 项目环境搭建1. 创建项目服务auth2. 工具类 I18nUtils3. 自定义异常 CommonException4. 统一异常处理 GlobalExceptionHandler3. 业务实现1. 实体类 UserEntity2. 请求实体 UserQo3. 控制层 UserController4. 业务逻辑层 UserService5. 将异常信…

144. 授人以渔 - 如何查找 SAP UI5 官网上没有提到的控件属性的使用明细

本教程第 113 步骤, SAP UI5 应用开发教程之一百一十三 - 授人以渔 - 如何自行查询任意 SAP UI5 控件属性的文档和技术实现细节我用一整篇文章的篇幅,解答了一位学习者这个疑问: 想请教一下 sap.m.Input 控件中,value里设置的内容,比如path,type,constraints,在哪里可以查…

C++ 多态

目录 一、多态的定义和实现 1.1 多态的构成条件&#xff1a; 1.2 虚函数的重写&#xff08;覆盖&#xff09;&#xff1a; 1.3 多态的两个特殊点&#xff1a; 1.4 析构函数的重写&#xff1a; 1.5 override和final 1.6 重载&#xff0c;重定义&#xff08;隐藏&#xff…

Linux【进程地址空间】

进程地址空间&#x1f4d6;1. 地址空间概念&#x1f4d6;2. 写时拷贝&#x1f4d6;3. 虚拟地址空间的优点&#x1f4d6;1. 地址空间概念 在学习C/C内存管理时&#xff0c;我们可能见过这样一幅图&#xff1a; 但是我们可能不是很理解它&#xff0c;首先有一个问题&#xff1a;…

OpenTCS客户端开发之Web客户端(一)

越来越多人私信我关于OpenTCS的问题。可以感觉到很多人对OpenTCS的研究的人多了很多&#xff0c;很好。这些问题很多是关于算法方面的&#xff0c;也有一部分是关于UI方面的&#xff0c;毕竟OpenTCS本质上是一个算法项目&#xff0c;但是如果希望把它进行商业化&#xff0c;那免…

【微服务】服务拆分和远程调用

2.1 服务拆分原则 这里总结了微服务拆分时的几个原则&#xff1a; 不同微服务&#xff0c;不要重复开发相同业务微服务数据独立&#xff0c;不要访问其它微服务的数据库微服务可以将自己的业务暴露为接口&#xff0c;供其它微服务调用 2.2 服务拆分示例 以微服务cloud-demo为…

第三节:运算符【java】

目录 &#x1f392;运算符 &#x1f4c3;1. 什么是运算符 &#x1f4d7;2. 算术运算符 2.1 基本四则运算符&#xff1a;加减乘除模( - * / %) 2.2 增量运算符 - * % 2.3 自增/自减运算符 -- &#x1f4d9;3. 关系运算符 &#x1f4d5;4.逻辑运算符(重点) 4.1 逻辑与…

隔离出来的“陋室铭”

被隔离了 日常锻炼身体就是去公司旁边的酒店游泳&#xff0c;结果酒店里除了小阳人&#xff0c;我就喜提次密称号&#xff0c;7天隔离走起&#xff1b;又因为不想耽误家里孩子上学&#xff0c;老人外出&#xff0c;就选择了单独隔离&#xff0c;结果就拉到了单独的隔离点&…

精通Git(三)——Git分支机制

文章目录前言分支机制简述创建分支切换分支基本的分支与合并操作基本的分支操作基本的合并操作基本的合并冲突处理分支管理与分支有关的工作流长期分支主题分支远程分支推送跟踪分支拉取删除远程分支变基基本的变基操作变基操作的潜在危害只在需要的时候执行变基操作变基操作与…

C++——vector容器的基本使用和模拟实现

1、vector的介绍 vector是表示可变大小数组的序列容器。 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且…

【动手学深度学习PyTorch版】18 使用块的网络 VGG

上一篇请移步【动手学深度学习PyTorch版】17 深度卷积神经网络 AlexNet_水w的博客-CSDN博客 目录 一、使用块的网络 VGG 1.1 AlexNet--->VGG ◼ VGG网络简介 1.2 VGG架构 1.3 总结 二、VGG网络的代码实现 2.1 VGG网络&#xff08;使用自定义&#xff09; 一、使用块的…

软件测试基本概念

目录本章要点什么是软件测试?软件测试的特定?软件测试和开发的区别?软件测试和软件开发中的调试有什么区别?软件测试在不同公司的定位?一个优秀的测试人员应该具备的素质(你为啥要选择测试开发)需求是衡量软件测试的依据从软件测试人员角度看需求为啥需求对软件测试人员如…

SpringBoot 面试题总结 (JavaGuide)

SpringBoot 面试题总结 &#xff08;JavaGuide&#xff09; 用 JavaGuide 复习 SpringBoot 时&#xff0c;找到一些面试题&#xff0c;没有答案&#xff0c;自己花了一天时间在网上找资料总结了一些&#xff0c;有些答案的来源比较杂忘了没有标注&#xff0c;望见谅。 1. 简单…