免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3608 | 回复: 0
打印 上一主题 下一主题

[FreeBSD] freebsd9.2-curthread宏的实现-标识当前正在运行的thread [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2014-06-20 13:42 |只看该作者 |倒序浏览
本帖最后由 71v5 于 2014-06-25 20:54 编辑

[freebsd9.2中的实现-curthread宏]:
  1. 244        static __inline __pure2 struct thread *
  2. 245        __curthread(void)
  3. 246        {
  4. 247                struct thread *td;
  5. 248       
  6. 249                __asm("movl %%fs:%1,%0" : "=r" (td)
  7. 250                    : "m" (*(char *)OFFSETOF_CURTHREAD));
  8. 251                return (td);
  9. 252        }

  10. 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数组中定义,如下所示:
  1.   1679  /*
  2.   1680   * software prototypes -- in more palatable form.
  3.   1681   *
  4.   1682   * GCODE_SEL through GUDATA_SEL must be in this order for syscall/sysret
  5.   1683   * GUFS_SEL and GUGS_SEL must be in this order (swtch.s knows it)
  6.            软件定义的段描述符,总共19个段,在init386函数中,将用gdt_segs中的段描述符
  7.            初始化系统中每个cpu的GDT。
  8.            从下面的定义中可以看出,内核态,用户态下的代码段和数据段的虚拟线性
  9.            基地址都被设置为0,这样的话,在翻译逻辑地址的时候,偏移地址和翻译后得到的
  10.            虚拟线性地址相等。
  11.   1684   */
  12.   1685  struct soft_segment_descriptor gdt_segs[] = {
  13. 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
  14. 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
  15.   1695   /************************************************************************
  16.          * GPRIV_SEL    1 SMP Per-Processor Private Data Descriptor
  17.            特定于每个cpu的私有段,在init386函数中,会重新将ssd_base设置为
  18.            struct pcpu数据对象的地址(每个cpu对应一个该数据对象),这样cpu只会
  19.            访问属于自己的私有数据,lgdt函数会用GPRIV_SEL构造相应的
  20.            segment selector,并且用该segment selector加载fs寄存器,这样当内核
  21.            访问当前cpu使用的私有数据时,就会使用该segment selector标识的段,
  22.            因为该段的基地址已经被改为struct pcpu数据对象的地址,所以内核只需
  23.            要几条简单的汇编指令就能访问struct pcpu数据对象的各个成员,内核中
  24.            用来标识当前正在cpu上执行的线程的宏curthread就基于这个段,
  25.            struct pcpu数据对象的pc_curthread成员始终指向当前正在cpu上运行的线程
  26.            的线程描述符,每当进程切换时,内核都会更新该成员的值。
  27.          **************************/
  28.   1696  {       .ssd_base = 0x0,
  29.   1697          .ssd_limit = 0xfffff,
  30.   1698          .ssd_type = SDT_MEMRWA,
  31.   1699          .ssd_dpl = SEL_KPL,
  32.   1700          .ssd_p = 1,
  33.   1701          .ssd_xx = 0, .ssd_xx1 = 0,
  34.   1702          .ssd_def32 = 1,
  35.   1703          .ssd_gran = 1           },
  36. 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
  37. 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
  38.         };


  39.   2802  init386(first)
  40.   2803          int first;
  41.   2804  {
  42.   2805          struct gate_descriptor *gdp;
  43.   2806          int gsel_tss, metadata_missing, x, pa;
  44.   2807          size_t kstack0_sz;
  45.   2808          struct pcpu *pc;
  46.   2809          /******************************************************************
  47.                  * 在源代码文件/src/sys/kern/init_main.c中,
  48.                    静态定义了下面的数据对象,由进程0使用:
  49.                    1:进程0对应的struct proc对象
  50.                    2:和进程0相关联线程0对应的struct thread对象
  51.                    1->struct  proc proc0;
  52.                    2->struct  thread thread0 __aligned(16);
  53.                    2810:
  54.                    设置thread0对象的td_kstack成员,该成员指向thread0内核栈的起始
  55.                    虚拟线性地址。
  56.                    2813:
  57.                    在thread0内核栈上开辟一段内存空间用来保存thread0对应的
  58.                    thread pcb对象,td_pcb成员指向struct pcb对象的起始位置
  59.                    (即struct pcb对象的起始虚拟线性地址)。
  60.                  ***************/
  61.   2810          thread0.td_kstack = proc0kstack;
  62.   2811          thread0.td_kstack_pages = KSTACK_PAGES;
  63.   2812          kstack0_sz = thread0.td_kstack_pages * PAGE_SIZE;
  64.   2813          thread0.td_pcb = (struct pcb *)(thread0.td_kstack + kstack0_sz) - 1;
  65. ..........................................................................................
  66. ..........................................................................................
  67.   2835  
  68.   2836          /********************************************************************
  69.   2837           * Make gdt memory segments.  All segments cover the full 4GB
  70.   2838           * of address space and permissions are enforced at page level.

  71.                    #define GCODE_SEL  4  Kernel Code Descriptor (order critical: 1)
  72.                    #define GDATA_SEL  5  Kernel Data Descriptor (order critical: 2)
  73.                    #define GUCODE_SEL 6  User Code Descriptor (order critical: 3)
  74.                    #define GUDATA_SEL 7  User Data Descriptor (order critical: 4)
  75.                    #define GUFS_SEL   2  User %fs Descriptor (order critical: 1)
  76.                    #define GUGS_SEL   3  User %gs Descriptor (order critical: 2)
  77.   2839           */
  78.   2840          gdt_segs[GCODE_SEL].ssd_limit = atop(0 - 1);
  79.   2841          gdt_segs[GDATA_SEL].ssd_limit = atop(0 - 1);
  80.   2842          gdt_segs[GUCODE_SEL].ssd_limit = atop(0 - 1);
  81.   2843          gdt_segs[GUDATA_SEL].ssd_limit = atop(0 - 1);
  82.   2844          gdt_segs[GUFS_SEL].ssd_limit = atop(0 - 1);
  83.   2845          gdt_segs[GUGS_SEL].ssd_limit = atop(0 - 1);
  84.   2846          /**********************************************************
  85.                  * struct pcpu __pcpu[MAXCPU]; 在多处理器中,每个cpu对应
  86.                    一个struct pcpu数据对象,用来存放反应当前cpu的状态,
  87.                   比如哪个线程正在该cpu执行,cpu的id等等,数组__pcpu以
  88.                   logical CPU ID为索引。
  89.                   2848-2849:更改gdt_segs[GPRIV_SEL]的ssd_limit和ssd_base
  90.                   成员,随后访问和操作每cpu数据对象的时候会使用这两个
  91.                   成员的值。
  92.                   2850:设置TSS描述符的地址,当cpu陷入内核时,都会使用
  93.                   tss_esp0标识的内核栈,在每次进程切换时,都要更新该成员
  94.                   的值,以使其指向当前进程的内核栈顶
  95.                  *******************/
  96.   2847          pc = &__pcpu[0];
  97.   2848          gdt_segs[GPRIV_SEL].ssd_limit = atop(0 - 1);
  98.   2849          gdt_segs[GPRIV_SEL].ssd_base = (int) pc;
  99.   2850          gdt_segs[GPROC0_SEL].ssd_base = (int) &pc->pc_common_tss;
  100.   2851          /***********************************************
  101.                  * 2852-2853:
  102.                    #define NGDT            19 GDT表项的数目
  103.                    用gdt_segs数组中的段描述符构造相应cpu的GDT。
  104.                    该过程由汇编函数ssdtosd完成。
  105.                    union descriptor gdt[NGDT * MAXCPU];
  106.                  ************/
  107.   2852          for (x = 0; x < NGDT; x++)
  108.   2853                  ssdtosd(&gdt_segs[x], &gdt[x].sd);
  109.   2854          /***************************************************************************
  110.                  * 2855-2858:加载GDTR。
  111.                  *
  112.                  * struct region_descriptor r_gdt;
  113.                    struct region_descriptor {
  114.                         unsigned rd_limit:16;         segment extent
  115.                         unsigned rd_base:32 __packed; base address
  116.                    };
  117.                    r_gdt.rd_limit:GDT的大小,单位为字节。
  118.                    r_gdt.rd_base:GDT的虚拟线性基地址。
  119.                   
  120.                    汇编函数lgdt主要完成下面的工作:
  121.                    1:加载GDTR。
  122.                    2:用内核数据段的Segment Selector重新加载ds,es,gs,ss段寄存器。
  123.                    3:KPSEL,即用Per-Processor Private Data Descriptor的Segment Selector
  124.                       加载fs寄存器,该段的基地址已经被设置为&__pcpu[0], PCPU_GET
  125.                       等宏都是围绕fs寄存器展开。
  126.                    4:用内核代码段的Segment Selector重新加载cs寄存器。
  127.                  ************************/
  128.   2855          r_gdt.rd_limit = NGDT * sizeof(gdt[0]) - 1;
  129.   2856          r_gdt.rd_base =  (int) gdt;
  130.   2857          mtx_init(&dt_lock, "descriptor tables", NULL, MTX_SPIN);
  131.   2858          lgdt(&r_gdt);
  132. ...............................................................................................
  133. ...............................................................................................
  134.   3089  }
复制代码
通过上面的描述可知,cpu的FS寄存器保存了Per-Processor Private Data Descriptor的Segment Selector,该
Data Descriptor中的base address已经被重置为该cpu对应的struct pcpu数据对象的地址,此时通过显式的
指定FS为要操作数据所在段的Segment Selector,就可以访问该cpu对应的struct pcpu数据对象中的各成员。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP