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;
其實在這裏cpu
和proc
變量跟線程局部存儲的性質差很少,每一個處理器均可以引用同一個變量,但這些變量都對應不一樣的存儲區域。
有可能這種實現技巧跟TLS(線程局部存儲)
差很少,有空研究下TLS
的實現看看是否是。