--------------------------------------------------------
启动第一个进程
--------------------------------------------------------
进程是个复杂的东西,需要很多数据结构配合才能运作起一个进程来,主要涉及有 特权级、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,我们的进程看起来运行正常!