1.進程和程序
進程是一個可執(zhí)行程序的實例,程序包含了一系列信息文件,這些信息描述了如何在運行期間創(chuàng)建一個進程:
二進制格式標識 :包含用于描述可執(zhí)行文件格式的元信息,內(nèi)核根據(jù)此信息來解釋文件中的其他信息:
a.out(匯編程序輸出)
COFF(通用文件格式)
ELF(可執(zhí)行連接格式),目前大多數(shù) linux 實現(xiàn)采用此種方式,具有更多優(yōu)點
機器語言指令 :對程序算法進行編碼
程序入口地址 :表示程序開始執(zhí)行時的起始指令位置
數(shù)據(jù) :程序文件包含的變量初始值和程序使用的字面量值(比如字符串)
符號表及重定位表 :描述程序中函數(shù)和變量的位置及名稱,包括調(diào)試和運行時的符號解析(動態(tài)鏈接)
共享庫和動態(tài)鏈接信息 :程序文件所包含的一些字段,列出程序運行時需要使用的共享庫,以及加載共享庫的動態(tài)鏈接器的路徑名
其他信息 :程序文件還包含了很多其他信息,用以描述如何創(chuàng)建進程
可以用一個程序來創(chuàng)建很多進程。
從內(nèi)核角度來看:
進程是內(nèi)核定義的抽象的實體,并為該實體分配用以執(zhí)行程序的各項系統(tǒng)資源。
進程由用戶內(nèi)存空間和一系列內(nèi)核數(shù)據(jù)結(jié)構(gòu)組成,用戶內(nèi)存空間包含了程序代碼及代碼所使用的變量,內(nèi)核數(shù)據(jù)結(jié)構(gòu)則用于維護進程狀態(tài)信息,其中包括:
進程相關(guān)的標識號
虛擬內(nèi)存表
打開文件的描述表
信號傳遞和處理的相關(guān)信息
進程資源使用及限制
當前工作目錄
一些其他信息
2.進程號和父進程號
每個進程都有一個進程號 PID,是一個正數(shù),唯一標識系統(tǒng)中的某個進程。
#include#include pid_t getpid(void);
getpid 返回值的數(shù)據(jù)類型是 pid_t ,專用于存儲進程號
Linux 內(nèi)核限制進程號需要小于 32767(內(nèi)核常量 PID_MAX 定義),新進程創(chuàng)建時,內(nèi)核會按順序?qū)⒁粋€可用的進程號分配給其使用,進程號到達 32767 的限制時,內(nèi)核將重置進程號計數(shù)器為300(不是1)
#include#include pid_t getppid(void);
getppid 獲取父進程的進程號
看 /proc/PID/status 文件提供的 PPid 字段可以獲知每個進程的父進程
pstree 命令可以查看進程家族樹
3.進程內(nèi)存布局
每個進程所分配的內(nèi)存都由很多虛擬內(nèi)存邏輯劃分的部分,稱之為 "段":
文本段 :包含了進程運行的程序機器語言指令,文本段是只讀的,也是可共享的
初始化數(shù)據(jù)段 :包含顯式初始化的全局變量和靜態(tài)變量
未初始化數(shù)據(jù)段 :包含了未顯示初始化的全局變量和靜態(tài)變量,程序啟動之前,系統(tǒng)將本段內(nèi)所有內(nèi)存初始化為0,這個段又稱為 BSS(block started by symbol) 段。未初始化的全局變量和靜態(tài)變量與初始化的全局變量和靜態(tài)變量分開放的原因:
程序在磁盤存儲時,沒有必要為未經(jīng)初始化的變量分配存儲空間
可執(zhí)行文件只需要記錄未初始化數(shù)據(jù)段的位置及大小,直到運行時再有程序加載器來分配這一空間
棧:動態(tài)增長和收縮的段,由棧幀組成,系統(tǒng)會為每個當前調(diào)用的函數(shù)分配一個棧幀,棧幀中存儲了函數(shù)的局部變量,實參和返回值
堆 :在運行時為變量動態(tài)進行分配的一塊區(qū)域,堆頂端稱為 program break
初始化數(shù)據(jù)段和未初始化數(shù)據(jù)段又常被稱為用戶初始化數(shù)據(jù)段和零初始化數(shù)據(jù)段。
size 命令可以顯示二進制文件的文本段,初始化數(shù)據(jù)段,未初始化數(shù)據(jù)段的大小。
argv 和 environ 用來存儲程序的命令行實參和環(huán)境列表
十六進制的地址會因為內(nèi)核配置和程序鏈接選項的差異而有所不同
灰色區(qū)域表示這些范圍在進程虛擬地址空間中不可用,也就是說沒有為這些區(qū)域創(chuàng)建頁表
4.虛擬內(nèi)存管理
linux 內(nèi)核采用虛擬內(nèi)存管理技術(shù),該技術(shù)利用了大多數(shù)程序的一個典型特征,即訪問局部性:要求高效使用 CPU 和 RAM(物理內(nèi)存)資源:
空間局部性 :程序傾向于訪問在最近訪問過的內(nèi)存地址附近的內(nèi)存(指令是順序執(zhí)行的)
時間局部性 :程序傾向于在不久將來再次訪問最近剛訪問的內(nèi)存地址(由于循環(huán))
正是因為局部性特征,使得程序即便在僅有部分地址空間存在于 RAM 中,依然可以獲得執(zhí)行。
虛擬內(nèi)存的規(guī)劃之一就是將每個程序使用的內(nèi)存切割成小型的,固定大小的 "頁" 單元。相應(yīng)的,將 RAM 劃分成一系列與虛擬頁尺寸相同的頁幀:
任意時刻,每個程序僅有部分頁駐留在物理內(nèi)存頁幀中,這些頁構(gòu)成了所謂的駐留集
程序未使用的頁拷貝保存在交換區(qū),交換區(qū)是磁盤空間中的保留區(qū)域,是作為計算機 RAM 補充,僅在需要時才會載入物理內(nèi)存
若進程欲訪問的頁面并未駐留在物理內(nèi)存中,將會發(fā)生頁面錯誤(page fault),內(nèi)核即刻掛起進程的執(zhí)行,同時從磁盤中將該頁面載入內(nèi)存
頁面的大小通常為 4096 個字節(jié),但也有的實現(xiàn)使用更大的頁面。
內(nèi)核中維護了一張頁表,該頁表描述了每頁在進程虛擬地址空間中的位置:頁表中的每個條目要么指出這個虛擬頁面在 RAM 中的具體位置,要么表明其駐留在磁盤上。
進程虛擬地址空間中,并非所有的地址范圍都需要頁表條目:
由于可能存在大段的虛擬地址空間并未投入使用,因此也不需要為其維護頁表條目
進程試圖訪問的地址無頁表條目與之對應(yīng),那么進程將收到 SIGSEGV 信號
由于內(nèi)核能夠為進程分配和釋放頁和頁表條目,所以進程的有效地址范圍在其聲明周期內(nèi)可以發(fā)生變化:
棧向下增長超出了之前未達到的位置
堆中分配或者釋放內(nèi)存時引起 program break 位置的變化
調(diào)用 shmat() 連接 System V 共享內(nèi)存區(qū)或者調(diào)用 shmdt() 脫離共享內(nèi)存區(qū)時
調(diào)用 mmap() 創(chuàng)建內(nèi)存映射時,或者調(diào)用 munmap() 解除內(nèi)存映射時
虛擬內(nèi)存的實現(xiàn)需要硬件中分頁內(nèi)存管理單元(PMMU)的支持,PMMU 把要訪問的每個虛擬地址轉(zhuǎn)換成相應(yīng)的物理內(nèi)存地址,當特定虛擬內(nèi)存地址所對應(yīng)的頁沒有駐留在 RAM 中時,將以頁面錯誤通知內(nèi)核。
虛擬內(nèi)存管理使虛擬地址空間與 RAM 物理地址隔離,帶來的優(yōu)點:
進程與進程,進程與內(nèi)核相互隔離,一個進程不能讀寫另一個進程或內(nèi)核中的數(shù)據(jù),因為每個進程的頁表條目指向 RAM 或交換分區(qū)中不同的物理頁面集合
適當情況下,兩個或者更多的進程能夠共享內(nèi)存,因為內(nèi)核可以使不同進程的頁表條目指向相同的 RAM 頁,經(jīng)常發(fā)生在下面的情形:
多個程序執(zhí)行相同的程序文件或者加載相同的共享庫時,會共享一份代碼副本
可以使用 shmget() 和 mmap() 等系統(tǒng)調(diào)用顯式的請求與其它進程共享內(nèi)存區(qū),這么做是出于通信的目的
便于實現(xiàn)內(nèi)存保護機制,可以對頁表條目進行標記,以表示相關(guān)頁面內(nèi)容是可讀、可寫、可執(zhí)行,多個進程共享 RAM 頁面時,允許每個進程對內(nèi)存采取不同的保護措施,例如一個進程可能以只讀方式訪問某頁面,而另一進程則以讀寫方式訪問同一頁面
程序員和編譯器、鏈接器之類的工具無需關(guān)注在 RAM 中的物理布局
因為需要駐留在內(nèi)存中的僅是程序的一部分,所以程序在加載和運行時都很快,而且,一個進程所占用的虛擬內(nèi)存大小能夠超出 RAM 容量
每個進程使用的 RAM 減少了,RAM 中同時可以容納的進程數(shù)量就增多了,從而任意時刻,CPU 都可執(zhí)行至少一個進程,CPU 的利用率也會提高
5.棧和棧幀
棧駐留在內(nèi)存的高端,并向下增長,專用寄存器——棧指針(stack pointer),用于跟蹤當前棧頂。
每次函數(shù)調(diào)用時,會在棧上新分配一幀,每當函數(shù)返回,再從棧上將此幀移去。
通常將這里的棧稱為用戶棧,以便與內(nèi)核棧加以區(qū)分,內(nèi)核棧是每個進程駐留在內(nèi)核內(nèi)存中的內(nèi)存區(qū)域,在執(zhí)行系統(tǒng)調(diào)用的過程中供內(nèi)核內(nèi)部函數(shù)調(diào)用使用。
每個用戶棧包括的信息:
函數(shù)實參和局部變量
函數(shù)調(diào)用鏈接信息
6.命令行參數(shù)
argc 表示命令行參數(shù)的個數(shù)
argv 是指向命令行參數(shù)的指針數(shù)組,每一個參數(shù)都是以空字符 null 結(jié)尾的字符串,第一個參數(shù),即 argv[0] 通常是程序名,argv[argc] 是 NULL
要想從程序內(nèi)的任一位置訪問部分或者全部內(nèi)容,還有兩種方法:
/proc/PID/cmdline 文件可以讀取任一進程的命令行參數(shù),每個參數(shù)都以 null 字節(jié)終止,程序可以通過 /proc/self/cmdline 文件訪問自己的命令行參數(shù)
GNC C 語言庫提供兩個全局變量,可以在程序中內(nèi)獲取調(diào)用該程序時的程序名稱,即命令行的第一個參數(shù),定義 _GNU_SOURCE 后,從
program_invocation_name :提供了用于調(diào)用該程序的完整路徑名
program_invocation_short_name :提供了程序基本名稱
argv 駐留于進程棧之上的一個內(nèi)存區(qū)域,此區(qū)域可存儲的字節(jié)數(shù)有上限要求,
7.環(huán)境列表
每個進程都有一個與其相關(guān)的稱之為環(huán)境列表的字符串數(shù)組,或者簡稱為環(huán)境:
環(huán)境是 "名稱-值" 的成對集合
常將列表中的名稱稱為環(huán)境變量
新進程在創(chuàng)建時會繼承其父進程的環(huán)境副本,這種傳遞是單向的,一次性的,子進程創(chuàng)建后,父、子進程均可各自修改自己的環(huán)境變量,且這些變更對對方是不可見的
環(huán)境變量常用于 shell 中,通過在自身環(huán)境變量中存放變量值,shell 可以確保將這些值傳遞給其所創(chuàng)建的進程
可以通過設(shè)置環(huán)境變量來改變一些庫函數(shù)的行為
大多數(shù) shell 使用 export 添加環(huán)境變量:
export SHELL=/bin/bash
printenv 命令可以顯示當前的環(huán)境列表,export 命令不加任何參數(shù)時也可以達到此目的。
通過 /proc/PID/environ 文件可以檢查任一進程的環(huán)境列表。
8.從程序中訪問環(huán)境
C 語言使用全局變量 char** environ 訪問列表。
#includechar *getenv(const char *name);
根據(jù)換了變量的名稱,返回相應(yīng)的字符串指針,如果該環(huán)境變量不存在則返回 NULL
9.修改環(huán)境
#includeint putenv(char *string);
向調(diào)用進程的環(huán)境添加一個新的變量或者修改一個已經(jīng)存在的變量值
string 是指向 name=value 形式的字符串
調(diào)用函數(shù)后,該字符串就稱為環(huán)境變量的一部分,environ 變量的某一元素的指向與 string 參數(shù)指向相同,而非 string 參數(shù)指向字符串的副本,因此:
后續(xù)修改 string 將會影響進程的環(huán)境
string 參數(shù)不能是??臻g變量
調(diào)用失敗返回非 0 值,而不是 -1
glibc 中將 putenv 做了非標準擴展,如果 string 中不包含 =,環(huán)境列表將會移除以 string 命名的環(huán)境變量
#includeint setenv(const char *name, const char *value, int overwrite);
會為 name=value 的字符串分配內(nèi)存緩沖區(qū),并將 name 和 value 所指的字符串復制到緩沖區(qū),創(chuàng)建一個新的環(huán)境變量
name 結(jié)尾處 和value開始處不要加 =,因為 setenv() 會自動添加
name 的環(huán)境變量在環(huán)境中已經(jīng)存在,且參數(shù) overwrite 的值是0,則 setenv() 不改變環(huán)境,如果 overwrite 的值非0,則 setenv() 將總是改變環(huán)境
之后修改 name 和 value 以及使用棧上變量都不會有影響
#includeint unsetenv(const char *name);
將 name 指定的環(huán)境變量從環(huán)境列表中移除
#includeint clearenv(void);
清除環(huán)境變量,即設(shè)置 environ=NULL
需要預定義 _SVID_SOURCE 或者_BSD_SOURCE
使用 setenv 和 clearenv() 可能導致內(nèi)存泄漏:
調(diào)用 setenv 分配一塊內(nèi)存緩沖區(qū),隨即稱為進程環(huán)境的一部分,調(diào)用 clearenv 并不會釋放上述的緩沖區(qū)
但是一般這不會稱為一個問題,因為程序一般在開始處調(diào)用 clearenv() 以清除繼承自父進程的環(huán)境
10.執(zhí)行非局部跳轉(zhuǎn)
#includeint setjmp(jmp_buf env); void longjmp(jmp_buf env, int val);
setjmp() 和 longjmp() 可執(zhí)行非局部跳轉(zhuǎn),"非局部" 指的是跳轉(zhuǎn)目標為當前執(zhí)行函數(shù)之外的某個位置
setjmp() 調(diào)用為后續(xù) longjmp() 調(diào)用執(zhí)行的跳轉(zhuǎn)確立了跳轉(zhuǎn)目標,該目標就是程序發(fā)起 setjmp() 調(diào)用的位置
通過查看 setjmp() 返回的整數(shù)值,可以區(qū)分 setjmp() 是初始返回還是第二次返回:
初始調(diào)用返回 0
后續(xù) "偽返回" 的返回值為 longjmp() 調(diào)用中 val 參數(shù)指定的任意值,如果 val 指定為 0,則 longjmp() 實際返回時用 1 替換
setjmp() 將環(huán)境變量 env 參數(shù)中,調(diào)用 longjmp() 時必須指定相同的 env 變量,因為跳轉(zhuǎn)發(fā)生在不同的函數(shù),所以:
env 定義為全局變量
env 作為函數(shù)的參數(shù)傳遞,此種方法比較少見
調(diào)用 setjmp() 時,env 除了保存當前進程的其他信息外,還保存了程序計數(shù)器(指向當前正在執(zhí)行的機器語言指令) 和棧指針寄存器(標記棧頂)的副本,這些信息保證后續(xù) longjmp() 調(diào)用完成后執(zhí)行兩個關(guān)鍵的操作:
將發(fā)起 longjmp() 調(diào)用的函數(shù)與之前調(diào)用 setjmp() 的函數(shù)之間的函數(shù)棧從棧上剝離,稱為棧解開,這是通過將棧指針寄存器重置為 env 參數(shù)的保存值
重置程序計數(shù)寄存器,使得程序得以從初始的 setjmp() 調(diào)用位置繼續(xù)執(zhí)行,這是通過 env 參數(shù)中的程序計數(shù)寄存器實現(xiàn)的
編譯器優(yōu)化會重組程序的指令執(zhí)行順序,并在 CPU 寄存器中而非 RAM 中存儲某些變量,這些優(yōu)化不會將 setjmp() 和 longjmp() 考慮在內(nèi),因而當有優(yōu)化時,可能出錯。
程序中應(yīng)該盡可能避免使用非局部跳轉(zhuǎn)。
-
Linux
+關(guān)注
關(guān)注
87文章
11508瀏覽量
213631 -
計數(shù)器
+關(guān)注
關(guān)注
32文章
2291瀏覽量
96361 -
PID控制
+關(guān)注
關(guān)注
10文章
461瀏覽量
41221 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
317瀏覽量
22375 -
機器語言
+關(guān)注
關(guān)注
0文章
36瀏覽量
10903
原文標題:Linux應(yīng)用開發(fā)之進程和程序
文章出處:【微信號:嵌入式應(yīng)用研究院,微信公眾號:嵌入式應(yīng)用研究院】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Linux下進程間如何實現(xiàn)共享內(nèi)存通信
【Linux學習雜談】之進程的誕生和消失
嵌入式Linux應(yīng)用程序開發(fā)詳解
進程間通信之Linux下進程間通信概述
進程控制開發(fā)之編寫多進程程序實驗解析

linux的進程怎么查看

你們知道Linux的進程是怎樣創(chuàng)建的嗎

如何在Linux使用ps/pstree/top命令查看進程
深入解析Linux程序與進程

評論