還是先看看Linux中用戶空間怎麼運用的,用戶空間編程實例如下:
#include<signal.h>
#include<stdio.h>
#include<unistd.h>
/*下面為兩個新的信號操作函數*/
void handler(int sig)
{
printf("Receive signal :%u\n",sig);
}
void sigroutine(int num)
{
switch(num)
{
case 1:
printf("SIGUP signal\n");
break;
case 2:
printf("SIGINT signal\n");
break;
case 3:
printf("SIGQUIT signal\n");
break;
default:
break;
}
return;
}
int main(void)
{
struct sigaction sa;
int count;
sa.sa_handler=handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags=0;
printf("task id is:%d\n",getpid());
/*下面四條語句為相應的信號設置新的處理方法*/
sigaction(SIGTERM,&sa,NULL);
signal(SIGHUP,sigroutine);
signal(SIGINT,sigroutine);
signal(SIGQUIT,sigroutine);
while(1)
{
sigsuspend(&sa.sa_mask);/*阻塞,一直等待信號到達*/
printf("loop\n");
}
return 0;
}
可見,用戶空間調用了很多系統調用來實現信號的編程,為了弄清楚他的內在原理,決定將內核中的實現做一個大致的梳理。為了理清思路,我們由內核中實現信號操作涉及的關鍵數據結構關系畫出下圖,我們看到,內核中的數據結構實現較簡單,主要分兩部分,一部分用於信號操作(即handler),由進程的sighand字段開始;另一部分用於信號的掛起,由進程的signal和pending字段索引。
由關系圖,我們大致觀其實現原理如下:
1, 進程的所有信號(現為32個)由一個數組task->sighand->action[]保存,數組的下標即為信號的ID,比如SIGQUIT等,每個操作由一個數據結構sigaction實現,該字段的sa_handler即為實現的操作;
2, 進程對掛起的信號有兩種隊列,一種為所有進程共享的。該隊列的每一項為一個sigqueue結構,通過該結構info字段的si_signo等屬性可以定位到對應的信號ID。其中sigset_t結構為一個32位整型,用於定位到ID,即類似位圖的表示。
我們看幾個最基本的操作於內核中的實現。