信号(二)——进程间数据传递sigaction函数(sigaction结构体、siginfo_t 结构体)、sigqueue函数(sigval联合体)、信号的顺序响应问题——linux系统编程

文章目录

    • sigaction信号安装函数
      • sigaction 结构体
      • siginfo_t 结构体
    • sigqueue信号发送函数
      • sigval联合体
    • 信号的顺序响应问题
    • 代码示例

  • 上一篇讲到信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。
  • Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
  • kill+signal与sigqueue+sigaction的主要差别体现在后者可以带值发信号,这样就可以实现进程间是数据通信啦
    sigqueue+sigaction

sigaction信号安装函数

部分man:

NAME
       sigaction, rt_sigaction - examine and change a signal action

SYNOPSIS
       #include 

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sigaction(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

       siginfo_t: _POSIX_C_SOURCE >= 199309L

DESCRIPTION
       The sigaction() system call is used to change the action taken by a process on receipt of a specific signal.  (See signal(7) for an overview of signals.)

       signum specifies the signal and can be any valid signal except SIGKILL and SIGSTOP.

       If act is non-NULL, the new action for signal signum is installed from act.  If oldact is non-NULL, the previous action is saved in oldact.

       The sigaction structure is defined as something like:

           struct sigaction {
     
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

       On some architectures a union is involved: do not assign to both sa_handler and sa_sigaction.

       The  sa_restorer  field  is  not  intended for application use.  (POSIX does not specify a sa_restorer field.)  Some further details of purpose of this field can be
       found in sigreturn(2).

       sa_handler specifies the action to be associated with signum and may be SIG_DFL for the default action, SIG_IGN to ignore this signal, or a pointer to a signal han‐
       dling function.  This function receives the signal number as its only argument.

       If  SA_SIGINFO  is  specified  in sa_flags, then sa_sigaction (instead of sa_handler) specifies the signal-handling function for signum.  This function receives the
       signal number as its first argument, a pointer to a siginfo_t as its second argument and a pointer to a ucontext_t (cast to void *) as its  third  argument.   (Com‐
       monly, the handler function doesn't make any use of the third argument.  See getcontext(3) for further information about ucontext_t.)

       sa_mask  specifies  a mask of signals which should be blocked (i.e., added to the signal mask of the thread in which the signal handler is invoked) during execution
       of the signal handler.  In addition, the signal which triggered the handler will be blocked, unless the SA_NODEFER flag is used.
       sa_flags specifies a set of flags which modify the behavior of the signal.  It is formed by the bitwise OR of zero or more of the following:

           SA_NOCLDSTOP
                  If signum is SIGCHLD, do not receive notification when child processes stop (i.e., when they receive one of SIGSTOP, SIGTSTP,  SIGTTIN,  or  SIGTTOU)  or
                  resume (i.e., they receive SIGCONT) (see wait(2)).  This flag is meaningful only when establishing a handler for SIGCHLD.

           SA_NOCLDWAIT (since Linux 2.6)
                  If signum is SIGCHLD, do not transform children into zombies when they terminate.  See also waitpid(2).  This flag is meaningful only when establishing a
                  handler for SIGCHLD, or when setting that signal's disposition to SIG_DFL.

                  If the SA_NOCLDWAIT flag is set when establishing a handler for SIGCHLD, POSIX.1 leaves it unspecified whether a SIGCHLD signal is generated when a child
                  process terminates.  On Linux, a SIGCHLD signal is generated in this case; on some other implementations, it is not.

           SA_NODEFER
                  Do  not  prevent  the  signal  from  being received from within its own signal handler.  This flag is meaningful only when establishing a signal handler.
                  SA_NOMASK is an obsolete, nonstandard synonym for this flag.

           SA_ONSTACK
                  Call the signal handler on an alternate signal stack provided by sigaltstack(2).  If an alternate stack is not available, the default stack will be used.
                  This flag is meaningful only when establishing a signal handler.

           SA_RESETHAND
                  Restore  the signal action to the default upon entry to the signal handler.  This flag is meaningful only when establishing a signal handler.  SA_ONESHOT
                  is an obsolete, nonstandard synonym for this flag.

           SA_RESTART
                  Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals.   This  flag  is  meaningful  only  when
                  establishing a signal handler.  See signal(7) for a discussion of system call restarting.

           SA_RESTORER
                  Not intended for application use.  This flag is used by C libraries to indicate that the sa_restorer field contains the address of a "signal trampoline".
                  See sigreturn(2) for more details.

           SA_SIGINFO (since Linux 2.2)
                  The signal handler takes three arguments, not one.  In this case, sa_sigaction should be set instead of sa_handler.  This flag is  meaningful  only  when
                  establishing a signal handler.

简单总结:

  • 包含头文件
  • 功能:sigaction函数用于改变进程接收到特定信号后的行为。
  • 原型: int sigaction(int signum,const struct sigaction *act,const struct sigaction *old);

参数 :

  • 该函数的第一个参数为信号的值,可以为除sigkill及sigstop外的任何一 个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)

  • 第二个参数是指向结构sigaction的一个实例的指针,在结构 sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理

  • 第三个参数oldact指向的对象用来保存原来对相应信号的处理,可以传入结构sigaction的指针来获取之前对信号处理情况,如果不需要保存可指定oldact为null。

  • 如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

  • 第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。

  • 返回值:函数成功返回0,失败返回-1

sigaction 结构体

在sigaction函数的第二个第三个参数,为指向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_handler指定信号关联函数,赋值为一个函数指针(函数名)即用户指定的信号处理函数。除此之外,还可以赋值为常数SIG_IGN表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作(采用缺省的处理方式)。
赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

2. sa_sigaction指定信号关联函数

和sa_handler的差别在于:

由sa_handler指定的处理函数只有一个参数,即信号值,所以信号不能传递除信号值之外的任何信息;
(例如上一篇的void signal_fun(int num))

由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。第一个参数为信号值,第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,第三个参数没有使用(posix没有规范使用该参数的标准)
例如void signal_handle_new(int num,siginfo_t *info,void *d)

sa_handler主要用于不可靠信号(实时信号当然也可以,只是不能带信息),sa_sigaction用于实时信号可以带信息(siginfo_t),两者不能同时出现。

3. sa_mask存放需要手动屏蔽的信号

sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位,处理程序执行完后,被阻塞的信号开始执行。

注:请注意sa_mask指定的信号阻塞的前提条件,是在由sigaction()安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。
(屏蔽相关内容将在下一篇中带来)

4.sa_flags指定一组修改信号行为的标志
flag在man文档中有详细列出,常用的有:
SA_SIGINFO:
当sig_act.sa_flags = SA_SIGINFO;
时需要指定sa_sigaction。a_sigaction函数的第一个参数与sa_handler一样表示当前信号的编号,第二个参数是一个siginfo_t 结构体,第三个参数一般不用。当使用sa_handler时sa_flags设置为0即可。

If SA_SIGINFO is specified in sa_flags, then sa_sigaction (instead of sa_handler) specifies the signal-handling function for signum.
This function receives the signal number as its first argument, a pointer to a siginfo_t as its second argument and a pointer to a ucontext_t (cast to void *)as its third argument. (Commonly, the handler function doesn’t make any use of the third argument. See getcontext(3) for further information aboutucontext_t.)
……
SA_SIGINFO (since Linux 2.2)
The signal handler takes three arguments, not one. In this case, sa_sigaction should be set instead of sa_handler. This flag is meaningful only when establishing a signal handler.
注:很多文献在阐述该标志位时都认为,如果设置了该标志位,就必须定义三参数信号处理函数。实际不是这样的,验证方法很简单:自己实现一个单一参数信号处理函数,并在程序中设置该标志位,可以察看程序的运行结果。实际上,可以把该标志位看成信号是否传递参数的开关,如果设置该位,则传递参数;否则,不传递参数。

SA_NODEFER:
当SA_NODEFER设置时在信号处理函数执行期间不会屏蔽当前信号;

SA_NODEFER
Do not prevent the signal from being received from within its own signal handler. This flag is meaningful only when establishing a signal handler.
SA_NOMASK is an obsolete, nonstandard synonym for this flag.

5.sa_restorer
已过时,POSIX不支持它,不应再被使用。

siginfo_t 结构体

在man sigaction 中对siginfo_t 结构体也作了说明

The siginfo_t argument to sa_sigaction is a struct with the following fields:

           siginfo_t {
     
               int      si_signo;     /* Signal number 信号编号 */
               int      si_errno;     /* An errno value 如果为非零值则错误代码与之关联 */
               int      si_code;      /* Signal code 说明进程如何接收信号以及从何处收到*/
               int      si_trapno;    /* Trap number that caused
                                         hardware-generated signal
                                         (unused on most architectures) */
               pid_t    si_pid;       /* Sending process ID适用于SIGCHLD,代表被终止进程的PID  */
               uid_t    si_uid;       /* Real user ID of sending process适用于SIGCHLD,代表被终止进程所拥有进程的UID  */
               int      si_status;    /* Exit value or signal 适用于SIGCHLD,代表被终止进程的状态 */
               clock_t  si_utime;     /* User time consumed 适用于SIGCHLD,代表被终止进程所消耗的用户时间 */
               clock_t  si_stime;     /* System time consumed 适用于SIGCHLD,代表被终止进程所消耗系统的时间 */
               ==================
               sigval_t si_value;     /* Signal value */
               ==================
               int      si_int;       /* POSIX.1b signal */
               void    *si_ptr;       /* POSIX.1b signal */
               int      si_overrun;   /* Timer overrun count;
                                         POSIX.1b timers */
               int      si_timerid;   /* Timer ID; POSIX.1b timers */
               void    *si_addr;      /* Memory location which caused fault */
               long     si_band;      /* Band event (was int in
                                         glibc 2.3.2 and earlier) */
               int      si_fd;        /* File descriptor */
               short    si_addr_lsb;  /* Least significant bit of address
                                         (since Linux 2.6.32) */
               void    *si_call_addr; /* Address of system call instruction
                                         (since Linux 3.5) */
               int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
               unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
           }

请留意两个用于传输数据的变量:
sigval_t si_value; /* Signal value /
int si_int; /
POSIX.1b signal */
实际上:

  • siginfo_t 结构体中(sigval_t) si_value就是sigqueue函数中传入的第三个参数sigval
  • siginfo_t 结构体中(int) si_int就是从sigqueue函数中传入的第三个参数sigval.sival_int中获得
  • siginfo_t 结构体中si_ptr就是从sigqueue函数中传入的第三个参数sigval.sival_ptr中获得
    所以可以通过sigqueue种对sigval值的传入来实现信号带值,在信号处理函数中void signal_handle_new(int num,siginfo_t *info,void *d)读取第二个参数中对应变量的值来获取信号所带参数,通过这种方法实现进程间通讯
    (下面会详细介绍sigqueue和sigval)

sigqueue信号发送函数

man:

NAME
       sigqueue - queue a signal and data to a process

SYNOPSIS
       #include 
       int sigqueue(pid_t pid, int sig, const union sigval value);
   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
       sigqueue(): _POSIX_C_SOURCE >= 199309L

DESCRIPTION
       sigqueue()  sends  the signal specified in sig to the process whose PID is given in pid.  The permissions required to send a signal are the same as for kill(2).  As
       with kill(2), the null signal (0) can be used to check if a process with a given PID exists.

       The value argument is used to specify an accompanying item of data (either an integer or a pointer value) to be sent with the signal, and has the following type:

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

       If the receiving process has installed a handler for this signal using the SA_SIGINFO flag to sigaction(2), then it can obtain this data via the si_value  field  of
       the siginfo_t structure passed as the second argument to the handler.  Furthermore, the si_code field of that structure will be set to SI_QUEUE.

RETURN VALUE
       On  success, sigqueue() returns 0, indicating that the signal was successfully queued to the receiving process.  Otherwise, -1 is returned and errno is set to indi‐
       cate the error.

ERRORS
       EAGAIN The limit of signals which may be queued has been reached.  (See signal(7) for further information.)
       EINVAL sig was invalid.
       EPERM  The process does not have permission to send the signal to the receiving process.  For the required permissions, see kill(2).
       ESRCH  No process has a PID matching pid.

  • 功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用。
  • 原型: int sigqueue(pid_t pid, int sig, const union sigval value);

参数:

  • 第一个参数是指定接收信号的进程id,
  • 第二个参数确定即将发送的信号,
  • 第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

  • 返回值成功返回0,失败返回-1

同样可以使用传送sig=0的方式,检查指定pid进程是否存在

sigval联合体

      The value argument is used to specify an accompanying item of data (either an integer or a pointer value) to be sent with the signal, and has the following type:

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

       If the receiving process has installed a handler for this signal using the SA_SIGINFO flag to sigaction(2), then it can obtain this data via the si_value  field  of
       the siginfo_t structure passed as the second argument to the handler.  Furthermore, the si_code field of that structure will be set to SI_QUEUE.

  • 采用联合数据结构,说明siginfo_t结构中si_value要么持有一个4字节的整数值,要么持有一个指针,这就构成了与信号相关的数据。
  • sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数的第二个参数中
  • 在信号的处理函数中,包含这样的信号相关数据指针,但没有规定具体如何对这些数据进行操作,操作方法应该由程序开发人员根据具体任务事先约定。
  • 这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。

实际开发中我们将要通过信号传输的值赋值入sigval,即可在siginfo_t 中获得到

信号的顺序响应问题

linux系统是无法预知信号接收的时机的,那么就有可能出现这样的情况:
linux在执行一个信号对应的自定义信号处理函数的时候,接收到了一个新的信号,那应该怎么处理呢?
分为两种情况:

  • 如果linux执行一个信号处理函数的时候如果又收到一个不同种信号,会去执行新的信号处理函数,执行完之后再回来执行。
  • 如果linux执行一个信号处理函数的时候如果又收到又收到一个同种信号,则不会打断,是排队顺序执行的
    这有一点像嵌套中断,但不同信号间没有优先级

代码示例

#include 
#include//unix stand lib
#include
#include
#include
#include
#include
#include
#include//file dir
#include //wait func
#include //ststem
#include 

using namespace std;


//新的信号处理函数多了两个参数,其中signfo_t里面携带了我们想要的数据
void signal_handle_new(int num, siginfo_t *info, void *d)
{
     
	cout << "get value=" << info->si_int << endl;
}

int main(int argc, char *argv[])
{
     
	//安装信号先于发送信号
	struct sigaction sig_act;//新建信号安装结构体
	sig_act.sa_sigaction = signal_handle_new;//指定信号关联函数
	sig_act.sa_flags = SA_SIGINFO;//声明信号是携带数据的
	//将信号和安装信号结构体关联
	sigaction(SIGUSR1, &sig_act,NULL);//信号num,新信号结构体指针,旧信号结构体指针


	pid_t pid;
	pid = fork();
	if (pid == 0)
	{
     
		while (1)
		{
     
			sleep(1);//为了降低CPU使用率
		}	
	}
	else if (pid > 0)
	{
     
		sleep(1);//保证子进程先走
		sigval value;//新建携带数据联合体value
		value.sival_int = 1999;

		cout << "send signal" << endl;
		sigqueue(pid, SIGUSR1, value);//携带value值发送信号SIGUSR1给进程号为pid的进程
	}	
		return 0;
}

解释详见代码中注释,运行结果如下:
信号(二)——进程间数据传递sigaction函数(sigaction结构体、siginfo_t 结构体)、sigqueue函数(sigval联合体)、信号的顺序响应问题——linux系统编程_第1张图片
证明信号处理函数获取到了信号发送携带的int值,接收成功

我们进一步定义两套信号和对应的信号处理函数,实验信号的顺序响应问题,即排队响应和打断响应问题
同种信号在信号处理函数执行时输入:

#include //wait func
#include //ststem
#include 

using namespace std;


//新的信号处理函数多了两个参数,其中signfo_t里面携带了我们想要的数据
void signal_handle_new(int num, siginfo_t *info, void *d)
{
     
	for (int i = 0; i < 5; i++)
	{
     
		cout << "signal_handle_new is running" <<i<< endl;
		sleep(1);
	}
}

void signal_handle_new2(int num, siginfo_t *info, void *d)
{
     
	cout << "signal_handle_new2 is running" << endl;
}

int main(int argc, char *argv[])
{
     
	//安装信号
	struct sigaction sig_act, sig_act2;//新建信号安装结构体
	sig_act.sa_sigaction = signal_handle_new;//指定信号关联函数
	sig_act2.sa_sigaction = signal_handle_new2;//指定信号关联函数
	//将信号和安装信号结构体关联
	sigaction(SIGUSR1, &sig_act,NULL);//信号num,新信号结构体指针,旧信号结构体指针
	sigaction(SIGUSR2, &sig_act2, NULL);//信号num,新信号结构体指针,旧信号结构体指针
	
	
	while (1)
	{
     
		cout << "process run..." <<getpid()<< endl;
		sleep(1);
	}
	return 0;
}

信号(二)——进程间数据传递sigaction函数(sigaction结构体、siginfo_t 结构体)、sigqueue函数(sigval联合体)、信号的顺序响应问题——linux系统编程_第2张图片
从打印数字顺序可以看出,如果linux执行一个信号处理函数的时候如果又收到又收到一个同种信号,则不会打断,是排队顺序执行的

同样的代码,接下来测试不同种信号在信号处理函数执行时输入:
信号(二)——进程间数据传递sigaction函数(sigaction结构体、siginfo_t 结构体)、sigqueue函数(sigval联合体)、信号的顺序响应问题——linux系统编程_第3张图片
可以验证上面的结论:如果linux执行一个信号处理函数的时候如果又收到一个不同种信号,会去执行新的信号处理函数,执行完之后再回来执行。

另外需注意:

  • 如果发送了信号但是没有安装对应接收信号,这是子进程会结束掉,即变成僵尸进程

结构体部分内容参考:
https://www.cnblogs.com/mickole/p/3191804.html
https://blog.csdn.net/jnu_simba/article/details/8947410

你可能感兴趣的:(linux,linux)