[APUE]進程式控制制(上)

来源:http://www.cnblogs.com/orlion/archive/2017/01/06/6253989.html
-Advertisement-
Play Games

一、進程標識 進程ID 0是調度進程,常常被稱為交換進程(swapper)。該進程並不執行任何磁碟上的程式--它是內核的一部分,因此也被稱為系統進程。進程ID 1是init進程,在自舉(bootstrapping)過程結束時由內核調用。該進程的程式文件在UNIX的早期版本中是/etc/init,在較 ...


一、進程標識

  進程ID 0是調度進程,常常被稱為交換進程(swapper)。該進程並不執行任何磁碟上的程式--它是內核的一部分,因此也被稱為系統進程。進程ID 1是init進程,在自舉(bootstrapping)過程結束時由內核調用。該進程的程式文件在UNIX的早期版本中是/etc/init,在較新版本中是/sbin/init。此進程負責在內核自舉後啟動一個UNIX系統。init通常讀與系統有關的初始化(/etc/rc*文件),並將系統引導到一個狀態(例如多用戶)。init進程決不會終止。它是一個普通的用戶進程(與交換進程不同,它不是內核中的系統進程),但是它以超級用戶特權運行。
  在某些UNIX的虛存實現中,進程ID 2是頁精靈進程(pagedaemon)。此進程負責支持虛存系統的請頁操作。與交換進程一樣,頁精靈進程也是內核進程。
除了進程ID,每個進程還有其他標識符。下列函數可以返回這些標識符:

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);
返回: 調用進程的進程ID
pid_t getppid(void);
返回: 調用進程的父進程ID
uid_t getuid(void);
返回: 調用進程的實際用戶ID
uid_t geteuid(void);
返回: 調用進程的有效用戶ID
gid_t getgid(void);
返回: 調用進程的實際組ID
gid_t getegid(void);
返回: 調用進程的有效阻ID

這些函數都沒有出錯返回

二、fork函數

  一個進程調用fork函數是UNIX內核創建一個新進程的唯一方法(除了交換進程、init進程和頁精靈進程)

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);
返回: 子進程中為0,父進程中為子進程的進程ID,出錯為-1.

  子進程和父進程繼續執行fork之後的指令。子進程是父進程的複製品。例如,進程獲得父進程數據空間、堆和棧的複製品。但是這些都是進程擁有的拷貝不是與父進程共用。如果正文段是只讀的,則父、子進程共用正文段。
  現在很多實現並不做一個父進程數據段和堆的完全拷貝,因為在fork之後經常跟隨著exec。作為替代,使用了在**寫時複製(Copy On Write, COW)**的技術。這些區域由父、子進程共用,而且內核將它們的存取許可權改成只讀的。如果有進程試圖修改這些區域,則內核為有關部分(典型的是虛存系統中的"頁"),做一個拷貝。

#include <sys/types.h>
#include <stdio.h>
#inlcude <unistd.h>

int glob = 6;
char buf[] = "a write to stdout\n";

int main(void)
{
    int var;
    pid_t pid;
    
    var = 88;
    if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1) {
        fprintf(stderr, "write error");
    }
    
    printf("before fork \n");
    
    if ((pid = fork()) < 0) {
        fprintf(stderr, "fork error");
    } else if (pid == 0) {
        glob++;
        var++;
    } else {
        sleep(2);
    }
    
    printf("pid=%d,glob=%d,var=%d\n", gitpid(), glob, var);
    
    return 0;
}

  

  編譯運行上面的進程:
 

  可以看到當我們將輸出重定向到temp.out文件後多出個before fork的輸出。write函數是不帶緩存的。因為在fork之前調用write,所以其數據寫到標準輸出一次。但是標準IO是帶緩存的。如果標準輸出連到終端設備,則它是行緩存,否則它是全緩存。當以交互方式運行該程式時,只得到printf輸出的行一次,其原因是標準輸出緩存由新行符刷新。當我們將printf("before fork \n");後的換行符去掉之後即printf("before fork");來驗證這一點,修改之後輸出結果是:

  

  可以看到before fork列印了兩次,這說明因為我們去掉了換行符所以標準輸出流的行緩存不會被flush。

  但是當將標註輸出重新定向到一個文件時,卻得到printf輸出行兩次。其原因是,將標準輸出重新定向到一個文件時標準輸出流就不是行緩存而是全緩存了,在fork之前調用了printf一次,但當調用fork時,該行數據仍在緩存中,然後在父進程數據空間複製到子進程的過程中時,該緩存數據也被覆制到了子進程中。於是那時父、子進程各自有了帶該行內容的緩存。在exit之前的第二個printf將其數據添加到現存的緩衝中。當每個進程終止時,緩存中的內容將被寫到相應文件中。
  文件共用   對於上面的程式需要註意:在重定向父進程的標準輸出時也重定向了子進程的標準輸出。fork的一個特性是所有由父進程打開的文件描述符都被覆制到子進程中。父、子進程每個相同的打開文件描述符共用一個文件表項。
  這種共用文件的方式使父子進程對同一文件使用了一個文件位移量。對於以下情況:一個進程fork了一個子進程,然後等待子進程終止。假定,作為普通處理的一部分,父、子進程都向標準輸出執行寫操作。如果父進程使其標準輸出重定向(很可能是由shell實現的),那麼子進程寫到該標準輸出時,他將更新與父進程共用的該文件的位移量。在我們所考慮的例子中,當父進程等待子進程時,子進程寫到標準輸出;而在子進程終止後,父進程也寫入到標準輸出上,並且知道其輸出會添加在子進程所寫數據之後。如果父、子進程不共用同一文件位移量,這種形式的交互就很難實現。[為了理解這一點可參看APUE3.10節]

 


  如果父、子進程寫到同一文件描述符文件,但又沒有任何形式的同步(例如使父進程等待子進程),那麼它們的輸出就會相互混合(假定所用的文件描述符是在fork之前打開的)。
  在fork之後處理文件描述符有兩種常見的情況:
  (1) 父進程等待子進程完成。這種情況下,父進程無需對其描述符做任何處理。
  (2) 父、子進程各自執行不同的程式段。在這種情況下,在fork之後,父、子進程各自它們不需使用的文件描述符,並且不幹擾對方使用的文件描述符。

  除了打開文件之外,很多父進程的其他性質也會由子進程繼承:

  • 實際用戶ID、實際組ID、有效用戶ID、有效組ID。
  • 添加組ID。
  • 進程組ID。
  • 對話期ID。
  • 控制終端。
  • 設置-用戶-ID標誌和設置-組-ID標誌。
  • 當前工作目錄。
  • 根目錄。
  • 文件方式創建屏蔽字。(umask)
  • 信號屏蔽和排列。
  • 對任一打開文件描述符的在執行時關閉標誌。
  • 環境。
  • 連接的共用存儲段。

  父、子進程之間的區別是:

  • fork的返回值。
  • 進程ID
  • 不同的父進程iD。
  • 子進程的tms_utime,tms_stime,tms_cutime以及tms_ustime設置為0。
  • 父進程設置的鎖,子進程不繼承。
  • 子進程的未決告警被清除。
  • 子進程的未決信號集設置被清除。

三、vfork函數

  vfork函數的調用序列和返回值與fork相同,但兩個函數的語義不同。
  vfork用於創建一個新進程,而該進程的目的是exec一個新程式。vfork與fork一樣都創建一個子進程,但是它並不將父進程的地址空間完全複製到子進程中,因為子進程會立即調用exec(或exit),所以就不會用到此地址空間。
  vfork和fork之間的另一個區別是:vfork保證子進程先運行,在它調用exec/exit之後父進程才可能被調度運行。(如果在調用exec/exit之前子進程依賴於父進程的進一步動作,則會導致死鎖。) 對於以下示例:

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int glob = 6; int main(void) { int var; pid_t pid; var = 88; printf("before vfork\n"); if ((pid = vfork()) < 0) { fprintf(stderr, "vfork error\n"); } else if (pid == 0) { glob++; var++; _exit(0); } printf("pid=%d,glob=%d,var=%d\n", getpid(), glob, var); return 0; }

  編譯運行該程式:
 

  需要註意在上面的程式我們調用了_exit而不是exit。_exit並不執行IO緩存的刷新操作。如果是調用exit而不是_exit,則該程式的輸出是:

  

(上圖是APUE原書, 下圖是我在centos上實驗結果

  

 之所以結果不同是因為在linux中子進程關閉的是自己的, 雖然他們共用標準輸入、標準輸出、標準出錯等 “打開的文件”, 子進程exit時,也不過是遞減一個引用計數,不可能關閉父進程的,所以父進程還是有輸出的。

)

  可見父進程的printf輸出消失了。其原因是子進程調用了exit,它刷新開關閉了所有標準IO流,這包括標準輸出。雖然這是由子進程執行的,但卻是在父進程的地址空間中進行的,所以所有受到影響的標準IO FILE對象都是在父進程中。當父進程調用prinf時,標準輸出已經被關閉了,於是printf返回-1。

四、exit函數

 進程有三種正常終止法和兩種異常終止法。

  (1) 正常終止:

      (a) 在main函數內執行return語句,這相當於調用exit。

  (b) 調用exit函數。

  (c) 調用_exit函數。

  (2) 異常終止:

  (a) 調用abort。它產生SIGABRT信號,所以是下一種異常終止的特例。

  (b) 當進程接收到某個信號時。進程本身(例如調用abort函數)、其他進程和內核都能產生傳送到某一進程的信號。例如:進程越出其地址空間訪問存儲單元,或者除以0,內核都會為該進程產生相應的信號。

  對上述任意一種終止情形,我們都希望終止進程能夠通知其父進程它是如何終止的。對於e x i t和_ e x i t,這是依靠傳遞給它們的退出狀態( exit status)參數來實現的。在異常終止情況,內核(不是進程本身)產生一個指示其異常終止原因的終止狀態( termination status) 。在任意一種情況下,該終止進程的父進程都能用 w a i t或w a i t p i d函數(在下一節說明)取得其終止狀態。(退出狀態是傳給exit/_exit的參數,或main返回值。在最後調用_exit時內核將其退出狀態轉為終止狀態,如果子進程正常終止那父進程可以獲取子進程的退出狀態)。

  一定是一個父進程生成一個子進程。上面又說明瞭子進程將其終止狀態返回給父進程。但是如果父進程在子進程之前終止,則將如何呢 ?其回答是對於其父進程已經終止的所有進程,它們的父進程都改變為init進程。我們稱這些進程由init進程領養。其操作過程大致是:在一個進程終止時,內核逐個檢查所有活動進程,以判斷它是否是正要終止的進程的子進程,如果是,則該進程的父進程 I D就更改為1 ( i n i t進程的I D )。這種處理方法保證了每個進程有一個父進程。

  另一個我們關心的情況是如果子進程在父進程之前終止,那麼父進程又如何能在做相應檢查時得到子進程的終止狀態呢?對此問題的回答是內核為每個終止子進程保存了一定量的信息,所以當終止進程的父進程調用 w a i t或waitpid 時,可以得到有關信息。這種信息至少包括進程I D、該進程的終止狀態、以反該進程使用的 C P U時間總量。內核可以釋放終止進程所使用的所有存儲器,關閉其所有打開文件。在 U N I X術語中,一個已經終止、但是其父進程尚未對其進行善後處理(獲取終止子進程的有關信息、釋放它仍占用的資源)的進程被稱為僵死進程。

  最後一個要考慮的問題是:一個由i n i t進程領養的進程終止時會發生什麼 ?它會不會變成一個僵死進程?對此問題的回答是“否” ,因為i n i t被編寫成只要有一個子進程終止, i n i t就會調用一個w a i t函數取得其終止狀態。這樣也就防止了在系統中有很多僵死進程。當提及“一個 i n i t的子進程”時,這指的是 i n i t直接產生的進程(例如,將在9 . 2節說明的g e t t y進程),或者是其父進程已終止,由init 領養的進程。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 記憶體優化表(Memory-Optimized Table,簡稱MOT)使用樂觀策略(optimistic approach)實現事務的併發控制,在讀取MOT時,使用多行版本化(Multi-Row versioning)創建數據快照,讀操作不會對數據加鎖,因此,讀寫操作不會相互阻塞。寫操作會申請行級鎖 ...
  • 因為長時間沒有使用資料庫了,或者把密碼改完之後就忘了資料庫密碼,不能正常進入資料庫,也無法修改密碼,有一個簡單的常用修改密碼方式: 1.首先找到和打開mysql.exe和mysqld.exe所在的文件夾(在你所安裝的Mysql的bin文件夾下),複製路徑地址。 2.Windows+R打開cmd命令提 ...
  • 一、為什麼需要瞭解鎖 1.1 死鎖問題 1.2 併發問題導致的不正確數據的讀取和存儲,破壞數據一致性的 丟失更新:當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題--最後的更新覆蓋了由其他事務所做的更新。例如,兩個編輯人員製作了同一 ...
  • 許多Linux使用者安裝操作系統時都會遇到這樣的困境:如何精確評估和分配各個硬碟分區的容量,如果當初評估不准確,一旦系統分區不夠用時可能不得不備份、刪除相關數據,甚至被迫重新規劃分區並重裝操作系統,以滿足應用系統的需要。 LVM是Linux環境中對磁碟分區進行管理的一種機制,是建立在硬碟和分區之上、 ...
  • 系統環境:centos7 需要軟體:nginx-1.3.16.tar.gz libevent-2.0.21-stable.tar.gz Pcre 和 pcre-devel nginx下載地址:http://nginx.org/download/nginx-1.3.16.tar.gz libevent ...
  • 1.寫出leds_open,leds_write函數2.1告訴內核這幾個函數的存在?定義一個結構體file_operations2.2把這個結構體告訴內核?用register_chrdev(major,name,file_operations) //將主設備號與file_operations結構一起 ...
  • 1、以4.3.2版本的編譯器為例 將arm-linux-gcc-4.3.2.tar.bz2放在/work目錄下 解壓縮: sudo tar jxvf /work/arm-linux-gcc-4.3.2.tar.bz2 -C / 添加環境變數: sudo vi /etc/environment 註釋掉 ...
  • 如果要ftp訪問linux需要安裝ftp服務,vsftpd是Linux下比較好的的FTP伺服器。 一、檢查安裝vsftp 二、編輯vsftp配置項 配置開啟虛擬賬戶(只能登陸ftp,不能登陸系統)以及設置訪問目錄 三、安裝Berkeley DB工具(文件資料庫,我們用文件存放虛擬用戶的賬號密碼) 四 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...