centos 微软 云计算 Ubuntu linux mysql php 程序员 google nginx apache wordpress java Python Android 开源 shell Firefox Windows 编程

Linux進程基礎知識總結

進程

進程表示一個正在運行的程序實例,它是分配資源的最小單位,這種說法特別官方。

進程是一個非常重要的東西,我們運行的系統中同時跑著N個進程,這些進程都在默默的工作著,我們編寫的代碼,經過編譯、運行,也會生成一個進程。這個進程由程序代碼、數據、變量(占用著系統內存)、打開的文件(文件描述符)和環境組成。一般來說,對於Linux系統,系統會在進程之間共享程序代碼和系統函數庫,所以在任何時刻,內存中都只存在代碼的一份副本。

由於進程這個東西如此的重要,這篇文章就對Linux中的進程進行一個基本的總結。

創建一個進程

故事還是先從創建一個進程開始。看看下面的代碼:

01    #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() {
02        pid_t pid; char *msg; int n; printf("Fork program starting\n");
03        pid = fork(); // 創建一個進程 // 創建完成以後,父進程和子進程都從這裏開始運行 switch (pid)
04        { case -1:
05                perror("Fork failed."); exit(EXIT_FAILURE); case 0: // 當fork返回值為0時,表示這是子進程 msg = "This is the child";
06                n = 2; break; default: // 當fork不為0和-1時,表示這是父進程 msg = "This is the parent";
07                n = 3; break;
08        } for (; n > 0; --n)
09        { printf("%s\n", msg);
10            sleep(1);
11        } exit(EXIT_SUCCESS);
12    }


我們使用fork函數創建一個子進程,fork函數的聲明如下:

#include <unistd.h> pid_t fork(void);
fork函數返回一個pid_t類型的值,pid_t類型就是一個int類型。當調用fork創建進程成功以後,子進程和父進程同時都從fork函數之後的代碼開始運行。那麽既然都從同一個地方開始運行,如何區分是子進程還是父進程呢?

Linux中是使用fork的返回值來進行區分的,方法如下:

當fork返回pid_t的值為-1時,就表示創建進程出現了錯誤;
當fork返回pid_t的值為0時,就表示是子進程;
當fork返回pid_t的值不為0和-1時,就表示是父進程。
在上面的代碼中,我就是根據fork的返回值來判斷是子進程還是父進程的。怎麽都覺的怪怪的。

進程的結構

再說一次,進程很重要,我們需要去理解,那麽對於進程的結構就不得不說了。那麽我們使用fork創建了一個進程以後,該進程在內存內部的結構是如何的呢?我們來看看。

进程内存映像

如上圖所示,我們在實際編碼時並沒有考慮到圖中所說的這些內容,而上圖中所展示的東西,卻是理解父進程和子進程的關鍵,關於進程的很多問題,我們也是基於上圖出發進行思考的。在後面,我會專門拿出幾個問題來分析為什麽進程內存映像如此重要。

進程調度

在單個處理器上,同一時間只能有一個進程可以運行,其它進程都處於等待運行狀態。但是,我們實際的感覺是同一時刻有多個進程在“同時”進行運行,這是為什麽呢?

操作系統會分給每個進程一定的運行時間,叫做“時間片”。進程在這個“時間片”內運行,由於“時間片”非常的短,這樣就給人一種多個程序在同時運行的假象。操作系統是如何調度進程的呢?進程調度的規則有很多,比如根據優先級進行調度算法,先進先出調度算法等。

Linux內核進程調度器是根據進程的優先級來進行進程調度的。優先級高的進程運行得更為頻繁。

進程狀態

Linux上進程有5種狀態:

運行(正在運行或在運行隊列中等待)
中斷(休眠中, 受阻, 在等待某個條件的形成或接受到信號)
不可中斷(收到信號不喚醒和不可運行, 進程必須等待直到有中斷發生)
僵死(進程已終止, 但進程描述符存在, 直到父進程調用wait4()系統調用後釋放)
停止(進程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信號後停止運行)
當我們使用ps -aux命令查看進程狀態時,那些標識進程的字母又是什麽意思呢?,具體的進程狀態的字符標識如下表所示:

狀態標誌    狀態描述
D    不可中斷
R    運行
S    中斷
T    停止
Z    僵死
但是,有的時候,我們還會看到一些其它的標識,例如:

狀態標誌    狀態描述
w    僵死
<    高優先級進程
N    低優先級進程
L    內存鎖頁
看到上面又是中斷啊,不可中斷啊,停止啊,僵死啊,直接暈死,那麽到底如何理解這些概念呢?

運行狀態
在Linux中,僅等待CPU時間的進程稱為就緒進程,它們被放置在一個運行隊列中,一個就緒進程的狀態標誌位為TASK_RUNNING。一旦一個運行中的進程時間片用完, Linux內核的調度器會剝奪這個進程對CPU的控制權,並且從運行隊列中選擇一個合適的進程投入運行。
睡眠狀態
Linux中的進程睡眠狀態有兩種:
一種是可中斷的睡眠狀態,其狀態標誌位TASK_INTERRUPTIBLE;
另一種是不可中斷的睡眠狀態,其狀態標誌位為TASK_UNINTERRUPTIBLE。可中斷的睡眠狀態的進程會睡眠直到某個條件變為真,比如說產生一個硬件中斷、釋放進程正在等待的系統資源或是傳遞一個信號都可以是喚醒進程的條件。不可中斷睡眠狀態與可中斷睡眠狀態類似,但是它有一個例外,那就是把信號傳遞到這種睡眠狀態的進程不能改變它的狀態,也就是說它不響應信號的喚醒。不可中斷睡眠狀態一般較少用到,但在一些特定情況下這種狀態還是很有用的,比如說:進程必須等待,不能被中斷,直到某個特定的事件發生。
當向進程發送一個SIGSTOP信號,它就會因響應該信號而進入TASK_STOPPED狀態(除非該進程本身處於 TASK_UNINTERRUPTIBLE狀態而不響應信號)。(SIGSTOP與SIGKILL信號一樣,是非常強制的。不允許用戶進程通過 signal系列的系統調用重新設置對應的信號處理函數。)
向進程發送一個SIGCONT信號,可以讓其從TASK_STOPPED狀態恢復到TASK_RUNNING狀態。

僵死狀態
進程在退出的過程中,處於TASK_DEAD狀態。在這個退出過程中,進程占有的所有資源將被回收,除了task_struct結構(以及少數資源)以外。於是進程就只剩下task_struct這麽個空殼,故稱為僵屍。之所以保留task_struct,是因為task_struct裏面保存了進程的退出碼、以及一些統計信息。而其父進程很可能會關心這些信息。比如在Shell中,$?變量就保存了最後一個退出的前臺進程的退出碼,而這個退出碼往往被作為if語句的判斷條件。
當然,內核也可以將這些信息保存在別的地方,而將task_struct結構釋放掉,以節省一些空間。但是使用task_struct結構更為方便,因為在內核中已經建立了從pid到task_struct查找關系,還有進程間的父子關系。釋放掉task_struct,則需要建立一些新的數據結構,以便讓父進程找到它的子進程的退出信息。
父進程可以通過wait系列的系統調用(如wait4、waitid)來等待某個或某些子進程的退出,並獲取它的退出信息。然後wait系列的系統調用會順便將子進程的屍體(task_struct)也釋放掉。

子進程在退出的過程中,內核會給其父進程發送一個信號,通知父進程來“收屍”。這個信號默認是SIGCHLD,但是在通過clone系統調用創建子進程時,可以設置這個信號。

通過下面的代碼能夠制造一個EXIT_ZOMBIE狀態的進程:

if (fork()) while(1) sleep(100);
使用ps -aux就會看到如下一條僵死進程的信息。

只要父進程不退出,這個僵屍狀態的子進程就一直存在。那麽如果父進程退出了呢,誰又來給子進程“收屍”?

當進程退出的時候,會將它的所有子進程都托管給別的進程(使之成為別的進程的子進程)。托管給誰呢?可能是退出進程所在進程組的下一個進程(如果存在的話),或者是1號進程。所以每個進程、每時每刻都有父進程存在。除非它是1號進程。

總結

哦,這篇文章總結了不少內容,大體上都是一些你在大學操作系統課就應該學會的東西。好吧,我大學操作系統課都在睡覺,導致現在還要寫這麽一篇文章來復習這些東西,罪過,罪過。

2015年1月13日 於深圳。

延伸阅读

评论