真象还原操作系统_第十一章_用户进程

一、为什么要有任务状态段TSS

1. LDT

  • 程序是一堆数据和指令的集合,它们只有被加载到内存并让CPU的寄存器指向它们后,CPU才能执行该程序。程序从文件系统上被加载到内存后,位于内存中的程序便称为映像,也称为任务。
  • LDT:局部描述符表,描述符是用于描述一段内存区域的作用及属性。
    在这里插入图片描述
    在这里插入图片描述
  • LDT是任务自己的私有资源,每个任务都有自己的LDT。

2. TSS的作用

  • 内存中的数据往往被加载到高速的寄存器后再处理,处理完成后再写回内存中。任何时候,寄存器中的内容才是任务的最新状态。
  • 每个任务关联一个TSS(任务状态段),当加载新任务时,CPU自动把当前旧任务的状态(寄存器的值)存入当前任务的TSS,然后将新任务TSS中的数据(寄存器的值)载入到相应寄存器中,实现了任务的切换。
  • TR寄存器:CPU中用于存储TSS起始地址的寄存器,任务切换的本质就是TR寄存器指向不同的TSS。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • TSS和LDT都只能且必须在GDT中注册描述符,TR寄存器中存储的是TSS的选择子,LDTR寄存器中存储的是LDT的选择子,GDTR中存储的是GDT的选择子。

3. CPU原生支持的任务切换方式

1. 中断+任务门

  • 优点:
    1. 实现简单
    2. 抢占式多任务调度,使所有任务都有运行的机会
  • 中断门中存储的不是TSS选择子,而是目标中断处理程序的代码段选择子及偏移地址,所以CPU并未把此中断门描述符中的中断处理程序当作新的任务。
  • CPU使用任务门实现了任务切换。
    在这里插入图片描述
  • 一个完整的任务包括用户空间代码和内核空间代码,只要TR未变,CPU都认识是同一个任务。中断处理程序属于内核代码,当中断处理程序中执行iretd指令从中断返回后:
    1. 从中断返回到当前任务的中断前代码。
    2. 当前任务是被嵌套调用的,它会调用自己TSS中“上一个任务的TSS指针”的任务,返回上一个任务。
  • 假设当前任务A被中断,CPU进入中断后,它可能的动作有:
    1. 假设是中断门/陷阱门,执行完中断处理程序后通过iretd返回到任务A中断前的指令部分。
    2. 假设是任务门,进行任务切换,此时是嵌套调用任务B,任务B在执行期间又发生了中断,进入到了相应的中断门。当执行完中断处理程序后通过iretd返回B。
    3. 假设是任务门,任务A调用任务B执行,任务B执行完后通过iretd指令返回任务A,使任务A继续完成后续的指令。
  • iretd如何避免被返回错任务?
    • NT位:Nest Task Flag,用于表示任务嵌套。
    • 当调用一个新任务时,CPU做了两件准备工作:
      1. 将NT置1,表示该任务是被其他任务调用。
      2. 将旧任务的TSS写入新任务TSS的“上一个任务的TSS指针”字段。
    • 当中断发生时,CPU会将NT位和TF位置0。如果是中断门描述符,CPU还会将IF位置0,防止中断嵌套,引发GP异常。
    • 不管CPU将eflags中的值改成什么样,这都是进入中断后的事情。任务eflags中原本的值已经在进入中断前压入栈中保存了,iretd退出中断时这些值会被恢复。
      在这里插入图片描述
  • 当中断发生时,通过任务门进行任务切换的过程:
    1. 从该任务描述符中取出任务的TSS选择子。
    2. 用新任务的TSS选择子在GDT中索引TSS描述符。
    3. 判断TSS描述符的P位是否为1(对应的TSS是否在内存中),若为0则异常。
    4. 从TR寄存器中获取旧任务的TSS位置,保存旧任务(当前任务)的环境到旧TSS中。其中任务状态是值CPU中寄存器的值,包括8个通用寄存器、6个段寄存器、指令指针寄存器eip、栈指针寄存器esp、页表寄存器cr3和标志寄存器eflags等。
    5. 恢复新任务环境,即把新任务TSS中的值加载到相应寄存器中。
    6. 使TR寄存器指向新任务的TSS。
    7. 将新任务(当前任务)的TSS描述符的B位置1。(表示该任务在运行)
    8. 将新任务的标志寄存器eflags的NT位置1。
    9. 将旧任务的TSS选择子写入新任务TSS中“上一个任务的TSS指针”中。
    10. 开始执行新任务。
  • 调用iretd返回旧任务,此时CPU检查NT位为1,则:
    1. 将当前任务(新任务)的NT位置0。
    2. 将当前任务TSS描述符的B位置0。
    3. 将当前任务的状态信息写入TR指向的TSS中。
    4. 获取当前任务TSS中“上一个任务的TSS指针”字段的值,将其加载到TR中,恢复旧任务的状态。
    5. 执行旧任务。

2. call/jmp+TSS选择子/任务门选择子

  • TSS中已经包含了任务的详细信息,任务门描述符中又包含了TSS选择子,所以TSS和任务门可以作为call和jmp的操作数。操作数中包含偏移量,CPU只用选择子部分即可,会忽略偏移量。
call 0x0010:0x1234	//假设任务门选择子定义在GDT中第2个描述符位置
call 0x0018:0x1234	//假设TSS选择子定义在GDT中第3个描述符位置
  • 任务切换步骤如下:
    1. CPU忽略偏移量0x1234,拿选择子0x0018在GDT中索引到第3个描述符。
    2. 检查描述符中的P位,看其在不在内存中,若为0则异常。同时检查S和TYPE位,判断其类型,如果是TSS描述符,检查其B位看其是否在运行,若为1则抛出GP异常。
    3. 进行特权级检查,CPL和TSS选择子中的RPL的特权都有>TSS描述符的DPL,否则抛出GP异常。
    4. 将当前任务的状态保存到寄存器TR指向的TSS中。
    5. 加载新任务TSS选择子到TR寄存器的选择器部分,同时将TSS描述符中的起始地址和偏移量等属性加载到TR寄存器中的描述符缓冲器中。
    6. 将新任务TSS中寄存器数据载入到相应的寄存器中,同时进行特权级检查。
    7. CPU会将新任务的标志寄存器eflags中的NT位置1。
    8. 将旧任务TSS选择子写入新任务TSS字段“上一个任务的TSS指针”中。
    9. 将新任务的TSS描述符中的B位置1,旧任务的TSS描述符的B位不变,也是1。(jmp指针会将旧任务的B位清零)旧任务的标志寄存器中的NT位不变。
    10. 开始执行新任务。

3. iretd

见“中断+任务门”一节

4. 现代OS采用的任务切换方式

  • linux对TSS的操作:
    • linux为每个CPU创建一个TSS,各CPU的TR寄存器保存各CPU的TSS。在用ltr指令加载TSS后,该TR寄存器永远指向各自CPU的TSS,之后不会重新加载TSS。
    • 在任务切换时,只需要将TSS中的SS0和esp0更新为新任务的内核栈的段地址和栈指针。
    • 优点:减少开销。和TR加载不同的TSS(TSS位于内存)相比,修改TSS中的内容带来的开销更小。
  • 任务的状态信息保存在哪里?
    • 当CPU从低特权级->高特权级时,CPU会自动从TSS中获取对应的高特权级的栈指针。
    • 因为linux只用到了特权级3和0,因此当CPU从3特权级->0特权级时,CPU会自动从当前任务的TSS中获取SS0和esp0字段的值作为0特权级的栈,然后linux“手动”执行一系列push指令将任务的状态保存在0特权级中,也就是TSS中SS0和esp0所指向的栈。

二、定义并初始化TSS

--------------------------代码----------------------------

  • 对线程的复习:
    在这里插入图片描述

  • “/home/lily/OS/boot/kernel/global.h”

#ifndef __KERNEL_GLOBAL_H
#define __KERNEL_GLOBAL_H 
#include "stdint.h"

#define RPL0 0
#define RPL1 1
#define RPL2 2
#define RPL3 3
#define DESC_G_4K   1
#define DESC_D_32   1
#define DESC_L      0
#define DESC_AVL    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清零*/
#define DESC_TYPE_DATA  2
//x=0.e=0,w=1,a=0 数据段是不可执行的、向上扩展的、可读写,已访问位清零
#define DESC_TYPE_TSS   9

#define TI_GDT 0
#define TI_LDT 1

#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)
//第三个段描述符是显存,第四个是tss
#define SELECTOR_U_CODE ((5 << 3) + (TI_GDT << 2) + RPL3)
#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_CODE << 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)

/*定义GDT中描述符的结构*/
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;
};

/*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位的门,不会用到

#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)

#endif
  • “/home/lily/OS/boot/userprog/tss.c”
#include "tss.h"
#include "print.h"
#include "string.h"
#define PG_SIZE 4096

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中esp0字段的值为pthread的0级栈*/
void update_tss_esp(struct task_struct* pthread){
    tss.esp0 = (uint32_t*)((uint32_t)pthread + PG_SIZE);    //不懂的见上图
}

/*创建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 = (((limit & 0x000f0000) >> 16) + (uint8_t)(attr_high));
    desc.base_high_byte = desc_base >> 24;
    return desc;
}

/*在gdt中创建tss并将其安装到gdt中*/
void tss_init(){
    put_str("tss_init start\n");
    uint32_t tss_size = sizeof(tss);
    memset(&tss,0,tss_size);
    tss.ss0 = SELECTOR_K_STACK;     //0级栈段的选择子
    tss.io_base = tss_size;         //表示tss中无io位图
    /*gdt段基址为0x900,把tss放在第4个位置,也就是0x900+0x20的位置*/

    /*在gdt中添加dpl为0的TSS描述符*/
    *((struct gdt_desc*)0xc0000920) = make_gdt_desc((uint32_t*)&tss,tss_size - 1,TSS_ATTR_LOW,TSS_ATTR_HIGH);

    /*在gdt中添加dpl为3的数据段和代码段描述符*/
    *((struct gdt_desc*)0xc0000928) = make_gdt_desc((uint32_t*)0,0xfffff,GDT_CODE_ATTR_LOW_DPL3,GDT_ATTR_HIGH);
    *((struct gdt_desc*)0xc0000930) = make_gdt_desc((uint32_t*)0,0xfffff,GDT_DATA_ATTR_LOW_DPL3,GDT_ATTR_HIGH);

    /*gdt 16位的limit 32位的段基址*/
    uint64_t gdt_operand = ((8*7-1) | ((uint64_t)(uint32_t)0xc0000900 << 16));  //7个描述符大小

    asm volatile("lgdt %0" : : "m"(gdt_operand));       //加载GDT
    asm volatile("ltr %w0" : : "r"(SELECTOR_TSS));      //加载TR
    put_str("tss_init and ltr done\n");
}
  • “/home/lily/OS/boot/userprog/tss.h”
#ifndef __USERPROG_TSS_H
#define __USERPROG_TSS_H
#include "stdint.h"
#include "thread.h"

/*更新tss中esp0字段的值为pthread的0级栈*/
void update_tss_esp(struct task_struct* pthread);
/*在gdt中创建tss并将其安装到gdt中*/
void tss_init(void);

#endif
  • “/home/lily/OS/boot/kernel/init.c”
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "../device/keyboard.h"
#include "../userprog/tss.h"

/*负责初始化所有模块*/
void init_all(){
    put_str("init_all\n");
    idt_init();     //初始化中断
    mem_init();
    thread_init();
    timer_init();   //初始化PIT   
    console_init(); //控制台初始化最好放在开中断之前
    keyboard_init();
    tss_init();
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、实现用户进程

1. 实现用户进程的原理

  1. 在thread_start()中,先通过get_kernel_pages(1)在内核内存池获取1页物理页做线程的PCB,即thread。
  2. 接着调用init_thread()初始化该线程的PCB信息。
  3. 然后用thread_create()创建线程运行的栈,将栈的返回地址指向kernel_thread()函数,在kernel_thread()中通过调用function来使function运行。
    在这里插入图片描述

2. 用户进程的虚拟地址空间

  • 进程与线程的区别在于进程拥有单独的4GB空间(是虚拟地址,而非物理地址),然后虚拟地址经过分页后最终要落在有限的物理页中。
  • 每个进程需要维护一个虚拟地址池,用此地址池来记录该进程的虚拟地址的分配情况。该变量最好保存在PCB中。因为进程和线程使用相同的PCB,即struct task_struct,我们要做的就是增加一个成员,用来表示用户虚拟空间的分配情况。

--------------------------代码----------------------------

  • “/home/lily/OS/boot/thread/thread.h”
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"
#include "list.h"
#include "../kernel/memory.h"

/*自定义通用函数类型,它将在很多线程函数中作为形参类型*/
typedef void thread_func(void*);

/*进程或线程的状态*/
enum task_status{
    TASK_RUNNING,
    TASK_READY,
    TASK_BLOCKED,
    TASK_WAITING,
    TASK_HANGING,
    TASK_DIED
};

/*中断栈intr_stack
用于中断发生时保护程序上下文环境:
进程/线程被外部中断/软中断打断时,会按照此结构压入上下文
寄存器,intr_exit中的出栈操作是此结构的逆操作
此栈在线程自己的内核栈中位置固定,所在页的最顶端
*/
struct intr_stack{
    uint32_t vec_no;    //kernel.S宏VECTOR中push %1压入的中断号
    uint32_t edi;
    uint32_t esi;
    uint32_t ebp;
    uint32_t esp_dummy; //虽然pushad把esp也压入了,但esp是不断变化的,所以会被popad忽略
    uint32_t ebx;
    uint32_t edx;
    uint32_t ecx;
    uint32_t eax;
    uint32_t gs;
    uint32_t fs;
    uint32_t es;
    uint32_t ds;

    /*下面由CPU从低特权级进入高特权级时压入*/
    uint32_t err_code;  //err_code会被压入在eip之后
    void (*eip) (void);
    uint32_t cs;
    uint32_t eflags;
    void* esp;
    uint32_t ss;
};

/*线程栈thread_stack
线程自己的栈,用于存储线程中待执行的函数
此结构在线程自己的内核栈中位置不固定
仅用在switch_to时保存线程环境
实际位置取决于实际情况。
*/
struct thread_stack{
    uint32_t ebp;
    uint32_t ebx;
    uint32_t edi;
    uint32_t esi;

    /*线程第一次执行时,eip指向待调用函数kernel_thread,
    其他时候,eip指向switch_to的返回地址*/
    void (*eip) (thread_func* func,void* func_arg);

    /*以下仅供第一次被调度上CPU时使用*/

    /*参数unused_ret只为占位置充数为返回地址*/
    void (*unused_retaddr);     //占位符,基线,表示返回地址
    thread_func* function;      //由kernel_thread所调用的函数名
    void* func_arg;             //由kernel_thread所调用的函数所需的参数
};

/*进程或线程的pcb,程序控制块*/
struct task_struct{
    uint32_t* self_kstack;      //各内核线程都用自己的内核栈
    enum task_status status;
    uint8_t priority;           //线程优先级
    char name[16];
    uint8_t ticks;              //每次在处理器上的嘀咕数
    uint32_t elapsed_ticks;     //此任务自上CPU运行至今用了多少CPU嘀咕数,也就是任务运行了多久
    struct list_elem general_tag;       //用于线程在一般队列中的节点
    struct list_elem all_list_tag;      //用于线程队列thread_all_list中的结点
    uint32_t* pgdir;            //进程自己页表的虚拟地址,如果是线程则为NULL
    struct virtual_addr userprog_vaddr; //用户进程的虚拟地址
    uint32_t stack_magic;       //栈的边界标记,用于检测栈的溢出
};

/*获取当前线程的pcb指针*/
struct task_struct* running_thread(void);
/*初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack中相应的位置*/
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg);

/*线程初始化*/
void init_thread(struct task_struct* pthread,char* name,int prio);

/*创建一优先级为prio,线程名为name,线程所执行函数为function(func_arg)的线程*/
struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg);

/*将kernel中的main函数完善为主线程*/
// static void make_main_thread(void);

/*实现任务调度*/
void schedule(void);

/*初始化线程环境*/
void thread_init(void);

/*当前线程将自己阻塞,标志其状态为stat*/
void thread_block(enum task_status stat);

/*将线程pthread接触阻塞*/
void thread_unblock(struct task_struct* pthread);
#endif

3. 为进程创建页表和3特权级栈

--------------------------代码----------------------------

  • “/home/lily/OS/boot/kernel/memory.c”
#include "memory.h"
#include "bitmap.h"
#include "stdint.h"
#include "global.h"
#include "debug.h"
#include "print.h"
#include "string.h"
#include "sync.h"
#include "thread.h"

#define PG_SIZE 4096
//4KB
#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)

/*位图地址
因为0xc009_f000是内核主线程栈顶,0xc009_e000是内核主线程的pcb。
一个页框大小的位图可表示128MB内存,位图位置安排在地址0xc009_a000,
这样本系统最大支持4个页框的位图,即512MB
*/
#define MEM_BITMAP_BASE 0xc009a000
/****************/

/*0xc000_0000是内核从虚拟地址3G起
0x10_0000是指跨过低端1MB内存,使虚拟地址在逻辑上连续*/
#define K_HEAP_START 0xc0100000

//内存池结构,生成两个实例用于管理内核内存池和用户内存池
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;   //用来给内核分配虚拟地址

/*在pf表示的虚拟内存池中申请pg_cnt个虚拟页,成功则返回虚拟页起始地址,失败则返回NULL*/
static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt){
    int vaddr_start = 0,bit_idx_start = -1;
    uint32_t cnt = 0;
    if(pf == PF_KERNEL){        //内核内存池
        //在位图中申请pg_cnt位
        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);
        vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start*PG_SIZE;
    }else{          //用户内存池
        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);
        vaddr_start = cur->userprog_vaddr.vaddr_start + bit_idx_start*PG_SIZE;
        /*(0xc0000000 - PG_SIZE)作为用户3级栈已经在start_process被分配*/
        ASSERT((uint32_t)vaddr_start < (0xc0000000 - PG_SIZE));
    }
    return (void*)vaddr_start;
}

/*得到虚拟地址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指向的物理内存池中分配 一个物理页,成功则返回页框物理地址,失败则返回NULL*/
static void* palloc(struct pool* m_pool){
    //检测有无空间
    int bit_idx = bitmap_scan(&m_pool->pool_bitmap,1);
    //分配空间
    if(bit_idx == -1)
        return NULL;
    bitmap_set(&m_pool->pool_bitmap,bit_idx,1);
    uint32_t page_phyaddr = m_pool->phy_addr_start + (bit_idx*PG_SIZE);
    return (void*)page_phyaddr;
}

// /*页表中添加虚拟地址_vaddr与物理地址_page_phyadddr的映射*/
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* pte = pte_ptr(vaddr);
    uint32_t* pde = pde_ptr(vaddr);

    /*要确保pde创建完成后才能执行pte*/
    if(*pde & 0x00000001){
        //如果pde存在,则将pte指向物理地址
        ASSERT(!(*pte & 0x00000001));   //pte理应不存在
        if(!(*pte & 0x00000001)){
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }else{
            PANIC("pte repeat!");
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }
    }else{
        //页目录项不存在,创建页目录项
        uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);  //页表中用到的页框一律从内核空间分配
        *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        //pte清零
        memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);   //pte & 0xfffff000表示新页表的起始地址,将整页清零
        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);
    /*malloc_page原理的三个动作:
    1. 通过vaddr_get在虚拟内存池中申请虚拟地址
    2. 通过palloc在物理内存池中申请物理地址
    3. 通过page_table_add将两个地址进行映射*/

    //1.在虚拟内存池中申请
    void* vaddr_start = vaddr_get(pf,pg_cnt);   
    if(vaddr_start == NULL)
        return NULL;
    uint32_t vaddr = (uint32_t)vaddr_start;
    uint32_t cnt = (uint32_t)pg_cnt;
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;

    //2.虚拟内存是连续的,物理内存不是,需要逐个映射
    while (cnt--){
        void* page_phyaddr = palloc(mem_pool);  //申请一个物理地址
        if(page_phyaddr == NULL)
            return NULL;
        //3.在页表中做映射
        page_table_add((void*)vaddr,page_phyaddr);
        vaddr += PG_SIZE;   //下一个虚拟页
    }
    return vaddr_start;
}

// /*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt){
    void* vaddr = malloc_page(PF_KERNEL,pg_cnt);
    if(vaddr == NULL)
        return 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 = malloc_page(PF_USER,pg_cnt);      //在用户内存池中以整页为单位分配内存,并返回分配的虚拟地址
    memset(vaddr,0,pg_cnt*PG_SIZE);
    lock_release(&user_pool.lock);
    return vaddr;
}

/*将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配;申请一页内存,并用vaddr映射到该页(可以指定虚拟地址)*/
void* get_a_page(enum pool_flags pf,uint32_t vaddr){
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
    lock_acquire(&mem_pool->lock);
    /*先将虚拟地址对应的位图置1*/
    struct task_struct* cur = running_thread();
    int32_t bit_idx = -1;

    /*若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图*/
    if(cur->pgdir != NULL && pf == PF_KERNEL){
        bit_idx = (vaddr - cur->userprog_vaddr.vaddr_start) / PG_SIZE;
        ASSERT(bit_idx > 0);
        bitmap_set(&cur->userprog_vaddr.vaddr_bitmap,bit_idx,1);
    }else if(cur->pgdir == NULL && pf == PF_KERNEL){
        /*如果是内核线程申请内核内存,就修改kernel_vaddr*/
        bit_idx = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
        ASSERT(bit_idx > 0);
        bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx,1);
    }else{
        PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernel space by get_a_page");
    }

    void* page_phyaddr = palloc(mem_pool);
    if(page_phyaddr == NULL)
        return NULL;
    page_table_add((void*)vaddr,page_phyaddr);
    lock_release(&mem_pool->lock);
    return (void*)vaddr;
}

/*得到虚拟地址映射到的物理地址*/
uint32_t addr_v2p(uint32_t vaddr){
    uint32_t* pte = pte_ptr(vaddr);
    /*(pte)的值是页表所在的物理页框地址,去掉其低12位的页表项属性 + 虚拟地址vaddr的低12位*/
    return ((*pte & 0xfffff000) + (vaddr & 0x00000fff));    //原理:PDE + PTE + 12位偏移地址 = 物理地址;这里直接用pte得到页表项,然后页表项 + 12位偏移地址 = 物理地址
}

// //初始化内存池
static void mem_pool_init(uint32_t all_mem){
    put_str("   mem_pool_init start\n");
    uint32_t page_table_size = PG_SIZE * 256;
    //页表大小=1页的页目录表 + 第0和第768个页目录项指向同一个页表 + (769~1022)个页目录项共指向254个页表 = 256
    uint32_t used_mem = page_table_size + 0x100000;     //0x10_0000是指跨过低端1MB内存 + 页表,使虚拟地址在逻辑上连续
    uint32_t free_mem = all_mem - used_mem;
    uint16_t all_free_pages = free_mem/PG_SIZE;
    uint16_t kernel_free_pages = all_free_pages / 2;
    uint16_t user_free_pages = all_free_pages - kernel_free_pages;

    //为简化位图操作,余数不处理
    uint32_t kbm_length = kernel_free_pages / 8;
    uint32_t ubm_length = user_free_pages / 8;
    uint32_t kp_start = used_mem;   //内核内存池起始地址
    uint32_t up_start = kp_start + kernel_free_pages*PG_SIZE;   //用户内存池起始地址

    kernel_pool.phy_addr_start = kp_start;
    user_pool.phy_addr_start = up_start;

    kernel_pool.pool_size = kernel_free_pages*PG_SIZE;
    user_pool.pool_size = user_free_pages*PG_SIZE;

    kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
    user_pool.pool_bitmap.btmp_bytes_len = ubm_length;

    /*内核内存池和用户内存池位图
    位图是全局数据,长度不固定
    全局/静态数组需要在编译时知道长度
    我们需要根据总内存大小算出需要多少字节,所以改为指定一块内存来生成位图
    */
    kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
    user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
    
    /*输出内存池信息*/
    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((int)user_pool.phy_addr_start);
        put_str("\n");

        /*将位图置0*/
        bitmap_init(&kernel_pool.pool_bitmap);
        bitmap_init(&user_pool.pool_bitmap);
        /*初始化锁*/
        lock_init(&kernel_pool.lock);
        lock_init(&user_pool.lock);

        /*初始化内核虚拟地址的位图,按照实际物理内存大小生成数组*/
        kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;
        //用于维护内核堆的虚拟地址,所以要和内核内存池大小一致
        /*位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/
        kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);
        kernel_vaddr.vaddr_start = K_HEAP_START;
        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");
}
  • “/home/lily/OS/boot/kernel/memory.h”
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"
//虚拟地址池,用于虚拟地址管理
struct virtual_addr
{
    struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构
    uint32_t vaddr_start;       //虚拟地址起始地址
};

enum pool_flags{
    PF_KERNEL = 1,  //内核内存池
    PF_USER = 2     //用户内存池
};

#define PG_P_1 1    //页表项或页目录项存在属性位:此页在内存中存在
#define PG_P_0 0    //页表项或页目录项存在属性位:此页在内存中不存在
#define PG_RW_R 0   //R/W属性位值,读/写操作:此页运行读、写、执行
#define PG_RW_W 2   //R/W属性位值,读/写操作:此页允许读、执行
#define PG_US_S 0   //U/S属性位值,系统级:只允许特权级0、1、2的程序访问
#define PG_US_U 4   //U/S属性位值,用户级:允许所有特权级的进程访问

extern struct pool kernel_pool, user_pool;

// static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt);

/*得到虚拟地址vaddr对应的pte指针*/
uint32_t* pte_ptr(uint32_t vaddr);

/*得到虚拟地址vaddr对应的pde指针*/
uint32_t* pde_ptr(uint32_t vaddr);

/*在m_pool指向的物理内存池中分配 一个物理页,成功则返回页框物理地址,失败则返回NULL*/
// static void* palloc(struct pool* m_pool);

/*页表中添加虚拟地址_vaddr与物理地址_page_phyadddr的映射*/
// static void page_table_add(void* _vaddr,void* _page_phyaddr);

/*分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL*/
void* malloc_page(enum pool_flags pf,uint32_t pg_cnt);

/*将地址vaddr与pf池中的物理地址关联,仅支持一页空间分配;申请一页内存,并用vaddr映射到该页(可以指定虚拟地址)*/
void* get_a_page(enum pool_flags pf,uint32_t vaddr);

/*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt);

/*得到虚拟地址映射到的物理地址*/
uint32_t addr_v2p(uint32_t vaddr);

/*内存管理部分初始化入口*/
void mem_init(void);

#endif

4. 进入特权级3

  • CPU一般不允许从高特权级->低特权级,除非是从中断返回和“调用门”返回的情况,因为我们不打算使用调用门,所以返回3特权级只能借助中断返回iretd的方式。
  • 从中断返回,必须要通过intr_exit函数。我们在中断入口函数intr%1entry中通过一系列push操作来保存环境的上下文,因此在intr_exit中恢复任务的上下文要通过一系列pop操作,这属于intr%1entry的逆过程。
  • 任务的上下文被保存在任务PCB的struct intr_stack中,即栈。
  • 返回到3特权级时,因为要将用户进程上CPU运行,所以要提前准备好用户进程所用的栈,在栈中填好用户进程的上下文信息。借着一系列pop出栈的机会,将用户进程的上下文信息载入CPU的寄存器,为用户进程的运行准备好环境。
  • CPU是如何知道中断返回后要进入到哪个特权级呢?这是由栈中保存的CS选择子中的RPL决定的。当执行iretd时,在栈中保存的CS选择子要被加载到代码段寄存器CS中,因此栈中CS选择子的RPL便是中断返回后CPU的新CPL。
  • 对于可屏蔽中断而言,任务之所以能进入中断,是因为标志寄存器eflags的IF位为1;退出中断后,还得保存IF = 1,继续响应新的中断。
  • 用户进程属于最低特权级,对于IO操作,不允许用户进程直接访问硬件,只允许OS有直接的硬件控制。这是由标志寄存器eflags的IOPL位决定的,必须使其值为0。

5. 用户进程何时运行?

在这里插入图片描述

  1. 进程的运行是由时钟中断调用schedule调度实现的,调用了process_activate来激活进程/线程的相关资源,随后通过switch_to函数调度进程。
  2. switch_to第一次执行时会返回kernel_thread,在switch_to中退出后,CPU会执行kernel_thread函数,相当于switch_to调用kernel_thread。
  3. 在thread_create中已经将start_process和user_prog作为了kernel_thread的参数,所以kernel_thread中可以以此形式调用start_process(user_prog)
    在这里插入图片描述
    在这里插入图片描述

6. 实现用户进程-上

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 在4GB的虚拟地址空间中,(0xc0000000-1)是用户空间的最高地址 ,0xc0000000~0xffffffff是内核空间。
  • 我们把用户空间的最高处即0xc0000000-1及以下部分内存空间用于存储用户进程的命令行参数,之下的空间再作为用户的栈和堆。命令行参数也是被压入用户栈的。
    在这里插入图片描述
  • 每个进程都有独立的虚拟地址空间,本质上是各个进程都有自己单独的页表。页表是存储在页表寄存器cr3中的,cr3只有一个。在不同进程执行前,我们要在cr3寄存器中为其换上配套的页表,从而实现虚拟地址空间的隔离。
  • 每个用户进程都是独立的页表,因为线程不是为用户进程服务的,而是为内核服务的,所以它与内核共享同一地址空间,也就是和内核用的是一套页表。当进程A切换到线程B时,cr3中的页表要从A的切换到内核所用的页表。因此,无论是进程还是线程,都有切换页表。
  • 内核页表为0x10_0000

--------------------------代码----------------------------

  • “/home/lily/OS/boot/kernel/global.h”
#ifndef __KERNEL_GLOBAL_H
#define __KERNEL_GLOBAL_H 
#include "stdint.h"

#define RPL0 0
#define RPL1 1
#define RPL2 2
#define RPL3 3
#define DESC_G_4K   1
#define DESC_D_32   1
#define DESC_L      0
#define DESC_AVL    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清零*/
#define DESC_TYPE_DATA  2
//x=0.e=0,w=1,a=0 数据段是不可执行的、向上扩展的、可读写,已访问位清零
#define DESC_TYPE_TSS   9

#define TI_GDT 0
#define TI_LDT 1

#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)
//第三个段描述符是显存,第四个是tss
#define SELECTOR_U_CODE ((5 << 3) + (TI_GDT << 2) + RPL3)
#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_CODE << 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)

/*定义GDT中描述符的结构*/
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;
};

/*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位的门,不会用到

#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)

#define EFLAGS_MBS      (1 << 1)    //此项必须设置
#define EFLAGS_IF_1     (1 << 9)    //if为1,开中断
#define EFLAGS_IF_0     0           //if为0,关中断
#define EFLAGS_IOPL_3   (3 << 12)   //IOPL3,用于测试用户程序在非系统调用下进行IO
#define EFLAGS_IOPL_0   (0 << 12)   //IOPL0

#define NULL ((void*)0)
#define DIV_ROUND_UP(X,STEP) ((X + STEP - 1) / (STEP))  //实现除法向上取整
#define bool int
#define true    1
#define false   0
#define PG_SIZE 4096

#endif

  • “/home/lily/OS/boot/userprog/process.c”
#include "thread.h"
#include "string.h"
#include "process.h"
#include "tss.h"

extern void intr_exit(void);

/*构建用户进程filename_的初始上下文信息,即struct intr_stack*/
void start_process(void* filename_){    //filename_表示用户进程的名称,因为用户进程是从文件系统中加载到内存的
    void* function = filename_;
    struct task_struct* cur = running_thread();
    cur->self_kstack += sizeof(struct thread_stack);
    struct intr_stack* proc_stack = (struct intr_stack*)cur->self_kstack;
    proc_stack->edi = proc_stack->esi = proc_stack->ebp = proc_stack->esp_dummy = 0;
    proc_stack->ebx = proc_stack->edx = proc_stack->ecx = proc_stack->eax = 0;
    proc_stack->gs = 0;
    proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA;
    proc_stack->eip = function;         //cs:eip是程序入口地址
    proc_stack->cs = SELECTOR_U_CODE;   //cs寄存器赋值为用户级代码段
    proc_stack->eflags = (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1);
    proc_stack->esp = (void*)((uint32_t)get_a_page(PF_USER,USER_STACK3_VADDR) + PG_SIZE);   //3特权级下的栈
    proc_stack->ss = SELECTOR_U_DATA;
    asm volatile("movl %0,%%esp;jmp intr_exit" : : "g"(proc_stack) : "memory");     //通过假装从中断返回的方式,使filename_运行
}

/*激活页表*/
void page_dir_activate(struct task_struct* p_thread){
    /*执行此函数时,当前任务可能是线程。
    *因为线程是内核级,所以线程用的是内核的页表。每个用户进程用的是独立的页表。
    *之所以对线程也要重新安装页表,原因是上一次被调度的可能是进程,否则不恢复页表的话,线程就会使用进程的页表了
    */
    uint32_t pagedir_phy_addr = 0x100000;   /*若为内核线程,需要重新填充页表为内核页表:00x10_0000*/
    //默认为内核的页目录物理地址,也就是内核线程所用的页目录表
    if(p_thread->pgdir != NULL){
        pagedir_phy_addr = addr_v2p((uint32_t)p_thread->pgdir);     //判断是线程/进程的方法:进程的pcb中pgdir指向页表的虚拟地址,而线程中无页表所以pgdir = NULL
    }
    /*更新页目录表寄存器cr3,使新页表生效*/
    asm volatile("movl %0,%%cr3" : : "r"(pagedir_phy_addr):"memory");   //将进程/线程的页表/内核页表加载到cr3寄存器中
    //每个进程都有独立的虚拟地址空间,本质上是各个进程都有自己单独的页表。
    //页表是存储在页表寄存器cr3中的,cr3只有一个。在不同进程执行前,我们要在cr3寄存器中为其换上配套的页表,从而实现虚拟地址空间的隔离。
}

/*激活线程/进程的页表,更新tss中的esp0为进程的特权级0的栈*/
void process_activate(struct task_struct* p_thread){
    ASSERT(p_thread != NULL);
    /*激活该进程/线程的页表*/
    page_dir_activate(p_thread);
    /*内核线程特权级本身是0,CPU进入中断时并不会从tss中获取0特权级栈地址,故不需要更新esp0*/
    if(p_thread->pgdir){
        /*更新该进程的esp0,用于此进程被中断时保留上下文*/
        update_tss_esp(p_thread);   //更新tss中的esp0为进程的特权级0栈
        //时钟中断进行进程调度,中断需要用到0特权级的栈
    }
}
  • “/home/lily/OS/boot/userprog/process.h”
#ifndef __USERPROG_PROCESS_H
#define __USERPROG_PROCESS_H
#include "stdint.h"
#define USER_STACK3_VADDR (0xc0000000 - 0x1000)
/*构建用户进程filename_的初始上下文信息,即struct intr_stack*/
void start_process(void* filename_);

/*激活页表*/
void page_dir_activate(struct task_struct* p_thread);

/*激活线程/进程的页表,更新tss中的esp0为进程的特权级0的栈*/
void process_activate(struct task_struct* p_thread);
#endif

7. bss简介

在这里插入图片描述

  • bss中的数据是是未初始化的全局变量和局部静态变量,程序运行后才会为它们赋值,在程序运行之初,OS的程序加载器直接将其置零即可。
  • 由于bss中的内容是变量,属性是可读写,和数据段属性一致,所以链接器将bss段占用的内存空间大小合并到数据段占用的内存中。这样便在数据段中预留出bss的空间以供程序在将来运行时使用。
  • 程序将来加载运行时,OS的程序加载器会为该程序的数据段分配内存,bss的内存区域也会被顺带分配。因此我们只需要知道数据段的起始地址和大小,便可以知道堆的起始地址。

8. 实现用户进程-下

  • OS是为用户进程服务的,它提供各种系统功能供用户进程使用。为了用户进程可以访问到内内核服务,必须确保用户进程在自己的地址空间中能够访问到内核,即内核空间必须是用户空间的一部分。
    在这里插入图片描述
    在这里插入图片描述
  • 页表是记录在页目录项中,此处页目录项对于内核物理内存的作用,相当于Linux中文件的符号链接,页目录项是访问内核的物理内存的入口。
  • 因此,为了访问到内核,我们只要给每个用户进程创建访问内核的入口即可。
  • 每创建一个进程,就将内核页目录项复制到用户进程的页目录项,为内核物理内存创建多个入口,从而实现所有用户进程共享物理内核。

--------------------------代码----------------------------

  • “/home/lily/OS/boot/userprog/process.c”
#include "thread.h"
#include "process.h"
#include "tss.h"
#include "console.h"
#include "memory.h"
#include "global.h"
#include "bitmap.h"
#include "interrupt.h"
#include "debug.h"

extern void intr_exit(void);

/*构建用户进程filename_的初始上下文信息,即struct intr_stack*/
void start_process(void* filename_){    //filename_表示用户进程的名称,因为用户进程是从文件系统中加载到内存的
    void* function = filename_;
    struct task_struct* cur = running_thread();
    cur->self_kstack += sizeof(struct thread_stack);
    struct intr_stack* proc_stack = (struct intr_stack*)cur->self_kstack;
    proc_stack->edi = proc_stack->esi = proc_stack->ebp = proc_stack->esp_dummy = 0;
    proc_stack->ebx = proc_stack->edx = proc_stack->ecx = proc_stack->eax = 0;
    proc_stack->gs = 0;
    proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA;
    proc_stack->eip = function;         //cs:eip是程序入口地址
    proc_stack->cs = SELECTOR_U_CODE;   //cs寄存器赋值为用户级代码段
    proc_stack->eflags = (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1);
    proc_stack->esp = (void*)((uint32_t)get_a_page(PF_USER,USER_STACK3_VADDR) + PG_SIZE);   //3特权级下的栈
    proc_stack->ss = SELECTOR_U_DATA;
    asm volatile("movl %0,%%esp;jmp intr_exit" : : "g"(proc_stack) : "memory");     //通过假装从中断返回的方式,使filename_运行
}

/*激活页表*/
void page_dir_activate(struct task_struct* p_thread){
    /*执行此函数时,当前任务可能是线程。
    *因为线程是内核级,所以线程用的是内核的页表。每个用户进程用的是独立的页表。
    *之所以对线程也要重新安装页表,原因是上一次被调度的可能是进程,否则不恢复页表的话,线程就会使用进程的页表了
    */
    uint32_t pagedir_phy_addr = 0x100000;   /*若为内核线程,需要重新填充页表为内核页表:00x10_0000*/
    //默认为内核的页目录物理地址,也就是内核线程所用的页目录表
    if(p_thread->pgdir != NULL){
        pagedir_phy_addr = addr_v2p((uint32_t)p_thread->pgdir);     //判断是线程/进程的方法:进程的pcb中pgdir指向页表的虚拟地址,而线程中无页表所以pgdir = NULL
    }
    /*更新页目录表寄存器cr3,使新页表生效*/
    asm volatile("movl %0,%%cr3" : : "r"(pagedir_phy_addr):"memory");   //将进程/线程的页表/内核页表加载到cr3寄存器中
    //每个进程都有独立的虚拟地址空间,本质上是各个进程都有自己单独的页表。
    //页表是存储在页表寄存器cr3中的,cr3只有一个。在不同进程执行前,我们要在cr3寄存器中为其换上配套的页表,从而实现虚拟地址空间的隔离。
}

/*激活线程/进程的页表,更新tss中的esp0为进程的特权级0的栈*/
void process_activate(struct task_struct* p_thread){
    ASSERT(p_thread != NULL);
    /*激活该进程/线程的页表*/
    page_dir_activate(p_thread);
    /*内核线程特权级本身是0,CPU进入中断时并不会从tss中获取0特权级栈地址,故不需要更新esp0*/
    if(p_thread->pgdir){
        /*更新该进程的esp0,用于此进程被中断时保留上下文*/
        update_tss_esp(p_thread);   //更新tss中的esp0为进程的特权级0栈
        //时钟中断进行进程调度,中断需要用到0特权级的栈
    }
}

/*创建页目录表,将当前页表的表示内核空间的pde复制,成功则返回页目录表的虚拟地址,否则返回-1*/
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_page failed!");
        return NULL;
    }

    /* 1 先复制页表,将内核的页目录项复制到用户进程使用的页目录表中*/
    /*page_dir_vaddr + 0x300*4是内核页目录的第768项 [0x300是十六进制的768,4是页目录项的大小]*/
    memcpy((uint32_t*)((uint32_t)page_dir_vaddr + 0x300*4),(uint32_t*)(0xfffff000 + 0x300*4),1024);     //dst src size
    //0xfffff000 + 0x300*4表示内核页目录表中第768个页目录项的地址,1024/4=256个页目录项的大小,相当于低端1GB的内容
    /*********************************************/

    /* 2 更新页目录地址*/
    uint32_t new_page_dir_phy_addr = addr_v2p((uint32_t)page_dir_vaddr);
    /*页目录地址是存入在页目录的最后一项,更新页目录地址为新页目录的物理地址*/
    page_dir_vaddr[1023] = new_page_dir_phy_addr | PG_US_U | PG_RW_W | PG_P_1;
    /*****************************************************************/

    return page_dir_vaddr;
}

/*创建用户进程虚拟地址位图*/
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;
    bitmap_init(&user_prog->userprog_vaddr.vaddr_bitmap);
}

/*创建用户进程并加入到就绪队列中*/
void process_execute(void* filename,char* name){
    /*PCB内核的数据结构,由内核来维护进程信息,因此要在内核内存池中申请*/
    console_put_str("process_execute");
    console_put_str(name);
    console_put_str("\n");
    struct task_struct* thread = get_kernel_pages(1);
    init_thread(thread,name,default_prio);
    create_user_vaddr_bitmap(thread);
    thread_create(thread,start_process,filename);
    thread->pgdir = create_page_dir();

    enum intr_status old_status = intr_disable();
    ASSERT(!elem_find(&thread_ready_list,&thread->general_tag));
    list_append(&thread_ready_list,&thread->general_tag);

    ASSERT(!elem_find(&thread_all_list,&thread->all_list_tag));
    list_append(&thread_all_list,&thread->all_list_tag);
    intr_set_status(old_status);
}
  • “/home/lily/OS/boot/userprog/process.h”
#ifndef __USERPROG_PROCESS_H
#define __USERPROG_PROCESS_H
#include "stdint.h"

#define USER_STACK3_VADDR (0xc0000000 - 0x1000)
#define USER_VADDR_START 0x8048000  //Linux用户程序的入口地址
#define default_prio 31 // 默认优先级
#define NULL ((void*)0)

//函数声明
/*构建用户进程filename_的初始上下文信息,即struct intr_stack*/
void start_process(void* filename_);

/*激活页表*/
void page_dir_activate(struct task_struct* p_thread);

/*激活线程/进程的页表,更新tss中的esp0为进程的特权级0的栈*/
void process_activate(struct task_struct* p_thread);

/*创建页目录表,将当前页表的表示内核空间的pde复制,成功则返回页目录表的虚拟地址,否则返回-1*/
uint32_t* create_page_dir(void);

/*创建用户进程虚拟地址位图*/
void create_user_vaddr_bitmap(struct task_struct* user_prog);

/*创建用户进程并加入到就绪队列中*/
void process_execute(void* filename,char* name);
#endif
  • “/home/lily/OS/boot/thread/thread.c”
#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
#include "interrupt.h"
#include "list.h"
#include "debug.h"
#include "print.h"
#include "process.h"

#define PG_SIZE 4096

// struct task_struct* main_thread;        //主线程PCB
// struct list thread_ready_list;          //就绪队列
// struct list thread_all_list;            //所有任务队列
static struct list_elem* thread_tag;    //用于保存队列中的线程结点

extern void switch_to(struct task_struct* cur,struct task_struct* next);

/*获取当前线程的pcb指针*/
struct task_struct* running_thread(void){
    uint32_t esp;
    asm("mov %%esp,%0" : "=g"(esp));      //将当前的栈指针(ESP)的值存储到变量esp中。
    return (struct task_struct*)(esp & 0xfffff000);     //取esp整数部分,即pcb起始地址
}

/*由kernel_thread去执行function(func_arg)*/
static void kernel_thread(thread_func* funcion,void* func_arg){
    intr_enable();      //开中断,避免后面的时钟中断被屏蔽,而无法调度其他线程
    funcion(func_arg);
}

/*初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack中相应的位置*/
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg){
    /*先预留中断使用栈的空间*/
    pthread->self_kstack -= sizeof(struct intr_stack);

    /*再预留出线程栈空间*/
    pthread->self_kstack -= sizeof(struct thread_stack);
    
    struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack;    //线程栈指针
    kthread_stack->eip = kernel_thread;
    kthread_stack->function = function;
    kthread_stack->func_arg = func_arg;
    kthread_stack->ebp = kthread_stack->ebx = kthread_stack->edi = kthread_stack->esi = 0;  //寄存器初始化为0
}

/*线程初始化*/
void init_thread(struct task_struct* pthread,char* name,int prio){
    memset(pthread,0,sizeof(*pthread));
    strcpy(pthread->name,name);
    if(pthread == main_thread)
        pthread->status = TASK_RUNNING;
    else
        pthread->status = TASK_READY;

    /*self_kstack是线程自己在内核态下使用的栈顶地址*/
    pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);
    pthread->priority = prio;
    pthread->ticks = prio;
    pthread->elapsed_ticks = 0;
    pthread->pgdir = NULL;
    pthread->stack_magic = 0x19870916;
}

/*创建一优先级为prio,线程名为name,线程所执行函数为function(func_arg)的线程*/
struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg){
    /*pcb都位于内核空间,包括用户进程的pcb也在内核空间*/
    struct task_struct* thread = get_kernel_pages(1);
    init_thread(thread,name,prio);
    thread_create(thread,function,func_arg);
    //确保之前不在就绪队列中
    ASSERT(!elem_find(&thread_ready_list,&thread->general_tag));
    //加入就绪队列
    list_append(&thread_ready_list,&thread->general_tag);
    //确保之前不在队列中
    ASSERT(!elem_find(&thread_all_list,&thread->all_list_tag));
    //加入全部线程队列
    list_append(&thread_all_list,&thread->all_list_tag);

    // asm volatile("movl %0,%%esp; \
    // pop %%ebp;pop %%ebx;pop %%edi;pop %%esi; \
    // ret": : "g"(thread->self_kstack) : "memory");
    // //esp = thread->self_kstack为栈顶;4个出栈,将初始化的0弹出到相应寄存器中;执行ret,将栈顶数据作为返回地址送入CPU的eip中;thread->self_kstack为输入
    return thread;
}

/*将kernel中的main函数完善为主线程*/
static void make_main_thread(void){
    /*因为main线程早已在运行,咱们在loader.S中进入内核时的mov esp,0xc009f000,就是为其预留pcb的。
    因为pcb的地址为0xc009e000,不需要通过get_kernel_page另外分配一页*/
    main_thread = running_thread();
    init_thread(main_thread,"main",31);

    /*main函数是当前线程,当前线程不在thread_ready_list中,所以只将其加在thread_all_list中*/
    ASSERT(!elem_find(&thread_all_list,&main_thread->all_list_tag));
    list_append(&thread_all_list,&main_thread->all_list_tag);
}

/*实现任务调度*/
void schedule(){
    ASSERT(intr_get_status() == INTR_OFF);
    struct task_struct* cur = running_thread();
    if(cur->status == TASK_RUNNING){
        //若此线程只是cpu时间片到了,将其加入就绪队列队尾
        ASSERT(!elem_find(&thread_ready_list,&cur->general_tag));
        list_append(&thread_ready_list,&cur->general_tag);
        cur->ticks = cur->priority;
        cur->status = TASK_READY;
    }else{
        //若此线程需要某事件发生后才能继续上cpu运行,不需要将其放入队列中,因为当前线程不在就绪队列中
    }

    ASSERT(!list_empty(&thread_ready_list));
    thread_tag = NULL;
    thread_tag = list_pop(&thread_ready_list);
    struct task_struct* next = elem2entry(struct task_struct,general_tag,thread_tag);   //将thread_tag转化为线程(链表)
    next->status = TASK_RUNNING;
    /*激活任务页表等*/
    process_activate(next);
    switch_to(cur,next);
}

/*初始化线程环境*/
void thread_init(void){
    put_str("thread_init start\n");
    list_init(&thread_ready_list);
    list_init(&thread_all_list);
    /*将当前main函数创建为线程*/
    make_main_thread();
    put_str("thread_init done\n");
}

/*当前线程将自己阻塞,标志其状态为stat*/
void thread_block(enum task_status stat){
    /*stat取值为TASK_BLOCKED,TASK_WAITING,TASK_HANGING,只有这三种状态才不会被调度*/
    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();     //将当前线程换下CPU
    intr_set_status(old_status);    //待当前线程被解除阻塞后才能继续运行intr_set_status
}

/*将线程pthread接触阻塞*/
void thread_unblock(struct task_struct* pthread){
    enum intr_status old_status = intr_disable();
    ASSERT(((pthread->status == TASK_BLOCKED) || (pthread->status == TASK_WAITING) || (pthread->status == TASK_HANGING)));
    if(pthread->status != TASK_READY){
        ASSERT(!elem_find(&thread_ready_list,&pthread->general_tag));
        if(elem_find(&thread_ready_list,&pthread->general_tag)){
            PANIC("thread_unblock:blocked thread in ready_list\n");
        }
        list_push(&thread_ready_list,&pthread->general_tag);
        pthread->status = TASK_READY;
    }
    intr_set_status(old_status);
}
  • “/home/lily/OS/boot/thread/thread.h”
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"
#include "list.h"
#include "../kernel/memory.h"

/*自定义通用函数类型,它将在很多线程函数中作为形参类型*/
typedef void thread_func(void*);

/*进程或线程的状态*/
enum task_status{
    TASK_RUNNING,
    TASK_READY,
    TASK_BLOCKED,
    TASK_WAITING,
    TASK_HANGING,
    TASK_DIED
};

/*中断栈intr_stack
用于中断发生时保护程序上下文环境:
进程/线程被外部中断/软中断打断时,会按照此结构压入上下文
寄存器,intr_exit中的出栈操作是此结构的逆操作
此栈在线程自己的内核栈中位置固定,所在页的最顶端
*/
struct intr_stack{
    uint32_t vec_no;    //kernel.S宏VECTOR中push %1压入的中断号
    uint32_t edi;
    uint32_t esi;
    uint32_t ebp;
    uint32_t esp_dummy; //虽然pushad把esp也压入了,但esp是不断变化的,所以会被popad忽略
    uint32_t ebx;
    uint32_t edx;
    uint32_t ecx;
    uint32_t eax;
    uint32_t gs;
    uint32_t fs;
    uint32_t es;
    uint32_t ds;

    /*下面由CPU从低特权级进入高特权级时压入*/
    uint32_t err_code;  //err_code会被压入在eip之后
    void (*eip) (void);
    uint32_t cs;
    uint32_t eflags;
    void* esp;
    uint32_t ss;
};

/*线程栈thread_stack
线程自己的栈,用于存储线程中待执行的函数
此结构在线程自己的内核栈中位置不固定
仅用在switch_to时保存线程环境
实际位置取决于实际情况。
*/
struct thread_stack{
    uint32_t ebp;
    uint32_t ebx;
    uint32_t edi;
    uint32_t esi;

    /*线程第一次执行时,eip指向待调用函数kernel_thread,
    其他时候,eip指向switch_to的返回地址*/
    void (*eip) (thread_func* func,void* func_arg);

    /*以下仅供第一次被调度上CPU时使用*/

    /*参数unused_ret只为占位置充数为返回地址*/
    void (*unused_retaddr);     //占位符,基线,表示返回地址
    thread_func* function;      //由kernel_thread所调用的函数名
    void* func_arg;             //由kernel_thread所调用的函数所需的参数
};

/*进程或线程的pcb,程序控制块*/
struct task_struct{
    uint32_t* self_kstack;      //各内核线程都用自己的内核栈
    enum task_status status;
    uint8_t priority;           //线程优先级
    char name[16];
    uint8_t ticks;              //每次在处理器上的嘀咕数
    uint32_t elapsed_ticks;     //此任务自上CPU运行至今用了多少CPU嘀咕数,也就是任务运行了多久
    struct list_elem general_tag;       //用于线程在一般队列中的节点
    struct list_elem all_list_tag;      //用于线程队列thread_all_list中的结点
    uint32_t* pgdir;            //进程自己页表的虚拟地址,如果是线程则为NULL
    struct virtual_addr userprog_vaddr;     //用户进程的虚拟地址
    uint32_t stack_magic;       //栈的边界标记,用于检测栈的溢出
};

struct task_struct* main_thread;        //主线程PCB
struct list thread_ready_list;          //就绪队列
struct list thread_all_list;            //所有任务队列
// static struct list_elem* thread_tag;    //用于保存队列中的线程结点

/*获取当前线程的pcb指针*/
struct task_struct* running_thread(void);
/*初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack中相应的位置*/
void thread_create(struct task_struct* pthread,thread_func function,void* func_arg);

/*线程初始化*/
void init_thread(struct task_struct* pthread,char* name,int prio);

/*创建一优先级为prio,线程名为name,线程所执行函数为function(func_arg)的线程*/
struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg);

/*将kernel中的main函数完善为主线程*/
// static void make_main_thread(void);

/*实现任务调度*/
void schedule(void);

/*初始化线程环境*/
void thread_init(void);

/*当前线程将自己阻塞,标志其状态为stat*/
void thread_block(enum task_status stat);

/*将线程pthread接触阻塞*/
void thread_unblock(struct task_struct* pthread);
#endif
  • “/home/lily/OS/boot/kernel/interrupt.c”
/*初始化可编程中断控制器8259A*/
static void pic_init(void){
    /*初始化主片*/
    outb(PIC_M_CTRL,0x11);          //ICW1:边沿触发,级联8259,需要ICW4
    outb(PIC_M_DATA,0x20);          //ICW2:起始中断向量号为0x20,也就是IR[0-7]为0x20~0x27
    outb(PIC_M_DATA,0x04);          //ICW3:IR2接从片
    outb(PIC_M_DATA,0x01);          //ICW4:8086模式,正常EOI
    /*初始化从片*/
    outb(PIC_S_CTRL,0x11);          //ICW1:边沿触发,级联8259,需要ICW4
    outb(PIC_S_DATA,0x28);          //ICW2:起始中断向量号为0x28,也就是IR[8-15]为0x28~0x2F
    outb(PIC_S_DATA,0x02);          //ICW3:设置从片连接到主片的IR2引脚
    outb(PIC_S_DATA,0x01);          //ICW4:8086模式,正常EOI
    /*打开主片上IR0,打开时钟产生的中断*/
    outb(PIC_M_DATA,0xfe);
    outb(PIC_S_DATA,0xff);
    // /*测试键盘,打开键盘中断*/
    // outb(PIC_M_DATA,0xfd);
    // outb(PIC_S_DATA,0xff);
    /*打开时钟中断和键盘中断*/
    // outb(PIC_M_DATA,0xfc);
    // outb(PIC_S_DATA,0xff);
    put_str("   pic_init done\n");
}
  • “/home/lily/OS/boot/kernel/main.c”
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "keyboard.h"
#include "ioqueue.h"
#include "process.h"

void k_thread_a(void*);
void k_thread_b(void*);
void u_prog_a(void);
void u_prog_b(void);

int test_var_a = 0,test_var_b = 0;

int main(void){
	put_str("I am kernel\n");
	init_all();

	thread_start("consumer_a",31,k_thread_a,"A ");
	thread_start("consumer_b",8,k_thread_b,"B ");
	process_execute(u_prog_a,"user_prog_a");
	process_execute(u_prog_b,"user_prog_b");

	intr_enable();	//打开中断,使时钟中断起作用
	while(1);
	return 0;
}

/*在线程中运行函数*/
void k_thread_a(void* arg){
	char* para = arg;
	while(1){
		console_put_str(" v_a:0x");
		console_put_int(test_var_a);
	}
}

/*在线程中运行函数*/
void k_thread_b(void* arg){
	char* para = arg;
	while(1){
		console_put_str(" v_b:0x");
		console_put_int(test_var_b);
	}
}

/*测试用户进程*/
void u_prog_a(void){
	while (1)
	{
		test_var_a++;
	}
}

/*测试用户进程*/
void u_prog_b(void){
	while (1)
	{
		test_var_b++;
	}
}
  • “/home/lily/OS/boot/makefile”
BUILD_DIR = ./build
##用来存储生成的所有目标文件
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/ -I userprog/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
##-fno-builtin是告诉编译器不要采用内部函数 -Wstrict-prototypes是要求函数声明中必须有参数类型 
## -Wmissing-prototypes要求函数必须有声明
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
	$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o $(BUILD_DIR)/debug.o \
	$(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o \
	$(BUILD_DIR)/switch.o $(BUILD_DIR)/list.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o  \
	$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o $(BUILD_DIR)/process.o
## OBJS用来存储所有目标文件名,不要用%.o,因为不能保证链接顺序

########## c代码编译 ##########
$(BUILD_DIR)/main.o:kernel/main.c lib/kernel/print.h lib/kernel/stdint.h kernel/init.h kernel/memory.h \
	thread/thread.h kernel/interrupt.h device/console.h device/keyboard.h device/ioqueue.h userprog/process.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/init.o:kernel/init.c kernel/init.h lib/kernel/print.h lib/kernel/stdint.h \
	kernel/interrupt.h device/timer.h kernel/memory.h thread/thread.h device/console.h device/keyboard.h \
	userprog/tss.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/interrupt.o:kernel/interrupt.c kernel/interrupt.h lib/kernel/stdint.h \
	kernel/global.h lib/kernel/io.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o:device/timer.c device/timer.h lib/kernel/stdint.h lib/kernel/io.h lib/kernel/print.h thread/thread.c \
	kernel/debug.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/thread.o:thread/thread.c thread/thread.h lib/kernel/stdint.h lib/string.h kernel/global.h kernel/memory.h \
	kernel/interrupt.h lib/kernel/list.h kernel/debug.h lib/kernel/print.h userprog/process.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/debug.o:kernel/debug.c kernel/debug.h lib/kernel/print.h lib/kernel/stdint.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/string.o:lib/string.c lib/string.h kernel/debug.h kernel/global.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/memory.o:kernel/memory.c kernel/memory.h lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h \
	lib/kernel/print.h kernel/global.h thread/sync.h thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/bitmap.o:lib/kernel/bitmap.c lib/kernel/bitmap.h lib/string.h kernel/interrupt.h lib/kernel/print.h \
	kernel/debug.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h kernel/interrupt.h lib/kernel/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h lib/kernel/list.h lib/kernel/stdint.h thread/thread.h lib/string.h \
	kernel/interrupt.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/console.o: device/console.c device/console.h lib/kernel/print.h lib/kernel/stdint.h thread/sync.h thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h lib/kernel/print.h lib/kernel/stdint.h kernel/interrupt.h lib/kernel/io.h \
	kernel/global.h device/ioqueue.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h lib/kernel/stdint.h kernel/interrupt.h  kernel/debug.h thread/thread.h \
	kernel/global.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h lib/kernel/stdint.h thread/thread.h lib/kernel/print.h lib/string.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h thread/thread.h userprog/tss.h device/console.h \
	kernel/memory.h kernel/global.h lib/kernel/bitmap.h kernel/interrupt.h lib/kernel/stdint.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

###########汇编代码编译############
$(BUILD_DIR)/kernel.o:kernel/kernel.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/print.o:lib/kernel/print.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/switch.o:thread/switch.S
	$(AS) $(ASFLAGS) $< -o $@

##########链接所有目标文件#############
$(BUILD_DIR)/kernel.bin:$(OBJS)
	$(LD) $(LDFLAGS) $^ -o $@

.PHONY: mk_dir hd clean all

mk_dir:
	if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi	
###fi为终止符

hd:	
	dd if=$(BUILD_DIR)/kernel.bin of=/home/lily/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc

clean:	##将build目录下文件清空
	cd $(BUILD_DIR) && rm -f ./*

build:$(BUILD_DIR)/kernel.bin	##编译kernel.bin,只要执行make build就是编译文件

all:mk_dir build hd	
##依次执行伪目标mk_dir build hd,只要执行make all就是完成了编译到写入硬盘的全过程

在这里插入图片描述
在这里插入图片描述

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值