xv6是一個支持多處理器的Unix-like操作系統,
近日閱讀源碼時發現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 implemented11 // 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的運行狀態。
那么問題來了:上面的內核代碼是運行于每個cpu之中的,那每個cpu如何知道自身的當前運行狀態呢?
對于這個問題,我們可以通過lapic獲取cpu自身編號,再利用編號對cpus尋址即可,
也就是說,對于任意一個cpu,自身狀態的存儲位置可以這樣獲得:struct cpu *c = &cpus[cpunum()];
然而,第二個問題來了:我們不可能每次引用cpu自身狀態時都通過lapic獲取編號啊,能不能弄一個全局變量把狀態位置一次性存儲下來呢?
像是這樣,struct cpu *cpu; //全局變量,存儲cpu自身狀態,然后在初始化代碼中cpu = c;
對于記錄每個cpu正在運行的進程也有這樣的問題,能不能寫成:struct proc *proc; //全局變量,存儲當前cpu正在運行的進程狀態
那么,第三個問題來了:每個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 below5 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 //加載gs11 loadgs(SEG_KCPU << 3);12 13 // 把當前cpu和proc狀態的地址賦給cpu和proc全局變量14 //而cpu變量實質為%gs:0, proc變量實質為%gs:415 cpu = c;16 proc = 0;
其實在這里cpu和proc變量跟線程局部存儲的性質差不多,每個處理器都可以引用同一個變量,但這些變量都對應不同的存儲區域。
有可能這種實現技巧跟TLS(線程局部存儲)差不多,有空研究下TLS的實現看看是不是。
新聞熱點
疑難解答