7- 信号

1. 信号相关的概念

1.1 基本概念

  1. 特点:简单, 携带的信息量很少
  2. linux中的信号特点:
    (1)Linux操作系统的消息机制
    (2)信号的优先级非常高
    (3)可以实现进程通信,但由于优先级非常高,所以不建议;
  3. 产生信号的途径
    (1)通过shell命令:如通过kill命令终止某一个进程。
    (2)对终端的操作:如ctrl+c让键盘输入产生一个硬件中断;
    (3)通过函数调用:如调用sleep()函数;
    (4)硬件错误:如发生了段错误,访问了非法内存,程序退出;

1.2 操作

  1. 查看信号列表:kill -l
  2. 查看信号的详细信息:man 7 signal
  3. Linux中的信号行为:即收到信号之后的处理动作;通过man 7 signal命令查询
    (1)Term:终止进程
    (2)Ign:当前进程忽略掉这个信号
    (3)Core:终止进程并生成一个core文件用于gdb调试;默认情况下操作系统不允许生成core文件:ulimit -a -->core file size,若要放开系统限制:ulimit -c 数量
    (4)Stop:暂停当前进程;
    (5)Cont:继续执行当前被暂停的进程;
  4. Core文件的使用方法
    (1)系统默认不允许生成Core文件,需要通过ulimit -c 数量设置Core的数量;
    (2)gcc -g来gdb编译文件
    (3)运行文件发生段错误后,通过gdb 文件调试文件
    (4)进入gdb调试后,core -file core命令会自动指出错误地址;
  5. 进程对信号可进行的操作
    (1)执行默认的处理操作
    (2)捕捉到后忽略
    (3)捕捉到后执行自定义的处理操作
    (4)注意9 -SIGKILL19 -SIGSTOP不允许被捕捉、阻塞和忽略;

2. 信号相关的函数

2.1 kill/raise/abort函数

  1. kill()函数
给pid进程发送sig信号,返回成功或失败
int kill(pid_t pid, int sig);
pid:
	>0:某个接收信号的进程;
	=0:当前进程组;
	=-1:每个有权限接收当前信号的进程
	<-1:某个接收信号的进程组(-pid);
杀死父进程
kill(getppid(), 9);
  1. raise()函数
给当前进程发送sig信号,返回成功或失败
int raise(int sig);
成功返回-1,失败返回0
通过kill()函数实现
kill(getpid(), sig);
  1. abort()函数
给当前进程发送SIGABRT信号,默认结束当前进程
int abort(void)
通过kill()函数实现
kill(getpid(), SIGABRT);

2.2 alarm()函数

  1. alarm()函数
从seconds开始倒计时,返回剩余时间,倒计时结束发送SIGALRM信号给当前进程,默认结束当前进程。
unsigned int alarm(unsigned int seconds);
	seconds == 0则定时器无效
	该函数不阻塞;
	alarm(0)结束倒计时;

进行文件IO操作的时候比较浪费时间,应尽量减少读写次数;

2.sititimer()函数

计时器按照witch方式计算时间,在it_interval时间发出SIGKILL信号后,每隔it_value时间再发送SIGKILL信号;
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
struct itimerval
{
	struct timeval it_interval; /* 第一次时间 */
	struct timeval it_value;    /* 以后的时间间隔 */
};
struct timeval
{
	time_t      tv_sec;         /* 秒 */
	suseconds_t tv_usec;        /* 毫秒*/
};

示例:

//1.设置第一次信号5s,此后每间隔2s发送一次
#include <sys/time.h>
#include <signal.h>
#include <stdio.h>
int main()
{
	//1.设置时间
	struct itimerval newval = 
	{
		//1.1设置时间间隔
		.it_interval.tv_sec = 5,
		.it_interval.tv_usec = 0,
		.it_value.tv_sec = 2,
		.it_value.tv_usec = 0,
	};
	//2.设置信号捕捉
		//2.1设置回调函数
	void sighandler(int sig);
		//2.2捕捉信号
	signal(9, sighandler);
	//3. 运行计时器	
	setitimer(ITIMER_REAL, &newval, NULL);
	
	while(1);
	
}
void sighandler(int sig)
{
	printf("signal: sig\n");
}

3. 信号捕捉

3.1 信号捕捉函数

  1. signal()函数
将捕捉信号函数handler作为signum信号的操作方法
sighandler_t signal(int signum, sighandler_t handler);
	signum:信号
	handler:信号操作的回调函数。
  1. 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);
};
 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
 将signum信号捕捉到后,按照act中sa_flags设定的方式,使用sa_handler()sa_sigaction()函数,对进程进行操作。再处理信号过程中,可以根据需要设置sa_mask临时阻塞信号集,sa_restorer()函数无用。
//2.设置捕捉信号
		//2.1设置结构体
		void sighandler(int sig);
		struct sigaction myaction = 
		{
			.sa_handler = sighandler,
			sigemptyset(&myaction.sa_mask),
			.sa_flags = 0,
		};
		//2.2设置捕获函数
		sigaction(SIGALRM, &myaction, NULL);

注意
signal()再某些Linux平台下表现是不同的,因其不完全遵循posix,sigaction()遵循posix标准,应尽量使用sigaction()

3.2 信号捕捉时的流程

  1. 程序正常运行,cpu处理用户区.text段的的代码;
  2. 产生信号(异常),cpu切到内核区处理未决信号集中的信号;
  3. 若该信号时自定义处理方式,会再切换到用户区执行自定义信号处理函数;
  4. 信号处理结束后,会再切换到内核区查看信号出现之前的位置;
  5. 切换到用户区信号出现之前的位置继续处理剩下的代码。 在这里插入图片描述

4. 信号集

4.1 阻塞信号集和未决信号集

  1. 信号集位于内核的pcb中,分别通过8个byte即64个位来表示每个信号的状态;
    (1)每个信号占用信号集中的一位数据,且信号与位置一一对应;
    (2)由于位于内核的pcb中,因此只能通过系统的API来修改;
  2. 阻塞信号集:linux中的64个信号在其中都有位置,若某个信号是阻塞状态,则其对应的状态位置1,若非阻塞状态则清0;
    (1)信号阻塞则意味着延迟对该信号的处理,但最终会被处理;
    (1)阻塞信号集默认非阻塞状态,即状态位为0;
    (2)可以通过系统提供的API来修改某些信号的阻塞状态;
  3. 未决信号集:其中的64位表示64个信号是否尚未被处理,若尚未处理则置1,已被处理或未产生则为0;
    (1)未决信号代表信号已经产生,尚未被处理;
    (2)未决信号在处理前,需要先判断阻塞信号是否为0,即只有信号未阻塞才能处理,否则信号继续阻塞延迟处理;
    (3)未决信号无法修改,也无修改的必要;在这里插入图片描述

4.2 修改信号集API

注意:只能修改阻塞信号集。

  1. 自定义信号集
0自定义信号集set
int sigemptyset(sigset_t *set);1自定义信号集set
int sigfillset(sigset_t *set);
将自定义信号集set的signum信号置1
int sigaddset(sigset_t *set, int signum);
将自定义信号集set的signum信号清0
int sigdelset(sigset_t *set, int signum);
判断自定义信号集set的signum信号是否是1
int sigismember(const sigset_t *set, int signum);
返回值:信号在信号集中1,不在信号集中0,失败-1
  1. 自定义信号集->内核
通过how的方式,将自定义set信号集修改到内核oldset信号集
 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
 SIG_BLOCK
 SIG_UNBLOCK
 SIG_SETMASK

将未决信号集中的状态读到自定义信号集set中
 int sigpending(sigset_t *set);

成功返回0,失败返回-1
  1. 示例
//阻塞2和3信号,并打印未决信号集,再取消阻塞信号
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main()
{
	//1.设置自定义信号集
	sigset_t myset;
		//1.2清空自定义信号集
		sigemptyset(&myset);
	//2.设置2和3信号阻塞
	sigaddset(&myset, 2);
	sigaddset(&myset, 3);
	//3.将自定义信号集传给内核
	sigprocmask(SIG_BLOCK, &myset, NULL);
	//4.循环打印未决信号集
	int number = 0;
	while(1)
	{
	sigset_t pend;
		//4.1获取未决信号集
		sig&pending(&pend);
		//4.2循环打印未决信号集
		for(int i = 0; i < 31; i++)
		{
			int ret = sigismember(&pend, i+1);
			if(ret == -1)
			{
				perror("sigismember");
			}
			printf("%d", ret);
		}	
		sleep(1);
		printf("\n");
		//4.3超过10s则解除阻塞
		number++;
		if(number > 10)
		{
			sigprocmask(SIG_UNBLOCK, &myset, NULL);
		}
	}
	return 0;	
}

5. SIGCHLD信号

5.1 产生条件

  1. 当子进程结束(包括自杀和被杀);
  2. 子进程暂停
  3. 子进程从暂停恢复运行

以上三种形况子进程都会给父进程发送SIGCHLD信号,父进程的默认处理方式时忽略。

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

/*
pid_t waitpid(pid_t pid, int *status, int options);
参数:
- pid:
>0: 某个子进程的pid, 比较常用
=0: 回收当前进程组的子进程
-1: 回收所有的子进程 == wait(NULL), 用的最多的
      <0: 某个进程组的组ID, 回收指定进程组的子进程
- options: 设置函数阻塞或者非阻塞
- 0: 阻塞
- WNOHANG: 非阻塞
-返回值:
- >0: 回收的子进程的ID
- =0: options=WNOHANG, 还有子进程活着
- -1: 错误, 已经没有子进程了
*/

void myhandler(int sig)
{
	printf("catch %d signal\n", sig);
	while(1)
	{
		pid_t ret = waitpid(-1, NULL, WNOHANG);
		//1.若有子进程结束
		if(ret > 0)
		{
			printf("%d进程被回收\n", ret);
		}
		//2.无子进程结束
		if(ret == 0)
		{
			break;
		}
		//3.子进程全结束
		if(ret == -1)
		{
			break;
		}
	}
}

int main()
{
	//1.创建子进程
	pid_t pid;
	for(int i = 0; i < 20; i++)
	{
		pid = fork();
		if(pid == 0)
		{
			break;
		}
	}
		//1.1子进程
	if(pid == 0)
	{
		printf("pid = %d\n", getpid());
	}
	//2.设置捕获信号
	if(pid > 0)
	{
		//2.1声明捕获信号函数
		struct sigaction myact = 
		{
			.sa_handler = myhandler,
			sigemptyset(&myact.sa_mask),
			.sa_flags = 0,
		};
		sigaction(SIGCHLD, &myact, NULL);
		//2.2父进程
		while(1)
		{
			printf("父进程pid: %d\n", getpid());
			sleep(1);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值