Linux信号(四)

SIGCHLD 信号

1. SIGCHLD 的产生条件

  1. 子进程终止时
  2. 子进程接收到 SIGSTOP 信号停止时
  3. 子进程处在停止态,接受到 SIGCONT 后唤醒

2. 借助 SIGCHLD 信号回收子进程

子进程结束运行,其父进程会收到 SIGCHLD 信号。该信号的默认动作时忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <assert.h>
void sigchld_handler()
{
	int status;
	pid_t pid;
	//这里的while不能改成if,若改成if,则很大可能会回收不完全,因外当有多个子进程同时结束,都向父进程发送 SIGCHLD 信号,这时候 SIGCHLD 信号只会被记录一次,那么也只会回收一个子进程
	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;
	struct sigaction act, oldact;
	int i;
	for(i = 0; i < 10; i++)
	{
		pid = fork();
		assert(pid >= 0);
		if(pid == 0)
			break;
		
	}
	if(pid == 0)
	{
		int n = 1;
		while(n--)
		{
			printf("I am child %d\n", i+1);
			sleep(1);
		}
		return i+1;
	}
	else if(pid > 0)
	{
		//设置 SIGCHLD 阻塞,万一父进程还没注册完 SIGCHLD 的捕捉函数,已经有子进程退出了,那么父进程就不会接收到该子进程发送的信号,那么就不能正常回收
		sigset_t mask, oldmask;
		sigemptyset(&mask);
		sigaddset(&mask, SIGCHLD);
		sigprocmask(SIG_BLOCK, &mask, &oldmask);
	
		//注册 SIGCHLD 信号
		act.sa_handler = sigchld_handler;
		sigemptyset(&act.sa_mask);
		act.sa_flags = 0;
		sigaction(SIGCHLD, &act, &oldact);

		//解除对 SIGCHLD 的阻塞
		sigprocmask(SIG_SETMASK, &oldmask, NULL);

		while(1)
		{
			printf("I am parent\n");
			sleep(1);
		}
	}
	return 0;
}

【运行结果】10个子进程都被正常回收
在这里插入图片描述


信号传参

1. 发送信号传参

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

int sigqueue(pid_t pid, int sig, const union sigval value);
【返回值】
成功:0
失败:-1,设置errno

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

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

2. 捕捉函数传参

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);
           };

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

           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 */
               uid_t    si_uid;       /* Real user ID of sending process */
               int      si_status;    /* Exit value or signal */
               clock_t  si_utime;     /* User time consumed */
               clock_t  si_stime;     /* System time consumed */
               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) */
           }


中断系统调用

系统调用可分为两类:慢速系统调用和其他系统调用

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

结合 pause,回顾慢速系统调用:
慢速系统调用被中断的相关行为,实际上就是 pause 的行为:如:read

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

可修改 sa_flags参数来设置被信号中断后系统调用是否重启。SA_INTERRUPT 不重启。SA_RESTART 重启。


【扩展了解】
sa_flags 还有很多可选参数,适用于不同情况。如:捕捉到信号后,在执行信号捕捉函数期间,不希望自动阻塞该信号,可将 sa_flags 设置为 SA_NODEFER(可重入),除非 sa_mask 中包含该信号。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值