您现在的位置是:首页 > 正文

Linux signal()和kill()

2024-01-30 23:30:47阅读 1

signal()和kill()的简介

signal()

signal(int signum, void (*)(int) handler)

signal()会接受一个int类型的信号量signum,这个信号量可以是运行中产生的也可以是用户通过键盘输入的,接收到之后会跳转到handler函数,并执行handler函数

signal()的参数:

  1. signal是一个带signum和handler两个参数的函数,准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数有handler给出
  2. handler这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void
  3. handler也可以是下面两个特殊值
    SIG_IGN:屏蔽该信号
    SIG_DFL:恢复默认行为
SIG_DFL 为 ((void(*)(int))0)
SIG_IGN 为 ((void(*)(int))1)

(void(*)())0表示将常数0转型为“指向返回值为void的函数的指针”

signal()的一般应用:

  1. 用于响应一个用户自定义的信号:站在应用程序的角度,注册一个信号处理函数
  2. 用于忽略一个信号:设置信号默认处理信号的安装和回复

kill()

kill(pid_t pid, int signum)

kill()会向进程id为pid的进程发送一个信号signum,ID为pid的进程会使用signal()来接收signum信号,并通过signal()做出相对应的响应


用一个流程图来表示kill()和signal()之间的交互:

在这里插入图片描述


那么signum这个参数都有哪些?又是怎么产生的呢?
可以分为以下几种情况:

Signum Description
SIGABRT 由调用abort函数产生,进程非正常退出
SIGALRM 用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
SIGBUS 某种特定的硬件异常,通常由内存访问引起
SIGCANCEL 由Solaris Thread Library内部使用,通常不会使用
SIGCHLD 进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
SIGCONT 当被stop的进程恢复运行的时候,自动发送
SIGEMT 和实现相关的硬件异常
SIGFPE 数学相关的异常,如被0除,浮点溢出,等等
SIGFREEZE Solaris专用,Hiberate或者Suspended时候发送
SIGHUP 发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
SIGILL 非法指令异常
SIGINFO BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
SIGINT 由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
SIGIO 异步IO事件
SIGIOT 实现相关的硬件异常,一般对应SIGABRT
SIGKILL 无法处理和忽略。中止某个进程
SIGLWP 由Solaris Thread Libray内部使用
SIGPIPE 在reader中止之后写Pipe的时候发送
SIGPOLL 当某个事件发送给Pollable Device的时候发送
SIGPROF Setitimer指定的Profiling Interval Timer所产生
SIGPWR 和系统相关。和UPS相关。
SIGQUIT 输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
SIGSEGV 非法内存访问
SIGSTKFLT Linux专用,数学协处理器的栈异常
SIGSTOP 中止进程。无法处理和忽略。
SIGSYS 非法系统调用
SIGTERM 请求中止进程,kill命令缺省发送
SIGTHAW Solaris专用,从Suspend恢复时候发送
SIGTRAP 实现相关的硬件异常。一般是调试异常
SIGTSTP Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
SIGTTIN 当Background Group的进程尝试读取Terminal的时候发送
SIGTTOU 当Background Group的进程尝试写Terminal的时候发送
SIGURG 当out-of-band data接收的时候可能发送
SIGUSR1 用户自定义signal 1
SIGUSR2 用户自定义signal 2
SIGVTALRM setitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITING Solaris Thread Library内部实现专用
SIGWINCH 当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
SIGXCPU 当CPU时间限制超时的时候
SIGXFSZ 进程超过文件大小限制
SIGXRES Solaris专用,进程超过资源限制的时候发送

例1

编写程序:用 fork( )创建两个子进程,再用系统调用 signal( )让父进程捕捉键盘上来的中断信号(即按^c 键);捕捉到中断信号后,父进程用系统调用 kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
Child process1 is killed by parent!
Child process2 is killed by parent!
父进程等待两个子进程终止后,输出如下的信息后终止:
Parent process is killed!

版本一

//
//  main.cpp
//  进程通信
//
//  Created by Chen on 2020/11/26.
//

//#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> //含kill()
//using  namespace::std;

pid_t p1,p2;

void stop(int sig){
    kill(p1, SIGUSR1);
    kill(p2, SIGUSR2);
}

void stop1(int sig){
    printf("Child process1  is  killed by parent!\n");
}
void stop2(int sig){
    printf("Child process2  is  killed by parent!\n");
}

int main(int argc, const char * argv[]) {
    p1 = fork();
    if (p1 > 0) {
        p2 = fork();
        if (p2 > 0) {
//            cout<<"this is parents"<<endl;
            signal(SIGINT,stop);
            wait(NULL);
            wait(NULL);
            printf("Parent process is killed!\n");
        }
        if (p2 == 0) {
//            cout<<"this is p2"<<endl;

            signal(SIGINT, SIG_IGN);    
            //屏蔽默认的 SIGINT信号处理,这句话非常重要,我会在下面分析原因
            
            signal(SIGUSR2, stop1);
            
            pause();
        }
    }
    
    if (p1 == 0) {
//        cout<<"this is p1"<<endl;
        signal(SIGINT, SIG_IGN);    //屏蔽默认的 SIGINT信号处理,同上
        signal(SIGUSR1,stop1);
        pause();
    }
    return 0;
}

在这里插入图片描述

接下来我们回到主函数main来分析一下整个流程:

  1. 第一句话p1 = fork();

我们在这里创建了一个子进程p1,如果p1的返回值是大于0的说明此时是父进程,所以此时可以创建另一个子进程p2

如果不懂fork函数干了什么?为什么要在if判断中生成p2 ?为什么p1、p2返回值>0说明是父进程,返回值是0说明是子进程?可以参考这篇文章

  1. 如果p2的返回值也是大于0的 if (p2 > 0) 说明还是在父进程中

此时根据我们的题目要求:系统调用 signal( )让父进程捕捉键盘上来的中断信号(即按^c 键),我们需要在此执行一次signal函数,也就是这句代码 signal(SIGINT,stop); 用来接收Ctrl + C,接收之后我们还需要用signal函数去发送一个信号signal(SIGINT,stop);,发送的这个信号是一个返回值为 void (*)(int) 类型的函数 注意:返回的是一个函数!

  1. 父进程在发送完信号之后会调用wait函数去等待其他进程(他所创建的两个子进程)执行完毕,然后在做输出,也就是这句话 printf("Parent process is killed!\n");

  2. 如果刚刚p2的返回值是0,说明此时是p2子进程

首先执行了这句话!!!!!!!!
signal(SIGINT, SIG_IGN);

为什么要去执行这句话呢?

因为,子进程会从父进程中继承Ctrl + C信号,即为其默认的处理程序,在子进程中并没有屏蔽Ctrl + C信号,因此,当输入Ctrl + C信号时,子进程会在处理父进程为其指定的信号之前,调用默认的处理Ctrl + C信号的程序,直接退出。因此,解决这个问题的办法,就是在子进程中屏蔽掉系统默认的对Ctrl + C信号的处理

signal(SIGINT, SIG_IGN);就是将Ctrl + C信号直接屏蔽掉

  1. 屏蔽掉Ctrl + C信号之后,执行signal(SIGUSR2, stop2);接收到SIGUSR2信号之后跳转执行stop2函数,输出printf("Child process2 is killed by parent!\n");结束

  2. 子进程p1同理与p2的过程,在此不进行赘述

通过以上分析可以得到,如果我们没有在两个子进程里面添加signal(SIGINT, SIG_IGN);这句话,那么我们的运行结果就是这样的

在这里插入图片描述


版本二:

//
//  main.c
//  进程通信
//
//  Created by Chen on 2020/11/26.
//
//编写程序:用 fork( )创建两个子进程,
//再用系统调用 signal( )让父进程捕捉键盘上来的中断信号(即按^c 键);
//捕捉到中断信号后,父进程用系统调用 kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
//Child process1 is killed by parent!
//Child process2 is killed by parent!
//父进程等待两个子进程终止后,输出如下的信息后终止:
//Parent process is killed!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> //含kill()

int wait_mark;

void stop(){
	wait_mark = 0;
}

void waiting(){
	while(wait_mark != 0);
}


int main(int argc, const char * argv[]) {
	pid_t p1,p2;
	//signal(SIGINT,stop);		//这里即将被取消注释
	while((p1=fork())==-1);		//创建子进程p1
    	if (p1 > 0) {
		while((p2=fork())==-1);	//创建子进程p2
        	if (p2 > 0) {
			wait_mark = 1;
            signal(SIGINT,stop);	//接受到^c信号,转到stop函数(这里即将被注释)
			waiting();
			kill(p1,16);	//向p1发送软中断信号16
			kill(p2,17);	//向p2发送软中断信号17
			wait(NULL);		//同步
			wait(NULL);
            	printf("Parent process is killed!\n");
			exit(0);
        	} else {
			wait_mark = 1;
            	signal(17, stop);	//接收到软中断信号17,转到stop函数
			waiting();
            	lockf(stdout,1,0);
			printf("Child process2 is killed by parent!\n");
			lockf(stdout,0,0);
			exit(0);
       		}
    	} else {
		wait_mark = 1;
		signal(16, stop);	//接收到软中断信号16,转到stop函数
		waiting();
		lockf(stdout,1,0);
		printf("Child process1 is killed by parent!\n");
		lockf(stdout,0,0);
		exit(0);
    	}
    return 0;
}

在这里插入图片描述

此时可以发现运行之后只输出了Parent process is killed!

为什么呢?

我们知道,同一个程序中创建的所有进程之间是没有优先级之分的,也就是说,OS可能先运行父进程,也可能先运行子进程!

如果OS先运行了子进程,但是子进程的用户自定义信号量是SIGUSR1不是SIGINT!此时子进程直接被Ctrl + C给结束掉了。。。

即使先运行父进程,子进程也会从父进程中继承Ctrl + C信号,即为其默认的处理程序,在子进程中并没有屏蔽Ctrl + C信号,因此,当输入Ctrl + C信号时,子进程会在处理父进程为其指定的信号之前,调用默认的处理Ctrl + C信号的程序,直接退出。


根据版本一我们知道,可以在子进程中屏蔽掉系统默认的对Ctrl + C信号的处理来完成p1、p2的输出,那么有没有其他方法呢?

我们知道创建一个子进程,那么这个子进程的代码内容和父进程是完全一样的,但是!执行的起点是不一样的

比如说,子进程p1的起点就是if (p1 > 0)

int main(int argc, const char * argv[]) {
	pid_t p1,p2;
	while((p1=fork())==-1);		//创建子进程p1
    	if (p1 > 0) {	//p1起点!!!!!!
		while((p2=fork())==-1);	//创建子进程p2
        	if (p2 > 0) {
			wait_mark = 1;
			...
			

那么我们在进程的起始位置之前就发送信号量,是不是就可以解决子进程直接使用从父进程中继承Ctrl + C信号,从而导致不能运行的问题

改过之后的代码是:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> //含kill()

int wait_mark;

void stop(){
	wait_mark = 0;
}

void waiting(){
	while(wait_mark != 0);
}


int main(int argc, const char * argv[]) {
	pid_t p1,p2;
	signal(SIGINT,stop);		//这里被取消注释了
	while((p1=fork())==-1);		//创建子进程p1
    	if (p1 > 0) {
		while((p2=fork())==-1);	//创建子进程p2
        	if (p2 > 0) {
			wait_mark = 1;
            //signal(SIGINT,stop);	//接受到^c信号,转到stop函数(这里被注释了)
			waiting();
			kill(p1,16);	//向p1发送软中断信号16
			kill(p2,17);	//向p2发送软中断信号17
			wait(NULL);		//同步
			wait(NULL);
            	printf("Parent process is killed!\n");
			exit(0);
        	} else {
			wait_mark = 1;
            	signal(17, stop);	//接收到软中断信号17,转到stop函数
			waiting();
            	lockf(stdout,1,0);
			printf("Child process2 is killed by parent!\n");
			lockf(stdout,0,0);
			exit(0);
       		}
    	} else {
		wait_mark = 1;
		signal(16, stop);	//接收到软中断信号16,转到stop函数
		waiting();
		lockf(stdout,1,0);
		printf("Child process1 is killed by parent!\n");
		lockf(stdout,0,0);
		exit(0);
    	}
    return 0;
}

在这里插入图片描述

例2

编写程序实现进程的管道通信。用系统调用 pipe( )建立一管道,二个子进程 P1 和 P2 分别向管道各写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
父进程从管道中读出二个来自子进程的信息并显示(要求先接收 P1,后 P2)。

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

int pid1,pid2;

main(){
    int fd[2];
    char outpipe[100],inpipe[100];
    pipe(fd);						//创建一个管道
    while((pid1=fork())==-1);
    if(pid1==0){
         lockf(fd[1],1,0);
         sprintf(outpipe,"child 1 process is sending message!");
									//把串放入数组outpipe中
         write(fd[1],outpipe,50);	//向管道写长为50字节的串
         sleep(5);					//自我阻塞5秒
         lockf(fd[1],0,0);
         exit(0);
    }else{
    	  while((pid2=fork())==-1);
        if(pid2==0){
            lockf(fd[1],1,0);		//互斥
            sprintf(outpipe,"child 2 process is sending message!");
            write(fd[1],outpipe,50);
            sleep(5);
            lockf(fd[1],0,0);
            exit(0);
        }else{
            wait(0);				//同步
            read(fd[0],inpipe,50);	//从管道中读长为50字节的串
            printf("%s\n",inpipe);
            wait(0);
            read(fd[0],inpipe,50);
            printf("%s\n",inpipe);
            exit(0);
       	  }
    }
}

在这里插入图片描述

本文所涉及的函数汇总(指导书给的定义)

懒的转换了,凑合一下

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

网站文章