程序:
程序是存放在存儲介質上的一個可執行文件。
進程:
進程是程序的執行實例,包括程序計數器、寄存器和變量的當前值。
程序是靜态的,進程是動态的:
程序是一些指令的有序集合,而進程是程序執行的過程。進程的狀态是變化的,其包括進程的創建、調度和消亡
在Linux系統中,進程是管理事務的基本單元。進程擁有自己獨立的處理環境和系統資源(處理器、存儲器、I/O設備、數據、程序)。 可使用exec函數由内核将程序讀入内存,使其執行起來成為一個進程。
1.2 進程的狀态及轉換進程整個生命周期可以簡單劃分為三種狀态: 就緒态:
進程已經具備執行的一切條件,正在等待分配CPU的處理時間。
執行态:
該進程正在占用CPU運行。
等待态:
進程因不具備某些執行條件而暫時無法繼續執行的狀态。
1.3 進程控制塊
進程控制塊(PCB)
OS是根據PCB來對并發執行的進程進行控制和管理的。系統在創建一個進程的時候會開辟一段内存空間存放與此進程相關的PCB數據結構。PCB是操作系統中最重要的記錄型數據結構。PCB中記錄了用于描述進程進展情況及控制進程運行所需的全部信息。PCB是進程存在的唯一标志,在linux中PCB存放在task_struct結構體中
調度數據
進程的狀态、标志、優先級、調度策略等。
時間數據
創建該進程的時間、在用戶态的運行時間、在内核态的運行時間等。
文件系統數據
umask掩碼、文件描述符表等。
内存數據、進程上下文、進程标識(進程号)等等。
2、進程控制2.1、進程号進程号為0及1的進程由内核創建。進程号為0的進程通常是調度進程,常被稱為交換進程(swapper)。進程号為1的進程通常是init進程。除調度進程外,在linux下面所有的進程都由進程init進程直接或者間接創建。
進程号(PID)
标識進程的一個非負整型數。
父進程号(PPID)
任何進程(除init進程)都是由另一個進程創建,該進程稱為被創建進程的父進程,對應的進程号稱為父進程号(PPID)。
進程組号(PGID)
進程組是一個或多個進程的集合。他們之間相互關聯,進程組可以接收同一終端的各種信号,關聯的進程有一個進程組号(PGID)
Linux操作系統提供了三個獲得進程号的函數getpid()、getppid()、getpgid()。需要包含頭文件:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void)
功能:獲取本進程号(PID)
pid_t getppid(void)
功能:獲取調用此函數的進程的父進程号(PPID)
pid_t getpgid(pid_t pid)
功能:獲取進程組号(PGID),參數為0時返回當前PGID,否則返回參數指定的進程的PGID
示例:
2.2、進程創建
在linux環境下,創建進程的主要方法是調用以下兩個函數:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
pid_t vfork(void);
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void)
功能:
fork()函數用于從一個已存在的進程中創建一個新進程,新進程稱為子進程,原進程稱為父進程。
返回值:
成功:子進程中返回0,父進程中返回子進程ID。
失敗:返回-1。
說明:
使用fork函數得到的子進程是父進程的一個複制品,它從父進程處繼承了整個進程的地址空間。地址空間:包括進程上下文、進程堆棧、打開的文件描述符、信号控制設定、進程優先級、進程組号等。子進程所獨有的隻有它的進程号,計時器等。因此,使用fork函數的代價是很大的。
fork函數執行結果 :
示例1:
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
int global_val=10;
int main(int argc,char *argv[])
{
pid_t pid;
int num=1;
pid = fork();
if(pid < 0)
perror("fork");
if(pid == 0)
{
num ;
global_val ;
printf("this is son process global_val=%d,num=%d\n",global_val,num);
}
else
{
sleep(1);
printf("this is father process global_val=%d,num=%d\n",global_val,num);
}
printf("common area\n");
return 0;
}
從上面的程序可以看出,子進程對變量所做的改變并不影響父進程中該變量的值,說明父子進程各自擁有自己的地址空間。一般來說,在fork之後是父進程先執行還是子進程先執行是不确定的。這取決于内核所使用的調度算法。如要求父子進程之間相互同步,則要求某種形式的進程間通信。
示例2:
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc,char *argv[])
{
pid_t pid;
char *buf = "hello world\n";
int lenth = write(1,buf,strlen(buf));
if(lenth!=strlen(buf))
{
printf("write error\n");
}
printf("fork \n");
pid = fork();
if(pid < 0)
perror("fork");
if(pid == 0)
{
printf("this is son process\n");
}
else
{
sleep(1);
printf("this is father process\n");
}
return 0;
調用fork函數後,父進程打開的文件描述符都被複制到子進程中。在重定向父進程的标準輸出時,子進程的标準輸出也被重定向。write函數是系統調用,不帶緩沖。标準I/O庫是帶緩沖的,當以交互方式運行程序時,标準I/O庫是是行緩沖的,否則它是全緩沖的。
2.3、進程挂起進程在一定的時間内沒有任何動作,稱為進程的挂起
#include <unistd.h>unsigned int sleep(unsigned int sec);
功能:
進程挂起指定的秒數,直到指定的時間用完或收到信号才解除挂起。
返回值:
若進程挂起到sec指定的時間則返回0,若有信号中斷則返回剩餘秒數。
注意:
進程挂起指定的秒數後程序并不會立即執行,系統隻是将此進程切換到就緒态
父子進程有時需要簡單的進程間同步,如父進程等待子進程的結束。linux下提供了以下兩個等待函數wait()、waitpid()。需要包含頭文件:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:
等待子進程終止,如果子進程終止了,此函數會回收子進程的資源。調用wait函數的進程會挂起,直到它的一個子進程退出或收到一個不能被忽視的信号時才被喚醒。若調用進程沒有子進程或它的子進程已經結束,該函數立即返回
參數:
函數返回時,參數status中包含子進程退出時的狀态信息。子進程的退出信息在一個int中包含了多個字段,用宏定義可以取出其中的每個字段。
返回值:
如果執行成功則返回子進程的進程号。出錯返回-1,失敗原因存于errno中
取出子進程 的退出信息
WIFEXITED(status)
如果 子進程是正常終止的,取出的字段值非零。WEXITSTATUS(status)返回子進程的退出狀态,退出狀态保存在status變量的8~16位。在用此宏前應先用宏WIFEXITED判斷子進程是否正常退出,正常退出才可以使用此宏。注意:此status是個wait的參數指向的整型變量
示例:
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0)
perror("fork");
if(pid == 0)
{
int i=0;
for(i=0;i< 3;i )
{
printf("this is son process\n");
sleep(1);
}
_exit(2);
}
else
{
int status;
wait(&status);
if(WIFEXITED(status)!=0)
{
printf("son process return %d\n",WEXITSTATUS(status));
}
printf("this is father process\n");
}
return 0;
}
pid_t waitpid(pid_t pid, int *status,int options);
功能:
等待子進程終止,如果子進程終止了,此函數會回收子進程的資源。
返回值:
如果執行成功則返回子進程ID。出錯返回-1,失敗原因存于errno中。
參數pid的值有以下幾種類型:
pid>0:等待進程ID等于pid的子進程。pid=0等待同一個進程組中的任何子進程,如果子進程已經加入了别的進程組,waitpid不會等待它。pid=-1:等待任一子進程,此時waitpid和wait作用一樣。pid<-1:等待指定進程組中的任何子進程,這個進程組的ID等于pid的絕對值。
status參數中包含子進程退出時的狀态信息。
options參數能進一步控制waitpid的操作:0:同wait,阻塞父進程,等待子進程退出。WNOHANG:沒有任何已經結束的子進程,則立即返回。WUNTRACED如果子進程暫停了則此函數馬上返回,并且不予以理會子進程的結束狀态。(跟蹤調試,很少用到)
返回值:
成功:返回狀态改變了的子進程的進程号;如果設置了選項WNOHANG并且pid指定的進程存在則返回0。出錯:返回-1。當pid所指示的子進程不存在,或此進程存在,但不是調用進程的子進程,waitpid就會出錯返回,這時errno被設置為ECHILD。
示例3:
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc,char *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0)
perror("fork");
if(pid == 0)
{
int i=0;
for(i=0;i<3;i )
{
printf("this is son process\n");
sleep(1);
}
_exit(2);
}
else
{
waitpid(pid,NULL,0);
printf("this is father process \n");
}
return 0;
}
僵屍進程(Zombie Process)
進程已運行結束,但進程的占用的資源未被回收,這樣的進程稱為僵屍進程。子進程已運行結束,父進程未調用wait或waitpid函數回收子進程的資源是子進程變為僵屍進程的原因。
孤兒進程(Orphan Process)
父進程運行結束,但子進程未運行結束的子進程。
守護進程(精靈進程)(Daemon process)
守護進程是個特殊的孤兒進程,這種進程脫離終端,在後台運行。
2.3、進程的終止在linux下可以通過以下方式結束正在運行的進程:
void exit(int value);
void _exit(int value);
#include <stdlib.h>
void exit(int value)
功能:
結束進程執行
參數:
status:返回給父進程的參數(低8位有效)。
#include <unistd.h>
void _exit(int value)
功能:
結束進程執行
參數:
status:返回給父進程的參數(低8位有效)。
exit和_exit函數的區别:
exit為庫函數,而_exit為系統調用
進程在退出前可以用atexit函數注冊退出處理函數。
#include <stdlib.h>
int atexit(void (*function)(void));
功能:
注冊進程正常結束前調用的函數,進程退出執行注冊函數。
參數:
function:進程結束前,調用函數的入口地址。
一個進程中可以多次調用atexit函數注冊清理函數,正常結束前調用函數的順序和注冊時的順序相反。
pid_t vfork(void)
功能:
vfork函數和fork函數一樣都是在已有的進程中創建一個新的進程,但它們創建的子進程是有區别的。
返回值:
創建子進程成功,則在子進程中返回0,父進程中返回子進程ID。出錯則返回-1。
fork和vfork函數的區别:
* vfork保證子進程先運行,在它調用exec或exit之後,父進程才可能被調度運行。
* vfork和fork一樣都創建一個子進程,但它并不将父進程的地址空間完全複制到子進程中,因為子進程會立即調用exec(或exit),于是也就不訪問該地址空間。相反,在子進程中調用exec或exit之前,它在父進程的地址空間中運行,在exec之後子進程會有自己的進程空間。
進程的替換
exec函數族
#include <unistd.h>
int execl(const char *pathname,const char *arg0,…,NULL);
int execlp(const char *filename,const char *arg0,…,NULL);
int execle(const char *pathname,const char *arg0,…,NULL,char *const envp[]);
int execv(const char *pathname,char *const argv[]);
int execvp(const char *filename,char *constargv[]);
int execve(const char *pathname,char *const argv[],char *const envp[]);
六個exec函數中隻有execve是真正意義的系統調用(内核提供的接口),其它函數都是在此基礎上經過封裝的庫函數。 l(list):
參數地址列表,以空指針結尾。
參數地址列表
char *arg0, char *arg1, ..., char *argn, NULL
v(vector):
存有各參數地址的指針數組的地址。使用時先構造一個指針數組,指針數組存各參數的地址,然後将該指針數組地址作為函數的參數。
p(path)
按PATH環境變量指定的目錄搜索可執行文件。以p結尾的exec函數取文件名做為參數。當指定filename作為參數時,若filename中包含/,則将其視為路徑名,并直接到指定的路徑中執行程序。
e(environment):
存有環境變量字符串地址的指針數組的地址。execle和execve改變的是exec啟動的程序的環境變量(新的環境變量完全由environment指定),其他四個函數啟動的程序則使用默認系統環境變量
exec函數族與一般的函數不同,exec函數族中的函數執行成功後不會返回。隻有調用失敗了,它們才會 返回 -1。失敗後從原程序的調用點接着往下執行。在編程中,如果用到了exec函數族,一定要記得加錯誤判斷語句
一個進程調用exec後,除了進程ID,進程還保留了下列特征不變:
父進程号進程組号控制終端根目錄當前工作目錄進程信号屏蔽集未處理信号
#include <stdlib.h>
int system(const char *command);
功能:
system會調用fork函數産生子進程,子進程調用exec啟動/bin/sh -c string來執行參數string字符串所代表的命令,此命令執行完後返回原調用進程。
參數:
要執行的命令的字符串
返回值:
如果command為NULL,則system()函數返回非0,一般為1。如果system()在調用/bin/sh時失敗則返回127,其它失敗原因返回-1。
注意:
system調用成功後會返回執行shell命令後的返回值。其返回值可能為1、127也可能為-1,故最好應再檢查errno來确認執行成功
示例:
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc,char *argv[])
{
int status;
status = system("sync");
if(WIFEXITED(status))
{
printf("the exit status is %d\n",status);
}
else
{
printf("exit\n");
}
return 0;
}
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!