Skip to content

LV027-实时信号

一、实时信号

1. 实时信号的优势

如果进程当前正在执行信号处理函数,在处理信号期间接收到了新的信号,且该信号是信号掩码中的成员,那么内核会将其阻塞,将该信号添加到进程的等待信号集(等待被处理,处于等待状态的信号)中,为了确定进程中处于等待状态的是哪些信号,可以使用sigpending()函数获取。

等待信号集只是一个掩码,仅表明一个信号是否发生,而不能表示其发生的次数。意思就是,如果一个同一个信号在阻塞状态下产生了多次,那么会将该信号记录在等待信号集中,并在之后仅传递一次(仅当做发生了一次),这个情况上边其实我们有遇到过就是在sigsuspend中遇到过,只是发生在任务处理函数中,不过这与发生在信号处理函数中是一样的,在执行不可被打断的操作时期间来的信号都只会被记录一次,后期也只会被处理一次,就是这是标准信号,也就是非实时信号的缺点之一。

相对于标准信号,实时信号有如下优势:

(1)实时信号的信号可应用于用户自定义的信号数量较多,标准信号仅提供了两个信号SIGUSR1SIGUSR2用于应用程序自定义使用。

(2)内核对于实时信号所采取的是队列化管理。如果将某一实时信号多次发送给另一个进程,那么将会多次传递此信号。对于某一标准信号正在等待某一进程,而此时即使再次向该进程发送此信号,信号也只会传递一次。

(3)当发送一个实时信号时,我们可以为信号指定伴随数据(一个整形数据或者指针值),然后接收信号的进程就可以在它的信号处理函数中获取这些数据。

(4)不同实时信号的传递顺序得到保障。如果有多个不同的实时信号处于等待状态,那么将率先传递具有最小编号的信号。也就是说,信号的编号越小,其优先级越高,如果是同一类型的多个信号在排队,那么信号(以及伴随数据)的传递顺序与信号发送来时的顺序保持一致。

2. 使用实时信号

Linux内核定义了31个不同的实时信号,信号编号范围为34~64,使用SIGRTMIN表示编号最小的实时信号,使用SIGRTMAX表示编号最大的实时信号,其它信号编号可使用这两个宏加上一个整数或减去一个整数。

使用实时信号的过程中,需要注意的有以下两点:

(1)发送进程使用sigqueue()系统调用向另一个进程发送实时信号以及伴随数据。

(2)接收实时信号的进程要为该信号建立一个信号处理函数,为了更便于我们的操作,我们应该选择使用sigaction函数为信号建立处理函数,并加入SA_SIGINFO,这样信号处理函数才能够接收到实时信号以及伴随数据,也就是要使用sa_sigaction指针指向的处理函数,而不是sa_handler,当然也允许使用sa_handler,但这样就无法获取到实时信号的伴随数据了。

2.1 sigqueue() 函数

2.1.1 函数说明

linux下可以使用man 3 sigqueue 命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <signal.h>

/* 函数声明 */
int sigqueue(pid_t pid, int sig, const union sigval value);

函数说明】该函数是一个发送信号的系统调用,主要是针对实时信号(当然也支持标准信号),支持信号带有数据,与函数sigaction()配合使用。

函数参数

  • pidpid_t类型,指定接收信号的进程对应的pid,后续会将信号发送给该进程。
  • sigint类型,表示需要发送的信号。与kill()函数一样,也可将参数sig设置为0,用于检查参数pid所指定的进程是否存在。
  • valueunion sigval类型的共用体,它指定了信号的伴随数据。

在使用man 3 sigqueue 查看使用手册的时候,下边有union sigval 这个共用体的介绍。

c
union sigval
{
	int   sival_int;
	void *sival_ptr;
};

返回值int类型,成功返回0,失败返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <signal.h>

/* 至少应该有的语句 */
union sigval sig_val;
sigqueue(pid, sig, sig_val);

注意事项none

2.1.2 使用实例

后面看最后的例子即可。

二、使用实例

1. signalSend.c

c
/* 头文件 */
#include <stdio.h>  /* perror getchar */
#include <stdlib.h> /* exit */
#include <unistd.h> /* sleep */
#include <signal.h> /* sigqueue */

int main(int argc, char *argv[])
{
	union sigval sig_val;/* 信号携带的数据的共用体变量 */
	pid_t pid;
	int sig;
	char ch;
	int i = 0;
	printf("Please enter PID , signal:");
	scanf("%d %d", &pid, &sig);
	while((ch = getchar()) != EOF && ch != '\n') ; //清除缓冲区的内容
	for(i = 0; i < 3; i++)
	{
		sig_val.sival_int = i;
		if (sigqueue(pid, sig, sig_val) == -1)
		{
			perror("sigqueue error");
			exit(-1);
		}
		printf("Send success[data=%d]!\n", sig_val.sival_int);
		sleep(1);
	}
	return 0;
}

2. signalReceive.c

c
/* 头文件 */
#include <stdio.h>  /* perror */
#include <unistd.h> /* sleep getpid */
#include <signal.h> /* sigaction */

void sigactionHandle(int sig, siginfo_t *info, void *context);

/* 主函数 */
int main(int argc, char *argv[])
{
	struct sigaction act;   /* 定义一个处理信号行为的结构体变量 */

	/* 1.捕获信号 */
	/* 1.1初始化处理信号行为的结构体变量 */
	act.sa_sigaction = sigactionHandle;  /* 指定信号处理函数 */
	act.sa_flags = SA_SIGINFO;           /* a_flags指定了一组标志,这些标志用于控制信号的处理过程 */
	sigemptyset(&act.sa_mask);           /* 将信号集初始化为空 */
	/* 1.2设置捕捉信号 */
	sigaction(SIGRTMIN, &act, NULL);       /* Ctrl + c 发送该信号 */
	while(1)
	{
		printf("Please send signal.[PID=%d]\n", getpid());
		sleep(1);
	}
	return 0;
}

void sigactionHandle(int sig, siginfo_t *info, void *context)
{
	static int count = 0;
	int i = 0;
	union sigval sig_val = info->si_value;
	printf("I catch the signal [%d] %d times,data=%d \n", sig, ++count, sig_val.sival_int);
	for(i = 1; i < 7; i++)
	{
		printf("sigactionHandle running![%d]\n", i);
		sleep(1);
	}
	printf("sigactionHandle end!\n");
}

3. Makefile

makefile
CC = gcc

DEBUG = -g -O2 -Wall
CFLAGS += $(DEBUG)

# 所有.c文件去掉后缀
TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} 

all : $(TARGET_LIST)

%.o : %.c
	$(CC) $(CFLAGS) -c $< -o $@

.PHONY: all clean clean_o
clean : clean_o
	@rm -vf $(TARGET_LIST) 
	
clean_o :
	@rm -vf *.o

3. 测试效果

在终端执行以下命令编译程序:

shell
make # 生成可执行文件
./signalSend    # 在一个终端执行发送信号函数
./signalReceive # 在另一个终端执行信号接收函数

然后,终端会有以下信息显示:

shell
# 执行 ./signalSend 的终端
hk@vm:/mnt/hgfs/Sharedfiles/temp$ ./signalSend
Please enter PID , signal:30650 34
Send success[data=0]!
Send success[data=1]!
Send success[data=2]!

# 执行 ./signalReceive 的终端
hk@vm:~/2Sharedfiles/temp$ ./signalReceive
Please send signal.[PID=30650]
Please send signal.[PID=30650]
I catch the signal [34] 1 times,data=0 
sigactionHandle running![1]
sigactionHandle running![2]
sigactionHandle running![3]
sigactionHandle running![4]
sigactionHandle running![5]
sigactionHandle running![6]
sigactionHandle end!
I catch the signal [34] 2 times,data=1 
sigactionHandle running![1]
sigactionHandle running![2]
sigactionHandle running![3]
sigactionHandle running![4]
sigactionHandle running![5]
sigactionHandle running![6]
sigactionHandle end!
I catch the signal [34] 3 times,data=2 
sigactionHandle running![1]
sigactionHandle running![2]
sigactionHandle running![3]
sigactionHandle running![4]
sigactionHandle running![5]
sigactionHandle running![6]
sigactionHandle end!
Please send signal.[PID=30650]

会发现,发送的三次信号并未打断正在执行的信号处理函数,并且所有的信号都保留了下来。