第4章-進程控制-進程的一生 === ###### tags: `twlkh`, `study-group` # 補充資料 ## program 0030與 process * 名詞解釋 * program (程式/程序) * process(行程/進程) * 程式運行時的狀態 * kernel: task * `current()` macro * `task_struct` 與`thread_info` 圖片來源:Understanding the Linux Kernel 3.2.2.1 ![](https://wiki.kldp.org/pds/ProcessManagement/process_descriptor.jpg) * process 所擁有的資源 * IDs * memory space * files(fd table) * timer * signal ## 在 Linux 中運行程式 * 運行程式 * 創建 process: `fork()` * Q:誰是第一個 process? * 載入程式並運行新的程式:exec-family * process 的一生 下圖出處:[What is a Process?](https://bash.cyberciti.biz/guide/What_is_a_Process%3F) ![](https://bash.cyberciti.biz/uploads/bashwiki/thumb/c/c3/Linux-processes-life-cycle.png/800px-Linux-processes-life-cycle.png) * clone() * 更精細控制 parent/child process 間的資源共享 * fork/vfork/pthread_create (`sysdeps/unix/sysv/linux/aarch64/arch-fork.h`) ``` clike #define ARCH_FORK() \ INLINE_SYSCALL (clone, 5, \ CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, \ NULL, NULL, NULL, &THREAD_SELF->tid) ``` * clone flags | clone flags | 描述 | | ------ | ----------- | | CLONE_VM | the calling process and the child process run in the same memory space | | CLONE_FS | share the same filesystem information. This includes the root of the filesystem, the current working directory, and the umask. | | CLONE_FILES | share the same file descriptor table | | CLONE_THREAD | the child is placed in the same thread group as the calling process | | CLONE_VFORK | the execution of the calling process is suspended until the child releases its virtual memory resources via a call to execve() or _exit() | ## pid 0 and 1 * kernel 啟動流程 * pid 0 圖片出處:[Kernel Initialization](http://www.slideshare.net/zmule/kernel-init) ![Kernel init 流程](http://image.slidesharecdn.com/kernel-init-111014013749-phpapp01/95/kernel-init-14-728.jpg?cb=1318556304) * who is pid 1? (`init/main.c` in linux-4.7) ```clike= static int __ref kernel_init(void *unused) { int ret; kernel_init_freeable(); ... if (execute_command) { ret = run_init_process(execute_command); if (!ret) return 0; panic("Requested init %s failed (error %d).", execute_command, ret); } if (!try_to_run_init_process("/sbin/init") || !try_to_run_init_process("/etc/init") || !try_to_run_init_process("/bin/init") || !try_to_run_init_process("/bin/sh")) return 0; panic("No working init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance."); } ``` ```clike= static int __init init_setup(char *str) { unsigned int i; execute_command = str; ... } __setup("init=", init_setup); ``` # 書本相關章節筆記與補充 ## 4.1 Process ID * pid 特性 * 每個 process pid 唯一 * 延遲重用算法 * 分配給新的 process pid 盡量不與最近終止的 process pid 重複 * 最小從 300 (`RESERVED_PIDS`) 開始找,300 以下給系統而不分配給 userspace process * pid_max * Ubuntu下的預設pid_max值 ``` $ sudo sysctl -a | grep pid_max kernel.pid_max = 32768 ``` * 如何設定pid_max ``` $ sudo sysctl -w kernel.pid_max=4194304 ``` * kernel config setting CONFIG_BASE_FULL => pid_max=32768(32bits) 4194304(64bits) ``` $ grep CONFIG_BASE_ /boot/config-4.4.0-21-generic CONFIG_BASE_FULL=y CONFIG_BASE_SMALL=0 ``` ## 4.2 進程的層次 * process 間的關係 * process、process group 與 session * process group 與 session 是為了支持 shell 作業控制而引入的概念。 * example * process group ```shell $ cmd1 | cmd2 | cmd3 ``` * session: 用戶登入的 shell 是 session 的第一個 process * API * `getpgid()`, `getsid()` * `setpgid()`, `setsid()` 圖片出處:[Shichao's Notes: Signals](https://notes.shichao.io/apue/ch10/) ![](https://notes.shichao.io/apue/figure_9.7.png) **4.2.1 進程組** **Limitation of setpgid(from 'man setpgid')** ``` EACCES An attempt was made to change the process group ID of one of the children of the calling process and the child had already performed an execve(2) (setpgid(), setpgrp()). EINVAL pgid is less than 0 (setpgid(), setpgrp()). EPERM An attempt was made to move a process into a process group in a different session, or to change the process group ID of one of the chil‐ dren of the calling process and the child was in a different session, or to change the process group ID of a session leader (setpgid(), setpgrp()). ESRCH For getpgid(): pid does not match any process. For setpgid(): pid is not the calling process and not a child of the calling process. ``` 下圖出處:[Linux PID, PPID, PGID (Process Group Leader), SID (Session Leader)](http://www.geekpills.com/operating-system/linux/the-ultimate-pid-ppid-pgid-process-group-leader-sid-session-leader) ![](http://pic002.cnblogs.com/images/2012/413416/2012100623084459.jpg) 下圖出處:[進程關係 by Vamei](http://www.cnblogs.com/vamei/archive/2012/10/07/2713023.html) ![](http://www.geekpills.com/wp-content/uploads/2015/02/PID-2C-2BPPID-2C-2BPGID-2B-Process-2BGroup-2BLeader-2C-2BSID-Session-2BLeader-_1.png) **4.2.2 會話** * `setsid()`常見使用場景 * login * shell * 創建 daemon ## 4.3 進程的創建之 fork() 圖片來源:[浅析内核中用户进程的创建](http://www.uml.org.cn/embeded/201307101.asp) ![](http://www.uml.org.cn/embeded/images/2013071011.jpg) * 延伸參考資料: Understanding The Linux Kernel 3e, * 3.4.1.1: `do_fork()` * 3.4.1.2: `copy_process()` * fork return value * child process: 0 * parent process: child process pid * 錯誤:-1 並設置 errno * ENOSYS ``` fork() is not supported on this platform (for example, hardware without a Memory-Management Unit). ``` * parent/child 誰先被執行? * 2.6.32 後,預設是 parent process * 原因:parent process 在 CPU 中屬於活躍狀態。 * 2.6.24 採 CFS 後可透過 `/proc/sys/kernel/sched_child_runs_first` 更改 **4.3.1 fork 之後父子進程的內存關係** * child process 完全複製 parent process 的記憶體空間。 * fork + exec * copy-on-write(COW) * 只複製 page table,並設置成唯讀。parent/child process 寫入時會觸發 page fault,才做真正複製。 **4.3.2 fork 之後父子進程與文件的關係** * parent/child process 同時操作同一個 file 互相影響。 * 範例: file offset * 解釋 FD_CLOSEXEC(file descriptor flag) 與 O_CLOSEXEC (`open()` flag) **4.3.3 文件描述符複製的內存實現** 圖片出處:[VFS文件系统结构分析](http://blog.chinaunix.net/uid-20528014-id-4094714.html) ![](http://blog.chinaunix.net/attachment/201402/12/20528014_1392194491H5h5.jpg) * 範例:parent/child process 共享檔案偏移 * proc * /proc/1/status 的 FDsize: fdtable 大小 * /proc/1/fd 已打開的 fd ## 4.4 進程的創建之 vfork() * 背景 * 早期沒有實現 copy-on-write(COW) 對於 fork + exec 的效率不好。 * BSD 引入 vfork,直接共享記憶體。 * vfork 細節 * parent/child process 共享記憶體。 * 確保 child process 先被排程。 * 在 child process 退出或是執行 `exec` 前,parent process 一直 suspend。 * child process 需呼叫 `_exit` 退出,不要使用 return。 * COW 實現後 * BSD 4.4 後讓 `fork` 與 `vfork` 一樣了。 * Linux 維持兩個函數實現,但除很特在在意效能外不建議使用 `vfork`。 ## 4.5 daemon 進程的創建 * daemon 特點 * 生命週期長 * 在後台執行,不與任何控制終端相關聯 * 終端的相關訊號與關閉終端都不會影響 daemon 的繼續執行。 * process naming rule: 以 d 結尾。 * 寫 daemon process 的步驟 (double-fork magic) 1. 執行 `fork()`,parent process 退出,child process 繼續。 2. child process 擺脫與環境的關係。 a. 修改當前目錄為根目錄 `/` b. 呼叫 setsid,創建一個新的 session。 c. 設置文件模式的 umask(0) 3. 再次 `fork()`,parent process 退出,child process 繼續。 4. 關閉 stdin/stdout/stderr * glibc 提供 `daemon()` 函數 * 但並無實現上述第二次的 `fork()` ## 4.6 進程的終止 **4.6.1 _exit 函數** 下圖出處:[CIS 307: Unix I](http://cis-linux1.temple.edu/~giorgio/old/cis307f95/readings/unix1.html) ![](http://cis-linux1.temple.edu/~giorgio/old/cis307f95/readings/exit.gif) * 返回值不要大於128,大於128會給shell帶來困擾。舉例,return 130就同於被signal中斷。 ``` $ sleep 10 ^C $ echo $? 130 ``` 下圖出處:書上p.119~120 ![](https://dl.dropboxusercontent.com/u/8612150/tlkh/study%20group%E5%BE%9E%E6%87%89%E7%94%A8%E5%88%B0%E5%85%A7%E6%A0%B8/ch4/%E6%96%B0%E5%BB%BA%E6%AA%94%E6%A1%88_130.jpg) ![](https://dl.dropboxusercontent.com/u/8612150/tlkh/study%20group%E5%BE%9E%E6%87%89%E7%94%A8%E5%88%B0%E5%85%A7%E6%A0%B8/ch4/%E6%96%B0%E5%BB%BA%E6%AA%94%E6%A1%88_131.jpg) **4.6.2 exit 函數** 下圖出處:書上p.120 ![](https://dl.dropboxusercontent.com/u/8612150/tlkh/study%20group%E5%BE%9E%E6%87%89%E7%94%A8%E5%88%B0%E5%85%A7%E6%A0%B8/ch4/%E6%96%B0%E5%BB%BA%E6%AA%94%E6%A1%88_132.jpg) * User defined exit function: User可以透過atexit()或者on_exit()來註冊clean function。 * exit()呼叫clean function的順序和註冊的順序相反。 * 收到kill signal時,clean function不會被呼叫。 * 在clean function內部呼叫_exit()則接在後面的clean function都不會被call。 **三種buffer flush機制** #### 1. _IONBF * No buffer flush每次呼叫stdio related functoin都會呼叫read/write system call。 #### 2. _IOLBF * 收到line feed '\n'才會呼叫system call。 #### 3. _IOFBF * Buffer滿之前不會呼叫read/write system call。 **4.6.3 return 退出** * 在main function內部執行return就相當於執行exit(n),return的回傳參數會被當作exit的參數。 ## 4.7 等待子進程 **4.7.1 殭屍進程** * 一個進程進入殭屍狀態,為的是保留重要訊息,提供給parent process。比如:process為什麼exit、收到signal退出還是正常exit、process exit code、user/system time、 etc。 下圖出處:[如何避免Linux zombie process的產生?](http://outsidenote.blogspot.tw/2013/01/linux-zombie-process.html) ![](https://lh5.googleusercontent.com/-ExrWz9SFb8A/UQjjWlyCxbI/AAAAAAAAFx8/XL6FODuO6mM/s740/20130130_zombie.png) * 如何避免zombie process? 1. Parent 呼叫 wait系列 system call等待child process exit。下圖出處:[如何避免Linux zombie process的產生?](http://outsidenote.blogspot.tw/2013/01/linux-zombie-process.html)![](https://lh3.googleusercontent.com/-kp6o84s3kcM/UQjjWhwov1I/AAAAAAAAFyA/4iYoifWGB9g/s740/20130130_normal.png) 2. Parent process exit,child process被init process接管,稍待會被清理。(可以fork兩次,第一次fork的process結束,留下grandchild給init接管。)下圖出處:[如何避免Linux zombie process的產生?](http://outsidenote.blogspot.tw/2013/01/linux-zombie-process.html)![](https://lh4.googleusercontent.com/-fg4xIOSjcfI/UQjjWlDFWKI/AAAAAAAAFyE/BA4kVhAi4AY/s729/20130130_forktwice.png) 3. 設置SACHLD or SA_NOCLDWAIT。Child process 在退出時,kernel會檢查這兩個標誌為是否有設置,若是,則呼叫release_task()自行了斷。 **4.7.2 等待子進程之 wait()** ```clike #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); ``` * wait() system call return 的三種可能 1. 等到了child process exit,返回 exit child process ID。 2. 在wait() system call block等待期間接收到signal造成system call return,並且signal handler在註冊時並沒有標誌SA_RESTART,所以system call不會被restart,wait() return -1,並且errno設為EINTR。 3. 所有的child processes都結束,wait() return -1, error為ECHILD。 * wait() system call的侷限 1. 不能等待特定的child process。 2. 不存在trywait()形式的機制,造成wait() system call只能block waiting。 3. wait() system call不能監測到child process的STOP/RESUME。e.g. 'pkill -STOP chrome' or 'pkill -CONT chrome'。 **4.7.3 等待子進程之 waitpid()** ```clike #include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options); ``` * waitpid() 改善了wait()新增了以下options * 沒法針對特定pid等待以及無法偵測等到process的stop/resume狀態改變。(WUNTRACE/WCONTINUED) * 新增WNOHANG, non-blocking wait。Child process 沒有發生狀態改變則會return。 * pid值 1. pid > 0: 等待特定的pid child process。 2. pid = 0: 等待和呼叫waitpid()同一個process group的任意child process。 3. pid = -1: 等待任意child process。(和wait()相同) 4. pid < -1: 等待進程組為絕對值abs(pid)的child processes。 * 缺點:雖然可以偵測process STOP/CONT,但是這是和process的termination混在一起。不能單只針對child process STOP/CONT做等待。 **4.7.4 等待子進程之等待狀態值** **4.7.5 等待子進程之 waitid()** ```clike #include <sys/types.h> #include <sys/wait.h> int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options); ``` * 更有彈性的wait界面。改善不能針對 child process STOP/CONT 狀態改變做等待的問題。 * 新增WNOWAIT option。指示kernel,只負責獲取child process,並不會使的呼叫waitid之後child process相關結構就會被回收釋放ㄖ。稍待可以在呼叫wait/waitpid/waitid再次獲取資訊同時釋放資源。 **4.7.6 進程退出和等待的內核實現** * How group_exit syscall work with Pthread? 下圖出處:Gavin ![](https://dl.dropboxusercontent.com/u/8612150/tlkh/study%20group%E5%BE%9E%E6%87%89%E7%94%A8%E5%88%B0%E5%85%A7%E6%A0%B8/ch4/threads_grou_exit.png) * How exit syscall work with Pthread? 下圖出處:Gavin ![](https://dl.dropboxusercontent.com/u/8612150/tlkh/study%20group%E5%BE%9E%E6%87%89%E7%94%A8%E5%88%B0%E5%85%A7%E6%A0%B8/ch4/single_thread_exit.png) ## 4.8 exec 家族 **4.8.1 execve 函數** * 最終被執行的 syscall ```clike #include <unistd.h> int execve(const char *filename, char *const argv[], char *const envp[]); `````` * return value * 成功:不返回 * 失敗: -1, 請見 errno * ENOEXEC: An executable is not in a recognized format **4.8.2 exec 家族** 下圖出處:[Shichao's note: chapter 8 - process control](https://notes.shichao.io/apue/ch8/) ![exec](https://notes.shichao.io/apue/figure_8.15.png) * exec 家族命名規則 * 參數採用 list(`l`) 或是 vector(`v`) 的形式 * 自動搜索 `$PATH`(`p`) * 自行提供環境變數 (`e`) **4.8.3 execve 系統調用的內核實現** * Flow 圖片來源:[execve()到底干了啥?](http://m.blog.csdn.net/article/details?id=51313567) ![](http://www.codexiu.cn/static/blog/imagesw8/2016/05/04/full/cf743ad7f78a5c009d7b9d57593eebc72fe6af17.jpg) * linux_binprm *  可執行文件 magic number: 頭 128 byte * linux_binfmt * 透過 register_binfmt 註冊可執行文件的 handler * binfmt_aout.c * binfmt_elf.c * binfmt_script.c * shebang (`#!/bin/sh`) * binfmt_misc.c * windows exe: wine **4.8.4 exec 與信號** * 執行 exec 後的 signal 處理函數 * 重新被設為 SIG_DFL * 特例:設為 SIG_IGN 的 SIGCHLD,Linux 保持為 SIG_IGN **4.8.5 執行 exec 之後進程繼承的屬性** * 仍保有的屬性 * 所有的 id 類 * alarm 剩餘時間 * pending signal * umask * 當前工作目錄與根目錄 * ... 等 * 與 fork 的比較。fork 之後的 child process 不繼承 * alarm 剩餘時間 * pending signal * 已用的時間 * ... 等 ## 4.9 system 函數 **4.9.1 system 函數接口** * system = fork + exec "sh -c command" * create 2 child process * system 的 return value * 0 或 1 * command == NULL * 可透過 system(NULL) 檢查 shell 是否可用 * -1 * fork 失敗:創造超過系統限制的 process * waitpid 失敗:SIGCHLD 處理函數是 SIG_IGN 或是設置了 SA_NOCLDWAIT * errno: ECHLD * 與 _exit(127) 終止時一樣。(child process 不能執行 shell) * 執行 command 的子 shell 的終止狀態。(所有的 syscall 都成功的 case) * 正確判斷 system() return value 的方法: ```clike int status = system(argv[1]); if (status == -1) { fprintf(stderr, "system() function return -1 (%s)\n", strerror(errno)); } else if (WIFEXITED(status) && WEXITSTATUS(status) == 127) { fprintf(stderr, "cannot invoke shell to exec command(%s)\n",argv[1]); } else print_wait_exit(status); ``` * 補充: * SIGCHLD: Child stopped or terminated * ECHLD: No child processes (POSIX.1) * SA_NOCLDWAIT (sa_flags in `sigaction()`) ``` If signum is SIGCHLD, do not transform children into zombies when they terminate. See also waitpid(2). This flag is meaningful only when establishing a handler for SIGCHLD, or when setting that signal's disposition to SIG_DFL. If the SA_NOCLDWAIT flag is set when establishing a handler for SIGCHLD, POSIX.1 leaves it unspecified whether a SIGCHLD signal is generated when a child process terminates. On Linux, a SIGCHLD signal is generated in this case; on some other implementations, it is not. ``` **4.9.2 system 函數與信號** * 考慮處理 signal 帶來的複雜度 * SIGCHLD * system 運行期間必須暫時阻塞 SIGCHLD 訊號。 * SIGINT 與 SIGQUIT * SUSv3(The Single UNIX Specification, Version 3) 規定 * 呼叫 system 函數的 process 需要忽略 SIGINT 和 SIGQUIT * system 函數內部創建的 process,需恢復對 SIGINT 與 SIGQUIT 的預設處理。 # Reference * [Linux Programmer's Manual:SIGNAL(7)](http://man7.org/linux/man-pages/man7/signal.7.html) * [Linux Programmer's Manual:ERRNO(3)](http://man7.org/linux/man-pages/man3/errno.3.html) * [Linux内核如何装载和启动一个可执行程序](http://www.jianshu.com/p/f1bf33883d84) * [How programs get run: ELF binaries](https://lwn.net/Articles/631631/) * [How programs get run](https://lwn.net/Articles/630727/) * [vfork 挂掉的一个问题](http://coolshell.cn/articles/12103.html)