一、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不會重新設置時鐘。