簡短版說明
前陣子遇到一個神祕的問題:
執行程式 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 stty 或 TLPI 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。
你標題寫錯囉!是 terminal!
回覆刪除閣下好眼力! 已修正。可惜網址就不方便改了 Orz
刪除