信号的捕捉、可重入函数、竞态条件、alarm和pause实现sleep、

信号的捕捉

信号的捕捉流程:针对的是自定义处理方式
一个进程如何捕捉到一个信号然后进行处理的过程。
这里写图片描述
如果信号的处理是用户⾃定义函数,在信号递达时就调⽤这个函数,这称为捕捉信号。由于信号处理函数的代码是在⽤户空间的,处理过程⽐较复杂,举例如下: ⽤户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执⾏main函数,这时发⽣中断或异常切换到内核态。 在中断处理完毕后要返回⽤户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回⽤户态后不是恢复main函数的上下⽂继续执⾏,⽽是执⾏sighandler函 数,sighandler和main函数使⽤不同的堆栈空间,它们之间不存在调⽤和被调⽤的关系,是 两个独⽴的控制流程。 sighandler函数返回后⾃动执⾏特殊的系统调⽤sigreturn再次进⼊内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
面试题:如何实现用户态到内核态的切换?(搜)


用户态切换到内核态的3种方式
a. 系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
b. 异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
c. 外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
可重入函数

可重入函数:这个函数调用的时候如果中间操作被打断,在其他地方多次调用,但是并不会对结果造成影响,这种函数称为可重入函数;
不可重入函数:这个函数调用的时候如果中间操作被打断,在其他地方多次调用,这些多次调用对运行结果造成影响,这类函数称为不可重入函数。

//不可重入函数
#include<stdio.h>
#include<signal.h>

int a=10;
int b=20;

int sum()
{
        printf("%d+%d\n",a+1,b+1);
        a++;
        sleep(4);   //休眠期间,信号对sum函数有影响
        b++;
        return a+b;
}
void sigcb(int sig)
{
        printf("signal sum=%d+%d=%d\n",a,b,sum());
        return ;
}
int main()
{
        signal(SIGINT,sigcb);
        printf("sum=%d+%d=%d\n",a,b,sum());
        return 0;
}

这里写图片描述
由上例可看出,在sum函数休眠期间,信号的打断对结果造成影响,是不可重入函数。
不可重入函数:这个函数调用的时候如果中间操作被打断,在其他地方多次调用,这些多次调用对运行结果造成影响,这类函数称为不可重入函数。
如果一个函数符合以下条件之一则是不可重入函数:

  • 调用了malloc或者free,因为malloc是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
    volatile(关键字):

    保证内存可见性:cpu每次处理这个被volatile变量的时候,都会从内存中重新加载变量的值到寄存器。因为程序在优化的时候,如果一个变量使用的频率非常高,那么这个变量有可能就会被优化为只向寄存器家在一次,往后直接使用寄存器中保存的值,而不关心这个变量内存里边的值,因此会造成逻辑上的一些错误。
    竞态条件:

    当我们的某个操作不是原子操作,那么也就意味着这个操作有可能被打断然后去做其他的事情,这时做其他的事情有可能对我们程序的整个逻辑产生不良的影响。
int sigsuspend(const sigset_t*mask)
功能:临时使用mask中的信号替换阻塞集合blocked中的信号,然后进入阻塞等待,唤醒后还原,也就是说,临时替换了信号阻塞集合,然后进入休眠,当休眠被唤醒的时候,再将原来的阻塞信号替换回去。集合了临时阻塞指定信号,并且陷入阻塞等待的一个原子操作。

利用alarm和pause实现sleep:

//利用alarm和pause实现sleep

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

void sigcb(int sig)
{
}

void mysleep(int nsces)
{
        alarm(nsces);//等待nsces后发一个SIGALRM信号,但是alarm自定义为什么都不干,就相等于等了nsces秒
        pause(); //使调用进程挂起直至有信号  
}

int main()
{
        signal(SIGALRM,sigcb);
        while(1)
        {   
                mysleep(1);
                printf("hhahah\n");
        }   
        return 0;
}

但是这种sleep实现方法存在问题,如果在alarm和pause之间加入其他的信号,而在处理其他的信号期间alarm可能超时就会处理SIGALRM信号,那么pause就收不到信号一直挂起等待,就会出现问题,那么可以用sigsuspend函数:

//利用alarm和pause实现sleep

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

void sigcb(int sig)
{
}

void mysleep2(int nsces)
{
        sigset_t mask;
        alarm(nsces);////等待nsces后发一个SIGALRM信号,但是alarm自定义为什么都不干,就相等于等了nsces 秒
        sigemptyset(&mask);
        sigfillset(&mask);//将所有信号放在mask集合中
        sigdelset(&mask,SIGALRM); //在mask集合中移除SIGALRM信号
        sigsuspend(&mask); //临时将除了SIGALRM信号阻塞,也就意味在阻塞期间,只有一个SIGALRM信号能够发送进来,nsces后信号SIGALRM信号打断了阻塞
}
int main()
{
        signal(SIGALRM,sigcb);
        while(1)
        {   
                mysleep2(1);
                printf("hhahah\n");
        }   
        return 0;
}

这里写图片描述
每隔一秒打印一次。
SIGCHLD 信号:

子进程退出时通过这个信号来通知父进程的,而父进程也是通过收到这个信号,才知道进程状态改变(退出),这时需要调用 wait
自定义信号的处理方式,在处理方式中调用
while(waitpid(-1,NULL,WNOHANG)>0);
这样的话,我们的子进程资源将随退随回收。并且不会影响我们父进程的大致逻辑。信号会打断进程的阻塞操作,唤醒正在休眠的进程。

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

void  sigcb(int sig)
{
        //wait(NULL);
        while(waitpid(-1,NULL,WNOHANG)!=0)
        {   
         //用循环是因为如果信号是非可靠信号,就有可能丢失,但是僵尸子进程可能回收不完,因此循环回收以下
            printf("wait child success\n");
            sleep(1);
        }   
        printf("child is quit\n");
}
int main()
{
        int pid=-1;
        signal(SIGCHLD,sigcb);
        pid=fork();
        if(pid==0)
        {   
                printf("child exit\n");
                sleep(3);
                exit(1);
        }   
        while(1)
        {   
                printf("hhha\n");
                sleep(1);
        }   
        return 0;
}

猜你喜欢

转载自blog.csdn.net/sophia__yu/article/details/82388566