深入理解计算机系统 ---信号

目录

1.基本概念

什么是信号:

信号由谁发出:

信号由谁处理、如何处理:

常用的信号的编号及简介:

2.发送信号

进程组:

用 /bin/kill 程序发送信号:

从键盘发送信号:

用alarm函数发送信号:

用kill函数发送信号:

3.接收信号

4.阻塞信号


1.基本概念

什么是信号:

信号是内容受限的一种异步通信机制

(1)信号的目的:用来通信(进程与进程之间的通信)

(2)信号是异步的(对比硬件中断),不确定信号到来时间和内容

(3)信号本质上是int型数字编号(事先定义好的)

信号由谁发出:

(1)用户在终端按下按键

(2)硬件异常后由操作系统内核发出信号

(3)用户使用kill命令向其他进程发出信号

(4)某种软件条件满足后也会发出信号,如alarm闹钟时间到会产生SIGALARM信号,向一个读端已经关闭的管道write时会产生SIGPIPE信号

信号由谁处理、如何处理:

(1)忽略信号

(2)默认处理(当前进程没有明显的管这个信号默认:忽略或终止进程

(3)捕获信号(信号绑定了一个函数)

常用的信号的编号及简介:

编号名称默认动作对应事件
2SIGINT终止用户输入 ctrl+c
9SIGKILL终止终止程序(不能重写或忽略)
11SIGSEGV终止且 Dump段冲突 Segmentation violation
14SIGALRM终止时间信号
17SIGCHLD忽略子进程停止或终止

2.发送信号

进程组:

每个进程都都只属于一个进程组

//返回当前进程的进程组
pid_t getpgrp(void); 

//改变一个进程的进程组 -- 若成功返回 0 ,若错误则为 -1
int setpgid(pid_t pid, pid_t pgid);  

/bin/kill 程序发送信号:

# 发送信号 9 (SIGKILL)给进程组15213
/bin/kill -9 15213

从键盘发送信号:

通过键盘让内核向每个前台进程发送 SIGINT(SIGTSTP) 信号

Ctrl+C  (SIGINT)   #默认终止进程
Ctrl+Z  (SIGTSTP)  #默认挂起进程

用alarm函数发送信号:

进程可以调用 alarm 函数向它自己发送 SIGALARM 信号

alarm函数安排内核在secs秒后发送一个SIGALRM信号给调用进程

unsigned int alarm(unsigned int seconds);

用kill函数发送信号:

//使用kill发送信号号码 sig 给进程 pid
int kill(pid_t pid, int sig);

如下程序为 父进程发送 SIGKILL 信号给它的子进程:

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

int main() 
{
    pid_t pid;

    /* Child sleeps until SIGKILL signal received, then dies */   
    if ((pid = fork()) == 0) 
	{   
		pause();  /* Wait for a signal to arrive */  
		printf("control should never reach here!\n");
		exit(0);
    }

    /* Parent sends a SIGKILL signal to a child */
    kill(pid, SIGKILL);
    exit(0);
}

3.接收信号

每个信号类型都有一个预定义的『默认动作』,可能是以下的情况:

  1. 终止进程
  2. 终止进程 并 转储内存(dump core)
  3. 停止进程,收到 SIGCONT 信号之后重启
  4. 忽略信号

signal 函数可以修改默认的动作,函数如下:

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

signal 函数可以通过下列三种方法之一来改变和信号 signum 相关联的行为:

  1. 如果 handler 是 SIG_IGN,那么忽略类型为 signum 的信号
  2. 如果 handler 是 SIG_DFL,那么类型为 signum 的信号行为恢复为默认行为
  3. 否则,handler就是用户定义的函数的地址,这个函数被称为 信号处理程序,只要进程接收到一个类型为signum,就会调用这个程序。

如下程序为捕获用户在键盘上的输入Ctrl + C时发送的SIGINT信号,SIGINT的默认行为是立即终止该程序,在这个程序中,我们将默认行为修改为捕获信号,输出一条消息,然后终止该进程。

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

void sigint_handler(int sig) /* SIGINT handler */   
{
    printf("\nCaught SIGINT!\n");    
    exit(0);           			//终止进程           
}                                             

int main() 
{
    /* Install the SIGINT handler */         
    if (signal(SIGINT, sigint_handler) == SIG_ERR)  
	printf("signal error");                 
    
    pause(); /* Wait for the receipt of a signal */  

    return 0;
}

执行程序,按下Ctrl + C,输出:

^C
Caught SIGINT!

4.阻塞信号

内核会阻塞与当前在处理的信号同类型的其他正待等待的信号,也就是说,一个 SIGINT 信号处理器是不能被另一个 SIGINT 信号中断的。(阻塞相同信号)

如果想要显式阻塞(阻塞特定信号),就需要使用sigprocmask函数了,以及其他一些辅助函数:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:
SIG_BLOCK    把 set 中的信号添加到 blocked 中

SIG_UNBLOCK  从 blocked 中删除 set 中的信号
            
SIG_SETMASK  阻塞信号集设置为参数集。

#其他辅助函数
sigemptyset - 创建空集
sigfillset  - 把所有的信号都添加到集合中(因为信号数目不多)
sigaddset   - 添加指定信号到集合中
sigdelset   - 删除集合中的指定信号

我们可以用下面这段代码来临时阻塞特定的信号:

sigset_t mask, prev_mask;

Sigemptyset(&mask);           # 初始化 set 为空集合
Sigaddset(&mask, SIGINT);     # 把 SIGINT 信号加入 set 中

# 阻塞对应信号,并保存之前的集合作为备份
Sigprocmask(SIG_BLOCK, &mask, &prev_mask);

...
... # 这部分代码不会被 SIGINT 中断
...

# 取消阻塞信号,恢复原来的状态
Sigprocmask(SIG_SETMASK, &prev_mask, NULL);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值