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
的实现看看是否是。