文章目录
- 一、为什么要有任务状态段TSS
- 二、定义并初始化TSS
- 三、实现用户进程
- 1. 实现用户进程的原理
- 2. 用户进程的虚拟地址空间
- --------------------------代码----------------------------
- 3. 为进程创建页表和3特权级栈
- --------------------------代码----------------------------
- 4. 进入特权级3
- 5. 用户进程何时运行?
- 6. 实现用户进程-上
- --------------------------代码----------------------------
- 7. bss简介
- 8. 实现用户进程-下
- --------------------------代码----------------------------
一、为什么要有任务状态段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. 中断+任务门
- 优点:
- 实现简单
- 抢占式多任务调度,使所有任务都有运行的机会
- 中断门中存储的不是TSS选择子,而是目标中断处理程序的代码段选择子及偏移地址,所以CPU并未把此中断门描述符中的中断处理程序当作新的任务。
- CPU使用任务门实现了任务切换。
- 一个完整的任务包括用户空间代码和内核空间代码,只要TR未变,CPU都认识是同一个任务。中断处理程序属于内核代码,当中断处理程序中执行iretd指令从中断返回后:
- 从中断返回到当前任务的中断前代码。
- 当前任务是被嵌套调用的,它会调用自己TSS中“上一个任务的TSS指针”的任务,返回上一个任务。
- 假设当前任务A被中断,CPU进入中断后,它可能的动作有:
- 假设是中断门/陷阱门,执行完中断处理程序后通过iretd返回到任务A中断前的指令部分。
- 假设是任务门,进行任务切换,此时是嵌套调用任务B,任务B在执行期间又发生了中断,进入到了相应的中断门。当执行完中断处理程序后通过iretd返回B。
- 假设是任务门,任务A调用任务B执行,任务B执行完后通过iretd指令返回任务A,使任务A继续完成后续的指令。
- iretd如何避免被返回错任务?
- NT位:Nest Task Flag,用于表示任务嵌套。
- 当调用一个新任务时,CPU做了两件准备工作:
- 将NT置1,表示该任务是被其他任务调用。
- 将旧任务的TSS写入新任务TSS的“上一个任务的TSS指针”字段。
- 当中断发生时,CPU会将NT位和TF位置0。如果是中断门描述符,CPU还会将IF位置0,防止中断嵌套,引发GP异常。
- 不管CPU将eflags中的值改成什么样,这都是进入中断后的事情。任务eflags中原本的值已经在进入中断前压入栈中保存了,iretd退出中断时这些值会被恢复。
- 当中断发生时,通过任务门进行任务切换的过程:
- 从该任务描述符中取出任务的TSS选择子。
- 用新任务的TSS选择子在GDT中索引TSS描述符。
- 判断TSS描述符的P位是否为1(对应的TSS是否在内存中),若为0则异常。
- 从TR寄存器中获取旧任务的TSS位置,保存旧任务(当前任务)的环境到旧TSS中。其中任务状态是值CPU中寄存器的值,包括8个通用寄存器、6个段寄存器、指令指针寄存器eip、栈指针寄存器esp、页表寄存器cr3和标志寄存器eflags等。
- 恢复新任务环境,即把新任务TSS中的值加载到相应寄存器中。
- 使TR寄存器指向新任务的TSS。
- 将新任务(当前任务)的TSS描述符的B位置1。(表示该任务在运行)
- 将新任务的标志寄存器eflags的NT位置1。
- 将旧任务的TSS选择子写入新任务TSS中“上一个任务的TSS指针”中。
- 开始执行新任务。
- 调用iretd返回旧任务,此时CPU检查NT位为1,则:
- 将当前任务(新任务)的NT位置0。
- 将当前任务TSS描述符的B位置0。
- 将当前任务的状态信息写入TR指向的TSS中。
- 获取当前任务TSS中“上一个任务的TSS指针”字段的值,将其加载到TR中,恢复旧任务的状态。
- 执行旧任务。
2. call/jmp+TSS选择子/任务门选择子
- TSS中已经包含了任务的详细信息,任务门描述符中又包含了TSS选择子,所以TSS和任务门可以作为call和jmp的操作数。操作数中包含偏移量,CPU只用选择子部分即可,会忽略偏移量。
call 0x0010:0x1234 //假设任务门选择子定义在GDT中第2个描述符位置
call 0x0018:0x1234 //假设TSS选择子定义在GDT中第3个描述符位置
- 任务切换步骤如下:
- CPU忽略偏移量0x1234,拿选择子0x0018在GDT中索引到第3个描述符。
- 检查描述符中的P位,看其在不在内存中,若为0则异常。同时检查S和TYPE位,判断其类型,如果是TSS描述符,检查其B位看其是否在运行,若为1则抛出GP异常。
- 进行特权级检查,CPL和TSS选择子中的RPL的特权都有>TSS描述符的DPL,否则抛出GP异常。
- 将当前任务的状态保存到寄存器TR指向的TSS中。
- 加载新任务TSS选择子到TR寄存器的选择器部分,同时将TSS描述符中的起始地址和偏移量等属性加载到TR寄存器中的描述符缓冲器中。
- 将新任务TSS中寄存器数据载入到相应的寄存器中,同时进行特权级检查。
- CPU会将新任务的标志寄存器eflags中的NT位置1。
- 将旧任务TSS选择子写入新任务TSS字段“上一个任务的TSS指针”中。
- 将新任务的TSS描述符中的B位置1,旧任务的TSS描述符的B位不变,也是1。(jmp指针会将旧任务的B位清零)旧任务的标志寄存器中的NT位不变。
- 开始执行新任务。
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. 实现用户进程的原理
- 在thread_start()中,先通过get_kernel_pages(1)在内核内存池获取1页物理页做线程的PCB,即thread。
- 接着调用init_thread()初始化该线程的PCB信息。
- 然后用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. 用户进程何时运行?
- 进程的运行是由时钟中断调用schedule调度实现的,调用了process_activate来激活进程/线程的相关资源,随后通过switch_to函数调度进程。
- switch_to第一次执行时会返回kernel_thread,在switch_to中退出后,CPU会执行kernel_thread函数,相当于switch_to调用kernel_thread。
- 在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就是完成了编译到写入硬盘的全过程