volatile:防止编译器性能优化,与移植性有关。

#include<stdio.h>
#include<signal.h>
int done=0;
void handle(int sig)
{
    printf("get sig %d\n",sig);
    done=1;
}
int main()
{
    signal(SIGINT,handle);
    while(!done);
}

Makefile:

my_volatile:my_volatile.c

     gcc -o $@ $^  -O3

.PHONY:clean

clean:

     rm -f my_volatile

在while循环中:没有写入修改done的值,编译器会在读取变量的时候,将其从内存拿出存入寄存器并比较,在下次读取时,直接从寄存器中取该变量的值。 

而在信号处理函数中,有修改done的值,需写回内存。

可指定编译器优化级别

编译器优化级别:

  1. gcc -o my_volatile  my_volatile.c -O0//不优化

  2. gcc -o my_volatile  my_volatile.c -O1//默认

  3. gcc -o my_volatile  my_volatile.c -O2

  4. gcc -o my_volatile  my_volatile.c -O3//优化级别最高

此时会发生内存级别的不一致:寄存器:0,内存:1.

运行结果:

wKiom1cq96yQ_O6EAAAQv51Dy2s254.png

要改变此程序显示预期效果,只需定义done为:volatile int done=0;//这样每次使用done时都会从内存中取done的值。

sig_atomic_t:由C语言提供。

虽然C代码只有一行,但是在32位机上对一个64位的long long变量赋值需要两条指令完成,因此不是原子操作。同样地,读取这个变量到寄存器需要两个32位寄存器才放得下,也需要两条指令, 不是原子操作。

为了解决这些平台相关的问,C标准定义了一个类型sig_atomic_t,在不同平台的C语言库中取不同的类型,例如在32位机 上定义sig_atomic_tint类型。

竟态条件:

由于异步事件在任何时候都有可能发生(这里的异步事件指出现更优 先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件 (Race Condition)

#include<stdio.h>
#include<signal.h>
#include<string.h>
void handler(int sig)
{
    //do nothing
}
int my_sleep(int time)
{
    struct sigaction act;
    act.sa_handler=handler;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    struct sigaction old;
    memset(&old,'\0',sizeof(old));
    sigaction(SIGALRM,&act,&old);//注册信号处理函数
    alarm(time);//time秒后让系统发SIGALRM信号
    pause();//内核切换到别的进程运行
    int ret=alarm(0);
    sigaction(SIGALRM,&old,NULL);//恢复默认信号处理动作
    return ret;
}
int main()
{
    while(1)
    {
        printf("I am sleep...\n");
        my_sleep(5);
    }
    return 0;
}


虽然alarm(nsecs)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(nsecs)之 后的nsecs秒之内被调用。

在调用pause之前屏蔽SIGALRM信号使它不能提前递达就可以了。
1. 屏蔽SIGALRM信号;
2. alarm(nsecs)
3. 解除对SIGALRM信号的屏蔽
4. pause();
从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。

要是解除信号屏蔽挂起等待信号这两步能合并成一个原子操作就好了,这正是sigsuspend
函数的功 能。 sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对
时序要求严格的场合下都应该调用sigsuspend不是pause

注:调用sigsuspend,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时
解除对某 个信号的屏蔽,然后挂起等待,sigsuspend返回时,进程的信号屏蔽字恢复为原
来的值,如果原来对该信号是屏蔽的,sigsuspend返回后仍然是屏蔽的。

#include<stdio.h>
#include<signal.h>
void handle(int sig)
{
    //do nothing
}
int sleep(int time)
{
    struct sigaction oldact,newact;
    sigset_t newmask,oldmask,suspmask;
    newact.sa_handler=handle;
    newact.sa_flags=0;
    sigemptyset(&newact.sa_mask);
    sigaction(SIGALRM,&newact,&oldact);//注册SIGALRM的信号处理函数
    sigemptyset(&newmask);
    sigaddset(&newmask,SIGALRM);
    sigprocmask(SIG_BLOCK,&newmask,&oldmask);//屏蔽SIGALRM信号;
    alarm(time);
    suspmask=oldmask;
    sigdelset(&suspmask,SIGALRM);//解除suspmask中SIGALRM信号的屏蔽;
    sigsuspend(&suspmask);//用suspmask去替换PCB中的block表,从而解除对SIGALRM信号的阻塞
    int ret=alarm(0);
    sigaction(SIGALRM,&oldact,NULL);
    sigprocmask(SIG_SETMASK,&oldmask,NULL);//恢复之前的系统默认处理信号方式
    return ret;
}
int main()
{
    while(1)
    {
        printf("I am sleep\n");
        sleep(5);
    }
    return 0;
}

进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。

优点:没花费时间在等待上,直到收到信号(异步信号)

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void my_sigchld(int sig)
{
    int status=0;
    pid_t ret=waitpid(-1,&status,0);
    if(ret>0)
    {
        printf("sig: %d,code: %d\n",status&0xff,(status>>8)&0xff);
    }
}
int main()
{
    pid_t tid=fork();
    if(tid<0)
    {
        perror("fork");
        exit(1);
    }
    else if(tid==0)
    {
         sleep(10);//保证父进程已注册完信号处理函数,父,子进程谁先运行不确定
         printf("child is quit!\n");
         exit(1);
    }
    else
    {
         signal(SIGCHLD,my_sigchld);
         while(1);
    }
    return 0;
}

但是,如果一个父进程有100个子进程,收到好多SIGCHLD信号,只会保存一份,只能wait一份,故应该修改代码防止此情况发生

void my_sigchld(int sig)
{
    int status=0;
    pid_t ret;
    while((ret=waitpid(-1,&status,0))>0)
    {
        printf("sig: %d,code: %d\n",status&0xff,(status>>8)&0xff);
    }
 }