Unix/Linux编程:实时信号

实时信号

定义于POSIX.b中的实时信号,意在弥补对标准信号的诸多限制。较之于标准信号,其优势如下所示:

  • 实时信号的信号范围有所扩大,可应用于应用程序自定义的目的。而标准信号可供应用随意使用的信号仅有两个:SIGUSR1 和 SIGUSR2
  • 对实时信号所采取的是队列化管理。如果将某一实时信号的多个实例发给一进程,那么将多次传递信号。相反,如果某一标准信号已经在等待某一进程,而此时即使再次向该进程发送此信号的实例,信号也只会传递一次。
  • 当发送一个实时信号时,可为信号指定伴随数据(一整形数或者指针值),供接收进程的信号处理器获取。
  • 不同实时信号的传递顺序得到保障、如果多个不同的实时信号处于等待状态,那么将率先传递具有最小编号的信号。也就是说,信号的编号越小,优先级越高。如果是同一类型的多个信号在排队,那么信号(以及伴随数据)的传递顺序与信号发送来时的顺序保持一致。

SUSv3要求,实现所提供的各种实时信号不得少于_POSIX_RTSIG_MAX(定义为 8)个。

Linux内核定义了32个不同的实时信号,编号范围为32-63。

< signal.h > 头文件所定义的RTSIG_MAX常量则表征实时信号的可用数量,而此外所定义的常量SIGRTMIN 和SIGRTMAX 则分别表示可用实时信号编号的最小值和最大值

采用 LinuxThreads 线程实现的系统将 SIGRTMIN 定义为 35(而非 32),这是因为LinuxThreads 内部使用了前三个实时信号。而采用 NPTL 线程实现的系统则将 SIGRTMIN 定义为 34,因为 NPTL 内部使用了前两个实时信号

对实时信号的区分方式有别于标准信号,不再依赖于所定义常量的不同。然而,程序员不应将实时信号编号的整型值在应用程序代码中写死,因为实时信号的范围因 UNIX 实现的不同而各异。与之相反,指代实时信号编号则可以采用 SIGRTMIN+x 的形式。例如,表达式(SIGRTMIN + 1)就表示第二个实时信号

注意,SUSv3 并未要求 SIGRTMAX 和 SIGRTMIN 是简单的整数值。可以将其定义为函数(就像 Linux 中那样)。这也意味着,不能编写如下代码以供预处理器处理:

#if SIGRTMIN + 100 > SIGRTMAX 
	#error "not enough realtime signals"
#endif

而必须在运行时执行等效检查。

对排队实时信号的数量限制

排队的实时信号(及其相关数据)需要内核维护相应的数据结构,用于罗列每个进程的排队信号。由于这些数据结构会消耗内核内存,因此需要内核对排队实时信号的数量设置了限制

SUSv3 允许实现为每个进程中可排队的(各类)实时信号数量设置上限,并要求其不得少于_POSIX_SIGQUEUE_MAX(定义为 32)。实现可借助于对SIGQUEUE_MAX 常量的定义来表示其所允许的排队实时信号数量。发起如下调用也能获得这一信息

sysconf(_SC_SIGQUEUE_MAX)

从版本 2.6.8 开始,Linux 定义了资源限制 RLIMIT_ SIGPENDING。针对某个特定实际用户 ID 下辖的所有进程,该限制限定了其可排队的信号总数。sysconf()调用从 glibc2.10 版本开始返回 RLIMIT_SIGPENDING 限制。(至于正在等待某一进程的实时信号数量,可以从 Linux 专有文件/proc/PID/status 中的 SigQ 字段读取。)

使用实时信号

为了能让一对进程收发实时信号,SUSv3提出以下几点要求:

  • 发送进程使用sigqueue()系统调用来发送信号以及其伴随数据。

使用 kill()、killpg()和 raise()调用也能发送实时信号。然而,至于系统是否会对利用此类接口所发送的信号进行排队处理,SUSv3 规定,由具体实现决定。这些接口在 Linux 中会对实时信号进行排队,但在其他许多 UNIX 实现中,情况则不然

  • 要为该信号建立了一个处理器函数,接收进程应以 SA_SIGINFO 标志发起对 sigaction()的调用。因此,调用信号处理器时就会附带额外参数,其中之一是实时信号的伴随数据

在Linux中,即使接收进程在建立信号处理器是并未指定SA_SIGINFO 标志,也能对实时信号进程排队化管理(但在这种情况下,将不可能获得信号的伴随数据)。然而,SUSv3也不要求实现确保这一行为,所以依赖这一点将有损于应用的可移植性

发送实时信号

NAME
       sigqueue - queue a signal and data to a process

SYNOPSIS
       #include <signal.h>

       int sigqueue(pid_t pid, int sig, const union sigval value);


系统调用 sigqueue()将由 sig 指定的实时信号发送给由 pid 指定的进程。

  • 使用 sigqueue()发送信号所需要的权限与 kill()的要求一致。
  • 也可以发送空信号(即信号 0),其语义与 kill()中的含义相同。
  • 不同于 kill(),sigqueue()不能通过将 pid指定为负值而向整个进程组发送信号。

参数value指定了信号的伴随数据:

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

对该参数的解释则取决于应用程序,由其选择对union中的sigval_int属性还是sigval_ptr进行设置。sigqueue()中很少使用sigval_ptr,因为指针的作用范围在进程内部,对于另一进程几乎没有意义。该字段得以一展身手之处,应该是在使用sigval联合体的其他函数中,比如POSIX 计时器和 POSIX 消息队列通知。

包括 Linux 在内的几个 UNIX 实现定义了与 union sigval 同义的数据类型 sigval_t。然而,该类型既未获得 SUSv3 接纳,也没有得到其他实现的支持。对可移植性有所要求的应用程序应当避免使用

一旦触及对排队信号的数量限制,sigqueue()调用将会失败,同时将errno置EAGAIN,以示需要再次发送该信号(在当前队列中某些信号传递之后的某一时间点)

处理实时信号

可以像标准信号一样,使用常规(单参数)信号处理器来处理实时信号。此外,也可以用带有 3 个参数的信号处理器函数来处理实时信号,其建立则会用到 SA_SIGINFO 标志。以下为使用SA_SIGINFO标志位第6个实时信号建立处理器函数的代码实例:

struct sigaction act;

sigemptyset(&act.sa_mask);
act.sa_sigaction = handler;
act.sa_flags = SA_RESTART | SA_SIGINFO ;

if(sigaction(SIGRTMIN + 5, &act, NULL) == -1){
	exit(1);
}

一旦采用了SA_SIGINFO标志,传递信号处理器函数的第二个参数将是一个siginfo_t结构,内含实时信号的附加信息。对于一个实时信号而言,会在 siginfo_t 结构中设置如下字段

  • si_signo 字段,其值与传递给信号处理器函数的第一个参数相同。
  • si_code 字段表示信号来源。对于通过 sigqueue()发送的实时信号来说,该字段值总是为 SI_QUEUE
  • si_value 字段所含数据,由进程于使用 sigqueue()发送信号时在 value 参数(sigval union)中指定。正如前文指出,对该数据的解释由应用程序决定。(若信号由 kill()发送,则si_value 字段所含信息无效。
  • si_pid 和 si_uid 字段分别包含信号发送进程的进程 ID 和实际用户 ID。

实例

实例一:发送与接收信号

首先是接收信号:

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

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int, siginfo_t *, void *);


int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_sigaction = handler; //sa_sigaction与sa_handler只能取其一
    //sa_sigaction多用于实时信号,可以保存信息
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO; // 设置标志位后可以接收其他进程
    // 发送的数据,保存在siginfo_t结构体中

    if (sigaction(SIGINT, &act, NULL) < 0)
        ERR_EXIT("sigaction error");

    for (; ;)
        pause();

    return 0;

}

void handler(int sig, siginfo_t *info, void *ctx)
{
    printf("recv a sig=%d data=%d data=%d\n",
           sig, info->si_value.sival_int, info->si_int);

}

然后是信号发送:

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

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)



int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage %s pid\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    pid_t pid = atoi(argv[1]); //字符串转换为整数
    union sigval val;
    val.sival_int = 100;
    sigqueue(pid, SIGINT, val); // 只可以发信号给某个进程,而不能是进程组

    return 0;

}

先运行recv程序:

$ ./sigqueue_recv

再ps出recv进程的pid,然后运行send程序:

$ ./sigqueue_send 3323

则recv进程会输出一条recv语句,当然我们也可以ctrl+c 给自己发送信号,如下所示,结果是一样的。

recv a sig=2 data=100 data=100
^Crecv a sig=2 data=100 data=100
^Crecv a sig=2 data=100 data=100

实例二:实时信号的排队处理

实时信号与不可靠信号的区别: 实时信号支持排队不会丢失。到达的顺序是可以保证的

首先是接收函数:在主函数中将SIGINT和SIGRTMIN信号加入信号屏蔽字,只有当接收到SIGUSR1信号时才对前面两个信号unblock。 注意,如果在信号处理函数中对某个信号进行解除阻塞时,则只是将pending位清0,让此信号递达一次(同个实时信号产生多次进行排队都会抵达),但不会将block位清0,即再次产生此信号时还是会被阻塞,处于未决状态

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int);

int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    sigset_t s;
    sigemptyset(&s);
    sigaddset(&s, SIGINT);
    sigaddset(&s, SIGRTMIN);
    sigprocmask(SIG_BLOCK, &s, NULL);
    if (sigaction(SIGINT, &act, NULL) < 0)
        ERR_EXIT("sigaction error");

    if (sigaction(SIGRTMIN, &act, NULL) < 0)
        ERR_EXIT("sigaction error");

    if (sigaction(SIGUSR1, &act, NULL) < 0)
        ERR_EXIT("sigaction error");
    for (;;)
        pause();
    return 0;
}

void handler(int sig)
{
    if (sig == SIGINT || sig == SIGRTMIN)
        printf("recv a sig=%d\n", sig);
    else if (sig == SIGUSR1)
    {
        sigset_t s;
        sigemptyset(&s);
        sigaddset(&s, SIGINT);
        sigaddset(&s, SIGRTMIN);
        sigprocmask(SIG_UNBLOCK, &s, NULL);
    }
}

然后是发送:

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

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)



int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage %s pid\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    pid_t pid = atoi(argv[1]); //字符串转换为整数
    union sigval val;
    val.sival_int = 100;
    sigqueue(pid, SIGINT, val); // 不可靠信号不会排队,即会丢失
    sigqueue(pid, SIGINT, val);
    sigqueue(pid, SIGINT, val);
    sigqueue(pid, SIGRTMIN, val); //实时信号会排队,即不会丢失
    sigqueue(pid, SIGRTMIN, val);
    sigqueue(pid, SIGRTMIN, val);
    sleep(3);
    kill(pid, SIGUSR1);

    return 0;

}

先是运行recv程序:

$ ./sigrtime_recv2

接着ps出recv进程的pid,运行send程序:

$ ./sigrtime_send 4076

在send程序中连续各发送了SIGINT和SIGRTMIN信号3次,接着睡眠3s后使用kill函数发送SIGUSR1信号给recv进程,此时recv进程会输出如下:

recv a sig=34
recv a sig=34
recv a sig=34
recv a sig=2

即实时信号支持排队,3个信号都接收到了,而不可靠信号不支持排队,只保留一个信号。

实例三

//(t_sigqueue.c)
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <zconf.h>
#define _POSIX_C_SOURCE 199309
#include <signal.h>
#include <string.h>
#include <signal.h>
#include <climits>

int main(int argc, char *argv[])
{
    int sig, numSigs, j, sigData;
    union sigval sv;

    if (argc < 4 || strcmp(argv[1], "--help") == 0){
        printf("%s pid sig-num data [num-sigs]\n", argv[0]);
        // 该程序最多接受 4 个参数,
        //     其中前 3 项为必填项:目标进程 ID、信号编号以及伴随实时信号的整型值
        //     如果需要为指定信号发送多个实例,那么可以用可选的第 4 个参数来指定实例数量。在这种情况下,会为每个信号的伴随整
        //     型值依次加 1
        exit(EXIT_FAILURE);
    }


    printf("%s: PID is %ld, UID is %ld\n", argv[0],
           (long) getpid(), (long) getuid());

    sig = atoi(argv[2]);
    sigData = atoi(argv[3]);
    numSigs = (argc > 4) ? atoi(argv[4]) : 1;

    for(j = 0; j < numSigs; j++){
        sv.sival_int = sigData + j;
        if (sigqueue(atoi(argv[1]), sig, sv) == -1){
            printf("sigqueue %d", j);
            exit(EXIT_FAILURE);
        }
    }

    exit(EXIT_SUCCESS);

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值