Linux signal()和kill()

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这个参数都有哪些?又是怎么产生的呢?
可以分为以下几种情况:

SignumDescription
SIGABRT由调用abort函数产生,进程非正常退出
SIGALRM用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
SIGBUS某种特定的硬件异常,通常由内存访问引起
SIGCANCEL由Solaris Thread Library内部使用,通常不会使用
SIGCHLD进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
SIGCONT当被stop的进程恢复运行的时候,自动发送
SIGEMT和实现相关的硬件异常
SIGFPE数学相关的异常,如被0除,浮点溢出,等等
SIGFREEZESolaris专用,Hiberate或者Suspended时候发送
SIGHUP发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
SIGILL非法指令异常
SIGINFOBSD 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的时候发送
SIGPROFSetitimer指定的Profiling Interval Timer所产生
SIGPWR和系统相关。和UPS相关。
SIGQUIT输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
SIGSEGV非法内存访问
SIGSTKFLTLinux专用,数学协处理器的栈异常
SIGSTOP中止进程。无法处理和忽略。
SIGSYS非法系统调用
SIGTERM请求中止进程,kill命令缺省发送
SIGTHAWSolaris专用,从Suspend恢复时候发送
SIGTRAP实现相关的硬件异常。一般是调试异常
SIGTSTPSuspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
SIGTTIN当Background Group的进程尝试读取Terminal的时候发送
SIGTTOU当Background Group的进程尝试写Terminal的时候发送
SIGURG当out-of-band data接收的时候可能发送
SIGUSR1用户自定义signal 1
SIGUSR2用户自定义signal 2
SIGVTALRMsetitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITINGSolaris Thread Library内部实现专用
SIGWINCH当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
SIGXCPU当CPU时间限制超时的时候
SIGXFSZ进程超过文件大小限制
SIGXRESSolaris专用,进程超过资源限制的时候发送

例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);
       	  }
    }
}

在这里插入图片描述

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

懒的转换了,凑合一下

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 13
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Linux signal 函数是一个用于在进程之间传递信号的机制。它允许进程给其他进程发送特殊的消息,以通知其执行特定的操作。常用的信号有SIGINT(中断进程)、SIGKILL(强制终止进程)、SIGSTOP(暂停进程)等。使用 signal 函数可以自定义信号处理函数,在收到特定信号时执行指定操作。 ### 回答2: Linux signal 函数是一个系统调用,用于处理程序的信号。程序可以在运行中被其他进程或者操作系统发出的信号中断,而 signal 函数可以被用于捕获和处理这些信号。这些信号可以是,来自操作系统的信号,例如 SIGUSR1 和 SIGUSR2,以及在程序中使用 kill 发送的自定义信号。 signal 函数的语法如下: ```c #include <signal.h> void (*signal(int sig, void (*func)(int)))(int); ``` signal 函数的第一个参数为要处理的信号的编号,第二个参数为捕获到对应信号时要执行的处理函数。 其中,如果第二个参数的取值为 SIG_IGN,表示程序忽略该信号;如果取值为 SIG_DFL,则恢复为信号的默认行为。 signal 函数的返回值是一个函数指针,指向传入的第二个参数。 例如,以下代码可以用来为处理 SIGINT (中断信号) 注册一个信号处理函数: ```c #include <stdio.h> #include <stdlib.h> #include <signal.h> void sigint_handler(int sig) { printf("Caught sigint, exiting...\n"); exit(1); } int main() { signal(SIGINT, sigint_handler); while (1) { printf("Hello, world!\n"); sleep(1); } return 0; } ``` 在这个例子中,我们定义了一个名为 sigint_handler 的函数,当 SIGINT 信号被捕获时执行该函数。在主函数中,我们先调用 signal 函数来注册处理函数,然后开始了一个无限循环输出 "Hello, world!"。如果在运行中按下 Ctrl-C,将会捕获到 SIGINT 信号,此时就会调用 sigint_handler 函数,并输出相应的信息。 总体来说,signal 函数是 Linux 操作系统和应用程序中很常用的函数之一。通过使用 signal 函数,操作系统能够更好地管理进程,而应用程序也能更好的处理信号。 ### 回答3: Linux操作系统中的signal函数用于处理进程接收到信号时的行为。信号可以是来自操作系统内核或其他进程的中断,例如键盘输入,终端中断,内存错误等。 signal函数的语法为: `void (*signal(int sig, void (*func)(int)))(int);` 其中,sig表示将要处理的信号的种类,func表示接收到该信号时运行的处理程序。signal函数可以分为三种类型: 1. 信号的默认行为 操作系统提供一些默认的信号处理行为,例如SIGKILL和SIGSTOP表示强制终止和暂停程序,这些信号不能更改其默认行为。 2. 信号被忽略 如果将sig设置为SIG_IGN,则进程将忽略接收到的该信号。 3. 自定义信号处理程序 使用signal函数还可以定义自定义信号处理程序。这意味着当进程接收到该信号时,将调用指定的处理程序(func)。 不同的信号可以同时使用signal函数进行处理。但是,由于某些信号是操作系统保留的,因此可能无法更改其默认行为或忽略它们。 在Linux中,处理信号也可以使用其他函数,例如sigaction和sigprocmask。但signal函数因其简单的语法和易用性而广泛使用。 总之,signal函数是Linux操作系统中一个非常有用的功能,可以为进程提供一个有效的方法来处理从操作系统中接收到的信息,并控制要采取的操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值