linux系統編程之信號(二):一些信號發送函數和不同精度的睡眠

一、kill, raise, killpg 函數

int kill(pid_t pid, int sig);
int raise(int sig);
int killpg(int pgrp, int sig);
kill命令是調用kill函數實現的,kill函數可以給一個指定的進程或進程組發送指定的信號,其中kill 函數的pid 參數取值不同表示不同含義,具體可man 一下。raise函數可以給當前進程發送指定的信號(自己給自己發信號)。killpg 函數可以給進程組發生信號。這三個函數都是成功返回0,錯誤返回-1。下面是個小程序示例:

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.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(SIGUSR1, handler) == SIG_ERR)
        ERR_EXIT("signal error");
    pid_t pid = fork();
    if (pid == -1)
        ERR_EXIT("fork error");

    if (pid == 0)
    {
        /*
            pid = getpgrp(); // 得到進程組pid
            kill(-pid, SIGUSR1); //向進程組發送信號
        */
        killpg(getpgrp(), SIGUSR1);
        exit(EXIT_SUCCESS); // 子進程處理完信號才退出
    }

    int n = 5;
    do
    {
        n = sleep(n); // sleep會被信號打斷,返回unslept的時間
    }
    while (n > 0);

    return 0;
}

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

/* raise(sig) 等價於 kill(getpid(), sig) 給自己發送信號 */

程序中註冊信號在fork之前,故子進程也會繼承,在子進程中對進程組發送了信號,故信號處理函數會被調用兩次:

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./kill 
recv a sig=10
recv a sig=10
simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ 

因爲sleep 函數會被信號處理函數打斷(RETURN VALUE: Zero if the requested time has elapsed, or the number of seconds left to sleep, if the call was interrupted  by  a signal handler.),如果我們想讓其睡夠5s, 則可以用一個while循環判斷其返回值。這裏需要注意的是輸出兩次recv之後繼續睡眠的時間是不一定的,也有可能是5s,即信號處理函數在調用sleep之前已經被調用(子進程先被系統調度執行),sleep未被中斷。也表明一點:只要接收到信號,信號處理函數可以在任意某個時刻被調用,不僅僅只在進程主動調用sleep, pause等函數(讓cpu去調度運行其他程序)的時候,cpu一直都在進行進程的調度,進行用戶空間和內核空間的切換, 當某個時刻要從內核返回到該進程的用戶空間代碼繼續執行之前,首先就會處理PCB中記錄的信號。


二、alarm, abort 函數
#include <unistd.h>
unsigned int alarm(unsigned int seconds);

調用alarm函數可以設定一個鬧鐘,也就是告訴內核在seconds秒之後給當前進程發SIGALRM信號,該信號的默認處理動作是終止當前進程。這個函數的返回值是0或者是以前設定的鬧鐘時間還餘下的秒數。打個比方,某人要小睡一覺,設定鬧鐘爲30分鐘之後響,20分鐘後被人吵醒了,還想多睡一會兒,於是重新設定鬧鐘爲15分鐘之後響,“以前設定的鬧鐘時間還餘下的時間”就是10分鐘。如果seconds值爲0,表示取消以前設定的鬧鐘,函數的返回值仍然是以前設定的鬧鐘時間還餘下的秒數。

示例程序:

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.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); //過了n秒會產生SIGALRM信號
    for (; ;)
        pause();

    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
    alarm(1); // 間接遞歸調用handler
}
輸出測試:

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./alarm 
recv a sig=14
recv a sig=14
recv a sig=14
recv a sig=14
recv a sig=14

....................

即每隔1s就會發送一個SIGALRM信號,其實alarm函數時間到時只發送一次信號,我們在信號處理函數中再次調用alarm函數,造成不斷的信號發送。

#include <stdlib.h>
void abort(void);
abort函數使當前進程接收到SIGABRT信號而異常終止。就像exit函數一樣,abort函數總是會成功的,所以沒有返回值。

三、setitimer 和不同精度的睡眠
1、首先來看三種不同的時間結構,如下:
time_t; /* seconds */
struct timeval {
long    tv_sec;         /* seconds */
long    tv_usec;        /* microseconds */
};
struct timespec {
time_t tv_sec;        /* seconds */
long   tv_nsec;       /* nanoseconds */
};
microseconds就是微秒, nanoseconds就是納秒。

2、三種不同精度的睡眠
unsigned int sleep(unsigned int seconds);
int usleep(useconds_t usec);
int nanosleep(const struct timespec *req, struct timespec *rem);
3、setitimer函數

包含頭文件<sys/time.h> 
功能setitimer()比alarm功能強大,會間歇性產生時鐘,支持3種類型的定時器。
原型:int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
參數:第一個參數which指定定時器類型;第二個參數是結構體itimerval的一個實例;第三個參數若不爲空則返回先前定時unslept的時間。
返回值:成功返回0,失敗返回-1。


參數 which的取值:

ITIMER_REAL:經過指定的時間後,內核將發送SIGALRM信號給本進程 
ITIMER_VIRTUAL :程序在用戶空間執行指定的時間後,內核將發送SIGVTALRM信號給本進程 
ITIMER_PROF :進程在用戶空間執行和內核空間執行時,時間計數都會減少,通常與ITIMER_VIRTUAL共用,代表進程在用戶空間與內核空間中運行指定時間後,內核將發送SIGPROF信號給本進程。


itimerval結構體:
struct itimerval {
               struct timeval it_interval; /* next value */
               struct timeval it_value;    /* current value */
           };
其中timeval 結構體如前面所示。

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

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

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

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

    struct timeval  tv_interval = {1, 0}; //以後每次延時1s
    struct timeval tv_value = {5, 0};//第一次延時5s
    struct itimerval it;
    /* Timers decrement from it_value to zero, generate a signal, and reset to it_interval */
    it.it_interval = tv_interval;
    /*  The element it_value is set to the amount of time remaining on the timer */
    it.it_value = tv_value;
    setitimer(ITIMER_REAL, &it, NULL); //間歇性地產生時鐘
    /*
        for (; ;)
            pause();
    */
    int i;
    for (i = 0; i < 10000; i++) ;
    struct itimerval oit;
    // 上面循環後也許定時時間還沒到,重新設置時鐘,並將先前時鐘剩餘值通過oit傳出
    setitimer(ITIMER_REAL, &it, &oit);
    /*  getitimer(ITIMER_REAL, &oit); */
    printf("%d %d %d %d\n", (int)oit.it_interval.tv_sec, (int)oit.it_interval.tv_usec,
           (int)oit.it_value.tv_sec, (int)oit.it_value.tv_usec);

    return 0;
}
如果我們把43,44行代碼打開,後面的都註釋掉,則會第一次經過5s 輸出recv語句,以後每次經過1s 就輸出recv語句。而如上程序所示的話,輸出爲simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./setitimer 
1 0 4 999924
即先是設定了鬧鐘,for了一個循環後重新設定鬧鐘,此次通過第三個參數返回上次時鐘unslept的時間,即本來再過oit這麼多時間就會產生信號,通過getitimer也可以獲得,但getitimer不會重新設置時鐘。

轉載於:http://blog.csdn.net/jnu_simba/article/details/8944647

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章