[go: up one dir, main page]

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值