Linux下“进程信号”相关内容整理分析

1. 掌握Linux信号的基本概念

在这里插入图片描述

2. 掌握信号产生的一般方式

第一种 键盘产生

在这里插入图片描述
证明:
键盘ctrl+c 的时候,本质是我们向指定进程发送2号信号!

#include<stdio.h>    
#include<unistd.h>    
#include<signal.h>    
#include<stdlib.h>   
    
void handler(int signal)   
{    
  printf("get a signal :signal no:%d pid:%d\n ",signal,getpid());    
  exit(1);                
}         

int main()                     
{  
  //通过signal注册对2号信号的处理动作,改成我们的自定义动作 
  signal(2,handler);  
      
  while(1){       
    printf("hello world!,pid: %d\n",getpid());     
    sleep(1);    
  }                 
  return 0;
} 

在这里插入图片描述
信号的产生方式其中一种就是通过键盘产生(键盘产生的信号,只能用来终止前台进程)

总结:
一般而言,进程收到信号的处理方案有三种情况

  1. 默认动作 — 一部分是终止自己,暂停等
  2. 忽略动作 — 是一种信号处理的方式,只不过动作就是什么也不干
  3. (信号的捕捉)自定义动作 — 我们刚刚用signal方法,就是再修改信号的处理动作,由:默认-》自定义动作

第二种 进程异常,也能产生信号

证明:
core dump是,并获取?
在这里插入图片描述

补充:
关于core dump
当你在重新启动进程,或者新开窗口至当前文件,等,,,
你的ulimit -c 会被重新置为0
此时需要你ulimit -c 10240 重新设置大小

第三种 通过系统调用,产生信号

在这里插入图片描述
实验:

在这里插入图片描述
采用系统调用,向目标进程发送信号

延伸:
int kill(目标进程pid,信号)
int raise(给自己发送信号)
成功0 错误-1
在这里插入图片描述

延伸:
abort()
给自己发送6 号信号,并终止进程

第四种 软件条件,也能产生信号

通过某种软件(OS),来触发信号的发送,系统层面设置定时器,或者某种操作而导致条件不就绪等这样的场景下,触发的信号发送
例如:
进程间通信:当读端不光不读,而且还关闭了读文件描述符,写端一直在写,最终写进程会收到sigpipe信号(13),就是一种典型的软件条件触发的信号发送

介绍接口alarm:
在这里插入图片描述
通过调用 alarm,告诉系统 seconds秒后给我发一个14号信号,
你设置了seconds秒后提醒你,,但是由于别的原因,OS给你了一个14号信号,此时这个函数返回二者差值

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

在这里插入图片描述
count++:
只在cpu上运行,所以速度块
printf在IO上运行,所以慢

所以当我们的程序出现大量IO时,要考虑效率问题

总结:

信号产生的方式种类虽然非常多,但是无论产生信号的方式千差万别,但是最终,一定都是通过OS向目标进程发送的信号!!!

产生信号的方式,本质都是OS发送的!

如何理解OS给进程发送信号?
目前我们理解:OS 发送信号数据给task_struct
在这里插入图片描述

在这里插入图片描述

3. 理解信号递达和阻塞的概念,原理。

1.信号其他相关常见概念

在这里插入图片描述

2. 在内核中的表示

在这里插入图片描述

block pending bandler
在这里插入图片描述

推出:进程是可以识别信号的

pending:保存的是,已经收到信号,但是还没有被递达的信号
OS发送信号的本质:修改目标位置的pending 位图
衍生:block:状态位图,表示那些信号不应该被递达,直到接触阻塞!
handler:函数指针数组,【31】,每个信号的编号就是该数组的下标

3. sigset_t

每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。
因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,
这个类型可以表示每个信号的“有效”或“无效”状态,
在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,
而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态
阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略

虽然sigset_t是一个位图结构,但是不同的OS实现是不一样的,不能让用户直接修改该变量
需要使用特定的函数
set是一个变量,该变量在什么地方保存?
和我们之前用到的int,double,没有任何差别,都是在用户栈上
sigset_t set;

4. 信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

sigprocmask

修改的是进程的block位图

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

在这里插入图片描述

在这里插入图片描述
结论:
在我们的信号中,9号信号,管理员信号,不能被屏蔽的,不能被自定义捕捉,必须永远遵守默认行为

sigpending

不对pending位图做修改,而只是单纯的获取进程的pending位图
在这里插入图片描述

问题:
pending位图谁修改?OS

测试:

先屏蔽2号信号
不断地获取当前进程的pending位图,并打印显示
然后手动发送2号信号,因为2号信号不会被递达
所以,不断地获取当前进程的pending位图,并打印显示

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

void show_pending(sigset_t *set)
{
  int i = 1;//有些linux下不支持在for里面定义变量
  printf("curr process pending(pending位图):");
  for(;i<=31;i++)
  {
    if(sigismember(set,i)){
      printf("1");
    }
    else printf("0");
  }
  printf("\n");
}

int main()
{
  sigset_t iset,oset; 
  
  sigemptyset(&iset);
  sigemptyset(&oset);

  sigaddset(&iset,2);

  sigprocmask(SIG_SETMASK,&iset,&oset);

  sigset_t pending;
  while(1)
  {
    sigemptyset(&pending);

    sigpending(&pending);
    show_pending(&pending);
    sleep(1);
  }
  return 0;
}     

代码描述;
执行之后,会每间隔一秒打印一长串数组(pending位图)
当你在另一个窗口发送 kill -2 程序
pengding位图中第二个数字就会变为1
在这里插入图片描述

4. 掌握信号捕捉的一般方式。

信号发送后,不是被立即处理的,而是在合适的时候。
“合适”是什么时候?
为什么是“合适的时候”?
信号的产生是异步的,当前进程可能正在做更重要的事情,
信号延时处理(取决于OS和进程)

信号什么时候被处理?
因为信号是被保存在进程的PCB中,pending位图里面,处理(检测,递达(默认,忽略,自定义))
当进程从内核态 返回 到 用户态 的时候,进行上面的检测并处理工作!

内核态和用户态

感性认识:

内核态:执行OS的代码和数据时,计算机所处的状态就叫做内核态。OS的代码的执行全部都是在内核态!
用户态:就是用户代码和数据被访问或者执行的时候,所处的状态,我们自己写的代码全部都是在用户态执行的!
主要区别:在于权限

系统调用:open函数的实现
用户调用系统函数的时候,除了进入函数,身份也会发生变化,用户身份变成内核身份


较为理性的认识

用户的身份是以进程为代表的

用户的数据和代码一定要被加载到内存
那么OS的数据和代码呢??也是一定要被加载内存中的!
OS的代码是怎么被执行的呢??只有一个CPU!

进程具有了地址空间是能够看到用户和内核的所有内容的,不一定能访问

用户使用的是,用户级页表,只能访问用户数据和代码
内核态使用的是内核级页表,只能访问内核级的数据和代码

CPU有寄存器保存了当前进程的状态

进程之间无论如何切换,我们能够保证我们一定能够找到同一个OS,因为我们每个进程都有3-4G的地址空间,使用同一张内核页表。
所谓的系统调用:就是进程的身份转化称为内核,然后根据内核页表找到对应的系统函数,执行就行
在大部分情况下,实际上我们OS都是可以在进程的上下文中直接运行的
在这里插入图片描述


在这里插入图片描述

默认:
终止进程,将进程相关资源全部释放掉
暂停, 将进程状态设置为stop,将进程PCB放到等待队列中
忽略:
将pending由1变为0,然后直接返回至下一行
自定义捕捉:(见上下图线)
在这里插入图片描述
问题:
为何一定要切换成为用户态,才能执行信号捕捉方法?
OS能否直接执行用户的代码呢?理论是可以的
OS不相信任何人!OS因为身份特殊,不能直接执行用户的代码!

总结:
什么是“合适”的时候?
从内核切换回用户态的时候,进行信号检测与信号的处理!

信号捕捉方式

1.signal
在这里插入图片描述
2.sigaction(和signal作用是一摸一样的)
在这里插入图片描述
修改的是handler函数指针数组
在这里插入图片描述
也可以直接对
act.sa_handler=SIG_IGN;忽略
SIG_DFL默认-----如果收到二号信号就会直接终止

上述代码是将2号信号进行自定义为 输出一段话
我们还可以顺便将其他的信号 自定义为这个同类型的
在这里插入图片描述
18:初始化act
19:给act的sa_mask添加3号信号

结果:
3号信号和我们修改后的2号信号将会一样

延伸:
我们将2号信号屏蔽了,然后给进程发送100个二号信号,此时我们的进程只能记住一个。
所以:我们的linux本身普通信号是可能丢失的,这里的丢失指的是2个以上的会被丢失,
实时信号是不可能丢失的,实时信号本身在linux内核中使用链表队列的形式将所有的实时信号,结构体链接到队列中,来一个链接一个,所以不会丢失,本质上还是底层数据结构的差别。

5. 重新了解可重入函数的概念。

在这里插入图片描述
此时node2:节点丢失/内存泄漏
这种现象是:insert函数被重复进入了!
insert函数一旦重入,有可能出现问题—该函数不可被重入
insert函数一旦重入,不会出现问题—该函数可被重入
即:重入或者不可重入是用来描述函数的特点的
我们所学的大部分函数,STL,boost库中的函数,大部分都是不可重入的!

volatile

在这里插入图片描述

我们发现编译器它本身在经过一定的优化的时候,会对同一份代码展现出来了不同的结果。
首先,我们不能因为这一份代码而认为编译器不应该进行优化,或者认为优化错误。
应该理解为:编译器优化并不能甄别出代码当中O执行流这样的情况,

在这里插入图片描述

一般情况下这里的flag是一个全局变量,是变量就应该在内存中开辟空间,cpu要识别flag一定是在内存中读flag,读到cpu内,在cpu内做判断,判断完后,然后再在从内存中读取flag,不断的在内存中读取,来检测flag。
但是编译器在main函数中发现没有人在main中对flag进行修改,所以就直接优化flag到寄存器当中,以后不在进行内存级别的访问,直接识别cpu内的寄存器相关的信息,这个没有问题,这是编译器应该做的。

但是在我们今天的代码中就会出现问题。该如何解决这个问题?
在这里插入图片描述
提出问题:为什么?volatile做了什么?

volatile作用1:告诉编译器,不要对我这个变量做任何优化,读取必须贯穿式的读取内存,不要读取中间缓冲区寄存器中的数据!
即:保存内存的可见性!

6. 了解SIGCHLD信号, 重新编写信号处理函数的一般处理机制

1.子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略
在这里插入图片描述

2.不回收进程,不waitpid,子进程退出的时候直接退出就行,也不要形成僵尸进程
在这里插入图片描述
这种情况只在Linux下有效,其他平台不知道

  • 30
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 21
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值