程序員成長之旅 ——進程概念

進程和程序的區別

程序

完成特定任務一系列指令集合

單道程序: CPU只能一次執行一個程序
多程序設計:CPU執行多個程序,每個程序分爲幾段錯開執行

進程

每個進程都有自己的狀態和獨立的地址空間

從用戶角度看:進程是程序的一次動態執行過程
分時系統:時間片輪轉
從操作系統:進程是操作系統分時的基本單位(最小單位)

區別

程序:數據+代碼
進程:數據+代碼+堆棧+PCB
PCB:進程控制塊(Process.Control.Block)Linux下的PCB是task_struct(每個進程有一個PCB表示)---- 用鏈表存儲

併發:多個進程在一個cpu下采用進程切換的方式,在一段時間之內讓多個進程得以推進。
並行:多個進程在多個CPU下分別.同時進行。

  1. 進程是動態的,程序是靜態的;
  2. 進程的生命週期是短暫的,而程序相對永久;
  3. 進程有重要的數據結構PCB;
  4. 一個進程只能對應一個程序,而一個程序可以對應多個進程。

進程的進程調度算法

  1. 時間片輪轉調度算法(RR): 給每個進程固定的執行時間,根據進程到達的先後順序讓進程在單位時間片內執行,執行完成後便調度下一個進程執行,時間片輪轉調度不考慮進程等待時間和執行時間,屬於搶佔式調度。優點是兼顧長短作業;缺點是平均等待時間較長,上下文切換較費時。適用於分時系統。

  2. 先來先服務調度算法(FCFS):根據進程到達的先後順序執行進程,不考慮等待時間和執行時間,會產生飢餓現象。屬於非搶佔式調度,優點是公平,實現簡單;缺點是不利於短作業。

  3. 優先級調度算法 (HPF):在進程等待隊列中選擇優先級最高的來執行。

  4. 多級反饋隊列調度算法:將時間片輪轉與優先級調度相結合,把進程按優先級分成不同的隊列,先按優先級調度,優先級相同的,按時間片輪轉。優點是兼顧長短作業,有較好的響應時間,可行性強,適用於各種作業環境。

  5. 高響應比優先調度算法:根據“響應比=(進程執行時間+進程等待時間)/ 進程執行時間”這個公式得到的響應比來進行調度。高響應比優先算法在等待時間相同的情況下,作業執行的時間越短,響應比越高,滿足段任務優先,同時響應比會隨着等待時間增加而變大,優先級會提高,能夠避免飢餓現象。優點是兼顧長短作業,缺點是計算響應比開銷大,適用於批處理系統。

在這裏插入圖片描述
在這裏插入圖片描述

調研task_struct結構體, 理解結構體中的各個字段的含義

  • 在Linux中描述進程的結構體叫task_struct
  • task_struct是Linux內核中的一種數據結構,它會被裝載到RAM(內存)中幷包含進程的信息。

task_struct都可能包含哪些成員的信息?

  1. 進程狀態,記錄進程在等待,運行或死鎖
  2. 調度信息,由那個函數調度,怎樣調度等
  3. 進程的通訊狀態
  4. 因爲要插入進程樹,必須有聯繫父子兄弟的指針,當然是task_struct型
  5. 時間信息,比如計算好執行的時間 以便CPU分配
  6. 標號,決定進程歸屬
  7. 可以讀寫打開的一些文件信息
  8. 進程上下文和內核上下文
  9. 處理上下文
  10. 內存信息

因爲每一個pcb都是這樣的,只有這些結構,才能滿足一個進程的所有要求。

進程狀態

  1. volatile long state;
  2. int exit_state

state成員的可能取值如下:

#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256

系統中的每個進程都必然處於以上所列進程狀態中的一種。

對上述信息進行簡要描述:

      TASK_RUNNING : 表示進程要麼正在執行,要麼正要準備執行。

TASK_INTERRUPTIBLE : 表示進程被阻塞(睡眠),直到某個條件變爲真。條件一旦達成,進程的狀態就被設置爲TASK_RUNNING。

TASK_UNINTERRUPTIBLE: 意義與TASK_INTERRUPTIBLE類似,除了不能通過接受一個信號來喚醒以外。

      __TASK_STOPPED: 表示進程被停止執行。

      __TASK_TRACED : 表示進程被debugger等進程監視。

        EXIT_ZOMBIE : 表示進程的執行被終止,但是其父進程還沒有使用wait()等系統調用來獲知它的終止信息。

          EXIT_DEAD :表示進程的最終狀態。

EXIT_ZOMBIE和EXIT_DEAD也可以存放在exit_state成員中。

代碼模擬實現孤兒和殭屍進程的場景

進程(id):PID
父進程(id):PPID

殭屍進程

什麼是殭屍進程?
簡而言之就是子進程先退出,父進程沒有接收到子進程退出時的返回代碼,進而形成殭屍進程。(Z狀態)

殭屍進程的危害

  • 退出狀態本身就是要用數據去維護的,也屬於進程信息,所以保存在task_struct(PCB)中,那麼z狀態一直不退出,pcb就要一直維護
  • 當一個父進程創建了很多子進程,但卻一直沒有回收,這樣會造成內存資源的浪費。(因爲數據結構本身就要佔用內存)
  • 如果父進程不去回收子進程,還會造成內存泄露。

怎樣避免殭屍進程?

  • 父進程通過wait和waitpid等函數等待子進程的結束,這樣會導致父進程掛起
  • 如果父進程不關心子進 程什麼時候結束,那麼可以用signal(SIGCHLD,SIG_IGN) 通知內核,自己對子進程的結束不感興趣,那麼子進程結束後,內核會回收, 並不再給父進程發送信號。
  • 倘若父進程非常忙,那麼可以用singal函數爲SIGCHLD安裝handler(因爲子進程結束後,父進程會收到該信號,可以在handler中調用wait回收)
  • fork兩次,父進程fork一個子進程,然後繼續工作,子進程fork一個孫進程後退出,那麼孫進程被init接管,孫進程結束後,init會回收。不過子進程的回收還是需要自己做。

代碼場景

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	pid_t id = fork();
	if(id < 0)
	{
		perror("fork");
		return 1;
	}
	else if(id > 0 )
	{
		//father
		printf("father [%d] is sleeping...\n",getpid());
		sleep(30);
	}
	else
	{
		printf("child [%d] is begin Z...\n",getpid());
		sleep(5);
		exit(EXIT_SUCCESS);
	}
	return 0;
}

子進程五秒後進入殭屍狀態
在這裏插入圖片描述
孤兒進程

什麼是孤兒進程?
孤兒進程就是在其父進程執行完成或被終止後仍然繼續運行的一類進程。這些孤兒進程將會被init進程(進程號爲1號)所收養,並由init進程對它們完成狀態收集工作。因爲有init進程會循環的wait()已經退出的子進程,所以孤兒進程並不會產生什麼危害。

代碼

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	pid_t id = fork();
	if(id < 0)
	{
		perror("fork");
		return 1;
	}
	else if(id == 0)
	{
		//child
		printf("I am child,pid : %d\n",getpid());
		sleep(10);
	}
	else
	{
		//parent
		printf("I am parent,pid : %d\n",getpid());
		sleep(3);
		exit(0);
	}
	return 0;
}

看到結果,父進程3秒後退出,子進程變成孤兒進程
在這裏插入圖片描述

setenv, export環境變量相關的函數和命令

setenv

相關的函數

作用:增加或者修改環境變量
注意:通過此函數並不能添加或修改 shell 進程的環境變量,或者說通過setenv函數設置的環境變量只在本進程,而且是本次執行中有效。如果在某一次運行程序時執行了setenv函數,進程終止後再次運行該程序,上次的設置是無效的,上次設置的環境變量是不能讀到的。
頭文件:#include<stdlib.h>
注:stdlib.h在Linux和Windows中略不同,比如setenv函數是用在linux中的,在Windows中沒有setenv函數而用putenv來代替
函數聲明:int setenv(const char *name,const char * value,int overwrite);
函數說明:setenv()用來改變或增加環境變量的內容。參數name爲環境變量名稱字符串。參數 value則爲變量內容,參數overwrite用來決定是否要改變已存在的環境變量。如果沒有此環境變量則無論overwrite爲何值均添加此環境變量。若環境變量存在,當overwrite不爲0時,原內容會被改爲參數value所指的變量內容;當overwrite爲0時,則參數value會被忽略。返回值 執行成功則返回0,有錯誤發生時返回-1。
相關函數:getenv,putenv,unsetenv

命令
Linux中的功能:查詢或顯示環境變量
語法:setenv [變量名稱] [變量值]
setenv用於在C shell設置環境變量的值
用法:setenv ENVVAR value
ENVVAR 爲所要設置的環境變量的名。value爲所要設置的環境變量的值
例:setenv PATH "/bin:/usr/bin:usr/sbin:"設置環境path的搜索路徑爲/bin,/usr/bin以及/usr/sbin

export

Linux中的功能:設置或顯示環境變量(比如我們要用一個命令,但這個命令的執行文件不在當前目錄,這樣我們每次用的時候必須制定執行文件的目錄,麻煩,在代碼中先執行export,這個相當於告訴程序,執行某某東西時,需要的文件或什麼東西在這些目錄裏)
說明:在shell中執行程序時,shell會提供一組環境變量。export可新增,修改或刪除環境變量,供後續執行的程序使用。export的效力僅及於該次登陸操作。
語法:export [-fnp] [變量名稱] = [變量設置值]
參數說明:

  • -f  代表[變量名稱]中爲函數名稱。
  • -n  刪除指定的變量。變量實際上並未刪除,只是不會輸出到後續指令的執行環境中。
  • -p  列出所有的shell賦予程序的環境變量。
    延伸:export設置環境變量是暫時的,只在本次登錄中有效,可修改如下文件來使命令長久有效。

注意:
1、執行腳本時是在一個子shell環境運行的,腳本執行完後該子shell自動退出;
2、一個shell中的系統環境變量纔會被複制到子shell中(用export定義的變量);
3、一個shell中的系統環境變量只對該shell或者它的子shell有效,該shell結束時變量消失(並不能返回到父shell中)。
4、不用export定義的變量只對該shell有效,對子shell也是無效的。

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