linux 2.6源代码情景分析笔记之内存2

linux中的段机制:
在linux中,逻辑地址到物理地址,是经过分段单元->分页单元这两个部分的转换来完成的。其中逻辑地址由段标识符和指定段内相对地址的偏移量。前者16位长的字段(段选择符segment selector),后者是32位长的字段。
段选择符的构成:
15-3(index)指定了放在GDT或者LDT中的相应段描述符的入口
2(TL-table indicator)标志:指明段描述符是在GDT中(TI=0)或者在LDT中(TI=1)
1-0(RPL)请求者特权级:当相应的段选择符号装入cs寄存器中时,指示出cpu当前的特权级;还可以用于在访问数据段时有选择地削弱处理器的特权级(intel文档)

处理器中有几个段寄存器用来存放段选择符,cs,ss,ds,es,fs,gs.其中cs,ss,ds有专门的用途。
cs:代码段寄存器,指向包含程序指令的段。(其中还含有一个两位的字段,用以指明cpu的当前特权级别(current privilege level,CPL)。当为0的时候,代表最高级优先级别,为3时代表最低优先级别。在linux中前者是内核级别,后者用户级别)
ds:数据段寄存器,指向包含静态数据或者全局数据段。
ss:栈段寄存器,指向包含当前程序栈的段。

而段的表示是由8字节的段描述符(segment descriptor)来完成的,此数据描述了段的特征。段描述符放在全局描述符表(global descriptor table,GDT)或局部描述符表(local descriptor table,LDT)中。
GDT在主存地址和大小存放在gdtr寄存器,当前正被使用的LDT地址和大小放在ldtr控制寄存器中。

其中几个字段:
31-24 23-16 15-0(base 63-56 39-32 31-16):包含段的首字节的线性地址。
G(55):为0时段是以字节为单位,为1时以4096的倍数计。
D/B(54):为D或者为B取决于是代码段还是数据段。D或B的含义在两种情况下有区别。当段偏移量的地址为32位,就设为1,如果偏移量16位长,被清0(intel手册)。
0(53)
AVL(52):被linux忽略
19-16 15-0(limit 51-48 15-0):存放段中最后一个内存单元的偏移量,从而决定段长度。如果段大小是按照字节单位则一个段的大小在1个字节到1mb之间变化;如果以4096字节的倍数计算,则在4kb-4GB之间变化。
p(47):segment-present标志:等于0表示段当前不在主存中。linux总将此设置(47位)为1
DPL(46-45):描述符特权级(descriptor privilege level)字段,用于限制对于这个段的存取。表示为访问这个段要求的cpu最小的优先级别。因此,dpl设为0的段只能当cpl为0时(内核态)才是可访问的,而dpl设为3的段对任何cpl值都是可访问的。
S(44):系统标志:如果被清0,则表明是系统段,存储如LDT等关键数据结构,否则是普通的代码段或者数据段。
TYPE(43-40):段的类型特征和存取权限,当为B或者D时,其分别表示为数据段或者代码段。此时s为1,因为二者都是非系统段,当表示系统段时,s=0,type字段的数值为11或者9.

访问段描述符方法:有6个可编程的段寄存器,每个含有8个字节的段描述符,由相应的段寄存器中的段选择符来指定,当一个段选择符被装入段寄存器时,相应的描述符就由内存装入到对应的非编程cpu寄存器。针对段的逻辑地址就可以不访问主存中的GDT或者LDT,处理器只需要直接引用存放段描述符的cpu寄存器即可。仅当段寄存器的内容改变时,才有必要访问GDT,LDT.

寻找方式就是,段描述符在gdt,ldt中的相对地址是由段选择符的最高13位的数值乘以8得到的。

#define GDT_ENTRY_BOOT_CS  2
#define __BOOT_CS (GDT_ENTRY_BOOT_CS * 8)
#define GDT_ENTRY_BOOT_DS  (GDT_ENTRY_BOOT_CS + 1)
#define __BOOT_DS (GDT_ENTRY_BOOT_DS * 8)

linux中的分段
运行在用户态的所有linux进程都使用一对相同的段来对指令和数据寻址。这两个段就是所谓的用户代码段和用户数据段。于此相同,运行在内核态的所有linux进程都使用一对相同的段对指令和数据寻址:分别为内核代码段和内核数据段。
相应的段选择符由宏 __USER_CS,__USER_DS,__KERNEL_CS,__KERNEL_DS分别定义。
在linux中如此定义:
#define GDT_ENTRY_KERNEL_BASE   12
#define GDT_ENTRY_KERNEL_CS             (GDT_ENTRY_KERNEL_BASE + 0)
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
#define GDT_ENTRY_KERNEL_DS             (GDT_ENTRY_KERNEL_BASE + 1)
#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)

此是内核在全局描述符表的内核代码段,内核数据段的位置。可以看到段选择符也就是索引定义的是12.
而看看linux中的全局描述符表的排列:
* The layout of the per-CPU GDT under Linux:
*
*   0 - null
*   1 - reserved
*   2 - reserved
*   3 - reserved
*
*   4 - unused                 <==== new cacheline
*   5 - unused
*
*  ------- start of TLS (Thread-Local Storage) segments:
*
*   6 - TLS segment #1                 [ glibc's TLS segment ]
*   7 - TLS segment #2                 [ Wine's %fs Win32 segment ]
*   8 - TLS segment #3
*   9 - reserved
*  10 - reserved
*  11 - reserved
*
*  ------- start of kernel segments:
*
*  12 - kernel code segment            <==== new cacheline
*  13 - kernel data segment
*  14 - default user CS
*  15 - default user DS
*  16 - TSS
*  17 - LDT
*  18 - PNPBIOS support (16->32 gate)
*  19 - PNPBIOS support
*  20 - PNPBIOS support
*  21 - PNPBIOS support
*  22 - PNPBIOS support
*  23 - APM BIOS support
*  24 - APM BIOS support
*  25 - APM BIOS support
*
*  26 - unused
*  27 - unused
*  28 - unused
*  29 - unused
*  30 - unused
*  31 - TSS for double fault handler
*/
其中的第12位正是kernel code segment而内核数据段紧随其后。
故索引的定义:(GDT_ENTRY_KERNEL_BASE + 0), (GDT_ENTRY_KERNEL_BASE + 1)的意图也就很明显了。
__KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)————12(000000001100)*8;
__KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)————13(000000001101)*8;

而default user CS则是在描述符表中的第14项。
#define GDT_ENTRY_DEFAULT_USER_CS       14
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)————(000000001110*8+3)
#define GDT_ENTRY_DEFAULT_USER_DS       15
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)————(000000001111*8+3)

不同之处在于多了个+3.这是特权级。在linux中的寻址过程是这样的:
index*8+gdtr/ldtr的基地址,找到gdtr/ldtr中的描述符,然后+offset得到线性地址。
其中cpu的当前特权级(cpl)反映了进程是在用户态还是在内核态,并由存放在cs寄存器中的段选择符的rpl字段指定。当特权级别改变时,相应的段寄存器必须相应的更新。
cpl=3时是用户态,ds寄存器必须含有用户数据段的段选择符;ss寄存器必须指向一个用户数据段中的用户栈。
cpl=0时是内核态,ds寄存器必须含有内核数据段的段选择符;ss寄存器必须装有内核数据段的段选择符。

在计算机中,每个cpu都对应一个GDT.而它们都存放在数组cpu_gdt_table中,而所有GDT的地址和大小都存放在cpu_gdt_descr中。

# boot GDT descriptor (later on used by CPU#0):
        .word 0                         # 32 bit align gdt_desc.address
cpu_gdt_descr:
        .word GDT_ENTRIES*8-1
        .long cpu_gdt_table
        .fill NR_CPUS-1,8,0             # space for the other GDT descriptors

struct Xgt_desc_struct {
        unsigned short size;
        unsigned long address __attribute__((packed));
        unsigned short pad;
} __attribute__ ((packed));

extern struct Xgt_desc_struct idt_descr, cpu_gdt_descr[NR_CPUS];


#define GDT_ENTRIES 32
extern struct desc_struct cpu_gdt_table[GDT_ENTRIES];
struct desc_struct {
        unsigned long a,b;
};
上面的数组元素宏说明最多可以有32个GDT,也就是32个cpu。段访问模式的基本特点——“选择符:偏移量”。虚拟地址最终通过——“段基地址+偏移量”转换成线性地址。在linux中,段基地址都为0

而在head.S函数中:
ENTRY(startup_32)
/*Set segments to known values.*/
        cld
        lgdt boot_gdt_descr - __PAGE_OFFSET
        movl $(__BOOT_DS),%eax
        movl %eax,%ds
        movl %eax,%es
        movl %eax,%fs
        movl %eax,%gs
寻找下__BOOT_DS发现:
#define GDT_ENTRY_BOOT_CS               2
#define __BOOT_CS       (GDT_ENTRY_BOOT_CS * 8)
#define GDT_ENTRY_BOOT_DS               (GDT_ENTRY_BOOT_CS + 1)
#define __BOOT_DS       (GDT_ENTRY_BOOT_DS * 8)
是从gdt全剧描述符表中的第三项。intel手册中说第一项设为0,为了确保空段选择符的逻辑地址被认为是无效的。故从第二项开始。

ENTRY(boot_gdt_table)
        .fill GDT_ENTRY_BOOT_CS,8,0
        .quad 0x00cf9a000000ffff        /* kernel 4GB code at 0x00000000 */
        .quad 0x00cf92000000ffff        /* kernel 4GB data at 0x00000000 */

关于此段对比下ENTRY(cpu_gdt_table)中的相同两项:
        .quad 0x00cf9a000000ffff        /* 0x60 kernel 4GB code at 0x00000000 */
        .quad 0x00cf92000000ffff        /* 0x68 kernel 4GB data at 0x00000000 */
也就是说内核从这个开始。
    
ENTRY(cpu_gdt_table)
        .quad 0x0000000000000000        /* NULL descriptor */
        .quad 0x0000000000000000        /* 0x0b reserved */
        .quad 0x0000000000000000        /* 0x13 reserved */
        .quad 0x0000000000000000        /* 0x1b reserved */
        .quad 0x0000000000000000        /* 0x20 unused */
        .quad 0x0000000000000000        /* 0x28 unused */
        .quad 0x0000000000000000        /* 0x33 TLS entry 1 */
        .quad 0x0000000000000000        /* 0x3b TLS entry 2 */
        .quad 0x0000000000000000        /* 0x43 TLS entry 3 */
        .quad 0x0000000000000000        /* 0x4b reserved */
        .quad 0x0000000000000000        /* 0x53 reserved */
        .quad 0x0000000000000000        /* 0x5b reserved */

        .quad 0x00cf9a000000ffff        /* 0x60 kernel 4GB code at 0x00000000 */
        .quad 0x00cf92000000ffff        /* 0x68 kernel 4GB data at 0x00000000 */
        .quad 0x00cffa000000ffff        /* 0x73 user 4GB code at 0x00000000 */
        .quad 0x00cff2000000ffff        /* 0x7b user 4GB data at 0x00000000 */

        .quad 0x0000000000000000        /* 0x80 TSS descriptor */
        .quad 0x0000000000000000        /* 0x88 LDT descriptor */

        /* Segments used for calling PnP BIOS */
        .quad 0x00c09a0000000000        /* 0x90 32-bit code */
        .quad 0x00809a0000000000        /* 0x98 16-bit code */
        .quad 0x0080920000000000        /* 0xa0 16-bit data */
        .quad 0x0080920000000000        /* 0xa8 16-bit data */
        .quad 0x0080920000000000        /* 0xb0 16-bit data */
        /*
         * The APM segments have byte granularity and their bases
         * and limits are set at run time.
         */
        .quad 0x00409a0000000000        /* 0xb8 APM CS    code */
        .quad 0x00009a0000000000        /* 0xc0 APM CS 16 code (16 bit) */
        .quad 0x0040920000000000        /* 0xc8 APM DS    data */

        .quad 0x0000000000000000        /* 0xd0 - unused */
        .quad 0x0000000000000000        /* 0xd8 - unused */
        .quad 0x0000000000000000        /* 0xe0 - unused */
        .quad 0x0000000000000000        /* 0xe8 - unused */
        .quad 0x0000000000000000        /* 0xf0 - unused */
        .quad 0x0000000000000000        /* 0xf8 - GDT entry 31: double-fault TSS */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值