高级操作系统——XV6进程管理
源代碼閱讀:
type.h:用于聲明一些數據類型的簡化名稱,和聲明頁表指針的數據類型。
param.h:主要用于聲明基本的一些常量,包括內核棧大小等。
memlayout.h:主要用于聲明一些和內存與地址相關的常量與方法,包括虛擬內存轉物理內存的方法以及物理內存轉虛擬內存的方法等。
defs.h:聲明在之后文件中要用到的函數。
x86.h:讓c代碼使用特殊的x86匯編的一些函數包括outb等,并聲明trapframe。
asm.h:一些匯編上的宏定義
mmu.h:定義x86內存管理單元,包括控制寄存器CR上的不同位代表的含義。
elf.hELF:可執行文件的格式,包括ELF頭的數據結構等。
vm.c:一些cpu虛擬內存管理的函數
proc.h:聲明了CPU在內核中的數據結構表示struct cpu,進程在內核中的數據結構struct proc,上下文在內核中的數據結構struct context。
swtch.S聲明用于上下文切換的swtch函數,匯編語言生成
kalloc.c聲明一些物理內存分配的函數,包括分配的kalloc()和用于free的kfree()。
Proc.c:
Ptable:進程索引表
Nextpid:進程的ID
Allocproc:分配進程的內核棧和一些寄存器,將進程狀態UNUSED改寫為EMBRYO
Userinit:第一個進程的初始化操作
Growproc:增加或者減少所占用的內存空間
Fork():創建進程
Exit():退出當前進程,并將其父進程喚醒
Wait();等待子進程退出的
scheduler():內核運行時用于進程調度的循環
sched():切換到內核的
yield():放棄cpu占用
sleep();換成SLEEPING狀態,并通過chan標志進程
wakeup();喚醒chan上所有的進程
kill():殺死進程
procdump;向控制臺上打印當前進程狀態的
(一) 什么是進程,什么是線程?操作系統的資源分配單位和調度單位分別是什么?XV6 中的
進程和線程分別是什么,都實現了嗎?
答案:
(1)什么是進程:
1:操作系統由單道處理轉向多道處理,傳統的靜態程序已經不能描述執行的并發性,所以由動態的進程對多道并發進行描述。
2:進程是程序,數據,程序控制塊的集合
(2)什么是線程:
(3):進程占用空間多,切換時空消耗大。而為了降低時空消耗,引入了線程
引入線程前:資源分配單位和調度為進程
引入線程后:資源分配的單位是進程,調度的單位是線程
(4)XV6中只實現了進程proc,具體內容在proc.c中
(二) 進程管理的數據結構是什么?在 Windows,Linux,XV6 中分別叫什么名字?其中包含哪些內容?操作系統是如何進行管理進程管理數據結構的?它們是如何初始化的?
(1):進程管理的數據結構是進程控制塊。
(2):Linux:task_struct
Windows:EPROCESS、KPROCESS、PEB
XV6:proc .h,proc.c
(3):包含 進程描述信息,進程控制信息,所擁有的資源和使用情況,CPU現場信息
1:Linux下為/include/linux/sched.h內部的struct task_struct,其中包括管理進程所需的各種信息。創建一個新進程時,系統在內存中申請一個空的task_struct ,并填入所需信息。同時將指向該結構的指針填入到task[]數組中。
2:Windows下擁有兩個相關的對象,EPROCESS(執行體進程對象) KPROCESS(內核進程對象 )其中KPROCESS又稱為PCB
EPEOCESS位于內核層之上它側重于提供各種管理策略,同時為上層應用程序提供基本的功能接口。所以,在執行體層的進程和線程數據結構中,有些成員直接對應于上層應用程序中所看到的功能實體。
KPROCESS結構體位于內核層,存儲著進程的必要信息
3:XV6下在proc .h內聲明,包括進程 ID ,進程狀態 ,父進程,context,cpu記錄了內存地址和棧指針等
xv6的PCB就是proc.h中的proc結構體。其內容包括:
uint sz:進程的內存空間大小。
pde_t* pgdir:進程的頁表。
char *kstack:進程的內核棧指針
enum procstate state:進程的狀態(包括六種UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE)
int pid:進程的pid
struct proc *parent:進程的父進程指針。
struct trapframe *tf:位于x86.h,是中斷進程后,需要恢復進程繼續執行所保存的寄存器內容。
struct context *context:切換進程所需要保存的進程狀態。切換進程需要維護的寄存器內容,定義在proc.h中。
void *chan:不為0時,保存該進程睡眠時的隊列。
int killed:不為0時,則該進程被殺死。
struct file *ofile[NOFILE]:打開文件的組。
struct inode *cwd:進程當前的目錄。
char name[16]:進程名。
(4):Xv6通過數據結構ptable對進程進行管理,代碼如下:
struct {
struct spinlock lock;
struct proc proc[NPROC];
} ptable;
(5):Xv6的初始化操作:
1:第一個進程初始化:自舉程序bootmain.c,調用主方法main.c,調用proc.c中的userinit()建立
2:第一個用戶進程(分配了一個物理頁,并將這個物理地址頁映射到虛擬地址為0處,將原來的加載到物理地址為0處的initcode.S的代碼加載到這個虛擬地址對應的物理頁中。另外還設置進程的trapframe,以便能從內核態返回到用戶態)
3:之后的進程初始化:調用fork()
4:無論是userinit()還是fork()都會調用allocproc()函數
5:allocproc的工作是在頁表中分配一個槽(即結構體 struct proc),并初始化進程的狀態,為其內核線程的運行做準備。簡而言之,即設置好一個特別準備的內核棧和一系列內核寄存器,并將進程狀態由UNUSED改寫為EMBRYO
(三) 進程有哪些狀態?請畫出 XV6 的進程狀態轉化圖。在 Linux,XV6 中,進程的狀態分別 包括哪些?你認為操作系統的設計者為什么會有這樣的設計思路?
(1):進程狀態分別有:UNUSED,EMBRYO,RUNNABLE,RUNNING,SLEEPING,ZOMBIE
1:UNUSED:進程的初始化狀態(enum枚舉特性決定)
2:fork()或者userinit()中調用allocproc()即分配內核棧后改為EMBRYO
3:fork()或者userinit()進行一系列初始化操作后改為RUNNABLE
4:scheduler()會一直執行選擇進程改為RUNNING,進程調用yield()改為RUNNABLE, 調用sleep()改為SLEEPING,而wakeup()由SLEEPING改為RUNNABLE
5:程序調用exit()和wait()會將其變為ZOMBIE
(2):Linux進程狀態:創建,就緒,執行,阻塞,就緒掛起,阻塞掛起,結束
(3):提高CPU的利用率,增加并發能力
(四) 如何啟動多進程(創建子進程)?如何調度多進程?調度算法有哪些?操作系統為何要 限制一個 CPU 最大支持的進程數?XV6 中的最大進程數是多少?如何執行進程的切換?什么是進程上下文?多進程和多 CPU 有什么關系?
(1):利用操作系統創建或者父進程創建,在xv6中,第一個進程是由Userinit()創建初始化,之后通過fork()創建初始化
(2):利用調度算法進行調度
(3):先來先服務,短作業先服務,時間片輪轉發,優先級調度法,多級反饋輪轉法,高響應比優先法
(4):一是考慮內存空間,進程數量過多占用大量內存 二是增加了缺頁中斷的可能性,會導致cpu不斷的執行頁面換入換出,浪費大量時間
(5):XV6的最大進程數見param.h文件中的#define NPROC 64,最大64;
#define NPROC 64 // maximum number of processes
(6):切換上下文:
1:當前進程通過調用yield()(可能是時間片用完),進行進程切換。yield函數調用sched函數,sched函數啟動switch函數完成進程切換,而switch又會觸發scheduler(void),scheduler(void)中會選擇一個進程切換上下文,仍調用swtch(&cpu->scheduler, proc->context);
2:整個流程是這樣的:yield->sched->switch->scheduler->switch
3:其中switch的函數原型void swtch(struct context **old, struct context *new);
4:它的工作主要包括:1. 保存當前(old)進程的上下文。 2. 加載新進程(new)的上下文到機器寄存器中。可以理解為是匯編實現
(7):進程運行時,其硬件狀態保存在CPU 上的寄存器中,寄存器:程序計數器、程序狀態寄存器、棧指針、通用寄存器、其他控制寄存器的值。進程不運行時,這些寄存器的值保存在PCB 中。將CPU 硬件狀態從一個進程換到另一個進程的過程稱為
上下文切換
(8):沒有必然聯系。一個CPU在某一時刻只能運行一個進程,宏觀上并行,微觀上串行。而多個CPU可以同時運行多個程序
(五) 內核態進程是什么?用戶態進程是什么?它們有什么區別?
(1):系統分為內核態和用戶態。在內核里產生的進程稱為內核級進程,在用戶態上產生的進程稱為用戶態進程。
(2)區別:
1)運行在不同的系統狀態,用戶態進程執行在用戶態,內核態進程執行在內核態
2)進入方式不同,用戶態進程可直接進入,內核態必須通過運行系統調用命令
3)返回方式不同,用戶態進程直接返回,內核態進程有重新調度過程
4)內核態進程優先級要高于用戶態進程,并且內核態進程特權級別最高,它可以執行系統級別的代碼
聯系:
用戶級進程和內核級進程存在一對一,多對一,多對多的關系
(六) 首先看一個簡略的進程在內存中的布局
再看一個詳細的部分
在內存中分為五部分:
(1)代碼區(Code segment):存放程序的二進制代碼文件
(2)常量區(Constant segment):所有常量均存放在常量區。程序結束后由OS釋放通常是字符串常量
(3)全局數據區(Global data segment):全局變量和靜態數據,函數內部的靜態局部變量。程序結束有OS釋放
(4)堆區(Heap segment):new,malloc產生的動態數據。由程序員分配和釋放 一般一個new就要對應一個delete
有時候堆區又分為 自由存儲區,就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。
(5)棧區(Stack segment):局部變量,函數參數。由編譯器自動分配和釋放
一個例子,大概介紹各個常量和變量的對應區關系
//main.cpp
int a = 0; 全局初始化區
char *p1; 全局未初始化區
main()
{
int b; 棧
char s[] = “123”; 棧
char *p2 = “abcdefg”;abcdefg\0在常量區,p3在棧上。
char *p3; 棧
static int c =0; 全局(靜態)初始化區
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得來得10和20字節的區域就在堆區。
strcpy(p1, “123456”); 123456\0放在常量區,編譯器可能會將它與2所指向的"abcdefg"
優化成一個地方。
}
棧和堆的區別
(1)申請后系統的響應
棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。
堆:堆中空閑內存地址是以鏈表方式進行存儲的。當需要申請分配時,遍歷鏈表尋找第一個大于所申請空間的堆結點,將此節點的空間分配給程序并且記錄本次分配的大小,以便于之后delete程序釋放內存空間。
若找到的堆節點大于申請的大小,系統會將多余的部分重新放入空閑鏈表
(2)申請大小的限度
棧:在Windows下,棧是向低地址擴展的數據結構, 是一塊連續的內存的區域。棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴展的數據結構,用鏈表來存儲,是不連續的內存區域。而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較
(3)申請效率的比較:
棧:系統自動分配,速度較快。但程序員是無法控制的
堆:由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.
(4)堆和棧中的存儲內容
棧: 在函數調用時,第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的地址,然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然后是函數中的局部變量。注意靜態變量是不入棧的,會進入全局數據區當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行
堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。
參考資源:
https://www.jianshu.com/p/e01680c00de4
https://blog.csdn.net/zhanglei8893/article/details/6101076
https://blog.csdn.net/qq_36347375/article/details/91354349
https://www.cnblogs.com/LittleHann/p/3454748.html
https://www.cnblogs.com/itzxy/p/9293677.html
總結
以上是生活随笔為你收集整理的高级操作系统——XV6进程管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vue 的路由实现 Hash模式 和
- 下一篇: 操作系统的概论梳理