由Linux0.11进程调度小窥内存分段机制

 

       内存分段机制的一个主要应用在于实现操作系统的多任务,它为应用程序提供了两个关键抽象:一个独立的逻辑控制流,一个私有的地址空间。本文将针对进程的创建和调度进行分析和实验,从而更深刻的理解分段机制。有关调试环境的建立见前文:从linux0.11引导代码小窥内存分段机制

进程调度初始化(sched_init函数)

       在引导代码执行结束后,执行序列将跳转到main函数,执行一系列的初始化工作,其中就有对任务0的初始化过程,其代码包含在kernel/sched.c中的sched_init函数中:

void sched_init(void)

{

       int i;

       struct desc_struct * p;

       if (sizeof(struct sigaction) != 16)

              panic("Struct sigaction MUST be 16 bytes");

/*建立第0号任务的TSSLDT描述符表项*/

       set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));

       set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));

       p = gdt+2+FIRST_TSS_ENTRY;

       for(i=1;i<NR_TASKS;i++) {

              task[i] = NULL;

              p->a=p->b=0;

              p++;

              p->a=p->b=0;

              p++;

       }

/* Clear NT, so that we won't have troubles with that later on */

       __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");

       ltr(0);   /*将任务0TSS加载到任务寄存器tr*/

       lldt(0);  /*将局部描述符表加载到局部描述符表寄存器*/

       outb_p(0x36,0x43);              /* binary, mode 3, LSB/MSB, ch 0 */

       outb_p(LATCH & 0xff , 0x40);     /* LSB */

       outb(LATCH >> 8 , 0x40);    /* MSB */

       set_intr_gate(0x20,&timer_interrupt);

       outb(inb_p(0x21)&~0x01,0x21);

       set_system_gate(0x80,&system_call);

}

       set_tss_desc函数在include/asm/system.h中定义:

/*8字节的描述符各个字节进行设置*/

#define _set_tssldt_desc(n,addr,type) /

__asm__ ("movw $104,%1/n/t" /

       "movw %%ax,%2/n/t" /

       "rorl $16,%%eax/n/t" /

       "movb %%al,%3/n/t" /

       "movb $" type ",%4/n/t" /

       "movb $0x00,%5/n/t" /

       "movb %%ah,%6/n/t" /

       "rorl $16,%%eax" /

       ::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), /

        "m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) /

       )

/*0x89TSS描述符的属性,0x82LDT描述符的属性*/

#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89")

#define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x82")

      

       上面那段汇编代码即对GDT一个8字节描述符表项的各个字节进行设置,设置完毕后每个描述符的内容如下表:

系统段

描述符

7

6

5

4

3

2

1

0

addr8

0x00890x0082

addr24

0x0068(段界限)

       0x0089表示可用的386TSS0x9),0x0082表示可用的LDT0x2)。

       init_task为全局变量,其初始化为INIT_TASKINIT_TASK宏定义如下:

#define INIT_TASK /

/* state etc */  { 0,15,15, /

/* signals */    0,{{},},0, /

/* ec,brk... */  0,0,0,0,0,0, /

/* pid etc.. */  0,-1,0,0,0, /

/* uid etc */    0,0,0,0,0,0, /

/* alarm */      0,0,0,0,0,0, /

/* math */       0, /

/* fs info */    -1,0022,NULL,NULL,NULL,0, /

/* filp */  {NULL,}, /

       { /

              {0,0}, /

/* ldt */   {0x9f,0xc0fa00}, /  /*代码长640k,界限粒度4k字节,基址0x0 */

              {0x9f,0xc0f200}, /  /*数据长640k,界限粒度4k字节,基址0x0 */

       }, /

/*tss*/     {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,/

        0,0,0,0,0,0,0,0, /

        0,0,0x17,0x17,0x17,0x17,0x17,0x17, /

        _LDT(0),0x80000000, /

              {} /

       }, /

}

       在这段代码中我们关心的是ldttss的设置,每个任务的ldt表有3个表项,第一项未使用,第二项为code段,第三项为data段。在任务0的描述符表设置完毕,这两个字段的地址也就成为任务0TSS0描述符和LDT0描述符的内容。

       ltrlldt函数用于将描述符表项在GDT表中的索引加载到相应寄存器中,以任务0为例,在执行完这两个函数之后,tr寄存器中的值为4*8 = 0x20ldt寄存器中的值为5*8 = 0x28

       下面对sched_init函数进行调试验证上述内容。首先在内核编译后产生的System.map文件中找到该函数的地址:0x72bc。启动bochsdbg,在0x72bc处设置断点,命令行如下:

<bochs:1> b 0x72bc

<bochs:2> c

(0) Breakpoint 1, 0x72bc in ?? ()

Next at t=16800742

(0) [0x000072bc] 0008:000072bc (unk. ctxt): push ebp                  ; 55

<bochs:3> u /50

……

000072ce: (                    ): mov word ptr ds:0x5cd8, 0x68 ; 66c705d85c00006

800

000072d7: (                    ): mov word ptr ds:0x5cda, ax ; 66a3da5c0000

000072dd: (                    ): ror eax, 0x10             ; c1c810

000072e0: (                    ): mov byte ptr ds:0x5cdc, al ; 8805dc5c0000

000072e6: (                    ): mov byte ptr ds:0x5cdd, 0x89 ; c605dd5c000089

000072ed: (                    ): mov byte ptr ds:0x5cde, 0x0 ; c605de5c000000

000072f4: (                    ): mov byte ptr ds:0x5cdf, ah ; 8825df5c0000

000072fa: (                    ): ror eax, 0x10             ; c1c810

000072fd: (                    ): add eax, 0xffffffe8       ; 83c0e8

00007300: (                    ): mov word ptr ds:0x5ce0, 0x68 ; 66c705e05c00006

800

00007309: (                    ): mov word ptr ds:0x5ce2, ax ; 66a3e25c0000

0000730f: (                    ): ror eax, 0x10             ; c1c810

00007312: (                    ): mov byte ptr ds:0x5ce4, al ; 8805e45c0000

00007318: (                    ): mov byte ptr ds:0x5ce5, 0x82 ; c605e55c000082

0000731f: (                    ): mov byte ptr ds:0x5ce6, 0x0 ; c605e65c000000

00007326: (                    ): mov byte ptr ds:0x5ce7, ah ; 8825e75c0000

0000732c: (                    ): ror eax, 0x10             ; c1c810

……

0000737a: (                    ): mov eax, 0x20             ; b820000000

0000737f: (                    ): ltr ax                    ; 0f00d8

00007382: (                    ): mov eax, 0x28             ; b828000000

00007387: (                    ): lldt ax                   ; 0f00d0

……

<bochs:5> b 0x7387

<bochs:6> c

(0) Breakpoint 2, 0x7387 in ?? ()

Next at t=16801469

(0) [0x00007387] 0008:00007387 (unk. ctxt): lldt ax                   ; 0f00d0

<bochs:7> dump_cpu

……

cs:s=0x8, dl=0x7ff, dh=0xc09a00, valid=1

ss:s=0x10, dl=0xfff, dh=0xc09300, valid=7

ds:s=0x10, dl=0xfff, dh=0xc09200, valid=7

es:s=0x10, dl=0xfff, dh=0xc09300, valid=5

fs:s=0x10, dl=0xfff, dh=0xc09300, valid=1

gs:s=0x10, dl=0xfff, dh=0xc09300, valid=1

ldtr:s=0x28, dl=0x84640068, dh=0x8201, valid=1

tr:s=0x20, dl=0x847c0068, dh=0x8901, valid=1

gdtr:base=0x5cb8, limit=0x7ff

idtr:base=0x54b8, limit=0x7ff

……

       0x000072d7 – 0x0000732c即对GDT表的TSS0LDT0表项的内容进行设置。第0x0000737a – 0x00007387即对tr寄存器和ldt寄存器进行设置,完成设置后,以cs段寄存器值0x8为例,bit0bit1位表示特权级为0bit2TI字段位0,表示高13位组成的的指是指向GDT表的索引(如果TI字段为1,表示高13位组成的值是指向LDT表的索引)。

启动任务0move_to_user_mode宏)

       在完成一系列的初始化工作之后,内核将切换到用户模式任务0中继续执行。其代码即宏move_to_user_mode,代码如下:

#define move_to_user_mode() /

__asm__ ("movl %%esp,%%eax/n/t" /

       "pushl $0x17/n/t" /

       "pushl %%eax/n/t" /

       "pushfl/n/t" /

       "pushl $0x0f/n/t" / /*压入任务0的代码段(cs)选择符*/

       "pushl $1f/n/t" / /*压入标号1的地址,作为iret的返回地址*/

       "iret/n" /

/*切换到任务0,开始执行其指令序列*/

       "1:/tmovl $0x17,%%eax/n/t" /

       "movw %%ax,%%ds/n/t" /

       "movw %%ax,%%es/n/t" /

       "movw %%ax,%%fs/n/t" /

       "movw %%ax,%%gs" /

       :::"ax")

       在进程0LDT表中设置的代码段和数据段的基址都为0,这与内核代码段和数据段的基址一致,在压栈过程中,压入的返回地址就是内核代码执行序列的地址,与之前内核执行序列的最关键的区别在于:段寄存器中的选择符为任务0独有LDT表的索引,而不再是指向GDT表的索引。LDT表的基址通过ldlt寄存器来查找:在初始化或任务切换过程中,把描述符对应任务LDT的描述符的选择子装入LDTR,处理器根据装入LDTR可见部分的选择子,从GDT中取出对应的描述符,并把LDT的基地址、界限和属性等信息保存到LDTR的不可见的高速缓冲寄存器中。

       下面对move_to_user_mode宏进行调试验证。首先在Systemp.map文件中找到main函数地址0x664c,通过查看汇编指令流找到move_to_user_mode宏的位置,命令行如下:

<bochs:1> b 0x664c

<bochs:2> c

(0) Breakpoint 1, 0x664c in ?? ()

Next at t=16769622

(0) [0x0000664c] 0008:0000664c (unk. ctxt): push ebp                  ; 55

<bochs:3> u /70

……

00006753: (                    ): mov eax, esp              ; 89e0

00006755: (                    ): push 0x17                 ; 6a17

00006757: (                    ): push eax                  ; 50

00006758: (                    ): pushfd                    ; 9c

00006759: (                    ): push 0xf                  ; 6a0f

0000675b: (                    ): push 0x6761               ; 6861670000

00006760: (                    ): iretd                     ; cf

00006761: (                    ): mov eax, 0x17             ; b817000000

00006766: (                    ): mov ds, ax                ; 668ed8

00006769: (                    ): mov es, ax                ; 668ec0

0000676c: (                    ): mov fs, ax                ; 668ee0

0000676f: (                    ): mov gs, ax                ; 668ee8

00006772: (                    ): add esp, 0xc              ; 83c40c

……

<bochs:4> b 0x6761

<bochs:5> c

(0) Breakpoint 1, 0x6761 in ?? ()

Next at t=16878984

(0) [0x00006761] 000f:00006761 (unk. ctxt): mov eax, 0x17             ; b8170000

00

<bochs:6> dump_cpu

……

eip:0x6761

cs:s=0xf, dl=0x9f, dh=0xc0fa00, valid=1

ss:s=0x17, dl=0x9f, dh=0xc0f200, valid=1

ds:s=0x0, dl=0x0, dh=0x0, valid=0

es:s=0x0, dl=0x0, dh=0x0, valid=0

fs:s=0x0, dl=0x0, dh=0x0, valid=0

gs:s=0x0, dl=0x0, dh=0x0, valid=0

ldtr:s=0x28, dl=0x84640068, dh=0x8201, valid=1

tr:s=0x20, dl=0x847c0068, dh=0x8901, valid=1

gdtr:base=0x5cb8, limit=0x7ff

idtr:base=0x54b8, limit=0x7ff

……

       这些调试信息有3个地方值得注意。首先是eip寄存器的指针指向iretd的下一条指令地址处。其次是此时的代码段描述符为0xfbit0bit1位表示特权级为3bit2TI字段位1,表示高13位组成的的指1是指向LDT表的第1项索引(从0开始)。最后是ldtr的值:s=0x28表示该描述符在GDT表的位置为0x28/8=58字节描述符的值为0x00 0x0082 0x018464 0x0068,即dldh的组合,它表示LDT表的地址为0x00018464(线性地址),查看内存可知代码段和数据段的基址和段限长,命令行如下:

<bochs:7> xp /6 0x018464

[bochs]:

0x00018464 <bogus+       0>:    0x00000000      0x00000000      0x0000009f

0x00c0fa00

0x00018474 <bogus+      16>:    0x0000009f      0x00c0f200

       这些值即之前分析到的init_task.task.ldt所设置的值。

创建子进程(fork函数)

       fork函数是一个系统调用,用于创建子进程。Linux中所有进程都是进程0的子进程。关于对系统函数的调用过程将在以后的文章进行阐述。这里仅对其辅助函数进行分析,这些函数位于kernel/fork.c中。copy_process函数用于创建并复制父进程的代码段和数据段以及环境,代码如下:

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,

              long ebx,long ecx,long edx,

              long fs,long es,long ds,

              long eip,long cs,long eflags,long esp,long ss)

{

       struct task_struct *p;

       int i;

       struct file *f;

/*在物理内存上找到一个未被占用的页面*/

       p = (struct task_struct *) get_free_page();

       if (!p)

              return -EAGAIN;

       task[nr] = p;

       *p = *current; /* NOTE! this doesn't copy the supervisor stack */

/* !以下省略对p的某些字段的初始化代码 */

       if (last_task_used_math == current)

              __asm__("clts ; fnsave %0"::"m" (p->tss.i387));

/*设置新任务代码和数据段基址、限长并复制页表*/

       if (copy_mem(nr,p)) {

              task[nr] = NULL;

              free_page((long) p);

              return -EAGAIN;

       }

       for (i=0; i<NR_OPEN;i++)

              if (f=p->filp[i])

                     f->f_count++;

       if (current->pwd)

              current->pwd->i_count++;

       if (current->root)

              current->root->i_count++;

       if (current->executable)

              current->executable->i_count++;

/*设置子进程在GDT表的TSSLDT描述符*/

       set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));

       set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));

       p->state = TASK_RUNNING;       /* do this last, just in case */

       return last_pid;

}

       copy_men函数将设置新任务的代码和数据段基址、限长并复制页表。代码如下:

int copy_mem(int nr,struct task_struct * p)

{

       unsigned long old_data_base,new_data_base,data_limit;

       unsigned long old_code_base,new_code_base,code_limit;

/*取得任务0LDT表中的代码段和数据段的基址和段限长*/

       code_limit=get_limit(0x0f);

       data_limit=get_limit(0x17);

       old_code_base = get_base(current->ldt[1]);

       old_data_base = get_base(current->ldt[2]);

       if (old_data_base != old_code_base)

              panic("We don't support separate I&D");

       if (data_limit < code_limit)

              panic("Bad data_limit");

/*设置被创建进程的基址*/

       new_data_base = new_code_base = nr * 0x4000000;

       p->start_code = new_code_base;

       set_base(p->ldt[1],new_code_base);

       set_base(p->ldt[2],new_data_base);

/*将新进程的线性地址内存页对应到实际物理地址内存页面*/

       if (copy_page_tables(old_data_base,new_data_base,data_limit)) {

              free_page_tables(new_data_base,data_limit);

              return -ENOMEM;

       }

       return 0;

}

       Linux0.11内核将整个4G的地址空间划分成64块,供64个进程使用,子进程和父进程的代码段或数据段的地址由于选择不同的段基址将使得它们在逻辑上是分离的(分页机制和写时复制可能使得任务0和任务1的代码或数据存放在同一物理页面)。

进程调度(schedule函数)

       schedule函数实现进程调度,位于kernel/sched.h中,代码如下:

void schedule(void)

{

       int i,next,c;

       struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

/*对所有进程进行检测,唤醒任何一个已经得到信号的任务*/

       for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)

              if (*p) {

                     if ((*p)->alarm && (*p)->alarm < jiffies) {

                                   (*p)->signal |= (1<<(SIGALRM-1));

                                   (*p)->alarm = 0;

                            }

                     if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&

                     (*p)->state==TASK_INTERRUPTIBLE)

                            (*p)->state=TASK_RUNNING;

              }

/* this is the scheduler proper: */

/*根据进程的时间片和优先权来选择随后要执行的任务*/

       while (1) {

              c = -1;

              next = 0;

              i = NR_TASKS;

              p = &task[NR_TASKS];

              while (--i) {

                     if (!*--p)

                            continue;

                     if ((*p)->state == TASK_RUNNING && (*p)->counter > c)

                            c = (*p)->counter, next = i;

              }

              if (c) break;

              for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)

                     if (*p)

                            (*p)->counter = ((*p)->counter >> 1) +

                                          (*p)->priority;

       }

/*cpu切换到新进程执行*/

       switch_to(next);

}

       switch_to宏代码完成cpu切换任务的工作,这也是我们研究内存分段机制的重点,它的代码位于include/linux/sched.h中,代码如下:

#define switch_to(n) {/

struct {long a,b;} __tmp; /

__asm__("cmpl %%ecx,_current/n/t" /

       "je 1f/n/t" /

       "movw %%dx,%1/n/t" /

       "xchgl %%ecx,_current/n/t" /

       "ljmp %0/n/t" /  /*此处完成任务切换*/

       "cmpl %%ecx,_last_task_used_math/n/t" /

       "jne 1f/n/t" /

       "clts/n" /

       "1:" /

       ::"m" (*&__tmp.a),"m" (*&__tmp.b), /

       "d" (_TSS(n)),"c" ((long) task[n])); /

}

       任务切换的具体操作见下图:

            1:任务切换操作示意图(摘自Linux内核完全注释)

       接下来将通过调试验证切换过程,首先在System.map文件中找到schedul的地址:0x6b8c,启动bochsdgb,找到switch_to宏中ljmp指令的位置,命令行如下:

<bochs:1>  b 0x6b8c

<bochs:2> c

(0) Breakpoint 1, 0x6b8c in ?? ()

Next at t=16886214

(0) [0x00006b8c] 0008:00006b8c (unk. ctxt): push ebp                  ; 55

<bochs:3> u /100

……

00006c6b: (                    ): cmp dword ptr ds:0x1919c, ecx ; 390d9c910100

00006c71: (                    ): jz .+0x6c8a               ; 7417

00006c73: (                    ): mov word ptr ss:[ebp+0xfffffffc], dx ; 668955f

c

00006c77: (                    ): xchg dword ptr ds:0x1919c, ecx ; 870d9c910100

00006c7d: (                    ): jmp far ss:[ebp+0xfffffff8] ; ff6df8

00006c80: (                    ): cmp dword ptr ds:0x191a0, ecx ; 390da0910100

00006c86: (                    ): jnz .+0x6c8a              ; 7502

00006c88: (                    ): clts                      ; 0f06

……

<bochs:4> b 0x6c7d

<bochs:5> c

(0) Breakpoint 2, 0x6c7d in ?? ()

Next at t=16886886

(0)   [0x00006c7d] 0008:00006c7d (unk. ctxt): jmp far ss:[ebp+0xfffffff8] ; ff6df8

      

       0x00006c7d处的jmp far指令跳转的地址为段选择符:偏移值,其中段选择符为ss:[ebp+0xfffffff8]32位到47位,偏移值为ss:[ebp+0xfffffff8]0位到31位,如果段选择符为任务状态段选择符TSScpu将自动切换进程,此时cpu会把所有寄存器的状态保存到当前任务寄存器TR中的TSS段选择符所指向的当前任务数据结构的tss结构中,然后把新任务状态段选择符所指向的新任务数据结构中tss结构中的寄存器信息恢复到cpu中,系统就正式运行新切换的任务了。通过调试查看这一个过程,命令行如下:

<bochs:6> dump_cpu

……

ebp:0x1915c

……

eip:0x6c7d

……

ldtr:s=0x28, dl=0x84640068, dh=0x8201, valid=1

tr:s=0x20, dl=0x847c0068, dh=0x8901, valid=1

gdtr:base=0x5cb8, limit=0x7ff

idtr:base=0x54b8, limit=0x7ff

……

<bochs:7> print-stack

   00019148 [00019148]  0003

   0001914c [0001914c]  0000

   00019150 [00019150]  0ffc

   00019154 [00019154]  1f248

   00019158 [00019158]  0030

   0001915c [0001915c]  1f248

   00019160 [00019160]  6ca4

   00019164 [00019164]  743b

   00019168 [00019168]  0003

   0001916c [0001916c]  3e400

   00019170 [00019170]  1fae4

   00019174 [00019174]  0017

   00019178 [00019178]  0017

   0001917c [0001917c]  0017

   00019180 [00019180]  67a1

   00019184 [00019184]  000f

      

       %ebp寄存器的值为0x1915cljmp跳转的地址为0x30:0x1f248%gdtr的基址为0x5cb8,段描述符的地质为0x5cb8+0x30 = 0x5ce8,取出该描述符进行判断:

<bochs:8> x /2 0x5ce8

[bochs]:

0x00005ce8 <bogus+       0>:    0xf2e80068      0x000089ff

       这段调试信息告诉我们:这个描述符是一个基地址为0x00fff2e8,段限长为0x68的可用的386TSS描述符。因此cpu将自动进行进程切换。Cpu将取出从地址0x00fff2e8开始的0x68个字节内容对接下来要执行的进程tss的设置:

<bochs:9> x /26 0x00fff2e8

[bochs]:

0x00fff2e8 <bogus+       0>:    0x00000000      0x01000000      0x00000010

0x00000000

0x00fff2f8 <bogus+      16>:    0x00000000      0x00000000      0x00000000

0x00000000

0x00fff308 <bogus+      32>:    0x0000677c      0x00000616      0x00000000

0x0003e400

0x00fff318 <bogus+      48>:    0x00000021      0x00000003      0x0001f248

0x0001f248

0x00fff328 <bogus+      64>:    0x00000000      0x00000ffc      0x00000017

0x0000000f

0x00fff338 <bogus+      80>:    0x00000017      0x00000017      0x00000017

0x00000017

0x00fff348 <bogus+      96>:    0x00000038      0x80000000

       对这些调试信息按照tss字段的顺序排列得出下表:























BIT31—BIT16

BIT15—BIT1

BIT0

Offset

Data

0000000000000000

链接字段

0

0x00000000

ESP0

4

0x01000000

0000000000000000

SS0

8

0x00000010

ESP1

0CH

0x00000000

0000000000000000

SS1

10H

0x00000000

ESP2

14H

0x00000000

0000000000000000

SS2

18H

0x00000000

CR3

1CH

0x00000000

EIP

20H

0x0000677c

EFLAGS

24H

0x00000616

EAX

28H

0x00000000

ECX

2CH

0x0003e400

EDX

30H

0x00000021

EBX

34H

0x00000003

ESP

38H

0x0001f248

EBP

3CH

0x0001f248

ESI

40H

0x00000000

EDI

44H

0x00000ffc

0000000000000000

ES

48H

0x00000017

0000000000000000

CS

4CH

0x0000000f

0000000000000000

SS

50H

0x00000017

0000000000000000

DS

54H

0x00000017

0000000000000000

FS

58H

0x00000017

0000000000000000

GS

5CH

0x00000017

0000000000000000

LDTR

60H

0x00000038

 

 

 

 

 

 

I/O许可位图偏移

000000000000000

T

64H

0x80000000

      

       切换后的各个寄存器将按照以上表对应的值进行赋值,继续调试,来观察一下这个切换过程,命令行如下:

<bochs:10> n

Next at t=16886887

(0) [0x0000677c] 000f:0000677c (unk. ctxt): test eax, eax             ; 85c0

<bochs:11> dump_cpu

eax:0x0

ebx:0x3

ecx:0x3e400

edx:0x21

ebp:0x1f248

esi:0x0

edi:0xffc

esp:0x1f248

eflags:0x616

eip:0x677c

cs:s=0xf, dl=0x9f, dh=0x4c0fa00, valid=1

ss:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

ds:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

es:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

fs:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

gs:s=0x17, dl=0x9f, dh=0x4c0f300, valid=1

ldtr:s=0x38, dl=0xf2d00068, dh=0x82ff, valid=1

tr:s=0x30, dl=0xf2e80068, dh=0x89ff, valid=1

gdtr:base=0x5cb8, limit=0x7ff

idtr:base=0x54b8, limit=0x7ff

dr0:0x0

dr1:0x0

dr2:0x0

dr3:0x0

dr6:0xffff0ff0

dr7:0x400

tr3:0x0

tr4:0x0

tr5:0x0

tr6:0x0

tr7:0x0

cr0:0x8000001b

cr1:0x0

cr2:0x0

cr3:0x0

cr4:0x0

inhibit_mask:0

done

       这个切换过程也就一目了然了:6个段寄存器的低三位都为1,表明这个段描述符是局部描述符表的索引;局部描述符表的地址由全局描述符表提供,即0x5cb8 + 0x38地址处的描述符;%eip中的地址作为切换后的新进程的执行序列;通用寄存器的值从切换进程的tss结构中取得;ldtr的值由cpu自动加载,从tss结构中取得。

后记

       终于完成了内存分段机制的分析,对于内存分段机制的理解实际上可以看成怎么将一个二维数组映射成一位数组,如果需要通过进程的局部描述符表来寻址,则可以把分段机制看成怎么将一个三维数组映射成一位数组,就是这么简单!(如果说错了,不要怪我^-^

你可能感兴趣的:(c,linux,struct,null,byte,任务)