xv6中存儲cpu和進程信息的技巧

xv6是一個支持多處理器的Unix-lik操做系統,api

近日閱讀源碼時發現xv6在記錄當前CPU和進程狀態時很是tricky數組

首先,上代碼:操作系統

1 extern struct cpu cpus[NCPU];
 2 extern int ncpu;
 3 
 4 // Per-CPU variables, holding pointers to the
 5 // current cpu and to the current process.
 6 // The asm suffix tells gcc to use "%gs:0" to refer to cpu
 7 // and "%gs:4" to refer to proc.  seginit sets up the
 8 // %gs segment register so that %gs refers to the memory
 9 // holding those two variables in the local cpu's struct cpu.
10 // This is similar to how thread-local variables are implemented
11 // in thread libraries such as Linux pthreads.
12 extern struct cpu *cpu asm("%gs:0");       // &cpus[cpunum()]
13 extern struct proc *proc asm("%gs:4");     // cpus[cpunum()].proc

其中struct cpu是一個用來保存每一個cpu運行狀態的結構體,線程

代碼第一行定義告終構體數組cpus[NCPU]NCPU對應cpu的總數(最大爲8),也就是說cpus用來存儲全部cpu的運行狀態。code

那麼問題來了:
上面的內核代碼是運行於每一個cpu之中的,那每一個cpu如何知道自身的當前運行狀態呢?進程

對於這個問題,咱們能夠經過lapic獲取cpu自身編號,再利用編號對cpus尋址便可,內存

也就是說,對於任意一個cpu,自身狀態的存儲位置能夠這樣得到:源碼

struct cpu *c = &cpus[cpunum()];

然而,第二個問題來了:
咱們不可能每次引用cpu自身狀態時都經過lapic獲取編號啊,能不能弄一個全局變量把狀態位置一次性存儲下來呢?
像是這樣, struct cpu *cpu; //全局變量,存儲cpu自身狀態,
而後在初始化代碼中 cpu = c;it

對於記錄每一個cpu正在運行的進程也有這樣的問題,能不能寫成:
struct proc *proc; //全局變量,存儲當前cpu正在運行的進程狀態asm

那麼,第三個問題來了:
每一個cpu是獨立並行的,在每一個cpu上運行的內核代碼都是同樣的,頁表也同樣,
這意味着全局變量cpu和proc的地址也是同樣的,這樣便不能夠用來區分不一樣cpu的狀態了。
所以,咱們須要一種方法,可讓咱們在每一個cpu中都用同一個符號記錄狀態,但這些符號倒是映射到不一樣的地址。

既然頁表同樣,咱們天然不能用一個絕對的數值來尋址啦,仔細想一想,頁表之上有什麼?頁表之上,還有段表啊。

因此咱們須要用segment register來尋址,只要咱們在創建段表時把該段都映射到不一樣的內存區域不就能夠了,因此咱們有了如下聲明:

1 extern struct cpu *cpu asm("%gs:0");       // &cpus[cpunum()]
2 extern struct proc *proc asm("%gs:4");     // cpus[cpunum()].proc

咱們用gs做爲段寄存器,cpu指向[%gs]proc指向[%gs+4]

其中爲何開頭要用extern呢?我問過某大神,他說是由於gs段是在外部創建的,至關於外部定義的。。。

OK,最後一個問題來了,
gs段應該指向哪,才能確保每一個cpu的gs段都位於不一樣的區域?

最直觀的想法固然是指向對應的cpus[num]內部啦,因此在struct cpu尾部增長兩個域:

1 struct cpu{
2    ........  //cpu狀態
3 
4    // Cpu-local storage variables; see below
5   struct cpu *cpu;
6   struct proc *proc;           // The currently-running process.
7 }

而後在創建段表時,增長gs段,並映射至尾部這兩個域:

1   c = &cpus[cpunum()];
 2   
 3   ......... //創建其餘段
 4 
 5   // 創建gs段,共兩個域(存儲cpu和proc地址),起始地址爲&c->cpu
 6   c->gdt[SEG_KCPU] = SEG(STA_W, &c->cpu, 8, 0);
 7 
 8   //加載gdt
 9   lgdt(c->gdt, sizeof(c->gdt));
10   //加載gs
11   loadgs(SEG_KCPU << 3);
12   
13   // 把當前cpu和proc狀態的地址賦給cpu和proc全局變量
14   //而cpu變量實質爲%gs:0, proc變量實質爲%gs:4
15   cpu = c;
16   proc = 0;

其實在這裏cpuproc變量跟線程局部存儲的性質差很少,每一個處理器均可以引用同一個變量,但這些變量都對應不一樣的存儲區域。

有可能這種實現技巧跟TLS(線程局部存儲)差很少,有空研究下TLS的實現看看是否是。

相關文章
相關標籤/搜索