1. 程式人生 > >Linux系統編程之進程

Linux系統編程之進程

得到 aik sig types 西遊 今天 適用於 type ces

前一段時間對文件I/O的基本操作基本操作做了總結,今天這裏繼續按照我的理解對linux系統編程的進程操作進行總結。


首先我們先理解幾個概念:程序、進程、線程。

所謂程序,就是計算機指令的集合,它以文件的形式存儲在磁盤上,進程是一個程序在其自身的地址空間中的一次執行活動。而線程進程內的一個執行單元,也是進程內的可調度實體。說完這個不知道大家理解了嗎?反正我第一次聽到這個概念以後看到的時候可以明白過後就忘記了,現在我給大家舉一個例子幫助大家理解,大家都看電視劇吧,所謂程序,就是一個劇本,像什麽《西遊記》、《神雕俠侶》……好多,這裏不是介紹電視劇,回歸我們的主題,程序呢就是一個劇本,那麽進程就是一個具體拍好的電視劇,比如86版《西遊記

》,每播放一次就是編譯好的程序的執行,也就是進程。有人會說了,那我不喜歡86版《西遊記》,就喜歡看張紀中版《西遊記》那這個有對應什麽呢?這可難不倒博主,博主對這個問題,也做了思考,這裏我使用C語言開發程序,那麽有人學的Java、python……對吧,那麽用不同語言開發的程序這就對應著不同版本的《西遊記》,我們繼續,那麽線程是什麽呢?我們的《西遊記》都有很多集,同樣的我們的進程也是由多個線程組成的也就是線程。這樣說,不知道大家記住了嗎?大家記住這個以後我還是給出他們的概念和相互之間的區別。

技術分享圖片技術分享圖片


1、概念:

程序:程序是計算機指令的集合,它以文件的形式存儲在磁盤上。

進程:進程是一個程序在其自身的地址空間中的一次執行活動。

線程:進程內的一個執行單元,也是進程內的可調度實體。

2、聯系:

1、一個進程是程序的一次動態執行,程序是放在硬盤中的靜態的,進程是占用系統資源是動態的加載到內存中的。

2、進程是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。而線程線程,有時被稱為輕量級進程,是程序執行流的最小單元。

3、一個線程可以創建和撤銷另一個線程;同一個進程中的多個線程之間可以並發執行.

4、相對進程而言,線程是一個更加接近於執行體的概念,它可以與同進程中的其他線程共享數據,但擁有自己的棧空間,擁有獨立的執行序列。

3、區別

地址空間:線程是進程內的一個執行單元;進程至少有一個線程;它們共享進程的地址空間;而進程有自己獨立的地址空間;

資源擁有:進程是資源分配和擁有的單位,同一個進程內的線程共享進程的資源 線程是處理器調度的基本單位,但進程不是. 進程和線程二者均可並發執行. 簡而言之,一個程序至少有一個進程,一個進程至少有一個線程. 線程的劃分尺度小於進程,使得多線程程序的並發性高。 另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。 線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程 序中,由應用程序提供多個線程執行控制。 從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

區分了這三個重要概念以後,我們重點來看與進程有關的Linux/Unix系統API。


1、創建子進程fork

       #include <unistd.h>
       pid_t fork(void);

fork系統調用用於創建子進程,一個現存進程調用fork函數是UNIX內核創建一個新進程的唯一方法(這並不適用於交換進程、init進程和精靈進程。這些進程是由內核作為自舉過程的一部分以特殊方式創建的)。由fork創建的新進程被稱為子進程( child process)。該函數被調用一次,但返回兩次。兩次返回的區別是子進程的返回值是0,而父進程的返回值則是新子進程的進程ID。將子進程I D返回給父進程的理由是:因為一個進程的子進程可以多於一個,所以沒有一個函數使一個進程可以獲得其所有子進程的進程ID。fork使子進程得到返回值0的理由是:一個進程只會有一個父進程,所以子進程總是可以調用getppid以獲得其父進程的進程ID (進程ID 0總是由交換進程使用,所以一個子進程的進程ID不可能為0)。

下來我寫個簡單的程序測試一下這個API的用法:

#include<stdlib.h>
#include<unistd.h>
int main()
{
    pid_t pid;
    //fork調用一次,返回兩次,子進程返回0,父進程返回子進程ID,
    pid = fork();

    if(pid < 0)
    {
        printf("NO  child \n");
        exit(1);
    }
    else if( 0 == pid)
    {
        printf("child \n");
    }
    else
    {
        printf("I am arent \n");
    }


    return 0;
}

2、vfork()

vfork同樣是用來創建進程的,但是fork由細微的不同,

1.vfork保證子進程先運行,在它調用exec或exit之後父進程才可能被調度運行。如果在調用這兩個函數之前子進程依賴於父進程的進一步動作,則會導致死鎖。

2.fork要拷貝父進程的進程環境;而vfork則不需要完全拷貝父進程的進程環境,在子進程沒有調用exec和exit之前,子進程與父進程共享進程環境,相當於線程的概念,此時父進程阻塞等待。

同樣的我給出簡單的測試用例:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    pid_t pid = -1;

    pid = vfork();

    if(pid < 0)
    {
        printf("vfork error\n");
        exit(1);
    }
    else if(pid > 0)
    {
        printf("I am parent\n");
    }
    else
    {
        sleep(10);
        printf("I am child\n");
        exit(1);
    }

    return 0;
}

我們可以從運行結果得到,每次都是子進程運行完成後,才是父進程運行。


3、getpid()/getppid()

獲取當前進程id和父進程id,進程id是標識進程的唯一標識。

我這裏同樣給出一個簡單測試用例:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>


int main()
{
    pid_t pid;
    //fork調用一次,返回兩次,子進程返回0,父進程返回子進程ID,
    pid = fork();
    if(pid < 0)
    {
        printf("NO  child \n");
        exit(1);
    }
    else if( 0 == pid)
    {
        printf("I am Child \n");
        printf("pid = %d\n",getpid());
        printf("ppid = %d\n",getppid());
    }
    else if (pid > 0)
    {
        sleep(1);
        printf("I am parent \n");
        printf("pid = %d\n",getpid());
        printf("ppid = %d\n",getppid());
    }
    return 0;
}
運行結果:                       
[root@localhost Fork]# ./fork-2 
I am Child 
pid = 6353
ppid = 6352
I am parent 
pid = 6352
ppid = 3674


進入下一個系統API之前我們先學習幾個概念:僵屍進程、孤兒進程,如同名字一樣,僵屍進程就是子進程


孤兒進程:一個父進程退出,而它的一個或多個子進程還在運行,那麽那些子進程將成為孤兒進程。孤兒進程將被init進程(進程號為1)所收養,並由init進程對它們完成狀態收集工作。

  僵屍進程:一個進程使用fork創建子進程,如果子進程退出,而父進程並沒有調用wait或waitpid獲取子進程的狀態信息,那麽子進程的進程描述符仍然保存在系統中。這種進程稱之為僵死進程。

之前我們用fork簡單創建一個進程後,執行程序後發現,子進程和父進程運行是沒有先後順序的,而且是分開顯示的,我們之前可以用sleep函數,實現防止產生孤兒進程,但是這種方法比較耗費系統資源,解決孤兒進程和僵屍進程,我們這裏就要引入其他幾個API: wait()/waitpid()

           #include <sys/types.h>
       #include <sys/wait.h>
       pid_t wait(int *status);
       pid_t waitpid(pid_t pid, int *status, int options);


wait()函數,當調用後,阻塞等待任意一個子進程退出後,就會立即返回。調用成功返回這個子進程的ID,調用失敗返回-1。wait()與waitpid函數裏面的的status返回一個值,用幾個宏測試其子進程退出狀態。

wait獲取staus後檢測處理
宏定義 描述
WIFEXITED(status) 如果進程子進程正常結束,返回一個非零值
WEXITSTATUS(status) 如果WIFEXITED非零,返回子進程退出碼
WIFSIGNALED(status) 子進程因為捕獲信號而終止,返回非零值
WTERMSIG(status) 如果WIFSIGNALED非零,返回信號代碼
WIFSTOPPED(status) 如果進程被暫停,返回一個非零值
WSTOPSIG(status) 如果WIFSTOPPED非零,返回信號代碼


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

int main()
{
    pid_t pid = -1,child_pid = -1;
    int status;
    pid = fork();

    if(pid < 0)
    {
        perror("fork ");
        exit(1);
    }
    else if( pid == 0)
    {
          printf("I am child\n");

    }
    else if(pid > 0)
    {
        printf("I am parent\n");
        printf("child pid = %d \n",pid);
        child_pid = wait(&status);
        printf("wait pid = %d\n",child_pid);
        //測試子進程返回狀態
        printf("Exit Status = %d\n",WIFEXITED(status));
    }
    return 0;
}

上面代碼我給出了,wait()函數的用法,以及status的基本用法,其他幾個宏的用法類似,這裏沒有給出其他幾個用法。


對於waitpid()系統調用,

與wait函數的區別就是waitpid用來等待某個特定進程的結束
函數原型:
pid_t waitpid(pid_t pid, int *status, int options);
參數:
status如果不為空,會把狀態信息寫到它指向的位置
options允許改變waitpid的行為,最有用的一個選項是WNOHANG,它的作用是防止waitpid把調用者的執行掛起
返回值:成功返回等待子進程的pid,失敗返回-1


以上就是關於進程操作的基本函數,後邊將繼續總結,通過綜合的案例來綜合使用這幾個API。


Linux系統編程之進程