XV6操作系统代码阅读心得(二):进程
1. 進(jìn)程的基本概念
從抽象的意義來(lái)說(shuō),進(jìn)程是指一個(gè)正在運(yùn)行的程序的實(shí)例,而線程是一個(gè)CPU指令執(zhí)行流的最小單位。進(jìn)程是操作系統(tǒng)資源分配的最小單位,線程是操作系統(tǒng)中調(diào)度的最小單位。從實(shí)現(xiàn)的角度上講,XV6系統(tǒng)中只實(shí)現(xiàn)了進(jìn)程, 并沒(méi)有提供對(duì)線程的額外支持,一個(gè)用戶進(jìn)程永遠(yuǎn)只會(huì)有一個(gè)用戶可見(jiàn)的執(zhí)行流。
2. 進(jìn)程管理的數(shù)據(jù)結(jié)構(gòu)
根據(jù)[1],進(jìn)程管理的數(shù)據(jù)結(jié)構(gòu)被叫做進(jìn)程控制塊(Process Control Block, PCB)。一個(gè)進(jìn)程的PCB必須存儲(chǔ)以下兩類信息:
XV6中進(jìn)程相關(guān)的數(shù)據(jù)結(jié)構(gòu)
在XV6中,與進(jìn)程有關(guān)的數(shù)據(jù)結(jié)構(gòu)如下
// Per-process state struct proc {uint sz; // Size of process memory (bytes)pde_t* pgdir; // Page tablechar *kstack; // Bottom of kernel stack for this processenum procstate state; // Process stateint pid; // Process IDstruct proc *parent; // Parent processstruct trapframe *tf; // Trap frame for current syscallstruct context *context; // swtch() here to run processvoid *chan; // If non-zero, sleeping on chanint killed; // If non-zero, have been killedstruct file *ofile[NOFILE]; // Open filesstruct inode *cwd; // Current directorychar name[16]; // Process name (debugging) };與前述的兩類信息的對(duì)應(yīng)關(guān)系如下
操作系統(tǒng)管理進(jìn)程有關(guān)的信息:內(nèi)核棧kstack,進(jìn)程的狀態(tài)state,進(jìn)程的pid,進(jìn)程的父進(jìn)程parent,進(jìn)程的中斷幀tf,進(jìn)程的上下文context,與sleep和kill有關(guān)的chan和killed變量。
進(jìn)程本身運(yùn)行所需要的全部環(huán)境:虛擬內(nèi)存信息sz和pgdir,打開(kāi)的文件ofile和當(dāng)前目錄cwd。
額外地,proc中還有一條用于調(diào)試的進(jìn)程名字name。
在操作系統(tǒng)中,所有的進(jìn)程信息struct proc都存儲(chǔ)在ptable中,ptable的定義如下
struct {struct spinlock lock;struct proc proc[NPROC]; } ptable;除了互斥鎖lock之外,一個(gè)值得注意的一點(diǎn)是XV6系統(tǒng)中允許同時(shí)存在的進(jìn)程數(shù)量是有上限的。在這里NPROC為64,所以XV6最多只允許同時(shí)存在64個(gè)進(jìn)程。
在proc.c中,userinit()用于創(chuàng)建第一個(gè)用戶進(jìn)程,allocproc()則被用于在ptable中尋找空位并在空位上創(chuàng)建一個(gè)新的進(jìn)程。當(dāng)操作系統(tǒng)初始化時(shí),通過(guò)userinit()調(diào)用allocproc創(chuàng)建第一個(gè)進(jìn)程init。絕大多數(shù)進(jìn)程相關(guān)信息都會(huì)在這里初始化。由于XV6系統(tǒng)只允許中斷返回一種從內(nèi)核態(tài)進(jìn)入用戶態(tài)的方式,因此allocproc()會(huì)創(chuàng)建中斷調(diào)用的棧結(jié)構(gòu),而userinit會(huì)設(shè)置其中的值,仿佛是從一次真正的中斷里返回進(jìn)程一樣。最后,在mpmain()中,系統(tǒng)調(diào)用schedule()函數(shù),開(kāi)始用戶進(jìn)程的調(diào)度。在init進(jìn)程被調(diào)度啟動(dòng)后,會(huì)創(chuàng)建shell進(jìn)程,用于和用戶交互。
Linux中進(jìn)程相關(guān)的數(shù)據(jù)結(jié)構(gòu)
Linux系統(tǒng)的實(shí)現(xiàn)中并不刻意區(qū)分進(jìn)程和線程,而是將其一概存儲(chǔ)在被稱作task_struct的數(shù)據(jù)結(jié)構(gòu)中。當(dāng)兩個(gè)task_struct共享同一個(gè)虛擬地址空間時(shí),它們就是同一個(gè)進(jìn)程的兩個(gè)線程。與Linux進(jìn)程有關(guān)的數(shù)據(jù)結(jié)構(gòu)定義大多數(shù)都在/include/linux/sched.h中。task_struct數(shù)據(jù)結(jié)構(gòu)相當(dāng)復(fù)雜,在32位機(jī)器上一條能占據(jù)1.7KiB的空間。task_struct中主要包含的數(shù)據(jù)結(jié)構(gòu)有管理處理器底層信息的thread_struct、管理虛擬內(nèi)存的mm_struct、管理文件描述符的file_struct、管理信號(hào)的signal_struct等等。Linux中的進(jìn)程與XV6一樣都有獨(dú)立的內(nèi)核棧,內(nèi)核模式下的代碼是在內(nèi)核棧中運(yùn)行的。
操作系統(tǒng)維護(hù)多個(gè)task_struct隊(duì)列來(lái)實(shí)現(xiàn)不同的功能。所有的隊(duì)列都是用雙向鏈表實(shí)現(xiàn)的。有一個(gè)隊(duì)列存放了所有的進(jìn)程;另一個(gè)隊(duì)列存放了所有正在運(yùn)行的進(jìn)程(kernel/sched.c中的struct runqueue );此外,對(duì)于每一個(gè)會(huì)導(dǎo)致進(jìn)程掛起的等待事件,都有一個(gè)隊(duì)列存放因?yàn)榈却耸录鴴炱鸬倪M(jìn)程(include/linux/wait.h中的wait_queue_t)。
Linux會(huì)將task_struct數(shù)據(jù)結(jié)構(gòu)分配到這個(gè)進(jìn)程的內(nèi)核棧的頂部,將thread_info數(shù)據(jù)結(jié)構(gòu)分配到這個(gè)進(jìn)程的內(nèi)核棧的底部。thread_info的名稱有些誤導(dǎo),它存儲(chǔ)的其實(shí)是一個(gè)task中更加底層和更加體系結(jié)構(gòu)相關(guān)的屬性。進(jìn)程數(shù)據(jù)結(jié)構(gòu)的分配方法被稱為Slab Allocator,通過(guò)精心優(yōu)化的虛擬內(nèi)存機(jī)制來(lái)提升進(jìn)程管理的效率、實(shí)現(xiàn)對(duì)象重用。
struct thread_info {struct task_struct *task;struct exec_domain *exec_domain;__u32 flags;__u32 status;__u32 cpu;int preempt_count;mm_segment_t addr_limit;struct restart_block restart_block;void *sysenter_return;int uaccess_err; };Windows中進(jìn)程相關(guān)的數(shù)據(jù)結(jié)構(gòu)
在Windows NT以后的Windows系統(tǒng)中,進(jìn)程用EPROCESS對(duì)象表示,線程用ETHREAD對(duì)象表示。在一個(gè)EPROCESS對(duì)象中,包含了進(jìn)程的資源相關(guān)信息,比如句柄表、虛擬內(nèi)存、安全、調(diào)試、異常、創(chuàng)建信息、I/O轉(zhuǎn)移統(tǒng)計(jì)以及進(jìn)程計(jì)時(shí)等。每個(gè)EPROCESS對(duì)象都包含一個(gè)指向ETHREAD結(jié)構(gòu)體的鏈表。值得一提的是Windows系統(tǒng)中EPROCESS和ETHREAD的設(shè)計(jì)都是分層的,KPROCESS和KTHREAD成員對(duì)象專門(mén)用來(lái)處理體系結(jié)構(gòu)有關(guān)的細(xì)節(jié),而Process Environment Block和Thead Environment Block對(duì)象則暴露給應(yīng)用程序來(lái)訪問(wèn)。
3. 進(jìn)程的狀態(tài)
在大多數(shù)教科書(shū)使用的標(biāo)準(zhǔn)五狀態(tài)進(jìn)程模型中,進(jìn)程分為New、Ready、Running、Waiting和Terminated五個(gè)狀態(tài),狀態(tài)轉(zhuǎn)換圖如圖所示(圖出自O(shè)perating System Concepts, 7th Edition)
除去標(biāo)記進(jìn)程塊未被使用的UNUSED狀態(tài),XV6操作系統(tǒng)中的狀態(tài)與上述的五狀態(tài)模型完全對(duì)應(yīng)。在XV6中這五個(gè)狀態(tài)的定義為
enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };XV6實(shí)現(xiàn)中源代碼中具體的轉(zhuǎn)換關(guān)系如下
這個(gè)轉(zhuǎn)換關(guān)系圖中,標(biāo)識(shí)出了在XV6系統(tǒng)中發(fā)生狀態(tài)轉(zhuǎn)換所需要的函數(shù)或者事件。
Linux中的進(jìn)程轉(zhuǎn)換圖與Xv6大致相同,但是有如下區(qū)別:
關(guān)于Windows的進(jìn)程狀態(tài),網(wǎng)上并沒(méi)有關(guān)于實(shí)現(xiàn)細(xì)節(jié)的特別詳細(xì)的解釋。一份關(guān)于Windows進(jìn)程的文檔[6]使用了如下的進(jìn)程轉(zhuǎn)換圖,但并沒(méi)有顯式地說(shuō)明其與Windows進(jìn)程實(shí)現(xiàn)之間的關(guān)系。
從中可以看出,Windows進(jìn)程可能額外多出了Suspend狀態(tài),用于以下情況的一種:
我認(rèn)為操作系統(tǒng)設(shè)計(jì)這些狀態(tài),是出于在有限的計(jì)算機(jī)系統(tǒng)資源上,對(duì)管理和調(diào)度多個(gè)進(jìn)程的需求。如果一個(gè)CPU核同一時(shí)間只會(huì)有一個(gè)進(jìn)程運(yùn)行,那就完全不需要設(shè)置進(jìn)程的狀態(tài)。但是一個(gè)實(shí)用的現(xiàn)代操作系統(tǒng)必須支持大量進(jìn)程共享一個(gè)CPU,也必須支持進(jìn)程的不斷創(chuàng)建與終止,從而實(shí)現(xiàn)資源利用效率的最大化和系統(tǒng)功能的多樣化。因此,操作系統(tǒng)選取了現(xiàn)在的五狀態(tài)進(jìn)程設(shè)計(jì),并且出于不同系統(tǒng)的需求不同,也會(huì)有更加細(xì)化的設(shè)計(jì)。
4. 進(jìn)程的調(diào)度算法
Xv6系統(tǒng)中的進(jìn)程可以使用fork()系統(tǒng)調(diào)用創(chuàng)建新進(jìn)程。為了創(chuàng)建一個(gè)進(jìn)程,操作系統(tǒng)必須為這個(gè)進(jìn)程分配相應(yīng)的資源,包括內(nèi)存、CPU時(shí)間、文件等,與此同時(shí),操作系統(tǒng)必須對(duì)此進(jìn)程做出相應(yīng)的管理,包括設(shè)置它的進(jìn)程ID、調(diào)度優(yōu)先級(jí)、虛擬內(nèi)存結(jié)構(gòu)、運(yùn)行資源限制等等。為了能夠維持多個(gè)進(jìn)程在一個(gè)CPU上運(yùn)行,必須對(duì)此做出相應(yīng)的調(diào)度。調(diào)度算法有很多種,由簡(jiǎn)到難如下
一個(gè)現(xiàn)代操作系統(tǒng)所使用的調(diào)度算法通常是Priority Based Multilevel Queue的一種變體。具體地說(shuō),根據(jù)操作系統(tǒng)的具體需求,將不同類別的進(jìn)程賦予不同的優(yōu)先級(jí)。比如,Windows系統(tǒng)中,用戶當(dāng)前使用的窗體進(jìn)程具有非常高的優(yōu)先級(jí)。對(duì)于每一個(gè)優(yōu)先級(jí)內(nèi)的進(jìn)程都會(huì)維護(hù)一個(gè)獨(dú)自的隊(duì)列,每個(gè)優(yōu)先級(jí)可以使用不同的調(diào)度算法。高優(yōu)先級(jí)的前臺(tái)進(jìn)程可以使用Round-Robin,后臺(tái)進(jìn)程可以使用First Come First Served。如果一個(gè)進(jìn)程很久沒(méi)有得到執(zhí)行,那么可以提升它的優(yōu)先級(jí),從低優(yōu)先級(jí)隊(duì)列進(jìn)入高優(yōu)先級(jí)隊(duì)列,從而避免饑餓的問(wèn)題。
一般而言,出于CPU資源的限制和操作系統(tǒng)內(nèi)核空間的內(nèi)存限制,操作系統(tǒng)會(huì)指定允許同時(shí)存在的最大進(jìn)程數(shù)。在Xv6系統(tǒng)中,最多同時(shí)存在64個(gè)進(jìn)程。操作系統(tǒng)會(huì)維護(hù)一個(gè)大小為64的struct proc數(shù)組,并在其中分配新的進(jìn)程。
進(jìn)程的上下文包含了這個(gè)進(jìn)程執(zhí)行時(shí)所需要的全部信息,主要是寄存器的值和運(yùn)行時(shí)棧。在Xv6系統(tǒng)中,執(zhí)行進(jìn)程的上下文切換就意味著要保存原進(jìn)程的調(diào)用保存寄存器 %ebp %ebx %esi %ebp,棧指針%esp和程序指針eip,并載入新的進(jìn)程的上述寄存器。特別地,Xv6中的進(jìn)程切換只會(huì)切換到內(nèi)核調(diào)度器進(jìn)程,并通過(guò)內(nèi)核調(diào)度器切換到新的進(jìn)程。
關(guān)于進(jìn)程調(diào)度的具體細(xì)節(jié),官方文檔具有精彩的描述,在此不再贅述。
多進(jìn)程和多CPU之間的關(guān)系在于,在操作系統(tǒng)面前,每個(gè)進(jìn)程都好似占用了一個(gè)獨(dú)立的虛擬CPU,但事實(shí)上操作系統(tǒng)會(huì)將多個(gè)進(jìn)程分配在一個(gè)或多個(gè)CPU上運(yùn)行,進(jìn)程的數(shù)量與CPU的數(shù)量之間并沒(méi)有直接的關(guān)系。
5. 內(nèi)核態(tài)進(jìn)程與用戶態(tài)進(jìn)程
內(nèi)核態(tài)進(jìn)程,顧名思義,是在操作系統(tǒng)內(nèi)核態(tài)下執(zhí)行的進(jìn)程。在內(nèi)核態(tài)下運(yùn)行的進(jìn)程一般用于完成操作系統(tǒng)最底層,最為核心,無(wú)法在用戶態(tài)下完成的功能。比如,調(diào)度器進(jìn)程是Xv6中的一個(gè)內(nèi)核態(tài)進(jìn)程,因?yàn)樵谟脩魬B(tài)下是無(wú)法進(jìn)行進(jìn)程調(diào)度的。相比較而言,用戶態(tài)進(jìn)程用于完成的功能可以多種多樣,并且其功能只依賴于操作系統(tǒng)提供的系統(tǒng)調(diào)用,不需要深入操作內(nèi)核的數(shù)據(jù)結(jié)構(gòu)。比如init進(jìn)程和shell進(jìn)程就是xv6中的用戶態(tài)進(jìn)程。
6. 進(jìn)程的內(nèi)存布局
Xv6進(jìn)程在虛擬內(nèi)存中的布局如上圖。當(dāng)然,其中的每一頁(yè)在物理內(nèi)存中大概率并不是這樣排列的,但是虛擬內(nèi)存系統(tǒng)為每個(gè)進(jìn)程提供了統(tǒng)一的內(nèi)存抽象。進(jìn)程的棧用于存放運(yùn)行時(shí)數(shù)據(jù)和運(yùn)行時(shí)軌跡,包含了函數(shù)調(diào)用的嵌套,函數(shù)調(diào)用的參數(shù)和臨時(shí)變量的存儲(chǔ)等。棧通常較小,不會(huì)在運(yùn)行時(shí)增長(zhǎng),不適合存儲(chǔ)大量數(shù)據(jù)。相比較而言,堆提供了一個(gè)存放全局變量和動(dòng)態(tài)增長(zhǎng)的數(shù)據(jù)的機(jī)制。堆的大小通常可以動(dòng)態(tài)增長(zhǎng),并且一般用于存儲(chǔ)較大的數(shù)據(jù)和程序執(zhí)行過(guò)程中始終會(huì)被訪問(wèn)的全局變量。
7. fork、wait、exit系統(tǒng)調(diào)用的實(shí)現(xiàn)。
fork()函數(shù)
// Create a new process copying p as the parent. // Sets up stack to return as if from system call. // Caller must set state of returned proc to RUNNABLE. int fork(void) {int i, pid;struct proc *np;struct proc *curproc = myproc();// Allocate process.if((np = allocproc()) == 0){return -1;}// Copy process state from proc.if((np->pgdir = copyuvm(curproc->pgdir, curproc->sz)) == 0){kfree(np->kstack);np->kstack = 0;np->state = UNUSED;return -1;}np->sz = curproc->sz;np->parent = curproc;*np->tf = *curproc->tf;// Clear %eax so that fork returns 0 in the child.np->tf->eax = 0;for(i = 0; i < NOFILE; i++)if(curproc->ofile[i])np->ofile[i] = filedup(curproc->ofile[i]);np->cwd = idup(curproc->cwd);safestrcpy(np->name, curproc->name, sizeof(curproc->name));pid = np->pid;acquire(&ptable.lock);np->state = RUNNABLE;release(&ptable.lock);return pid; }fork()函數(shù)的代碼如上。fork()函數(shù)首先調(diào)用allocproc()函數(shù)獲得并初始化一個(gè)進(jìn)程控制塊struct proc(12-14行)。此外,在allocproc()函數(shù)中還會(huì)對(duì)進(jìn)程的內(nèi)核棧進(jìn)行初始化,在內(nèi)核棧里設(shè)置一個(gè)Trap Frame,把Trap Frame的上下文部分都置為0。然后,fork()函數(shù)使用copyuvm()函數(shù)復(fù)制原進(jìn)程的虛擬內(nèi)存結(jié)構(gòu)(17-24行)。為了能讓子進(jìn)程返回時(shí)處在和父進(jìn)程一模一樣的狀態(tài),Trap Frame也會(huì)被拷貝(25行,需要注意這里的運(yùn)算符優(yōu)先級(jí))。為了讓子進(jìn)程系統(tǒng)調(diào)用的返回值為0,子進(jìn)程的eax寄存器會(huì)被置為0(28行)。然后,父進(jìn)程打開(kāi)的文件描述符會(huì)被全部拷貝給子進(jìn)程(30-32行),還有父進(jìn)程所處于的目錄(33行)。這些操作都會(huì)增加文件描述符和目錄的被引用數(shù)。最后,fork()函數(shù)拷貝了父進(jìn)程的名字,設(shè)置了子進(jìn)程的狀態(tài)為RUNNABLE,然后返回子進(jìn)程pid給父進(jìn)程。子進(jìn)程被創(chuàng)建后,在某個(gè)時(shí)刻調(diào)度子進(jìn)程運(yùn)行時(shí),fork()函數(shù)會(huì)第二次返回給子進(jìn)程,此時(shí)返回值為0。
wait()函數(shù)
// Wait for a child process to exit and return its pid. // Return -1 if this process has no children. int wait(void) {struct proc *p;int havekids, pid;struct proc *curproc = myproc();acquire(&ptable.lock);for(;;){// Scan through table looking for exited children.havekids = 0;for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){if(p->parent != curproc)continue;havekids = 1;if(p->state == ZOMBIE){// Found one.pid = p->pid;kfree(p->kstack);p->kstack = 0;freevm(p->pgdir);p->pid = 0;p->parent = 0;p->name[0] = 0;p->killed = 0;p->state = UNUSED;release(&ptable.lock);return pid;}}// No point waiting if we don't have any children.if(!havekids || curproc->killed){release(&ptable.lock);return -1;}// Wait for children to exit. (See wakeup1 call in proc_exit.)sleep(curproc, &ptable.lock); //DOC: wait-sleep} }wait()函數(shù)的代碼如上。wait()函數(shù)首先必須要獲得ptable的鎖(10行),因?yàn)樗锌赡軙?huì)對(duì)ptable做出修改。然后它會(huì)遍歷ptable,從中尋找自己的子進(jìn)程(14-32行)。如果發(fā)現(xiàn)僵尸子進(jìn)程,就把僵尸子進(jìn)程回收,具體地說(shuō)要回收它的虛擬內(nèi)存,內(nèi)核棧,并設(shè)置狀態(tài)為UNUSED(18-30行),有趣的是,在這里wait()函數(shù)根本沒(méi)有回收這個(gè)子進(jìn)程打開(kāi)的文件描述符,因?yàn)樵趀xit()函數(shù)內(nèi)這個(gè)進(jìn)程打開(kāi)的文件描述符已經(jīng)全部被關(guān)閉了,而且只有exit()之后的進(jìn)程才可能是ZOMBIE狀態(tài)。對(duì)于沒(méi)有子進(jìn)程的情況,wait()會(huì)直接返回,否則他會(huì)調(diào)用sleep(),并傳入ptable的鎖作為參數(shù)。之所以要在sleep函數(shù)中傳入ptable鎖,是為了避免在wait()把進(jìn)程設(shè)置為SLEEP狀態(tài)之前,子進(jìn)程就已經(jīng)成為僵死進(jìn)程并在exit()函數(shù)中調(diào)用了wakeup(),這會(huì)使得父進(jìn)程接收不到wakeup從而進(jìn)入死鎖狀態(tài)。
exit()函數(shù)
// Exit the current process. Does not return. // An exited process remains in the zombie state // until its parent calls wait() to find out it exited. void exit(void) {struct proc *curproc = myproc();struct proc *p;int fd;if(curproc == initproc)panic("init exiting");// Close all open files.for(fd = 0; fd < NOFILE; fd++){if(curproc->ofile[fd]){fileclose(curproc->ofile[fd]);curproc->ofile[fd] = 0;}}begin_op();iput(curproc->cwd);end_op();curproc->cwd = 0;acquire(&ptable.lock);// Parent might be sleeping in wait().wakeup1(curproc->parent);// Pass abandoned children to init.for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){if(p->parent == curproc){p->parent = initproc;if(p->state == ZOMBIE)wakeup1(initproc);}}// Jump into the scheduler, never to return.curproc->state = ZOMBIE;sched();panic("zombie exit"); }exit()函數(shù)首先關(guān)閉這個(gè)進(jìn)程打開(kāi)的所有文件描述符(15-20行),然后除去自己對(duì)所處的文件目錄的引用(22-25行),對(duì)文件管理相關(guān)數(shù)據(jù)結(jié)構(gòu)的訪問(wèn)必須要獲得和釋放相關(guān)的鎖(begin_op()和end_op())。清除這些引用可以允許文件系統(tǒng)管理當(dāng)前的緩存。如果這個(gè)進(jìn)程的父進(jìn)程正在等待子進(jìn)程結(jié)束,那么這個(gè)進(jìn)程必須喚醒父進(jìn)程(30行),只有這樣父進(jìn)程才能夠在某個(gè)時(shí)刻回收僵尸子進(jìn)程。如果這個(gè)進(jìn)程有子進(jìn)程的話,就把這個(gè)進(jìn)程的子進(jìn)程都傳給init進(jìn)程,并由init進(jìn)程來(lái)負(fù)責(zé)回收僵尸子進(jìn)程(33-39行)。最后,這個(gè)進(jìn)程的狀態(tài)會(huì)被設(shè)置為ZOMBIE,調(diào)度器調(diào)度其他進(jìn)程運(yùn)行(42-44行)。
參考資料
Operating System Concepts, 7th Edition
Computer Systems: a Programmer's Perspective, 3rd Edition
Process in Linux, https://www.cs.columbia.edu/~junfeng/10sp-w4118/lectures/l07-proc-linux.pdf
10 Things Every Linux Programmer Should Know, http://www.mulix.org/lectures/kernel_workshop_mar_2004/things.pdf
Introduction to Linux Kernel, Chapter 3 Process Management, https://notes.shichao.io/lkd/ch3/#chapter-3-process-management
A Complete Introduction to Windows Processes, Threads and Related Resources, https://www.tenouk.com/ModuleT.html
Windows進(jìn)程數(shù)據(jù)結(jié)構(gòu)及創(chuàng)建流程,https://blog.csdn.net/cuit/article/details/9200097
轉(zhuǎn)載于:https://www.cnblogs.com/hehao98/p/10603045.html
總結(jié)
以上是生活随笔為你收集整理的XV6操作系统代码阅读心得(二):进程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 19_03_26校内训练[魔法卡片]
- 下一篇: gitbook 入门教程之解决windo