Linux 多任務編程之進程

多任務編程

在這裏插入圖片描述

1、進程的概述(瞭解)

1.1、程序和進程的區別:

程序:靜態的 佔磁盤空間
進程:可執行文件 執行的動態過程 動態(創建、進程的調度、消亡) 佔內存。

1.2、併發和並行的區別:

不管是併發、並行 都是多個任務 同時執行。

並行(parallel):

指在同一時刻,有多條指令在多個處理器上同時執行
在這裏插入圖片描述

併發(concurrency):

指在同一時刻只能有一條指令執行,但多個進程指令被快速的輪換執行,使得在宏觀上具有多個進程同時執行的效果,但在微觀上並不是同時執行的,只是把時間分成若干段,使多個進程快速交替的執行
在這裏插入圖片描述

總結:

並行:多核 多任務真正的並行
併發:單核 多任務 通過時間片 輪轉 是宏觀上的並行。

2、進程控制塊PCB(瞭解)

進程運行時,內核爲進程每個進程分配一個PCB(進程控制塊),維護進程相關的信息,Linux內核的進程控制塊是task_struct結構體:
在這裏插入圖片描述
部分截圖:
在這裏插入圖片描述
在這裏插入圖片描述

進程內存佈局:32位平臺 系統 會爲每一個進程 分配4G空間

並不是真正的四個內存空間,(虛擬的)
在這裏插入圖片描述

3、進程的狀態(瞭解)

進程狀態反映進程執行過程的變化。這些狀態隨着進程的執行和外界條件的變化而轉換。 在三態模型中,進程狀態分爲三個基本狀態,即運行態,就緒態,阻塞態。 在五態模型中,進程分爲新建態、終止態,運行態,就緒態,阻塞。

三態:就緒態、等待態、執行態 (以及狀態轉換)

就緒態:執行條件都準備了 等待CPU的調用

等待態:等待執行條件的滿足

執行態:cpu正在調度進程

在這裏插入圖片描述
如何查看進程狀態: ps -aux

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
0號進程 是內核進程 也是調度進程---->1初始化進程—>直接或者間接創建了所有進程

進程:是系統分配資源的基本單位(重要!!!!!)

如果想查看進程關係:pstree
pstree -p 會顯示進程號
在這裏插入圖片描述

4、進程號(重要)

每個進程都由一個進程號來標識,其類型爲 pid_t(整型),進程號的範圍:0~32767。進程號總是唯一的,但進程號可以重用。當一個進程終止後,其進程號就可以再次使用。

進程號(PID):

標識進程的一個非負整型數。

父進程號(PPID):

任何進程( 除 init 進程)都是由另一個進程創建,該進程稱爲被創建進程的父進程,對應的進程號稱爲父進程號(PPID)。如,A 進程創建了 B 進程,A 的進程號就是 B 進程的父進程號

進程組號(PGID):

進程組是一個或多個進程的集合。他們之間相互關聯,進程組可以接收同一終端的各種信號,關聯的進程有一個進程組號(PGID)

getpid函數
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
功能:
    獲取本進程號(PID)
參數:
    無
返回值:
    本進程號
    
getppid函數
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
功能:
    獲取調用此函數的進程的父進程號(PPID)
參數:
    無
返回值:
    調用此函數的進程的父進程號(PPID)
    
getpgid函數
#include <sys/types.h>
#include <unistd.h>
pid_t getpgid(pid_t pid);
功能:
    獲取進程組號(PGID)
參數:
    pid:進程號
返回值:
    參數爲 0 時返回當前進程組號,否則返回參數指定的進程的進程組號

案例:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
	printf("進程號的PID:%d\n", getpid());
	printf("父進程的PPID:%d\n", getppid());
	printf("進程組號的PGID:%d\n", getpgid(0));
	while(1);//阻塞 方便終端 驗證
	return 0;
}

運行結果:
在這裏插入圖片描述
在這裏插入圖片描述

5、進程的創建

5.1、創建進程解決啥問題?

讓多任務 同時執行 不相互阻塞

在這裏插入圖片描述

5.2、創建進程fork()

系統允許一個進程創建新進程,新進程即爲子進程,子進程還可以創建新的子進程,形成進程樹結構模型.

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:
    用於從一個已存在的進程中創建一個新進程,新進程稱爲子進程,原進程稱爲父進程。
參數:
    無
返回值:
    成功:子進程中返回 0,父進程中返回子進程 ID。pid_t,爲整型。
    失敗:返回-1。
    失敗的兩個主要原因是:
        1)當前的進程數已經達到了系統規定的上限,這時 errno 的值被設置爲 EAGAIN。
        2)系統內存不足,這時 errno 的值被設置爲 ENOMEM

案例:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
	//創建一個子進程
	pid_t pid =  fork();
	if(pid < 0)//創建失敗返回的是-1
	{
		perror("fork");
		return 0;
	}
	else if(pid == 0)//單純的認爲 就是子進程
	{
		//任務一
		while(1)
		{
			static int i=0;
			printf("i = %d\n",i++);
			sleep(1);
		}
	}
	else if(pid > 0)//單純的認爲 就是父進程
	{
		//任務二
		while(1)
		{
			static int j=0;
			printf("j = %d\n",j++);
			sleep(1);
		}
	}
	
	return 0;
}

運行結果:

在這裏插入圖片描述

5.3、fork創建的子進程 會複製 父進程資源

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
	int num = 10;
	//創建一個子進程
	pid_t pid =  fork();
	if(pid < 0)//創建失敗返回的是-1
	{
		perror("fork");
		return 0;
	}
	else if(pid == 0)//單純的認爲 就是子進程
	{
		num =1000;
		printf("子進程%d 中的num=%d\n", getpid(),num);
		getchar();//阻塞一下
	}
	else if(pid > 0)//單純的認爲 就是父進程
	{
		sleep(5);//等5秒 保證子進程 給num複製
		printf("父進程%d 中的num=%d\n", getpid(),num);
		getchar();//阻塞一下
	}
	
	return 0;
}

運行結果:
在這裏插入圖片描述
原理圖:
在這裏插入圖片描述

總結:父進程 創建 子進程 那麼 子進程 複製父進程的資源 父子進程擁有獨立的空間

5.4、父進程 創建 子進程的過程

使用fork函數得到的子進程是父進程的一個複製品,它從父進程處繼承了整個進程的地址空間。 地址空間: 包括進程上下文、進程堆棧、打開的文件描述符、信號控制設定、進程優先級、進程組號等。 子進程所獨有的只有它的進程號,計時器等。因此,使用fork函數的代價是很大的
在這裏插入圖片描述
在這裏插入圖片描述

5.5、fork創建的父子進程 誰先運行?

在fork之後是父進程先執行還是子進程先執行是不確定的。這取決於內核所使用的調度算法。

注意:子父進程之間讀共享 寫獨立

在這裏插入圖片描述

6、回收子進程的資源

在每個進程退出的時候,內核釋放該進程所有的資源、包括打開的文件、佔用的內存等。但是仍然爲其保留一定的信息,這些信息主要主要指進程控制塊PCB的信息(包括進程號、退出狀態、運行時間等)。 父進程可以通過調用wait或waitpid得到它的退出狀態同時徹底清除掉這個進程。

注意:

1、wait() 和 waitpid() 函數的功能一樣,區別在於,wait() 函數會阻塞,waitpid() 可以設置不阻塞,waitpid() 還可以指定等待哪個子進程結束。
2、一次wait或waitpid調用只能清理一個子進程,清理多個子進程應使用循環

6.1、wait函數

函數說明:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:
    等待任意一個子進程結束,如果任意一個子進程結束了,此函數會回收該子進程的資源。
參數:
    status : 進程退出時的狀態信息。
返回值:
    成功:已經結束子進程的進程號
    失敗: -1

1、父進程 有子進程 但是子進程 沒有退出

調用 wait() 函數的進程會掛起(阻塞),直到它的一個子進程退出或收到一個不能被忽視的信號時才被喚醒(相當於繼續往下執行)。 

2、父進程 沒有創建 子進程

若調用進程沒有子進程,該函數立即返回;

3、父進程 有子進程 但是 子進程 都退出了

若它的子進程已經結束,該函數同樣會立即返回,並且會回收那個早已結束進程的資源

總結一句話:wait 有子進程運行 我就等待
如果參數 status 的值不是 NULL,wait() 就會把子進程退出時的狀態取出並存入其中,這是一個整數值(int),指出了子進程是正常退出還是被非正常結束的。 這個退出信息在一個 int 中包含了多個字段,直接使用這個值是沒有意義的,我們需要用宏定義取出其中的每個字段。
取出子進程的退出信息 WIFEXITED(status) 如果子進程是正常終止的,取出的字段值非零。 WEXITSTATUS(status) 返回子進程的退出狀態,退出狀態保存在status變量的8~16位。在用此宏前應先用宏WIFEXITED判斷子進程是否正常退出,正常退出纔可以使用此宏。 注意:此status是個wait的參數指向的整型變量

案例:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main(int argc,char *argv[])
{
	pid_t pid = fork();//創建一個子進程
	if(pid < 0)
	{
		perror("fork");
		return 0;
	}
	else if(pid == 0)//子進程
	{
		for(int i=0;i<5;i++)
		{
			printf("子進程:%d的i=%d\n",getpid(),i);
			sleep(1);
		}
		
		//退出exit
		exit(10);
	}
	else if(pid > 0)//父進程
	{
		//父進程回收子進程的資源
		printf("父進程%d正在等子進程%d\n",getpid(), pid);
		int status = 0;
		pid_t son_pid = wait(&status);//阻塞
		printf("子進程%d退出了\n",son_pid );
		
		if( WIFEXITED(status))//正常退出
		{
			//獲取狀態碼
			printf("退出的狀態值爲:%d\n",WEXITSTATUS(status));
		}
	}
	return 0;
}

6.2、waitpid(瞭解)

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
功能:
    等待子進程終止,如果子進程終止了,此函數會回收子進程的資源。
參數:
    pid : 參數 pid 的值有以下幾種類型:
      pid > 0  等待進程 ID 等於 pid 的子進程。
      pid = 0  等待同一個進程組中的任何子進程,如果子進程已經加入了別的進程組,waitpid 不會等待它。
      pid = -1 等待任一子進程,此時 waitpid 和 wait 作用一樣。
      pid < -1 等待指定進程組中的任何子進程,這個進程組的 ID 等於 pid 的絕對值。
    status : 進程退出時的狀態信息。和 wait() 用法一樣。
    options : options 提供了一些額外的選項來控制 waitpid()。
            0:同 wait(),阻塞父進程,等待子進程退出。
            WNOHANG:沒有任何已經結束的子進程,則立即返回。
            WUNTRACED:如果子進程暫停了則此函數馬上返回,並且不予以理會子進程的結束狀態。(由於涉及到一些跟蹤調試方面的知識,加之極少用到)
返回值:
    waitpid() 的返回值比 wait() 稍微複雜一些,一共有 3 種情況:
        1) 當正常返回的時候,waitpid() 返回收集到的已經回收子進程的進程號;
        2) 如果設置了選項 WNOHANG,而調用中 waitpid() 還有子進程在運行,且沒有子進程退出,返回0;  父進程的所有子進程都已經退出了 返回-1; 返回>0表示等到一個子進程退出
        3) 如果調用中出錯,則返回-1,這時 errno 會被設置成相應的值以指示錯誤所在,如:當 pid 所對應的子進程不存在,或此進程存在,但不是調用進程的子進程,waitpid() 就會出錯返回,這時 errno 被設置爲 ECHILD

案例1:waitpid等價wait

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
	//創建一個進程
	pid_t  pid = fork();
	if(pid == 0)//表示子進程
	{
		int i=0;
		for(i=5;i>0; i--)
		{
			printf("子進程%d生命值%d\n",getpid(), i);
			sleep(1);
		}
		
		exit(0);
	}
	else if(pid > 0)//父進程
	{
		//pid是子進程的id
		int my_status=0;
		printf("父進程等待子進程%d中......\n",pid);
		waitpid(pid, &my_status, 0);
		printf("子進程%d已退出\n",pid);
		
		if(WIFEXITED(my_status))
		{
			printf("退出的狀態值:%d\n", WEXITSTATUS(my_status) );
		}
	}
	return 0;
}

運行結果:
在這裏插入圖片描述

7、創建多個子進程(重要)

7.1、創建多個子進程 引入

需求:我想創建2個子進程

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
	int i=0;
	for(i=0;i<2;i++)
	{
		pid_t pid = fork();//創建了3個子進程
	}
	while(1);
	return 0;
}

運行分析:
在這裏插入圖片描述
分析:循環兩次 得到3個子進程
如果能保證 子進程 不會創建新的子進程 那麼 對父進程 創建的子進程個數 就可以得到有效控制

案例:創建多進程的方法

如果我們創建n個子進程 那麼i=0,1,2,3,…,n-1都是子進程 i==n表示是父進程

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
	int i=0;
	for(i=0;i<2;i++)
	{
		pid_t pid = fork();
		//子進程 不創建 新的子進程(子進程 立馬 退出 for循環就行)
		if(pid == 0)
			break;
	}
	
	//區分父子進程
	if(i==0)//子進程1
	{
		printf("子進程1 進程號爲%d\n", getpid());
		getchar();
          //具體的任務一代碼
	}
	else if(i==1)//子進程2
	{
		printf("子進程2 進程號爲%d\n", getpid());
		getchar();
          //具體的任務二代碼
	}
	else if(i==2)//父進程 回收子進程的資源
	{
		printf("父進程 進程號爲%d\n", getpid());
		while(1)
		{	
			//參數-1 所有子進程  NULL不關心退出的狀態值  WNOHANG不阻塞
			pid_t pid = waitpid(-1, NULL, WNOHANG);
			if(pid > 0)//檢測到子進程退出
			{
				printf("子進程%d退出了\n",pid);
			}
			else if(pid == 0)//還有子進程
			{
				continue;//繼續waitpid等待
			}
			else if(pid == -1)//所有子進程都退出了
			{
				break;
			}
		}
	}
	return 0;
}

運行結果:
在這裏插入圖片描述
在這裏插入圖片描述

8、進程的特例

8.1、殭屍進程

子進程終止,父進程尚未回收子進程資源,子進程殘留資源(PCB)存放於內核中,子進程變成殭屍(Zombie)進程。 這樣就會導致一個問題,如果父進程不調用wait() 或 waitpid() 的話, 那麼保留的那段信息就不會釋放,其進程號就會一直被佔用,但是系統所能使用的進程號是有限的,如果大量的產生殭屍進程,將因爲沒有可用的進程號而導致系統不能產生新的進程,此即爲殭屍進程的危害,應當避免。

總結:子進程先結束 父進程沒有調用wait/waitpid回收子進程的資源 那麼子進程就是殭屍進程。

案例:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
	int i=0;
	for(i=0;i<2;i++)
	{
		pid_t pid = fork();
		if(pid == 0)
			break;
	}
	
	//區分父子進程
	if(i==0)//子進程1
	{
		printf("子進程1 進程號爲%d\n", getpid());
	}
	else if(i==1)//子進程2
	{
		printf("子進程2 進程號爲%d\n", getpid());
	}
	else if(i==2)//父進程 回收子進程的資源
	{
		while(1);//父進程不結束
	}
	
	return 0;
}

運行結果:
在這裏插入圖片描述
在這裏插入圖片描述

如果父進程 被結束 殭屍進程也會結束

8.2、孤兒進程

父進程運行結束,但子進程還在運行(未運行結束)的子進程就稱爲孤兒進程(Orphan Process)。 每當出現一個孤兒進程的時候,內核就把孤兒進程的父進程設置爲 init ,而 init 進程會循環地 wait() 它的已經退出的子進程。這樣,當一個孤兒進程淒涼地結束了其生命週期的時候,init 進程就會代表黨和政府出面處理它的一切善後工作。 因此孤兒進程並不會有什麼危害.

總結:父進程先結束 子進程任在運行 那麼子進程就是孤兒進程(被init進程接管)

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
	int i=0;
	for(i=0;i<2;i++)
	{
		pid_t pid = fork();
		if(pid == 0)
			break;
	}
	
	//區分父子進程
	if(i==0)//子進程1
	{
		printf("子進程1 進程號爲%d\n", getpid());
		while(1);
	}
	else if(i==1)//子進程2
	{
		printf("子進程2 進程號爲%d\n", getpid());
		while(1);
	}
	else if(i==2)//父進程 回收子進程的資源
	{
		printf("父進程 進程號爲%d\n", getpid());
	}
	return 0;
}

運行結果:
在這裏插入圖片描述

9、終端

在UNIX系統中,用戶通過終端登錄系統後得到一個Shell進程,這個終端成爲Shell進程的控制終端(Controlling Terminal),進程中,控制終端是保存在PCB中的信息,而fork會複製PCB中的信息,因此由Shell進程啓動的其它進程的控制終端也是這個終端。 默認情況下(沒有重定向),每個進程的標準輸入、標準輸出和標準錯誤輸出都指向控制終端,進程從標準輸入讀也就是讀用戶的鍵盤輸入,進程往標準輸出或標準錯誤輸出寫也就是輸出到顯示器上
在這裏插入圖片描述

獲取終端的名稱:
#include <unistd.h>
char *ttyname(int fd);
功能:由文件描述符查出對應的文件名
參數:
    fd:文件描述符
返回值:
    成功:終端名
    失敗:NULL

下面我們藉助ttyname函數,通過實驗看一下各種不同的終端所對應的設備文件

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
	pid_t pid = fork();
	if(pid == 0)//子
	{
		printf("子進程標準輸入的設備名:%s\n",ttyname(0));
		printf("子進程標準輸出的設備名:%s\n",ttyname(1));
		printf("子進程標準錯誤輸出的設備名:%s\n",ttyname(2));
		
	}
	else if(pid > 0)//父
	{
		sleep(1);
		printf("父進程標準輸入的設備名:%s\n",ttyname(0));
		printf("父進程標準輸出的設備名:%s\n",ttyname(1));
		printf("父進程標準錯誤輸出的設備名:%s\n",ttyname(2));
	}
	return 0;
}

運行結果:
在這裏插入圖片描述
在這裏插入圖片描述

10、進程組(瞭解)

進程組 進程組,也稱之爲作業。BSD於1980年前後向Unix中增加的一個新特性。代表一個或多個進程的集合。 每個進程都屬於一個進程組。在waitpid函數和kill函數的參數中都曾使用到。操作系統設計的進程組的概念,是爲了簡化對多個進程的管理。 當父進程,創建子進程的時候,默認子進程與父進程屬於同一進程組。進程組ID爲第一個進程ID(組長進程)。所以,組長進程標識:其進程組ID爲其進程ID
可以使用kill -SIGKILL -進程組ID(負的)來將整個進程組內的進程全部殺死
總結:一個或多個進程的集合 就是進程組。每個進程組 有自己的組ID 這個組ID就是進程組號。
在這裏插入圖片描述
終端運行的進程 如果沒有加入任何一個組 那麼該進程 自成一個進程組 (進程組ID == 當前進程的ID)

組長進程:如果進程的ID == 進程組的ID 那麼這個進程就叫組長進程。

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
	printf("進程%d\n",getpid());
	
	pid_t pid = fork();
	if(pid == 0)
	{
		printf("子進程ID:%d\n",getpid());
		pid_t pid = fork();
		if(pid == 0)
		{
			printf("孫進程ID:%d\n",getpid());
			getchar();
		}
		getchar();
	}
	else if(pid > 0)
	{
		printf("父進程ID:%d\n",getpid());
		getchar();
	}
	return 0;
}

運行結果:
在這裏插入圖片描述
原理圖:
在這裏插入圖片描述
kill -9 -進程組ID:進程組中的所有進程都會結束。
進程組啥時候解散?進程組中的所有進程都結束的時候才解散。

進程組生存期:進程組創建到最後一個進程離開(終止或轉移到另一個進程組)。 一個進程可以爲自己或子進程設置進程組ID。

#include <unistd.h>
pid_t getpgrp(void);                 /* POSIX.1 version */
功能:獲取當前進程的進程組ID
參數:無
返回值:總是返回調用者的進程組ID
pid_t getpgid(pid_t pid);
功能:獲取指定進程的進程組ID
參數:
    pid:進程號,如果pid = 0,那麼該函數作用和getpgrp一樣
返回值:
    成功:進程組ID
    失敗:-1
int setpgid(pid_t pid, pid_t pgid)
功能:
    改變進程默認所屬的進程組。通常可用來加入一個現有的進程組或創建一個新進程組。
參數:
    將參1對應的進程,加入參2對應的進程組中
返回值:
    成功:0
    失敗:-1

11、會話(瞭解)

會話是一個或多個進程組的集合。
會話是一個或多個進程組的集合。 一個會話可以有一個控制終端。這通常是終端設備或僞終端設備; 建立與控制終端連接的會話首進程被稱爲控制進程; 一個會話中的幾個進程組可被分爲一個前臺進程組以及一個或多個後臺進程組; 如果一個會話有一個控制終端,則它有一個前臺進程組,其它進程組爲後臺進程組; 如果終端接口檢測到斷開連接,則將掛斷信號發送至控制進程(會話首進程)

在這裏插入圖片描述
在這裏插入圖片描述
會話首進程特點:進程ID == 進程組ID == 會話ID
前臺進程組:擁有當前控制終端的進程組
後臺進程組:不擁有當前控制終端的進程組

創建會話注意事項

1) 調用進程不能是進程組組長,該進程變成新會話首進程(session header)
2) 該調用進程是組長進程,則出錯返回
3) 該進程成爲一個新進程組的組長進程
4) 需有root權限(ubuntu不需要)
5) 新會話丟棄原有的控制終端,該會話沒有控制終端。
6) 建立新會話時,先調用fork, 父進程終止,子進程調用setsid
API函數介紹 getsid函數:
#include <unistd.h>
pid_t getsid(pid_t pid);
功能:獲取進程所屬的會話ID
參數:
    pid:進程號,pid爲0表示查看當前進程session ID
返回值:
    成功:返回調用進程的會話ID
    失敗:-1

組長進程不能成爲新會話首進程,新會話首進程必定會成爲組長進程。
setsid函數:
setsid函數:
#include <unistd.h>
pid_t setsid(void);
功能:
    創建一個會話,並以自己的ID設置進程組ID,同時也是新會話的ID。調用了setsid函數的進程,既是新的會長,也是新的組長。
參數:無
返回值:
    成功:返回調用進程的會話ID
    失敗:-1

案例:創建一個會話:

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>

int main()
{
	printf("進程%d\n",getpid());
	//創建會話
	pid_t pid = fork();
	if(pid > 0)//父進程
		exit(0);
	
	//子進程設置會話
	setsid();
	
	while(1);
	return 0;
}

運行結果:
在這裏插入圖片描述

12、守護進程

守護進程(Daemon Process),也就是通常說的 Daemon 進程(精靈進程),是 Linux 中的後臺服務進程。它是一個生存期較長的進程,通常獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。一般採用以d結尾的名字。 守護進程是個特殊的孤兒進程,這種進程脫離終端,爲什麼要脫離終端呢?之所以脫離於終端是爲了避免進程被任何終端所產生的信息所打斷,其在執行過程中的信息也不在任何終端上顯示。由於在 Linux 中,每一個系統與用戶進行交流的界面稱爲終端,每一個從此終端開始運行的進程都會依附於這個終端,這個終端就稱爲這些進程的控制終端,當控制終端被關閉時,相應的進程都會自動關閉。 Linux 的大多數服務器就是用守護進程實現的。比如,Internet 服務器 inetd,Web 服務器 httpd 等。

創建守護進程模型

1) 創建子進程,父進程退出(必須) 所有工作在子進程中進行形式上脫離了控制終端
2) 在子進程中創建新會話(必須) setsid()函數 使子進程完全獨立出來,脫離控制
3) 改變當前目錄爲根目錄(不是必須) chdir()函數 防止佔用可卸載的文件系統 也可以換成其它路徑
4) 重設文件權限掩碼(不是必須) umask()函數 防止繼承的文件創建屏蔽字拒絕某些權限 增加守護進程靈活性
5) 關閉文件描述符(不是必須) 繼承的打開文件不會用到,浪費系統資源,無法卸載
6) 開始執行守護進程核心工作(必須) 守護進程退出處理程序模型

案例:守護進程

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	//1、創建會話
	pid_t pid = fork();
	if(pid > 0)//父進程
		exit(0);
	
	//子進程設置會話
	setsid();
	
	//2、更改目錄
	chdir("/");
	
	//3、設置掩碼
	umask(0002);
	
	//4、關閉文件描述符0 1 2
	close(0);
	close(1);
	close(2);
	
	while(1)
	{
		;//具體守護進程的服務程序
	}

	return 0;
}

運行結果:
在這裏插入圖片描述

13、vfork創建進程(瞭解)

vfork函數:創建一個新進程
pid_t vfork(void)
功能:
    vfork函數和fork函數一樣都是在已有的進程中創建一個新的進程,但它們創建的子進程是有區別的。
返回值:
    創建子進程成功,則在子進程中返回0,父進程中返回子進程ID。出錯則返回-1。

fork和vfork函數的區別:

vfork保證子進程先運行,在子進程調用exec或exit之後,父進程纔可能被調度運行。 vfork和fork一樣都創建一個子進程,但vfork並不將父進程的地址空間完全複製到子進程中,因爲子進程會立即調用exec(或exit),於是也就不訪問該地址空間。相反,在子進程中調用exec或exit之前,它在父進程的地址空間中運行,在exec之後子進程會有自己的進程空間.
在這裏插入圖片描述

案例:

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	pid_t pid = vfork();
	if(pid == 0)//子進程
	{
		int i=0;
		for(i=5;i>0;i--)
		{
			printf("子進程%d i=%d\n",getpid(),i);
			sleep(1);
		}
		exit(0);
	}
	else if(pid>0)//父進程
	{
		printf("父進程%d\n",getpid());
	}
	return 0;
}

運行結果:
在這裏插入圖片描述

總結一下vfork:

vfork創建的子進程 會先執行
如果父進程想執行:
子進程調用exit、_exit、退出,父進程纔會執行。
子進程調用exec函數族(系統會爲調用exec的進程 重新分配資源) 父進程也會運行。

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	pid_t pid = vfork();
	if(pid == 0)//子進程
	{
		printf("子進程2秒後啓動exec\n");
		sleep(2);
		printf("子進程已啓動exec\n");
		execlp("ls","ls",NULL);
		sleep(10);
	}
	else if(pid>0)//父進程
	{
		printf("父進程%d\n",getpid());
	}
	return 0;
}

14、exec函數族(瞭解)

在進程內部 啓動外部可執行程序。
exec 指的是一組函數,一共有 6 個:
在這裏插入圖片描述
l(list): 參數地址列表,以空指針結尾。 參數地址列表 char *arg0, char *arg1, …, char *argn, NULL v(vector)
p(path) 按PATH環境變量指定的目錄搜索可執行文件
e(environment): 存有環境變量字符串地址的指針數組的地址。
v(vector): 存有各參數地址的指針數組的地址
在這裏插入圖片描述

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
#if 0
	//execl 
	//int execl(const char *path, const char *arg, .../* (char  *) NULL */);
	printf("execl前\n");
	execl("/bin/ls", "ls", "-a","-l","-h",NULL);
	printf("execl後\n");//不會執行
#endif
#if 0
	//execlp 
	//int execlp(const char *file,cconst char *arg, ... /* (char  *) NULL */);
	printf("execlp前\n");
	execlp("ls", "ls", "-a","-l","-h",NULL);
	printf("execlp後\n");//不會執行
#endif
#if 0
	//execv 
	//int execv(const char *path, char *const argv[]);
	char *argv[]={"ls", "-a","-l","-h",NULL};
	printf("execv前\n");
	execv("/bin/ls", argv);
	printf("execv後\n");//不會執行
#endif
#if 1
	//execvp
	//int execvp(const char *file, char *const argv[]);
	char *argv[]={"ls", "-a","-l","-h",NULL};
	printf("execvp前\n");
	execvp("ls", argv);
	printf("execvp後\n");//不會執行
#endif
	return 0;
}

運行結果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章