QNX® Neutrino 進程間通信編程之Signals

介紹

Interprocess Communication(IPC,進程間通信)在QNX Neutrino從一個嵌入式實時系統向一個全面的POSIX系統轉變起着至關重要的作用。IPC是將在內核中提供各種服務的進程內聚在一起的粘合劑。在QNX中,消息傳遞是IPC的主要形式,也提供了其他的形式,除非有特殊的說明,否則這些形式也都是基於本地消息傳遞而實現的。

將更高級別的 IPC 服務(如通過我們的消息傳遞實現的管道和 FIFO)與其宏內核對應物進行比較的基準測試表明性能相當。

QNX Neutrino提供以下形式的IPC:

Service: Implemented in:
Message-passing Kernel
Pules Kernel
Signals Kernel
Event Delivery External process
POSIX message queues External process
Shared memory Process manager
Pipes External process
FIFOs External process

本篇幅介紹的是POSIX IPC Signals。

Signals

進程屬於用戶應用程序或操作系統。 我們需要一種機制讓內核和這些進程協調它們的活動。 一種方法是讓流程在發生重要事件時通知其他人。 這就是爲什麼我們有信號。信號基本上是一種單向通知。 信號可以由內核發送給一個進程,一個進程發送給另一個進程,或者一個進程發送給它自己。

下圖試圖捕捉硬件級異常(包括系統調用)和信號與應用程序進程的關係。

許多硬件中斷由它們自己的設備驅動程序處理; 偶爾中斷會啓動一個信號。 內存故障通常由分頁處理或用於更新內部內核狀態——但有時會是真正的分段違規。 雖然大多數系統調用是爲同一個進程提供服務,但有幾個能夠發送信號作爲進程間通信,尤其是在父子進程之間。

信號是同步的還是異步的?

當信號產生時,它們可以被認爲是同步的synchronous 或異步的asynchronous

synchronous信號是由於指令導致不可恢復的錯誤(例如非法地址訪問)而產生的。 這些信號被髮送到引起它的線程。 這些也稱爲陷阱,因爲它們也會導致進入內核陷阱處理程序的陷阱

asynchronous信號在當前執行上下文的外部。 從另一個進程發送 SIGKILL 就是一個例子。 這些也稱爲軟件中斷。

信號的典型生命週期是什麼?

一個信號經歷三個階段:

Generation:信號可以由內核或任何進程生成。無論誰生成信號,都會將其發送到特定進程。信號由其編號表示,沒有額外的數據或參數。因此,信號是輕量級的。但是,可以爲 POSIX 實時信號傳遞額外的數據。可以生成信號的系統調用和函數包括 raise、kill、killpg、pthread_kill、tgkill 和 sigqueue。

Delivery:一個信號被認爲是掛起的,直到它被交付。通常,內核會盡快將信號傳遞給進程。但是,如果進程阻塞了信號,它將保持掛起狀態直到解除阻塞。

Processing:一旦信號被傳遞,它就會以多種方式之一進行處理。每個信號都有一個關聯的默認操作:忽略信號;或終止進程,有時使用核心轉儲;或停止/繼續該過程。對於非默認行爲,可以調用處理程序函數。通過 sigaction 函數指定這些發生的確切情況。

標準或常規信號

通過kill -l我們可以查詢到系統下包含的64個信號是什麼?

bspserver@ubuntu:~/workspace/$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	
Signal range Description
1–56 56 POSIX signals (including traditional UNIX signals) 56個POSIX信號(其中包含傳統的UNIX信號)
41–56 16 POSIX realtime signals (SIGRTMIN to SIGRTMAX) 16個POSIX實時信號
57–64 Eight special-purpose QNX Neutrino signals, some of which are named (e.g., SIGSELECT). They're always masked, and attempts to unmask them are ignored.

下面只介紹我們常用的信號

Signal Value Action Comment
SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process 如果終端界面檢測到一個連接斷開,則將此信號送給與該終端相關的控制進程。
SIGINT 2 Term Interrupt from keyboard 當用戶按中斷鍵(一般採用DELETE或Ctrl-C)時,終端驅動程序產生此信號並送至前臺進程組中的每一個進程。
SIGQUIT 3 Core Quit from keyboard 當用戶在終端上按退出鍵(一般採用Ctrl-\)時,產生此信號,並送至前臺進程組中的所有進程。
SIGILL 4 Core Illegal Instruction 此信號指示進程已執行一條非法硬件指令。
SIGABRT 6 Core Abort signal from abort(3) 調用abort函數時產生此信號,進程異常終止。
SIGFPE 8 Core Floating point exception 此信號表示一個算術運算異常,例如除以0,浮點溢出等。
SIGKILL 9 Term Kill signa 這是兩個不能被捕捉或忽略信號中的一個。
SIGUSR1 10 Term User-defined signal 1 這是一個用戶定義的信號,可用於應用程序。
SIGSEGV 11 Core Invalid memory reference 指示進程進行了一次無效的存儲訪問。名字SEGV表示“段違例(segmentation violation)”。
SIGUSR2 12 Term User-defined signal 2 這是一個用戶定義的信號,可用於應用程序。
SIGPIPE 13 Term Broken pipe: write to pipe with no readers 如果在讀進程已終止時寫管道,則產生此信號。
SIGALRM 14 Term Timer signal from alarm(2) 當用alarm函數設置得定時器超時時,產線此信號。若有setitimer函數設置得間隔時間已經超時時,也產生此信號。
SIGTERM 15 Term Termination signal 這是由kill ( 1 )命令發送的系統默認終止信號。
SIGCHLD 17 Ign Child stopped or terminated 在一個進程終止或停止時,SIGCHLD信號被送給其父進程。按系統默認,將忽略此信號。如果父進程希望瞭解其子進程的這種狀態改變,則應捕捉此信號。信號捕捉函數中通常要調用wait函數以取得子進程I D和其終止狀態。
SIGCONT 18 Cont Continue executing, if stopped. 此作業控制信號送給需要繼續運行的處於停止狀態的進程。如果接收到此信號的進程處於停止狀態,則系統默認動作是使該進程繼續運行,否則默認動作是忽略此信號。
SIGSTOP 19 Stop Stop process 這是一個作業控制信號,它停止一個進程。它類似於交互停止信號( SIGTSTP ),但是SIGSTOP不能被捕捉或忽略。
SIGTSTP 120 Stop Stop typed at terminal 交互停止信號,當用戶在終端上按掛起鍵 (一般採用Ctrl-Z)時,終端驅動程序產生此信號。
SIGTTIN 21 Stop Terminal input for background process 當一個後臺進程組進程試圖讀其控制終端時,終端驅動程序產生此信號。
SIGTTOU 22 Stop Terminal output for background process 當一個後臺進程組進程試圖寫其控制終端時產生此信號。

POSIX signals API

下表總結了常用的POSIX信號API接口:

Function Description
kill() Send a signal to a process
sigemptyset() Set a set of signals to NULL
sigfillset() Set all signals in a set
sigaddset() Add an individual signal to a set
sigdelset() Delete an individual signal from a set
sigismember() Test whether a signal is member of a set
sigaction() Set/Examine action for a given signal.
pthread_sigmask() Set/Examine signal mask for a pthread
sigprocmask() Set/Examine signal mask for a process
sigpending() Examine pending signals
sigsuspend() Wait for a signal
sigwait() Accept a signal
pthread_kill() Send a signal to a given thread
alarm() Schedule delivery of an alarm signal
pause() Suspend process execution
sleep() Delay process execution

signal函數

#include <signal.h>
#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)())0
#define SIG_IGN (void (*)())1

void ( * signal( int sig, void ( * func)(int) ) )( int );
                                  返回:成功則爲以前的信號處理配置,若出錯則爲 SIG_ERR

sig參數是常見的標準進程信號表中的信號名。func的值是:(a)常數SIG_IGN,或(b)常數SIG_DFL,或(c)當接到此信號後要調用的函數的地址。如果指定SIG_IGN,則向內核表示忽略此信號。(記住有兩個信號SIGKILL和SIGSTOP不能忽略。)如果指定SIG_DFL,則表示接到此信號後的動作是系統默認動作。當指定函數地址時,我們稱此爲捕捉此信號。我們稱此函數爲信號處理程序(signal handler)或信號捕捉函數(signal-catching function)。

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
static void sig_usr(int); /* one handler for both signals */
int 
main(void) 
{ 
	 if (signal(SIGUSR1, sig_usr) == SIG_ERR) 
		  perror("can't catch SIGUSR1"); 
	  if (signal(SIGUSR2, sig_usr) == SIG_ERR) 
		   perror("can't catch SIGUSR2"); 
	   for ( ; ; ) 
		    pause(); 
} 
static void 
sig_usr(int signo) /* argument is signal number */ 
{ 
	 if (signo == SIGUSR1) 
		  printf("received SIGUSR1\n"); 
	  else if (signo == SIGUSR2) 
		   printf("received SIGUSR2\n"); 
	   else 
		    printf("received signal %d\n", signo); 
}

我們使該程序在後臺運行,並且用kill(1)命令將信號送給它。kill(1)命令和kill(2)函數只是將一個信號送給一個進程或進程組。該信號是否終止該進程則取決於該信號的類型,以及該進程是否安排了捕捉該信號。

$ a.out &         #在後臺啓動進程
[1]    4720       #作業控制shell打印作業號和進程ID
$ kill -USR1 4720 #向該進程發送SIGUSR1
received SIGUSR1
$ kill -USR2 4720 #向該進程發送SIGUSR2
received SIGUSR2
$ kill 4720       #向該進程發送SIGTERM
[1] + Terminated a.out &

當向該進程發送SIGTERM信號後,該進程就終止,因爲它不捕捉此信號,而對此信號的系統默認動作是終止。

sigaction函數

sigaction函數的功能是檢查或修改 (或兩者)與指定信號相關聯的處理動作。

#include <signal.h>

struct sigaction {
void (*sa_handler)(int); /* addr of signal handler, */
/* or SIG_IGN, or SIG_DFL */
sigset_t sa_mask; /* additional signals to block */
int sa_flags; /* signal options, Figure 10.16 */
    /* 影響信號行爲的標誌:
    SA_NOCLDSTOP 不要在父節點上爲通過 SIGSTOP 停止的子節點生成 SIGCHLD。 該標誌僅在信號爲 SIGCHLD 時使用。
    SA_NOCLDWAIT 不要在子進程終止時創建殭屍進程或狀態信息。
    SA_NODEFER   不要在進入處理程序時自動阻塞信號。
    SA_RESETHAND 將處理程序重置爲 SIG_DFL 並在進入信號處理程序時清除 SA_SIGINFO。 在 QNX Neutrino 中,如果您設置 
                 SA_RESETHAND,sigaction() 和 SignalAction() 的行爲就像您還設置了 SA_NODEFER。
                 如果將 SA_RESETHAND 設置爲 SIGILL 或 SIGTRAP,則這些函數將忽略它。
    SA_SIGINFO   排隊這個信號。 默認設置是不對傳遞給進程的信號進行排隊。 如果信號未排隊,並且在運行之前在進程或線程上
                 多次設置相同的信號,則只會傳遞最後一個信號。 如果您設置了 SA_SIGINFO 標誌,信號將排隊,並且全部被傳送。
/* alternate handler */
void (*sa_sigaction)(int, siginfo_t *, void *);
};

int sigaction( int sig, const struct sigaction * act, struct sigaction * oact );

其中,參數sig是要檢測或修改具體動作的信號的編號數。若 act指針非空,則要修改其動作。如果oact指針非空,則系統返回該信號的原先動作。

註冊信號處理函數是通過系統調用signal()、sigaction()。其中signal()在可靠信號系統調用的基礎上實現, 是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實 現:sys_signal以 sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優於signal()主要體現在支持信號帶有參數。

可重入函數

POSIX.1說明了保證可再入的函數。下表列出了這些可再入函數。

         
abort faccessat linkat select socketpair
accept fchmod listen sem_post stat
access fchmodat lseek send symlink
aio_error fchown lstat sendmsg symlinkat
aio_return fchownat mkdir sendto tcdrain
aio_suspend fcntl mkdirat setgid tcflow
alarm fdatasync mkfifo setpgid tcflush
bind fexecve mkfifoat setsid tcgetattr
cfgetispeed fork mknod setsockopt tcgetpgrp
cfgetospeed fstat mknodat setuid tcsendbreak
cfsetispeed fstatat open shutdown tcsetattr
cfsetospeed fsync openat sigaction tcsetpgrp
chdir ftruncate pause sigaddset time
chmod futimens pipe sigdelset timer_getoverrun
chown getegid poll sigemptyset timer_gettime
clock_gettime geteuid posix_trace_event sigfillset timer_settime
close getgid pselect sigismember times
connect getgroups raise signal umask
creat getpeername read sigpause uname
dup getpgrp readlink sigpending unlink
dup2 getpid readlinkat sigprocmask unlinkat
execl getppid recv sigqueue utime
execle getsockname recvfrom sigset utimensat
execv getsockopt recvmsg sigsuspend utimes
execve getuid rename sleep wait
_Exit kill renameat sockatmark waitpid
_exit link rmdir socket write

kill和raise函數

kill函數將信號發送給進程或進程組。raise函數則允許進程向自身發送信號。

#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig);
                             兩個函數返回:若成功則爲 0,若出錯則爲-1

kill的pid參數有四種不同的情況:

pid > 0 將信號發送給進程ID爲pid的進程。

pid == 0 將信號發送給其進程組ID等於發送進程的進程組ID,而且發送進程有許可權向其發送信號的所有進程。

pid < 0 將信號發送給其進程組ID等於pid絕對值,而且發送進程有許可權向其發送信號的所有進程。如上所述一樣,“所有進程”並不包括系統進程集中的進程。

pid == -1 (QNX Neutrino 7.0 或更高版本)該進程有權發送該信號的所有進程。

kill函數用法

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sig_handler(int sig)
{
    printf("Inside handler function \n");
}

int main()
{
    pid_t pid;
    signal(SIGUSR1,sig_handler);  // Register signal handler
    printf("Inside main function \n");
    pid=getpid();                 // Process ID of itself
    kill(pid,SIGUSR1);
    printf("Inside main function \n");
    return 0; 
}

raise函數用法

#include <stdio.h>
#include <signal.h>

void sig_handler(int sig)
{
    printf("Inside handler function \n");
}

int main()
{
    signal(SIGUSR1,sig_handler);  // Register signal handler
    printf("Inside main function \n");
    raise(SIGUSR1);
    printf("Inside main function \n");
    return 0;
}

運行結果如下:

Inside main function 
Inside handler function 
Inside main function 

alarm和pause函數

使用alarm函數可以設置一個時間值(鬧鐘時間),在將來的某個時刻該時間值會被超過。當所設置的時間值被超過後,產生 SIGALRM信號。如果不忽略或不捕捉此信號,則其默認動作是終止該進程。

#include <unistd.h>

unsigned int alarm( unsigned int seconds );
                                           返回:0或以前設置的鬧鐘時間的餘留秒數

參數seconds的值是秒數,經過了指定的seconds秒後會產生信號SIGALRM。要了解的是,經過了指定秒後,信號由內核產生,由於進程調度的延遲,進程得到控制能夠處理該信號還需一段時間。

每個進程只能有一個鬧鐘時間。如果在調用 alarm時,以前已爲該進程設置過鬧鐘時間,而且它還沒有超時,則該鬧鐘時間的餘留值作爲本次al arm函數調用的值返回。以前登記的鬧鐘時間則被新值代換。

如果有以前登記的尚未超過的鬧鐘時間,而且 seconds值是0,則取消以前的鬧鐘時間,其餘留值仍作爲函數的返回值。

雖然SIGALRM的默認動作是終止進程,但是大多數使用鬧鐘的進程捕捉此信號。如果此時進程要終止,則在終止之前它可以執行所需的清除操作。

pause函數使調用進程掛起直至捕捉到一個信號。

#include <unistd.h>

int pause( void );
                   返回:-1,errno設置爲EINTR

只有執行了一個信號處理程序並從其返回時,pause才返回。在這種情況下,pause返回-1,errno設置爲EINTR。

利用alarm來實現個不完善的sleep

#include	<setjmp.h>
#include	<signal.h>
#include	<unistd.h>

static jmp_buf	env_alrm;

static void
sig_alrm(int signo)
{
	longjmp(env_alrm, 1);
}

unsigned int
sleep2(unsigned int seconds)
{
	if (signal(SIGALRM, sig_alrm) == SIG_ERR)
		return(seconds);
	if (setjmp(env_alrm) == 0) {
		alarm(seconds);		/* start the timer */
		pause();			/* next caught signal wakes us up */
	}
	return(alarm(0));		/* turn off timer, return unslept time */
}

在一個捕捉其他信號的程序中調用 sleep2

#include "apue.h"

unsigned int	sleep2(unsigned int);
static void		sig_int(int);

int
main(void)
{
	unsigned int	unslept;

	if (signal(SIGINT, sig_int) == SIG_ERR)
		err_sys("signal(SIGINT) error");
	unslept = sleep2(5);
	printf("sleep2 returned: %u\n", unslept);
	exit(0);
}

static void
sig_int(int signo)
{
	int				i, j;
	volatile int	k;

	/*
	 * Tune these loops to run for more than 5 seconds
	 * on whatever system this test program is run.
	 */
	printf("\nsig_int starting\n");
	for (i = 0; i < 300000; i++)
		for (j = 0; j < 4000; j++)
			k += i * j;
	printf("sig_int finished\n");
}

運行結果

$ ./tsleep2 
^C
sig_int starting
sig_int finished
sleep2 returned: 2   ##表示alarm被Ctrl-C 信號喚醒後alarm還剩2s,剩餘時間得多殺取決於sig_int處理了多長時間。

信號集

typedef struct {

  unsigned long sig [_NSIG_WORDS];

  } sigset_t
    
#include <signal.h>

int sigemptyset( sigset_t *set );    //初始化由set指定的信號集,信號集裏面的所有信號被清空,相當於64爲置0
int sigfillset( sigset_t *set );     //調用該函數後,set指向的信號集中將包含POSIX支持的64種信號,相當於64爲都置1;
int sigaddset( sigset_t *set, int signo );    //在set指向的信號集中加入signum信號,相當於將給定信號所對應的位置1;
int sigdelset( sigset_t *set, int signo );    //在set指向的信號集中刪除signum信號,相當於將給定信號所對應的位置0;
                                           四個函數返回:若成功則爲0,若出錯則爲-1
int sigismember( const sigset_t *set, int signo );
//判定信號signum是否在set指向的信號集中,相當於檢查給定信號所對應的位是0還是1。
                                                   返回:若真則爲1,若假則爲0

函數sigemptyset初始化由set指向的信號集,使排除其中所有信號。函數 sigfillset初始化由set指向的信號集,使其包括所有信號。所有應用程序在使用信號集前,要對該信號集調用sigemptyse t或sigfillset一次。這是因爲C編譯程序將不賦初值的外部和靜態度量都初始化爲 0,而這是否與給定系統上信號集的實現相對應並不清楚。一旦已經初始化了一個信號集,以後就可在該信號集中增、刪特定的信號。函數 sigaddset將一個信號添加到現存集中, sigdelset則從信號集中刪除一個信號。對所有以信號集作爲參數的函數,都向其傳送信號集地址。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
void print_sigset(sigset_t *set);
int main(void)
{
    sigset_t myset;
    sigemptyset(&myset);
    sigaddset(&myset,SIGINT);
    sigaddset(&myset,SIGQUIT);
    sigaddset(&myset,SIGUSR1);
    sigaddset(&myset,SIGRTMIN);
    print_sigset(&myset);

    return 0;

}
void print_sigset(sigset_t *set)
{
    int i;
    printf("Binary:");
    for(i = 1; i < NSIG; ++i){
        if(sigismember(set,i))
            printf("1");
        else
            printf("0");
    }
    putchar('\n');
    printf("Hex:");
	i = SIGRTMAX;
	do {
		int x = 0;
		i -= 4;
		if (sigismember(set, i+1)) x |= 1;
		if (sigismember(set, i+2)) x |= 2;
		if (sigismember(set, i+3)) x |= 4;
		if (sigismember(set, i+4)) x |= 8;
		printf("%x", x);
	} while (i >= 4); 
    putchar('\n');
}

運行結果如下:

bspserver@bspserver-SH:~/WorkSpace/apue.3e/signals$ gcc sigset.c -o sigset
bspserver@bspserver-SH:~/WorkSpace/apue.3e/signals$ ./sigset 
Binary:0110000001000000000000000000000001000000000000000000000000000000
Hex:0000000200000206

sigprocmask函數

調用函數sigprocmask可以檢測或更改(或兩者)進程的信號屏蔽字。

#include <signal.h>

int sigprocmask( int how, const sigset_t *set, sigset_t *oset );
                                                                 返回:若成功則爲0,若出錯則爲-1

首先,oset是非空指針,進程的當前信號屏蔽字通過 oset返回。其次,若 set是一個非空指針,則參數how指示如何修改當前信號屏蔽字。表說明了how可選用的值。SIG_BLOCK是或操作,而SIG_SETMASK則是賦值操作。

how 說明
SIG_BLOC 該進程新的信號屏蔽字是其當前信號屏蔽字和 set指向信號集的並集。 set包含了我們希望阻塞的附加信號。
SIG_UNBLOC 該進程新的信號屏蔽字是其當前信號屏蔽字和set所指向信號集的交集。 set包含了我們希望解除阻塞的信號。
SIG_SETMASK 該進程新的信號屏蔽是set指向的值。

如果set是個空指針,則不改變該進程的信號屏蔽字, how的值也無意義。如果在調用sigprocmask後有任何未決的、不再阻塞的信號,則在 sigprocmask返回前,至少將其中之一遞送給該進程。

你能解釋一下阻塞和解除阻塞信號的概念嗎?

每個進程都可以指定是否要阻止特定信號。如果被阻塞並且信號確實發生,操作系統會將信號保持爲掛起狀態。一旦進程解除阻塞,信號就會被傳遞。當前阻塞的信號集稱爲信號掩碼。無限期地阻塞信號是沒有意義的。爲此,進程可以在信號傳遞後忽略該信號。

一個進程阻塞的信號不會影響其他進程,其他進程可以正常接收信號。

可以使用 sigprocmask(單線程)或 pthread_sigmask(多線程)設置信號掩碼。當一個進程有多個線程時,可以在每個線程的基礎上阻塞一個信號。信號將被傳遞到任何一個沒有阻塞它的線程。本質上,信號處理程序是每個進程,信號掩碼是每個線程。

sigpending函數

sigpending返回對於調用進程被阻塞不能遞送和當前未決的信號集。該信號集通過 set參數返回。

#include <signal.h>

int sigpending( sigset_t *set );
                                 返回:若成功則爲0,若出錯則爲-1

sigpending實例

#include "apue.h"

static void	sig_quit(int);

int
main(void)
{
	sigset_t	newmask, oldmask, pendmask;

	if (signal(SIGQUIT, sig_quit) == SIG_ERR)
		err_sys("can't catch SIGQUIT");

	/*
	 * Block SIGQUIT and save current signal mask.
	 */
	sigemptyset(&newmask);
	sigaddset(&newmask, SIGQUIT);
	if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
		err_sys("SIG_BLOCK error");

	sleep(5);	/* SIGQUIT here will remain pending */

	if (sigpending(&pendmask) < 0)
		err_sys("sigpending error");
	if (sigismember(&pendmask, SIGQUIT))
		printf("\nSIGQUIT pending\n");

	/*
	 * Restore signal mask which unblocks SIGQUIT.
	 */
	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
		err_sys("SIG_SETMASK error");
	printf("SIGQUIT unblocked\n");

	sleep(5);	/* SIGQUIT here will terminate with core file */
	exit(0);
}

static void
sig_quit(int signo)
{
	printf("caught SIGQUIT\n");
	if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
		err_sys("can't reset SIGQUIT");
}

然後該進程再睡眠5秒鐘。如果在此期間再產生退出信號,那麼它就會使該進程終止,因爲在上次捕捉到該信號時,已將其處理方式設置爲默認動作。此時如果鍵入終端退出字符 Ctrl-\,則輸出“QUIT ( coredump )”信息,表示進程因接到 SIGQUIT而終止,但是在core文件中保存了與進程有關的信息(該信息是由shell發現其子進程異常終止時打印的)。

bspserver@bspserver-SH:~/WorkSpace/apue.3e/signals$ ./critical 
^\                                               #產生信號一次(在5秒之內)
SIGQUIT pending                                  #從sleep返回後
caught SIGQUIT                                   #在信號處理程序中
SIGQUIT unblocked                                #從sigprocmask返回後
^\Quit (core dumped)                             #再次產生信號
bspserver@bspserver-SH:~/WorkSpace/apue.3e/signals$ ./critical 
^\^\^\^\^\^\^\^\^\^\                             #產生信號10次(在5秒之內)
SIGQUIT pending
caught SIGQUIT                                   #只產生信號一次
SIGQUIT unblocked
^\Quit (core dumped)                             #再產生信號

注意,在第二次運行該程序時,在進程睡眠期間使 SIGQUIT信號產生了10次,但是解除了對該信號的阻塞後,只向進程傳送一次SIGQUIT。從中可以看出在此係統上沒有將信號進行排隊。

sigsuspend函數

#include <signal.h>

int sigsuspend( const sigset_t *sigmask );

sigqueue函數

#include <signal.h>

union sigval {
    int sival_int;
    void *sival_ptr;
};

int sigqueue ( pid_t pid, int signo, const union sigval value );

下面是sigaction,sigsuspend與sigqueue聯合用例:

#include <unistd.h>
#include <signal.h>
#include <siginfo.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

// 該程序將 POSIX 1003.1b (SIGRTMIN+1) 定義的實時信號之一定義爲 SIG_STOP_CHILD 信號
#define SIG_STOP_CHILD SIGRTMIN+1

int main()
{
pid_t pid;
sigset_t newmask, oldmask;

    if ((pid = fork()) == 0) {      /*Child*/
        struct sigaction action;
        void catchit();
        // 子進程對信號集的初始化和進程信號掩碼的創建
        sigemptyset(&newmask);
        sigaddset(&newmask, SIG_STOP_CHILD);
        sigprocmask(SIG_BLOCK, &newmask, &oldmask);
        // 通過在 sigaction 結構的 sa_flags 成員中指定 SA_SIGINFO 標誌,子進程指示關聯的信號將使用實時排隊信令行爲。
        action.sa_flags = SA_SIGINFO;
        action.sa_sigaction = catchit;
        // 子進程調用 sigaction 函數來設置 catchit 信號處理程序,以便在進程收到 SIG_STOP_CHILD 信號時調用它。
        if (sigaction(SIG_STOP_CHILD, &action, NULL) == -1) {
            perror("sigusr: sigaction");
            _exit(1);
        }
        // 調用 sigsuspend 函數來等待信號。
        sigsuspend(&oldmask);
    }
    else {                             /* Parent */
        // 父進程聲明一個 sigval 聯合。該聯合的成員可以是整數或指針,具體取決於父級發送到其子級信號處理程序的值。
        // 在這種情況下,該值是一個整數。
        union sigval sval;
        sval.sival_int = 1;
        int stat;
        sleep(1);
        // 父進程調用 sigqueue 函數向子進程發送 SIG_STOP_CHILD 信號和一個信號值。
        sigqueue(pid, SIG_STOP_CHILD, sval);
        pid = wait(&stat);
        printf("Child exit status = %d\n", WEXITSTATUS(stat));
        _exit(0);
    }
}
void catchit(int signo, siginfo_t *info, void *extra)
{
       void *ptr_val = info->si_value.sival_ptr;
       int int_val = info->si_value.sival_int;
       printf("Signal %d, value %d  received from parent\n", signo, int_val);
       _exit(0);
}

sigwaitinfo函數

#include <signal.h>

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) */
}

int sigwaitinfo ( const sigset_t *set, siginfo_t *info );

使用 sigwaitinfo 函數的實例

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#define SHOW_PEND 0

int main()
{
    sigset_t        set, pend;
    int             i, sig, sigq_max, numsigs = 0;
    siginfo_t       info;
    int             SIG = SIGRTMIN;
    

    //sigq_max = sysconf(_SC_SIGQUEUE_MAX); 
    //信號隊列最大值爲65535
    sigq_max = 10;
    sigemptyset(&set);
    sigaddset(&set, SIG);
    sigprocmask(SIG_SETMASK, &set, NULL);
    printf("\nNow create a child to send signals...\n");
    if (fork() == 0) {      /* child */
        union sigval sval;
        pid_t parent = getppid();
        printf("Child will signal parent %d\n", parent);
        for (i = 0; i < sigq_max; i++) {
            sval.sival_int = i;
            if (sigqueue(parent, SIG, sval) < 0)
                perror("sigqueue");
        # if SHOW_PEND
        sleep(1);
        #endif
        }
        exit(1);
    }
    printf("Parent sigwait for child to queue signal...\n");
    #if SHOW_PEND
    sleep(3);
    #endif
    sigpending(&pend);
    printf("Is signal pending: %s\n",
           sigismember(&pend, SIG) ? "yes" : "no");
    for (i = 0; i < sigq_max; i++) {
        sig = sigwaitinfo(&set, &info);
        if (sig < 0) {
            perror("sigwait");
            exit(1);
        }
        printf("Main woke up after signal %d\n", sig);
        printf("signo = %d, pid = %d, uid = %d, val = %d,\n",
               info.si_signo, info.si_pid, info.si_uid, info.si_int);
        numsigs++;
    }
    printf("Main: done after %d signals.\n", numsigs);
}

在此示例中,子進程向其父進程發送系統允許排隊的最大信號數。 當一個 SIG 信號被傳遞給它時,父級會對其進行計數並打印一條信息性消息。 在收到 _SC_SIGQUEUE_MAX 信號後,父進程打印一條消息,指示它已收到的信號數量。

分別查看一下SHOW_PEND爲0和1的運行結果:

// SHOW_PEND 爲 0 時
Now create a child to send signals...
Parent sigwait for child to queue signal...
Is signal pending: no
Child will signal parent 1989
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 0,
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 1,
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 2,
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 3,
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 4,
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 5,
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 6,
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 7,
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 8,
Main woke up after signal 34
signo = 34, pid = 1990, uid = 1000, val = 9,
Main: done after 10 signals.

// SHOW_PEND 爲 1 時
Now create a child to send signals...
Parent sigwait for child to queue signal...
Child will signal parent 1996
Is signal pending: yes
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 0,
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 1,
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 2,
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 3,
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 4,
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 5,
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 6,
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 7,
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 8,
Main woke up after signal 34
signo = 34, pid = 1997, uid = 1000, val = 9,
Main: done after 10 signals.

abort函數

#include <stdlib.h>

void abort( void );

一個POSIX.1 abort函數的可靠實現

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void
abort(void) /* POSIX-style abort() function */
{
    sigset_t mask;
    struct sigaction action;
    
    /* Caller can’t ignore SIGABRT, if so reset to default */
    sigaction(SIGABRT, NULL, &action);
    if (action.sa_handler == SIG_IGN) {
        action.sa_handler = SIG_DFL;
        sigaction(SIGABRT, &action, NULL);
    }
    if (action.sa_handler == SIG_DFL)
        fflush(NULL); /* flush all open stdio streams */
    
    /* Caller can’t block SIGABRT; make sure it’s unblocked */
    sigfillset(&mask);
    sigdelset(&mask, SIGABRT); /* mask has only SIGABRT turned off */
    sigprocmask(SIG_SETMASK, &mask, NULL);
    kill(getpid(), SIGABRT); /* send the signal */
    
    /* If we’re here, process caught SIGABRT and returned */
    fflush(NULL); /* flush all open stdio streams */
    action.sa_handler = SIG_DFL;
    sigaction(SIGABRT, &action, NULL); /* reset to default */
    sigprocmask(SIG_SETMASK, &mask, NULL); /* just in case ... */
    kill(getpid(), SIGABRT); /* and one more time */
    exit(1); /* this should never be executed ... */
}

sleep函數

#include <unistd.h>

unsigned int sleep( unsigned int seconds );

一個POSIX.1 sleep函數的可靠實現

#include "apue.h"

static void
sig_alrm(int signo)
{
	/* nothing to do, just returning wakes up sigsuspend() */
}

unsigned int
sleep(unsigned int seconds)
{
	struct sigaction	newact, oldact;
	sigset_t			newmask, oldmask, suspmask;
	unsigned int		unslept;

	/* set our handler, save previous information */
	newact.sa_handler = sig_alrm;
	sigemptyset(&newact.sa_mask);
	newact.sa_flags = 0;
	sigaction(SIGALRM, &newact, &oldact);

	/* block SIGALRM and save current signal mask */
	sigemptyset(&newmask);
	sigaddset(&newmask, SIGALRM);
	sigprocmask(SIG_BLOCK, &newmask, &oldmask);

	alarm(seconds);
	suspmask = oldmask;

	/* make sure SIGALRM isn't blocked */
	sigdelset(&suspmask, SIGALRM);

	/* wait for any signal to be caught */
	sigsuspend(&suspmask);

	/* some signal has been caught, SIGALRM is now blocked */

	unslept = alarm(0);

	/* reset previous action */
	sigaction(SIGALRM, &oldact, NULL);

	/* reset signal mask, which unblocks SIGALRM */
	sigprocmask(SIG_SETMASK, &oldmask, NULL);
	return(unslept);
}

UNIX環境高級編程源碼環境配置

《UNIX環境高級編程(第三版)》被譽爲UNIX編程“聖經”。這版本包含了70很多*版POSIX.1標準的提升插口,包含POSIX多線程I/O、旋轉鎖、天然屏障(barrier)和POSIX信號量。下面告訴大家如何獲取appu.3e的源碼。如何把整個書中源碼編譯運行起來。

wget http://www.apuebook.com/src.3e.tar.gz  # 直接從網絡獲取UNIX環境高級編程(第三版)書中源碼
tar  -zxvf  src.3e.tar.gz                   # 下載後,進行解壓
cd apue.3e/                                 # 進入源碼目錄
make     
# 在這個過程中,你會看到最後由於can,t find-lbsd而不能make成功,解決辦法是添加libbsd.a的靜態鏈接庫 
sudo apt-get install libbsd-dev             # 安裝依賴庫
make(again)                                 # 在編譯成功的基礎上,我們進行安裝apue.h文件及其對應的靜態鏈接庫libapue.a
# 複製make出來的頭文件及靜態庫
sudo cp  ./include/apue.h   /usr/include/
sudo cp  ./lib/libapue.a   /usr/local/lib/

複雜實例

統計Ctrl-C信號次數

#include <stdio.h>     /* standard I/O functions                         */
#include <unistd.h>    /* standard unix functions, like getpid()         */
#include <signal.h>    /* signal name macros, and the signal() prototype */

/* first, define the Ctrl-C counter, initialize it with zero. */
int ctrl_c_count = 0;
#define	CTRL_C_THRESHOLD	5

/* the Ctrl-C signal handler */
void catch_int(int sig_num)
{
    sigset_t mask_set;	/* used to set a signal masking set. */
    sigset_t old_set;	/* used to store the old mask set.   */

    /* re-set the signal handler again to catch_int, for next time */
    signal(SIGINT, catch_int);
    /* mask any further signals while we're inside the handler. */
    sigfillset(&mask_set);
    sigprocmask(SIG_SETMASK, &mask_set, &old_set);
    
    /* increase count, and check if threshold was reached */
    ctrl_c_count++;
    printf("The number of ^C's is %d\n",ctrl_c_count);
    if (ctrl_c_count >= CTRL_C_THRESHOLD) {
	char answer[30];

	/* prompt the user to tell us if to really exit or not */
	printf("\nRealy Exit? [y/N]: ");
	fflush(stdout);
	fgets(answer,80,stdin);
	if (answer[0] == 'y' || answer[0] == 'Y') {
	    printf("\nExiting...\n");
	    fflush(stdout);
	    exit(0);
	}
	else {
	    printf("\nContinuing\n");
	    fflush(stdout);
	    /* reset Ctrl-C counter */
	    ctrl_c_count = 0;
	}
    }
    /* restore the old signal mask */
    sigprocmask(SIG_SETMASK, &old_set, NULL);
}

/* the Ctrl-Z signal handler */
void catch_suspend(int sig_num)
{
    sigset_t mask_set;	/* used to set a signal masking set. */
    sigset_t old_set;	/* used to store the old mask set.   */

    /* re-set the signal handler again to catch_suspend, for next time */
    signal(SIGTSTP, catch_suspend);
    /* mask any further signals while we're inside the handler. */
    sigfillset(&mask_set);
    sigprocmask(SIG_SETMASK, &mask_set, &old_set);

    /* print the current Ctrl-C counter */
    printf("\n\nSo far, '%d' Ctrl-C presses were counted\n\n", ctrl_c_count);
    fflush(stdout);

    /* restore the old signal mask */
    sigprocmask(SIG_SETMASK, &old_set, NULL);
}

int main(int argc, char* argv[])
{
    /* set the Ctrl-C and Ctrl-Z signal handlers */
    signal(SIGINT, catch_int);
    signal(SIGTSTP, catch_suspend);

    /* enter an infinite loop of waiting for signals */
    for ( ;; )
	pause();

    return 0;
}

代碼運行結果如下:

^CThe number of ^C's is 1
^CThe number of ^C's is 2
^CThe number of ^C's is 3
^CThe number of ^C's is 4
^CThe number of ^C's is 5

Realy Exit? [y/N]: N

Continuing
^CThe number of ^C's is 1
^CThe number of ^C's is 2
^CThe number of ^C's is 3
^CThe number of ^C's is 4
^CThe number of ^C's is 5

Realy Exit? [y/N]: 

USR1, USR2, HUP, INT多信號處理

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>

extern int errno;

void usr1(int sig) ;
void usr2(int sig) ;
void hup(int sig) ;
void intr(int sig);

int loop=1;
pid_t mypid;
int counter=0;

main () {

     char buf[100];
     int len;


     mypid = getpid();

     if (  SIG_ERR == signal(SIGUSR1, usr1)) {
	perror("Unable to set signal handler");
     }
     if (  SIG_ERR == signal(SIGUSR2, usr2)) {
	perror("Unable to set signal handler");
     }
     if (  SIG_ERR == signal(SIGHUP, hup)) {
	perror("Unable to set signal handler");
     }
     if (  SIG_ERR == signal(SIGINT, intr)) {
	perror("Unable to set signal handler");
     }


     printf("The process (%d) is set for a USR1, USR2, HUP, INT\n",mypid);
     while (loop) {
      
     }
     printf("Out of the loop, type something in\n");


doover:
     len = read(0,buf,100);
     buf[len-1]='\0';
     printf("We were able to read a <%s>, len = %d\n",buf,len);
     if (errno == EINTR) {
	errno = 0;
        printf("In doover loop\n");
	goto doover;
     }

}

void usr1(int sig){    
    printf("In usr1, got %d\n",sig);
    raise(SIGUSR1);
    usleep(100);
   
     if (  SIG_ERR == signal(SIGUSR1,SIG_DFL)) {
	perror("Unable to set signal handler");
    
     }
  
}

void usr2(int sig){
    
     printf("In usr2, got %d\n",sig);
}

void hup(int sig){
    
     printf("In hup, got %d\n",sig);
     printf (" Resetting SIGHUP handler \n");
     kill (mypid,sig);
     printf("counter=%d\n",counter++);
     if (  SIG_ERR == signal(SIGHUP, SIG_DFL)) {
	perror("Unable to set signal handler");
     }
     printf("counter=%d\n",counter--);
     sleep(3);
         printf("After sleep in hup\n");
}

void intr(int sig){
    
    printf("In intr, got %d,error=%d\n",sig,errno);
     if (  SIG_ERR == signal(SIGINT, intr)) {
	perror("Unable to set signal handler");
     }
    loop=0;
}

代碼運行結果如下:

$ ./signals_process 
The process (3048) is set for a USR1, USR2, HUP, INT
^CIn intr, got 2,error=0                   // 鍵盤輸入Ctrl-C觸發INT信號
Out of the loop, type something in
Hello
We were able to read a <Hello>, len = 6

$ ./signals_process 
The process (3051) is set for a USR1, USR2, HUP, INT     // 在另一個終端輸入 kill -SIGUSR1 3051
In usr1, got 10
User defined signal 1

$ ./signals_process 
The process (3056) is set for a USR1, USR2, HUP, INT    // 在另一個終端輸入 kill -SIGUSR2 3056
In usr2, got 12

$ ./signals_process 
The process (3059) is set for a USR1, USR2, HUP, INT    // 在另一個終端輸入 kill -SIGHUP 3059
In hup, got 1
 Resetting SIGHUP handler 
counter=0
counter=1
After sleep in hup
Hangup

父子進程互發信號

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SIG_STOP_CHILD SIGUSR1
#define SIG_STOP_PARENT SIGUSR1

void sig_handler_parent(int sig)
{
    printf("Parent : Signal %d received from child\n", sig);
}

void catchit(int signo)
{
       printf("Child : Signal %d received from parent\n", signo);
       sleep(1);
       kill(getppid(),SIG_STOP_PARENT);  // // 子進程調用kill函數向父進程發送SIG_STOP_CHILD信號。
       _exit(0);
}

int main()
{
pid_t pid;
sigset_t newmask, oldmask;

    if ((pid = fork()) == 0) {/*Child*/
        struct sigaction action;
        void catchit();

        sigemptyset(&newmask);   // newmask sigset_t 結構初始化爲零
        sigaddset(&newmask, SIG_STOP_CHILD);  // 設置newmask sigset_t結構中SIG_STOP_CHILD信號對應的位。
        sigprocmask(SIG_BLOCK, &newmask, &oldmask); //阻塞SIG_STOP_CHILD 信號

        action.sa_flags = 0;
        action.sa_handler = catchit; 
        //註冊信號處理函數catchit
        if (sigaction(SIG_STOP_CHILD, &action, NULL) == -1) {
            perror("sigusr: sigaction");
            _exit(1);
        }
        //SIG_STOP_CHILD 信號被解除阻塞並且子進程暫停,直到 SIG_STOP_CHILD 信號被傳遞(並導致其 catchit 信號處理程序運行。
        sigsuspend(&oldmask);
    }
    else {                             /* Parent */
        int stat;
        signal(SIGUSR1,sig_handler_parent);  // 父進程註冊信號處理函數sig_handler_parent
        sleep(1);
        kill(pid, SIG_STOP_CHILD);  // 父進程調用kill函數向子進程發送SIG_STOP_CHILD信號。
        pid = wait(&stat);
        // 它等待子進程終止,並在它終止時打印子進程的退出狀態。然而,在這發生之前,孩子的 catchit 信號處理程序必須運行。
        printf("Child exit status = %d\n", WEXITSTATUS(stat));
        _exit(0);
     }
}

代碼運行結果如下:

Child : Signal 10 received from parent
Parent : Signal 10 received from child
Child exit status = 0

參考文獻: Programming with POSIX Threads
Introduction To Unix Signals Programming (kent.edu)
UNIX環境高級編程
COMP25111 - Unix Signals(manchester.ac.uk)
Linux Signals (devopedia.org)
System Architecture - Interprocess Communication (IPC)

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