信號是進程在運行過程中,由自身產生或由進程外部發過來的消息(事件)。信號是硬件中斷的軟件模擬(軟中斷)。每個信號用一個整型常量宏表示,以SIG開頭,比如SIGCHLD( 子進程結束向父進程發送的一個信號 )、SIGINT(Ctrl+c)等,它們在系統頭文件<signal.h>中定義,也可以通過在shell下鍵入kill –l查看信號列表,或者鍵入man 7 signal查看更詳細的說明。
信號的生成來自內核,讓內核生成信號的請求來自3個地方:
l 用戶:用戶能夠通過輸入CTRL+c(2號信號)、Ctrl+\ ( 產生的是三號信號,SIGQUIT ),或者是終端驅動程序分配給信號控制字符的其他任何鍵來請求內核產生信號;
l 內核:當進程執行出錯時,內核會給進程發送一個信號,例如非法段存取(內存訪問違規)、浮點數溢出等;
由進程的某個操作產生的信號稱為同步信號(synchronous signals),例如除0;由像用戶擊鍵這樣的進程外部事件產生的信號叫做異步信號(asynchronous signals)。
進程接收到信號以后,可以有如下3種選擇進行處理:
l 接收默認處理:接收默認處理的進程通常會導致進程本身消亡。例如連接到終端的進程,用戶按下CTRL+c,將導致內核向進程發送一個SIGINT的信號,進程如果不對該信號做特殊的處理,系統將采用默認的方式處理該信號,即終止進程的執行; signal(SIGINT,SIG_DFL);
l 忽略信號:進程可以通過代碼,顯示地忽略某個信號的處理,例如:signal(SIGINT,SIG_IGN);但是某被些信號是不能忽略的,例如9號信號;
l 捕捉信號並處理:進程可以事先注冊信號處理函數,當接收到信號時,由信號處理函數自動捕捉並且處理信號。
可以用函數signal注冊一個信號捕捉函數。原型為:
#include <signal.h>
typedef void (*sighandler_t)(int); //函數指針(鈎子,也就是回調函數)
sighandler_t signal(int signum, sighandler_t handler);
signal的第1個參數signum表示要捕捉的信號,第2個參數是個函數指針,表示要對該信號進行捕捉的函數,該參數也可以是SIG_DFL(表示交由系統缺省處理,相當於白注冊了)或SIG_IGN(表示忽略掉該信號而不做任何處理)。
sighandler_t 是信號捕捉函數,由 signal 函數注冊,注冊以后,在整個進程運行過程中均有效,並且對不同的信號可以注冊同一個信號捕捉函數。該函數只有一個整型參數,表示信號值。signal.c | signal_sleep.c |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_func(int sig)
{
printf("sig num=%d\n",sig);
}
int main()
{
if( signal(SIGINT,sig_func)==SIG_ERR )
{
perror("signal");
return -1;
}
while(1);
return 0;
}
|
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_func(int sig)
{
printf("before sleep ,sig=%d is coming\n",sig);
sleep(3);
printf("after sleep ,sig=%d is coming\n",sig);
}
int main()
{
if( signal(SIGINT,sig_func)==SIG_ERR )
{
perror("signal");
return -1;
}
if( signal(SIGQUIT,sig_func)==SIG_ERR )
{
perror("signal");
return -1;
}
while(1);
return 0;
}
|
signal_ign.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_func(int sig)
{
printf("sig=%d is coming\n",sig);
}
int main()
{
if(signal(SIGINT,SIG_IGN)==SIG_ERR)
{
perror("signal");
return -1;
}
if(
signal(SIGQUIT,sig_func)==SIG_ERR)
{
perror("signal");
return -1;
}
while(1);
return 0;
}
|
![]() |
在signal處理機制下,還有許多特殊情況需要考慮:
1、 注冊一個信號處理函數,並且處理完畢一個信號之后,是否需要重新注冊,才能夠捕捉下一個信號;(不需要)
2、 如果信號處理函數正在處理信號,並且還沒有處理完畢時,又發生了一個同類型的信號,這時該怎么處理;(接着執行),后續相同信號忽略(會多執行一次)。
3、 如果信號處理函數正在處理信號,並且還沒有處理完畢時,又發生了一個不同類型的信號,這時該怎么處理;(跳轉去執行另一個信號,之后再執行剩下的沒有處理完的信號)
4、 如果程序阻塞在一個系統調用 ( 如read(...) ) 時,發生了一個信號,這時是讓系統調用返回錯誤再接着進入信號處理函數,還是先跳轉到信號處理函數,等信號處理完畢后,系統調用再返回。(后者)
signal_read.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_func(int sig)
{
printf("before sleep ,sig=%d is coming\n",sig);
sleep(3);
printf("after sleep ,sig=%d is coming\n",sig);
}
int main()
{
if( signal(SIGINT,sig_func )==SIG_ERR )
{
perror("signal");
return -1;
}
if( signal(SIGQUIT,sig_func)==SIG_ERR )
{
perror("signal");
return -1;
}
char buf[128]={0};
read(0,buf,sizeof(buf));
printf("buf=%s\n",buf);
return 0;
}
|
函數原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //函數調用成功,將返回0,否則返回-1
sigaction也用於注冊一個信號處理函數
參數signum為需要捕捉的信號
參數act是一個結構體,里面包含信號處理函數地址、處理方式等信息
參數oldact是一個傳出參數,sigaction函數調用成功后,oldact里面包含以前對signum的處理方式的信息,通常為NULL
如果函數調用成功,將返回0,否則返回-1
結構體 struct sigaction (注意名稱與函數sigaction相同)的原型為:
struct sigaction
{
void (*sa_handler)(int); //老類型的信號處理函數指針,不用,我們用最新的
void (*sa_sigaction)(int, siginfo_t *, void *); //新類型的信號處理函數指針
sigset_t sa_mask; //將要被阻塞的信號集合(sigset_t)
int sa_flags; //信號處理方式掩碼 ( SA_SIGINFO )
void (*sa_restorer)(void); //保留,不要使用
};
|
該結構體的各字段含義及使用方式:
1、字段sa_handler是一個函數指針,用於指向原型為void handler(int)的信號處理函數地址,即老類型的信號處理函數(如果用這個再將sa_flags = 0,就等同於signal()函數)
2、字段sa_sigaction也是一個函數指針,用於指向原型為:
void handler(int iSignNum, siginfo_t *pSignInfo, void *pReserved); 的信號處理函數,即新類型的信號處理函數
該函數的三個參數含義為:
iSignNum :傳入的信號
pSignInfo:與該信號相關的一些信息,它是個結構體
pReserved:保留,現沒用,通常為NULL
3、字段 sa_handler 和 sa_sigaction 只應該有一個生效,如果想采用老的信號處理機制,就應該讓 sa_handler 指向正確的信號處理函數,並且讓字段 sa_flags 為0;否則應該讓 sa_sigaction 指向正確的信號處理函數,並且讓字段sa_flags包含SA_SIGINFO選項
4、字段sa_mask是一個包含信號集合的結構體,該結構體內的信號表示在進行信號處理時,將要被阻塞的信號。針對 sigset_t 結構體,有一組專門的函數對它進行處理,它們是:
sigset_t sa_mask; //將要被阻塞的信號集合
#include <signal.h>
int sigemptyset(sigset_t *set); //清空信號集合set
int sigfillset(sigset_t *set); //將所有信號填充進set中
int sigaddset(sigset_t *set, int signum); //往set中添加信號signum
int sigdelset(sigset_t *set, int signum); //從set中移除信號signum
int sigismember(const sigset_t *set, int signum); //判斷signum是否包含在set中(是:返回1,否:0)
int sigpending(sigset_t *set); //將被阻塞的信號集合由參數set指針返回(掛起信號) //成功返回 0 , 失敗返回 -1 .
****** 其中,對於函數sigismember而言,如果signum在set集中,則返回1;不在,則返回0;出錯時返回-1。其他的函數都是成功返回0,失敗返回-1.
例如,如果打算在處理信號SIGINT時,只阻塞對SIGQUIT信號的處理,可以用如下方法:
struct sigaction act;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = newHandler; //newHandler參數指定
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGQUIT);
sigaction(SIGINT,&act,NULL);
|
通過 man sigaction 查看 siginfo_t 的結構體
5、 字段sa_flags是一組掩碼的合成值,指示信號處理時所應該采取的一些行為,各掩碼的含義為:
掩碼 | 描述 |
SA_RESETHAND | 處理完畢要捕捉的信號后,將自動撤消信號處理函數的注冊,即必須再重新注冊信號處理函數,才能繼續處理接下來產生的信號。該選項不符合一般的信號處理流程,現已經被廢棄。 |
SA_NODEFER | 在處理信號時,如果又發生了其它的信號,則立即進入其它信號的處理,等其它信號處理完畢后,再繼續處理當前的信號,即遞規地處理。(不常用)不斷重入,次數不丟失 |
SA_RESTART | 如果在發生信號時,程序正阻塞在某個系統調用,例如調用read()函數,則在處理完畢信號后,接着從阻塞的系統返回。如果不指定該參數,中斷處理完畢之后,read函數讀取失敗。 |
SA_SIGINFO | 指示結構體的信號處理函數指針是哪個有效,如果sa_flags包含該掩碼,則 sa_sigaction 指針有效,否則是 sa_handler 指針有效。(常用) |
sigaction.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void sig(int signum,siginfo_t* p,void* p1)
{
printf("signum=%d is coming\n",signum);
}
int main()
{
struct sigaction act;
bzero(&act,sizeof(act));
act.sa_sigaction=sig;
act.sa_flags=SA_SIGINFO; //act.sa_flags=SA_SIGINFO
|SA_RESTART;
int ret=sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
return -1;
}
while(1); // char buf[128]={0}; read(0,buf,sizeof(buf)); printf("buf=%s\n",buf);
return 0;
}
|
![]() //不設置 act.sa_flags=SA_SIGINFO|SA_RESTART; ![]() //阻塞等待輸入,如果在輸入之前有信號中斷,中斷處理完就直接退出了 //設置 ![]() |
sigaction_mask.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
void sig(int signum,siginfo_t* p,void* p1)
{
printf("before signum=%d is coming\n",signum);
sleep(3);
printf("after signum=%d is coming\n",signum);
}
int main()
{
struct sigaction act;
bzero(&act,sizeof(act));
act.sa_sigaction=sig;
act.sa_flags=SA_SIGINFO|SA_RESTART;
int ret=sigaddset(&act.sa_mask,SIGQUIT);
//sigaction執行中屏蔽SIGQUIT信號,不受影響,執行完后再執行過程中發生的SIGQUIT信號操作
if(-1==ret)
{
perror("sigaddset");
return -1;
}
ret=sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
return -1;
}
while(1);
return 0;
}
|
![]() |
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */
pid_t si_pid;
/* Sending process ID */
uid_t si_uid;
/* Real user ID of sending process */
|
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count ; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */
|
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address (since Linux 2.6.32) */
void *si_call_addr; /* Address of system call instruction (since Linux 3.5) */
int si_syscall; /* Number of attempted system call (since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call (since Linux 3.5) */
}
|
sigaction_siginfo.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void sig(int signum,siginfo_t* p,void* p1)
{
printf("signum=%d is coming\n",signum);
printf("sending signal process id=%d\n",p->si_pid);
printf("sending signal user id=%d\n",p->si_uid);
}
int main()
{
struct sigaction act;
bzero(&act,sizeof(act));
act.sa_sigaction=sig;
act.sa_flags=SA_SIGINFO;
int ret=sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
return -1;
}
while(1);
return 0;
}
|
//本窗口: id都為0,在其他窗口 kil -2 進程號id(表示向該進程發送2號信號(SIGINT)) 進程的id就是它自己,為用戶id為當前登錄用戶 |
3.3. sigprocmask信號阻塞
函數sigaction中設置的被阻塞信號集合只是針對於要處理的信號,例如
struct sigaction act;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
sigaction(SIGINT,&act,NULL);
|
函數sigprocmask是全程阻塞,在sigprocmask中設置了阻塞集合后,被阻塞的信號將不能再被信號處理函數捕捉,直到重新設置阻塞信號集合。
原型為:
參數how 的值為如下3者之一:
a:SIG_BLOCK ,將參數2的信號集合添加到進程原有的阻塞信號集合中
b:SIG_UNBLOCK ,從進程原有的阻塞信號集合移除參數2中包含的信號
c:SIG_SETMASK,重新設置進程的阻塞信號集為參數2的信號集
參數set 為阻塞信號集
參數oldset 是傳出參數,存放進程原有的信號集,通常為NULL
sigprocmask.c | sigpro_pending.c |
#include<stdio.h>
#include<signal.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
void sigFunc(int sig,siginfo_t *p,void *p1)
{
printf("before sleep , signum=%d\n",sig);
sleep(3);
printf("after sleep , signum=%d\n",sig);
}
int main(void)
{
sigset_t set;
memset(&set,0,sizeof(set));
sigaddset(&set,SIGINT);
int ret = sigprocmask(SIG_BLOCK,&set,NULL);
if(-1==ret)
{
perror("sigprocmask");
exit(-1);
}
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_sigaction = sigFunc;
act.sa_flags = SA_SIGINFO | SA_RESTART;
sigemptyset(&act.sa_mask);
ret = sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
exit(-1);
}
//while(1);
sleep(5);
printf("\nafter sleep , unblock SIG_INT\n");
ret=sigismember(&set,SIGINT); //查看信號SIGINT在不在指定集合中
if(ret !=1)
{
return -1;
}
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1);
return 0;
}
|
//將被阻塞的信號集合由參數set指針返回(掛起信號)
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
int ret ;
ret=sigprocmask(SIG_BLOCK,&set,NULL);
if(-1==ret)
{
perror("sigprocmask");
return -1;
}
sleep(5);
sigemptyset(&set); //把集合set清空
ret =sigpending(&set); //把阻塞的信號拿出來放到set中,有阻塞信號返回1,沒有返回0
//這個阻塞不是前面設定好,就會找到,是設置好阻塞,在執行過程中觸發了這個信號,但是沒有處理,內核記錄了這個信號發生。俗稱掛起。
ret = sigismember(&set,SIGINT);
if(1==ret)
{
printf("SIGINT is in the set\n");
}
if(0==ret)
{
printf("SIGINT is not in the set\n");
}
return 0;
}
|
sigaction_mask_pending.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
void sig(int signum,siginfo_t* p,void* p1)
{
printf("before signum=%d\n",signum);
sleep(3);
sigset_t set;
sigemptyset(&set);
sigpending(&set);
if( sigismember(&set,SIGQUIT) )
{
printf("SIGQUIT is come , but pending\n");
}
else
{
printf("SIGQUIT is not come\n");
}
printf("after signum=%d\n",signum);
}
|
int main()
{
struct sigaction act;
bzero(&act,sizeof(act));
act.sa_sigaction=sig;
act.sa_flags=SA_SIGINFO|SA_RESTART;
int ret;
ret=sigaddset(&act.sa_mask,SIGQUIT);
//sigaction執行中屏蔽SIGQUIT信號,不受影響,執行完后在來執行相應操作
if(-1==ret)
{
perror("sigaddset");
return -1;
}
ret=sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
return -1;
}
while(1);
return 0;
}
|
4. 用程序發送信號
kill.c | kill0.c |
#include<sys/types.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("error args\n");
return -1;
}
pid_t pid;
pid=atoi(argv[1]); //傳遞一個進程id號
int ret =kill(pid,SIGINT);
if(-1==ret)
{
perror("kill");
return -1;
}
return 0;
}
|
#include<sys/types.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
if(!fork())
{
printf("I am child\n");
while(1);
}
else
{
printf("I am parent\n");
sleep(10);
int ret=kill(0,SIGINT);
if(-1==ret)
{
perror("kill");
return -1;
}
wait(NULL);
printf("wait is execute\n");
}
return 0;
}
![]() |
5.1.睡眠函數
alarm.c | pause.c |
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
//設定SIGALRM信號的處理行為,alarm(3),while(1)
void sigFunc(int sig)
{
printf("I am sig %d\n",sig);
}
int main(void)
{
signal(SIGALRM,sigFunc);
sleep(3);
//等待3秒
printf("sleep over\n");
alarm(1);
printf("alarm over\n");
while(1);
//3秒時候才產生一個SIGALRM信號,所以一定要等,不然程序結束了,看不到對應輸出了
return 0;
}
![]() |
#include<unistd.h>
int main()
{
pause();
return 0;
}
![]() ![]() |
Linux為每個進程維護3個計時器,分別是真實計時器、虛擬計時器和實用計時器。
l 真實計時器計算的是程序運行的實際時間;---直接
l 虛擬計時器計算的是程序運行在用戶態時所消耗的時間( 可認為是實際時間減掉(系統調用和程序睡眠所消耗)的時間 );---需要了解內核
l 實用計時器計算的是程序處於用戶態和處於內核態所消耗的時間之和。(睡覺就不算)---常用
例如:有一程序運行,在用戶態運行了5秒,在內核態運行了6秒,還睡眠了7秒,則真實計算器計算的結果是18秒,虛擬計時器計算的是5秒,實用計時器計算的是11秒。
用指定的初始間隔和重復間隔時間為進程設定好一個計時器后,該計時器就會定時地向進程發送時鍾信號。3個計時器發送的時鍾信號分別為:SIGALRM , SIGVTALRM 和 SIGPROF 。
用到的函數與數據結構:
#include <sys/time.h>
1. int getitimer(int which, struct itimerval *value); //獲取計時器的設置;成功返回 0 , 失敗返回 -1 ;
參數which 指定哪個計時器,可選項為ITIMER_REAL(真實計時器)、ITIMER_VIRTUAL(虛擬計時器、ITIMER_PROF(實用計時器))
參數value 為一結構體的傳出參數,用於傳出該計時器的初始間隔時間和重復間隔時間
返回值:如果成功,返回0,否則-1
2.int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue); //設置計時器 ; 成功返回 0,失敗返回 -1 ;
參數which 指定哪個計時器,可選項為ITIMER_REAL(真實計時器)、ITIMER_VIRTUAL(虛擬計時器、ITIMER_PROF(實用計時器))
參數value 為一結構體的傳入參數,指定該計時器的初始間隔時間和重復間隔時間
參數ovalue 為一結構體傳出參數,用於傳出以前的計時器時間設置(NULL)。
返回值:如果成功,返回0,否則-1
struct itimerval
{
struct timeval it_interval; /* next value */ //重復間隔
struct timeval it_value; /* current value */ //初始間隔
};
//初始間隔就是隔多少秒,發送SIGALRM信號;重復間隔就是以后每隔多少時間再發送SIGALRM信號
|
struct timeval
{
long tv_sec; /* seconds */ //時間的秒數部分
long tv_usec; /* microseconds */ //時間的微秒部分
};
|
setitimer.c //啟用真實計時器的進行時鍾處理(獲得當前系統時間,並一秒更新一次) |
#include<sys/time.h>
#include<signal.h>
#include<stdio.h>
#include<time.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
void sig_handle(int sig)
{
time_t t;
time(&t); //取當前秒數;
struct tm* p=gmtime(&t); //ctime();很簡單,這里就不用了;用的格林威治時間;
printf("%2d:%2d:%d\n",p->tm_hour,p->tm_min,p->tm_sec);
}
int main()
{
signal(SIGALRM,sig_handle);
kill(0,SIGALRM); //為了設定timer之前,就發一個SIGALRM信號打印時間,看看后面是不是間隔5秒發送SIGALRM信號,我們可以用kill來發送,當前運行程序的窗口id就是0;
//sig_handle(0); //參數隨便傳
struct itimerval rt;
memset(&rt,0,sizeof(rt)); //這里全部賦0,后面就不用設置微妙0了;
rt.it_value.tv_sec=5;
rt.it_interval.tv_sec=2; //設置初始間隔和重復間隔,微秒我們也感應不出來,就不設置了;也可以設置為0 ;
int ret=setitimer(ITIMER_REAL,&rt,NULL);
if(-1==ret)
{
perror("setitimer");
return -1;
}
while(1);
return 0;
}
|
