问题
基于信号发送的进程间通信方式可靠吗???
信号分为不可靠信号和可靠信号,使用可靠信号的方式来实现进程间通信是可靠的
信号查看(kill -l)
信号的分类
不可靠信号 (传统信号)
- 信号值在 [1, 31] 之间的所有信号
可靠信号 (实时信号)
- 信号值在 [SIGRTMIN,SIGRTMAX],即:[34,64]
- SIGRTMIN => 34
- SIGRTMAX => 64
信号小知识
信号 32 与信号 33 (SIGCANCEL & SIGSETXID) 被NPTL 线程库征用
NPTL => Native Posix Threading Library
- 即:POSIX 线程标准库,Linux 可以使用这个库进行多线程编程
对于 Linux 内核,信号 32 是最小的可靠信号
SIGRTMIN 在 signal.h 中定义,不同平台的 linux 可能不同 (arm linux)
不可靠信号 vs 可靠信号
不可靠信号
- 内核不保证信号可以递送到目标进程 (内核对信号状态进行标记)
- 如果信号处于未决状态,并且相同信号被发送,内核丢弃后续相同信号
可靠信号
- 内核维护信号队列,未决信号位于队列中,因此信号不会被丢弃
- 严格意义上,信号队列有上限,因此不可以无限制保存可靠信号
一些注意事项。。。
不可靠信号的默认处理行为可能不同 (忽略,结束)
可靠信号的默认处理行为都是结束进程
信号的可靠性由信号数值决定,与发送方式无关
信号队列的上限可通过命令设置
查询信号队列上限:ulimit -i
设置信号队列上限:ulimit -i 1000
信号可靠性试验设计
目标:验证信号可靠性 (不可靠信号 or 可靠信号)
方案:对目标进程 "疯狂" 发送 N 次信号,验证信号处理函数调用次数
预备函数:
- int sigaddset(sigset* set, int signum);
- int sigdelset(sigset_t* set, int signum);
- int sigfillset(sigset_t* set);
- int sigfillset(sigset* set);
- int sigemptyset(sigset* set);
- int sigprocmask(int how, const sigset* set, sigset* oldset);
进程可靠性实验
main1.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
static int g_count = 0;
static int g_obj_sig = 0;
void signal_handler(int sig, siginfo_t* info, void* ucontext)
{
if( sig == g_obj_sig )
{
g_count++;
}
}
int main(int argc, char* argv[])
{
struct sigaction act = {0};
sigset_t set = {0};
int i = 0;
g_obj_sig = atoi(argv[1]);
printf("current pid(%d) ...\n", getpid());
act.sa_sigaction = signal_handler;
act.sa_flags = SA_RESTART | SA_SIGINFO;
sigaddset(&act.sa_mask, g_obj_sig);
sigaction(g_obj_sig, &act, NULL);
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
for(i=0; i<15; i++)
{
sleep(1);
printf("i = %d\n", i);
}
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
printf("g_count = %d\n", g_count);
return 0;
}
g_obj_sig 是我们测试用的目标信号,第 35 行,我们为目标信号注册信号处理函数 signal_handler()
第 40 行,我们屏蔽所有的信号;随后 sleep 15s,在这 15s 内,我们要发送目标信号;第 49 行,又恢复所有被屏蔽的信号,如果有被屏蔽的目标信号未决,那么会调用到信号处理函数,将 g_count 变量加一;第 51 行,打印 g_count 的次数
test1.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char* argv[])
{
int pid = atoi(argv[1]);
int sig = atoi(argv[2]);
union sigval sv = {1234567};
int i = 0;
printf("current pid(%d) ...\n", getpid());
printf("send sig(%d) to process(%d)...\n", sig, pid);
for(i=0; i<5000; i++)
{
sigqueue(pid, sig, sv);
}
return 0;
}
用于发送 5000 次目标信号给指定的进程
我们首先测试不可靠信号,我们将目标信号值设定为 2,程序运行结果如下图所示:
g_count 的值为 1,目标信号的信号处理只处理了一次,说明不可靠信号在未决的时候发送了多次的话,就只会处理一次
之后我们测试可靠信号,我们将目标信号值设定为 40,程序运行结果如下图所示:
g_count 的值为 5000,目标信号的信号处理每次都去处理了
我们使用 ulimit -i 命令查看信号队列的上限
然后,我们将发送端发送目标信号的次数由 5000 改为 10000,查看程序运行结果,程序运行结果如下图所示:
我们发送目标信号发送了 10000 次,但目标信号处理函数只处理了 7606 次,这是因为,可靠信号是保存在信号队列中的,信号队列的长度是受限的,当前系统信号队列的最大个数是 7606,最多只能同时保存 7606 个可靠信号
在可靠信号需要发送的次数比较多的时候,我们需要及时去处理信号,否则可能会导致信号队列满,漏信号
基于信号的进程间通信实验
A 进程将 TLV 类型的数据通过可靠信号传递给 B 进程
- TLV => (type, length, value)
- 由于可靠信号的限制,每次传输4字节数据
B 进程首先接收 4 字节数据 (type 或 type + length)
- 根据接收到 length 信息,多次接收后续的字节数据
- 每次只能接收 4 字节数据,设计层面需要进行状态处理
状态设计
数据发送进程关键实现
数据接收进程关键实现
进程间通信实验
test2.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
typedef struct _tlv_t_
{
short type;
short length;
char data[];
} Message;
int main(int argc, char* argv[])
{
int pid = atoi(argv[1]);
int sig = atoi(argv[2]);
char* data = argv[3];
int len = sizeof(Message) + strlen(data) + 1;
int size = (len / 4 + !!(len % 4)) * 4;
Message* msg = (Message*)malloc(size);
if(msg)
{
int* pi = (int*)msg;
union sigval sv = {0};
int i = 0;
printf("current pid(%d) ...\n", getpid());
printf("send sig(%d) to process(%d)...\n", sig, pid);
msg->type = 0;
msg->length = size - sizeof(Message);
strcpy(msg->data, data);
for(i = 0; i < size; i += 4)
{
sv.sival_int = *pi++;
sigqueue(pid, sig, sv);
}
}
free(msg);
return 0;
}
使用信号来进程间通信,必须使用可靠信号,否则会丢失数据
由于发送数据的长度不定,所以这里采用了柔性数组,使用 malloc 的方式来分配空间
第 21 行,做了一个 4 字节对齐的操作,这样做可以统一数据的处理方式
main2.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
static int g_obj_sig = 0;
static int g_current = -1;
static int g_type = -1;
static int g_length = 0;
static char* g_data = NULL;
static void ipc_data_handler(char* data, int length)
{
printf("ipc data: %s\n", data);
}
static void signal_handler(int sig, siginfo_t* info, void* ucontext)
{
if( sig == g_obj_sig )
{
if(g_current == -1)
{
g_type = info->si_value.sival_int & 0xFF;
g_length = (info->si_value.sival_int >> 16) & 0xFF;
g_data = (char*)malloc(sizeof(char) * g_length);
if(!g_data)
{
exit(-1);
}
else
{
g_current = 0;
}
}
else
{
int i = 0;
while((i < 4) && (g_current < g_length))
{
g_data[g_current++] = (info->si_value.sival_int >> (i * 8)) & 0xFF;
i++;
}
}
if(g_current == g_length)
{
ipc_data_handler(g_data, g_length);
g_current = -1;
g_length = 0;
free(g_data);
g_data = NULL;
}
}
}
int main(int argc, char* argv[])
{
struct sigaction act = {0};
sigset_t set = {0};
int i = 0;
g_obj_sig = atoi(argv[1]);
printf("current pid(%d) ...\n", getpid());
act.sa_sigaction = signal_handler;
act.sa_flags = SA_RESTART | SA_SIGINFO;
sigaddset(&act.sa_mask, g_obj_sig);
sigaction(g_obj_sig, &act, NULL);
while(1)
{
sleep(1);
}
return 0;
}
首先解析出 type 和 length,然后根据 length 来读取完整的数据
程序运行结果如下图所示:
我们使用发送可靠信号的方式,接收方成功的接收到了发送方的数据