linux系統程式設計之訊號(四):alarm和可重入函式

mickole發表於2013-07-15

一,alarm()

在將可重入函式之前我們先來了解下alarm()函式使用:

#include <unistd.h>

unsigned int alarm(unsigned int seconds)

系統呼叫alarm安排核心為呼叫程式在指定的seconds秒後發出一個SIGALRM的訊號。如果指定的引數seconds為0,則不再傳送 SIGALRM訊號。後一次設定將取消前一次的設定。該呼叫返回值為上次定時呼叫到傳送之間剩餘的時間,或者因為沒有前一次定時呼叫而返回0。

注意,在使用時,alarm只設定為傳送一次訊號,如果要多次傳送,就要多次使用alarm呼叫。

man幫助說明:


DESCRIPTION
       alarm()  arranges  for  a SIGALRM signal to be delivered to the calling
       process in seconds seconds.

       If seconds is zero, no new alarm() is scheduled.

       In any event any previously set alarm() is canceled.

RETURN VALUE
       alarm() returns the number of seconds remaining  until  any  previously
       scheduled alarm was due to be delivered, or zero if there was no previ-
       ously scheduled alarm.

示例:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

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


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
int main(int argc, char *argv[])
{
    if (signal(SIGALRM, handler) == SIG_ERR)
        ERR_EXIT("signal error");

    alarm(1);
    for (;;)
        pause();
    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
    alarm(1);
}

結果:

QQ截圖20130715110837

因為在使用時,alarm只設定為傳送一次訊號,如果要多次傳送,就要多次使用alarm呼叫,所以可在訊號處理函式中呼叫alarm()實現每隔指點秒受傳送SIGALRM訊號。

二,可重入函式

為了增強程式的穩定性,在訊號處理函式中應使用可重入函式。

訊號處理程式中應當使用可再入(可重入)函式(注:所謂可重入函式是指一個可以被多個任務呼叫的過程,任務在呼叫時不必擔心資料是否會出錯)。因為程式在收到訊號後,就將跳轉到訊號處理函式去接著執行。如果訊號處理函式中使用了不可重入函式,那麼訊號處理函式可能會修改原來程式中不應該被修改的資料,這樣程式從訊號處理函式中返回接著執行時,可能會出現不可預料的後果。不可再入函式在訊號處理函式中被視為不安全函式。

滿足下列條件的函式多數是不可再入的:(1)使用靜態的資料結構,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函式實現時,呼叫了malloc()或者free()函式;(3)實現時使用了標準I/O函式的。

The Open Group視下列函式為可再入的:

_exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown() 、close()、creat()、dup()、dup2()、execle()、execve()、fcntl()、fork()、fpathconf()、fstat()、fsync()、getegid()、 geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、kill()、link()、lseek()、mkdir()、mkfifo()、 open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、setgid()、setpgid()、setsid()、setuid()、 sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、 umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。

即使訊號處理函式使用的都是"安全函式",同樣要注意進入處理函式時,首先要儲存errno的值,結束時,再恢復原值。因為,訊號處理過程中,errno值隨時可能被改變。另外,longjmp()以及siglongjmp()沒有被列為可再入函式,因為不能保證緊接著兩個函式的其它呼叫是安全的。

示例程式:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


typedef struct
{
    int a;
    int b;
} TEST;

TEST g_data;

void handler(int sig);
int main(int argc, char *argv[])
{
    TEST zeros = {0, 0};
    TEST ones = {1, 1};
    if (signal(SIGALRM, handler) == SIG_ERR)
        ERR_EXIT("signal error");

    g_data = zeros;
    alarm(1);
    for (;;)
    {
        g_data = zeros;
        g_data = ones;
    }
    return 0;
}

void unsafe_fun()
{
    printf("%d %d\n", g_data.a, g_data.b);
}

void handler(int sig)
{
    unsafe_fun();
    alarm(1);
}

結果:

QQ截圖20130715111634

也是程式建立了一個結構體,設定一個全域性變數,然後在main函式中利用兩個區域性變數分別給全域性變數賦值,由於這個賦值操作是可被中斷的,如以上每一次結構體的賦值可視為兩步:

g_data.a=zeros.a;

g_data.b=zeros.b;

所以當g_data.a=one.a;做完然後被中斷,跑去執行處理函式,在處理函式中呼叫unsafe_fun()列印全域性變數值,可知結果是全域性變數a值變了,b值還是之前的沒來的及改變,所以出現了1,0

所以結果不確定

相關文章