Linux--信号

一.引号的产生

1.什么是信号

在生活中,信号的产生是多种多样的,比如红绿灯,闹钟等等,Linux中的信号是什么呢?

1.在Linux中,信号的本质是一种通信方式,由用户或操作系统发送信号,通知进程,某些事情已经发生,同事进程在合适的时间以合适的方式进行处理。信号本身,可能不会被立即处理,也一定要被保存下来。操作系统可以识别这些信号。

2.结合进程,分析信号

a.进程要分析信号,必须要识别信号即,进程要接收并且要具备其如何处理的动作。

b.进程如何识别信号呢?通过程序员设置的方式让进程去识别。

c.信号的产生是随机的,所以进程的处理方式可能不是立即处理,而是将信号存储起来,在合适的场合处理。

d.进程会降临是信号进行存储,方便之后的处理。

e.一般而言,信号的产生相对于进程来说是异步的。

2.信号的产生

a,通过键盘的组合键产生

比如:通过Ctrl+c终止进程,这就是一种通过键盘向进程发送信号,让进程退出了。

处理信号的常见方式:

默认;忽略;自定义一共有这三种处理方式。

如何理解这些组合键是如何变成信号的?

键盘的工作方式是通过中断的方式进行的,OS解释组合键,然后在进程列表中查找,找到前台运行的进程,OS将信号写入当前的进程结构中的位图中。

常见的信号:

Linux--信号_第1张图片

前31个信号为普通信号,后31个信号为实时信号。

如何理解信号被进程保存

进程必须具有保存信号的相关数据结构的位图,进程PCB内部保存了信号,信号发送的本质是OS向目标进程发送对行的信号。OS修改进程内部PCB所对应的位图结构,完成发送进程的过程。

键盘产生的信号:

signal函数:signal(int signum, sighandler_t handler);

第一个参数为信号,可以是信号的编号,也可以是信号的名称,第二个参数是一个回调函数,通过回调函数处理信号,可以为默认,忽略,自定义。默认和忽略所对应的参数名称为:SIG_DFL,SIG_IDO。自定义的参数需要自己写回调函数去完成。通过回调的方式,去修改对应的信号捕捉方法。

比如:

static void handle(int signam)
{
  std::cout<<"我是一个进程"<

默认处理动作是终止进程并且Core Dump(核心转储)

Core Dump是什么?

当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部 保存到磁盘上,文件名通常是core,这叫做Core Dump。我们可以通过使用gdb调试查看core文件查看进程退出的原因,这也叫事后调试

Core Dump使用演示:
一个进程允许产生多大的core文件取决于进程的Resource Limit,这个信息报错在PCB中。默认情况下,不允许产生core文件,因为core文件中比较大(容易浪费资源),包含用户密码等敏感信息,不安全。我们可以通过命令ulimit -a 查看系统允许我们产生多大的core文件。

Linux--信号_第2张图片

可以发现的是,系统是不允许产生这个core文件的,但是我们可以通过命令ulimit -c size 修改,允许产生size大小的core文件。
下面我们写一个程序验证一下:

int main(int argc,const char* argv[])
{
  signal(2,SIG_DFL);
  if(argc!=3)
  {
    while(true)
     { 
        cout<<"Hello word"<

结果:Linux--信号_第3张图片

core dumped标志是否发生核心转储。主要是为了调试。

可以看到core.2549这个文件很大,所以在一般生产环境下都需要关闭核心转储功能。

b.系统调用接口产生信号

用户调用系统接口,执行OS所对应的代码,OS设置参数,OS向目标进程写入,OS修改对应的位图结构,进程后续执行所对应的信号。介绍三个系统调用函数 kill函数 raise函数 abort函数。

kill函数

给任意进程发送任意信号

#include

int kill(pid_t pid, int sig);

参数:pid 进程pid sig 要发送的信号

raise函数

给进程自己发送信号

#include   
int raise(int sig);  
  • 1
  • 2

参数:

  • sig:要发送的信号

返回值: 成功返回0,失败返回-1
和kill比较:

 raise函数相当于kill(getpid(), sig)  

abort函数

功能: 使用当前进程收到信号而异常终止(发送6号信号)
函数原型:

#include   
void abort(void); 

c.由软件条件产生信号

SIGPIPE是一种由软件条件产生的信号,在“管道”中已经介绍过了。主要介绍alarm函数 和SIGALRM信号。

alarm函数

int main()
{
  //signal(SIGALRM, handler);
  alarm(5);
  //int count=0;
  while(true)
  {
    count++;
    cout<<"count:"<

结果:

Linux--信号_第4张图片

所以:

功能: 设定一个闹钟,操作系统会在闹钟到了时送SIGALRM 信号给进程,默认处理动作是终止进程
函数原型:

#include  
unsigned alarm(unsigned seconds); 
1
2
参数:

second:设置时间,单位是s
返回值: 0或者此前设定的闹钟时间还余下的秒数

d.硬件异常产生

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。
这里给大家介绍两个硬件异常:CPU产生异常 和 MMU产生异常

CPU产生异常 发生除零错误,CPU运行单元会产生异常,内核将这个异常解释为信号,最后OS发送SIGFPE信号给进程。

MMU产生异常: 当进程访问非法地址时,mmu想通过页表映射来将虚拟转换为物理地址,此时发现页表中不存在该虚拟地址,此时会产生异常,然后OS将异常解释为SIGSEGV信号,然后发送给进程。

2.信号的保存

捕捉信号

先思考一个问题:信号是什么时候被处理的

首先,不是立即被处理的。而是在合适的时候,这个合适的时候,具体指的是进程从用户态切换回内核态时进行处理。

  • 用户态: 处于⽤户态的 CPU 只能受限的访问内存,用户的代码,并且不允许访问外围设备,权限比较低。
  • 内核态: 处于内核态的 CPU 可以访问任意的数据,包括外围设备,⽐如⽹卡、硬盘等,权限比较高。
  • 注意: 操作系统中有一个cr寄存器来记录当前进程处于何种状态

进程空间分为用户空间和内核空间。此前我们介绍的页表都是用户级页表,其实还有内核级页表。进程的用户空间是通过用户级页表映射到物理内存上,内核空间是通过内核级页表映射到物理内存上。

进程有不同的用户空间,但是只有一个内核空间,不同进程的用户空间的代码和数据是不一样的,但是内核空间的代码和数据是一样的。
上面这些主要是想说:进程处于用户态访问的是用户空间的代码和数据,进程处于内核态,访问的是内核空间的代码和数据。

信号处理函数:

sigaction函数:

功能:可以读取和修改与指定信号相关联的处理动作
函数原型:

#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

signum: 要操作的信号

act:一个结构体

sa_handler:SIG_DFT、SIG_IGN和handler(用户自定义处理函数)
sa_sigaction:实时信号处理的函数,我们不关心
sa_mask:一个信号屏蔽字,里面有需要额外屏蔽的的信号
sa_flags:包含一下选项,这里我们给0
sa_restorer:我们这里不使用。

结构体如下:

struct sigaction {
	void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

返回值: 成功返回0,失败返回-1

3.信号的处理

你可能感兴趣的:(linux,运维,服务器)