linux基础——信号阻塞及未决信号

简介: linux基础——信号阻塞及未决信号

文章目录

信号阻塞和未决信号

信号集相关API函数

操作流程

代码示例

未决信号

代码示例

pause的使用

代码示例

信号传送处理过程

可重入函数

代码示例

信号处理函数的继承

代码示例

setitimer实现定时器

代码示例


信号阻塞和未决信号

进程可以设置对某个信号的阻塞(屏蔽),需要用到sigset_t(信号集)数据类型。


信号集相关API函数

sigemptyset(3)


#include <signal.h>
int sigemptyset(sigset_t *set);
功能:将信号集清空
参数:
set:指定要清空的信号集
返回值:
0  成功
-1 错误


int sigfillset(sigset_t *set);


功能:将信号集设置为满
参数:
set:指定要置满的信号集
返回值:
0  成功
-1 错误


int sigaddset(sigset_t *set, int signum);


功能:给信号集添加信号
参数:
set:指定信号集
signum:指定要添加的信号
返回值:
0  成功
-1 错误


int sigdelset(sigset_t *set, int signum);


功能:删除信号集中的某个信号
参数:
set:指定信号集
signum:指定要删除的信号
返回值:
0  成功
-1 错误


int sigismember(const sigset_t *set,int signum);


功能:测试信号是否是信号集中的一个成员
参数:
set:指定的信号集
signum:指定信号
返回值:
-1  错误
0   信号不是信号集中的一个成员
1   信号集中有这个信号


操作流程

如果希望设置进程对2号信号阻塞,就需要设置阻塞信号集


  1. sigset_t block_set;
  2. 将block_set集合中的所有成员清空;
  3. 将2好信号添加到block_set集合中;
  4. 将block_set设置为这个进程的阻塞信号集。sigprocmask函数使用。

sigprocmask(2)


#include <signal.h>
int sigprocmask(int how,  const  sigset_t *set, \
  sigset_t *oldset);
功能:检测或改变阻塞信号
参数:
how:
SIG_BLOCK   当前信号集和set指定信号集的并集
SIG_UNBLOCK  将set集合里的成员从当前进程阻塞信号集中移除
SIG_SETMASK  将set指定为当前进程的阻塞信号集
set:指定的新的信号集
oldset:之前的信号集保存到oldset中。
返回值:
0  成功
-1   错误


代码示例

blocked.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void doit(int n){
  printf("recv %d signal...\n",n);
  return;
}
int main(void){
  sigset_t block;
  printf("pid:%d\n",getpid());
  //为2号信号添加处理函数
  signal(64,doit);
  //将block清空
  sigemptyset(&block);
  //将2号信号添加到block集合中
  sigaddset(&block,64);
  //将block设置为进程的阻塞信号集
  sigprocmask(SIG_SETMASK,&block,NULL);
  //while(1){
  sleep(10);
  //}
  //解除信号阻塞
  sigprocmask(SIG_UNBLOCK,&block,NULL);
  return 0;
}


  • 执行结果

20200206102915520.png

未决信号

在信号则是期间,可以查看未决信号。

如何查看未决信号。使用sigpending(2)查看


#include <signal.h>
int sigpending(sigset_t *set);
功能:检测未决信号
参数:
set:值-结果参数,用于返回进程的未决信号掩码
返回值:
0  成功
-1   错误


代码示例

  • pending.c
#include <stdio.h>
#include <signal.h>
int main(void){
  //定义信号集变量
  sigset_t b,p;
  //清空b信号集
  sigemptyset(&b);
  //将2号信号添加到b信号集中
  sigaddset(&b,2);
  sigaddset(&b,3);
  //设置进程的信号屏蔽掩码为b
  sigprocmask(SIG_SETMASK,&b,NULL);
  while(1){
  sleep(1);
  //清空信号集p
  sigemptyset(&p);
  //获取未决信号掩码
  sigpending(&p);
  //检测信号集p中有哪些未决信号
  int f=sigismember(&p,2);
  if(f==-1){
    perror("sigismember");
    return 1;
  }
  if(f==0){
    printf("not 2\n");
  }else{
    printf("2号信号未决\n");
  }
  int f3=sigismember(&p,3);
  if(f3==-1){
    perror("sigismember");
    return 1;
  }
  if(f3==0){
    printf("not 3\n");
  }else{
    printf("3号信号未决\n");
  }
  }
  return 0;
}


  • 执行结果

20200206103814271.png

pause的使用

int pause(void);


#include <unistd.h>
int pause(void);
功能:等待一个信号
参数:
void
返回值:
-1   错误  errno被设置为EINTR


代码示例

  • pause.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void doit(int n){
  printf("recv %d signal\n",n);
  return;
}
int main(void){
  signal(2,doit);
  //等待信号到来
  int f=pause();
  /*if(f==-1){
  perror("pause");
  return 1;
  }*/
  printf("pause after...%d\n",f);
  return 0;
}


  • 执行结果

20200206104520570.png


  • 使用alarm和pause完成sleep的功能,mysleep.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void doit(int n){
  return;
}
unsigned int mysleep(unsigned int seconds){
  signal(SIGALRM,doit);
  //设置闹钟
  int l=alarm(seconds);
  //暂停
  pause();
  return l;
}
int main(void){
  while(1){
  mysleep(4);
  printf("jjjjjjj\n");
  }
  return 0;
}


  • 执行结果

20200206104832120.png

信号传送处理过程

  1. 用户输入命令,在bash下启动一个前台作业。
  2. 用户按下ctrl+c键,这个键盘输入产生一个硬件中断。
  3. 如果CPU正在执行这个进程的代码,则该进程的用户空间代码暂停执行。CPU从用户态切换到内核态处理硬件中断。
  4. 终端驱动程序将ctrl+c解释成一个SIGINT信号,记录在该进程的PCB中。
  5. 当某个时刻,进程从内核态切换会用户态的时候,首先处理PCB中记录的信号,如果PCB中有未处理信号,找到信号对应的信号处理程序进行处理。
  6. 信号处理函数处理完毕,调用sigreturn(2),继续返回到进程的内核态。再次循环到第五步。

可重入函数

函数使用到的变量的空间全部分配在栈桢中,那这样的函数称为可重入函数。否则称为不可重入函数。

信号的处理函数尽量保证为可重入函数。


代码示例

  • reenterable.c
#include <stdio.h>
#include <signal.h>
void doit(int n){
  int v;
  static int c=0;
  v=c;
  v++;
  usleep(5000);
  c=v;
  //usleep(5000);
  printf("c=%d\n",c);
  return;
}
int main(void){
  signal(2,doit);
  while(1)
  doit(333);
  return 0;
}


  • 执行结果

20200206105322813.png

信号处理函数的继承

子进程会继承父进程的信号处理函数,信号属于进程资源,在进程的PCB中会有信号的记录。


代码示例

  • p_signal.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void doit(int n){
  printf("recv %d signal\n",n);
  return ;
}
int main(void){
  pid_t pid;
  //注册2号信号的处理函数为用户自定义
  signal(2,doit);
  //创建子进程
  pid=fork();
  if(pid ==-1){
  perror("fork");
  return 1;
  }
  if(pid==0){
  printf("child pid %d\n",\
    getpid());
  while(1);
  }else{
  wait(NULL);
  }
  return 0;
}


  • 执行结果

20200206105930315.png

setitimer实现定时器

系统计时器,运行一个进程的时候,进程所消耗的时间包括三个部分:


  • 用户时间:进程消耗在用户态的时间
  • 内核时间:进程消耗在内核态的时间
  • 睡眠时间:进程消耗在等待I/O、睡眠等不被调度的时间。

内核为每个进程都维护了三个计时器:


  • 真实计时器:统计进程的执行时间。
  • 虚拟计时器:统计进程的用户时间。
  • 实用计时器:统计进程的用户时间和内核时间之和。

执行时间 = 用户时间 + 内核时间 + 睡眠时间。


这三个计时器除了统计进程的各种时间以外,还可以按照各自的计时规则,以定时器的方式工作,向进程周期性的发送不同的信号。


SIGALRM  真实定时器
SIGVTALRM   虚拟定时器  
SIGPROF    实用定时器


通过使用setitimer(2)设置、启动、关闭定时器


#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value,
                     struct itimerval *old_value);
功能:设置一个间隔时间的定时器
参数:
which:
ITIMER_REAL:SIGALRM
ITIMER_VIRTUAL:SIGVTALRM
ITIMER_PROF:SIGPROF
new_value:定时器新值
old_value:定时器原来的值
返回值:
0  成功
-1  错误  errno被设置


  • 补充
struct itimerval{
  /*间隔时间*/
  struct timeval it_interval; /* next value */
  /*初值时间*/
    struct timeval it_value;    /* current value */
};
struct timeval{
  long tv_sec;                /* seconds */
    long tv_usec;               /* microseconds */  
};


代码示例

  • timer.c
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
void doit(int n){
  printf("recv signal...\n");
  return ;
}
int main(void){
  struct itimerval new;
  //设置计时器的初始时间
  new.it_value.tv_sec=5;
  new.it_value.tv_usec=0;
  //设置定时器的间隔时间
  new.it_interval.tv_sec=0;
  new.it_interval.tv_usec=500000;
  signal(SIGALRM,doit);
  //设置定时器
  setitimer(ITIMER_REAL,&new,NULL);
  while(1)
  pause();
  return 0;
}


  • 执行结果

2020020611075390.png

相关文章
|
25天前
|
Linux 调度
Linux0.11 信号(十二)(下)
Linux0.11 信号(十二)
19 1
|
1月前
|
存储 Linux 调度
|
25天前
|
存储 Unix Linux
Linux0.11 信号(十二)(上)
Linux0.11 信号(十二)
21 0
|
1月前
|
Linux 开发者
深入理解Linux I/O模型:同步、异步、阻塞与非阻塞
【8月更文挑战第1天】在探索操作系统的奥秘中,I/O模型作为影响性能的关键因素之一,常常让开发者们感到困惑。本文将通过浅显易懂的语言和实际代码示例,揭示Linux下同步与异步、阻塞与非阻塞的概念及其区别,并指导如何在实际应用中选择合适的I/O模型以优化程序性能。
57 1
|
1月前
|
Linux
|
2月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
128 1
|
2月前
|
存储 NoSQL Unix
【Linux】进程信号(下)
【Linux】进程信号(下)
31 0
|
2月前
|
安全 Linux Shell
【Linux】进程信号(上)
【Linux】进程信号(上)
34 0
蜕变成蝶~Linux设备驱动中的阻塞和非阻塞I/O
  今天意外收到一个消息,真是惊呆我了,博客轩给我发了信息,说是俺的博客文章有特色可以出本书,,这简直让我受宠若惊,俺只是个大三的技术宅,写的博客也是自己所学的一些见解和在网上看到我一些博文以及帖子里综合起来写的,,总之这又给了额外的动力,让自己继续前进,,希望和大家能够分享一些自己的经验,,在最需要奋斗的年级以及在技术的领域踽踽独行的过程中有共同的伙伴继续前进~   今天写的是Linux设备驱动中的阻塞和非阻塞I/0,何谓阻塞与非阻塞I/O?简单来说就是对I/O操作的两种不同的方式,驱动程序可以灵活的支持用户空间对设备的这两种访问方式。