进程间通信(IPC):信号


一、信号的概念

信号在生活当中随处可见,如果古代战争中的摔杯为号、体育比赛中的信号枪等。
它们都有以下共性:
  1)简单(执行一个简单的动作就可以触发)
  2)不能携带大量信息(只是发送一个讯息,并不能指导你去怎样处理)
  3)必须满足特殊条件才能触发。

1、信号的生命周期和处理流程

1)信号被某个进程产生,并设置此信号传递的对象(一般为对应进程的pid,也可以为一个进程组),然后传递给操作系统(内核)

2)操作系统根据接收进程的设置(是否阻塞)而选择性的发送给接收者,如果接收者阻塞该信号(且该信号是可以阻塞的),操作系统将暂时保留该信号,而不传递,直到该进程解除了对此信号的阻塞(如果对应进程已经退出,则丢弃此信号),如果对应进程没有阻塞,操作系统将传递此信号。

3)目标进程接收到此信号后,将根据当前进程对此信号设置的预处理方式,暂时终止当前代码的执行,保护上下文(主要包括临时寄存器数据,当前程序位置以及当前CPU的状态)、转而执行中断服务程序,执行完成后在回复到中断的位置。 当然,对于抢占式内核,在中断返回时还将引发新的调度。


  完整过程:信号产生 -> 内核转发 -> 进程停止 -> 内核处理 -> 进程恢复

  信号的功能与时钟中断类似,收到信号后,不管执行到程序的什么位置,都要暂停运行。去处理信号,处理完毕,再继续执行。时钟中断是硬件中断,信号是软件层面实现的中断,都是异步模式。所以,早期信号常被称为软中断。由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。


Note:

每个进程收到的所有信号,都是由内核负责发送的,内核处理的。


2、信号屏蔽字与未决信号集

  正常情况下,信号从产生到发送完成是一个瞬时的过程。从信号产生,到信号递送完成,几乎瞬时完成。

  但是每个进程都可以设置阻塞信号集(也叫信号屏蔽字),它相当于一个位图,记录了进程阻塞哪些信号。你可以设置屏蔽(阻塞)一些信号,一旦信号被阻塞了,它就不会被发送到指定进程。这个状态称为未决状态,就是你的信号一直在发送的途中,没有被处理。 所有的未决信号都被保存在PCB未决信号集中,未决信号集阻塞信号集的影响。当一个信号发送给进程B时,B的未决信号集先将对应标志位的值置为1,表示信号已经产生,还未被处理。如果信号没有被阻塞,那么信号立即被处理,标志位的值会迅速恢复成0。如果信号设置为阻塞,那么它在阻塞信号集对应的标志位为1,并且在未决信号集(位图)中的标志位也为1。这样的话,这个信号就不能递达到对应的进程了。


Note:

信号的未决状态,只有信号生产并且阻塞,才会发生。

未决信号集可读,阻塞信号集可读写


3、信号4要素

1) 信号的编号

Linux支持的信号列表可以通过 kill -l 显示。编号1 - 31 的信号为传统的UNIX支持的信号,是不可靠信号(非实时的);编号为32 - 64的信号是后来扩充的,称为可靠信号(实时信号)。不可靠信号和可靠信号的区别是:前者不支持排队,可能造成信号丢失,而后者不会,后者一般用于嵌入式开发。

不可靠信号:信号可能会丢失,一旦信号丢失了,进程并不能知道信号丢失。什么情况下信号会丢失?比如内核已经在调用信号的处理函数了,在执行期间,你再次发送了多个信号,后面的信号就可能丢失。

可靠信号:也是阻塞信号,不会丢失。当内核发送了一个阻塞信号,无论该信号的处理动作是系统默认动作还是捕捉,信号从发出以后会一直保持未决的状态,直到该进程对此信号解除了阻塞,或将对此信号的动作更改为忽略。

kill -l

[chen@baboon-chen ~]$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

Linux中的kill命令不是杀死一个进程,而是将指定的信号发送给一个进程或一组进程。其中,要求接收信号进程和发送信号进程的所有者相同,或者发送信号进程的所有者是超级用户。

权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。kill -9(root 用户的pid)是不可以的。同样,普通用户也不能向其它用户发送信号,终止其进程。只能向自己创建的进程发送信号。普通用户使用kill命令的基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID

kill的第二个参数也可以是负数,表示发送一个信号给指定的进程组。如:kill -9 -1103 表示杀掉进程组为1103的所有进程。

2)信号名

由于不同的平台,某些信号的编号可能不同,最好用信号名标识信号。

有两个信号不能被进程捕获、阻塞、或者忽略:SIGKILL(编号9,立即终止进程)、SIGSTOP(编号19,暂停进程)。

3)信号触发事件

man 7 signal

Signal     Value     Action   Comment
──────────────────────────────────────────────────────────────────────
SIGHUP        1       Term    Hangup detected on controlling terminal
                              or death of controlling process
SIGINT        2       Term    Interrupt from keyboard
SIGQUIT       3       Core    Quit from keyboard
SIGILL        4       Core    Illegal Instruction
SIGABRT       6       Core    Abort signal from abort(3)
SIGFPE        8       Core    Floating point exception
SIGKILL       9       Term    Kill signal
SIGSEGV      11       Core    Invalid memory reference
SIGPIPE      13       Term    Broken pipe: write to pipe with no
                              readers
SIGALRM      14       Term    Timer signal from alarm(2)
SIGTERM      15       Term    Termination signal
SIGUSR1   30,10,16    Term    User-defined signal 1
SIGUSR2   31,12,17    Term    User-defined signal 2
SIGCHLD   20,17,18    Ign     Child stopped or terminated
SIGCONT   19,18,25    Cont    Continue if stopped
SIGSTOP   17,19,23    Stop    Stop process
SIGTSTP   18,20,24    Stop    Stop typed at terminal
SIGTTIN   21,21,26    Stop    Terminal input for background process
SIGTTOU   22,22,27    Stop    Terminal output for background process

The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

  关于一些有多个编号的信号,取中间编号为准,中间的编号表示X86平台通用,前或后是其它类Unix平台下使用的编号。

4)信号默认处理方式

A 忽略信号,内核将信号丢弃,信号对进程没有任何影响;
B 终止进程;
C 产生核心转储文件,同时进程终止;
D 进程暂停;
E 执行之前暂停的进程;

  34 ~ 64 是LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。




二、信号的产生

1、信号产生的5种方法

1)按键产生,如:Ctrl+c -> 2 SIGINT 终止、Ctrl+z -> 20 SIGSTP 暂停、Ctrl+\ -> 3 SIGQUIT core

2) 系统调用产生,如:kill、raise、abort

3) 软件条件产生,如:定时器alarm

4) 硬件异常产生,如:非法访问内存(段错误)、除0(浮点数异常)、内存对齐出错(总线错误)

5) 命令产生,如:kill命令

2、信号的状态

递达:递送并到达进程。

未决:产生和递达之间的状态,主要由于阻塞(屏蔽)导致该状态。

3、信号的处理方式

1)忽略信号。比如子进程退出时会发送SIGCHLD给父进程,父进程默认屏蔽。

2)执行与这个信号相关的默认操作。
  A 忽略信号,内核将信号丢弃,信号对进程没有任何影响;
  B 终止进程;
  C 产生核心转储文件,同时进程终止;
  D 进程暂停;
  E 执行之前暂停的进程;

3)捕捉。
  进程可以事先注册特殊的信号处理函数提供给内核,充当回调函数当进程收到信号时,内核调用回调函数处理信号,处理完成后,回到内核,再通知进程恢复运行。

4、kill

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

/*
其中,pid是进程标识,有以下4种:
(1) 当pid>0时,pid是信号想要发送的进程的标识。
(2) 当pid=0时,信号将送往所有与调用kill()的那个进程属于同一个使用组的进程。
(3) 当pid=-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)外。
(4) 当pid<-1时,信号将送往以-pid为组标识的进程。

返回值:
成功: 0
错误:-1,失败原因在errno中
*/

举例:

#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


int main() {
    pid_t childpid;
    int status;
    int retval;

    childpid = fork();  //  创建子进程
    if (-1 == childpid) {               //  判断是否创建失败
        perror("fork()");
        exit(EXIT_FAILURE);
    } else if (0 == childpid) {
        puts("In chld process");
        sleep(100);                     //  让子进程睡眠,以便查看父进程的行为
        exit(EXIT_SUCCESS);
    } else {
        if (0 == (waitpid(childpid, &status, WNOHANG))) {   //  判断子进程是否已经退出
            retval = kill(childpid, SIGKILL);               //  发送kill -9 给子进程,要求其终止

            if (retval) {                   //  判断信号是否发送成功
                puts("kill failed.");
                perror("kill");
                waitpid(childpid, &status, 0);
            } else {
                printf("%d killed\n", childpid);
            }
        }
    }

    exit(EXIT_SUCCESS);
}

5、raise

#include <signal.h>
int raise(int sig);

// RETURN VALUE
// raise() returns 0 on success, and nonzero for failure.

//  raise(signo) == kill(getpid(), signo)

  给当前进程发送指定信号(自己给自己发)。

6、abort

#include <stdlib.h>
void abort(void);

  给自己发送异常终止信号6 SIGABRT 信号,终止并产生Core文件。该函数无返回值。

7、alarm

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

/*
返回值:
返回0 或 上一个闹钟距离触发仍剩余的秒数,无失败。
*/

设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14 SIGALRM信号。进程收到该信号,默认动作终止

每个进程都有且只有唯一一个定时器。这里的定时是采用自然定时法,与进程状态无关,无论进程就绪、运行、挂起(阻塞、暂停)、终止、僵尸…无论进程处于任何状态,alarm都计时。

常用:取消定时器alarm(0),返回旧闹钟余下的秒数。


查看1秒内程序能进行多少次累加:

#include <stdio.h>
#include <unistd.h>

int main()
{
        alarm(1);
        int i = 0;
        while (1) printf("%d\n", ++i);

        return 0;
}

利用time查看运行时间:

0
...
200125
200126
Alarm clock

real    0m3.000s
user    0m0.213s
sys     0m0.770s

  实际执行时间 = 系统时间 + 用户时间 + 等待时间。

  这里性能瓶颈在于IO等待,如果将输出结果重定向,即可提高运行效率。

8、setitimer

#include <sys/time.h>

// itimer == interval timer
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

struct itimerval {
    struct timeval it_interval; /* next value */
    struct timeval it_value;    /* current value */
};

struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

/*
setitimer 参数:

which:
ITIMER_REAL:自然计时 -> 发送信号14 SIGALRM
ITIMER_VIRTUAL:虚拟空间(用户空间)计时 -> 发送信号26 SIGVTALRM 只计算进程占用CPU的时间
ITIMER_PROF:运行时(用户+内核)计时 -> 发送信号27 SIGPROF 计算占用CPU及执行系统调用的时间

new_value: 设置闹钟定时
it_value: 第一次定时的时长
itimerval:后续每次定时的时长
如果两个参数都设置为0,即清0操作,取消闹钟。

old_value:返回上次闹钟运行的时长。


返回值
On success, zero is returned.
On error, -1 is returned, and errno is set appropriately.
*/

  设置定时器(闹钟)。可代替alarm函数。精度微秒us,可以实现周期定时。


查看1秒内程序能进行多少次累加:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>


int main()
{
        struct itimerval it_cur, it_old;
        it_cur.it_value.tv_sec = 1;
        it_cur.it_value.tv_usec = 0;
        it_cur.it_interval.tv_sec = 0;
        it_cur.it_interval.tv_usec = 0;

        int ret = setitimer(ITIMER_REAL, &it_cur, &it_old);
        if (ret == -1)
        {
                perror("setitimer failed");
                exit(EXIT_FAILURE);
        }

        int i = 0;
        while(1) printf("%d\n", ++i);

        return 0;
}




三、信号的捕捉

1、signal

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

/*
signum是我们要处理的信号

参数handler就是处理的动作,它可以取以下3个值:
(1) SIG_ING:忽略此信号;
(2) SIG_DFL:恢复对此信号的系统默认处理;
(3) 函数指针:用自定义的函数来处理此信号;

如果函数执行成功则返回该信号上一次的handler值(一般不需要返回值)。
如果出错,则返回SIG_ERR,此时通过errno可获取错误码。

signal是ANSI C 标准的,而sigaction是POSIX标准。
并且sigaction下次收到相同的信号时,会继续使用sigaction注册的信号处理函数处理。
而 signal只有在gcc编译时不加上-std=99,才会依旧使用signal下定义的函数来处理信号。
*/

举例:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int main() {
    signal(SIGINT, SIG_IGN);    // 忽略Ctrl+C
    for (int i = 0; i < 100; i++)
    {
        sleep(5);
        printf("i\n");
    }
    return 0;
}

2、sigaction

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

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

/*
Note:
sa_restorer: 弃用
sa_sigaction: 当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理函数,参数中可以携带更多关于信号的信息。(很少使用)

sa_handler: 指定信号被捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL 表执行默认动作。
sa_mask:调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
sa_flags: 通常设置为0,表使用默认属性。即,如果在处理函数执行期间,发送相同的信号会被自动屏蔽。
*/

/*
参数:
signum: 操作的信号
act: 入参,信号的处理方式
oldact:原本对信号的处理方式。

返回值:
成功:返回0
失败:返回-1。
*/

  • 进程正常运行时,默认PCB中有一个信号屏蔽字,假定为A,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数的有可能执行很长时间,在这期间所屏蔽的信号不由A来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为A。

  • N信号捕捉函数执行期间,N信号自动被屏蔽。

  • 阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)

举例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void docatch(int signo)
{
        printf("%d signal is catched\n", signo);
        sleep(10);
        printf("process %d signal end\n"), signo;
}


int main()
{
        int ret;
        struct sigaction act;

        act.sa_handler = docatch;
        sigemptyset(&act.sa_mask);
        sigaddset(&act.sa_mask, SIGQUIT);
        act.sa_flags = 0; // 默认属性 信号捕捉函数执行期间,自动屏蔽本信号

        ret = sigaction(SIGINT, &act, NULL);
        if (ret < 0)
        {
                perror("sigaction error");
                exit(EXIT_FAILURE);
        }

        while(1);

        return 0;
}

3、内核实现信号捕捉的过程


在这里插入图片描述

Note: 信号捕捉函数应被定义为可重入函数。

4、捕捉SIGCHLD信号


通过捕捉SIGCHLD信号回收子进程:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>


void sys_err(char *str)
{
	perror(str);
	exit(EXIT_FAILURE);
}

void deal_sig_child(int signo)
{
	int status;
	pid_t pid;

	// if((pid = waitpid(0, &status, WNOHANG)) > 0)
	while((pid = waitpid(0, &status, WNOHANG)) > 0)
	{
		if (WIFEXITED(status))
			printf("---------child:%d exit %d\n", pid, WEXITSTATUS(status));
		else if (WIFSIGNALED(status))
			printf("child %d cancel signal %d\n", pid, WTERMSIG(status));
	}
}

int main()
{
	pid_t pid;
	int i;

	for (i = 0; i < 10; i++)
	{
		if ((pid = fork()) == 0) break;
		else if (pid < 0) sys_err("fork error");
	}

	if (pid == 0)
	{
		printf("child process id: %d\n", getpid());
		sleep(1);
		return i+1;
	}
	else if (pid > 0)
	{
		// 1、阻塞SIGCHLD信号   --此处省略
		// 子进程可能先于父进程退出,所以在注册信号捕捉函数前,先将信号阻塞
		
		struct sigaction act;
		act.sa_handler = deal_sig_child;
		sigemptyset(&act.sa_mask);
		act.sa_flags = 0;
		sigaction(SIGCHLD, &act, NULL);

		// 2、解除SIGCHLD阻塞	--此处省略
		// 注册完成,再解除阻塞

		while(1)
		{
			printf("Parent process id: %d\n", getpid());
			sleep(1);
		}
	}
	

	return 0;
}

  在SIGCHLD的处理函数中,如果将while循环修改成if条件,会导致部分子进程未能回收,成为僵尸进程。

  这是因为:进程是通过扫描CPB中的未决信号集来判断是否有信号未被处理的。当一个SIGCHLD信号在被处理时,如果其它子进程也已退出,一起发送了SIGCHLD信号,信号处理函数只会执行一次,导致其它子进程未能被回收。

  将条件改成while循环,waitpid函数会回收所有可以回收的子进程。

5、利用管道实现ls | wc -l

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

void do_wait()
{
	pid_t pid;
	while((pid = waitpid(0, NULL, WNOHANG)) > 0);
}

int main()
{
	pid_t pid;
	int fd[2];

	pipe(fd);
	
	pid = fork();
	if (pid == 0)
	{
		close(fd[1]);	//	子进程从管道中读数据,关闭写端
		dup2(fd[0], STDIN_FILENO);	// 让wc从管道中读取数据
		execlp("wc", "wc", "-l", NULL);	//	wc命令默认从标准输入读取数据
	}
	else if (pid > 0)
	{
		signal(SIGCHLD, do_wait);	// 注册信号捕捉
		close(fd[0]);		// 关闭读端
		dup2(fd[1], STDOUT_FILENO);	// 将ls结果输出到屏幕
		execlp("ls", "ls", NULL);
	}

	return 0;
}



四、信号集操作函数

内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字 mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。


未决信号集阻塞信号集

在这里插入图片描述



信号屏蔽过程:

在这里插入图片描述


1、信号集设定

#include <signal.h>

sigset_t set; // typedef unsigned long sigset_t;
//	sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。

int sigemptyset(sigset_t *set);	// 将某个信号集清0  成功:0  失败: -1  

int sigfillset(sigset_t *set);	// 将某个信号集置1  成功:0  失败: -1  

int sigaddset(sigset_t *set, int signum);	// 将某个信号加入信号集  成功:0  失败: -1  

int sigdelset(sigset_t *set, int signum);	// 将某个信号清出信号集  成功:0  失败: -1  

int sigismember(const sigset_t *set, int signum);	// 将某个信号是否在信号集中  在:1  不在: 0  出错: -1  

2、sigprocmask

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

/*
参数: 
set: 传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。
oldset: 传出参数,保存旧的信号屏蔽集。
how参数取值: 假设当前的信号屏蔽字为mask
1、SIG_BLOCK: 当 how 设置为此值,set 表示需要屏蔽的信号。 相当于 mask = mask | set
2、SIG_UNBLOCK: 当 how 设置为此值,set 表示需要解除屏蔽的信号。 相当于 mask = mask & ~set
3、SIG_SETMASK: 当 how 设置为此值,set 表示用于代替原始屏蔽集的新屏蔽集。 相当于 mask = set
若调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
*/


  用来屏蔽信号,或者解除信号屏蔽。其本质,读取或修改PCB信号屏蔽字

  需要注意:屏蔽信号,只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃。

3、sigpending

#include <signal.h>

int sigpending(sigset_t *set);
/*
set: 传出参数

返回值:
成功:0
失败:-1
*/


读取当前进程的未决信号集

#include <stdio.h>
#include <signal.h>
#include <unistd.h>


void printpending(sigset_t *set)
{
        int i = 0;
        for (i = 1; i < 31; i++) printf("%d", sigismember(set, i));
        printf("\n");
}

int main()
{
        sigset_t myset, oldset, ped;

        sigemptyset(&myset);
        sigaddset(&myset, SIGQUIT);
        sigaddset(&myset, SIGINT);

        sigprocmask(SIG_BLOCK, &myset, &oldset);

        while(1)
        {
                sigpending(&ped);
                printpending(&ped);
                sleep(1);
        }

        return 0;
}



五、信号传参

1、发送信号传参


sigqueue函数对应kill函数,但可在指定进程发送信号的同时携带参数。

#include <signal.h>

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

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

/*
RETURN VALUE
On  success,  sigqueue() returns 0
Otherwise -1  and errno is set to indicate the error.
*/

  向指定进程发送信号的同时,携带数据。但,如传地址,需要注意,不同进程之间的虚拟内存地址空间各自独立,将当前进程地址传递给另一进程没有实际意义。 不过可以给当前进程发送信号时携带地址信息。

2、捕捉函数传参

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

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

  当注册信号捕捉函数,希望获取更多信号相关信息,不应该使用sg_handler而应该用sa_sigaction。但此时的sa_flags必须指定为SA_SIGINFO,siginfo_t是一个成员十分丰富的结构体类型,可以携带各种信号相关的数据。



六、信号中断系统调用

系统调用可以分为两类,慢速系统调用和其它系统调用。

1、慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞间期收到一个信号,该系统调用就被中断,不再继续执行;也可以设定系统调用是否重启。如:read、write、pause、wait…

2、其它系统调用:getpid、getppid、fork…

慢速系统调用在运行期间,有可能被信号打断。

  如果想要信号中断系统调用,比如pause,收到信号后不再挂起。那么需要具备以下这些条件:

  1. 想中断pause, 信号不能被屏蔽。
  2. 信号的处理方式必须是捕捉(默认、忽略都不可以)。
  3. 中断后返回-1,设置errno为EINTR(被信号中断)。

  可修改sa_flags参数来设置被信号中断后系统调用是否重启。SA_INTERRUPT不重启,SA_RESTART重启。sa_falgs还有很多可选参数,适用于不同情况。如:捕捉到信号后,在执行捕捉函数期间,不希望自动阻塞信号,可将sa_flag设置为SA_NODEFFER,除非sa_mask中包含该信号。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值