本章在内核线程的基础上实现用户进程,主要区别是内核线程运行在特权级别3下,用户进程运行在特权级别0下。
Intel原生为CPU提供的多任务机制主要是LDT/TSS,关于LDT/TSS简要阐述一下,现代操作系统与本书实现的操作系统均未实现该机制。
LDT
Intel官方提出LDT的目的就是为了把每个任务自己的代码、数据、栈段这些私有资源用单独的一个结构来存储,避免多任务运行时各个程序的资源混乱。但书中采用虚拟内存(平坦模型)+二级页表的机制已经天然的将各个任务的资源隔绝开了,不存在互相访问导致混乱的问题。所以LDT感觉没什么用,多此一举。
1、LDT是什么?
按照内存分段的方式,内存中的程序映像自然被分成了代码段、数据段等资源,这些资源属于程序私有的部分,因此Intel建议,为每个程序单独赋予一个结构来存储其私有的资源,这个结构就是LDT。
2、怎么找到每个任务自己的LDT?
LDT表以GDT表选择子的方式注册到了GDT表中,GDTR寄存器中存储了GDT表的基础与偏移量,通过GDTR寄存器找到GDT表,通过段选择子找到对应的GDT表项,GDT表项对应的内存位置便是LDT表
3、怎么使用LDT表?
(1)通过执行lldt [ldt表的段选择子]指令,可以将ldt加载到LDTR寄存器中。
(2)段选择子16位,高13位为索引值,0~1位为RPL,第二位是TI位,TI=1表示用LDT表检索段选择子,TI=0表示用GDT表检索段选择子。
TSS
1、TSS的作用
TSS个人认为还是非常有用的,其主要作用就是存储各个任务被换下CPU时的上下文环境(具体来说就是:8个通用寄存器,6个段落寄存器,指令指针eip,栈指针寄存器esp,页表寄存器cr3和标志寄存器eflags),不过Linux后来并没采用TSS存储处理器上下文环境,而是所有任务共用一个TSS,切换任务时仅切换TSS的ss0和esp0。这样做的原因是因为切换TSS时步骤繁琐效率低,且TSS需要注册到GDT中使用,每次新建一个任务都要注册一个TSS,频繁修改GDT效率也很低。基于这些原因,TSS切换的机制废弃了。
2、如何找到TSS
(1)构造好TSS后,需要将TSS结构以GDT表选择子的方式注册到GDT表中
(2)ltr [TSS结构的段选择子]指令,可以将TSS加载到寄存器TR中
之后CPU便可以通过TR寄存器找到TSS了。
3、TSS相关结构
GDT中TSS描述符示意图:
TSS结构图:
铺垫知识阐述完了,下面正式开始编码,其实LDT没啥用,只是书读了顺带记录下。有点用的是TSS,Intel原本打算让程序员为每个任务提供一个TSS保存该任务切换时CPU的上下文环境,但是后来Linux实现的时候只构造了一个TSS,但是每次任务切换时还是需要保存当前任务的CPU上下文环境,而且还需要将切换后任务的CPU上下文环境恢复,那么每个任务的CPU上下文环境保存在哪里呢?其实就是保存在TSS中0级栈指针SS0和ESP0所指向的栈中。
一、定义并初始化TSS(11.2节)
该节主要工作分两块
1、在global.h中用宏定义用于TSS段、用户态的数据段、代码段的GDT表项的相关属性,以及这三个段的选择子。
2、在userprog/tss.c中定义TSS结构体,实现用于更新TSS中esp0的函数,实现构造GDT表项的函数,实现tss_init函数。tss_init的主要工作就是构造TSS段、用户态的数据段、代码段的GDT表项并将其安装到GDT表中,最后用内联汇编重新加载GDTR寄存器以及TR寄存器,使安装的GDT表项以及TSS结构生效。
global.h如下:
#ifndef __KERNEL_GLOBAL_H
#define __KERNEL_GLOBAL_H
#include "stdint.h"
#define PG_SIZE 4096
#define RPL0 0
#define RPL1 1
#define RPL2 2
#define RPL3 3
#define TI_GDT 0
#define TI_LDT 1
//-------------- IDT描述符属性 ------------
#define IDT_DESC_P 1
#define IDT_DESC_DPL0 0
#define IDT_DESC_DPL3 3
#define IDT_DESC_32_TYPE 0xE // 32位的门
#define IDT_DESC_16_TYPE 0x6 // 16位的门,不用,定义它只为和32位门区分
#define IDT_DESC_ATTR_DPL0 ((IDT_DESC_P << 7) + (IDT_DESC_DPL0 << 5) + IDT_DESC_32_TYPE)
#define IDT_DESC_ATTR_DPL3 ((IDT_DESC_P << 7) + (IDT_DESC_DPL3 << 5) + IDT_DESC_32_TYPE)
// ---------------- GDT描述符属性 ----------------
#define DESC_G_4K 1
#define DESC_G_1B 0
#define DESC_D_32 1
#define DESC_L 0 // 64位代码标记,此处标记为0便可。
#define DESC_AVL 0 // cpu不用此位,暂置为0
#define DESC_P 1
#define DESC_DPL_0 0
#define DESC_DPL_1 1
#define DESC_DPL_2 2
#define DESC_DPL_3 3
/*
代码段和数据段属于存储段,tss和各种门描述符属于系统段
s为1时表示存储段,为0时表示系统段.
*/
#define DESC_S_CODE 1
#define DESC_S_DATA DESC_S_CODE
#define DESC_S_SYS 0
#define DESC_TYPE_CODE 8 // x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0.
#define DESC_TYPE_DATA 2 // x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0.
#define DESC_TYPE_TSS 9 // B位为0,不忙
#define SELECTOR_K_CODE ((1 << 3) + (TI_GDT << 2) + RPL0)
#define SELECTOR_K_DATA ((2 << 3) + (TI_GDT << 2) + RPL0)
#define SELECTOR_K_STACK SELECTOR_K_DATA
#define SELECTOR_K_GS ((3 << 3) + (TI_GDT << 2) + RPL0)
/* 第3个段描述符是显存,第4个是tss */
#define SELECTOR_U_CODE ((5 << 3) + (TI_GDT << 2) + RPL3) //用户代码段选择子,位于GDT表第5项,TI_GDT表示在GDT表中索引,RPL3表示用户特权级3
#define SELECTOR_U_DATA ((6 << 3) + (TI_GDT << 2) + RPL3)
#define SELECTOR_U_STACK SELECTOR_U_DATA
#define GDT_ATTR_HIGH ((DESC_G_4K << 7) + (DESC_D_32 << 6) + (DESC_L << 5) + (DESC_AVL << 4))
#define GDT_CODE_ATTR_LOW_DPL3 ((DESC_P << 7) + (DESC_DPL_3 << 5) + (DESC_S_CODE << 4) + DESC_TYPE_CODE)
#define GDT_DATA_ATTR_LOW_DPL3 ((DESC_P << 7) + (DESC_DPL_3 << 5) + (DESC_S_DATA << 4) + DESC_TYPE_DATA)
//--------------- TSS描述符属性 ------------
#define TSS_DESC_D 0
#define TSS_ATTR_HIGH ((DESC_G_4K << 7) + (TSS_DESC_D << 6) + (DESC_L << 5) + (DESC_AVL << 4) + 0x0)
#define TSS_ATTR_LOW ((DESC_P << 7) + (DESC_DPL_0 << 5) + (DESC_S_SYS << 4) + DESC_TYPE_TSS)
#define SELECTOR_TSS ((4 << 3) + (TI_GDT << 2 ) + RPL0)
struct gdt_desc {
uint16_t limit_low_word;
uint16_t base_low_word;
uint8_t base_mid_byte;
uint8_t attr_low_byte;
uint8_t limit_high_attr_high;
uint8_t base_high_byte;
};
//--------------- 常用数学宏 ------------
#define DIV_ROUND_UP(X, STEP) ((X + STEP - 1) / (STEP))
//--------------- EFLASG寄存器 ------------
#define EFLAGS_MBS (1 << 1)
#define EFLAGS_IF_1 (1 << 9)
#define EFLAGS_IF_0 0
#define EFLAGS_IOPL_3 (3 << 12) //IOPL3,用于测试用户程序在非系统调用下进行IO
#define EFLAGS_IOPL_0 (0 << 12) //IOPL0
#endif
userprog/tss.c如下
#include "tss.h"
#include "thread.h"
#include "stdint.h"
#include "global.h"
#include "string.h"
#include "print.h"
#define PG_SIZE 4096
/* TSS结构体 */
struct tss {
uint32_t backlink;
uint32_t* esp0;
uint32_t ss0;
uint32_t* esp1;
uint32_t ss1;
uint32_t* esp2;
uint32_t ss2;
uint32_t cr3;
uint32_t (*eip) (void);
uint32_t eflags;
uint32_t eax;
uint32_t ecx;
uint32_t edx;
uint32_t ebx;
uint32_t esp;
uint32_t ebp;
uint32_t esi;
uint32_t edi;
uint32_t es;
uint32_t cs;
uint32_t ss;
uint32_t ds;
uint32_t fs;
uint32_t gs;
uint32_t ldt;
uint32_t trace;
uint32_t io_base;
};
static struct tss tss;
/* 更新TSS中0级栈指针 */
void update_tss_esp(struct task_struct* thread) {
tss.esp0 = (uint32_t*) ((uint32_t)thread + PG_SIZE);
}
/* C函数构造GDT描述符 */
static struct gdt_desc make_gdt_desc(uint32_t* desc_addr, uint32_t limit, uint8_t attr_low, uint8_t attr_high) {
uint32_t desc_base = (uint32_t) desc_addr;
struct gdt_desc desc;
desc.limit_low_word = limit & 0x0000ffff;
desc.base_low_word = desc_base & 0x0000ffff;
desc.base_mid_byte = ((desc_base & 0x00ff0000) >> 16);
desc.attr_low_byte = (uint8_t) (attr_low);
desc.limit_high_attr_high = ((uint8_t) (attr_high) + ((limit & 0x000f0000) >> 16));
desc.base_high_byte = desc_base >> 24;
return desc;
}
/* 1、初始化TSS
* 2、任务共用的TSS、用户代码段、数据段选择子构造并安装到GDT表下标4、5、6处
* 3、重新加载改变的GDT表到GDTR寄存器
* */
void tss_init() {
put_str("tss_init start\n");
/* 初始化TSS */
memset(&tss, 0, sizeof(tss));
tss.ss0 = SELECTOR_K_STACK;
tss.io_base = sizeof(tss);
/* 构造TSS的GDT项,用户态代码段的GDT项,用户态数据段的GDT项 */
struct gdt_desc tss_desc = make_gdt_desc((uint32_t*) &tss, sizeof(tss) - 1, TSS_ATTR_LOW, TSS_ATTR_HIGH);
struct gdt_desc user_code_desc = make_gdt_desc((uint32_t*) 0, 0xfffff, GDT_CODE_ATTR_LOW_DPL3, GDT_ATTR_HIGH);
struct gdt_desc user_data_desc = make_gdt_desc((uint32_t*) 0, 0xfffff, GDT_DATA_ATTR_LOW_DPL3, GDT_ATTR_HIGH);
/* 安装上面3个GDT表项在GDT表中的位置4、5、6 */
*((struct gdt_desc*) (0xc0000920)) = tss_desc;
*((struct gdt_desc*) (0xc0000928)) = user_code_desc;
*((struct gdt_desc*) (0xc0000930)) = user_data_desc;
/* 重新加载改变后的GDT表到GDTR寄存器,加载TSS选择子到TR寄存器 */
/* gdt表基址在0x900位置处,偏移量等于7个表项大小(包含位置0的哑描述符)-1 */
/* 32位指针只能转换成32位整型,不能直接转换成64位整型 */
uint64_t gdt_operand = ((((uint64_t) (uint32_t) (0xc0000900)) << 16) | (8 * 7 - 1));
asm volatile("lgdt %0"::"m"(gdt_operand));
asm volatile("ltr %w0"::"r"(SELECTOR_TSS));
put_str("tss_init done\n");
}
二、实现用户进程前置工作(对应11.3.1~11.3.3节)
(1)thread.h的pcb结构体中添加虚拟地址池字段userprog_vaddr以及页表虚拟地址字段pgdir,进程与线程的区别就是线程拥有资源,所谓资源就是独立的地址空间。所以进程相比线程pcb需要管理自己的4GB虚拟内存,虚拟内存需要访问依靠页表映射到真实物理内存上,故还增加了页表的虚拟地址字段。
/* 进程或线程的pcb,程序控制块 */
struct task_struct {
uint32_t* self_kstack;
enum task_status status;
uint32_t priority;
char name[16];
uint32_t ticks;
uint32_t elapsed_ticks;
struct list_elem thread_ready_list_tag;
struct list_elem thread_all_list_tag;
//新增以下两个字段,也是进程与线程的区别
uint32_t* pgdir;
struct virtual_addr userprog_vaddr;
uint32_t stack_magic;
};
(2)修改memory.c
1、在物理内存池结构struct pool中增加lock结构,防止并发操作下的混乱情况。
2、在vaddr_get函数中增加了在用户进程虚拟内存池中申请虚拟内存的功能
3、新增了get_user_pages函数其功能是在用户物理内存池空间申请物理内存,在用户进程虚拟内存池中申请虚拟内存,返回虚拟内存起始地址
4、新增了通过虚拟地址得到物理地址的方法,将来更新页目录表寄存器cr3时使用
5、在mem_pool_init中对两个共用的用户、内核物理内存池的锁结构lock进行初始化
#include "memory.h"
#include "string.h"
#include "bitmap.h"
#include "debug.h"
#include "print.h"
#include "global.h"
#include "sync.h"
#define PG_SIZE 4096
#define MEM_BITMAP_BASE 0xc009a000
#define K_HEAP_START 0xc0100000
#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)
struct pool {
struct bitmap pool_bitmap;
uint32_t phy_addr_start;
uint32_t pool_size;
struct lock lock;
};
struct pool kernel_pool, user_pool;
struct virtual_addr kernel_vaddr;
void mem_pool_init(uint32_t all_mem) {
put_str(" mem_pool_init start\n");
/* 实际物理内存的使用情况 */
/* 页表大小 */
uint32_t page_table_size = PG_SIZE * 256;
/* 已经使用的内存,从内存起始位置0开始算起,低端1mb被操作系统和bios占用
* 1mb往上的位置由页表占用,先是4KB大小的页目录表,然后是一个存在的页表(0-4mb,3G-3G+4,b)
* 然后是769~1023个页目录项保留的页表(254*4KB)
* */
uint32_t used_mem = 0x100000 + page_table_size;
uint32_t free_mem = all_mem - used_mem;
uint32_t all_free_pages = free_mem / PG_SIZE;
/* 将可用的物理内存均分给用户内存池和内核内存池 */
uint32_t kernel_free_pages = all_free_pages / 2;
uint32_t user_free_pages = all_free_pages - kernel_free_pages;
kernel_pool.phy_addr_start = used_mem;
kernel_pool.pool_size = kernel_free_pages * PG_SIZE;
kernel_pool.pool_bitmap.bits = (uint8_t*) MEM_BITMAP_BASE;
kernel_pool.pool_bitmap.btmp_bytes_len = kernel_free_pages / 8;
lock_init(&kernel_pool.lock);
user_pool.phy_addr_start = used_mem + kernel_pool.pool_size;
user_pool.pool_size = user_free_pages * PG_SIZE;
user_pool.pool_bitmap.bits = (uint8_t*) MEM_BITMAP_BASE + kernel_pool.pool_bitmap.btmp_bytes_len;
user_pool.pool_bitmap.btmp_bytes_len = user_free_pages / 8;
lock_init(&user_pool.lock);
/* 输出内存池信息 */
/* 内核内存池信息 */
put_str(" kernel_pool_bitmap_start:");
put_int((int) kernel_pool.pool_bitmap.bits);
put_str(" kernel_pool_phy_addr_start:");
put_int(kernel_pool.phy_addr_start);
put_str("\n");
/* 用户内存池信息 */
put_str(" user_pool_bitmap_start:");
put_int((int) user_pool.pool_bitmap.bits);
put_str(" user_pool_phy_addr_start:");
put_int(user_pool.phy_addr_start);
put_str("\n");
/* 位图初始化为0 */
bitmap_init(&kernel_pool.pool_bitmap);
bitmap_init(&user_pool.pool_bitmap);
/* 初始化内核虚拟地址位图,按照实际物理大小生成数组 */
kernel_vaddr.vaddr_bitmap.bits = MEM_BITMAP_BASE + kernel_pool.pool_bitmap.btmp_bytes_len + user_pool.pool_bitmap.btmp_bytes_len;
kernel_vaddr.vaddr_start = K_HEAP_START;
kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kernel_pool.pool_bitmap.btmp_bytes_len;
bitmap_init(&kernel_vaddr.vaddr_bitmap);
put_str(" mem_pool_init done\n");
}
void mem_init() {
put_str("mem_init start\n");
uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
mem_pool_init(mem_bytes_total);
put_str("mem_init done\n");
}
/*
* 在pf表示的虚拟内存池中申请pg_cnt个虚拟页
* 成功返回虚拟页的起始地址,失败返回NULL
* */
static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) {
uint32_t start_addr = 0, bit_idx_start = -1;
uint32_t cnt = 0;
if(pf == PF_KERNEL) {
bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
if(bit_idx_start == -1) {
return NULL;
}
while (cnt < pg_cnt) {
bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
}
start_addr = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
} else {
/* 如果是分配用户进程的虚拟内存,则虚拟内存池从当前用户的PCB中取 */
struct task_struct* cur = running_thread();
bit_idx_start = bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap, pg_cnt);
if(bit_idx_start == -1) {
return NULL;
}
while (cnt < pg_cnt) {
bitmap_set(&cur->userprog_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
}
start_addr = cur->userprog_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
ASSERT((uint32_t) vaddr_start < (0xc0000000 - PG_SIZE));
}
return (void*) start_addr;
}
/* 得到虚拟地址vaddr对应的pte指针 */
uint32_t* pte_ptr(uint32_t vaddr) {
uint32_t* pte = (uint32_t*) (0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);
return pte;
}
/* 得到虚拟地址vaddr对应的pde指针 */
uint32_t* pde_ptr(uint32_t vaddr) {
uint32_t* pde = (uint32_t*) (0xfffff000 + PDE_IDX(vaddr) * 4);
return pde;
}
/* 在m_pool指向的物理内存池中分配1个物理页
* 成功返回该页的物理地址,否则返回NULL
* */
static void* palloc(struct pool* m_pool) {
int idx_bit_start = bitmap_scan(&m_pool->pool_bitmap, 1);
if(idx_bit_start == -1) {
return NULL;
}
bitmap_set(&m_pool->pool_bitmap, idx_bit_start, 1);
void* phy_addr = m_pool->phy_addr_start + idx_bit_start * PG_SIZE;
return phy_addr;
}
/*
* 页表中添加虚拟地址_vaddr与物理地址_page_phyaddr的映射
* */
static void page_table_add(void* _vaddr, void* _page_phyaddr) {
uint32_t vaddr = (uint32_t) _vaddr;
uint32_t page_phyaddr = (uint32_t) _page_phyaddr;
uint32_t* pde = pde_ptr(vaddr);
uint32_t* pte = pte_ptr(vaddr);
/*1 判断vaddr对应的PDE是否存在*/
if(*pde & 0x00000001) {
/* 存在,判断vaddr对应的PTE是否存在(不应该存在,此处是对新分配的虚拟页和物理页做映射) */
ASSERT(!(*pte & 0x00000001));
if(!(*pte & 0x00000001)) {
*pte = page_phyaddr | PG_US_U | PG_RW_W | PG_P_1;
} else {
put_str("\n!!!!!!!!!!!!!!!!!!!!!!! error pte repeat !!!!!!!!!!!!!!!!!!!!!!\n");
while(1);
}
} else {
/* 不存在,创建vaddr对应的页表 */
uint32_t pt_phyaddr = (uint32_t) palloc(&kernel_pool);
/* 将页表物理地址写入对应的PDE */
*pde = pt_phyaddr | PG_US_U | PG_RW_W | PG_P_1;
/* 初始化将页表清0 */
memset((void*) ((uint32_t)pte & 0xfffff000), 0, PG_SIZE);
/* 将page_phyaddr写入对应的PTE */
ASSERT(!(*pte & 0x00000001));
*pte = page_phyaddr | PG_US_U | PG_RW_W | PG_P_1;
}
}
/* 分配pg_cnt个页空间,成功返回起始虚拟地址,失败时返回NULL */
void* malloc_page(enum pool_flags pf, uint32_t pg_cnt) {
ASSERT(pg_cnt > 0 && pg_cnt < 3840);
/* 1 通过vaddr_get申请连续的cnt页虚拟地址 */
uint32_t vaddr = (int) vaddr_get(pf, pg_cnt);
if(vaddr == NULL) {
return NULL;
}
uint32_t cnt = 0;
struct pool* mem_pool = (pf == PF_KERNEL) ? &kernel_pool : &user_pool;
while(cnt < pg_cnt) {
/* 2 通过palloc申请单独一页的物理内存地址 */
uint32_t phyaddr = palloc(mem_pool);
if(phyaddr == NULL) {
/* 此处省略了回滚操作,后续需要添加 */
return NULL;
}
/* 3 通过page_table_add将虚拟地址与物理地址建立页表映射,循环pg_cnt次 */
page_table_add((void*)(vaddr + cnt * PG_SIZE), (void*) phyaddr);
cnt++;
}
return (void*) vaddr;
}
/* 从内核物理内存池中申请pg_cnt页内存
* 成功返回其虚拟地址
* 失败返回NULL
* */
void* get_kernel_pages(uint32_t pg_cnt) {
void* vaddr = malloc_page(PF_KERNEL, pg_cnt);
if(vaddr != NULL) {
memset(vaddr, 0, pg_cnt * PG_SIZE);
}
return vaddr;
}
/* 用户态申请4K内存,并返回其虚拟地址 */
void* get_user_pages(uint32_t pg_cnt) {
lock_acquire(&user_pool.lock);
void* vaddr_start = malloc_page(PF_USER, pg_cnt);
memset(vaddr_start, 0, pg_cnt * PG_SIZE);
lock_release(&user_pool.lock);
return vaddr_start;
}
/* 将地址vaddr与pf池中的物理地址关联,仅支持一页内存分配 */
void* get_a_page(enum pool_flags pf, uint32_t vaddr) {
/* 判断是内核线程申请内存还是用户进程申请内存 */
struct pool* pool = (pf == PF_KERNEL) ? &kernel_pool : &user_pool;
lock_acquire(&pool->lock);
struct task_struct* cur_thread = running_thread();
int32_t bit_idx = -1;
/*
* if对应内核线程在内核虚拟内存池中将vaddr在位图中对应的bit位置1
* else if对应用户进程在自己独享的4GB虚拟内存中将vaddr在位图中对应的bit位置1
* */
if(cur_thread->pgdir == NULL && pf == PF_KERNEL) {
bit_idx = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
ASSERT(bit_idx > 0);
bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx, 1);
} else if(cur_thread->pgdir != NULL && pf == PF_USER) {
bit_idx = (vaddr - cur_thread->userprog_vaddr.vaddr_start) / PG_SIZE;
ASSERT(bit_idx > 0);
bitmap_set(&cur_thread->userprog_vaddr.vaddr_bitmap, bit_idx, 1);
} else {
console_put_str("\npgdir:");
console_put_int((uint32_t) cur_thread->pgdir);
console_put_str("\npf=");
console_put_int(pf);
PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernelspace by get_a_page");
}
/* 若用户进程申请则到用户物理内存池中申请物理内存 */
/* 若内核线程申请则到内核物理内存池中申请物理内存 */
void* page_phyaddr = palloc(pool);
/* 将虚拟内存与物理内存的映射关系填写到表中 */
page_table_add((void*) vaddr, page_phyaddr);
lock_release(&pool->lock);
return (void*) vaddr;
}
/* 通过虚拟地址得到对应的物理地址 */
uint32_t addr_v2p(uint32_t vaddr) {
uint32_t* pte = pte_ptr(vaddr);
return ((*pte & 0xfffff000) + (vaddr & 0x00000fff));
}
三、实现用户进程主要工作(对应11.3.4节到本章结束)
主要分两个主线进程
主线1:创建用户态进程(由process_execute函数开始)
主线2:执行用户态进程(由时钟中断函数schedule开始)
主线图如下:
主线1相关代码(process.c)
#include "process.h"
#include "stdint.h"
#include "memory.h"
#include "console.h"
#include "interrupt.h"
#include "debug.h"
#include "tss.h"
#include "list.h"
#include "print.h"
#define default_prio 63
void start_process(void* arg);
/*--------------------------- 主线1:创建用户进程相关函数 ------------------------------*/
/* 创建页目录表,将当前页表表示内核空间的pde复制
* 成功返回页目录表的虚拟地址,失败返回NULL */
uint32_t* create_page_dir(void) {
/* 申请用户进程页目录表 */
uint32_t* page_dir_vaddr = get_kernel_pages(1);
if(page_dir_vaddr == NULL) {
console_put_str("create_page_dir: get_kernel_pages failed!");
return NULL;
}
/* 将内核页目录表的3G-4G复制到申请的页目录表处 */
memcpy((void*) ((uint32_t)page_dir_vaddr + 768*4), (void*) (0xfffff000 + 768*4), 256*4);
/* 将进程页目录表最后一个pde改为其自身地址 */
page_dir_vaddr[1023] = addr_v2p((uint32_t) page_dir_vaddr) | PG_US_U | PG_RW_W | PG_P_1;
return page_dir_vaddr;
}
/*
* 创建用户进程虚拟地址位图,该函数实际作用是初始化用户进程中的虚拟内存池字段userprog_vaddr
* 在此记录下struct virtual_addr虚拟内存池的结构
* struct virtual_addr {
* struct bitmap vaddr_bitmap;
* uint32_t vaddr_start;
* };
* bitmap结构
* struct bitmap {
* uint32_t btmp_bytes_len;
* uint8_t *bits;
* };
* */
void create_user_vaddr_bitmap(struct task_struct* user_prog) {
user_prog->userprog_vaddr.vaddr_start = USER_VADDR_START;
/* 用户进程自己的虚拟内存池位图需要占用多少个物理内存页 */
uint32_t bitmap_pg_cnt = DIV_ROUND_UP((0xc0000000 - USER_VADDR_START) / PG_SIZE / 8, PG_SIZE);
user_prog->userprog_vaddr.vaddr_bitmap.bits = get_kernel_pages(bitmap_pg_cnt);
/* 用户进程自己的虚拟内存池的位图结构有多少字节 */
user_prog->userprog_vaddr.vaddr_bitmap.btmp_bytes_len = (0xc0000000 - USER_VADDR_START) / PG_SIZE / 8;
/* 对用户进程自己的虚拟内存池的位图的每个Bit位进行清零操作 */
bitmap_init(&user_prog->userprog_vaddr.vaddr_bitmap);
}
void process_execute(void* _filename, char* name) {
/*
* 创建用户进程主要步骤如下:
* 1、在内核中申请一页大小的内存作为pcb,将返回类型强转为struct task_struct*类型
* 2、初始化pcb相关字段(页表、用户进程虚拟内存池在后面单独初始化)
* 3、用户进程页表初始化
* 4、用户进程虚拟内存池初始化
* 5、此步与内核线程一样,初始化用户进程的内核栈(准备好相关参数供switch_to调用)
* 6、将用户进程加入等待队列(临界资源需要上锁)
* */
/* 初始化用户进程pcb相关参数 */
struct task_struct* pcb = get_kernel_pages(1);
init_thread_pcb(pcb, name, default_prio);
pcb->pgdir = create_page_dir();
create_user_vaddr_bitmap(pcb);
/* 初始化switch_to函数用到的内核栈 */
init_thread_stack(pcb, start_process, _filename);
/* 确保线程不在thread_ready_list和all_list中 */
/* 将线程加入thread_ready_list和all_list中 */
enum intr_status old_status = intr_disable();
ASSERT(!elem_find(&thread_ready_list, &pcb->thread_ready_list_tag));
list_append(&thread_ready_list, &pcb->thread_ready_list_tag);
ASSERT(!elem_find(&thread_all_list, &pcb->thread_all_list_tag));
list_append(&thread_all_list, &pcb->thread_all_list_tag);
intr_set_status(old_status);
}
主线2相关代码(process.c)
/*--------------------------- 主线2:执行用户进程相关函数 ------------------------------*/
extern void intr_exit(void);
void start_process(void* filename_) {
void* function = filename_;
struct task_struct* cur = running_thread();
struct intr_stack* cur_intr_stack = (struct intr_stack*) ((uint32_t) (cur->self_kstack) + sizeof(struct thread_stack));
cur_intr_stack->edi = 0;
cur_intr_stack->esi = 0;
cur_intr_stack->ebp = 0;
cur_intr_stack->esp_dummy = 0;
cur_intr_stack->ebx = 0;
cur_intr_stack->edx = 0;
cur_intr_stack->ecx = 0;
cur_intr_stack->eax = 0;
cur_intr_stack->gs = 0;
cur_intr_stack->fs = SELECTOR_U_DATA;
cur_intr_stack->es = SELECTOR_U_DATA;
cur_intr_stack->ds = SELECTOR_U_DATA;
cur_intr_stack->eip = filename_;
cur_intr_stack->cs = SELECTOR_U_CODE;
cur_intr_stack->eflags = (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1);
cur_intr_stack->esp = ((uint32_t) get_a_page(PF_USER, 0xc0000000 - PG_SIZE)) + PG_SIZE;
cur_intr_stack->ss = SELECTOR_U_STACK;
put_str("start_process done!!\n");
asm volatile("movl %0, %%esp; jmp intr_exit"::"g"(cur_intr_stack):"memory");
}
void page_dir_activate(struct task_struct* thread) {
uint32_t page_dir = 0x100000;
if(thread->pgdir != NULL) {
page_dir = addr_v2p((uint32_t) thread->pgdir);
}
asm volatile("movl %0, %%cr3"::"r"(page_dir):"memory");
}
void process_activate(struct task_struct* thread) {
ASSERT(thread != NULL);
/* 切换页表 */
page_dir_activate(thread);
/* 更新TSS中的0级栈起始地址,只有用户进程需要更新esp0
* 内核线程进入中断时不会从TSS中获取0级栈指针,因此不需要更新
* */
if(thread->pgdir != NULL) {
update_tss_esp(thread);
}
}
修改后的thread.c中的schedule函数
#include "thread.h"
#include "print.h"
#include "string.h"
#include "list.h"
#include "debug.h"
#include "interrupt.h"
#include "process.h"
#include "console.h"
#define PG_SIZE 4096
static struct task_struct* main_task_struct;
struct list thread_ready_list;
struct list thread_all_list;
extern void switch_to(struct task_struct* cur_thread, struct task_struct* next_thread);
/* 获取当前线程指针 */
struct task_struct* running_thread() {
uint32_t esp;
asm volatile("mov %%esp, %0":"=g"(esp));
return (struct task_struct*) (esp & 0xfffff000);
}
/* 线程启动函数 */
static void kernel_thread(thread_func* func, void* arg) {
intr_enable();
func(arg);
}
/* 初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack相应位置 */
void init_thread_stack(struct task_struct* pthread, thread_func* function, void* arg) {
pthread->self_kstack -= sizeof(struct intr_stack);
struct thread_stack* ts = (struct thread_stack*) pthread->self_kstack;
ts->ebp = 0;
ts->ebx = 0;
ts->edi = 0;
ts->esi = 0;
ts->eip = kernel_thread;
ts->func = function;
ts->arg = arg;
}
/* 初始化线程基本信息 */
void init_thread_pcb(struct task_struct* pthread, char* thread_name, uint8_t priority) {
pthread->self_kstack = (uint32_t*) ((uint32_t) pthread + PG_SIZE);
pthread->status = TASK_READY;
pthread->priority = priority;
strcpy(pthread->name, thread_name);
pthread->stack_magic = (uint32_t) 0x19970814;
pthread->ticks = priority;
pthread->elapsed_ticks = 0;
pthread->pgdir = NULL;
}
/* 根据提供的基本信息,创建线程并启动线程 */
struct task_struct* thread_start(char* thread_name, uint32_t priority, thread_func* function, void* arg) {
struct task_struct* pcb = get_kernel_pages(1);
init_thread_pcb(pcb, thread_name, priority);
init_thread_stack(pcb, function, arg);
/* 初始化线程后将PCB加入就绪队列 */
ASSERT(!elem_find(&thread_ready_list, &pcb->thread_ready_list_tag))
list_append(&thread_ready_list, &pcb->thread_ready_list_tag);
/* 确保线程不在all_list中 */
ASSERT(!elem_find(&thread_all_list, &pcb->thread_all_list_tag));
/* 将线程加入all_list中 */
list_append(&thread_all_list, &pcb->thread_all_list_tag);
return pcb;
}
/* 为主线程创造PCB并初始化 */
static void make_main_thread() {
/* 获得主线程的PCB在Loader将kernel.bin加载进来时我们就将主线程的栈设置为0x0009f000(开启页表后是0xc009f000) */
/* 所以PCB为0xc009e000 */
main_task_struct = running_thread();
init_thread_pcb(main_task_struct, "main thread", 31);
main_task_struct->status = TASK_RUNNING;
/* 确保main线程不在all_list中 */
ASSERT(!elem_find(&thread_all_list, &main_task_struct->thread_all_list_tag));
/* 将主线程加入all_list中 */
list_append(&thread_all_list, &main_task_struct->thread_all_list_tag);
}
void schedule() {
ASSERT(intr_get_status() == INTR_OFF);
struct task_struct* cur_thread = running_thread();
if(cur_thread->status == TASK_RUNNING) {
cur_thread->ticks = cur_thread->priority;
cur_thread->status = TASK_READY;
ASSERT(!elem_find(&thread_ready_list, &cur_thread->thread_ready_list_tag));
list_append(&thread_ready_list, &cur_thread->thread_ready_list_tag);
} else {
/* 如果不是由于时间片到期而进行调度,需要另外考虑(后续处理) */
}
ASSERT(!list_empty(&thread_ready_list));
struct list_elem* next_elem = list_pop(&thread_ready_list);
struct task_struct* next_thread = elem2entry(struct task_struct, thread_ready_list_tag, next_elem);
next_thread->status = TASK_RUNNING;
process_activate(next_thread);
switch_to(cur_thread, next_thread);
}
void thread_init() {
put_str("thread_init start\n");
list_init(&thread_ready_list);
list_init(&thread_all_list);
make_main_thread();
put_str("thread_init done\n");
}
/* 线程阻塞 */
void thread_block(enum task_status stat) {
ASSERT((stat == TASK_BLOCKED) || (stat == TASK_WAITING) || (stat == TASK_HANGING));
enum intr_status old_status = intr_disable();
struct task_struct* cur_thread = running_thread();
cur_thread->status = stat;
schedule();
intr_set_status(old_status);
}
/* 解除正在阻塞的线程 */
void thread_unblock(struct task_struct* thread) {
enum task_status stat = thread->status;
ASSERT(stat == TASK_BLOCKED || (stat == TASK_WAITING) || (stat == TASK_HANGING));
thread->status = TASK_READY;
enum intr_status old_status = intr_disable();
ASSERT(!elem_find(&thread_ready_list, &thread->thread_ready_list_tag));
list_push(&thread_ready_list, &thread->thread_ready_list_tag);
intr_set_status(old_status);
}
thread.h头文件增加了thread_ready_list和thread_all_list队列的引用,方便其他c文件引用
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"
#include "list.h"
#include "memory.h"
extern struct list thread_ready_list;
extern struct list thread_all_list;
/* 自定义通用函数类型,作为线程函数实际调用函数的指针 */
typedef void thread_func(void*);
/* 枚举类型进程状态 */
enum task_status {
TASK_READY,
TASK_RUNNING,
TASK_BLOCKED,
TASK_WAITING,
TASK_HANGING,
TASK_DIED
};
/* 中断栈intr_stack */
struct intr_stack {
/* 中断处理程序手动压入 */
uint32_t vec_no;
uint32_t edi;
uint32_t esi;
uint32_t ebp;
uint32_t esp_dummy;
uint32_t ebx;
uint32_t edx;
uint32_t ecx;
uint32_t eax;
uint32_t gs;
uint32_t fs;
uint32_t es;
uint32_t ds;
/* 处理器发生中断时自动压入 */
uint32_t err_code;
void (*eip) (void);
uint32_t cs;
uint32_t eflags;
uint32_t esp;
uint32_t ss;
};
/* 线程栈thread_stack */
struct thread_stack {
uint32_t ebp;
uint32_t ebx;
uint32_t edi;
uint32_t esi;
void (*eip) (thread_func*, void*);
uint32_t unused_retaddr;
thread_func* func;
void* arg;
};
/* 进程或线程的pcb,程序控制块 */
struct task_struct {
uint32_t* self_kstack;
enum task_status status;
uint32_t priority;
char name[16];
uint32_t ticks;
uint32_t elapsed_ticks;
struct list_elem thread_ready_list_tag;
struct list_elem thread_all_list_tag;
uint32_t* pgdir;
struct virtual_addr userprog_vaddr;
uint32_t stack_magic;
};
struct task_struct* running_thread();
struct task_struct* thread_start(char* thread_name, uint32_t priority, thread_func* function, void* arg);
void thread_init();
void init_thread_pcb(struct task_struct* pthread, char* thread_name, uint8_t priority);
void init_thread_stack(struct task_struct* pthread, thread_func* function, void* arg);
void thread_block(enum task_status stat);
void thread_unblock(struct task_struct* thread);
#endif
process.h头文件
#ifndef __USERPROG_PROCESS_H
#define __USERPROG_PROCESS_H
#include "global.h"
#include "thread.h"
#define USER_VADDR_START 0x8048000
#define USER_STACK3_VADDR (0xc0000000 - PG_SIZE)
void process_activate(struct task_struct* thread);
void process_execute(void* _filename, char* name);
#endif //__USERPROG_PROCESS_H
最后make all运行bochs后的效果:
思路感觉很清晰,最后写完代码调了好久才把所有bug调通。本章结束!