Linux0.11信号处理详解
之前在看操作系统信号这一章的时候,一直是云里雾里的,不知道信号到底是个啥玩意儿。。比如在看《Unix环境高级编程》时,就感觉信号是个挺神奇的东西。比如看到下面这段代码:
#include<signal.h>
#include<stdio.h>
#include<unistd.h>
void handler(int sig)
{
printf("The signal is %d\n",sig);
}
int main()
{
(void) signal(SIGINT,handler);
while(1)
{
printf("Signal test.\n");
sleep(1);
}
}
运行这个程序,当我在屏幕敲入ctrl+c时:
那个SIGINT就是一个信号,表示来自键盘的中断,用ctrl+c产生。下面我就来详细分析Linux0.11内核对信号是怎么处理的。
在内核中用一个无符号的长整数(32位)中的比特位来表示各种不同信号,在task_struct中有一个变量signal,这个就是信号位图,里面每一个比特位代表一个信号。在Linux0.11版本中定义了22种不同的信号。定义在linux/include/signal.h中:
当一个进程接收到一个信号时,有三种不同的处理或操作方式。(1)忽略该信号。但有两个信号不能被忽略掉:SIGKILL和SIGSTOP。(2)执行默认操作。内核为每种信号都提供了一种默认信号,通常的默认操作就是终止进程的执行。(3)捕获该信号。我们可以在这里自定义信号处理函数,来以我们喜欢方式处理信号。
那么,我们如何来定义信号处理函数呢?
(我们这里不讨论可靠信号和不可靠信号,在Linux中1-31是不可靠信号,32-64是可靠信号)
可以通过signal()函数或sigaction()函数。其中signal()的声明如下:
void (*signal(int signr,void(*handler)(int)))(int);
signr代表需要捕获的信号,handler代表信号处理函数指针。signal()函数有一个特点:在新句柄(处理函数)被调用执行过一次后,信号处理句柄又会恢复成默认处理句柄。
sigaction()函数采用了sigaction数据结构来保存指定的信号信息。在task_struct中就有struct sigaction sigaction[32]这一字段,这是一个sigaction数组,对应task_struct中的signal信号位图每一个信号的处理方式。sigaction结构如下:
struct sigaction{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
那个sa_handler就是信号处理句柄,sa_mask表示信号屏蔽码,代表在处理这一信号时需要屏蔽的信号,sa_flags用于指定一些处理信号的选项,定义在include/signal.h:
SA_NOCLDSTOP表示进程处于停止状态,不对信号做处理,SA_NOMASK表示对当前信号不进行屏蔽,即这个时候可以运行信号嵌套。SA_ONESHOT指明信号处理函数一旦被调用过就恢复到默认的信号处理函数去。
sigaction()函数的声明为:
int sigaction(
int sig,
struct sigaction *act,
struct sigaction *oldact
);
sig表示指定的信号,如果act不是NULL时,就把act作为信号sig新的处理方式。当oldact不为空时,oldact返回该信号原来的处理方式。
介绍完了有关信号的基本知识,下面我们来具体看看信号时如何被处理的。
首先,你要明白:信号时异步的。进程本身也不知道信号什么时候到达,因此它不必通过什么操作来等待信号的到达。那么进程什么时候处理信号呢?一般是在系统调用发生后,从系统空间返回到用户空间前夕。
我们在系统调用详解那一篇中讲到系统调用的大致实现过程,在系统调用返回之前内核会去检查进程是否有需要处理的信号。System_call.s中的109到119代码如下:
109表示将当前进程的信号位图放入%ebx中,110表示将当前信号的阻塞位图放入%ecx中,111中将阻塞信号位图每位取反,然后通过112获取许可的信号位图,113从低位开始扫描位图,看是否有1,如果有则ecx保留该位的偏移值(0-31),114如果没有则跳出。115复位该信号,116重新保存signal位图信息,117将信号调整为从1开始的数(1=32),然后通过118将信号值入栈作为调用do_signal的参数之一。最后119调用信号处理程序。
为了方便理解,在介绍do_signal()函数之前,我们先来大致了解一下执行信号处理时内核堆栈和用户态堆栈所发生的一些变化,这对理解整个信号处理有好处。
我们知道,当进程在执行系统调用时,它会陷入内核态,对应每个进程都有一个内核堆栈和一个用户堆栈,当进程陷入内核态时,它会在内核堆栈保存原来的用户堆栈寄存器的SS和esp,以及代码寄存器cs和eip,其中eip中保存着用户进程的下一个指令。接着把原来的数据段寄存器ds、es、fs放入堆栈。然后把edx、ecx、ebx入栈,这些寄存器通常用来放系统调用的参数,至于系统调用的返回值则放在eax中。记住:上面这些寄存器的值将来都是要恢复的。当系统调用结束后,这些寄存器将依次出栈以使得进程回到用户态的执行流中。
那么如果要进行信号处理,堆栈会发生什么变化呢?当系统调用处理函数运行结束后,内核开始检查该进程有没有需要处理的信号,如果有,则调用do_signal()函数,那么我们来看看do_signal()这个函数到底做了什么。do_signal()函数定义在Kernerl/Signal.c中。
首先,它把eip的值保存在old_eip中(后面会解释为什么),然后它找到所要处理的信号对应的sigaction结构(第89行),并找到对应的处理句柄,接下来,94—101行检查该信号是不是默认处理方式或者忽略处理方式。第102和103检查是不是SA_ONESHOT处理方式。接下来的从104行开始比较难理解,它让eip指向信号处理句柄地址,如图所示,也就是说此时eip不再指向用户进程中系统调用后的下一条指令了。接下来的代码,将内核堆栈中的eflags,edx,ecx,eax(系统调用返回值)入栈,同时包括old_eip,[blocked],signr,sa_restorer也入栈(记住这里是用户空间堆栈,而不是内核堆栈)。这个时候do_signal()函数的使命就完成了。那么为什么要将这些寄存器入栈呢?当so_signal()执行完成后,System_call.s中的接下来代码是这样的:
121—127将相应的寄存器出栈(恢复用户空间的现场),然后通过iret指令将cs,eip以及ss,esp等出栈,至此,进程就进入了用户空间运行,由于之前eip已经指向了信号处理程序,于是接下来,信号处理程序将被执行。那么为什么偏偏要在do_signal中把edx,ecx等入栈,而其他几个寄存器并不入栈呢?那是因为在信号处理程序中需要用到eax,ecx,edx等寄存器,因此需要将这些寄存器的值保存起来,将要再“恢复”一次。信号处理程序执行完成后,CPU会通过ret指令把控制器移交给sa_restorer恢复程序去执行,我们就以没有blocked的restorer函数为例:
你看这个sa_restorer程序就是做一些用户堆栈的清理工作的,将原先入栈的eax,ecx,edx等等出栈,然后通过ret将old_eip恢复到eip中,这样就彻底恢复了原先的进程执行环境了。
Linux0.11信号处理详解的更多相关文章
- 【小白学PyTorch】11 MobileNet详解及PyTorch实现
文章来自微信公众号[机器学习炼丹术].我是炼丹兄,欢迎加我微信好友交流学习:cyx645016617. @ 目录 1 背景 2 深度可分离卷积 2.2 一般卷积计算量 2.2 深度可分离卷积计算量 2 ...
- Acunetix 11 配置详解
Acunetix 扫描配置 Full Scan– 使用Full Scan来发起一个扫描的话,Acunetix会检查所有可能得安全漏洞. High Rish Vulnerabilities–这个扫描选项 ...
- Java容器解析系列(11) HashMap 详解
本篇我们来介绍一个最常用的Map结构--HashMap 关于HashMap,关于其基本原理,网上对其进行讲解的博客非常多,且很多都写的比较好,所以.... 这里直接贴上地址: 关于hash算法: Ha ...
- Jmeter 常用函数(11)- 详解 __TestPlanName
如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.html 作用 返回测试计划名称 语法格式 ${__T ...
- Appium自动化(11) - 详解 Applications 类里的方法和源码解析
如果你还想从头学起Appium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1693896.html 前言 Applications 类 ...
- 2020了你还不会Java8新特性?(五)收集器比较器用法详解及源码剖析
收集器用法详解与多级分组和分区 为什么在collectors类中定义一个静态内部类? static class CollectorImpl<T, A, R> implements Coll ...
- C++11 并发指南三(Lock 详解)
在 <C++11 并发指南三(std::mutex 详解)>一文中我们主要介绍了 C++11 标准中的互斥量(Mutex),并简单介绍了一下两种锁类型.本节将详细介绍一下 C++11 标准 ...
- C++11 并发指南六(atomic 类型详解四 C 风格原子操作介绍)
前面三篇文章<C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)>.<C++11 并发指南六( <atomic> 类型详解二 std::at ...
- C++11 并发指南六(atomic 类型详解三 std::atomic (续))
C++11 并发指南六( <atomic> 类型详解二 std::atomic ) 介绍了基本的原子类型 std::atomic 的用法,本节我会给大家介绍C++11 标准库中的 std: ...
随机推荐
- mouseleave mouseout时候悬浮框不应该消失的时候消失了 css 解决办法
要实现的效果和代码思路 简单来说就是 用一个div包着喇叭和悬浮框 悬浮事件写在这个div上 鼠标悬浮到div上的时候 悬浮框出现 最终要做成鼠标从小喇叭移动到下面的框上的时候 下面框是不会消失的. ...
- 【读书笔记】《深入浅出nodejs》第三章 异步I/O
1. 为什么要异步I/O (1)用户体验上: 并发的优势: M+N+... -> max(M,N,...) --> 使后端能够快速的响应资源 *并发的劣势:... (2)资源分配: 单线 ...
- Oracle修改表结构
--添加字段 alter table [tablename] add [column name] [column data type]; --修改字段数据类型 alter table [tablen ...
- [BZOJ1058]报表统计
Description 小Q的妈妈是一个出纳,经常需要做一些统计报表的工作.今天是妈妈的生日,小Q希望可以帮妈妈分担一些工 作,作为她的生日礼物之一.经过仔细观察,小Q发现统计一张报表实际上是维护一个 ...
- eclipse 工程没有build path
项目的.project文件添加: <buildSpec><buildCommand><name>org.eclipse.jdt.core.javabuilder&l ...
- kylin入门到实战:cube详述
版权申明:转载请注明出处. 文章来源:http://bigdataer.net/?p=306 排版乱?请移步原文获得更好的阅读体验 1.什么是cube? cube是所有dimession的组合,每一种 ...
- OKR学习总结
OKR学习总结 背景:因为公司最近采用OKR工作法,所以来了解一下. 简介 OKR ——Object Key Results 主要分为两部分:O 和 KR ,就是目标和关键结果. 将这个丰满点描述,就 ...
- 【Semantic segmentation】Fully Convolutional Networks for Semantic Segmentation 论文解析
目录 0. 论文链接 1. 概述 2. Adapting classifiers for dense prediction 3. upsampling 3.1 Shift-and-stitch 3.2 ...
- 2017 beijing icpc A - Euler theorem
2017-09-22 21:59:43 writer:pprp HazelFan is given two positive integers a,ba,b, and he wants to calc ...
- linux一键安装mysql脚本
#!/bin/sh if [ -s /etc/my.cnf ];then rm -rf /etc/my.cnf fi echo "------------------------------ ...