歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

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。下面是個小程序示例:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
#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,表示取消以前設定的鬧鐘,函數的返回值仍然是以前設定的鬧鐘時間還余下的秒數。

示例程序:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
#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 結構體如前面所示。

示例程序如下:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
#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不會重新設置時鐘。

Copyright © Linux教程網 All Rights Reserved