UNIX 環境高級編程|進程控制(下)

      網友投稿 818 2022-05-29

      GitHub: https://github.com/storagezhang

      Emai: debugzhang@163.com

      本文為《UNIX 環境高級編程》第 8 章學習筆記

      對在 UNIX 環境中的高級編程而言,完整地了解 UNIX 的進程控制是非常重要的。

      其中必須熟練掌握的只有幾個函數——fork、exec 系列、_exit、wait 和 waitpid。很多應用程序都使用這些簡單的函數。fork 函數也給了我們一個了解競爭條件的機會。

      本章說明了 system 函數和進程會計,這也使我們能進一步了解所有這些進程控制函數。

      本章還說明了 exec 函數的另一種變體:解釋器文件以及它們的工作方式。

      對各種不同的用戶 ID 和組 ID(實際、有效和保存的)的理解,對編寫安全的設置用戶 ID 程序是至關重要的。

      8.10 函數 exec

      當進程調用一種 exec 函數時,該進程執行的程序完全替換為新程序,而新程序則從其 main 函數開始執行。因為調用 exec 并不創建新進程,所以前后的進程 ID 并未改變。exec 只是用磁盤上的一個新程序替換了當前進程的正文段、數據段、堆段和棧段。

      有 7 種不同的 exec 函數可供使用,它們常常被統稱為 exec 函數,我們可以使用這 7 個函數中的任一個。這些 exec 函數使得 UNIX 系統進程控制原語更加完善。用 fork 可以創建新進程,用 exec 可以初始執行新的程序。exit 函數和 wait 函數處理終止和等待終止。這些是我們需要的基本的進程控制原語。

      #include int execl(const char *pathname, const char *arg0, ... /* (char *)0 */); int execv(const char *pathname, char *const argv[]); int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */); int execve(const char *pathname, char *const argv[], char *const envp[]); int execlp(const char *filename, const char *arg0, ... /* (char *)0 */); int execvp(const char *filename, char *const argv[]); int fexecve(int fd, char *const argv[], cahr *const envp[]); // 返回值:若成功,不返回;若出錯,返回 -1

      區別:

      前 4 個函數取路勁名作為參數,后 2 個函數則取文件名作為參數,最后一個取文件描述符作為參數。

      當指定 filename 作為參數時:

      如果 filename 中包含 /,就將其視為路徑名;

      否則,按照 PATH 環境變量,在它所指定的各目錄中搜尋可執行文件。

      PATH 變量包含了一張目錄表(稱為路徑前綴),目錄之間用冒號分隔。

      如果 execlp 或 execvp 使用路勁前綴中的一個找到了一個可執行文件,但是該文件不是由連接器產生的機器可執行文件,就認為該文件是一個 shell 腳本,于是試著調用 /bin/sh,并以該 filename 作為 shell 的輸入。

      fexecv 函數避免了尋找正確的可執行文件,而是依賴調用進程來完成這項工作。調用進程可以使用文件描述符驗證所需要的文件并且無競爭地執行該文件。否則,擁有特權的惡意用戶就可以在找到文件位置并且驗證之后,但在調用進程執行該文件之前,替換可執行文件(或可執行文件的部分路徑)。

      函數 execl、execlp 和 execle 要求將新程序的每個命令行參數都說明為一個單獨的參數。這種參數表以空指針結尾。

      函數 execv、execvp、execve 和 fexecve 應先構造一個指向各參數的指針數組,然后將該數組地址作為這 4 個函數的參數。

      以 e 結尾的 3 個函數(execle、execve 和 fexecve)可以傳遞一個指向環境字符串指針數組的指針。其他 4 個函數則使用調用進程中的 environ 變量為新程序復制現有的環境。

      注意:

      每個系統對參數表和環境表的總長度都有一個限制。

      在 POSIX.1 系統中,此值至少是 4096 字節。

      當使用 shell 的文件名擴充功能產生一個文件名列表時,可能會受到此值的限制。

      為了擺脫對參數表長度的限制,我們可以使用 xargs(1) 命令,將長參數表斷開成幾部分。

      在執行 exec 后,進程 ID 沒有改變,但新進程從調用進程繼承了的下列屬性:

      進程 ID 和父進程 ID

      實際用戶 ID 和實際組 ID

      附屬組 ID

      進程組 ID

      會話 ID

      控制終端

      鬧鐘尚余留的時間

      當前工作目錄

      根目錄

      文件模式創建屏蔽字

      文件鎖

      進程信號屏蔽

      未處理信號

      資源限制

      友好值

      tms_utime、tms_stime、tms_cutime 以及 tms_cstime 值

      對打開文件的處理于每個描述符的執行時關閉(close-on-exec)標志值有關。

      進程中每個打開描述符都有一個執行時關閉標志。若設置了此標志,則在執行 exec 時關閉該描述符;否則該描述符仍打開。除非特地用 fcntl 設置了該執行時關閉標志,否則系統的默認操作是在 exec 后仍保持這種描述符打開。

      在 exec 前后實際用戶 ID 和實際組 ID 保持不變,而有效 ID 是否改變則取決于所執行程序文件的設置用戶 ID 位和設置組 ID 位是否設置。如果新程序的設置用戶 ID 為已設置,則有效用戶 ID 變成程序文件所有者的 ID;否則有效用戶 ID 不變。對組 ID 的處理方式與此相同。

      在很多 UNIX 實現中,這 7 個函數中只有 execve 是內核的系統調用。另外 6 個只是庫函數,它們最終都要調用該系統調用。這 7 個函數之間的關系如下:

      在這種安排中,庫函數 execlp 和 execvp 使用 PATH 環境變量,查找第一個包含名為 filename 的可執行文件的路徑名前綴。fexecve 庫函數使用 /proc 把文件描述符參數轉換成路徑名,execve 用該路徑名去執程序。

      8.11 更改用戶 ID 和更改組 ID

      在 UNIX 系統中,特權已經訪問孔子,是基于用戶 ID 和組 ID 的。當程序需要增加特權,或需要訪問當前并不允許訪問的資源時,我們需要更換自己的用戶 ID 或組 ID,使得新 ID 具有合適的特權或訪問權限。與此類似,當程序需要降低其特權或阻止對某些資源的訪問時,也需要更換用戶 ID 或組 ID,新 ID 不具有相應特權或訪問這些資源的能力。

      一般而言,在設計應用時,我們總是試圖使用最小特權模型。依照此模型,我們的程序應當只具有為完成給定任務所需的最小特權。這降低了由惡意用戶試圖哄騙我們的程序以未預料的方式使用特權造成的安全性風險。

      可以用 setuid 函數設置實際用戶 ID 和有效用戶 ID,用 setgid 函數設置實際組 ID 和有效組 ID:

      UNIX 環境高級編程|進程控制(下)

      #include int setuid(uid_t uid); int setgid(gid_t gid); // 返回值:若成功,返回 0;若失敗,返回 -1

      關于更改用戶 ID 的規則(關于用戶 ID 所說明的一切都適用于組 ID):

      若進程具有超級用戶特權,則 setuid 函數將實際用戶 ID、有效用戶 ID 以及保存的設置用戶 ID(saved set-user-ID)設置為 uid。

      若進程沒有超級用戶特權,但是 uid 等于實際用戶 ID 或保存的設置用戶 ID,則 setuid 只將有效用戶 ID 設置為 uid,不更改實際用戶 ID 和保存的設置用戶 ID。

      如果上面兩個條件都不滿足,則 errno 設置為 EPERM,并返回 -1。

      在此假定 _POSIX_SAVED_IDE 為真,如果沒有提供這種功能,則上面所說的關于保存的設置用戶 ID 部分都無效。

      關于內核所維護的 3 個用戶 ID(實際用戶、有效用戶和保存的設置用戶),還要注意:

      只有超級用戶進程可以更改實際用戶 ID。

      通常,實際用戶 ID 是在用戶登錄時,由 login 程序設置的,而且絕不會改變它。

      因為 login 是一個超級用戶進程,當它調用 setuid 時,設置所有 3 個用戶 ID。

      僅當對程序文件設置了設置用戶 ID 時,exec 函數才設置有效用戶 ID。如果設置用戶 ID 位沒有設置,exec 函數不會改變有效用戶 ID,而將維持其現有值。

      任何時候都可以調用 setuid 將有效用戶 ID 設置為實際用戶 ID 或者保存的設置用戶 ID。

      調用 setuid 時,不能將有效用戶 ID 設置為任一隨機值,只能從實際用戶 ID 或者保存的設置用戶 ID 中取得。

      保存的設置用戶 ID 是由 exec 復制有效用戶 ID 而得到的。

      如果設置了文件的設置用戶 ID 位,則在 exec 根據文件的用戶 ID 設置了進程的有效用戶 ID 以后,這個副本就被保存起來了。

      下表總結了更改這 3 個用戶 ID 的不同方法:

      注意:getuid 和 geteuid 函數只能獲得實際用戶 ID 和有效用戶 ID 的當前值,我們沒有可移植的方法去獲得保存的設置用戶 ID 的當前值。

      函數 seteuid 和 setegid

      POSIX.1 包含了兩個函數 seteuid 和 setegid,它們類似于 setuid 和 setgid,但只更改有效用戶 ID 和有效組 ID :

      #include int seteuid(uid_t uid); int setegid(gid_t gid); // 返回值:若成功,返回 0;若出錯,返回 -1

      一個非特權用戶可將其有效用戶 ID 設置為其實際用戶 ID 或其保存的設置用戶 ID。

      一個特權用戶用戶可將有效用戶 ID 設置為 uid。

      8.12 解釋器文件

      所有現今的 UNIX 系統都支持解釋器文件。

      這種文件是文本文件,其起始行的形式是:

      #! pathname [optional-argument],在感嘆號和 pathname 之間的空格是可選的。

      最常見的解釋器文件以下列行開始:

      #! /bin/sh

      pathname 通常是絕對路徑名,對它不進行什么特殊處理(不使用 PATH 進行路徑搜索)。

      對這種文件的識別是由內核作為 exec 系統調用處理的一部分來完成的。內核使調用 exec 函數的進程實際執行的并不是該解釋器文件,而是在該解釋器文件第一行中 pathname 所指定的文件。

      一定要將解釋器文件(文本文件,它以 #! 開頭)和解釋器(有該解釋器文件第一行中的 pathaname 指定)區分開來。

      解釋器使用戶得到效率方面的好處,其代價是內核的額外開銷(因為識別解釋器文件的是內核)。

      由于下述理由,解釋器文件是有用的:

      有些程序是用某種語言寫的腳本,解釋器文件可將這一事實隱藏起來。

      解釋器腳本提升了效率。

      解釋器腳本使我們可以使用除 /bin/sh 以為的其他 shell 來編寫 shell 腳本。

      8.13 函數 system

      ISO C 定義了 system 函數,用于在程序中執行一個命令字符串,但是其操作對系統的依賴性很強。POSIX.1 包括了 system 接口,它擴展了 ISO C 定義,描述了 system 在 POSIX.1 環境中的運行行為。

      #include int system(const char *cmdstring)

      參數:

      cmdstring:命令字符串(在 shell 中執行)

      返回值:

      如果 cmdstring 是一個空指針,則僅當命令處理程序可用時,system 返回非 0 值。

      這一特征可以確定在一個給定的操作系統上是否支持 system 函數。

      在 UNIX 中,system 總是可用的。

      system 在其實現中調用了 fork、exec 和 waitpid,因此有 3 中返回值:

      fork 失敗或者 waitpid 返回除 EINTR 之外的出錯,則 system 返回 -1,并且設置 errno 以指示錯誤類型。

      如果 exec 失敗(表示不能執行 shell),則其返回值如同 shell 執行了 exit(127) 一樣。

      否則所有 3 個函數(fork、exec 和 waitpid)都成功,那么 system 的返回值是 shell 的終止狀態,其格式已在 waitpid 中說明。

      使用 system 而不是直接使用 fork 和 exec 的優點是:

      system 進行了所需的各種出錯處理以及各種信號處理。

      如果一個進程正以特殊的權限(設置用戶 ID 或設置組 ID)運行,它又想生成另一個進程執行另一個程序,則它應當直接使用 fork 和 exec,而且在 fork 之后、exec 之前要更改回普通權限。設置用戶 ID 或設置組 ID 程序絕不應調用 system 函數。

      8.14 進程會計

      大多數 UNIX 系統提供一個選項以進行進程會計(process accounting)處理。啟動該選項后,每當進程結束時內核就寫一個會計記錄。

      典型的會計記錄包含總量較小的二進制數據,一般包括命令名、所使用的 CPU 時間總量、用戶 ID 和組 ID、啟動時間等。

      任一標準都沒有對進程會計進行過說明。于是,所有實現都有令人厭煩的差別。

      函數 acct 啟用和禁用進程會計。唯一使用這一函數的是 accton(8) 命令。超級用戶執行一個帶路徑名參數的 accton 命令啟動會計處理。執行不帶任何參數的 accton 命令則停止會計處理。

      會議記錄結構定義在頭文件 中,雖然每種系統的實現各不相同,但會計記錄樣式基本如下:

      typedef u_short comp_t; /* 3-bit base 8 exponent; 13-bit fraction */ struct acct { char ac_flag; /* flag (see Figure 8.26) */ char ac_stat; /* termination status (signal & core flag only) */ /* (Solaris only) */ uid_t ac_uid; /* real user ID */ gid_t ac_gid; /* real group ID */ dev_t ac_tty; /* controlling terminal */ time_t ac_btime; /* starting calendar time */ comp_t ac_utime; /* user CPU time */ comp_t ac_stime; /* system CPU time */ comp_t ac_etime; /* elapsed time */ comp_t ac_mem; /* average memory usage */ comp_t ac_io; /* bytes transferred (by read and write) */ /* "blocks" on BSD systems */ comp_t ac_rw; /* blocks read or written */ /* (not present on BSD systems) */ char ac_comm[8]; /* command name: [8] for Solaris, */ /* [10] for Mac OS X, [16] for FreeBSD, and */ /* [17] for Linux */ };

      在大多數的平臺上,時間是以時鐘滴答數記錄的,但 FreeBSD 以微妙進行記錄。ac_flag 成員記錄了進程執行期間的某些事件。這些事件見下圖:

      會計記錄所需的各個數據(各 CPU 時間、傳輸的字符數等)都由內核保存在進程表中,并在一個新進程被創建時初始化(如 fork 之后在子進程中)。進程終止時寫一個會計記錄,這產生兩個后果:

      我們不能獲取永遠不終止的進程的會計記錄。

      像 init 這樣的進程在系統生命周期中一直在運行,并不產生會計記錄。

      這也同樣適用于內核守護進程,它們通常不會終止。

      在會計文件中記錄的順序對應于進程終止的順序,而不是它們啟動的順序。

      為了確定啟動順序,需要讀全部會計文件,并按啟動日歷時間進行排序。

      這不是一種很完善的方法,因為日歷時間的單位是秒,在一個給定的秒鐘可能啟動了多個進程。而墻上時鐘時間的單位是時鐘滴答(通常每秒滴答數在 60~128)。

      但是我們并不知道進程的終止時間,所知道的只是啟動時間和終止順序。這就意味著,即使墻上時鐘時間比啟動時間要精確得多,仍不能按照會計文件中的數據重構各進程的精確啟動順序。

      會計記錄對應于進程而不是程序。在 fork 之后,內核為子進程初始化一個記錄,而不是在一個新程序被執行時初始化。雖然 exec 并不創建一個新的會計記錄,但相應記錄中的命令名改變了,AFORK 標志則被清除。這意味著,如果一個進程順序執行了 3 個程序(A exec B、B exec C,最后是 C exit),只會寫一個會計記錄。在該記錄中的命令名對應于程序 C,但 CPU 時間是程序 A、B 和 C 之和。

      8.15 用戶標識

      任一進程都可以得到其實際用戶 ID 和有效用戶 ID 及組 ID。但是,我們有時希望找到運行該程序用戶的登錄名。系統通常記錄用戶登錄時使用的名字,用 getlogin 函數可以獲取此登錄名。

      #include char *getlogin(void); // 返回值:若成功,返回指向登錄名字符串的指針;若出錯,返回 NULL

      如果調用此函數的進程沒有連接到用戶登錄時所用的終端,則函數會失敗。通常稱這些進程為守護進程(daemon)。

      給出了登錄名,就可用 getpwnam 在口令文件中查找用戶的相應記錄,從而確定其登錄 shell 等。

      8.16 進程調度

      UNIX 系統歷史上對進程提供的只是基于調度優先級的粗粒度的控制。調度策略和調度優先級是由內核確定的。進程可以通過調整友好值選擇以更低優先級運行(通過調整友好值降低它對 CPU 的占有,因此該進程是“友好的”)。只有特權進程允許提高調度權限。

      Single UNIX Specification 中友好值的范圍在 0~(2*NZERO)-1 之間,有些實現支持 0~2*NZERO。友好值越小,優先級越高:你越友好,你的調度優先級就越低。NZERO 是系統默認的友好值。

      進程可以通過 nice 函數獲取或更改它的友好值。使用這個函數,進程只能影響自己的友好值,不能影響任何其他進程的友好值。

      #include int nice(int incr); // 返回值:若成功,返回新的友好值 NZERO;若出錯,返回 -1

      參數:

      incr :調用進程的友好值增加量。

      如果 incr 太大,系統直接把它降到最大合法值,不給出提示。

      如果 incr 太小,系統也會把它提高到最小合法值,不給出提示。

      由于 -1 是合法的成功返回值,在調用 nice 函數之前需要清除 errno,在 nice 函數返回 -1 時,需要檢查它的值。

      如果 nice 調用成功,并且返回值為 -1,那么 errno 仍然為 0。

      如果 errno 不為 0,說明 nice 調用失敗。

      getpriority 函數可以像 nice 函數那樣用于獲取進程的友好值,但是 getpriority 還可以獲取一組相關進程的友好值。

      #include int getpriority(int which, id_t who); // 返回值:若成功,返回 -NZERO~NZERO-1 之間的友好值;若出錯,返回 -1

      參數:

      which:控制 who 參數是如何解釋的,可以取以下三個值之一:

      PRIO_PROCESS:進程

      PRIO_PGRP:進程組

      PRIO_USER:用戶 ID

      who:選擇感興趣的一個或多個進程。

      如果 who 為 0,表示調用進程、進程組或者用戶。

      當 which 設為 PRIO_USER 并且 who 為 0 時,使用調用進程的實際用戶 ID。

      如果 which 作用與多個進程,則返回所有作用進程中優先級最高的(最小的友好值)。

      setpriority 函數可用于為進程、進程組和屬于特定用戶 ID 的所有進程設置優先級。

      #include int setpriority(int which, id_t who, int value); // 返回值:若成功,返回 0;若出錯,返回 -1

      參數:

      which:與 getpriority 函數中相同。

      who:與 getpriority 函數中相同。

      value:增加到 NZERO 上,然后變為新的友好值。

      8.17 進程時間

      任一進程都可調用 times 函數獲得它自己以及已終止子進程的墻上時鐘時間、用戶 CPU 時間和系統 CPU 時間:

      #include clock_t times(struct tms *buf); // 返回值:若成功,返回流逝的墻上時鐘時間(以時鐘滴答數為單位);若出錯,返回 -1

      此函數填寫由 buf 指向的 tms 結構,該結構定義如下:

      struct tms { clock_t tms_utime; /* user CPU time */ clock_t tms_stime; /* system CPU time */ clock_t tms_cutime; /* user CPU time, terminated children */ clock_t tms_cstime; /* system CPU time, terminated children */ };

      注意:

      此結構沒有包含墻上時鐘時間。times 函數返回墻上時鐘時間作為其函數值。此值是相對于過去的某一時刻度量的,所以不能用其絕對值而必須使用其相對值。

      該結構中兩個針對子進程的字段包含了此進程用 wait 函數族已等待到的各子進程的值。

      所有由此函數返回的 clock_t 值都用 _SC_CLK_TCK(由 sysconf 函數返回的每秒時鐘滴答數)轉換成秒數。

      Linux Unix

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:這些計算機經典書,如果你沒讀過不合適
      下一篇:Git內部原理之深入解析Git對象
      相關文章
      www.91亚洲| 亚洲人成网站色7799| 国产亚洲精品免费| 亚洲国产美女精品久久久| 亚洲国产精品免费观看| 亚洲毛片一级带毛片基地| 亚洲一区二区在线免费观看| 亚洲国产精品免费视频| 亚洲免费视频在线观看| 青青草原精品国产亚洲av| 亚洲一区二区成人| 亚洲美女人黄网成人女| 亚洲性色高清完整版在线观看| 亚洲沟沟美女亚洲沟沟| 亚洲精品在线视频观看| 亚洲大片免费观看| 亚洲日本人成中文字幕| 亚洲熟妇无码av另类vr影视 | 亚洲精品日韩一区二区小说| 亚洲精品V天堂中文字幕| 久久亚洲中文无码咪咪爱| 亚洲1区2区3区精华液| 亚洲第一页综合图片自拍| 4338×亚洲全国最大色成网站| 区三区激情福利综合中文字幕在线一区亚洲视频1 | 亚洲一区二区三区高清不卡| 亚洲狠狠成人综合网| 亚洲色欲色欱wwW在线| 韩国亚洲伊人久久综合影院| 亚洲国产成人爱av在线播放| 国产午夜亚洲精品午夜鲁丝片| 亚洲中文字幕不卡无码| 久久精品国产亚洲AV网站| 亚洲黄网站wwwwww| 77777亚洲午夜久久多喷| 亚洲欧美黑人猛交群| 国产精品亚洲专区一区| 久久久久亚洲AV成人网人人网站| 久久精品国产亚洲一区二区| 亚洲大香人伊一本线| 亚洲av午夜国产精品无码中文字|