一、內核如何實現信號的捕捉
如果信號的處理動作是用戶自定義函數,在信號遞達時就調用這個函數,這稱為捕捉信號。由於信號處理函數的代碼是在用戶空間的,處理過程比較復雜,舉例如下:
1. 用戶程序注冊了SIGQUIT信號的處理函數sighandler。
2. 當前正在執行main函數,這時發生中斷或異常切換到內核態。
3. 在中斷處理完畢后要返回用戶態的main函數之前檢查到有信號SIGQUIT遞達。
4. 內核決定返回用戶態后不是恢復main函數的上下文繼續執行,而是執行sighandler函數,sighandler和main函數使用不同的堆棧空間,它們之間不存在調用和被調用的關系,是兩個獨立的控制流程。
1
2 |
(By
default, the signal handler is invoked on the normal process stack. It is possible to arrange that the signal handler uses an alternate stack; see sigaltstack(2) for a discussion of how to do this and when it might be useful.) |
5. sighandler函數返回后自動執行特殊的系統調用sigreturn再次進入內核態。
6. 如果沒有新的信號要遞達,這次再返回用戶態就是恢復main函數的上下文繼續執行了。
上圖出自ULK。
二、sigaction函數
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
sigaction函數可以讀取和修改與指定信號相關聯的處理動作。調用成功則返回0,出錯則返回-1。signo是指定信號的編號。若act指針非空,則根據act修改該信號的處理動作。若oact指針非空,則通過oact傳出該信號原來的處理動作。act和oact指向sigaction結構體: struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
將sa_handler賦值為常數SIG_IGN傳給sigaction表示忽略信號,賦值為常數SIG_DFL表示執行系統默認動作,賦值為一個函數指針表示用自定義函數捕捉信號,或者說向內核注冊了一個信號處理函數,該函數返回值為void,可以帶一個int參數,通過參數可以得知當前信號的編號,這樣就可以用同一個函數處理多種信號。顯然,這也是一個回調函數,不是被main函數調用,而是被系統所調用。
當某個信號的處理函數被調用時,內核自動將當前信號加入進程的信號屏蔽字,當信號處理函數返回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產生,那么它會被阻塞到當前處理結束為止。如果在調用信號處理函數時,除了當前信號被自動屏蔽之外,還希望自動屏蔽另外一些信號,則用sa_mask字段說明這些需要額外屏蔽的信號,當信號處理函數返回時自動恢復原來的信號屏蔽字。
需要注意的是sa_restorer 參數已經廢棄不用,sa_handler主要用於不可靠信號(實時信號當然也可以,只是不能帶信息),sa_sigaction用於實時信號可以帶信息(siginfo_t),兩者不能同時出現。sa_flags有幾個選項,比較重要的有兩個:SA_NODEFER 和 SA_SIGINFO,當SA_NODEFER設置時在信號處理函數執行期間不會屏蔽當前信號;當SA_SIGINFO設置時與sa_sigaction 搭配出現,sa_sigaction函數的第一個參數與sa_handler一樣表示當前信號的編號,第二個參數是一個siginfo_t 結構體,第三個參數一般不用。當使用sa_handler時sa_flags設置為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 kernel 2.6.32) */
}
需要注意的是並不是所有成員都在所有信號中存在定義,有些成員是共用體,讀取的時候需要讀取對某個信號來說恰當的有定義的部分。
下面用sigaction函數舉個小例子:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/************************************************************************* > File Name: process_.c > Author: Simba > Mail: dameng34@163.com > 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[]) { struct sigaction act; act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (sigaction(SIGINT, &act, NULL) < 0) ERR_EXIT("sigaction error"); for (; ;) pause(); return 0; } void handler(int sig) { printf("rev sig=%d\n", sig); } |
simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sigaction
^Crev sig=2
^Crev sig=2
^Crev sig=2
...........................
即按下ctrl+c 會一直產生信號而被處理打印recv語句。
其實我們在前面文章說過的signal 函數是調用sigaction 實現的,而sigaction函數底層是調用 do_sigaction() 函數實現的。可以自己實現一個my_signal 函數,如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/************************************************************************* > File Name: process_.c > Author: Simba > Mail: dameng34@163.com > 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); /* 系統調用signal()實際上調用了sigaction() */ __sighandler_t my_signal(int sig, __sighandler_t handler); int main(int argc, char *argv[]) { my_signal(SIGINT, handler); for (; ;) pause(); return 0; } __sighandler_t my_signal(int sig, __sighandler_t handler) { struct sigaction act; struct sigaction oldact; act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (sigaction(sig, &act, &oldact) < 0) return SIG_ERR; return oldact.sa_handler; // 返回先前的處理函數指針 } void handler(int sig) { printf("rev sig=%d\n", sig); } |
輸出測試是一樣的,需要注意的是 signal函數成功返回先前的handler,失敗返回SIG_ERR。而sigaction 是通過oact 參數返回先前的handler,成功返回0,失敗返回-1。
下面再舉個小例子說明sa_mask 的作用:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
/************************************************************************* > File Name: process_.c > Author: Simba > Mail: dameng34@163.com > 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[]) { struct sigaction act; act.sa_handler = handler; sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask, SIGQUIT); // 在信號處理函數執行期間屏蔽SIGQUIT信號,完畢后會抵達 /* 注意sigprocmask中屏蔽的信號是一直不能抵達的,除非解除了阻塞*/ act.sa_flags = 0; if (sigaction(SIGINT, &act, NULL) < 0) ERR_EXIT("sigaction error"); for (; ;) pause(); return 0; } void handler(int sig) { printf("rev sig=%d\n", sig); sleep(5); } |
先按下ctrl+c ,然后馬上ctrl+\,程序是不會馬上終止的,即等到handler處理完畢SIGQUIT信號才會抵達。
simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sa_mask
^Crev sig=2
^\
5s過后接着才輸出Quit (core dumped),即在信號處理函數執行期間sa_mask集合中的信號被阻塞直到運行完畢。
sa_flags 和 sa_sigaction 參數的示例看這里。
在多線程環境下,編寫信號處理函數需要安全地處理,可以參考這篇文章:
tgkill()發給指定進程中的指定線程;
pthread_kill()由一個線程發給同進程中的另一個線程,實際上是通過封裝tgkill()實現的;
《Linux 多線程應用中如何編寫安全的信號處理函數》
http://www.ibm.com/developerworks/cn/linux/l-cn-signalsec/
參考:《APUE》、《linux c 編程一站式學習》
Linux 多線程應用中如何編寫安全的信號處理函數
Linux 多線程應用中編寫安全的信號處理函數
在開發多線程應用時,開發人員一般都會考慮線程安全,會使用 pthread_mutex
去保護全局變量。如果應用中使用了信號,而且信號的產生不是因為程序運行出錯,而是程序邏輯需要,譬如 SIGUSR1、SIGRTMIN 等,信號在被處理后應用程序還將正常運行。在編寫這類信號處理函數時,應用層面的開發人員卻往往忽略了信號處理函數執行的上下文背景,沒有考慮編寫安全的信號處理函數的一些規則。本文首先介紹編寫信號處理函數時需要考慮的一些規則;然后舉例說明在多線程應用中如何構建模型讓因為程序邏輯需要而產生的異步信號在指定的線程中以同步的方式處理。
線程和信號
Linux 多線程應用中,每個線程可以通過調用 pthread_sigmask()
設置本線程的信號掩碼。一般情況下,被阻塞的信號將不能中斷此線程的執行,除非此信號的產生是因為程序運行出錯如 SIGSEGV;另外不能被忽略處理的信號 SIGKILL 和 SIGSTOP 也無法被阻塞。
當一個線程調用 pthread_create()
創建新的線程時,此線程的信號掩碼會被新創建的線程繼承。
POSIX.1 標准定義了一系列線程函數的接口,即 POSIX threads(Pthreads)。Linux C 庫提供了兩種關於線程的實現:LinuxThreads 和 NPTL(Native POSIX Threads Library)。LinuxThreads 已經過時,一些函數的實現不遵循POSIX.1 規范。NPTL 依賴 Linux 2.6 內核,更加遵循 POSIX..1 規范,但也不是完全遵循。
基於 NPTL 的線程庫,多線程應用中的每個線程有自己獨特的線程 ID,並共享同一個進程ID。應用程序可以通過調用 kill(getpid(),signo)
將信號發送到進程,如果進程中當前正在執行的線程沒有阻礙此信號,則會被中斷,線號處理函數會在此線程的上下文背景中執行。應用程序也可以通過調用 pthread_kill(pthread_t thread, int sig)
將信號發送給指定的線程,則線號處理函數會在此指定線程的上下文背景中執行。
基於 LinuxThreads 的線程庫,多線程應用中的每個線程擁有自己獨特的進程 ID,getpid()
在不同的線程中調用會返回不同的值,所以無法通過調用 kill(getpid(),signo)
將信號發送到整個進程。
下文介紹的在指定的線程中以同步的方式處理異步信號是基於使用了 NPTL 的 Linux C 庫。請參考“Linux 線程模型的比較:LinuxThreads 和 NPTL”和“pthreads(7) - Linux man page”進一步了解 Linux 的線程模型,以及不同版本的 Linux C 庫對 NPTL 的支持。
編寫安全的異步信號處理函數
信號的產生可以是:
- 用戶從控制終端終止程序運行,如 Ctrk + C 產生 SIGINT;
- 程序運行出錯時由硬件產生信號,如訪問非法地址產生 SIGSEGV;
- 程序運行邏輯需要,如調用
kill
、raise
產生信號。
因為信號是異步事件,即信號處理函數執行的上下文背景是不確定的,譬如一個線程在調用某個庫函數時可能會被信號中斷,庫函數提前出錯返回,轉而去執行信號處理函數。對於上述第三種信號的產生,信號在產生、處理后,應用程序不會終止,還是會繼續正常運行,在編寫此類信號處理函數時尤其需要小心,以免破壞應用程序的正常運行。關於編寫安全的信號處理函數主要有以下一些規則:
- 信號處理函數盡量只執行簡單的操作,譬如只是設置一個外部變量,其它復雜的操作留在信號處理函數之外執行;
errno
是線程安全,即每個線程有自己的errno
,但不是異步信號安全。如果信號處理函數比較復雜,且調用了可能會改變errno
值的庫函數,必須考慮在信號處理函數開始時保存、結束的時候恢復被中斷線程的errno
值;
- 信號處理函數只能調用可以重入的 C 庫函數;譬如不能調用
malloc(),free()
以及標准 I/O 庫函數等; - 信號處理函數如果需要訪問全局變量,在定義此全局變量時須將其聲明為
volatile,
以避免編譯器不恰當的優化。
從整個 Linux 應用的角度出發,因為應用中使用了異步信號,程序中一些庫函數在調用時可能被異步信號中斷,此時必須根據errno
的值考慮這些庫函數調用被信號中斷后的出錯恢復處理,譬如socket 編程中的讀操作:
rlen = recv(sock_fd, buf, len, MSG_WAITALL);
if ((rlen == -1) && (errno == EINTR)){
// this kind of error is recoverable, we can set the offset change
//‘rlen’ as 0 and continue to recv
}
在指定的線程中以同步的方式處理異步信號
如上文所述,不僅編寫安全的異步信號處理函數本身有很多的規則束縛;應用中其它地方在調用可被信號中斷的庫函數時還需考慮被中斷后的出錯恢復處理。這讓程序的編寫變得復雜,幸運的是,POSIX.1 規范定義了sigwait()、 sigwaitinfo()
和 pthread_sigmask()
等接口,可以實現:
- 以同步的方式處理異步信號;
- 在指定的線程中處理信號。
這種在指定的線程中以同步方式處理信號的模型可以避免因為處理異步信號而給程序運行帶來的不確定性和潛在危險。
sigwait
sigwait()
提供了一種等待信號的到來,以串行的方式從信號隊列中取出信號進行處理的機制。sigwait(
)只等待函數參數中指定的信號集,即如果新產生的信號不在指定的信號集內,則 sigwait()
繼續等待。對於一個穩定可靠的程序,我們一般會有一些疑問:
- 多個相同的信號可不可以在信號隊列中排隊?
- 如果信號隊列中有多個信號在等待,在信號處理時有沒有優先級規則?
- 實時信號和非實時信號在處理時有沒有什么區別?
筆者寫了一小段測試程序來測試 sigwait
在信號處理時的一些規則。
清單 1. sigwait_test.c
#include <
signal.h
>
#include <
errno.h
>
#include <
pthread.h
>
#include <
unistd.h
>
#include <
sys
/types.h>
void sig_handler(int signum)
{
printf("Receive signal. %d\n", signum);
}
void* sigmgr_thread()
{
sigset_t waitset, oset;
int sig;
int rc;
pthread_t ppid = pthread_self();
pthread_detach(ppid);
sigemptyset(&waitset);
sigaddset(&waitset, SIGRTMIN);
sigaddset(&waitset, SIGRTMIN+2);
sigaddset(&waitset, SIGRTMAX);
sigaddset(&waitset, SIGUSR1);
sigaddset(&waitset, SIGUSR2);
while (1) {
rc = sigwait(&waitset, &sig);
if (rc != -1) {
sig_handler(sig);
} else {
printf("sigwaitinfo() returned err: %d; %s\n", errno, strerror(errno));
}
}
}
int main()
{
sigset_t bset, oset;
int i;
pid_t pid = getpid();
pthread_t ppid;
sigemptyset(&bset);
sigaddset(&bset, SIGRTMIN);
sigaddset(&bset, SIGRTMIN+2);
sigaddset(&bset, SIGRTMAX);
sigaddset(&bset, SIGUSR1);
sigaddset(&bset, SIGUSR2);
if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)
printf("!! Set pthread mask failed\n");
kill(pid, SIGRTMAX);
kill(pid, SIGRTMAX);
kill(pid, SIGRTMIN+2);
kill(pid, SIGRTMIN);
kill(pid, SIGRTMIN+2);
kill(pid, SIGRTMIN);
kill(pid, SIGUSR2);
kill(pid, SIGUSR2);
kill(pid, SIGUSR1);
kill(pid, SIGUSR1);
// Create the dedicated thread sigmgr_thread() which will handle signals synchronously
pthread_create(&ppid, NULL, sigmgr_thread, NULL);
sleep(10);
exit (0);
}
程序編譯運行在 RHEL4 的結果如下:
圖 1. sigwait 測試程序執行結果
從以上測試程序發現以下規則:
- 對於非實時信號,相同信號不能在信號隊列中排隊;對於實時信號,相同信號可以在信號隊列中排隊。
- 如果信號隊列中有多個實時以及非實時信號排隊,實時信號並不會先於非實時信號被取出,信號數字小的會先被取出:如 SIGUSR1(10)會先於 SIGUSR2 (12),SIGRTMIN(34)會先於 SIGRTMAX (64), 非實時信號因為其信號數字小而先於實時信號被取出。
sigwaitinfo()
以及 sigtimedwait()
也提供了與 sigwait()
函數相似的功能。
Linux 多線程應用中的信號處理模型
在基於 Linux 的多線程應用中,對於因為程序邏輯需要而產生的信號,可考慮調用 sigwait()
使用同步模型進行處理。其程序流程如下:
- 主線程設置信號掩碼,阻礙希望同步處理的信號;主線程的信號掩碼會被其創建的線程繼承;
- 主線程創建信號處理線程;信號處理線程將希望同步處理的信號集設為
sigwait()
的第一個參數。 - 主線程創建工作線程。
圖 2. 在指定的線程中以同步方式處理異步信號的模型
代碼示例
以下為一個完整的在指定的線程中以同步的方式處理異步信號的程序。
主線程設置信號掩碼阻礙 SIGUSR1 和 SIGRTMIN 兩個信號,然后創建信號處理線程sigmgr_thread()
和五個工作線程 worker_thread()
。主線程每隔10秒調用 kill()
對本進程發送 SIGUSR1 和 SIGTRMIN 信號。信號處理線程 sigmgr_thread()
在接收到信號時會調用信號處理函數 sig_handler()
。
程序編譯:gcc -o signal_sync signal_sync.c -lpthread
程序執行:./signal_sync
從程序執行輸出結果可以看到主線程發出的所有信號都被指定的信號處理線程接收到,並以同步的方式處理。
清單 2. signal_sync.c
#include <
signal.h
>
#include <
errno.h
>
#include <
pthread.h
>
#include <
unistd.h
>
#include <
sys
/types.h>
void sig_handler(int signum)
{
static int j = 0;
static int k = 0;
pthread_t sig_ppid = pthread_self();
// used to show which thread the signal is handled in.
if (signum == SIGUSR1) {
printf("thread %d, receive SIGUSR1 No. %d\n", sig_ppid, j);
j++;
//SIGRTMIN should not be considered constants from userland,
//there is compile error when use switch case
} else if (signum == SIGRTMIN) {
printf("thread %d, receive SIGRTMIN No. %d\n", sig_ppid, k);
k++;
}
}
void* worker_thread()
{
pthread_t ppid = pthread_self();
pthread_detach(ppid);
while (1) {
printf("I'm thread %d, I'm alive\n", ppid);
sleep(10);
}
}
void* sigmgr_thread()
{
sigset_t waitset, oset;
siginfo_t info;
int rc;
pthread_t ppid = pthread_self();
pthread_detach(ppid);
sigemptyset(&waitset);
sigaddset(&waitset, SIGRTMIN);
sigaddset(&waitset, SIGUSR1);
while (1) {
rc = sigwaitinfo(&waitset, &info);
if (rc != -1) {
printf("sigwaitinfo() fetch the signal - %d\n", rc);
sig_handler(info.si_signo);
} else {
printf("sigwaitinfo() returned err: %d; %s\n", errno, strerror(errno));
}
}
}
int main()
{
sigset_t bset, oset;
int i;
pid_t pid = getpid();
pthread_t ppid;
// Block SIGRTMIN and SIGUSR1 which will be handled in
//dedicated thread sigmgr_thread()
// Newly created threads will inherit the pthread mask from its creator
sigemptyset(&bset);
sigaddset(&bset, SIGRTMIN);
sigaddset(&bset, SIGUSR1);
if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)
printf("!! Set pthread mask failed\n");
// Create the dedicated thread sigmgr_thread() which will handle
// SIGUSR1 and SIGRTMIN synchronously
pthread_create(&ppid, NULL, sigmgr_thread, NULL);
// Create 5 worker threads, which will inherit the thread mask of
// the creator main thread
for (i = 0; i < 5; i++) {
pthread_create(&ppid, NULL, worker_thread, NULL);
}
// send out 50 SIGUSR1 and SIGRTMIN signals
for (i = 0; i < 50; i++) {
kill(pid, SIGUSR1);
printf("main thread, send SIGUSR1 No. %d\n", i);
kill(pid, SIGRTMIN);
printf("main thread, send SIGRTMIN No. %d\n", i);
sleep(10);
}
exit (0);
}
注意事項
在基於 Linux 的多線程應用中,對於因為程序邏輯需要而產生的信號,可考慮使用同步模型進行處理;而對會導致程序運行終止的信號如 SIGSEGV 等,必須按照傳統的異步方式使用 signal()
、 sigaction()
注冊信號處理函數進行處理。這兩種信號處理模型可根據所處理的信號的不同同時存在一個 Linux 應用中:
- 不要在線程的信號掩碼中阻塞不能被忽略處理的兩個信號 SIGSTOP 和 SIGKILL。
- 不要在線程的信號掩碼中阻塞 SIGFPE、SIGILL、SIGSEGV、SIGBUS。
- 確保
sigwait()
等待的信號集已經被進程中所有的線程阻塞。 - 在主線程或其它工作線程產生信號時,必須調用
kill()
將信號發給整個進程,而不能使用pthread_kill()
發送某個特定的工作線程,否則信號處理線程無法接收到此信號。 - 因為
sigwait()
使用了串行的方式處理信號的到來,為避免信號的處理存在滯后,或是非實時信號被丟失的情況,處理每個信號的代碼應盡量簡潔、快速,避免調用會產生阻塞的庫函數。
小結
在開發 Linux 多線程應用中, 如果因為程序邏輯需要引入信號, 在信號處理后程序仍將繼續正常運行。在這種背景下,如果以異步方式處理信號,在編寫信號處理函數一定要考慮異步信號處理函數的安全; 同時, 程序中一些庫函數可能會被信號中斷,錯誤返回,這時需要考慮對 EINTR 的處理。另一方面,也可考慮使用上文介紹的同步模型處理信號,簡化信號處理函數的編寫,避免因為信號處理函數執行上下文的不確定性而帶來的風險。
免責聲明:
- 本文所提出的方式方法僅代表作者個人觀點。
- 本文屬於原創作品,資料來源不超出參考文獻所列范疇,其中任何部分都不會侵犯任何第三方的知識產權。
相關主題
- “Advanced Programming in the UNIX® Environment: Second Edition”,W. Richard Stevens, Stephen A. Rago, 清楚、全面地介紹了Linux線程和信號方面的知識,但是對Linux線程的介紹是基於 LinuxThreads 而不是 NPTL,有些過時。
- 參看“Linux 線程模型的比較:LinuxThreads 和 NPTL”,了解 LinuxThreads 和 NPTL 線程模型的歷史和區別。
- 參看“Signal Handling”, 全面地介紹了 Linux C 庫的信號機制。
- 參看“The Native POSIX Thread Library for Linux”,了解 Linux 的 NPTL 線程庫。
- 參看“Use reentrant functions for safer signal handling”,了解編寫安全的異步信號處理函數的一些規則。
- 在 developerWorks Linux 專區 尋找為 Linux 開發人員(包括 Linux 新手入門)准備的更多參考資料,查閱我們 最受歡迎的文章和教程。
- 在 developerWorks 上查閱所有 Linux 技巧 和 Linux 教程。