Ctrl+C 運作的原理以及 session, terminal 和 process group

簡短版說明

前陣子遇到一個神祕的問題:

執行程式 P 的時候, 可以用 ^C 中止它。但透過 shell script S 執行 P (S 內只有該程式名稱和參數而已), 就無法用 ^C 中止它。^\ 也是一樣情況, 不過 ^Z 可以正常 suspend。

檢查其它的環境因素發現:

  • kill -INT PID 可以中止 P, 表示程式沒有攔截 SIGINT。
  • stty -a -F TTY_DEVICE 顯示 intr = ^C 且 isig, 表示輸入 ^C 後會被轉譯成 SIGINT。按 ^C 後螢幕上有顯示 ^C。

綜合以上的資訊, 無法理解還有什麼原因會讓 ^C 轉送 SIGINT 失效。

卡了一陣子後, 回頭翻 TLPI ch34 Process Groups, Sessions, and Job Control 以及 ch62 Teriminals 才找到答案。

答案是: P 有用 setpgid() 產生新的 process group, 於是 P 就不是 terminal foreground process group, 而 ^C 只會送給 terminal foreground process group。

詳細說明

在沒有透過 shell script 執行 P 的情況, P 會自成一個新的 process group, 同時也是 terminal foreground process group (這是 shell fork process 後設定的)。之後即使程式有呼叫 setpgid(), 也不會改變 pgid, 因為 P 本來就是自己 process group leader。這時, P 仍然是 terminal foreground process group, 收得到傳給 terminal 的 ^C。

另一方面, 透過 shell script S 執行的情況, S 自成一個新的 process group 也會是 terminal foreground process group (同樣的, 是 shell 設定的)。S 執行 P, P 又用 setpgid() 自己成立一個新的 process group, 這個新的 process group 不是 terminal foreground process group, 也就收不到 ^C 了。

若希望 P 能收到 ^C, 呼叫 setpgid() 後, 記得要呼叫 tcsetpgrp()。

像是這樣:

setpgid(0, 0);
tcsetpgrp(0, getpgid(0));

Terminal 相關知識

TLPI ch62 Terminals 介紹 terminal 相關參數的意思。可以用指令 tty 得知目前 shell 連接的 terminal, 像這樣:

$ tty
/dev/pts/10

之後就可以在別的 terminal 用 stty -F TTY 觀察或修改該 terminal 的設定, 方便了解程式是否有修改 terminal 參數:

$ stty -F /dev/pts/10 -a
speed 38400 baud; rows 47; columns 183; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke

stty 顯示的含意可看 man sttyTLPI ch62。透過 stty 可以確定 intr 和 isig 都有正常運作, 問題不在這裡。

Session, Process Group 相關知識

為了方便管理 process, Linux 有 session 和 process group 兩層結構。一個 session 可以沒有 terminal, 或是有唯一一個 terminal。一個 terminal 也只能屬於一個 session。

一個 session 可有多個 process group, 有些 system call 或指令可操作整個 process group, 所以切成多個 process group 易於管理 process。

比方說:

  • kill(-PID, SIG) 會送 signal SIG 給所有 pgid 為 PID 的 process, 方便一次暫停或殺掉整群 process (shell 用 pipe 串起多個指令時, 這些指令就是同一個 pgid)
  • terminal 收到 ^C、^Z、^\ 這類會轉成 signal 的控制字元時, 會送給整個 terminal foreground process group (有設 isig 時才會轉成 signal, 預設有設 isig)。

若想查看 pid, ppid, pgid, sid, tpgid (terminal foreground process group), 可用 ps -o pid,ppid,pgid,sid,tpgid。細節見 ps 或 man proc, ps 也只是讀 /proc/PID/stat 和 /proc/PID/ 下其它檔案。

完整的介紹見 TLPI ch34 Process Groups, Sessions, and Job Control。

留言

  1. 你標題寫錯囉!是 terminal!

    回覆刪除
    回覆
    1. 閣下好眼力! 已修正。可惜網址就不方便改了 Orz

      刪除

張貼留言

這個網誌中的熱門文章

virtualbox 使用 USB 裝置

熟悉系統工具好處多多

如何 git merge 更改檔名的檔案