Linux 进程通信 -下 (信号)

在这里插入图片描述

1、信号的概述

信号的概念 信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
信号的特点 简单 不能携带大量信息 满足某个特设条件才发送。
一个完整的信号周期包括四个部分:信号的产生,信号在进程中的注册,信号在进程中的注销,执行信号处理函数

在这里插入图片描述

查看信号:kill -l

在这里插入图片描述
其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关。名字上区别不大。而前32个名字各不相同.

每个信号必备4要素,分别是:

1)编号 2)名称 3)事件 4)默认处理动作 可通过man 7 signal查看帮助文档获取:
当一个信号发生的时候:忽略、执行默认动作、执行自定义的动作

未决信号集合 信号阻塞集

信号的实现手段导致信号有很强的延时性,但对于用户来说,时间非常短,不易察觉。 Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

  1. 阻塞信号集(信号屏蔽字) 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(处理发生在解除屏蔽后)。
  2. 未决信号集 信号产生,未决信号集中描述该信号的位立刻翻转为1,表示信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。 信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态

2、信号的API

2.1、 kill函数

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:给指定进程发送指定信号(不一定杀死)
参数:
    pid : 取值有 4 种情况 :
        pid > 0:  将信号传送给进程 ID 为pid的进程。
        pid = 0 :  将信号传送给当前进程所在进程组中的所有进程。
        pid = -1 : 将信号传送给系统内所有的进程。
        pid < -1 : 将信号传给指定进程组的所有进程。
        这个进程组号等于 pid 的绝对值。
    sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义,
    可以通过命令 kill - l("l" 为字母)进行相应查看。不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
返回值:
    成功:0
    失败:-1

案例:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<signal.h>

int main()
{
	pid_t pid = fork();
	if(pid == 0)//子进程
	{
		while(1)
		{
			static i=0;
			printf("子进程%d中的i=%d\n", getpid(), i++);
			sleep(1);
		}
	}
	else if(pid > 0)//父进程
	{
		printf("父进程 将在5秒后杀死子进程\n");
		sleep(5);
		kill(pid, SIGINT);
	}
	return 0;
}

运行结果:
在这里插入图片描述

2.2、raise函数 (自杀)

#include <signal.h>
int raise(int sig);
功能:给当前进程发送指定信号(自己给自己发),等价于 kill(getpid(), sig)
参数:
    sig:信号编号
返回值:
    成功:0
    失败:非0值

在这里插入图片描述

2.3、abort函数

#include <stdlib.h>
void abort(void);
功能:给自己发送异常终止信号 6) SIGABRT,并产生core文件,等价于kill(getpid(), SIGABRT);
参数:无
返回值:无

在这里插入图片描述

2.4、alarm函数(闹钟)

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:
    设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。
    进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
    取消定时器alarm(0),返回旧闹钟余下秒数。
参数:
    seconds:指定的时间,以秒为单位
返回值:
    返回0或剩余的秒数
定时,与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵尸……
无论进程处于何种状态,alarm都计时

案例:

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

int main()
{
	printf("进程%d将在5秒后结束\n",getpid());
	
	//s本质上是闹钟上一次剩余的秒数 但是第一次调用返回的是0
	//第二次调用 返回的是 上一次的剩余描述
	int s = alarm(5);//不阻塞
	printf("s = %d\n",s);
	printf("sleep 3秒\n");
	sleep(3);
	
	//重新设置闹钟
	s = alarm(5);
	printf("s = %d\n",s);//2秒
	
	while(1)
	{
		static int i=0;
		printf("i=%d\n",i++);
		sleep(1);
	}
	return 0;
}

运行结果:
在这里插入图片描述

2.5、setitimer函数(定时器)

#include <sys/time.h>
int setitimer(int which,  const struct itimerval *new_value, struct itimerval *old_value);
功能:
    设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。
参数:
    which:指定定时方式
        a) 自然定时:ITIMER_REAL → 14)SIGALRM计算自然时间
        b) 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM  只计算进程占用cpu的时间
        c) 运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF计算占用cpu及执行系统调用的时间
    new_value:struct itimerval, 负责设定timeout时间
        struct itimerval {
            struct timerval it_interval; // 闹钟触发周期  第一次以后 周期时间
            struct timerval it_value;    // 闹钟触发时间 第一次触发的时候
        };
        struct timeval {
            long tv_sec;            // 秒
            long tv_usec;           // 微秒
        }
        itimerval.it_value: 设定第一次执行function所延迟的秒数
        itimerval.it_interval:  设定以后每几秒执行function
    old_value: 存放旧的timeout值,一般指定为NULL
返回值:
    成功:0
    失败:-1
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
void my_func(int sig)
{
	printf("hello\n");
}
int main()
{
	struct itimerval new_value;
	//第一次执行执行的时间
	new_value.it_value.tv_sec = 4;//4秒
	new_value.it_value.tv_usec=0;//微妙
	
	//第一次以后的周期时间
	new_value.it_interval.tv_sec = 1;//4秒
	new_value.it_interval.tv_usec=0;//微妙
	
	//更改SIGALRM的处理方式
	signal(SIGALRM, my_func);
	
	setitimer(ITIMER_REAL, &new_value , NULL);
	
	while(1);
	return 0;
}

运行结果:
在这里插入图片描述

3、修改信号的处理动作(了解)

信号处理方式

一个进程收到一个信号的时候,可以用如下方法进行处理:
1)执行系统默认动作 对大多数信号来说,系统默认动作是用来终止该进程。
2)忽略此信号(丢弃) 接收到此信号后没有任何动作。
3)执行自定义信号处理函数(捕获) 用用户定义的信号处理函数处理该信号。

【注意】:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法
内核实现信号捕捉过程
在这里插入图片描述

捕捉信号并且信号信号的处理方式有两个函数,signal 和sigaction

1、signal函数

#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
    注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。
参数:
    signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母)进行相应查看。
    handler : 取值有 3 种情况:
          SIG_IGN:忽略该信号
          SIG_DFL:执行系统默认动作
          信号处理函数名:自定义信号处理函数,如:func
          回调函数的定义如下:
            void func(int signo)
            {
                // signo 为触发的信号,为 signal() 第一个参数的值
            }
返回值:
    成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型。
    失败:返回 SIG_ERR

在这里插入图片描述
结束方法:
在这里插入图片描述

2、sigaction函数

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:
    检查或修改指定信号的设置(或同时执行这两种操作)。
参数:
    signum:要操作的信号。
    act:   要设置的对信号的新处理方式(传入参数)。
    oldact:原来对信号的处理方式(传出参数)。
    如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式存入 oldact。
返回值:
    成功:0
    失败:-1
struct sigaction结构体:
struct sigaction {
    void(*sa_handler)(int); //旧的信号处理函数指针
    void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针
    sigset_t   sa_mask;      //信号阻塞集
    int        sa_flags;     //信号处理的方式
    void(*sa_restorer)(void); //已弃用
};
1) sahandler、sasigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给sasigaction、sahandler 两者之一赋值,其取值如下:
	a) SIGIGN:忽略该信号 
	b) SIGDFL:执行系统默认动作 
	c) 处理函数名:自定义信号处理函数
2) samask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。
3) saflags:用于指定信号处理的行为,通常设置为0,表使用默认属性。它可以是一下值的“按位或”组合: Ø SARESTART:使被信号打断的系统调用自动重新发起(已经废弃) Ø SANOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。 Ø SANOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。 Ø SANODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。 Ø SARESETHAND:信号处理之后重新设置为默认的处理方式。 Ø SASIGINFO:使用 sasigaction 成员而不是 sahandler 作为信号处理函数。
信号处理函数:
	void(*sa_sigaction)(int signum, siginfo_t *info, void *context);
参数说明:
    signum:信号的编号。
    info:记录信号发送进程信息的结构体。
    context:可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在传递信号时被中断的接收进程或线程的上下文

案例:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
void my_func(int sig)
{
	printf("ctrl+c被按下了\n");
}
int main()
{
	struct sigaction act;
	
	//act存放回调函数
	act.sa_handler = my_func;
	//act添加阻塞集 act.sa_mask 
	sigemptyset(&act.sa_mask);
	//SA_RESETHAND:信号处理之后重新设置为默认的处理方式
	act.sa_flags=0;//默认方式
	//act.sa_flags |= SA_RESETHAND;
	
	sigaction(SIGINT, &act, NULL);
	
	while(1);
	return 0;
}

运行结果:
在这里插入图片描述

4、信号集

在PCB中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未 决信号集”。 这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我 们直接对其进行位操作。而需自定义另外一个集合,借助信号集操作函数来对PCB 中的这两个信号集进行修改。
在这里插入图片描述
sigset_t set,set即一个信号集:

#include <signal.h>  
int sigemptyset(sigset_t *set);       //将set集合置空
int sigfillset(sigset_t *set);          //将所有信号加入set集合
int sigaddset(sigset_t *set, int signo);  //将signo信号加入到set集合
int sigdelset(sigset_t *set, int signo);   //从set集合中移除signo信号
int sigismember(const sigset_t *set, int signo); //判断信号是否存在
除sigismember外,其余操作函数中的set均为传出参数。sigset_t类型的本质是位图。
但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
int main()
{
	//定义一个信号集合
	sigset_t set;
	
	//清空集合
	sigemptyset(&set);
	
	//判断SIGINT信号时候在set集合中
	if(sigismember(&set, SIGINT))
	{
		printf("SIGINT在集合中\n");
	}
	else
	{
		printf("SIGINT不在集合中\n");
	}
	
	//将SIGINT信号 添加到集合set中
	sleep(3);
	sigaddset(&set, SIGINT);
	if(sigismember(&set, SIGINT))
	{
		printf("SIGINT在集合中\n");
	}
	else
	{
		printf("SIGINT不在集合中\n");
	}
	sleep(3);
	//将SIGINT信号 从集合set中删除
	sigdelset(&set, SIGINT);
	if(sigismember(&set, SIGINT))
	{
		printf("SIGINT在集合中\n");
	}
	else
	{
		printf("SIGINT不在集合中\n");
	}
	
	return 0;
}

运行结果:
在这里插入图片描述

5、信号阻塞集

所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:
    检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
参数:
    how : 信号阻塞集合的修改方法,有 3 种情况:
        SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。²相当于 mask = mask | set。
        SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于 mask = mask & ~ set。
        SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于mask = set。
    set : 要操作的信号集地址。
        若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。
    oldset : 保存原先信号阻塞集地址
返回值:
    成功:0,
    失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法

案例:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
int main()
{
	//定义一个集合
	sigset_t set;
	sigset_t old_set;
	
	//清空集合
	sigemptyset(&set);
	sigemptyset(&old_set);
	
	
	//将SIGINT添加到集合中
	sigaddset(&set, SIGINT);
	
	//将集合set添加到 阻塞集中
	sigprocmask(SIG_BLOCK, &set, &old_set);
	printf("SIGINT已经在阻塞集中\n");
	
	sleep(10);
	//将集合从阻塞集中删除
	printf("SIGINTc从阻塞集中删除了\n");
	sigprocmask(SIG_UNBLOCK, &set, NULL);
	//sigprocmask(SIG_SETMASK, &old_set, NULL);//恢复以前的集合
	
	while(1);
	
	return 0;
}

运行结果:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章