linux系统编程5-信号

一、信号的概念

信号在我们的生活中随处可见,如:古代战争中挠杯为号;现代战争中的信号弹;体育比赛中使用的信号机枪…他们都有共性:1.简单2.不能携带大量信息 3.满足某个特设条件才发送

1、信号的特质

信号是软件层面上的“中断”。一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,再继续执行后续指令。与硬件中断类似—―异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”"。
所有信号的产生及处理全部都是由内核完成的。

2、与信号相关的事件和状态

1)产生信号
  1. 按键产生,如:Ctrltc-Ctrltz、Ctrl+\w
  2. 系统调用产生,如: kill、raise、abort
  3. 软件条件产生,如:定时器alarm
  4. 硬件异常产生,如:非法访问内存(毁错误)、除0(浮点数例外)、内存对齐出错(总线错误)
  5. 命令产生,如: kill命令
2)递达

递送并且到达进程

3)未决

产生和递归之间的状态。主要是由于阻塞(屏蔽)导致该状态。

4)信号的处理方式:
  • 执行默认动作
  • 忽略(丢弃), 也是一种处理
  • 捕捉, 调用户处理函数.

Linux内核的进程控制块 PCB是一个结构体,task_struct,除了包含进程 id,状态,工作目录,用户 id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

5)阻塞信号集(信号屏蔽字):

将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后) kill -9无法屏蔽

6)未决信号集

1.信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。
2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
在这里插入图片描述
但如果加到信号屏蔽字(详细的图往下翻)
在这里插入图片描述

总结

	概念:
		未决:产生与递达之间状态。  

		递达:产生并且送达到进程。直接被内核处理掉。

		信号处理方式: 执行默认处理动作、忽略、捕捉(自定义)


		阻塞信号集(信号屏蔽字): 本质:位图。用来记录信号的屏蔽状态。一旦被屏蔽的信号,在解除屏蔽前,一直处于未决态。

		未决信号集:本质:位图。用来记录信号的处理状态。该信号集中的信号,表示,已经产生,但尚未被处理。

3、信号4要素

与变量三要素类似的,每个信号也有其必备4要素,分别是:

  • 编号
  • 名称
  • 事件
  • 默认处理动作。
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4、kill发送信号函数

kill不仅仅局限于杀死,如上面提到的那些信号


int kill(pid_t pid, int sig);
pid:    	> 0:发送信号给指定进程

			= 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。

			< -1: 取绝对值,发送信号给该绝对值所对应的进程组的所有组员(由于大于0只发一个,这里加个符号表示他那个组的所有成员)。

			= -1:发送信号给,有权限发送的所有进程。

sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。

成功: 0;
失败:-1(ID非法,信号非法,普通用户杀init进程等权级问题),设置 errno

关于pid
在这里插入图片描述

在这里插入图片描述

5、alarm函数

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

unsigned int alarm(unsigned int seconds);

谁调用就给谁发,要求多少秒后给自己发信号

返回定时器剩余的秒数或0 返回0是已经过了定时器设置的时间了


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

定时,与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵尸…无论进程处于何种状态,alarm都计时。

time 命令 : 查看程序执行时间。 实际时间 = 用户时间 + 内核时间 + 等待时间。 --》 优化瓶颈 IO

6、setitimer函数

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

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

	参数:
	     which:	ITIMER_REAL: 进行自然计时。 ——> SIGALRM

					ITIMER_VIRTUAL: 进行用户空间计时  ---> SIGVTALRM

					ITIMER_PROF: 进行内核+用户空间计时 ---> SIGPROF
		
		new_value:定时秒数

		           类型:struct itimerval {

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

           				}it_interval;---> 周期定时秒数(第一次执行后进行每隔这个时间的周期执行)

               				 struct timeval {
               					time_t      tv_sec;         
               					suseconds_t tv_usec;        

           				}it_value;  ---> 第一次执行定时秒数(仅执行一次)  
           			 };

		old_value:传出参数,上次定时剩余时间。
	
		e.g.
			struct itimerval new_t;	
			struct itimerval old_t;	

			new_t.it_interval.tv_sec = 0;
			new_t.it_interval.tv_usec = 0;
			new_t.it_value.tv_sec = 1;
			new_t.it_value.tv_usec = 0;

			int ret = setitimer(&new_t, &old_t);  定时1秒

	返回值:
		成功: 0

		失败: -1 errno


当setitimer()所执行的timer时间到了,会呼叫SIGALRM signal,
itimerval.it_value设定第一次执行function所延迟的秒数,
itimerval.it_interval设定以后每几秒执行function,
若只想延迟一段时间执行function,只要设定 itimerval.it_value即可,
若要设定间格一段时间就执行function,则it_value和it_interval都要设定,否则 funtion的第一次无法执行,就别说以后的间隔执行了。

二、信号操作函数

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

在这里插入图片描述
因为不允许直接对mask进行操作,因此必须要有个缓冲,如上图所示,然后再和mask进行运算

1、信号集操作函数


	sigset_t set;  自定义信号集。 首先设置一个信号集

	sigemptyset(sigset_t *set);	清空信号集

	sigfillset(sigset_t *set);	全部置1

	sigaddset(sigset_t *set, int signum);	将一个信号添加到集合中

	sigdelset(sigset_t *set, int signum);	将一个信号从集合中移除

	sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。 在--1, 不在--0

2、sigprocmask函数

读取或修改进程的信号屏蔽字(PCB中)

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

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

成功: 0;
失败:-1,设置errno参数:

set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。
oldset:传出参数,保存旧的信号屏蔽集。

how参数取值:假设当前的信号屏蔽字为mask
1. SIG_BLOCK:当how设置为此值,set表示需要屏蔽的信号。相当于mask = mask|set
2. SlG_UNBLOCK:当how设置为此,set表示需要解除屏蔽的信号。相当于mask = mask & ~set3. 
3. SlG_SETMASK:当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于mask = set

若,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

3、sigpending函数

取当前进程的未决信号集


int sigpending(sigset_t *set); 

  set传出参数。
  
  返回值:
     成功:0;
     失败:-1,设置errno

例子,下面运行后执行ctlc+c会打印0100000
在这里插入图片描述

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

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

void print_set(sigset_t *set)
{
    int i;
    for (i = 1; i<32; i++) {
        if (sigismember(set, i)) 
            putchar('1');
        else 
            putchar('0');
    }
    printf("\n");
}
int main(int argc, char *argv[])
{
    sigset_t set, oldset, pedset;
    int ret = 0;

    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);
    sigaddset(&set, SIGBUS);
    sigaddset(&set, SIGKILL);

    ret = sigprocmask(SIG_BLOCK, &set, &oldset);
    if (ret == -1)
        sys_err("sigprocmask error");

    while (1) {
        ret = sigpending(&pedset);
        print_set(&pedset);
        sleep(1);
    }

    return 0;
}

三、信号捕捉

1、signal函数

注册一个信号捕捉函数,抓信号还是内核来

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

typedef定义了一个类型,函数指针类型

该函数由ANSI定义,由于历史原因在不同版本的 Unix和不同版本的 Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。
在这里插入图片描述

2、sigaction函数

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
 参数:
     act:传入参数,新的处理方式。
     oldact:传出参数,旧的处理方式。
 返回值:
   成功: 0;
   失败:-1,设置errno

这里要有信号屏蔽,比如捕捉到一个信号,进入回调函数处理过程,假设回调函数特别长,这时候正好又来了一个信号,由于信号比程序执行优先级高那么前面那个回调函数就没法再执行。sa_flags设置为0将默认屏蔽这个信号。
在这里插入图片描述

4、信号捕捉特性


	1. 捕捉函数执行期间,信号屏蔽字 由 mask --> sa_mask , 捕捉函数执行结束。 恢复回mask

	2. 捕捉函数执行期间,本信号自动被屏蔽(依赖于sa_flgs = 0).

	3. 捕捉函数执行期间,被屏蔽信号多次发送,如果有多次的话解除屏蔽后只处理其中的一次!

3、内核实现信号捕捉

在这里插入图片描述

四、sigchld信号

1、sigchld的产生条件

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

2、借助sigchld信号回收子进程

在这里插入图片描述
在这里插入图片描述
阻塞代码
在这里插入图片描述

五、中断系统调用

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贪睡的蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值