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 call10 movl $0x10,%edx # set up ds,es to kernel space11 mov %dx,%ds12 mov %dx,%es13 movl $0x17,%edx # fs points to local data space14 mov %dx,%fs15 call _sys_call_table(,%eax,4) #_sys_call_table + %eax*416 pushl %eax17 movl _current,%eax18 cmpl $0,state(%eax) # state19 jne reschedule20 cmpl $0,counter(%eax) # counter21 je reschedule22 ret_from_sys_call:23 movl _current,%eax # task[0] cannot have signals24 cmpl _task,%eax25 je 3f26 cmpw $0x0f,CS(%esp) # was old code segment supervisor ?27 jne 3f28 cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?29 jne 3f30 movl signal(%eax),%ebx31 movl blocked(%eax),%ecx32 notl %ecx33 andl %ebx,%ecx34 bsfl %ecx,%ecx35 je 3f36 btrl %ecx,%ebx37 movl %ebx,signal(%eax)38 incl %ecx39 pushl %ecx40 call _do_signal41 popl %eax42 3: popl %eax43 popl %ebx44 popl %ecx45 popl %edx46 pop %fs47 pop %es48 pop %ds49 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