溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

volatile關(guān)鍵字,竟態(tài)條件

發(fā)布時(shí)間:2020-07-16 14:47:50 來(lái)源:網(wǎng)絡(luò) 閱讀:474 作者:小止1995 欄目:編程語(yǔ)言

volatile:防止編譯器性能優(yōu)化,與移植性有關(guān)。

#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循環(huán)中:沒(méi)有寫入修改done的值,編譯器會(huì)在讀取變量的時(shí)候,將其從內(nèi)存拿出存入寄存器并比較,在下次讀取時(shí),直接從寄存器中取該變量的值。 

而在信號(hào)處理函數(shù)中,有修改done的值,需寫回內(nèi)存。

可指定編譯器優(yōu)化級(jí)別

編譯器優(yōu)化級(jí)別:

  1. gcc -o my_volatile  my_volatile.c -O0//不優(yōu)化

  2. gcc -o my_volatile  my_volatile.c -O1//默認(rèn)

  3. gcc -o my_volatile  my_volatile.c -O2

  4. gcc -o my_volatile  my_volatile.c -O3//優(yōu)化級(jí)別最高

此時(shí)會(huì)發(fā)生內(nèi)存級(jí)別的不一致:寄存器:0,內(nèi)存:1.

運(yùn)行結(jié)果:

volatile關(guān)鍵字,竟態(tài)條件

要改變此程序顯示預(yù)期效果,只需定義done為:volatile int done=0;//這樣每次使用done時(shí)都會(huì)從內(nèi)存中取done的值。

sig_atomic_t:由C語(yǔ)言提供。

雖然C代碼只有一行,但是在32位機(jī)上對(duì)一個(gè)64位的long long變量賦值需要兩條指令完成,因此不是原子操作。同樣地,讀取這個(gè)變量到寄存器需要兩個(gè)32位寄存器才放得下,也需要兩條指令, 不是原子操作。

為了解決這些平臺(tái)相關(guān)的問(wèn),C標(biāo)準(zhǔn)定義了一個(gè)類型sig_atomic_t,在不同平臺(tái)的C語(yǔ)言庫(kù)中取不同的類型,例如在32位機(jī) 上定義sig_atomic_tint類型。

竟態(tài)條件:

由于異步事件在任何時(shí)候都有可能發(fā)生(這里的異步事件指出現(xiàn)更優(yōu) 先級(jí)的進(jìn)程),如果我們寫程序時(shí)考慮不周密,就可能由于時(shí)序問(wèn)題而導(dǎo)致錯(cuò)誤,這叫做競(jìng)態(tài)條件 (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);//注冊(cè)信號(hào)處理函數(shù)
    alarm(time);//time秒后讓系統(tǒng)發(fā)SIGALRM信號(hào)
    pause();//內(nèi)核切換到別的進(jìn)程運(yùn)行
    int ret=alarm(0);
    sigaction(SIGALRM,&old,NULL);//恢復(fù)默認(rèn)信號(hào)處理動(dòng)作
    return ret;
}
int main()
{
    while(1)
    {
        printf("I am sleep...\n");
        my_sleep(5);
    }
    return 0;
}


雖然alarm(nsecs)緊接著的下一行就是pause(),但是無(wú)法保證pause()一定會(huì)在調(diào)用alarm(nsecs)之 后的nsecs秒之內(nèi)被調(diào)用。

在調(diào)用pause之前屏蔽SIGALRM信號(hào)使它不能提前遞達(dá)就可以了。
1. 屏蔽SIGALRM信號(hào);
2. alarm(nsecs);
3. 解除對(duì)SIGALRM信號(hào)的屏蔽;
4. pause();
從解除信號(hào)屏蔽到調(diào)用pause之間存在間隙,SIGALRM仍有可能在這個(gè)間隙遞達(dá)。

要是解除信號(hào)屏蔽掛起等待信號(hào)這兩步能合并成一個(gè)原子操作就好了,這正是sigsuspend
函數(shù)的功 能。 sigsuspend包含了pause的掛起等待功能,同時(shí)解決了競(jìng)態(tài)條件的問(wèn)題,在對(duì)
時(shí)序要求嚴(yán)格的場(chǎng)合下都應(yīng)該調(diào)用sigsuspend不是pause。

注:調(diào)用sigsuspend時(shí),進(jìn)程的信號(hào)屏蔽字由sigmask參數(shù)指定,可以通過(guò)指定sigmask來(lái)臨時(shí)
解除對(duì)某 個(gè)信號(hào)的屏蔽,然后掛起等待,當(dāng)sigsuspend返回時(shí),進(jìn)程的信號(hào)屏蔽字恢復(fù)為原
來(lái)的值,如果原來(lái)對(duì)該信號(hào)是屏蔽的,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);//注冊(cè)SIGALRM的信號(hào)處理函數(shù)
    sigemptyset(&newmask);
    sigaddset(&newmask,SIGALRM);
    sigprocmask(SIG_BLOCK,&newmask,&oldmask);//屏蔽SIGALRM信號(hào);
    alarm(time);
    suspmask=oldmask;
    sigdelset(&suspmask,SIGALRM);//解除suspmask中SIGALRM信號(hào)的屏蔽;
    sigsuspend(&suspmask);//用suspmask去替換PCB中的block表,從而解除對(duì)SIGALRM信號(hào)的阻塞
    int ret=alarm(0);
    sigaction(SIGALRM,&oldact,NULL);
    sigprocmask(SIG_SETMASK,&oldmask,NULL);//恢復(fù)之前的系統(tǒng)默認(rèn)處理信號(hào)方式
    return ret;
}
int main()
{
    while(1)
    {
        printf("I am sleep\n");
        sleep(5);
    }
    return 0;
}

進(jìn)程在終止時(shí)會(huì)給父進(jìn)程發(fā)SIGCHLD信號(hào),該信號(hào)的默認(rèn)處理動(dòng)作是忽略,父進(jìn)程可以自定義SIGCHLD信號(hào)的處理函數(shù),這樣父進(jìn)程只需專心處理自己的工作,不必關(guān)心子子進(jìn)程了,子進(jìn)程終止時(shí)會(huì)通知父進(jìn)程,父進(jìn)程在信號(hào)處理函數(shù)中調(diào)用wait清理子進(jìn)程即可。

優(yōu)點(diǎn):沒(méi)花費(fèi)時(shí)間在等待上,直到收到信號(hào)(異步信號(hào))

#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);//保證父進(jìn)程已注冊(cè)完信號(hào)處理函數(shù),父,子進(jìn)程誰(shuí)先運(yùn)行不確定
         printf("child is quit!\n");
         exit(1);
    }
    else
    {
         signal(SIGCHLD,my_sigchld);
         while(1);
    }
    return 0;
}

但是,如果一個(gè)父進(jìn)程有100個(gè)子進(jìn)程,收到好多SIGCHLD信號(hào),只會(huì)保存一份,只能wait一份,故應(yīng)該修改代碼防止此情況發(fā)生

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);
    }
 }


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI