进程间通信--信号

这篇文章从以下几个方面来了解信号

1、什么是信号

2、信号的产生

3、信号的处理

4、发送信号

5、信号处理函数安全

1、什么是信号?

信号是一种事件通知机制,当接收到该信号的进程会执行相应的操作

2、信号的产生

  1. 由硬件产生,如从键盘输入Ctrl+C可以终止当前进程
  2. 由其他进程发送,例如,在shell进程下,使用命令kill  -信号值 PID
  3. 异常,当进程异常时发送信号

3、信号的处理

信号是由操作系统处理的,所以信号的处理在内核态。如果不是紧急信号的话,它不一定被立即处理,操作系统不会为了处理一个信号而把当前正在运行的进程挂起,因为挂起(进程切换)当前进程消耗很大。所以操作系统一般会将信号先放入信号表中,一般选择在内核态切换回用户态的时候处理信号(不用自己单独进行进程切换以免浪费时间)

信号的处理过程图:

可以使用 kill -l 命令查看所有的信号

信号的处理方式有3种

  1. 忽略(就是不管你发啥我都不理你)
  2. 默认处理方式(会终止一个进程)
  3. 自定义处理方式(自己写一个函数,信号发生时执行自己的函数

下面看看自定义信号的处理方式

(1)signal函数

void(*signal(int sig,void (*func)(int)(int))
sig:信号值
func:信号处理的函数指针,参数为信号值

示例代码:发送信号后执行对数组的排序

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

void fun_sort(int sig)
{
    int arr[6]={3,1,7,4,8,10};
    int n=sizeof(arr)/sizeof(arr[0]);
    int i;
    int j;
    int tmp;
    for(i=0;i<n;i++)
    {
        for(j=0;j<n-i-1;j++)
        {
            if(arr[j]>arr[j+1])
            {
                tmp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=tmp;
            }
        }
    }
    for(j=0;j<n;j++)
    {
        printf("%d ",arr[j]);
    }
    printf("\n");
}

int main()
{
    signal(SIGINT,fun_sort);
    while(1)
    {
        printf("runing\n");
        sleep(1);
    }
    exit(0);
}

运行结果:

(2)sigaction函数

int sigaction(int sig,const struct sigaction *act,struct sigaction *oact);
sig:信号值
act:指定信号的动作,相当于func
oact:保存原信号的动作

看看代码示例:

int main()
{
    struct sigaction act;
    act.sa_handler = fun_sort;
    sigemptyset(&act.sa_mask); 
  //act.sa_flags=SA_RESETHAND;如果不设置,则默认情况下是不被重置的
    sigaction(SIGINT,&act,0);
    while(1)
    {
        printf("runing\n");
        sleep(1);
    }
}

下面是不设置act.sa_flags=SA_RESETHAND的结果,代码会一直运行,上面重置了第二次接收信号就会停止运行。

4、发送信号

Kill函数,进程可以通过kill函数向包括它本身以外的其他进程发送信号,如果没有权限就失败(原因通常是目标进程由另一个进程拥有,就是没权限的时候,总不能控制别人的程序,root会除外)

int kill(pid_t pid,int sig)
它的作用是把信号sig发送给pid进程,成功时返回0;失败原因一般存在3点:给定的信号无效、发送权限不够、目标进程不存在

代码示例:

int main()
{
	pid_t pid;
	pid=fork();
	switch(pid)
	{
	case -1:
		perror("fork failed\n");

	case 0://子进程
		sleep(5);
		kill(getppid(),SIGALRM);
		exit(0);
	default:;
	}

	signal(SIGALRM,func);
	while(!n)
	{
		printf("hello world\n");
		sleep(1);
	}
	if(n)
	{
		printf("hava a signal %d\n",SIGALRM);
	}
	exit(0);
}

运行结果:

可以看到在进程中fork复制一个新进程,在子进程5秒后发送一个SIGALRM信号,父进程捕获这个信号后用func函数改变n的值,然后退出循环,所以结果输出5个hello world后,程序收到SIGARLM信号,结束进程。

3)alarm函数:提供一个闹钟的功能,进程可以调用alarm函数在经过预定时间时发送一个SIGALRM信号。

函数原型:unsigned int alarm(unsigned int seconds);

注:如果父进程在子进程的信号到来之前什么也不做,可以调用pause()函数俩挂起父进程,直到接收到信号。

现在我们看看加上aralm函数和pause函数后的结果

int main()
{
	pid_t pid;
	signal(SIGALRM,func);
	alarm(5);//调用函数,5秒后发送SIGALRM信号
	pause();//挂起进程直到接收到信号
	if(n)
	{
		printf("hava a signal %d\n",SIGALRM);
	}
	exit(0);
}

运行结果:5秒后收到SIGALRM信号,程序恢复运行,输出信息并退出。

5、信号处理函数的安全问题

信号处理函数时可以在其执行期间被中断并被再次调用,当返回到第一次调用时,它能继续正确执行是关键,所以信号处理的函数必须是可重入的(不论什么时候再去执行都会是相同的结果)下面是可重入函数表:

猜你喜欢

转载自blog.csdn.net/ShWe_yayaya/article/details/81737848