天天看點

Linux中程式、程序、線程的基礎知識( 精心整理!帶fork\exec代碼實作!)1. 程式和程序、線程是什麼?2. Linux下程序、線程的基礎知識3. linux下程序、線程相關代碼實作(C語言)

1. 程式和程序、線程是什麼?

程式定義:計算機程式是指電腦能夠識别的機器指令的集合。

  • 在linux系統中,簡單來說程式就是一種靜态的、被存儲在計算機的指定位置(磁盤)上的檔案,其中包含一些計算機能夠識别的指令和資料,也就是所謂的可執行檔案。
  • 其實正如我們運作一個程式(可執行檔案),通常在 Shell中輸入指令運作就可以了,在這運作的過程中包含了程式到程序轉換的過程,整個轉換過程主要包含以下 3 個步驟:(1) 查找指令對應程式檔案的位置。(2)使用 fork()函數為啟動一個新程序。(3)在新程序中調用 exec 族函數裝載程式檔案,并執行程式檔案中的main()函數。

程序定義:程序是一個正在執行的程式的執行個體。

  • 通常我們所說的程式運作,是指磁盤中的程式(靜态的實體檔案)被拿到電腦記憶體中去了,這個時候,也就産生了所謂的程序(系統動态執行某程式(可執行檔案)中的指令)。
  • 程序通過享有系統資源來執行程式,例如CPU\記憶體\磁盤\外設\程序ID等資訊和資源。程序是系統中程式執行和資源配置設定的基本機關。每個程序都擁有自己的資料段、代碼段和堆棧段。

線程定義:線程(英語:thread)是作業系統能夠進行運算排程的最小機關。它被包含在程序之中,是程序中的實際運作機關。

  • 程序是資源配置設定的最小機關,線程是程式執行的最小機關。一個标準的線程由線程ID,目前指令指針(PC),寄存器集合和堆棧組成。另外,線程是程序中的一個實體,是被系統獨立排程和分派的基本機關,線程是一種輕量級的程序,也就是說線程可以簡化由程序帶來的系統資源的開銷。
  • 線程自己不擁有系統資源,隻擁有一點兒在運作中必不可少的資源,一個程序可以有多個線程來執行,但它可與同屬一個程序的其它線程共享程序所擁有的全部資源。一個線程可以建立和撤消另一個線程,同一程序中的多個線程之間可以并發執行。
程式并不能單獨執行,隻有将程式加載到記憶體中,系統為他配置設定資源後才能夠執行,這種執行的程式稱之為
	程序,也就是說程序是系統進行資源配置設定和排程的一個獨立機關,每個程序都有自己單獨的位址空間。程式由文
	件變成運作狀态,至少需要一個程序,而一個程序至少需要一個線程來幹活。線程使用的資源是程序的資源,但
	線程占用的資源要遠小于程序所占到的資源,故程序崩潰則該程序下的線程也随之崩潰。
           

2. Linux下程序、線程的基礎知識

前言:我們都知道,Linux是一個多任務系統,它使用某種排程政策支援多個任務并發執行。事實上,單核處理器在某一個時刻隻能執行一個任務,而Linux采用先來先服務、短作業優先、優先權排程等排程算法使得任務會頻繁地切換執行,是以給使用者多個任務同時運作的感覺。而多任務會帶來多個程序,當有多個程序時,對系統而言,什麼最能區分出程序之間的差別呢?答案就是程序辨別符(PID),Linux通過每個程序的唯一辨別符來區分程序。而這麼多程序既然是通過排程算法,切換運作,那麼某一時刻未正在運作的程序、等待運作的程序、已經運作完的程序等等,它們是不是有不同的狀态呢?如果說我想讓一個程序停止,是不是要改變程序的狀态呢?這就涉及到程序的狀态和狀态轉換了:

Linux系統中終端中可使用

ps -e -l

指令檢視所有程序狀态,其中STAT一欄對應的狀态說明如下:

狀态 說明
R 可運作狀态。表示程序在運作隊列中,處于正在執行或即将運作狀态,隻有該狀态的程序才可能在CPU上運作,而同一時刻可能有多個程序處于可運作狀态
S 可中斷的睡眠狀态。處于這個狀态的程序因為等待某種事件的發生而被挂起,比如程序在等待信号
D 不可中斷的睡眠狀态。通常是在等待輸入或者輸出(I/O)完成,處于這種狀态的程序不能響應異步信号
T 停止狀态。通常是被shell的工作信号控制,或因為它被追蹤,程序正處于調試器的控制之下
Z 僵死狀态。程序成為僵屍程序
X 退出狀态。程序即将被回收
I 程序是多線程的
s 程序是會話首程序
. 程序屬于前台程序組
< 高優先級任務

Linux程序間狀态轉換圖:

Linux中程式、程式、線程的基礎知識( 精心整理!帶fork\exec代碼實作!)1. 程式和程式、線程是什麼?2. Linux下程式、線程的基礎知識3. linux下程式、線程相關代碼實作(C語言)

linux中程序相關常用指令:

把程序運作到背景: ./a.out &
	jobs    檢視背景程序
	ps  	檢視系統程序
	top 	動态顯示程序
	nice 	按使用者指定的優先級運作程序
	{
			優先級大小: -20 ~ 19
			nice -[n] [參數]  例子: nice -9 ./a.out  将程式的優先級設定為9 --9表示 -9
	}
	renice  改變正在運作程序的優先級
	{
		renice N PID          例子: renice 9 1024  沒有 - 字首.
		renice N -u farsight  将屬于使用者farsight的程序
		renice N -g group     将屬于group組的程序
	}
	kill 向程序發信号
	bg 将挂起的程序在背景執行
	fg 把背景運作的程序放到前台
           

(具體指令用法可以man手冊檢視或另行百度)

《Linux中ps指令使用方法超連結》

《野火開源電子書程序章節超連結》

《Linux系統中的程序檢視及管理超連結》

3. linux下程序、線程相關代碼實作(C語言)

linux中的程序分類:

  • 互動式程序: 顧名思義,互動式程序需要同使用者進行互動,需要等待使用者的輸入,這類程序能夠立刻響應,常見的有shell指令程序、文本編輯器的程序等。
  • 批處理程序: 這類程序不需要同使用者互動,一般都是在背景運作,不必很快響應,故往往不會被系統優先排程,常見的有gcc的編譯操作程序等等。
  • 守護程序: 這類程序一直在背景運作,和任何終端都沒有關聯,系統啟動時運作,開機時結束,當然中途也可以人為用kill指令殺死,很多系統的程序,例如服務程序都是守護程序的形式。

fork()函數的使用:

我們在linux系統中程式設計時,可以調用系統的fork函數來根據我們的需要建立一個新的程序。在一個程序中調用fork函數創造出的新程序成為子程序,原來的新程序為父程序。fork得到的子程序幾乎完全複制了父程序的内容,包括程序的虛拟記憶體空間的位址、程序的上下文、代碼段、程序堆棧、記憶體資訊、檔案描述符等等,故fork出來一個新程序會消耗掉同原來的程序一樣多的系統資源,現在的Linux系統為了節約資源,采用了寫時複制(Copy on write)技術,在系統建立程序時,資源沒有立刻被複制過來,而是被推遲到需要寫入資料的時候,在此之前子程序以隻讀的形式共享父程序的資源,這種方式比調用時馬上克隆執行的更快。

我們可以通過

man 2 fork

指令使用幫助手冊來了解fork函數的資訊和用法,這裡我們重點關注傳回值,fork傳回不同的傳回值用來為供我們區分父程序(程序辨別符(PID)(>0))、子程序(傳回0)和錯誤狀态(傳回-1)。

RETURN VALUE
       Upon  successful  completion,  fork() shall return 0 to the child process and shall
       return the process ID of the child process to the parent  process.  Both  processes
       shall continue to execute from the fork() function. Otherwise, −1 shall be returned
       to the parent process, no child process shall be created, and errno shall be set to
       indicate the error.
       
ERRORS
       The fork() function shall fail if:

       EAGAIN The  system lacked the necessary resources to create another process, or the
              system-imposed limit on the total number of processes under  execution  sys‐
              tem-wide or by a single user {CHILD_MAX} would be exceeded.

       The fork() function may fail if:

       ENOMEM Insufficient storage space is available.

       The following sections are informative.
           

廢話不多說,直接上代碼:

/*************************************************************************
	> File Name: fork.c
	> Author:    許可
	> Created Time: 2020年04月04日 星期六 10時22分28秒
	> Description: 
 ************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>	//fork()需要包含的頭檔案
#include <unistd.h>		//fork()需要包含的頭檔案

int main(int argc, char *argv[])
{
	pid_t retvalue;
	retvalue = fork();	//調用fork()函數,調出的子程序從本行的下一行開始執行
	if (retvalue == -1)		//出錯
	{
		perror("fork error");
		return -1;
	}
	else if (retvalue == 0)	//子程序
	{
		while(1)
		{
		printf("child process,retvaluevalue is %d,My PID is %d\n",retvalue,getpid());
		sleep(5);
		}
	}
	else				//父程序
	{
		while(1)
		{
		printf("parent process.retvaluevalue is %d.My PID is %d\n",retvalue,getpid());
		sleep(5);
		}
	}
    return 0;
}
           

運作代碼,試驗現象如下:

Linux中程式、程式、線程的基礎知識( 精心整理!帶fork\exec代碼實作!)1. 程式和程式、線程是什麼?2. Linux下程式、線程的基礎知識3. linux下程式、線程相關代碼實作(C語言)

exec函數族的使用:

fork函數建立了一個跟父程序幾乎一樣的子程序,子程序能幹的活父程序也能自己幹,如果我們想讓子程序執行另外一個程式,做和父程序不一樣的事要怎麼辦呢?這裡我們就要用到exec函數,它可以根據指定的檔案名或目錄名找到可執行程式,并用它來取代目前程序的資料段、代碼段和堆棧段。當某個程序執行完exec函數後,除了程序号,其他的内容都被替換了,這樣就使得原來調用exec函數的程序另辟天地、重新做人了。

exec函數族基本可以滿足你在程式設計中操作任何一個linux中的可執行檔案(包括腳本)的需求,它是一種非常強大的API,強大到使用他的人也必須對他非常了解,不然的話使用時容易出錯,對于exec函數族的用法,我們可以使用

man 3 exec

指令來擷取相關資訊:

EXEC(3)                          Linux Programmer's Manual                         EXEC(3)

NAME
       execl, execlp, execle, execv, execvp - 執行某個檔案

總覽 (SYNOPSIS)
       #include <unistd.h>

       extern char **environ;

       int execl( const char *path, const char *arg, ...);
       int execlp( const char *file, const char *arg, ...);
       int execle( const char *path, const char *arg , ..., char * const envp[]);
       int execv( const char *path, char *const argv[]);
       int execvp( const char *file, char *const argv[]);

描述 (DESCRIPTION)
       exec 系列 函數 用 新的 程序 映象 置換 目前的 程序 映象.  本 手冊頁 描述的 這些 函數
       實際上 是 對 execve(2) 函數 的 前端(front-end) 包裝. (關于 目前程序  的  置換  詳見
       execve 的 手冊頁.)

       這些 函數 的 第一個 參數 是 待執行 程式 的 路徑名(檔案名).

       在  函數 execl, execlp, 和 execle 中, const char *arg 以及 省略号 代表 的 參數 可被
       視為 arg0, arg1, ..., argn.  他們 合起來 描述了 指向 null 結尾的 字元串 的 指針  列
       表,  即 執行程式 的 參數清單. 作為 約定, 第一個 arg 參數 應該 指向 執行程式名 自身.
       參數清單 必須 用 NULL 指針 結束!

       execv 和 execvp 函數 提供 指向 null 結尾的 字元串 的 指針數組 作為 新程式 的 參數列
       表.  作為 約定, 指針數組 中 第一個 元素 應該 指向 執行程式名 自身. 指針數組 必須 用
       NULL 指針 結束!

       execle 函數 同時 說明了 執行程序 的 環境(environment), 他 在 NULL 指針 後面 要求 一
       個 附加參數, NULL 指針 用于 結束 參數清單, 或者說, argv 數組. 這個 附加參數 是 指向
       null 結尾的 字元串 的 指針數組, 他 必須 用 NULL 指針 結束! 其他 函數 從 目前程序 的
       environ 外部變量 中 擷取 新程序 的 環境.

       某些 函數 有 特殊的 語義.

       如果 提供的 檔案名 中 不包含 斜杠符(/), 函數 execlp 和 execvp 将 同 shell 一樣 搜尋
       執行檔案. 搜尋路徑 由 環境變量 PATH 指定. 如果 該 變量  不存在,  則  使用  預設路徑
       ``:/bin:/usr/bin''.  另外, 某些 錯誤 要 特殊處理.

       如果 對 某個 檔案 的 通路 遭到 拒絕 ( execve 傳回 EACCES), 這些 函數 将 在 搜尋路徑
       中 繼續 尋找. 如果 沒有 找到 符合的 檔案, 他們 傳回時 把 errno 置為 EACCES.

       如果 無法 識别 檔案首部 ( execve 傳回 ENOEXEC), 這些 函數 将 以 檔案名 作為  第一個
       參數 調用 shell.  (如果 這個 嘗試 失敗 就 不再 進行 搜尋 了.)

傳回值 (RETURN VALUE)
       任何  exec  函數 傳回 均 表明 發生了 錯誤. 傳回值 是 -1, 全局變量 errno 指出 錯誤類
       型.

檔案 (FILE)
       /bin/sh

錯誤 (ERROR)
       這些 函數 均可能 失敗, errno 被 置為 庫函數 execve(2) 設定的 各種 錯誤類型.

另見 (SEE ALSO)
       sh(1), execve(2), fork(2), environ(5), ptrace(2)

相容性 (COMPATIBILITY)
       在 某些 其他系統 中, 預設路徑 (當 環境變量 PATH 不存在) 把 目前目錄  列在  /bin  和
       /usr/bin 後面, 這是 為了 防止 特洛伊木馬. Linux 在這兒 采取了 傳統的 "目前目錄優先"
       預設路徑.

           

例如如果我們要通過父程序fork出來的一個子程序去幫我們自動執行

ls -l -a

指令,我們可以用以下c代碼實作:

/*************************************************************************
	> File Name: exec.c
	> Author:    許可
	> Created Time: 2020年04月04日 星期六 12時49分45秒
	> Description: 
 ************************************************************************/

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

int main(int argc, char *argv[])
{
	pid_t ret;
	ret = fork();	//建立子程序
	if (ret == -1)	//出錯
	{
		perror("fork error");
		return -1;
	}
	else if (ret == 0) //子程序
	{
		printf("this is child process, id is %d\n",getpid());
		printf("the process will use ls -l -a:\n");
		if (execlp("/usr/bin/ls","-a","-l",NULL)<0)	//調用execp()運作ls
		{
			perror("execlp error");
			return -1;
		}
		//exit(EXIT_SUCCESS);
		return 0;
	}
	else	//父程序
	{
		printf("this is patent process ,id is %d,child procrss id is%d\n",getpid(),ret);
		exit(EXIT_SUCCESS);		//退出程序
	}
    return 0;
}

           

運作a.out試驗現象:

Linux中程式、程式、線程的基礎知識( 精心整理!帶fork\exec代碼實作!)1. 程式和程式、線程是什麼?2. Linux下程式、線程的基礎知識3. linux下程式、線程相關代碼實作(C語言)

END…熬夜碼字、整理不易,有點費頭發(* - *)~~ 如果喜歡本文,記得點贊、收藏支援一下額 !

繼續閱讀