冬天OS(七):第一个进程

--------------------------------------------------------

启动第一个进程

--------------------------------------------------------

进程是个复杂的东西,需要很多数据结构配合才能运作起一个进程来,主要涉及有 特权级、LDT、TSS、进程表...

 

一,特权级
显然,内核是运行在 0 特权级的,当内核交权给普通任务的时候,要为其设置特权级 3 的运行环境,我们并没有为进程分配独立的进程空间,而是将整个 4G 作为其运行环境,只不过其特权级为 3 而已!后面我们会看到,当中断调度程序切回内核态的时候,会发生有意思的栈的切换!

 

二,进程表
目前我们的进程表拥有两大成分:
1,堆栈数据结构
2,LDT 信息
这两大成分都很重要,前者在调度程序调度的时候保存前一个进程的状态,如果细分的话,堆栈数据结构还可以分为三个用处,第一个用处就是保存普通的寄存器信息,第二个用处用于支持中断响应框架,第三个用处在于引入 SP0 ,让 SP0 将前一个进程的 ss、esp 、eflags、cs、eip 保存在那个进程的进程表中,前提是 SP0 必须指向那个进程的进程表!后者 LDT 在于规划进程可以使用的进程代码空间,进程数据空间,且进程执行时的特权级!
 

三、TSS
使用 TSS 的地方都是因为低特权级和高特权级之间的切换,我们目前需要关心的是 TSS 中 SS0、SP0 ,SS0 和 SP0 用于堆栈切换,简单而言,当特权级从低到高时,在SS0、SP0 的位置保存低特权级下的堆栈信息,参数信息、返回地址信息,用的方法是拷贝,然后在高特权级下执行的代码使用由 SS0、SP0 指向的高特权级栈!等到由高特权级转移到低特权级时,高特权级下的代码恢复高特权级栈,然后将栈切换回为低特权级栈!这种切换对低特权级代码透明,之后低特权级下的视角就像正常使用低特权级栈!
在我们的任务切换机制中,SS0、SP0 核心的作用就是:进入任务切换模块时,保存被切换任务的 ss、esp 、eflags、cs、eip ,进入任务切换模块后,让 SS0、SP0 指向新任务的栈顶,用以下次切换时保存新任务的 ss、esp 、eflags、cs、eip 到新任务的进程表中!
 

 OK,以上四种数据结构都了解得差不多了,让我们开始启动第一个进程:

// ----------------------------
// <proc.h>
// Jack Zheng 11.28
// ----------------------------
#ifndef	_TINIX_PROC_H_
#define _TINIX_PROC_H_

typedef struct s_stackframe {	
	t_32	gs;		
	t_32	fs;		
	t_32	es;		
	t_32	ds;		
	t_32	edi;		
	t_32	esi;		
	t_32	ebp;		
	t_32	kernel_esp;	
	t_32	ebx;		
	t_32	edx;		
	t_32	ecx;		
	t_32	eax;		
	t_32	retaddr;	
	t_32	eip;		
	t_32	cs;		
	t_32	eflags;		
	t_32	esp;		
	t_32	ss;
}STACK_FRAME;


typedef struct s_proc {
	STACK_FRAME			regs;			/* process' registers saved in stack frame */

	t_16				ldt_sel;		/* selector in gdt giving ldt base and limit*/
	DESCRIPTOR			ldts[LDT_SIZE];		/* local descriptors for code and data */
								/* 2 is LDT_SIZE - avoid include protect.h */
	t_32				pid;			/* process id passed in from MM */
	char				p_name[16];		/* name of the process */
}PROCESS;


/* Number of tasks */
#define NR_TASKS	1

/* stacks of tasks */
#define STACK_SIZE_TESTA	0x8000
#define STACK_SIZE_TOTAL	STACK_SIZE_TESTA

#endif

——主要声明了进程表数据结构!

 

// ----------------------------
// <gloabl.h>
// Jack Zheng 11.27
// ----------------------------
#ifndef _TINIX_GLOBAL_H_
#define _TINIX_GLOBAL_H_

#ifdef	GLOBAL_VARIABLES_HERE
#undef 	EXTERN
#define	EXTERN
#endif

// gdt & idt
EXTERN	int		disp_pos;
EXTERN	t_8		gdt_ptr[6];
EXTERN	DESCRIPTOR	gdt[GDT_SIZE];
EXTERN	t_8		idt_ptr[6];
EXTERN	GATE		idt[IDT_SIZE];

EXTERN	TSS		tss;
EXTERN	PROCESS*	p_proc_ready;

extern	PROCESS		proc_table[];
extern	char		task_stack[];

#endif
// ----------------------------
// <global.c>
// Jack Zheng 11.28
// ----------------------------
#define GLOBAL_VARIABLES_HERE

#include "type.h"
#include "const.h"
#include "protect.h"
#include "proto.h"
#include "proc.h"
#include "global.h"

PUBLIC	PROCESS			proc_table[NR_TASKS];
PUBLIC	char			task_stack[STACK_SIZE_TOTAL];
// ...

——这两个文件定义了进程表数组和测试任务使用的堆栈!

// ----------------------------
// <protect.h>
// Jack Zheng 11.28
// ----------------------------
#ifndef	_TINIX_PROTECT_H_
#define	_TINIX_PROTECT_H_

/* 存储段描述符/系统段描述符 */
typedef struct s_descriptor		/* 共 8 个字节 */
{
	t_16	limit_low;		/* Limit */
	t_16	base_low;		/* Base */
	t_8	base_mid;		/* Base */
	t_8	attr1;			/* P(1) DPL(2) DT(1) TYPE(4) */
	t_8	limit_high_attr2;	/* G(1) D(1) 0(1) AVL(1) LimitHigh(4) */
	t_8	base_high;		/* Base */
}DESCRIPTOR;

/* 门描述符 */
typedef struct s_gate
{
	t_16	offset_low;	/* Offset Low */
	t_16	selector;	/* Selector */
	t_8	dcount;		/* 该字段只在调用门描述符中有效。
				如果在利用调用门调用子程序时引起特权级的转换和堆栈的改变,需要将外层堆栈中的参数复制到内层堆栈。
				该双字计数字段就是用于说明这种情况发生时,要复制的双字参数的数量。 */
	t_8	attr;		/* P(1) DPL(2) DT(1) TYPE(4) */
	t_16	offset_high;	/* Offset High */
}GATE;

typedef struct s_tss {
	t_32	backlink;
	t_32	esp0;		/* stack pointer to use during interrupt */
	t_32	ss0;		/*   "   segment  "  "    "        "     */
	t_32	esp1;
	t_32	ss1;
	t_32	esp2;
	t_32	ss2;
	t_32	cr3;
	t_32	eip;
	t_32	flags;
	t_32	eax;
	t_32	ecx;
	t_32	edx;
	t_32	ebx;
	t_32	esp;
	t_32	ebp;
	t_32	esi;
	t_32	edi;
	t_32	es;
	t_32	cs;
	t_32	ss;
	t_32	ds;
	t_32	fs;
	t_32	gs;
	t_32	ldt;
	t_16	trap;
	t_16	iobase;	/* I/O位图基址大于或等于TSS段界限,就表示没有I/O许可位图 */
	/*t_8	iomap[2];*/
}TSS;

/* GDT */
/* 描述符索引 */
#define	INDEX_DUMMY		0	// ┓
#define	INDEX_FLAT_C		1	// ┣ LOADER 里面已经确定了的.
#define	INDEX_FLAT_RW		2	// ┃
#define	INDEX_VIDEO		3	// ┛
#define	INDEX_TSS		4
#define	INDEX_LDT_FIRST		5
/* 选择子 */
#define	SELECTOR_DUMMY		   0		// ┓
#define	SELECTOR_FLAT_C		0x08		// ┣ LOADER 里面已经确定了的.
#define	SELECTOR_FLAT_RW	0x10		// ┃
#define	SELECTOR_VIDEO		(0x18+3)	// ┛<-- RPL=3
#define	SELECTOR_TSS		0x20		// TSS. 从外层跳到内存时 SS 和 ESP 的值从里面获得.
#define SELECTOR_LDT_FIRST	0x28

#define	SELECTOR_KERNEL_CS	SELECTOR_FLAT_C
#define	SELECTOR_KERNEL_DS	SELECTOR_FLAT_RW
#define	SELECTOR_KERNEL_GS	SELECTOR_VIDEO

/* 每个任务有一个单独的 LDT, 每个 LDT 中的描述符个数: */
#define LDT_SIZE		2

/* 描述符类型值说明 */
#define	DA_32			0x4000	/* 32 位段				*/
#define	DA_LIMIT_4K		0x8000	/* 段界限粒度为 4K 字节			*/
#define	DA_DPL0			0x00	/* DPL = 0				*/
#define	DA_DPL1			0x20	/* DPL = 1				*/
#define	DA_DPL2			0x40	/* DPL = 2				*/
#define	DA_DPL3			0x60	/* DPL = 3				*/
/* 存储段描述符类型值说明 */
#define	DA_DR			0x90	/* 存在的只读数据段类型值		*/
#define	DA_DRW			0x92	/* 存在的可读写数据段属性值		*/
#define	DA_DRWA			0x93	/* 存在的已访问可读写数据段类型值	*/
#define	DA_C			0x98	/* 存在的只执行代码段属性值		*/
#define	DA_CR			0x9A	/* 存在的可执行可读代码段属性值		*/
#define	DA_CCO			0x9C	/* 存在的只执行一致代码段属性值		*/
#define	DA_CCOR			0x9E	/* 存在的可执行可读一致代码段属性值	*/
/* 系统段描述符类型值说明 */
#define	DA_LDT			0x82	/* 局部描述符表段类型值			*/
#define	DA_TaskGate		0x85	/* 任务门类型值				*/
#define	DA_386TSS		0x89	/* 可用 386 任务状态段类型值		*/
#define	DA_386CGate		0x8C	/* 386 调用门类型值			*/
#define	DA_386IGate		0x8E	/* 386 中断门类型值			*/
#define	DA_386TGate		0x8F	/* 386 陷阱门类型值			*/

/* 选择子类型值说明 */
/* 其中, SA_ : Selector Attribute */
#define	SA_RPL_MASK	0xFFFC
#define	SA_RPL0		0
#define	SA_RPL1		1
#define	SA_RPL2		2
#define	SA_RPL3		3

#define	SA_TI_MASK	0xFFFB
#define	SA_TIG		0
#define	SA_TIL		4

/* 中断向量 */
#define	INT_VECTOR_DIVIDE		0x0
#define	INT_VECTOR_DEBUG		0x1
#define	INT_VECTOR_NMI			0x2
#define	INT_VECTOR_BREAKPOINT		0x3
#define	INT_VECTOR_OVERFLOW		0x4
#define	INT_VECTOR_BOUNDS		0x5
#define	INT_VECTOR_INVAL_OP		0x6
#define	INT_VECTOR_COPROC_NOT		0x7
#define	INT_VECTOR_DOUBLE_FAULT		0x8
#define	INT_VECTOR_COPROC_SEG		0x9
#define	INT_VECTOR_INVAL_TSS		0xA
#define	INT_VECTOR_SEG_NOT		0xB
#define	INT_VECTOR_STACK_FAULT		0xC
#define	INT_VECTOR_PROTECTION		0xD
#define	INT_VECTOR_PAGE_FAULT		0xE
#define	INT_VECTOR_COPROC_ERR		0x10

/* 中断向量 */
#define	INT_VECTOR_IRQ0			0x20
#define	INT_VECTOR_IRQ8			0x28


/* 宏 */
/* 线性地址 → 物理地址 */
#define vir2phys(seg_base, vir)	(t_32)(((t_32)seg_base) + (t_32)(vir))


#endif /* _TINIX_PROTECT_H_ */

——这个文件定义了操作 GDT 和 LDT 需要的信息!

; ----------------------------
; <sconst.inc>
; Jack Zheng 11.28
; ----------------------------
P_STACKBASE	equ	0
GSREG		equ	P_STACKBASE
FSREG		equ	GSREG		+ 4
ESREG		equ	FSREG		+ 4
DSREG		equ	ESREG		+ 4
EDIREG		equ	DSREG		+ 4
ESIREG		equ	EDIREG		+ 4
EBPREG		equ	ESIREG		+ 4
KERNELESPREG	equ	EBPREG		+ 4
EBXREG		equ	KERNELESPREG	+ 4
EDXREG		equ	EBXREG		+ 4
ECXREG		equ	EDXREG		+ 4
EAXREG		equ	ECXREG		+ 4
RETADR		equ	EAXREG		+ 4
EIPREG		equ	RETADR		+ 4
CSREG		equ	EIPREG		+ 4
EFLAGSREG	equ	CSREG		+ 4
ESPREG		equ	EFLAGSREG	+ 4
SSREG		equ	ESPREG		+ 4
P_STACKTOP	equ	SSREG		+ 4
P_LDT_SEL	equ	P_STACKTOP
P_LDT		equ	P_LDT_SEL	+ 4

TSS3_S_SP0	equ	4

; 以下选择子值必须与 protect.h 中保持一致!!!
SELECTOR_FLAT_C		equ		0x08		; LOADER 里面已经确定了的.
SELECTOR_TSS		equ		0x20		; TSS. 从外层跳到内存时 SS 和 ESP 的值从里面获得.
SELECTOR_KERNEL_CS	equ		SELECTOR_FLAT_C

——这个文件中的定义方便了进程表中的成员操作(主要是寄存器操作)!

——这个函数(入口)将使用我们上面介绍的所有机制,从这里进入测试进程执行!

 

// ----------------------------
// <main.c>
// Jack Zheng 11.28
// ----------------------------
#include "type.h"
#include "const.h"
#include "protect.h"
#include "proto.h"
#include "string.h"
#include "proc.h"
#include "global.h"

PUBLIC	int tinix_main()
{
	disp_str("-----\"tinix_main\" begins-----\n");
	
	// initial process table!
	PROCESS * p_proc = proc_table;
	p_proc->ldt_sel = SELECTOR_LDT_FIRST;

	memcpy(&p_proc->ldts[0], &gdt[SELECTOR_KERNEL_CS >> 3], sizeof(DESCRIPTOR));
	p_proc->ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5;
	
	memcpy(&p_proc->ldts[1], &gdt[SELECTOR_KERNEL_DS >> 3], sizeof(DESCRIPTOR));
	p_proc->ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5;

	// assign LDT selectors!
	p_proc->regs.cs = ((8 * 0) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
	p_proc->regs.ds = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
	p_proc->regs.es = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
	p_proc->regs.fs = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
	p_proc->regs.ss = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
	p_proc->regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK) | RPL_TASK;
	p_proc->regs.eip = (t_32)TestA;
	p_proc->regs.esp = (t_32) task_stack + STACK_SIZE_TOTAL;
	// set IF and IOPL '1'
	p_proc->regs.eflags = 0x1200;
	
	p_proc_ready = proc_table;
	restart();
	while(1){}
}

void TestA()
{
	int i = 0;
	while(1)
	{
		disp_str("A");
		disp_int(i++);
		disp_str(".");
		delay(1);
	}
}

 ——这个文件初始化进程表并定义了测试进程体!

 

——同时很重要的是,LDT、TSS 要加入 GDT 描述符之后才能使用! 

 

编译:

运行:

 

——OK,我们的进程看起来运行正常!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柔弱胜刚强.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值