目錄
1、馮諾依曼體系結構
2、操作系統(Operating System)
2.1 基本概念
2.2 目的
3、Linux的進程
3.1 基本概念
3.1.1 PCB
3.1.2?struct task_struct
3.1.3 進程的定義
3.2 基本操作
3.2.1 查看進程
3.2.2?初識fork
3.3?進程狀態
3.3.1?操作系統的進程狀態
3.3.2?Linux的進程狀態
3.4?進程優先級
3.4.1 基本概念
3.4.2 PRI&&NI
3.4.3 競爭&&獨立&&并行&&并發
3.5?進程切換
3.6?Linux2.6內核進程O(1)調度隊列
4、Linux的環境變量
4.1 基本概念
4.2?常見的環境變量
4.3?環境變量的相關命令
4.3.1 查看環境變量
4.3.2?修改環境變量
4.3.3 刪除環境變量
4.4 環境變量的特點
5、Linux的進程虛擬地址空間
5.1 程序地址空間
5.2 問題拋出
5.3 進程虛擬地址空間和分頁機制
5.4 虛擬地址空間和分頁機制的作用
5.5 拓展
1、馮諾依曼體系結構
- 輸入設備:鍵盤,鼠標,網卡,磁盤等。
- 輸入設備:顯示器,網卡,磁盤等。
- 存儲器:即內存。
- CPU:簡單來說,是中央處理器(運算器+控制器)。
注意:
- 輸入輸出設備的傳輸效率低,但是,讓輸入輸出的設備的傳輸效率變高,成本太高,所以出現內存,即效率與成本之間的平衡,才普及了電腦。
- 程序的運行需要CPU,而CPU只能訪問內存,所以程序必須加載到內存中。
- 數據流動的本質:多臺馮諾依曼體系結構的交互。
2、操作系統(Operating System)
2.1 基本概念
操作系統包括:
- 內核(進程管理,內存管理,文件管理,驅動管理)
- 其他程序(例如函數庫,shell程序等等)
2.2 目的
操作系統,是一款進行 軟硬件 管理 的軟件。管理:先描述(類),再組織(數據結構)。
sysrem call(系統調用),驅動程序,都是為了屏蔽底層細節,外部實現統一。安全且方便。
-
系統調用封裝內核?→ 對應用程序統一。
-
驅動程序封裝硬件?→ 對操作系統統一。
3、Linux的進程
3.1 基本概念
3.1.1 PCB
PCB(Process Control Block),進程控制塊,一種類型,Linux中的PCB為:struct task_struct。
3.1.2?struct task_struct
內容分類(后續會詳細介紹)
- 標識符(PID):描述本進程的唯一標識符,用于區分其他進程。
- 狀態:任務狀態,包括退出代碼、退出信號等。
- 優先級:相對于其他進程的優先級。
- 程序計數器:程序中即將被執行的下一條指令的地址。
- 內存指針:包括程序代碼和進程相關數據的指針,以及與其他進程共享的內存塊指針。
- 上下文數據:進程執行時處理器的寄存器中的數據(例如:CPU 寄存器狀態,需附圖說明)。
- I/O 狀態信息:包括未完成的 I/O 請求、分配給進程的 I/O 設備,以及進程使用的文件列表。
- 記賬信息:可能包括處理器占用時間、時鐘周期總和、時間限制、計賬號等。
- 其他信息:與進程相關的其他數據。
在 Linux 內核中,所有進程均通過?struct task_struct?結構體描述,并以雙向鏈表的形式(即隊列)組織和管理。
3.1.3 進程的定義
進程 = 內核數據結構對象(PCB)+代碼和數據
對進程的管理,就是對數據結構的增刪改查。
3.2 基本操作
3.2.1 查看進程
1.?通過 /proc?文件系統查看進程信息
-
/proc?是一個虛擬文件系統,提供內核和進程信息的實時訪問。
-
每個進程的信息存儲在?/proc/[PID]/?目錄下,例如:
ls /proc/1/ # 查看 PID=1 的進程信息(通常是 init/systemd)
2. top:動態查看進程狀態(CPU、內存占用等)
top # 默認動態顯示所有進程(按 CPU 占用排序)
top -p PID1,PID2,PID3 # 只監控指定 PID 的進程
top -u username # 只顯示某用戶的進程
交互命令(在 top?運行時使用)
-
k?→ 結束指定 PID 的進程(輸入 PID 后回車)。
-
M?→ 按內存占用排序。
-
P?→ 按 CPU 占用排序(默認)。
-
q?→ 退出?top?。?
3. ps:靜態查看進程列表
ps aux # 適用于查看所有進程的資源占用,進程狀態等
ps -l PID # 適用于查看進程的父子關系,進程優先級等
注意:
- 可以配合grep進行搜索。
- ;和&&可以同時執行多條命令。
- 命令本身也是進程。
4.? 通過系統調用,獲取進程標識符(PID & PPID)
-
getpid():獲取當前進程的 PID。
-
getppid():獲取當前進程的父進程 PPID。
如:
#include <stdio.h>
#include <unistd.h>int main() {printf("PID: %d\n", getpid()); // 當前進程 IDprintf("PPID: %d\n", getppid()); // 父進程 IDreturn 0;
}
3.2.2?初識fork
通過fork(系統調用),創建子進程。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{int ret = fork();printf("hello proc : %d!, ret: %d\n", getpid(), ret);sleep(1);return 0;
}
?
- 兩個返回值,對父進程返回子進程的PID,對子進程返回0。因為父:子 = 1:N,父進程需要區分子進程,而子進程能通過PPID找到父進程。所以可以if,讓父子進程執行不同的語句。
-
fork() 創建子進程后,父子進程從?fork() 返回處繼續執行。注意:子進程不會執行fork()之前的代碼。
-
當父子進程嘗試修改數據,會發生寫時拷貝,重新拷貝一份數據。所以父子進程獨立運行。
3.3?進程狀態
3.3.1?操作系統的進程狀態
以上可以分為三類:
- 運行:PCB對象在調度隊列中,正在運行(運行)或準備運行(創建+就緒)。
- 阻塞:等待某種設備或資源就緒,PCB對象進入設備隊列或資源隊列。
- 掛起:內存不足,將進程的代碼和數據放到磁盤中,進程是運行狀態就是就緒掛起,進程是阻塞狀態就是阻塞掛起。
注意:
- 一個CPU,一個調度隊列
- PCB對象,可以同時在不同的數據結構中,即可以在不同的隊列中。
- 進程的狀態,就是PCB對象在不同隊列之間的流動,本質是數據結構的增刪改查。?
3.3.2?Linux的進程狀態
/** The task state array is a "bitmap" of reasons to sleep.* "Running" is 0, other states can be combined via bit tests.*/
static const char *const task_state_array[] = {"R (running)", /* 0 - 運行中或就緒 */"S (sleeping)", /* 1 - 可中斷睡眠(等待事件)*/"D (disk sleep)", /* 2 - 不可中斷睡眠(通常等待I/O)*/"T (stopped)", /* 4 - 被信號暫停(如SIGSTOP)*/"t (tracing stop)", /* 8 - 被調試器跟蹤暫停 */"X (dead)", /* 16 - 完全終止(不會出現在任務列表)*/"Z (zombie)", /* 32 - 僵尸進程(已終止但未回收)*/
};
- R:運行中或就緒(進程一創建,就進入就緒狀態)。
- S:可中斷休眠(淺睡眠,一種阻塞),能被操作系統殺死。
- D:不可中斷休眠(深睡眠,一種阻塞),不能被操作系統殺死。
- T:暫停,如:Ctrl+z。
- t:暫停,如:debug的斷點。
- X:死亡,進程結束。
- Z:僵尸,子進程退出,父進程需要獲取子進程退出前的信息(即子進程PCB對象里面的信息,其指向的代碼和數據已被釋放),并釋放子進程的PCB對象,如果父進程沒有獲取子進程退出前的信息,那么子進程被稱為"僵尸進程",其PCB對象將會一直存在,造成內存泄漏。如果父進程先結束,其子進程稱為"孤兒進程",會被1號進程"領養",不會成為"僵尸進程"。
注意:
阻塞是進程的?正常狀態(因等待資源主動暫停),而?饑餓是?異常現象(可能是一直阻塞,或進程可能無需等待資源,但因調度問題無法運行等)
3.4?進程優先級
3.4.1 基本概念
進程得到CPU資源的先后順序。
注意:
- 優先級是一種數字,值越低,優先級越高。
- 優先級,能得到某種資源(只是先后問題),權限,能否得到某種資源。
- Linux,基于時間片的分時操作系統,要考慮公平性,所以優先級變化不大。
3.4.2 PRI&&NI
- PRI:進程的優先級,默認80。
- NI:nice值,進程優先級的修正數據,默認0。范圍是[-20,19]。
注意:
- 進程真實的優先級PRI = 80 + NI。所以優先級的范圍是[60,99]。保證公平性。
-
NI 的存在?是為了在?靈活性(用戶態調整)和?穩定性(內核控制)之間取得平衡。
3.4.3 競爭&&獨立&&并行&&并發
- 競爭:系統中進程數量遠多于 CPU 資源(如單核 CPU 只能同時運行 1 個進程),因此進程之間需要競爭 CPU 時間片、內存、I/O 等資源。通過?優先級(Priority)?或?調度算法(如時間片輪轉)來合理分配資源,確保高優先級或關鍵任務能優先執行。
- 獨立:每個進程擁有獨立的地址空間、文件描述符、寄存器狀態等資源,一個進程崩潰不會直接影響其他進程。
- 并行:多個進程在?多個 CPU/核心上真正同時運行(物理層面的同時執行)。
- 并發:多個進程在?單個 CPU 上通過快速切換(時間片輪轉)?模擬“同時運行”的效果(邏輯層面的交替執行)。
3.5?進程切換
CPU上下文切換(Context Switch),實際上是任務切換,或CPU寄存器的切換。
流程:
-
保存現場:
當多任務操作系統決定切換到另一個任務時,首先將當前運行任務的CPU寄存器狀態完整保存到該任務的私有堆棧中。 -
恢復現場:
從待運行任務的堆棧中加載其之前保存的寄存器狀態到CPU。 -
切換執行:
CPU開始執行新任務的指令流。
注意:
- 進程在一個時間片內占用CPU,不會一直占用。
- 進程切換的本質:保存和恢復 進程硬件上下文的數據(即CPU寄存器的狀態)。?
3.6?Linux2.6內核進程O(1)調度隊列
- 對于active隊列,先看nr_active,有沒有進程,再通過bitmap[5],按照優先級,快速定位隊列,最后挑隊首的進程,執行。
- 進程執行完一個時間片,進入expired隊列(防止高優先級進程執行完一個時間片,又插隊)。當active隊列為空時,swap(&active,&expired),交換兩個指針,繼續調度active隊列。
- 新來一個進程,如果放到expired隊列,就是就緒狀態,如果放到active隊列,也是就緒狀態,但是"插隊"了。
- 如果active中的進程,更改NI(nice值),即更改優先級,因為麻煩,所以執行完一個時間片后,進入過期隊列時,再更新優先級。
4、Linux的環境變量
4.1 基本概念
環境變量是操作系統中用于指定運行環境參數的鍵值對(KEY=VALUE)。
KEY是環境變量的名字,VALUE是環境變量的內容。
4.2?常見的環境變量
4.3?環境變量的相關命令
4.3.1 查看環境變量
命令行:
- env:顯示當前進程 所有的環境變量。
- echo $環境變量名字:顯示環境變量的內容。
- set:顯示當前進程 所有的變量。如:直接i=10或i,定義本地變量i。
系統調用:
- int main(int argc,char* argv[ ],char* env[ ]){ return 0;},argv是命令行輸入的命令字符串數組(以空格為分隔符,將命令分成若干個字符串,數組以NULL結尾),argc是argv數組元素的個數,env是該進程 環境變量的字符串數組(環境變量放在字符串里,數組以NULL結尾)。
- getenv(),在當前進程,根據環境變量的名字,獲取環境變量的內容。
- 全局變量environ(環境變量字符串數組,數組以NULL結尾),必須先extern char** environ;聲明,再使用。
4.3.2?修改環境變量
- 環境變量名=$環境變量名:內容,給環境變量加內容。如:PATH=$PATH:/home/Lzc/test。
- export 變量名=“值”,新增環境變量。
注意:
以上關閉終端,重新登錄,就會失效。想要永久生效,就要更改配置文件(~/.bashrc或~/.bash_profile),因為bash每次都是拷貝配置文件的內容。
4.3.3 刪除環境變量
- unset 變量名:清除變量,本地變量和環境變量都可以。
4.4 環境變量的特點
- 新創建的子進程會繼承父進程的環境變量(全局性)。進程相互獨立,所以環境變量也獨立,互不影響。
- 本地變量不會被新創建的子進程繼承。
5、Linux的進程虛擬地址空間
5.1 程序地址空間
以32位機器為例:
5.2 問題拋出
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_val = 0; // 全局變量,初始化為0int main() {pid_t id = fork(); // 創建子進程if (id < 0) { // fork失敗perror("fork");return 0;}else if (id == 0) { // 子進程分支g_val = 100; // 子進程修改全局變量printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);} else { // 父進程分支sleep(3); // 父進程休眠3秒,確保子進程先執行printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1); // 防止父進程或子進程提前終止return 0;
}
為什么地址一樣,內容卻不一樣? ?
說明:
- 該地址絕對不是物理地址!
- 在Linux系統下,這種地址稱為**虛擬地址**。 ?
- 我們用C/C++語言看到的地址都是虛擬地址,物理地址對用戶完全不可見,由操作系統統一管理。 ?
5.3 進程虛擬地址空間和分頁機制
所以,程序地址空間,準確來說是,進程虛擬地址空間。
首先,一個進程,一個虛擬地址空間。?
struct task_struct {/*...*/struct mm_struct *mm; // 指向進程用戶空間虛擬地址空間描述符// - 對普通用戶進程:指向其虛擬地址空間的用戶空間部分// - 對內核線程:NULL(因內核線程無獨立用戶空間)struct mm_struct *active_mm; // 內核線程使用的替代mm字段// - 內核線程的mm為NULL時,可借用其他進程的地址空間// - 所有進程的內核空間映射相同,故內核線程可復用/*...*/
};// 1.當虛擬區較少時采取單鏈表,由mmap指針指向這個鏈表;
// 2.當虛擬區間多時采取紅?樹進?管理,由mm_rb指向這棵樹。
struct mm_struct {/*...*/struct vm_area_struct *mmap; // 虛擬內存區域(VMA)鏈表頭struct rb_root mm_rb; // VMA紅黑樹根節點(加速查找)unsigned long task_size; // 用戶虛擬地址空間大小/* 各段地址邊界 */unsigned long start_code, end_code; // 代碼段起止unsigned long start_data, end_data; // 數據段起止unsigned long start_brk, brk; // 堆段起止unsigned long start_stack; // 棧起始地址unsigned long arg_start, arg_end; // 命令行參數段unsigned long env_start, env_end; // 環境變量段/*...*/
};struct vm_area_struct {unsigned long vm_start; // 虛擬內存區域起始地址unsigned long vm_end; // 虛擬內存區域結束地址/* 鏈表與樹結構 */struct vm_area_struct *vm_next, *vm_prev; // 雙向鏈表指針struct rb_node vm_rb; // 紅黑樹節點unsigned long rb_subtree_gap;/* 關聯的地址空間 */struct mm_struct *vm_mm; // 所屬的mm_struct/* 權限與標志 */pgprot_t vm_page_prot; // 訪問權限(讀/寫/執行)unsigned long vm_flags; // 區域標志(如VM_READ|VM_WRITE)/* 共享與反向映射 */struct {struct rb_node rb;unsigned long rb_subtree_last;} shared;struct list_head anon_vma_chain;struct anon_vma *anon_vma;/* 操作方法與文件映射 */const struct vm_operations_struct *vm_ops; // 區域操作函數集unsigned long vm_pgoff; // 文件映射偏移量(以頁為單位)struct file *vm_file; // 映射的文件指針(若為文件映射)void *vm_private_data; // 驅動私有數據/* 其他配置 */atomic_long_t swap_readahead_info;
#ifdef CONFIG_NUMAstruct mempolicy *vm_policy; // NUMA內存策略
#endifstruct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;
如圖所示:
?一個進程,一個頁表,進行虛擬地址和物理地址的映射。
將物理地址轉化為虛擬地址,提供給用戶使用。?
5.4 虛擬地址空間和分頁機制的作用
- 將地址,"無序"變"有序"。
- 地址轉化的過程中,可以對操作進行合法判定,進而保護物理內存(根據權限)。
- 讓進程管理和內存管理在一定程度上解耦合。
5.5 拓展
- 可以不加載代碼和數據到物理內存,只有struct task_struct,struct mm_struct,頁表,需要訪問時,“缺頁中斷”,再加載。所以創建進程,先有struct task_struct,struct mm_struct等,再有代碼和數據。
- 當物理內存不足時,對于阻塞的進程,通過頁表換出物理地址(釋放內存),變為阻塞掛起,騰出內存空間。