本章在内核线程的基础上实现用户进程,主要区别是内核线程运行在特权级别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 63void 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 4096static 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调通。本章结束!

操作系统真象还原[11章]-用户进程相关推荐

  1. 操作系统真象还原第一章

    开一个新坑,最终目标是按照<操作系统真象还原>这本书实现一个操作系统. 在读每一章的过程中都会按照书中的步骤配环境.写代码.做实验,完成每章后都产出一篇博客. 写博客的主要目的是鞭策自己不 ...

  2. 操作系统真象还原 第一章

    参考:<操作系统真象还原>第一章 ---- 安装Vmware Station 安装Ubuntu 装载配置Bochs 安装Vmware tools 开始乘帆历险!_Love 6的博客-CSD ...

  3. 操作系统真象还原 第二章

    1.创建hd60M.img yangjun@yangjun-Inspiron-7559:~/bochs$ bin/bximage =================================== ...

  4. [书]操作系统真象还原 -- 第11、12章 用户进程及调度、系统调用、内存管理

    ======  第 12 章 系统调用.内存管理 ====== GITHUB: https://github.com/trb331617/os_elephant/tree/master/chapter ...

  5. 《操作系统真象还原》第九章 ---- 终进入线程动斧开刀 豁然开朗拨云见日 还需解决同步机制才能长舒气

    文章目录 专栏博客链接 相关查阅博客链接 本书中错误勘误 进程 线程的自我小理解 线程 进程的状态 内核级线程 & 用户级线程 初步实现内核级线程 浪费两三个小时调试的辛酸史 编写thread ...

  6. 《操作系统真象还原》第十四章 ---- 实现文件系统 任务繁多 饭得一口口吃路得一步步走啊(上二)

    文章目录 专栏博客链接 相关查阅博客链接 本书中错误勘误 闲聊时刻 部分缩写熟知 实现文件描述符的原理 文件描述符的介绍 文件描述符与inode的介绍 文件描述符与PCB的描述符数组的介绍 实现文件操 ...

  7. 《操作系统真象还原》第七章

    <操作系统真象还原>第七章 本篇对应书籍第七章的内容 本篇内容介绍了操作系统的中断处理机制,建立中断描述符表,填充门描述符,以及中断处理程序,初始化8259A中断控制器实现外部中断功能,控 ...

  8. 《操作系统真象还原》第十五章 ---- 实现系统交互 操作系统最终章 四十五天的不易与坚持终完结撒花(上)

    文章目录 专栏博客链接 相关查阅博客链接 本书中错误勘误 闲聊时刻 实现fork 实现fork的介绍 实现fork的原理 编写完的thread.c(fork_pid) 编写完的thread.h(str ...

  9. 《操作系统真象还原》第十三章 ---- 编写硬盘驱动软件 行百里者半九十终成时喜悦溢于言表

    文章目录 专栏博客链接 相关查阅博客链接 本书中错误勘误 部分缩写熟知 闲聊时刻 提前需要准备编写的函数 实现printk 实现sprintf函数 创建从盘 创建从盘的步骤 修改后的bochsrc.d ...

最新文章

  1. @所有城市:想建AI智算中心的看这里!国家认可的那种
  2. 企业如何应对BT传输
  3. 11年标致307多少钱_11优布劳幼兽红西柚精酿啤酒多少钱一瓶?
  4. 做网站用UTF-8还是GB2312?
  5. esxi root 密码规则_陌陌风控系统静态规则引擎aswan
  6. java五子棋实训训心得,java五子棋实习报告
  7. request.getContextPath()取不到值
  8. 12 种经典亿级流量架构之资源隔离思想与方法论
  9. Python中的字符串方法
  10. echarts中tooltip提示框位置控制
  11. LayuI固定块关闭
  12. 构造体中变量后面的冒号_类型在变量前面还是后面,终于有答案了
  13. Dart教程(四):语法
  14. 内蒙古自治区及其盟市行政单位中英文名称对照表
  15. 利用python爬取租房信息网_Python3爬虫实战:以爬取小猪短租租房信息为例
  16. ubuntu 图形化桌面
  17. Docker 启动和退出一个容器
  18. SpringBoot——安全管理(一)
  19. Smith数问题C++代码实现
  20. 虾皮如何注册店铺_Shopee(虾皮购物)入驻申请流程?

热门文章

  1. Python处理DICOM(02)--DICOM转PNG
  2. ASP.NET中的Session和Cookie
  3. spring form标签的使用
  4. 把WinRAR默认压缩格式换为ZIP
  5. 新年「开门红」| 送你 108 份开工牛气能量!
  6. 京东科技寒假实习前端一面面经
  7. python基础:4.请至少列举5个 PEP8 规范(越多越好)。
  8. 我来说说百度的问题吧。。别和谐就行。
  9. 看完富爸爸穷爸爸的感悟
  10. 鸿蒙系统是封闭式吗,华为首应用的鸿蒙系统目前看来还是太封闭了...