- 论坛徽章:
- 0
|
本帖最后由 71v5 于 2014-06-25 20:54 编辑
[freebsd9.2中的实现-curthread宏]:- 244 static __inline __pure2 struct thread *
- 245 __curthread(void)
- 246 {
- 247 struct thread *td;
- 248
- 249 __asm("movl %%fs:%1,%0" : "=r" (td)
- 250 : "m" (*(char *)OFFSETOF_CURTHREAD));
- 251 return (td);
- 252 }
- 256 #define curthread (__curthread())
复制代码 而在freebsd9.2中,采用了一种和linux完全不同的方式来标识当前线程,这种方式建立在每cpu数据结构struct pcpu
(以及操作struct pcpu数据对象的PCPU_宏)和段寄存器FS之上,这里只分析freebsd内核怎么标识当前正在运行的线程,
所以只引用该数据对象的pc_curthread成员,这个成员始终指向当前正在cpu上运行线程对应的struct thread对象。
struct pcpu __pcpu[MAXCPU]; 在多处理器中,每个cpu对应一个struct pcpu数据对象和自己的GDT,假设系统中有多个cpu,编号依次为
a,b,c,那么其对应的struct pcpu数据对象的地址依次为依次为&__pcpu[a],&__pcpu,&__pcpu[c]等等。
每个cpu的GDT中,专门有一个段描述符来访问其对应的struct pcpu数据对象,这个段描述符Segment Selector为GSEL(GPRIV_SEL, SEL_KPL),
FS寄存器的值始终为GSEL(GPRIV_SEL, SEL_KPL),这个段描述符对应段的base address初始时为0,当每个cpu启动时,都会将自己GDT中这个
段描述符的base address设置为相应struct pcpu数据对象的地址(&__pcpu[a],&__pcpu,&__pcpu[c])。可以看出,freebsd内核把每个
cpu对应的struct pcpu数据对象当做一个段,这样就可以通过汇编指令访问这个段(也就是访问了struct pcpu数据对象)。
该段描述符的初始值在struct soft_segment_descriptor gdt_segs数组中定义,如下所示:- 1679 /*
- 1680 * software prototypes -- in more palatable form.
- 1681 *
- 1682 * GCODE_SEL through GUDATA_SEL must be in this order for syscall/sysret
- 1683 * GUFS_SEL and GUGS_SEL must be in this order (swtch.s knows it)
- 软件定义的段描述符,总共19个段,在init386函数中,将用gdt_segs中的段描述符
- 初始化系统中每个cpu的GDT。
- 从下面的定义中可以看出,内核态,用户态下的代码段和数据段的虚拟线性
- 基地址都被设置为0,这样的话,在翻译逻辑地址的时候,偏移地址和翻译后得到的
- 虚拟线性地址相等。
- 1684 */
- 1685 struct soft_segment_descriptor gdt_segs[] = {
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- 1695 /************************************************************************
- * GPRIV_SEL 1 SMP Per-Processor Private Data Descriptor
- 特定于每个cpu的私有段,在init386函数中,会重新将ssd_base设置为
- struct pcpu数据对象的地址(每个cpu对应一个该数据对象),这样cpu只会
- 访问属于自己的私有数据,lgdt函数会用GPRIV_SEL构造相应的
- segment selector,并且用该segment selector加载fs寄存器,这样当内核
- 访问当前cpu使用的私有数据时,就会使用该segment selector标识的段,
- 因为该段的基地址已经被改为struct pcpu数据对象的地址,所以内核只需
- 要几条简单的汇编指令就能访问struct pcpu数据对象的各个成员,内核中
- 用来标识当前正在cpu上执行的线程的宏curthread就基于这个段,
- struct pcpu数据对象的pc_curthread成员始终指向当前正在cpu上运行的线程
- 的线程描述符,每当进程切换时,内核都会更新该成员的值。
- **************************/
- 1696 { .ssd_base = 0x0,
- 1697 .ssd_limit = 0xfffff,
- 1698 .ssd_type = SDT_MEMRWA,
- 1699 .ssd_dpl = SEL_KPL,
- 1700 .ssd_p = 1,
- 1701 .ssd_xx = 0, .ssd_xx1 = 0,
- 1702 .ssd_def32 = 1,
- 1703 .ssd_gran = 1 },
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- };
- 2802 init386(first)
- 2803 int first;
- 2804 {
- 2805 struct gate_descriptor *gdp;
- 2806 int gsel_tss, metadata_missing, x, pa;
- 2807 size_t kstack0_sz;
- 2808 struct pcpu *pc;
- 2809 /******************************************************************
- * 在源代码文件/src/sys/kern/init_main.c中,
- 静态定义了下面的数据对象,由进程0使用:
- 1:进程0对应的struct proc对象
- 2:和进程0相关联线程0对应的struct thread对象
- 1->struct proc proc0;
- 2->struct thread thread0 __aligned(16);
- 2810:
- 设置thread0对象的td_kstack成员,该成员指向thread0内核栈的起始
- 虚拟线性地址。
- 2813:
- 在thread0内核栈上开辟一段内存空间用来保存thread0对应的
- thread pcb对象,td_pcb成员指向struct pcb对象的起始位置
- (即struct pcb对象的起始虚拟线性地址)。
- ***************/
- 2810 thread0.td_kstack = proc0kstack;
- 2811 thread0.td_kstack_pages = KSTACK_PAGES;
- 2812 kstack0_sz = thread0.td_kstack_pages * PAGE_SIZE;
- 2813 thread0.td_pcb = (struct pcb *)(thread0.td_kstack + kstack0_sz) - 1;
- ..........................................................................................
- ..........................................................................................
- 2835
- 2836 /********************************************************************
- 2837 * Make gdt memory segments. All segments cover the full 4GB
- 2838 * of address space and permissions are enforced at page level.
- #define GCODE_SEL 4 Kernel Code Descriptor (order critical: 1)
- #define GDATA_SEL 5 Kernel Data Descriptor (order critical: 2)
- #define GUCODE_SEL 6 User Code Descriptor (order critical: 3)
- #define GUDATA_SEL 7 User Data Descriptor (order critical: 4)
- #define GUFS_SEL 2 User %fs Descriptor (order critical: 1)
- #define GUGS_SEL 3 User %gs Descriptor (order critical: 2)
- 2839 */
- 2840 gdt_segs[GCODE_SEL].ssd_limit = atop(0 - 1);
- 2841 gdt_segs[GDATA_SEL].ssd_limit = atop(0 - 1);
- 2842 gdt_segs[GUCODE_SEL].ssd_limit = atop(0 - 1);
- 2843 gdt_segs[GUDATA_SEL].ssd_limit = atop(0 - 1);
- 2844 gdt_segs[GUFS_SEL].ssd_limit = atop(0 - 1);
- 2845 gdt_segs[GUGS_SEL].ssd_limit = atop(0 - 1);
- 2846 /**********************************************************
- * struct pcpu __pcpu[MAXCPU]; 在多处理器中,每个cpu对应
- 一个struct pcpu数据对象,用来存放反应当前cpu的状态,
- 比如哪个线程正在该cpu执行,cpu的id等等,数组__pcpu以
- logical CPU ID为索引。
- 2848-2849:更改gdt_segs[GPRIV_SEL]的ssd_limit和ssd_base
- 成员,随后访问和操作每cpu数据对象的时候会使用这两个
- 成员的值。
- 2850:设置TSS描述符的地址,当cpu陷入内核时,都会使用
- tss_esp0标识的内核栈,在每次进程切换时,都要更新该成员
- 的值,以使其指向当前进程的内核栈顶
- *******************/
- 2847 pc = &__pcpu[0];
- 2848 gdt_segs[GPRIV_SEL].ssd_limit = atop(0 - 1);
- 2849 gdt_segs[GPRIV_SEL].ssd_base = (int) pc;
- 2850 gdt_segs[GPROC0_SEL].ssd_base = (int) &pc->pc_common_tss;
- 2851 /***********************************************
- * 2852-2853:
- #define NGDT 19 GDT表项的数目
- 用gdt_segs数组中的段描述符构造相应cpu的GDT。
- 该过程由汇编函数ssdtosd完成。
- union descriptor gdt[NGDT * MAXCPU];
- ************/
- 2852 for (x = 0; x < NGDT; x++)
- 2853 ssdtosd(&gdt_segs[x], &gdt[x].sd);
- 2854 /***************************************************************************
- * 2855-2858:加载GDTR。
- *
- * struct region_descriptor r_gdt;
- struct region_descriptor {
- unsigned rd_limit:16; segment extent
- unsigned rd_base:32 __packed; base address
- };
- r_gdt.rd_limit:GDT的大小,单位为字节。
- r_gdt.rd_base:GDT的虚拟线性基地址。
-
- 汇编函数lgdt主要完成下面的工作:
- 1:加载GDTR。
- 2:用内核数据段的Segment Selector重新加载ds,es,gs,ss段寄存器。
- 3:KPSEL,即用Per-Processor Private Data Descriptor的Segment Selector
- 加载fs寄存器,该段的基地址已经被设置为&__pcpu[0], PCPU_GET
- 等宏都是围绕fs寄存器展开。
- 4:用内核代码段的Segment Selector重新加载cs寄存器。
- ************************/
- 2855 r_gdt.rd_limit = NGDT * sizeof(gdt[0]) - 1;
- 2856 r_gdt.rd_base = (int) gdt;
- 2857 mtx_init(&dt_lock, "descriptor tables", NULL, MTX_SPIN);
- 2858 lgdt(&r_gdt);
- ...............................................................................................
- ...............................................................................................
- 3089 }
复制代码 通过上面的描述可知,cpu的FS寄存器保存了Per-Processor Private Data Descriptor的Segment Selector,该
Data Descriptor中的base address已经被重置为该cpu对应的struct pcpu数据对象的地址,此时通过显式的
指定FS为要操作数据所在段的Segment Selector,就可以访问该cpu对应的struct pcpu数据对象中的各成员。
|
|