(已转)Linux基础第六章 信号
6.1 前言
本章简单描述信号。信号是Linux系统中,内核和进程通信的一种方式。如果内核希望打断进程的顺序执行,那么内核会在进程的PCB中记录信号。而当这个进程被分配到CPU,进入执行状态时,首先会检查是否有信号,如果有信号,那么进程会先尝试执行信号处理函数。
内核需要打断进程运行的时机:
进程无法继续了
按下ctrl+c
ctrl+c其实是bash向前台进程组发送SIGINT
运行该程序后,再按Ctrl+c,结果是四个进程全部退出
有了signal的处理之后,ctrl+c发送的SIGINT不会导致进程退出。
6.2 信号类型
通过kill -l命令可以看到系统定义的信号类型,信号值小于32的是传统的信号,称之为非实时信号,而大于32的称之为实时信号。这里只讨论非实时信号。
6.3 信号的处理
可以通过signal函数,注册信号处理函数。如果没有注册信号处理函数,那么按照默认方式处理。
也可以通过signal设置忽略信号。
| 信号 | 默认处理动作 | 发出信号的原因 |
|---|---|---|
| SIGHUP | A | 进程session leader退出时,同session的其他进程会收到这个信号 |
| SIGINT | A | Ctrl+C |
| SIGQUIT | C | Ctrl+D |
| SIGILL | C | 非法指令 |
| SIGABRT | C | 调用abort函数产生的信号 |
| SIGFPE | C | 浮点异常 |
| SIGKILL | AEF | Kill信号 |
| SIGSEGV | C | 无效的内存引用 |
| SIGPIPE | A | 管道破裂: 写一个没有读端口的管道 |
| SIGALRM | A | 由alarm(2)发出的信号 |
| SIGTERM | A | 终止信号 |
| SIGUSR1 | A | 用户自定义信号1 |
| SIGUSR2 | A | 用户自定义信号2 |
| SIGCHLD | B | 子进程状态变化会给父进程发送SIGCHLD信号 |
| SIGCONT | 进程继续(曾被停止的进程) | |
| SIGSTOP | DEF | 暂停进程 |
| SIGTSTP | D | 控制终端(tty)上按下停止键 |
| SIGTTIN | D | 后台进程企图从控制终端读 |
| SIGTTOU | D | 后台进程企图从控制终端写 |
A 缺省的动作是终止进程
B 缺省的动作是忽略此信号
C 缺省的动作是终止进程并进行内核映像转储(dump core)
D 缺省的动作是停止进程
E 信号不能被捕获
F 信号不能被忽略
#include <stdio.h>
#include <signal.h>
#include <stdlib.h> void signal_handle(int a)
{
if(a == SIGINT)
printf("signal_handle\n");
else if(a == SIGABRT)
printf("abrt\n");
else if(a == SIGALRM)
printf("alarm\n");
else if(a == SIGCHLD)
printf("child\n");
else if(a == SIGUSR1)
printf("usr1 signal\n");
} int main()
{
// SIGINT 2
signal(SIGINT, signal_handle);
signal(SIGABRT, signal_handle);
signal(SIGALRM, signal_handle);
signal(SIGCHLD, signal_handle);
signal(SIGUSR1, signal_handle); pid_t pid = fork();
if(pid == 0)
return 0; // 给自己发送一个abrt信号
//abort();
alarm(1); while(1)
{
sleep(1);
}
}
6.4 不可靠信号
信号值小于32的都是不可靠信号,假如进程收到一个信号来不及处理,这时候又收到一个同样的信号,那么这两个信号会合并成一个信号,这个原因是因为进程保存该信号的值只有1位。
6.5 中断系统调用(中断阻塞)
假如一个进程调用了某系统调用导致该进行处于挂起状态,而此时该进程接收到一个信号,那么该系统调用被唤醒。通常该系统调用会返回-1,错误码是EINTR。
也有些系统调用,可以设置打断后重启,这样就不会被信号打断,具体参考man 7 signal
如果使用signal函数注册信号处理函数,默认被中断的系统调用是自动重启的。
#include <signal.h>
#include <stdio.h>
#include <errno.h>
void handle(int v){
printf("ctrl+c\n");
} int main()
{
signal(SIGINT, handle); char buf;
int ret = read(0, buf, sizeof(buf)); // read被中断打断了
printf("ret = %d, errno=%d, EINTR=%d\n", ret, errno, EINTR); // EINTR
}
6.6 可重入问题
信号会导致可重入问题,比如一个全局链表。
以上代码在一定情况下会崩溃,在main函数中不停调用push_back,如果在push_back执行一半时,被中断打断,然后去执行中断处理函数时,那么该中断处理函数的push_back会崩溃。
有些系统调用本身带有局部静态变量,因此那些函数不能在信号处理函数中调用,比如strtok,readdir等,对应的可重入的函数是strtok_r,readdir_r。
6.7 发送信号
可以通过kill函数发送信号。
kill也可以进程组发送信号
#include <sys/types.h>
#include <signal.h> int main()
{
kill(27054, SIGUSR1);
}
补充:掩盖信号
//掩盖不可靠信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
// 掩盖SIGINT
// 掩盖一个可靠信号
void handle(int v)
{
printf("sigint \n");
} int main()
{
signal(SIGINT, handle);
sigset_t set; // 将set集合设置为空
sigemptyset(&set);
// 将SIGINT信号加入集合
sigaddset(&set, SIGINT);
// 把这个集合代表的信号,加入信号掩码
sigprocmask(SIG_BLOCK, &set, NULL); // 从此,该进程收到SIGINT,不会被处理 sleep(5);
printf("remove SIGINT from mask\n"); // 去掉SIGINT的掩码
sigprocmask(SIG_UNBLOCK, &set, NULL); while(1)
{
sleep(1);
}
}
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
// 掩盖34
// 掩盖一个可靠信号
void handle(int v)
{
printf("hahahaha \n");
} int main()
{
signal(34, handle);
sigset_t set; // 将set集合设置为空
sigemptyset(&set);
// 将34信号加入集合
sigaddset(&set, 34);
// 把这个集合代表的信号,加入信号掩码
sigprocmask(SIG_BLOCK, &set, NULL); // 从此,该进程收到34,不会被处理 kill(getpid(), 34);
kill(getpid(), 34);
kill(getpid(), 34);
kill(getpid(), 34);
kill(getpid(), 34); sleep(5);
printf("remove 34 from mask\n"); // 去掉34的掩码
sigprocmask(SIG_UNBLOCK, &set, NULL); while(1)
{
sleep(1);
}
}
6.8 忽略信号
#include <signal.h> void handle(int v)
{ } int main()
{
// 表示忽略SIGINT
// 忽略信号和掩盖信号是有区别:
//
// 未决的信号:已经发出但是没有被处理的信号叫未决的信号
signal(SIGINT, SIG_IGN); // 从这里开始就不忽略
signal(SIGINT, handle); // 设置一个处理函数
signal(SIGINT, SIG_DFL); // 恢复成默认处理
while(1)
{
sleep(1);
}
}
以上例子,忽略SIGPIPE信号,那么进程收到SIGPIPE后,不会有任何反应。
6.9 屏蔽信号
屏蔽和忽略不同,忽略意味着在忽略期间,接收的信号就被忽略了。而屏蔽的意思,是暂时屏蔽,屏蔽期间收到的信号依旧在,如果某一时刻该信号不再忽略时,该信号的处理程序会被调用。
设置屏蔽集合,使用sigprocmask
6.10 SIGCHLD
SIGCHLD信号产生于子进程退出和状态变化,父进程通常在该信号的处理函数中,调用wait来回收子进程的PCB,这样可以避免阻塞。
#include <signal.h> void chld_handle(int v)
{
// 有子进程退出了
// wait(NULL);
//
while(1) // 使用while(1)是避免有多个子进程同时退出,由于SIGCHLD是不可靠信号,函数只会调用一次
{
int ret = waitpid(-1, NULL, WNOHANG); // 每次回收都应该用非阻塞方式去回收
if(ret == -1) // 没有僵尸进程了,之前僵尸进程已经被回收了
break;
}
} int main()
{
// 等待子进程的结束,问题是:ddddd
// 它阻塞主进程的执行,影响效率
// wait(NULL);
//
signal(SIGCHLD, chld_handle); pid_t pid = fork();
if(pid == 0) return 0; while(1)
{
sleep(1);
}
}
6.11 sigaction和sigqueue
sigaction和signal一样用来注册信号处理函数,siqqueue和kill一样,用来发送信号,但是sigaction比signal功能强大,signal比较简单。
强大:
可以传递参数
可以获得发送信号的进程信息
可以设置SA_RESTART
#include <signal.h>
#include <stdio.h>
#include <errno.h>
//void handle(int v){}
//
// 新的信号处理函数
void handle(int v, siginfo_t* info, void* p)
{
printf("ctrl+c\n");
}
int main()
{
// 默认的signal已经有SA_RESTART这个flag了
//signal(SIGINT, handle); struct sigaction sig;
sig.sa_handler = NULL;
sig.sa_sigaction = handle;
sigemptyset(&sig.sa_mask);
// sig.sa_flags = 0;
sig.sa_flags = SA_RESTART; // 让阻塞的系统调用,被这个信号打断之后,要重启
sig.sa_restorer = NULL; // 在Linux下没用,直接填NULL就可以了
sigaction(SIGINT, &sig, NULL); char buf;
int ret = read(0, buf, sizeof(buf)); // read被中断打断了
printf("ret = %d, errno=%d, EINTR=%d\n", ret, errno, EINTR); // EINTR
}#include <stdio.h>
#include <stdlib.h>
#include <signal.h> char buf[1024]; void handle_data()
{
printf("user input is %s\n", buf);
} void handle_chld1(int v)
{
while(1)
{
int ret = waitpid(-1, NULL, WNOHANG);
if(ret < 0) break;
}
} void handle_chld(int v, siginfo_t* info, void* p)
{
handle_chld1(v);
} int main()
{
// signal(SIGCHLD, handle_chld); struct sigaction act;
act.sa_handler = NULL;
act.sa_sigaction = handle_chld;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_restorer = NULL;
sigaction(SIGCHLD, &act, NULL); while(1)
{
// char* ret = fgets(buf, sizeof(buf), stdin);
// if(ret == NULL) break; int ret = read(0, buf, sizeof(buf));
if(ret < 0)
{
// 说明read出错,不是真正的出错,而是被中断打扰了
// 那此时,应该重新调用read函数,去获取信息
if(errno == EINTR)
{
continue;
}
// 如果是其他错误原因,就break
break;
} pid_t pid = fork();
if(pid == 0)
{
handle_data(); // 创建一个子进程去处理数据
exit(0);
}
}
}#include <signal.h>
#include <stdio.h>
void handle(int v, siginfo_t* info, void* p)
{
printf("recv SIGINT, arg=%d\n", info->si_value.sival_int);
} int main()
{
struct sigaction act;
act.sa_handler = NULL;
act.sa_sigaction = handle;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO|SA_RESTART;
act.sa_restorer = NULL; // 注册信号处理函数
sigaction(SIGINT, &act, NULL); union sigval v;
v.sival_int = 99;
// 相当于kill,但是它可以传递一个参数
sigqueue(getpid(), SIGINT, v); getchar();
}#include <signal.h>
#include <stdio.h> int main()
{
char* p = malloc(100);
union sigval v;
// v.sival_int = 98;
v.sival_ptr = p;
// 相当于kill,但是它可以传递一个参数
sigqueue(28360, SIGINT, v); getchar();
}
6.12 函数命令
signal:注册信号处理函数
kill:发送信号
sigprocmask:设置信号掩码
sigemptyset:清空信号集
sigfillset:设满信号集
sigaddset:往信号集增加一个信号
sigdelset:从信号集删除一个信号
sigismember:判断信号否则在信号集
sigaction:注册更加强大的处理函数
sigqueue:发送信号
abort
alarm
pause
(已转)Linux基础第六章 信号的更多相关文章
- Linux 笔记 - 第六章 Linux 磁盘管理
博客地址:http://www.moonxy.com 一.前言 1.1 硬盘 硬盘一般分为 IDE 硬盘.SCSI 硬盘和 SATA 硬盘.在 Linux 中,IDE 接口的设备被称为 hd,SCSI ...
- Linux基础入门 第二章 Linux终端和shell
Linux终端 进入编辑IP地址命令:vi /etc/sysconfig/network-scripts/ifcfg-eth0 按键“i”:进行编辑 按键“ESC”:退出编辑 按键“:”:输入wq, ...
- linux基础-第六单元 用户、群组和权限
用户及passwd文件 /etc/passwd文件的功能 /etc/passwd文件每个字段的具体含义 shadow文件 /etc/shadow文件的功能 /etc/shadow文件每个字段的具体含义 ...
- java基础(六章)
一.for循环的使用场合 l while循环--先判断,再循环 while(1.条件表达式){ //2.循环操作 //3.更改循环条件表达式 } l do-while--先循环 ...
- Linux基础入门 第一章:Linux环境搭建——Redhat 6.4图文安装教程
1.创建新的虚拟机 2.选择自定义 3.选择Workstation 10.0 4.选择稍后安装操作系统 5.选择Red Hat 6 64位 6.对虚拟机命名和选择安装位置 7.选择处理器配置 8.选择 ...
- linux基础(六)
今天我们来看一下Samba服务和nginx服务. Samba服务 1.samba的功能 samba是一个网络服务器,用于Linux和Windows之间共享文件. 2.samba服务的启动.停止.重启 ...
- java基础 第六章课后习题
1.说明循环结构中break语句和continue语句的区别. 在循环结构中 break语句 是结束程序运行. continue语句是结束本句程序 不是结束程序. 2.使用for循环结构实现,从键盘 ...
- Linux基础第六课——grep|awk|sort|uniq
管道符 | 前面的输出作为后面的输入 grep 可以理解为正则表达式 grep [参数] 文件名 -c 打印符合要求的行数 -v 打印不符合要求的行 -n 在输出符合要求的行的同时连同行号一起输出 - ...
- C语言基础-第六章
数组和字符串 1.一维数组 数组当中最简单的数据 声明: 类型说明符 数组名[常量表达式] int a[3];说明a的长度为3,那么给a赋值的语句是:a={1,2,3}; 2.多维数组 2.1 二维数 ...
- Linux基础篇六:Linux文件属性和类型
-:代表文件 s: sorket文件 b:block块设备 (磁盘,光驱等) c:字符设备 l:连接文件 p:管道文件 d:代表目录文件 为了更加区分- (文件的具体类型),系统提供了file命令更加 ...
随机推荐
- [CG从零开始] 3. 安装 pyassimp 库加载模型文件
assimp 是一个开源的模型加载库,支持非常多的格式,还有许多语言的 binding,这里我们选用 assimp 的 python 的 binding 来加载模型文件.不过社区主要是在维护 assi ...
- SpringBoot课程学习(一)
@SpringBootTest指定测试的启动类 声明@SpringBootTest @Test注解 @Test 指定测试方法 @Order排序 一:先声明排序模式 @TestMethodOrder(M ...
- Java注解(1):码农的小秘
很多码农在写代码的时候不太爱写注释,结果任务一多,时间一长,需求一改,就完全不知道当初自己都干了些啥了.好在现在大多数编程语言都有注释功能,能够在代码里面做一些备注,不至于时间长了忘掉.但这些注释只是 ...
- Ubuntu 环境下安装 Docker
系统要求 Docker目前只能运行在64位平台上,并且要求内核版本不低于3.10,实际上内核越新越好,过低的内核版本容易造成功能不稳定. 用户可以通过如下命令检查自己的内核版本详细信息: $ unam ...
- 规则引擎深度对比,LiteFlow vs Drools!
前言 Drools是一款老牌的java规则引擎框架,早在十几年前,我刚工作的时候,曾在一家第三方支付企业工作.在核心的支付路由层面我记得就是用Drools来做的. 难能可贵的是,Drools这个项目在 ...
- MIPI-DSI协议
MIPI联盟,即移动产业处理器接口(Mobile Industry Processor Interface 简称MIPI)联盟.MIPI(移动产业处理器接口)是MIPI联盟发起的为移动应用处理器制定的 ...
- SpringCloud微服务实战——搭建企业级开发框架(四十八):【移动开发】整合uni-app搭建移动端快速开发框架-使用第三方UI框架
uni-app默认使用uni-ui全端兼容的.高性能UI框架,在我们开发过程中可以满足大部分的需求了,并且如果是为了兼容性,还是强烈建议使用uni-ui作为UI框架使用. 如果作为初创公司,自 ...
- gin框架——使用viper读取配置
什么是viper Viper是Go应用程序的完整配置解决方案,包括12-Factor(也称为"十二要素",是一套流行的应用程序开发原则. 其实我也不是很清楚)应用程序.它被设计为在 ...
- 关于图计算&图学习的基础知识概览:前置知识点学习(Paddle Graph Learning (PGL))
关于图计算&图学习的基础知识概览:前置知识点学习(Paddle Graph Learning (PGL)) 欢迎fork本项目原始链接:关于图计算&图学习的基础知识概览:前置知识点学习 ...
- ui自动化测试数据复原遇到的坑——2、python连接informix时pytest报致命错误Windows fatal exception: access violation
python连接informix只能通过jdbc(需要先部署java环境.我试过到IBM上下载ODBC但结局是失败的),在执行pytest时发现有一串报错(大致是下面的这样): Windows fat ...