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