linux0.11 基于内核栈的进程切换

Linux-0.11代码学习

在linux-0.11中,进程的切换是基于TSS(Task struct segment,任务结构段),这种切换方式简单,但是耗时比较大。TSS中存储进程运行的一次快照,在需要切换进程时,将当前进程的状态在TSS中保存下来,并将目标TSS内容扣到CPU中,即完成了进程的切换。具体是将目标TSS在GDT表中的描述符赋值给TR寄存机即可。这里将基于TSS的进程切换方式更改为基于栈的切换方式,并在这里进行学习记录。
主要思想如下图所示,因此这里的核心就是对switch_to中实现的进程切换方式进行更改。
基于TSS的进程切换机制
基于栈的进程切换机制

这里首先对改写后的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)

信号位图具体可见信号位图

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值