多进程间通信IPC:信号操作函数之signal和kill

        这段时间因为别的组在用我们代码的时候发现ctrl+c无法使进程退出,表现出来的现象是我们的init函数有问题,只要加上我们的init函数,那么遇到ctrl+c的操作时,进程就是无法退出。最终通过gdb调试,发现是另一个组的基于shm和udp的消息发布订阅模块造成的,但是因为没有源码,只能定位到是析构函数有问题。因为开发人员休假无法到位,于是趁此机会把跨进程的信号通信机制了解了一下。毕竟以前都是在书上看的,纸上得来终觉浅。

1. 信号是啥

        它是一种特殊的IPC(进程间通信)机制,它是系统里面已经设计好了的,我们只能去使用它,且是一种异步通信方式。系统里面自带的信号可以用kill -l列出来,总共64个。

  • 非实时信号:前面31种信号值是不可靠信号值(非实时信号),同时发送多个一样的信号只会响应第一个信号,后面的会忽略掉;
  • 实时信号:后面33是后来添加的是可靠的信号值(实时信号值) ,无论发送多少个一样的信号都会逐个响应
  • 信号间可以嵌套,但是会按接收到的顺序逐一响应,接收到多个相同的不可靠信号(1-31号信号)只会执行一个

比较常用的信号:

  • 2号SIGINT:终止一个进程(ctrl + c);
  • 9号SIGKILL:强制杀死一个进程;
  • 18号SIGCONT:回复一个进程的运行(之前已经执行了 SIGSTOP 这个暂停信号);
  • 19号SIGSTOP:暂停一个进程的运行,并没有结束掉

2. kill指令

kill和killall都是用来发信号给某个(些)进程的,和我们原来理解的“杀死”有点区别的,不能光看到kill -9 就以为kill是杀死进程的意思哈。

  • kill -sigNum pid
  • killall -sigNum procName

注:可先使用ps -ef查看目前系统正在运行的所有进行及其进程号等进程信息
kill除了是系统命令外,还是系统提供的函数。用户可以在函数中使用。

函数原型: 

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

函数形参:
    pid:想要用于接收发送出去的信号的进程pid号
        >0:将信号发送给指定的进程;
        =0:将信号发送给当前的进程组;
        =-1:将信号发送给每一个有权限接收这个信号的进程;
        <-1:这个pid=某个进程组的ID取反(-12345)
    sig:需要发送的信号值,0表示不发送任何信号

函数返回值:
    成功返回0
    失败返回-1

示例:

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

int main()
{
	//创建一个子进程
	pid_t id = fork();

	if(id<0)
	{
		perror("fork failed");
		return -1;
	}
	else if(id==0)
	{
		printf("I am %d\n",getpid());
		sleep(10);
		//把自己杀掉
		kill(getpid(),9);
		printf("i am child and pid = %d\n",getpid());
	}
    else
    {
	    printf("I am parent, pid= %d\n",getpid());
	    sleep(20);
    }
	//退出程序并且刷新缓冲区
	exit(0);
}

可以看到子进程自己把自己杀死了,并变成了“僵尸进程(defunct)”,后面的语句无法输出。之后等待10s后,父进程退出,系统回收资源,进程资源释放,无法再显示这两个进程。

 3. 信号中的signal等函数

进程对信号的操作主要有:

  • 缺省:执行默认动作
  • 捕捉:接收到信号后,执行自己规定的动作
  • 忽略:接收到信号后,不执行任何操作
  • 阻塞:接收到信号后,不立即响应,等该信号被解除阻塞,再响应(延迟响应)

注:9,18,19这几个信号不可捕捉,忽略,阻塞;否则所有进程都忽略了这几个函数所有进程将变成守护进程,当进程创建到达最大数时将会导致系统奔溃

(1)捕捉与忽略信号函数signal

函数原型:  

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

函数形参:
    typedef void (*sighandler_t)(int):宏定义了一个函数指针,返回值类型为void,形参类型为:int
    signum:需要捕捉的信号值
    handler:本质是一个指针函数,接受接收到相应信号执行的函数,也可使用:
        SIG_IGN —》忽略
        SIG_DFL —》执行默认动作

函数返回值:
    成功:返回前一个这个信号处理的回调函数的指针
    出错:出现错误时,返回SIG_ERR

当signal(int signum, sighandler_t handler)的handler设置为:

  • SIG_IGN时接收到相对应的信号时将不执行任何动作
  • SIG_DFL时接收到相对应的信号时将执行系统默认的动作(和正常使用效果一样)
  • 其他函数时接收到相对应的信号时按照设定的函数执行动作

示例:
        这里实际上是更改了系统设计的信号值为2、3、4的处理函数,也就是当程序遇到2、3、4信号值时,不再使用默认的处理方式。例如2号sigINT,也就是ctrl+c。如果我们按下ctrl+c或者执行kill -2 pid,这两个操作是否还是像往常一样把进程杀死呢?

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

void handel(int num)
{
	if(num == 2)
	{
		printf("catch the 2 signal\n");
	}
	
	else if(num == 3)
	{
		printf("catch the 3 signal\n");
	}
	else if(num == 4)
	{
		printf("catch the 4 signal\n");
	}
}

int main()
{
	//捕捉2,3,4号信号,并且执行相对应的函数
	signal(2,handel);
	signal(3,handel);
	signal(4,handel);
	//创建一个子进程
	while(1)
	{
		printf("hello world\n");
		sleep(1);
	}

	//退出程序并且刷新缓冲区
	exit(0);
}



 下面我们在程序运行过程中使用ctrl+c试一下,也就是左图中的^Ccatch the 2 signal那一行。我们发现进程并不退出,说明我们使用ctrl+c和kill -2 效果一样。

 (2)阻塞相关信号函数:

        阻塞时,该信号不执行,等待解除阻塞再执行该信号,并且如果是1-31号的信号,发送多个相同信号也只会执行一次。常用的阻塞信号函数原型:

		//一个信号列表(集合)
		sigset_t set;
		
		//信号值
		int signum;
		
		//清空一个信号列表(集合)
		int sigemptyset(sigset_t *set);
	
		//把所有的信号都加入一个信号列表(集合)
       int sigfillset(sigset_t *set);
		
		//把某一个信号都加入一个信号列表(集合)
       int sigaddset(sigset_t *set, int signum);
		
		//把某一个信号从一个信号列表(集合)删除
       int sigdelset(sigset_t *set, int signum);
		
		//判断一个信号是否在一个信号列表(集合)
       int sigismember(const sigset_t *set, int signum);
	
		//把一个信号列表(集合)设置为阻塞状态或解除阻塞
		// how:(1)SIG_BLOCK:在原有的阻塞信号中再添加信号列表(集合)中的信号
		//	   	(2)SIG_UNBLOCK:在原有阻塞信号基础上(在阻塞信号中找)解除信号列表(集合)中的信号
		//	   	(3)SIG_SETMASK:把原有的阻塞信号全部替换成信号列表(集合)中的信号
		//
		//const sigset_t *set:新的信号列表(集合)
		//sigset_t *oldset:原有的信号列表(集合)
		int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
		
函数的返回值:
    成功:sigismember返回1,其他函数均为返回0
    失败:sigismember返回0,其他函数均为返回-1

示例:

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


int main()
{
	//定义一个信号列表(集合)
	sigset_t set;

	//清空一下信号列表(集合)
	sigemptyset(&set);

	//添加信号到信号列表
	sigaddset(&set,2);	
	sigaddset(&set,3);	
	sigaddset(&set,4);	
	//设置信号列表为阻塞
	sigprocmask(SIG_SETMASK, &set, NULL);

	int i=0;
	while(1)
	{
		//判断i是否为20,如果是就解除阻塞
		if(i==20)
		{
			//解除信号阻塞
			sigprocmask(SIG_UNBLOCK, &set, NULL);	
		}
	
		//最后看会不会打印这行
		printf("hello world\n");
		sleep(1);
		
		//每次加1
		i++;
	}

	//退出程序并且刷新缓冲区
	exit(0);
}




  另外,误操作学到一个高级操作,在本目录下搜索字符串:

 带上通配符能搜字符串匹配的内容,不带通配符就是列出进程啦。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值