Linux-0.11代码学习
在linux-0.11中,进程的切换是基于TSS(Task struct segment,任务结构段),这种切换方式简单,但是耗时比较大。TSS中存储进程运行的一次快照,在需要切换进程时,将当前进程的状态在TSS中保存下来,并将目标TSS内容扣到CPU中,即完成了进程的切换。具体是将目标TSS在GDT表中的描述符赋值给TR寄存机即可。这里将基于TSS的进程切换方式更改为基于栈的切换方式,并在这里进行学习记录。
主要思想如下图所示,因此这里的核心就是对switch_to中实现的进程切换方式进行更改。
这里首先对改写后的switch_to函数做注释
switch_to:
pushl %ebp
movl %esp,%ebp
pushl %ecx
pushl %ebx
pushl %eax
movl 8(%ebp),%ebx
cmpl %ebx,current //true 1
je 1f //if true jump
!切换PCB
movl %ebx,%eax
xchgl %eax,current
!rewrite tss pointer
movl tss,%ecx //tss=&(init_task.task.tss)
addl $4096,%ebx //进程PCB的位置加上4096字节(4kb 一页内存)找到当前内核栈的栈基址
movl %ebx,ESP0(%ecx) //define ESP0 4
!switch_to kernel stack
movl %esp,KERNEL_STACK(%eax) //eax current pcb
//Kernel_stack is the location in task_struct
movl 8(%ebp),%ebx //let ebx equal the location of next PCB in the stack
movl KERNEL_STACK(%ebx),%esp
!switch_to LDT
movl 12(%ebp),%ecx //let ebx equal the location of LDT(next) in the stack
lldt %cx
movl $0x17,%ecx
movl %ecx,%fs /*重新获取fs寄存器的值,这里由于fs寄存器段基址已经改变,
变为新的进程的基址,因此使用这两条指令,可以将fs寄存器指向新的进程的用户态内存*/
1://不需要切换进程,对现场进行恢复
popl %eax
popl %ebx
popl %ecx
popl %ebp
ret//返回调用位置
first_return_from_kernel: //这里对进程第一次被创建时候时候的状态进行处理
popl %edx
popl %edi
popl %esi
pop %gs
pop %fs
pop %es
pop %ds
iret //return user
switch_to函数的c语言原形为switch_to(pnext,LDT(next)),pnext表示指向下一进程PCB的指针
,在执行调用语句时,函数压栈顺序为pushl LDT(next), push pnext, push switch_to(函数返回地址)
,进入switch_to
后,switch_to先将ebp寄存器压栈保护,再将esp寄存器值赋给ebp以进行栈操作,此时ebp指向的位置为push ebp后的栈位置
,mov 8(%ebp), %ebx
指令将ebp
指针上移8个字节指向pushl pnext
执行之后的位置,即指向pnext
处,取出下一进程PCB。后续就是常规的汇编内容,比较容易理解。
EBP--基址指针寄存器,通常与SS寄存器联用,由于SP指针指向当前栈的偏移地址,无法通过
SP对栈内元素进行访问,因此将SP寄存器内容传送给EBP,再通过SS:EBP为基址在栈内访问数据
ESP--指向当前内核栈的偏移地址
ecx、ebx、eax--函数调用传进的参数
由于在进程第一次被创建时的进程调度涉及到一些栈的操作,因此需要对fork()函数进行更改,使进程在创建时候栈内有东西可以弹出。
//将子进程内核栈与用户栈进行绑定
krnstack = (long *) (PAGE_SIZE+(long)p);
*(--krnstack) = ss&0xffff;
*(--krnstack) = esp;
*(--krnstack) = eflags;
*(--krnstack) = cs&0xffff;
*(--krnstack) = eip;
//为保证进程第一次被switch_to时候有东西可以弹出,在栈内填充数据
*(--krnstack) = ds & 0xffff;
*(--krnstack) = es & 0xffff;
*(--krnstack) = fs & 0xffff;
*(--krnstack) = gs & 0xffff;
*(--krnstack) = esi;
*(--krnstack) = edi;
*(--krnstack) = edx;
*(--krnstack) = (long) &first_return_from_kernel;
*(--krnstack) = ebp;
*(--krnstack) = ecx;
*(--krnstack) = ebx;
*(--krnstack) = 0;
p->kernelstack=krnstack; //保存当前栈顶
对代码编译运行之后,在系统启动时候会卡在进入界面一直在闪,怀疑是无法进入用户态的问题。
对问题进行排查,发现问题出在对task_struct的更改中。
上述在task_struct中添加了一个指向内核栈位置的变量,
struct task_struct {
/* these are hardcoded - don't touch */
long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
//新增kernelstack
long kernelstack;
long signal;
struct sigaction sigaction[32];
//......
这里signal(信号位图)的位置变化了,因此在文件中要把下述变量从12改为16.修改完成后正常运行。
state = 0 # these are offsets into the task-struct.
counter = 4
priority = 8
signal = 16
sigaction = 20 # MUST be 16 (=len of sigaction)
blocked = (33*16)
信号位图具体可见信号位图。