c语言系统编程五:linux进程间通信之信号

linux进程间通信之信号

  • 一 信号的概述
    • 1.1 信号的概念
    • 1.2 信号的特点
    • 1.3 信号周期的三部分
    • 1.4 信号的编号
    • 1.5 信号的四要素
  • 二 信号的产生与动作
  • 三 未决信号集合,信号阻塞集
  • 四 发送信号API
    • 4.1 kill函数
    • 4.2 raise函数
    • 4.3 abort函数
    • 4.4 alarm函数(闹钟)
    • 4.5 setitimer函数(定时器)
  • 五 修改信号的处理动作
    • 5.1 signal函数
    • 5.2 sigaction函数
  • 六 信号集
    • 6.1 信号集的概述
    • 6.2 自定义信号集,信号集的增删改查
    • 6.3 屏蔽信号集操作函数sigprocmask
    • 6.4 未决信号集查看函数sigpending

一 信号的概述

1.1 信号的概念

信号是Linux进程间通信的最古老的方式。信号是软件中断,它是在软件层面上对中断机制的一种模拟,是一种异步通信方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

1.2 信号的特点

简单,不能携带大量信息,满足某个特设条件才发送。

1.3 信号周期的三部分

  1. 信号的产生;
  2. 信号在进程中注册;
  3. 信号在进程中注销;
  4. 执行信号处理函数

c语言系统编程五:linux进程间通信之信号_第1张图片

1.4 信号的编号

c语言系统编程五:linux进程间通信之信号_第2张图片

1.5 信号的四要素

  1. 编号
  2. 名称
  3. 事件
  4. 默认处理动作

可以通过man 7 signal查看

二 信号的产生与动作

  1. 当用户按某些终端键时,将产生信号,如Ctrl+c产生SIGINT信号
  2. 硬件异常将产生信号。除数为0,无效的内存访问等。
  3. 软件异常将产生信号,当检测到某种软件条件已发生,将其通知有关进程时,产生信号。
  4. 调用系统函数(kill,raise,abort)将发送信号。注意接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户
  5. 运行kill/killall命令将发送信号,此程序实际上是使用kill’函数来发送信号

三 未决信号集合,信号阻塞集

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

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

四 发送信号API

4.1 kill函数

  1. 需要的头文件
#include 
#include 
  1. 函数原型
int kill(pid_t pid, int sig);
  1. 函数功能
给指定进程发送指定信号(不一定杀死)

  1. 参数
pid: 取值有四种情况:
	pid>0: 将信号传送给进程id为pid的进程。
	pid=0: 将信号传送给当前进程所在进程组中所有进程。
	pid=-1: 将信号传送给系统内所有的进程。
	pid<-1: 将信号传送给指定进程组的所有进程。这个进程组号等于pid的绝对值。

sig: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令kil -l 查看。不推荐直接用数字,应使用宏名,因为不同操作系统信号编码可能不同,但名称一致。
  1. 返回值
成功:0
失败:-1

4.2 raise函数

  1. 需要的头文件和函数原型
#include
int raise(int sig);

2. 功能
给当前进程发送信号
3. 参数

```powershell
sig:信号编号
  1. 返回值
成功:0
失败:非0

4.3 abort函数

  1. 需要的头文件和函数原型
#include
void abort(void);
  1. 功能
    给自己发送异常终止信号6) SIGABRT, 并产生core文件,等价于kill(getpid(),SIGABRT)

4.4 alarm函数(闹钟)

  1. 需要的头文件和函数原型
#include 
unsigned int alarm(unsigned int seconds);
  1. 功能
    设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14) SIGALRM信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
    取消定时器alarm(0), 返回旧闹钟余下秒数。

  2. 参数
    sconds:指定的时间,以秒为单位

  3. 返回值
    返回0或剩余秒数

  4. 注意
    定时与进程状态无关,就绪、运行、挂起、终止、僵尸等进程处于何种状态,alarm都计时

4.5 setitimer函数(定时器)

  1. 需要头文件和函数原型
#include 
int setitimer(int which, const struct itimerval *new_value, struct itimerval *ole_value);
  1. 功能
    设置定时器(闹钟)。可代替alarm函数。精度微妙us,可以实现周期定时。

  2. 参数

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

  1. 返回值
成功:0
失败:-1

c语言系统编程五:linux进程间通信之信号_第3张图片

  1. 实例
[root@ansible9 ~]# cat 1.c
#include 
#include 
#include 
#include 
#include 
#include


void my_fun(int sig)
{
	printf("触发的信号sig=%d\n", sig);
}

void main(int arg,char *args[])
{
	struct itimerval tv;
	//设置第一次运行的延迟时间
	tv.it_value.tv_sec = 5;
	tv.it_value.tv_usec = 0;
	//设置周期执行时间
	tv.it_interval.tv_sec = 2;
	tv.it_interval.tv_usec = 0;
	//注册信号的自定义函数
	signal(SIGALRM, my_fun);

	setitimer(ITIMER_REAL, &tv, NULL);
	getchar();
	return 0;
}




[root@ansible9 ~]# ./a.out 
触发的信号sig=14
触发的信号sig=14
触发的信号sig=14

五 修改信号的处理动作

信号处理方式:

一个进程收到一个信号的时候,可以用如下方法来处理:
1)执行系统默认动作,对大多数信号来说默认动作是终止该进程。
2)忽略此信号。
3)执行自定义信号处理函数(捕获)
注意:SIGKILL和SIGSTOP两个信号不能更改信号的处理方式,因为他们向用户提供了一种让进程终止的可靠方法

内核实现信号捕获过程:
c语言系统编程五:linux进程间通信之信号_第4张图片

捕获并处理信号有两个函数:signal和sigaction

5.1 signal函数

  1. 需要的头文件和函数原型
#include 
typedef void(*sighandler_t)(int);  //给指向函数void (int)的指针起个别名sighandler_t
sighandler_t signal(int signum, sighandler_t handler);
  1. 功能
    注册信号处理函数(不可用于SIGKILL和SIGSTOP信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞

  2. 参数

signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令kill -l进行查看。
handler:取值有三种情况:
	SIG_IGN: 忽略该信号
	SIG_DFL:执行系统默认动作
	信号处理函数名:自定义信号处理函数,如:my_func

  1. 回调函数的定义如下
void func(int signo)
{
	//signo为触发的信号,为signal()函数的第一个参数
}
  1. 返回值
成功:第一次返回NULL, 下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型。
失败:返回SIG_ERR
  1. 注意
该函数由ANSI定义,由于历史原因不同版本的Unix和Linux中可能不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。
  1. 代码
[root@ansible9 ~]# cat 1.c
#include 
#include 
#include 
#include 
#include 
#include


void my_fun(int sig)
{
	printf("触发的信号sig=%d\n", sig);
}

void main(int arg,char *args[])
{
	struct itimerval tv;
	//设置第一次运行的延迟时间
	tv.it_value.tv_sec = 5;
	tv.it_value.tv_usec = 0;
	//设置周期执行时间
	tv.it_interval.tv_sec = 2;
	tv.it_interval.tv_usec = 0;
	//注册信号的自定义函数
	signal(SIGALRM, my_fun);

	setitimer(ITIMER_REAL, &tv, NULL);
	getchar();
	return 0;
}




[root@ansible9 ~]# ./a.out 
触发的信号sig=14
触发的信号sig=14
触发的信号sig=14

5.2 sigaction函数

  1. 需要的头文件和函数原型
#include 
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  1. 功能
    检查或修改指定信号的设置(或同时执行这两种操作)

  2. 参数

signum: 要操作的信号
act:要设置的对信号的新处理方式(传入参数)
oldact:原来对信号的处理方式(传出参数)

如果act指针非空,则要改变指定信号的处理方式(设置);
如果oldact指针非空,则系统将此前指定信号的处理方式存入oldact。
  1. 返回值
成功:0
失败:-1
  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. sa_handler和sa_sigaction:信号处理函数指针,和signal()里的函数指针用法一样,	应该根据情况给sa_handler和sa_sigaction二者之一赋值,其取值如下:
	1.1 SIGIGN:忽略该信号
	1.2 SIGDFL:执行系统默认动作
	1.3 处理函数名:自定义信号处理函数

2. sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号
3. sa_flags:用于指定信号处理的行为,通常设置为0,表示使用默认属性。他可以是以下值的“按位或”组合:
	SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃) 
	SA_NOCLDSTOP:使父进程在他的子进程暂停或继续运行时不会收到SIGCHLD信号
	SA_NOCLDWAIT: 使父进程在他的子进程退出时不会收到SIGCHLD信号,这时子进程如果退出也不会成为僵尸进程。
	SA_NODEFER: 使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号
	SA_RESETHAND:信号处理之后重新设置为默认的处理方式
	SA_SIGINFO:使用sasigaction成员而不是sahandler作为信号处理函数
  1. 信号处理函数
void(*sa_sigaction)(int signum, siginfo_t *info, void *context);
参数说明:
	signum:信号的编号
	info:记录信号发送进程信息的结构体
	context:可以赋给指向ucontext_t类型的一个对象的指针,以引用在传递信号时被中断的接收进程或线程的上下文
  1. 实例
#include
#include 

void my_func(int sig)
{
	printf("ctrl+c被按下了\n");
}

int main(int argc, char * kwargs[])
{
	struct sigaction act;

	//指定用哪个自定义函数来处理信号
	act.sa_handler = my_func;
	//act添加阻塞集 act.sa_mask
	sigemptyset(&act.sa_mask);
	act.sa_flags=0; //默认方式
	//act.sa_flags |= SA_RESETHAND; //信号处理之后重新设置为默认的处理动作
	
	sigaction(SIGINT, &act, NULL);
	while(1)
		;
	return 0;
}

c语言系统编程五:linux进程间通信之信号_第5张图片

六 信号集

6.1 信号集的概述

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

c语言系统编程五:linux进程间通信之信号_第6张图片

6.2 自定义信号集,信号集的增删改查

  1. 定义信号集
#include 
sigset_t set       //set是一个信号集
  1. 将信号集置空
int sigemptyset(sigset_t *set);
  1. 将所有信号加入set信号集
int sigfillset(sigset_t *set);
  1. 将signo信号加入set信号集
int sigaddset(sigset_t *set, int signo);
  1. 从set信号集中移除signo信号
int sigdelset(sigset_t *set, int signo);
  1. 判断信号是否存在
int sigismember(const sigset_t *set, int signo);
  1. 实例
#include 
#include 

int main(int arg, char *argv[])
{
	//定义一个信号集合
	sigset_t set;

	//清空信号集合
	sigemptyset(&set);

	//将SIGINT 加入set集合
	sigaddset(&set, SIGINT);

	//判断SIGINT是否在set集合中
	if (sigismember(&set, SIGINT))
	{
		printf("SIGINT在set集合中\n");
	}

	return 0;
}

6.3 屏蔽信号集操作函数sigprocmask

  1. 概述
a. 信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。
b. 信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住他,直到进程准备好时再将信号通知进程)。
c. 所谓阻塞并不是禁止传递信号,而是暂缓信号的传递。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。
d. 我们可以通过sigprocmask()函数修改当前的信号掩码来改变信号的阻塞情况。
  1. 需要的头文件和函数原型
#include 
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  1. 功能
    检查或修改信号阻塞集,根据how指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由set指定,而原先的信号阻塞集由oldset保存

  2. 参数

how: 信号阻塞集合的修改方式,有三种情况:
	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:保存原先信号阻塞集地址
  1. 返回值
成功:0
失败:-1, 失败时错误代码只可能是EINVAL,表示参数how不合法
  1. 实例
#include 
#include 
#include 

int main(int arg, char *argv[])
{
	//定义一个信号集合
	sigset_t set;

	//清空信号集合
	sigemptyset(&set);

	//将SIGINT 加入set集合
	sigaddset(&set, SIGINT);

	//set集合添加到阻塞集中
	sigprocmask(SIG_BLOCK, &set, NULL);

	printf("5秒后SIGINT将从阻塞集中删除\n");
	sleep(5);

	//set集合从阻塞集中删除
	sigprocmask(SIG_UNBLOCK, &set, NULL);

	getchar();

	return 0;
}

6.4 未决信号集查看函数sigpending

  1. 需要的头文件和函数原型
#include 
int sigpending(sigset_t *set);
  1. 功能
读取当前进程的未决信号集
  1. 参数
未决信号集
  1. 返回值
成功:0
失败:-1
  1. 实例
#include 
#include 
#include 

int main(int arg, char *argv[])
{
	//定义一个信号集合
	sigset_t set;

	//清空信号集合
	sigemptyset(&set);

	//将SIGINT 加入set集合
	sigaddset(&set, SIGINT);

	//set集合添加到阻塞集中
	sigprocmask(SIG_BLOCK, &set, NULL);

	printf("5秒后判断SIGINT是否在未决信号集中\n");
	sleep(5);

	//定义一个信号集合
	sigset_t set2;
	//清空信号集
	sigemptyset(&set2);
	//将set2集合添加到未决信号集
	sigpending(&set2);
	if(sigismember(&set2, SIGINT))
	{
		printf("SIGINT在未决信号集合中\n");
	}

	//set集合从阻塞集中删除
	sigprocmask(SIG_UNBLOCK, &set, NULL);
	if(sigismember(&set2, SIGINT))
	{
		printf("SIGINT在未决信号集合中\n");
	}

	getchar();

	return 0;
}

你可能感兴趣的:(c语言,C语言系统编程,linux,c语言,运维)