# 進程控制,第 1 部分:使用信號等待宏
> 原文:<https://github.com/angrave/SystemProgramming/wiki/Process-Control%2C-Part-1%3A-Wait-macros%2C-using-signals>
## 等待宏
## 我可以找出孩子的退出價值嗎?
您可以找到子項退出值的最低 8 位(`main()`的返回值或`exit()`中包含的值):使用“等待宏” - 通常您將使用“WIFEXITED”和“WEXITSTATUS”。有關更多信息,請參見`wait` / `waitpid`手冊頁。
```c
int status;
pid_t child = fork();
if (child == -1) return 1; //Failed
if (child > 0) { /* I am the parent - wait for the child to finish */
pid_t pid = waitpid(child, &status, 0);
if (pid != -1 && WIFEXITED(status)) {
int low8bits = WEXITSTATUS(status);
printf("Process %d returned %d" , pid, low8bits);
}
} else { /* I am the child */
// do something interesting
execl("/bin/ls", "/bin/ls", ".", (char *) NULL); // "ls ."
}
```
一個進程只能有 256 個返回值,其余的位是信息性的。
## 位移
請注意,無需記住這一點,這只是對狀態變量中信息存儲方式的高級概述
[Android 源代碼](https://android.googlesource.com/platform/prebuilts/gcc/linuxx86/host/i686-linux-glibc2.7-%0A4.6/+/tools_r20/sysroot/usr/include/bits/waitstatus.h)
/ *如果是 WIFEXITED(STATUS),則為低位 8 位的狀態。 * /
#define __WEXITSTATUS(status)(((狀態)&amp; 0xff00)&gt;&gt; 8)
/ *如果 WIFSIGNALED(STATUS),終止信號。 * /
#define __WTERMSIG(status)((status)&amp; 0x7f)
/ *如果 WIFSTOPPED(STATUS),停止孩子的信號。 * /
#define __WSTOPSIG(status)__ WEXITSTATUS(status)
/ *非零,如果 STATUS 表示正常終止。 * /
#define __WIFEXITED(status)(__ WTERMSIG(status)== 0)
內核具有跟蹤信號,退出或停止的內部方式。該 API 是抽象的,以便內核開發人員可以隨意更改。
## 小心。
請記住,只有滿足前提條件時,宏才有意義。這意味著如果發出進程信號,則不會定義進程的退出狀態。宏不會為你做檢查,所以由編程來確保邏輯檢出。
## 信號
## 什么是信號?
信號是內核提供給我們的構造。它允許一個進程異步地向另一個進程發送信號(思考消息)。如果該進程想要接受該信號,則它可以,然后,對于大多數信號,可以決定如何處理該信號。這是一個簡短的列表(非全面的)信號。
| 名稱 | 默認操作 | 常用用例 |
| --- | --- | --- |
| SIGINT | 終止進程(可以捕獲) | 告訴進程好好停止 |
| SIGQUIT | Terminate Process (Can be caught) | 告訴進程嚴厲阻止 |
| SIGSTOP | 停止進程(無法捕獲) | 停止繼續進程 |
| SIGCONT | 繼續一個進程 | 繼續運行進程 |
| SIGKILL | 終止進程(不能忽略) | 你希望你的進程消失了 |
## 我可以暫停我的孩子嗎?
是的!您可以通過發送 SIGSTOP 信號暫時暫停正在運行的進程。如果成功,它將凍結一個進程;即,該進程將不再分配 CPU 時間。
要允許進程恢復執行,請發送 SIGCONT 信號。
例如,這是一個程序,每秒慢慢打印一個點,最多 59 點。
```c
#include <unistd.h>
#include <stdio.h>
int main() {
printf("My pid is %d\n", getpid() );
int i = 60;
while(--i) {
write(1, ".",1);
sleep(1);
}
write(1, "Done!",5);
return 0;
}
```
我們將首先在后臺啟動該進程(注意&amp; at end)。然后使用 kill 命令從 shell 進程發送一個信號。
```
>./program &
My pid is 403
...
>kill -SIGSTOP 403
>kill -SIGCONT 403
```
## 如何從 C 中殺死/停止/暫停我的孩子?
在 C 中,使用`kill` POSIX 調用向孩子發送信號,
```c
kill(child, SIGUSR1); // Send a user-defined signal
kill(child, SIGSTOP); // Stop the child process (the child cannot prevent this)
kill(child, SIGTERM); // Terminate the child process (the child can prevent this)
kill(child, SIGINT); // Equivalent to CTRL-C (by default closes the process)
```
如上所述,shell 中還有一個 kill 命令,例如獲取正在運行的進程列表,然后終止進程 45 和進程 46
```
ps
kill -l
kill -9 45
kill -s TERM 46
```
## 如何檢測“CTRL-C”并正常清理?
我們稍后會回到信號 - 這只是一個簡短的介紹。在 Linux 系統上,如果您有興趣了解更多內容,請參閱`man -s7 signal`(例如,異步信號安全的系統和庫調用列表。
信號處理程序中的可執行代碼有嚴格的限制。大多數庫和系統調用都不是“異步信號安全” - 它們可能不會在信號處理程序中使用,因為它們不是可重入的安全。在單線程程序中,信號處理會暫時中斷程序執行,以執行信號處理程序代碼。假設您的原始程序在執行`malloc`的庫代碼時被中斷; malloc 使用的內存結構不會處于一致狀態。調用`printf`(使用`malloc`)作為信號處理程序的一部分是不安全的,并且將導致“未定義的行為”,即它不再是有用的,可預測的程序。在實踐中,您的程序可能會崩潰,計算或生成不正確的結果或停止運行(“死鎖”),具體取決于您的程序在執行信號處理程序代碼時被中斷時的確切執行情況。
信號處理程序的一個常見用途是設置一個布爾標志,偶爾輪詢(讀取)作為程序正常運行的一部分。例如,
```c
int pleaseStop ; // See notes on why "volatile sig_atomic_t" is better
void handle_sigint(int signal) {
pleaseStop = 1;
}
int main() {
signal(SIGINT, handle_sigint);
pleaseStop = 0;
while ( ! pleaseStop) {
/* application logic here */
}
/* cleanup code here */
}
```
上面的代碼似乎在紙上是正確的。但是,我們需要為編譯器和將執行`main()`循環的 CPU 內核提供提示。我們需要阻止編譯器優化:表達式`! pleaseStop`似乎是一個循環不變量,即永遠為真,因此可以簡化為`true`。其次,我們需要確保`pleaseStop`的值不使用 CPU 寄存器進行高速緩存,而是始終讀取和寫入主存儲器。 `sig_atomic_t`類型意味著變量的所有位都可以作為“原子操作”讀取或修改 - 單個不間斷操作。不可能讀取由一些新位值和舊位值組成的值。
通過使用正確的類型`volatile sig_atomic_t`指定`pleaseStop`,我們可以編寫可移植代碼,其中主循環將在信號處理程序返回后退出。在大多數現代平臺上,`sig_atomic_t`類型可以與`int`一樣大,但在嵌入式系統上可以與`char`一樣小,并且只能表示(-127 到 127)值。
```c
volatile sig_atomic_t pleaseStop;
```
這種模式的兩個例子可以在“COMP”基于終端的 1Hz 4bit 計算機中找到( [https://github.com/gto76/comp-cpp/blob/1bf9a77eaf8f57f7358a316e5bbada97f2dc8987/src/output.c#L121](https://github.com/gto76/comp-cpp/blob/1bf9a77eaf8f57f7358a316e5bbada97f2dc8987/src/output.c#L121) )。使用兩個布爾標志。一個標記`SIGINT`(CTRL-C)的傳送,并正常關閉程序,另一個標記`SIGWINCH`信號以檢測終端調整大小并重繪整個顯示。
- UIUC CS241 系統編程中文講義
- 0. 簡介
- #Informal 詞匯表
- #Piazza:何時以及如何尋求幫助
- 編程技巧,第 1 部分
- 系統編程短篇小說和歌曲
- 1.學習 C
- C 編程,第 1 部分:簡介
- C 編程,第 2 部分:文本輸入和輸出
- C 編程,第 3 部分:常見問題
- C 編程,第 4 部分:字符串和結構
- C 編程,第 5 部分:調試
- C 編程,復習題
- 2.進程
- 進程,第 1 部分:簡介
- 分叉,第 1 部分:簡介
- 分叉,第 2 部分:Fork,Exec,等等
- 進程控制,第 1 部分:使用信號等待宏
- 進程復習題
- 3.內存和分配器
- 內存,第 1 部分:堆內存簡介
- 內存,第 2 部分:實現內存分配器
- 內存,第 3 部分:粉碎堆棧示例
- 內存復習題
- 4.介紹 Pthreads
- Pthreads,第 1 部分:簡介
- Pthreads,第 2 部分:實踐中的用法
- Pthreads,第 3 部分:并行問題(獎金)
- Pthread 復習題
- 5.同步
- 同步,第 1 部分:互斥鎖
- 同步,第 2 部分:計算信號量
- 同步,第 3 部分:使用互斥鎖和信號量
- 同步,第 4 部分:臨界區問題
- 同步,第 5 部分:條件變量
- 同步,第 6 部分:實現障礙
- 同步,第 7 部分:讀者編寫器問題
- 同步,第 8 部分:環形緩沖區示例
- 同步復習題
- 6.死鎖
- 死鎖,第 1 部分:資源分配圖
- 死鎖,第 2 部分:死鎖條件
- 死鎖,第 3 部分:餐飲哲學家
- 死鎖復習題
- 7.進程間通信&amp;調度
- 虛擬內存,第 1 部分:虛擬內存簡介
- 管道,第 1 部分:管道介紹
- 管道,第 2 部分:管道編程秘密
- 文件,第 1 部分:使用文件
- 調度,第 1 部分:調度過程
- 調度,第 2 部分:調度過程:算法
- IPC 復習題
- 8.網絡
- POSIX,第 1 部分:錯誤處理
- 網絡,第 1 部分:簡介
- 網絡,第 2 部分:使用 getaddrinfo
- 網絡,第 3 部分:構建一個簡單的 TCP 客戶端
- 網絡,第 4 部分:構建一個簡單的 TCP 服務器
- 網絡,第 5 部分:關閉端口,重用端口和其他技巧
- 網絡,第 6 部分:創建 UDP 服務器
- 網絡,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:遠程過程調用簡介
- 網絡復習題
- 9.文件系統
- 文件系統,第 1 部分:簡介
- 文件系統,第 2 部分:文件是 inode(其他一切只是數據...)
- 文件系統,第 3 部分:權限
- 文件系統,第 4 部分:使用目錄
- 文件系統,第 5 部分:虛擬文件系統
- 文件系統,第 6 部分:內存映射文件和共享內存
- 文件系統,第 7 部分:可擴展且可靠的文件系統
- 文件系統,第 8 部分:從 Android 設備中刪除預裝的惡意軟件
- 文件系統,第 9 部分:磁盤塊示例
- 文件系統復習題
- 10.信號
- 過程控制,第 1 部分:使用信號等待宏
- 信號,第 2 部分:待處理的信號和信號掩碼
- 信號,第 3 部分:提高信號
- 信號,第 4 部分:信號
- 信號復習題
- 考試練習題
- 考試主題
- C 編程:復習題
- 多線程編程:復習題
- 同步概念:復習題
- 記憶:復習題
- 管道:復習題
- 文件系統:復習題
- 網絡:復習題
- 信號:復習題
- 系統編程笑話