信号

信号(软中断)

概念

信号用于通知一个事件的发生,会打断当前操作,去处理这个事件;

当然有一个前提:必须识别这个信号

信号种类有很多,每个都代表不同事件

信号是有生命周期:产生->注册->注销->处理

kill -l  //查看信号列表

在这里插入图片描述
信号的分类

​ 信号总共有62种,有两大类型

​ 非可靠信号:1~31号信号

​ 可靠信号:34~64号信号

信号的产生

​ 硬件产生:ctrl+c ctrl+l ctrl+z

​ 软件产生:kill -signum pid 向进程发送一个signum信号

kill(pid,signum)
raise(signum)
abort()
alarm(nsec)

实现

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

int main()
{
  //向pid进程发送sig信号
  //原型:int kill(pid_t pid,int sig);
// kill(getpid(),SIGKILL);
  
  //给当前进程发送信号
  //原型:int raise(int sig);
 //raise(SIGTERM);
  
  //给当前信号发送SIGABRT
  //原型:void abort(void);
  //abort();
  
  //经过固定秒后,给当前进程发送SIGALRM信号
  //unsigned int alarm(unsigned int seconds);
  //当seconds == 0时,表示取消上一个时间为到的定时器
 alarm(5);
 alarm(0);
  while(1)
  {
    printf("hello!!\n");
    sleep(1);
  }
  return 0;
}
信号注册

信号在进程中的注册:在进程pcb中做标记,标记进程收到了哪些信号

非可靠信号注册:判断pcb中的pending位图中相应信号是否已经注册(位置是否已经置一);若未注册则位图修改为1,向sigqueue链表中添加一个信号节点;若已经注册,则不做任何操作(事件丢失)。

可靠信号注册:不管信号是否已经注册,都会向链表中添加一个新的信号节点(事件不会丢失)
在这里插入图片描述

信号在进程中的注销

非可靠信号:节点只有一个,注销就是删除节点,位图置零

可靠信号:节点有可能有多个,注销就是删除一个节点,判断链表中是否还有相同信号节点;若没有则位图置零;否则位图不变依然需要标记有这个信号待处理。
v

信号的处理

​ 信号的处理并不是立即被处理;而是选择一个合适的时机去处理信号,即进程的运行从内核态返回用户态的时候。

​ **进程如何从用户态切换到内核态:**发起系统调用;程序异常;中断

​ 进程运行的代码若是库函数或者用户自己写的函数,就说进程当前运行在用户态;

信号处理有多种方式:

​ 默认处理方式—既定义好的处理方式

​ 忽略处理方式—处理动作中什么都没做

​ 自定义处理方式—用户自己确定信号如何处理—自定义信号的处理函数替换原有的处理函数

如何修改信号处理方式:

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


void sigcb(int signo)
{
  printf("recv a signo:%d\n",signo);
  signal(signo,SIG_DFL);//将信号的处理方式还原成默认方式
}

int main()
{
 // sighandler_t signal(int signum,sighandler_t handler);
  signal(SIGINT,SIG_IGN);
  
 /*
  //int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
  struct sigaction new_act,old_act;
  new_act.sa_flags = 0;
  new_act.sa_handler = sigcb;
  //清空set信号集合
  //int sigemptyset(sugset_t *set);
  sigemptyset(&new_act.sa_mask);
  //sigaction使用new_act替换2号信号的处理动作,将原有动作保存到old_act
  sigaction(SIGINT,&new_act,&old_act);
*/
  while(1)
  {
    printf("~~~~~~leihaoa!!!!!!!!!");
    sleep(5);
  }
  return 0;
}

在代码中,用sigaction将信号SIGINT的处理动作替换成了我们自定义的函数sigcb,并将原处理方式保存在old_act中,当接收到信号SIGINT时,会打印信号编号,接下来我们将信号的处理方式又还原成默认处理方式,所以当我们第二次再给一个SIGINT信号的时候,程序就可以直接退出了,运行结果如下:
在这里插入图片描述

自定义处理方式信号的捕捉流程

[外链图片转存失败(img-VukX8DqY-1566029002970)(D:\Desktop\笔记\笔记\笔记\Linux笔记\Linux笔记\Linux笔记\图片\信号\1.png)]

信号的阻塞

阻止信号被递达—信号依然可以注册,只是暂时不处理

​ 递达:一个动作—信号的处理

​ 在pcb中还有一个集合—阻塞信号集合—标记哪些信号暂时不被处理

[外链图片转存失败(img-mV6WAdTf-1566029002971)(D:\Desktop\笔记\笔记\笔记\Linux笔记\Linux笔记\Linux笔记\图片\信号\2.png)]
在这里插入图片描述

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
how:
	SIG_BLOCK	像阻塞集合中加入set集合中的信号 block = mask | set
	SIG_UNBLOCK 向阻塞集合中移除set集合中的信号 block = mask & (~set)
	SIG_SETMASK 将set集合的信号设置为阻塞信号   block = set
oldset 用于保存修改前,阻塞集合中的信号

在所有信号中9号信号SIGKILL19号信号SIGSTOP无法被阻塞,无法被自定义。

//实现信号阻塞:先将所有信号全部阻塞,getchar()在用户按下回车之前,这些信号一直被阻塞对所有信号解除
阻塞  ---这时候信号解除阻塞,将被处理。体会可靠信号和不可靠信号
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void sigcb(int signo)
{
  printf("recv signo:%d\n",signo);
}
int main()
{
  signal(40,sigcb);
  signal(SIGINT,sigcb);
  //int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
  sigset_t set,oldset;
  sigemptyset(&set);

  //向set集合中添加所有信号
  //int sigfillset(sigset_t *set)
  //向set集合中添加signum信号
  //int sigfillset(sigset_t *set,int signum);
  sigfillset(&set);
  //阻塞set中的信号
  sigprocmask(SIG_BLOCK,&set,&oldset);
  getchar();

  // sigprocmask(SIG_BLOCK,&oldset,&oldset);
  sigprocmask(SIG_UNBLOCK,&set,NULL);
  return 0;
}

运行后,信号全都会被阻塞,直到用户按下回车后,阻塞解除,信号处理正常。
在这里插入图片描述

可重入函数与不可重入函数

在这里插入图片描述

//体会函数的重入造成的影响
#include<stdio.h>
#include<unistd>
#include<signal.h>
int a =1;
int b = 1;
int sum(int *a,int *b)
{
	(*a)++;
	sleep(3);
	(*b)++;
	return *a + *b;
}

void sigcb(int no)
{
	printf("signal------%d\n",sum(&a,&b));
}
int main()
{
	signal(SIGINT,sigcb);
	printf("main--------%d\n",sum(&a,&b))
	return 0;
}

函数的重入:多个执行流程同时执行进入相同的函数

函数的可重入与不可重入

可重入:多个执行流程同时执行进入相同的函数,不会造成数据二义性以及代码逻辑混乱

不可重入:多个执行流程同时执行进入相同的函数,有可能造成数据二义性以及代码逻辑混乱

当用户设计一个函数或使用一个函数的视乎在多个执行流中,那么这时候就需要考虑是否可重入情况

函数可重入与不可重入的关键点

​ 这个函数是否对临界资源(全局数据)进行了非原子操作

如果一个函数符合以下条件之一则是不可重入的

1.调用了malloc或free,因为malloc也是用全局链表来管理堆的。
2.调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值