【总结】进程信号

信号的基本概念

操作系统内部的交互机制。

  • 信号相对于进程的控制流程来说是异步,即信号在任何时间点都可能产生。
  • 进程通过位图知道自己收到了几号信号,位图放在PCB中。
  • 比特位代表信号编号,比特位的内容代表该信号是否被阻塞。
  • 操作系统通过修改pending表位图中的某一位来给进程发送信号,进程收到信号后先把信号保存起来,再在合适的时间去处理信号。
  • 处理信号的方式有忽略(9号信号SIGKILL、19号信号SIGSTOP不能忽略),默认,自定义。


产生信号

1.通过终端按键产生信号

Ctrl+c、Ctrl+\

SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump

Core Dump叫做核心转储,当一个进程要异常终止时(通常是有Bug),可以选择把进程的用户空间内存数据全部保存到磁盘上。文件名叫做core。线上的服务器是不允许产生core文件的。

2.调用系统函数向进程发信号

使用kill命令,kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定信号(自己给自己发送信号)。

3.由软件条件产生信号

管道读端关闭产生SIGPIPE,也是一种软件条件产生的信号。

4.硬件异常产生

CPU:零作为除数,会产生SIGFPE信号。

mmu:访问内存发现一个内存非法或内存越界,mmu设备会发现错误并通知操作系统内核发一个11号信号,就会产生段错误。


阻塞信号

信号递达:实际执行信号的处理动作

信号未决:信号从产生到递达之间的状态。

进程可以选择阻塞某个信号。

阻塞和忽略的区别:

只要信号被阻塞就不会递达,忽略是在递达之后可选的一种处理动作。

信号在内核中表示

每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。

sigset_t:信号集,非零即一。阻塞信号集也叫做当前进程的信号屏蔽字。

pause:调用进程挂起直到有信号递达

#include <unistd.h>

int pause(void);

sigprocmask

读取或更改进程的信号屏蔽字

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

pending

读取当前进程的未决信号,只能读不能改。

#include<signal.h>
sigpending(sigset_t *set);

经典信号处理

SIGALARM时钟信号

#include <unistd.h>

unsigned int alarm(unsigned int seconds);//时间以秒为单位

代码实现捕捉信号,读取信号屏蔽字。

#include<stdio.h>                     
#include<unistd.h>                           
#include<signal.h>                  
                        
void myhander(int sig)                     
{                                  
  printf("sig=%d\n",sig); 
} 
void Printsigset(sigset_t* set){ 
  int i=1;
  for(;i<=31;i++){      
    if(sigismember(set,i)){ 
      printf("1");                     
    }else{ 
      printf("0");                       
    }                      
  }   
  printf("\n");                    
}                           
int main()                    
{                               
  //1.捕捉SIGINT信号                      
  signal(SIGINT,myhander);
  //2.把SIGINT信号屏蔽                
  sigset_t set;                     
  sigset_t oset;                                      
  sigemptyset(&set);                       
  sigaddset(&set,SIGINT);                   
  sigprocmask(SIG_BLOCK,&set,&oset);                          
  //3.循环读取未决信号集                    
  int count=0;                       
  while(1){                          
    ++count;                                     
    if(count>=5){                      
      count=0;                        
      //解除信号屏蔽字,再设置上                       
      printf("解除信号屏蔽字!\n");                       
      sigprocmask(SIG_SETMASK,&oset,NULL);                           
      sleep(1);                                  
      printf("再次设置信号屏蔽字!\n");                      
      sigprocmask(SIG_BLOCK,&set,&oset);                      
    }                                 
    sigset_t pending_set;                          
    sigpending(&pending_set);  
    Printsigset(&pending_set);                          
    sleep(1);                
  }                     
  return 0;                     
}

可重入函数

同一个函数在多个执行流中同时调用,没有逻辑问题。如果一个函数访问自己的局部变量或参数,叫做可重入函数。

不可重入函数

同一个函数在多个执行流中同时调用,有逻辑问题。如果函数访问一个全局链表,则可能会因为重入而发生错乱。

volatile限定符

关键字,防止编译器对代码过度的优化

竞态条件

运行时序并不像我们写程序所设想的那样,由于异步事件在任何时候都有可能发生(异步事件有时候指出现更高优先级的进程),由于时序问题导致错误,叫做竞态条件。

SIGCHLD

为避免产生僵尸进程

  • 父进程可以阻塞等待子进程结束。--父进程阻塞饿就不能处理自己的工作
  • 轮询方式---父进程非阻塞查询是否有子进程等待清理。----父进程还要时不时的轮询一下,程序实现复杂。
  • 子进程在终止时会给父进程发送SIGCHLD信号,信号的默认处理动作是忽略,所以父进程就可以处理自己的工作,不必关心子进程。

代码实现:

void handler(int sig_id){ 
    if(waitpid(-1,NULL,WNOHANG)>0){ 
      printf("wait child success!\n");
    }
    printf("child exit!\n"); 
  }
  int main(){
    signal(SIGCHLD,handler);
    pid_t id=fork(); 
    if(id==0){ 
      //child
      printf(" %d,i am child\n",getpid());
    exit(1); 
    }else if(id>0){ 
      //father
      while(1){ 
        printf("i am father!\n");
        sleep(1);
      } 
    }else{
      perror("fork\n");
    }
        return 0; 
  }                            

猜你喜欢

转载自blog.csdn.net/moralin_/article/details/80504957
今日推荐