main.c/move_to_user_mode()分析

main.c/move_to_mode()

在做李治军操作系统实验课的时候,在进程运行轨迹与统计部分,有提到使用move_to_mode()函数,但是当时的实验并没有涉及到这个函数的具体内容,于是自己找了一些资料,捋了一下。

1.进程0的创建
move_to_user_mode()实现从内核模式切换到用户模式,执行进程0.
进程0则在经过sti()之前的语句,已经创建完毕。
在kernel/sched.c中:

static union task_union init_task = {INIT_TASK,};
//union task_union {
//	struct task_struct task;
//	char stack[PAGE_SIZE];
//};

其中INIT_TASK为宏定义,在include/linux/sched.h:

#define INIT_TASK \
/* state etc */	{ 0,15,150, \
/* 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}, \
		{0x9f,0xc0f200}, \
	}, \
/*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, \
		{} \
	}, \
}

这里我们看ldt的三个值:

{ \
		{0,0}, \
/* ldt */	{0x9f,0xc0fa00}, \
		{0x9f,0xc0f200}, \
	}, \

ldt[0]是一个空描述符;ldt[1]是进程0的代码段描述符,ldt[2]是进程的数据段描述符。
段描述符的格式为:
在这里插入图片描述
关于段描述符的分析:段描述符分析

ldt[1]的值为0x0000 009f,0x00c0 fa00,其中第一个数在低32位,第二个数在高32位:
0x00c0 fa00
0x0000 009f
根据上图,可以知道段基地址为0,特权级DPL为3,

再查看gdt的代码段描述符号:

gdt:	.quad 0x0000000000000000	/* NULL descriptor */
	.quad 0x00c09a0000000fff	/* 16Mb */
	.quad 0x00c0920000000fff	/* 16Mb */
	.quad 0x0000000000000000	/* TEMPORARY - don't use */
	.fill 252,8,0			/* space for LDT's and TSS's etc */

第二项为代码段描述符:
0x00c0 9a00
0x0000 0fff
其基址也为0,DPL为1,所以进程0的代码段基址与内核段基址是相同的。

具体分析:进程0的LDT0分析

tss的值:

/*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, \
		{} \
	}

其中tss_struct的定义为:

struct tss_struct {
	long	back_link;	/* 16 high bits zero */
	long	esp0;
	long	ss0;		/* 16 high bits zero */
	long	esp1;
	long	ss1;		/* 16 high bits zero */
	long	esp2;
	long	ss2;		/* 16 high bits zero */
	long	cr3;
	long	eip;
	long	eflags;
	long	eax,ecx,edx,ebx;
	long	esp;
	long	ebp;
	long	esi;
	long	edi;
	long	es;		/* 16 high bits zero */
	long	cs;		/* 16 high bits zero */
	long	ss;		/* 16 high bits zero */
	long	ds;		/* 16 high bits zero */
	long	fs;		/* 16 high bits zero */
	long	gs;		/* 16 high bits zero */
	long	ldt;		/* 16 high bits zero */
	long	trace_bitmap;	/* bits: trace 0, bitmap 16-31 */
	struct i387_struct i387;
};

第二项PAGE_SIZE+(long)&init_taskinit_task联合体的最后一个字节,这是tss_struct中0特权esp的值。

每个task_union都是4096字节(一页)大小的。而前104个字节是一个task_struct结构,其布局大致是:
|---------------------------------------4096个字节----------------------------------------------------|
|-------task_struct----------|----------------------剩余未用-----------------------------------------|

esp0指向这个结构的最后一个字节,作为内核栈的栈顶。

为什么会有这么多的剩余未用空间呢?如果这部分是闲置不用的。对于追求最高效利用内存资源的OS来说,不太可能如此浪费资源。其实这个剩余未用部分,就作为一个个进程的0特权栈来使用。我们都晓得,进程在运行期间很有可能使用调用system call函数的,而一调用system call函数,CPL就会从原来的3特权,翻转到0特权。而翻转特权以后,原来3特权的栈将不得被0特权的内核使用,于是Intel要求每个进程在创建之初都得指定一个0特权的栈,用于将来进程进入0特权时使用。
并且让task[0]指向这个task_struct。
在sched.c中:

struct task_struct * task[NR_TASKS] = {&(init_task.task), };

进程0的东西准备完毕,就需要将进程的特权级由0转移到3,因为操作系统规定进程必须都运行在3特权上。

接下来就是move_to_user_mode()的工作了。

2.move_to_user_mode()
move_to_user_mode()是一个宏定义,定义在include/asm/system.h中:

#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" \
	"pushl $1f\n\t" \
	"iret\n" \
	"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")

该函数利用iret模拟中断返回,继续执行进程0.

iret前面的指令,模拟int中断过程中需要一次将ss,esp,eflag,cs,eip压栈的过程:

movl %%esp,%%eax将esp的值赋给eax,esp是当前栈的栈顶。当前栈为那个大小为一个页的user_stack的栈。
pushl $0x17将0x17压栈。
pushl %%eax将刚才保存的esp内容压栈。
pushfl 将eflag压栈。
pushl $0x0f将0x0f压栈。
pushl $1f将指令为1f的偏移压栈。

最后栈中的内容为:
在这里插入图片描述
通过iret弹出以后,cs为0x0f,这是一个段选择子,0x0f = 0x0000 1111,表示以DPL=3去选择LDT中的第2项,这一项正是前面INIT_TASK中ldt的第二项,即进程0的代码段。
ss为0x17 = 0x 0001 0111,表示以DPL=3去选择LDT中的第三项,也就是INIT_TASK中ldt的第三项。

iret之后,将会执行1f偏移的指令:

    movl $0x17,%%eax
	movw %%ax,%%ds
	movw %%ax,%%es
	movw %%ax,%%fs
	movw %%ax,%%gs

将ds,es,fs,gs对齐到0x17选择子上。

自此,特权级就从0转为3了,开始执行 fork()创建进程1。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值