main.c 流程

1.shedule初始化

(1)系统段的设置

a.首先设置TSS段描述符

 

seg limit:104 byte         

     

Type:32bit Tss DPL:0

G:0 无分页

base addr:由三个部分组成,这里指的是gdt表中tss开始的基地址

具体的内容见下面的介绍:

 

 

 1 struct tss_struct {
 2     long    back_link;    /* 16 high bits zero */
 3     long    esp0;
 4     long    ss0;        /* 16 high bits zero */
 5     long    esp1;
 6     long    ss1;        /* 16 high bits zero */
 7     long    esp2;
 8     long    ss2;        /* 16 high bits zero */
 9     long    cr3;
10     long    eip;
11     long    eflags;
12     long    eax,ecx,edx,ebx;
13     long    esp;
14     long    ebp;
15     long    esi;
16     long    edi;
17     long    es;        /* 16 high bits zero */
18     long    cs;        /* 16 high bits zero */
19     long    ss;        /* 16 high bits zero */
20     long    ds;        /* 16 high bits zero */
21     long    fs;        /* 16 high bits zero */
22     long    gs;        /* 16 high bits zero */
23     long    ldt;        /* 16 high bits zero */
24     long    trace_bitmap;    /* bits: trace 0, bitmap 16-31 */
25     struct i387_struct i387;
26 };
back_link:
Contains the segment selector for the TSS of the previous task (updated on a task
switch that was initiated by a call, interrupt, or exception). This field (which is sometimes called the back link
field) permits a task switch back to the previous task by using the IRET instruction.
设置为0;
esp0:权限等级为0时的esp,这里其设置的值为init_task+4K,还不清楚为什么要从init_task开始后的4K地址处
ss0:0x10
esp1~ss2:0
cr3:页目录开始的地址
eip~edi:0
es~gs:0x17 含义为选择LDT表中的第二个段描述符,即为以下cs段描述符:
0x9f,0xc0fa00 根据段描述符的定义,意思为段base:0 limit:640K G:页粒度为4K DPL(description privilege level):3
ldt:segment selector for ldt

b.设置LDT段描述符

seg limit:104 byte         

Type:LDT    DPL:0

G:0 无分页

base addr:由三个部分组成,这里指的是gdt表中ldt开始的基地址,具体表项内容见下:
    { \
        {0,0}, \
/* ldt */    {0x9f,0xc0fa00}, \               /* G=1 D=1 P=1 GPL=3 Type=cs base=0 limit=9f */
        {0x9f,0xc0f200}, \
    }
此处有三个段描述符
bit12置1表示为数据段或者代码段,段基址为0,limit:4K*160=640K
权限:
cs:执行、可读
ds:读写
系统段的设置大致可以用下图来表示,在设置完系统段描述符后,会执行ltr和lldt来手动加载其寄存器。这里会将tr和ldt的索引加载到寄存器的段选择符当中,
当段选择符被加载到寄存器后,cpu使用段选择符来定位描述符的位置,会接着加载描述符(ldt and tss desp)的段限制和基址到寄存器

 

这里GDT表中的前三个描述符分别是0-nul, 1-cs, 2-ds, 3-syscall,4-TSS0, 5-LDT0, 6-TSS1, ... 2n-TSS(n-2),2n+1-LDT(n-2)

2.切换成用户态
此处模拟中断去运行进程0
首先将ss,esp,cs,eip的初始化的值压入栈中,之后执行iret
 1 __asm__ ("movl %%esp,%%eax\n\t" \
 2     "pushl $0x17\n\t" \          
 3     "pushl %%eax\n\t" \          /* point to esp */
 4     "pushfl\n\t" \               /* point to eflag */
 5     "pushl $0x0f\n\t" \          /* point to cs */
 6     "pushl $1f\n\t" \            /* point to EIP */
 7     "iret\n" \
 8     "1:\tmovl $0x17,%%eax\n\t" \
 9     "movw %%ax,%%ds\n\t" \
10     "movw %%ax,%%es\n\t" \
11     "movw %%ax,%%fs\n\t" \
12     "movw %%ax,%%gs" \
13     :::"ax")

 注意:这里的段选择符的RPL=3,查LDT表

3.fork init进程(pid=1)

3.1在执行fork时这里会触发一个0x80的系统调用,首先说明下下系统调用:system_call

系统调用栈示意图:

 /*  Stack layout in 'ret_from_system_call':
 *
 *     0(%esp) - %eax
 *     4(%esp) - %ebx
 *     8(%esp) - %ecx
 *     C(%esp) - %edx
 *    10(%esp) - %fs
 *    14(%esp) - %es
 *    18(%esp) - %ds
 *    1C(%esp) - %eip
 *    20(%esp) - %cs
 *    24(%esp) - %eflags
 *    28(%esp) - %oldesp
 *    2C(%esp) - %oldss
 */

系统根据int n 把n作为索引查询IDT表,找到中断处理程序的地址:

(1)检查中断号有没有超过规定值,如果超过返回-1,如果没有往下执行

(2)将ds es fs edx ecx ebx压入栈(会在后续函数调用中用到),接着把ds es设置成内核空间(rpl=0, gdt, 640k), fs设置成本地数据空间(rpl=3, ldt, 16m)

    movl $0x10,%edx        # set up ds,es to kernel space
    mov %dx,%ds
    mov %dx,%es
    movl $0x17,%edx        # fs points to local data space
    mov %dx,%fs

(3)调用具体的中断处理函数,比如sys_fork

    call _sys_call_table(,%eax,4)  #_sys_call_table + %eax*4

(4)将eax压入栈,查看当前进程有没有运行(running),如果没有重新调度;查看当前时间片是否为0,0:重新调度;检查是否为初始化任务,是则返回;

判断cs是否为0xf(内核模式),判断栈段是否为用户态模式,如果都是则往下执行,否则返回;再往下就是信号处理(暂时略去,后面补充)

代码如下:

 1 _system_call:
 2     cmpl $nr_system_calls-1,%eax
 3     ja bad_sys_call           #mean: zf=0 or of=0
 4     push %ds                  #int 0x80 eflag cs & eip is pushed to stack
 5     push %es
 6     push %fs
 7     pushl %edx
 8     pushl %ecx        # push %ebx,%ecx,%edx as parameters
 9     pushl %ebx        # to the system call
10     movl $0x10,%edx        # set up ds,es to kernel space
11     mov %dx,%ds
12     mov %dx,%es
13     movl $0x17,%edx        # fs points to local data space
14     mov %dx,%fs
15     call _sys_call_table(,%eax,4)  #_sys_call_table + %eax*4
16     pushl %eax
17     movl _current,%eax
18     cmpl $0,state(%eax)        # state
19     jne reschedule
20     cmpl $0,counter(%eax)        # counter
21     je reschedule
22 ret_from_sys_call:
23     movl _current,%eax        # task[0] cannot have signals
24     cmpl _task,%eax
25     je 3f
26     cmpw $0x0f,CS(%esp)        # was old code segment supervisor ?
27     jne 3f
28     cmpw $0x17,OLDSS(%esp)        # was stack segment = 0x17 ?
29     jne 3f
30     movl signal(%eax),%ebx
31     movl blocked(%eax),%ecx
32     notl %ecx
33     andl %ebx,%ecx
34     bsfl %ecx,%ecx
35     je 3f
36     btrl %ecx,%ebx
37     movl %ebx,signal(%eax)
38     incl %ecx
39     pushl %ecx
40     call _do_signal
41     popl %eax
42 3:    popl %eax
43     popl %ebx
44     popl %ecx
45     popl %edx
46     pop %fs
47     pop %es
48     pop %ds
49     iret

(5)关于调度

首先将$ret_from_sys_call压入栈,其调用调度函数

3.2 sys_fork要做的事

1)找到一个空的进程,last_id这个全局变量表示的是进程id,个人理解是递增的,递增到最大后从1开始,如果task[i]为null,则返回i

2)将gs edi ebp eax压入栈,作为copy_process的入参

3)copy_process是fork的主体

首先获取一个空页来存储task_struct的任务信息;设置该任务的结构体信息;

拷贝内存:

code seg:

limit=640k base: nr * 0x4000000;     /* nr*64M */

data seg:

limit=16M base:上同

拷贝数据段的数据、设置LDT、TSS等

 

运行init

 

posted @ 2016-09-06 23:53  Xiaodigua  阅读(400)  评论(0编辑  收藏  举报