前言
這個專欄其實是博主在復習操作系統和計算機網絡時候的筆記,所以如果是博主比較熟悉的知識點,博主可能就直接跳過了,但是所有重要的知識點,在這個專欄里面都會提到!而且我也一定會保證這個專欄知識點的完整性,大家可以放心訂閱~
進程相關概念
文章目錄
- 前言
- 進程相關概念
- 1. 基本理念
- 2. 進程的描述
- 2.1 為什么需要pcb
- 2.2 什么是pcb
- 3. 查看進程
- 4. 在程序中pid
- 5. 父進程是什么
- 6. 創建子進程
- 創建進程的時候,OS要干嘛?
- cpu的運行隊列 run_queue
- 7. 進程狀態
- 8. 狀態優先級
- 9. 環境變量
- 10. 地址空間
- 10.1 什么是地址空間
- 10.2 地址空間是如何設計的
- 10.3 擴展內容(比較難理解)
- 10.4 為什么要有地址空間的三個理由
- 10.4.1 理由一
- 10.4.2 理由二
- 10.4.3 理由三
- 10.5 重新理解掛起
1. 基本理念
重要理論:先組織再描述:struct結構體
在操作系統內部,一定存在大量的數據結構和算法
2. 進程的描述
2.1 為什么需要pcb
為了描述每一個進程,Linux內核會給每一個進程創建一個結構體:PCB
PCB結構體包含了該進程的屬性!
struct PCB
{// 屬性數據,進程全部的屬性數據,如pid// ... struct PCB* next;struct PCB* prev;
};
對進程的管理,變成了對進程PCB結構體鏈表的增刪查改!
什么是進程:進程=對應的代碼數據+進程對應的PCB結構體
2.2 什么是pcb
在linux中,叫task_struct
里面會有這些內容。

是一個雙鏈表!
3. 查看進程
表頭也可以帶上
ps axj | head -1 && ps axj | grep myproc
top命令也可以查看進程
Linux系統下存在一個目錄記錄進程的信息。
/proc # 這個目錄下都是進程的信息
在這個目錄下,都是進程的屬性。
我們可以看下里面的信息。
事實上,/proc
目錄是動態的,多一個進程就會多一個目錄,少一個進程就會少一個目錄。
4. 在程序中pid
在程序中如何獲得pid呢?
getpid() // 這是我們人生中第一個系統調用接口
5. 父進程是什么
ps axj 一下,發現父進程是bash
bash是shell命令行外殼程序
很熟悉了這些。
6. 創建子進程
fork
fork()
返回值:
- fork失敗,返回-1
- fork成功:給父進程返回子進程的pid,給子進程返回0
為什么會有兩個返回值呢?不是只能返回一個嗎?后面再說。
我們簡單寫一個代碼。
第二個為什么被執行了兩次?
因為不加判斷,父進程和子進程都會執行。
復習到后面就會知道,創建子進程的時候,這份代碼是被復制了的!
所以第二句打印語句,父子進程共享。
我們加上一個判斷,就能把子進程和父進程分開來!
創建進程的時候,OS要干嘛?
本質,創建一個新的task_struct
,然后這里里面的字段,有一些是復制父進程的,有一些事自己的。
cpu的運行隊列 run_queue
進程調度本質上就是調度程序,在run_queue里面挑選一個task_struct來執行!
但是這個運行隊列也不是按一般順序的,這個是調度程序決定的!
7. 進程狀態
具體可以看博客。
進程狀態|操作系統|什么是pcb|什么是僵尸進程 |什么是孤兒進程 【超詳細的圖文解釋】【Linux OS】_pcb結構體-CSDN博客
后臺運行一個進程
./test &
8. 狀態優先級
狀態優先級 = 老的優先級 + nice值
PRI就是優先級,越小越先執行
NI就是nice值
9. 環境變量
比較熟了,不再贅述。
要改可以用export
但是要記得把之前的帶上
注意,環境變量的組織方式是一個字符指針數組!
可以用程序打印所有環境變量。
第一種獲取方式:
第二種獲取方式:
略。不常用,其實第一種也不常用,第三種才常用。
main
函數的第三個參數,也就是環境變量參數,是從哪里來的?
一般都是父進程中繼承下來的
10. 地址空間
10.1 什么是地址空間
我們在所有的語言里面提到的地址的概念,本質上都是一個虛擬地址,而不是物理地址。
頁表映射,現在只知道大概,不知道細節,所以這一節簡單復習一下。

這個結構是可以用代碼進行驗證的。
// 驗證地址空間的棧結構
int g_unval; // 未初始化的全局變量
int g_val = 100; // 已經初始化的全局變量
int main(int argc, char *argv[], char *env[])
{printf("code addr: %p\n", main); // 代碼塊位置printf("init global addr: %p\n", &g_val); // 初始化全局變量printf("uninit global addr: %p\n", &g_unval); // 未初始化全局變量char *heap_memory = (char *)malloc(10);printf("heap addr: %p\n", heap_memory); // 堆上的空間printf("stack addr: %p\n", &heap_memory); // 棧上的空間for (int i = 0; i < argc; i++){printf("argv[%d]: %p\n", i, argv[i]);}for (int i = 0; env[i]; i++){printf("env[%d]: %p\n", i, env[i]);}return 0;
}
10.2 地址空間是如何設計的
其實就是給各個進程畫餅。
先描述后組織!
如果我們可以直接訪問物理內存的話,其實是特別不安全的。
所以我們構建了映射機制。
如何理解地址的劃分? — 其實就是一個簡單的struct
結構體就行了。
struct myroom
{int __start;int __end;
};
其實地址空間的各個區域,也是通過這個方式進行劃分的。
struct addr_room {int code_start; int code_end;int init_start; int init_end;int uninit_start; int uninit_end;int heap_start; int heap_end; //...其他屬性
}
現在我們又可以知道,task_struct
里面的又一個字段了! — mm_struct* mm
。
地址空間和頁表(用戶級)是每一個進程都私有一份的,只要保證,每一個進程的頁表,映射的是物理內存的不同區域,我們就能做到,進程之間不會互相干擾進程的獨立性。
回答一個遺留問題:return兩個不同的值是怎么回事?
Return會被執行兩次
Return的本質不就是對值進行寫入嗎? – 此時發生了寫時拷貝!
所以兩個進程各自其實在物理內存中,有屬于自己的變量空間!只不過是在用戶層面用同一個變量(虛擬地址!)來標識了!
10.3 擴展內容(比較難理解)
10.4 為什么要有地址空間的三個理由
10.4.1 理由一
可以有效保護物理內存,禁止非法映射
10.4.2 理由二
因為有地址空間的存在,因為有頁表的映射,我們的物理內存中,是不是可以對未來的數據進行任意位置的加載?
當然可以!
首先,物理內存的分配,可以和進程管理完全解耦!
所以我們new
,malloc
的時候都是申請虛擬地址空間。
緊接著一個問題:如果我們申請了物理空間,但是又不馬上使用,是不是造成了空間的浪費呢?當然是的!
所以事實上,OS是非常聰明的,你雖然malloc了100個字節,但是我可以一個都不給你!
而你去訪問或者使用這100個字節的事哦呼,下面的物理地址空間的相關管理算法才把這個100字節分給你,再讓你訪問!但是你上層是0感知的!
這個叫做延遲分配的策略!
那么,OS是如何知道,一些內存空間雖然在虛擬上給了,但是物理上還 沒給呢?這里有個技術叫做 —— 缺頁中斷!(后面我們再完善這個概念)
10.4.3 理由三
因為物理內存中理論上可以任意位置加載,那么是不是物理內存中的幾乎所有的數據和代碼在內存是亂序的?
但是,因為頁表的存在,它可以進行映射!
那么是不是在進程視角所有的內存分布, 都可以是有序的?
是的! 地址空間+頁表的存在可以將內存的分布有序化!
比如說:
我一個進程看到的,是一個連續的0-ffff的地址,但是事實上,在物理上,這個可能是分散的,分塊的哦!
但是我進程需要知道這些嗎?根本不需要care,我只知道,我用的是0-ffff的連續的地址就行了,底層是怎么樣的,我根本不需要知道!