A 前情提要 在此之前,第一部分我们完成了基础的环境配置,bochs配置 ,以及MBR,loader 的的基础编写,成功的进入了保护模式 并且开启了内存分页功能
第二部分完成了对内联汇编 ,中断初始化 ,定时器初始化 ,也实现了基础打印函数 *
第三部分完成了对内存管理 ,线程 ,同步 ,也实现了基础生产者与消费者 *
这一部分主要学习用户进程/完善内核(系统调用,完善内存)/硬盘驱动 相关内容
ps:如果参考本系列文章来实操,需要结合《操作系统真象还原》一起观看,否则会缺失很多细节
B 用户进程 这里概念就不多说了(大家可以自己查询TSS–任务状态段),真象还原是仿照Linux的任务切换方法,其中我们也会使用到TSS,但是只是希望为0特权级的任务提供栈
Linux 为每个 CPU 创建一个 TSS,在各个 CPU 上的所有任务共享同一个 TSS,各 CPU 的 TR 寄存器保存各 CPU 上的 TSS,在用 ltr 指令加载 TSS 后,该 TR 寄存器永远指向同一个 TSS,之后再也不会重新加载 TSS。在进程切换时,只需要把 TSS 中的 SS0 及 esp0 更新为新任务的内核栈的段地址及栈指针
Linux 中任务切换不使用 call 和 jmp 指令,这也避免了任务切换的低效
这里我们直接开始写代码吧
B.1 定义并初始化TSS 首先是在global.h中添加等会需要用到的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #ifndef __KERNEL_GLOBAL_H #define __KERNEL_GLOBAL_H #include "stdint.h" #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 #define DESC_S_CODE 1 #define DESC_S_DATA DESC_S_CODE #define DESC_S_SYS 0 #define DESC_TYPE_CODE 8 #define DESC_TYPE_DATA 2 #define DESC_TYPE_TSS 9 #define RPL0 0 #define RPL1 1 #define RPL2 2 #define RPL3 3 #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) #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_DATA << 4) + DESC_TYPE_DATA) #define TSS_DESC_D 0 #define TSS_ATTR_HIGH ((DESC_G_4K << 7) + (TSS_DESC_D << 6) + (DESC_L << 5) + (DESC_AVL << 4) + 0x0) #define TSS_ATTR_LOW ((DESC_P << 7) + (DESC_DPL_0 << 5) + (DESC_S_SYS << 4) + DESC_TYPE_TSS) #define SELECTOR_TSS ((4 << 3) + (TI_GDT << 2 ) + RPL0) struct gdt_desc { uint16_t limit_low_word; uint16_t base_low_word; uint8_t base_mid_byte; uint8_t attr_low_byte; uint8_t limit_high_attr_high; uint8_t base_high_byte; }; #define IDT_DESC_P 1 #define IDT_DESC_DPL0 0 #define IDT_DESC_DPL3 3 #define IDT_DESC_32_TYPE 0xE #define IDT_DESC_16_TYPE 0x6 #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 PG_SIZE 4096 #endif
然后创建一个目录userprog,以后用来存放有关用户进程的文件,同理,添加一个Makefile,修改后和添加的文件放在下面了
下面主要是对tss的初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include "tss.h" #include "stdint.h" #include "global.h" #include "string.h" #include "print.h" 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 ;void update_tss_esp (struct task_struct* pthread) { tss.esp0 = (uint32_t *)((uint32_t )pthread + PG_SIZE); } 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; } 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; tss.io_base = tss_size; *((struct gdt_desc*)0xc0000920 ) = make_gdt_desc((uint32_t *)&tss, tss_size - 1 , TSS_ATTR_LOW, TSS_ATTR_HIGH); *((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); uint64_t gdt_operand = ((8 * 7 - 1 ) | ((uint64_t )(uint32_t )0xc0000900 << 16 )); asm volatile ("lgdt %0" : : "m" (gdt_operand)) ; asm volatile ("ltr %w0" : : "r" (SELECTOR_TSS)) ; put_str("tss_init and ltr done\n" ); }
1 2 3 4 5 6 7 8 #ifndef __USERPROG_TSS_H #define __USERPROG_TSS_H #include "thread.h" void update_tss_esp (struct task_struct* pthread) ;void tss_init (void ) ;#endif
下面是对Makefile的修改,主要还是添加文件依赖,以及头文件包含等,同时修改了之前有个位置写错了(有关用户库和内核库的文件编译重复了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 .PHONY : all kernel user clean img dirsMOUSE_DIR := .. BUILD_DIR := $(MOUSE_DIR) /build BUILD_KERNEL_DIR := $(BUILD_DIR) /kernel BUILD_USER_DIR := $(BUILD_DIR) /user IMG_PATH := /home/mouse/OS_mouse/tool/bochs/hd60M.img CC := gcc ASM := nasm LD := ld CFLAGS := -m32 -fno-builtin -fno-stack-protector -ffreestanding \ -I$(MOUSE_DIR) /lib -I$(MOUSE_DIR) /lib/kernel -I$(MOUSE_DIR) /lib/user \ -I$(MOUSE_DIR) /kernel -I$(MOUSE_DIR) /device -I$(MOUSE_DIR) /thread -I$(MOUSE_DIR) /userprog -c ASMFLAGS := -f elf LDFLAGS := -m elf_i386 -Ttext 0xc0001500 -e main include $(MOUSE_DIR) /lib/kernel/Makefileinclude $(MOUSE_DIR) /lib/user/Makefileinclude $(MOUSE_DIR) /device/Makefileinclude $(MOUSE_DIR) /lib/Makefileinclude $(MOUSE_DIR) /thread/Makefileinclude $(MOUSE_DIR) /userprog/MakefileKERNEL_SRCS := main.c init.c interrupt.c debug.c memory.c KERNEL_ASMS := kernel.S KERNEL_SRCS := $(addprefix $(MOUSE_DIR) /kernel/,$(KERNEL_SRCS) ) KERNEL_ASMS := $(addprefix $(MOUSE_DIR) /kernel/,$(KERNEL_ASMS) ) LIB_KERNEL_SRCS := $(addprefix $(MOUSE_DIR) /lib/kernel/,$(LIB_KERNEL_SRCS) ) LIB_KERNEL_ASMS := $(addprefix $(MOUSE_DIR) /lib/kernel/,$(LIB_KERNEL_ASMS) ) LIB_USER_SRCS := $(addprefix $(MOUSE_DIR) /lib/user/,$(LIB_USER_SRCS) ) LIB_USER_ASMS := $(addprefix $(MOUSE_DIR) /lib/user/,$(LIB_USER_ASMS) ) DEVICE_SRCS := $(addprefix $(MOUSE_DIR) /device/,$(DEVICE_SRCS) ) DEVICE_ASMS := $(addprefix $(MOUSE_DIR) /device/,$(DEVICE_ASMS) ) LIB_SRCS := $(addprefix $(MOUSE_DIR) /lib/,$(LIB_SRCS) ) LIB_ASMS := $(addprefix $(MOUSE_DIR) /lib/,$(LIB_ASMS) ) THREAD_SRCS := $(addprefix $(MOUSE_DIR) /thread/,$(THREAD_SRCS) ) THREAD_ASMS := $(addprefix $(MOUSE_DIR) /thread/,$(THREAD_ASMS) ) USERPROG_SRCS := $(addprefix $(MOUSE_DIR) /userprog/,$(USERPROG_SRCS) ) USERPROG_ASMS := $(addprefix $(MOUSE_DIR) /userprog/,$(USERPROG_ASMS) ) ALL_C_SRCS := $(KERNEL_SRCS) $(LIB_KERNEL_SRCS) $(LIB_USER_SRCS) $(DEVICE_SRCS) $(LIB_SRCS) $(THREAD_SRCS) $(USERPROG_SRCS) ALL_ASMS := $(KERNEL_ASMS) $(LIB_KERNEL_ASMS) $(LIB_USER_ASMS) $(DEVICE_ASMS) $(LIB_ASMS) $(THREAD_ASMS) $(USERPROG_ASMS) OBJS_KERNEL := \ $(patsubst $(MOUSE_DIR) /%.c,$(BUILD_KERNEL_DIR) /%.o,$(filter $(MOUSE_DIR) /%.c,$(filter -out $(MOUSE_DIR) /lib/user/%,$(ALL_C_SRCS) ) )) \ $(patsubst $(MOUSE_DIR) /%.S,$(BUILD_KERNEL_DIR) /%.o,$(filter $(MOUSE_DIR) /%.S,$(filter -out $(MOUSE_DIR) /lib/user/%,$(ALL_ASMS) ) )) OBJS_USER := \ $(patsubst $(MOUSE_DIR) /lib/user/%.c,$(BUILD_USER_DIR) /%.o,$(filter $(MOUSE_DIR) /lib/user/%.c,$(ALL_C_SRCS) ) ) \ $(patsubst $(MOUSE_DIR) /lib/user/%.S,$(BUILD_USER_DIR) /%.o,$(filter $(MOUSE_DIR) /lib/user/%.S,$(ALL_ASMS) ) ) KERNEL_BIN := $(BUILD_KERNEL_DIR) /kernel.bin all: dirs kernel user dirs: @echo "Creating build directories..." @mkdir -p $(BUILD_KERNEL_DIR) $(BUILD_USER_DIR) kernel: $(KERNEL_BIN) user: $(OBJS_USER) $(KERNEL_BIN) : $(OBJS_KERNEL) $(OBJS_USER) @echo "Linking kernel object files to generate kernel.bin..." $(LD) $(LDFLAGS) -o $@ $(filter $(BUILD_KERNEL_DIR) /%.o,$^ ) $(filter $(BUILD_USER_DIR) /%.o,$^ ) @echo "Kernel linking completed!" $(BUILD_KERNEL_DIR) /%.o: $(MOUSE_DIR) /%.c | dirs @echo "Compiling C: $< " @mkdir -p $(@D) $(CC) $(CFLAGS) -o $@ $< $(BUILD_KERNEL_DIR) /%.o: $(MOUSE_DIR) /%.S | dirs @echo "Assembling: $< " @mkdir -p $(@D) $(ASM) $(ASMFLAGS) -o $@ $< $(BUILD_USER_DIR) /%.o: $(MOUSE_DIR) /lib/user/%.c | dirs @echo "Compiling user C: $< " @mkdir -p $(@D) $(CC) $(CFLAGS) -o $@ $< $(BUILD_USER_DIR) /%.o: $(MOUSE_DIR) /lib/user/%.S | dirs @echo "Assembling user S: $< " @mkdir -p $(@D) $(ASM) $(ASMFLAGS) -o $@ $< $(BUILD_USER_DIR) /%.o: $(MOUSE_DIR) /userprog/%.c | dirs @echo "Compiling userprog C: $< " @mkdir -p $(@D) $(CC) $(CFLAGS) -o $@ $< $(BUILD_USER_DIR) /%.o: $(MOUSE_DIR) /userprog/%.S | dirs @echo "Assembling userprog S: $< " @mkdir -p $(@D) $(ASM) $(ASMFLAGS) -o $@ $< img: $(KERNEL_BIN) @echo "Writing kernel image to disk..." dd if=$(KERNEL_BIN) of=$(IMG_PATH) bs=512 count=200 seek=9 conv=notrunc @echo "Kernel image writing completed!" clean: @echo "Cleaning build files..." -rm -f $(BUILD_KERNEL_DIR) /*.o $(BUILD_KERNEL_DIR) /kernel.bin -rm -f $(BUILD_USER_DIR) /*.o @echo "Cleanup completed!" mouse: clean all img @echo "Mouse build process (clean -> all -> img) completed!"
1 2 3 4 5 USERPROG_SRCS := tss.c process.c USERPROG_ASMS :=
注意 ,要在init.c文件中添加函数tss_init()
这些准备工作更加偏向概念,这里就不多说了,大家可以仔细阅读一下原书内容,下面才正式开始实现用户进程
B.2 实现用户进程(1) 首先在要在进程的基础上来实现进程,我们先来大致了解一下原理,做点准备:
进程与内核线程最大的区别是进程有单独的 4GB 空间,这里指的是虚拟地址,每个进程都拥有 4GB 的虚拟地址空间,虚拟地址连续而物理地址可以不连续 ,这就是保护模式下分页机制的优势
为演示此特性,我们需要单独为每个进程维护一个虚拟地址池,用此地址池来记录该进程的虚拟中,哪些已被分配,哪些可以分配。与各个进程相关的数据,如果数据量不大的话,最好是存储在该进程的 PCB 中 ,这样便于管理。在上一节中已经知道,进程是基于线程实现的,因此它和线程一样使用相同的 pcb 结构,即 struct task_struct,我们要做的就是在此结构中增加一个成员,用它来跟踪用户空间虚拟地址的分配情况
下面就来修改thread.h文件,这里给出修改的部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include "memory.h" #include "bitmap.h" struct task_struct { uint32_t * self_kstack; enum task_status status ; uint8_t priority; char name[16 ]; uint8_t ticks; uint32_t elapsed_ticks; struct list_elem general_tag ; struct list_elem all_list_tag ; uint32_t * pgdir; struct virtual_addr userprog_vaddr ; uint32_t stack_magic; };
然后再memory.c中添加相关的功能,在内存池struct pool中添加一个锁 用来在申请内存的时候作为互斥使用,后面添加了在用户内存池分配内存的函数 等,以及让物理地址和虚拟地址建立映射 ,最后再添加一个通过虚拟地址返回物理地址的函数 ,记得还要在初始化函数中添加锁的初始化 ,这里还是给出片段,总体的代码我应该会添加到本节的最后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 #include "thread.h" #include "sync.h" struct pool { struct bitmap pool_bitmap ; uint32_t phy_addr_start; uint32_t pool_size; struct lock lock ; }; 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) { 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; ASSERT((uint32_t )vaddr_start < (0xc0000000 - PG_SIZE)); } return (void *)vaddr_start; } void * get_user_pages (uint32_t pg_cnt) { lock_acquire(&user_pool.lock); void * vadder = malloc_page(PF_USER,pg_cnt); memset (vadder,0 ,pg_cnt*PG_SIZE); lock_release(&user_pool.lock); return vadder; } 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); struct task_struct * cur = running_thread(); int32_t bit_idx = -1 ; if (cur->pgdir != NULL && pf == PF_USER) { 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) { 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 kernelspace 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); return ((*pte & 0xfffff000 ) + (vaddr & 0x00000fff )); } void mem_init () { put_str("mem_init start\n" ); uint32_t mem_bytes_total = (*(uint32_t *)(0xb00 )); lock_init(&kernel_pool.lock); lock_init(&user_pool.lock); mem_pool_init(mem_bytes_total); put_str("mem_init done\n" ); }
咳咳,后面还要继续修改memory.c函数…….
B.3 实现用户进程(2) 因为是用户进程,所以我肯定要进入特权级3,现在又该如何从搞特权级转入低优先级嘞
,一般情况下,CPU 不允许从高特权级转向低特权级,除非是从中断和调用门返回的情况下。咱们系统中不打算使用调用门,因此,咱们进入特权级 3 只能借助从中断返回的方式,但用户进程还没有运行,何谈被中断?更谈不上从中断返回了……但是 CPU 比较呆头呆脑,我们可以骗过 CPU,在用户进程运行之前,使其以为我们在中断处理环境中,这样便“假装”从中断返回
如何假装:首先得在特权级 0 的环境中,其次是执行 iretd 指令
iretd 指令会用到栈中的数据作为返回地址,还会加载栈中 eflags的值到 eflags 寄存器,如果栈中 cs.rpl 若为更低的特权级,处理器的特权级检查通过后,会将栈中 cs 载入到 CS 寄存器,栈中 ss 载入 SS 寄存器,随后处理器进入低特权级,所以在栈中提前准备好数据即可
但是既然涉及到栈的操作了,那不如将进程的上下文也存到栈中,然后通过一系列的pop操作将用户进程的数据装载到寄存器,然后通过iretd指令退出中断,我们在退出中断的函数intr_exit中修改(kernel.S)
即便是假装,退出中断也要经过intr_exit
需要提前准备好栈,在里面装填用户进程的上下文,借用pop出栈的机会将上下文载入CPU寄存器
要在栈中存储CS选择子,将RPL修改为3(用户特权级)
栈中段寄存器的选择子必须指向 DPL 为 3 的内存段 用户进程只能访问DPL为3的内存段
必须使栈中 eflags 的 IF 位为 1 响应新的中断
必须使栈中 eflags 的 IOPL 位为 0 IO操作
下面来开始继续写代码,首先是global添加的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #define IDT_DESC_P 1 #define IDT_DESC_DPL0 0 #define IDT_DESC_DPL3 3 #define IDT_DESC_32_TYPE 0xE #define IDT_DESC_16_TYPE 0x6 #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) #define EFLAGS_IF_0 0 #define EFLAGS_IOPL_3 (3 << 12) #define EFLAGS_IOPL_0 (0 << 12) #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
现在还是在userprog目录下创建process.c/h来实现用户进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #include "process.h" #include "thread.h" #include "interrupt.h" #include "stdint.h" #include "memory.h" #include "console.h" #include "string.h" #include "bitmap.h" #include "debug.h" #include "tss.h" extern void intr_exit (void ) ; extern struct list thread_ready_list ; extern struct list thread_all_list ; void start_process (void * 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; proc_stack->cs = SELECTOR_U_CODE; 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 ); proc_stack->ss = SELECTOR_U_DATA; asm volatile ("movl %0,%%esp; jmp intr_exit" : : "g" (proc_stack) : "memory" ) ; } void page_dir_activate (struct task_struct* p_thread) { uint32_t pagedir_phy_addr = 0x100000 ; if (p_thread->pgdir != NULL ) { pagedir_phy_addr = addr_v2p((uint32_t )p_thread->pgdir); } asm volatile ("movl %0, %%cr3" : : "r" (pagedir_phy_addr) : "memory" ) ; } void process_activate (struct task_struct* p_thread) { ASSERT(p_thread != NULL ); page_dir_activate(p_thread); if (p_thread->pgdir) { update_tss_esp(p_thread); } } uint32_t * create_page_dir (void ) { uint32_t * page_dir_vaddr = get_kernel_pages(1 ); if (page_dir_activate == NULL ) { console_put_str("create_page_dir: get_kernel_page failed!" ); return NULL ; } memcpy ((uint32_t *)((uint32_t )page_dir_vaddr + 0x300 *4 ), (uint32_t *)(0xfffff000 +0x300 *4 ), 1024 ); 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) { 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); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef __USERPROG_PROCESS_H #define __USERPROG_PROCESS_H #include "thread.h" #include "stdint.h" #define default_prio 31 #define USER_STACK3_VADDR (0xc0000000 - 0x1000) #define USER_VADDR_START 0x8048000 void process_execute (void * filename, char * name) ;void start_process (void * filename_) ;void process_activate (struct task_struct* p_thread) ;void page_dir_activate (struct task_struct* p_thread) ;uint32_t * create_page_dir (void ) ;void create_user_vaddr_bitmap (struct task_struct* user_prog) ;#endif
同理要在调度器中更新对进程的处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include "process.h" void schedule () { ASSERT(intr_get_status() == INTR_OFF); struct task_struct * cur = running_thread(); if (cur->status == TASK_RUNNING) { 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 { } 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); next->status = TASK_RUNNING; process_activate(next); switch_to(cur,next); }
B.4 测试用户进程 还是直接在main函数中测试,因为还没有实现根文件系统等,所以这里还是通过在主函数 添加函数来模拟用户级的进程(线程):
同时,因为用户级特权不能直接使用打印的函数,所以这里我们只修改变量,然后让内核线程来打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include "print.h" #include "init.h" #include "interrupt.h" #include "debug.h" #include "string.h" #include "memory.h" #include "thread.h" #include "console.h" #include "process.h" #include "ioqueue.h" #include "keyboard.h" void k_thread_one (void * arg) ;void k_thread_two (void * arg) ;void u_prog_a (void ) ;void u_prog_b (void ) ;int test_var_a = 0 , test_var_b = 0 ; int main () { put_str("I am kernel!\n" ); init_all(); thread_start("k_thread_one" ,8 ,k_thread_one,"A__:" ); thread_start("k_thread_two" ,8 ,k_thread_two,"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_one (void * arg) { char * para = arg; while (1 ) { console_put_str(" v_a:0x" ); console_put_int(test_var_a); } } void k_thread_two (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++; } }
在调试之前,补充一个问题,就是之前在loader.S文件,当我们进入保护模式创建页表之后,我通过info gdt会发现基地址并不是预期的0x900,而是0x903,原因已经补充到了当时出现问题的地方
如果loader.S开头运用了jmp loader_start这一命令,gdt表的基地址就会发生偏移,因为跳转指令本身可能占有3字节,可能会往后偏移,导致后面的tss添加的时候不能以0x900作为gdt基地址
所以之前在初始化tss的时候,地址发生了错误,这样编译运行便会发生报错
这里有两个修改方案,第一个就是不使用jmp loader_start指令,让基地址为正常值,同时修改mbr文件中跳转的命令,让其可以正常跳转到loader_start的位置 ,第二个就是修改初始化tss的时候使用的基地址(先通过info gdt得到真正的基地址,但是这样肯定不推荐的)
其中跳转+0x300,是通过计算得出的,最开始是填充偏移到0x200,然后又添加了256字节的数据区,累计起来就0x300
下面给出第一种方案的修改部分,注意修改之后还要重新编译写入磁盘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 %include "boot.inc" section loader vstart=LOADER_BASE_ADDRGDT_BASE: dd 0x00000000 dd 0x00000000 CODE_DESC: dd 0x0000FFFF dd DESC_CODE_HIGH4 DATA_STACK_DESC: dd 0x0000FFFF dd DESC_DATA_HIGH4 VIDEO_DESC: dd 0x80000007 dd DESC_VIDEO_HIGH4 GDT_SIZE equ $ - GDT_BASE GDT_LIMIT equ GDT_SIZE - 1 times 60 dq 0 SELECTOR_CODE equ (0x0001 <<3 ) + TI_GDT + RPL0 SELECTOR_DATA equ (0x0002 <<3 ) + TI_GDT + RPL0 SELECTOR_VIDEO equ (0x0003 <<3 ) + TI_GDT + RPL0 total_mem_bytes dd 0 align 4 gdt_ptr: dw GDT_LIMIT dd GDT_BASE loadermsg db 'Mosue' ards_buf times 239 db 0 ards_nr dw 0 loader_start:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 %include "boot.inc" SECTION MBR vstart=0x7c00 mov ax ,cs mov ds ,ax mov es ,ax mov ss ,ax mov fs ,ax mov sp ,0x7c00 mov ax ,0xb800 mov gs ,ax mov ax , 0600h mov bx , 0700h mov cx , 0 mov dx , 184fh int 10h mov byte [gs :0x00 ],'1' mov byte [gs :0x01 ],0xA4 mov byte [gs :0x02 ],' ' mov byte [gs :0x03 ],0xA4 mov byte [gs :0x04 ],'M' mov byte [gs :0x05 ],0xA4 mov byte [gs :0x06 ],'B' mov byte [gs :0x07 ],0xA4 mov byte [gs :0x08 ],'R' mov byte [gs :0x09 ],0xA4 mov eax ,LOADER_START_SECTOR mov bx ,LOADER_BASE_ADDR mov cx ,8 call rd_disk_m_16 jmp LOADER_BASE_ADDR + 0x300
然后编译写入运行,就可以得到书中的效果,即数字一直增大,下面我们来验证是否真的是用户特权级3
首先通过指令获取地址
1 nm build/kernel/kernel.bin |grep -P 'u_prog|test_var'
mouse@ubuntu:~/OS_mouse/tool/bochs/mouse$ nm build/kernel/kernel.bin |grep -P 'u_prog|test_var'c0007300 B test_var_ac0007304 B test_var_bc00015e7 T u_prog_ac00015f9 T u_prog_b
得到变量的地址后,我们设置变量u_prog_a的断点,然后查看cs寄存器的值
1 2 3 4 5 lb 0xc00015e7 c sreg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <bochs:1> lb 0xc00015e7 <bochs:2> c (0) Breakpoint 1, 0xc00015e7 in ?? () Next at t=22335944 (0) [0x0000000015e7] 002b:c00015e7 (unk. ctxt): push ebp ; 55 <bochs:3> sreg es:0x0033, dh=0x00cff300, dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit =0xffffffff, Read/Write, Accessed cs:0x002b, dh=0x00cff900, dl=0x0000ffff, valid=1 Code segment, base=0x00000000, limit =0xffffffff, Execute-Only, Non-Conforming, Accessed, 32-bit ss:0x0033, dh=0x00cff300, dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit =0xffffffff, Read/Write, Accessed ds:0x0033, dh=0x00cff300, dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit =0xffffffff, Read/Write, Accessed fs:0x0033, dh=0x00cff300, dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit =0xffffffff, Read/Write, Accessed gs:0x0000, dh=0x00001000, dl=0x00000000, valid=0 ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1 tr :0x0020, dh=0xc0808b00, dl=0x7500006b, valid=1gdtr:base=0xc0000900, limit =0x37 idtr:base=0xc0007320, limit =0x197 <bochs:4>
我们通过调试来设置断点,查看cs寄存器的值,此时 cs 的值为 0x002b,我们关注最低 4 位,其值为 b,换为二进制是 1011,最低 2 位为 rpl,也就是 3,所以可以判断此时用户进程确实是在 3 特权级下,与预期符合
ok呀,终于结束了,代码一多好难找调试找错误啊…….
C 完善内核(系统调用,完善内存) 之前我们用过系统调用,现在我们要自己来实现了
C.1 系统调用简介 系统调用就是让用户进程申请操作系统的帮助,让操作系统帮其完成某项工作,也就是相当于用户进程调用了操作系统的功能,我们还是参照 Linux 系统调用的原理,模仿着它咱们实现一份简易的系统调用版本
下面还是引用部分书中的句子
Linux 系统调用是用中断门来实现的,通过软中断指令 int 来主动发起中断信号。由于要支持的系统功能很多,总不能一个系统功能调用就占用一个中断向量,真要是这样的话整个中断描述符表都不够用呢。Linux 只占用一个中断向量号,即 0x80,处理器执行指令 int 0x80 时便触发了系统调用。为了让用户程序可以通过这一个中断门调用多种系统功能,在系统调用前,Linux 在寄存器 eax 中写入子功能号,例如系统调用 open 和 close 都是不同的子功能号,当用户程序通过int 0x80 进行系统调用时,对应的中断处理例程会根据eax 的值来判断用户进程申请哪种系统调用
Linux中使用的是系统调用 syscall,原型是 int syscall(int number, …),其中的number 是 int 型,这是系统调用号,也就是前面所说的子功能号。不同的子功能需要的参数也是不同的,所以number 后面的“…”表示此函数支持变参,函数 syscall支持不同参数个数的系统调用,在新版本 Linux 中,所有的系统调用功能都可通过这一个函数完成。顺便说一句,函数 syscall 并不是由操作系统提供的,它是由 C 运行库 glibc(GNU 发布的 libc 库版本)提供的,因此 syscall实际上是库函数
书中给出了具体的例子,来讲解这个函数,库函数的syscall是简介使用系统调用的方式,那么肯定有直接使用的方式,也就是操作系统提供的_syscall,但是它已经被linux废弃了(注意,只是废弃了_syscall 这 个符号),因为此方式最多支持6个参数,但是它的实现方式和思路非常简单,我们的简易操作系统就是使用的它
下面咱们拿_syscall3举例,当然还有其他的类型(_syscall是系统调用”族,原型是_syscallX(type,name,type1,arg1,type2,arg2,…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3) \ type name(type1 arg1, type2 arg2, type3 arg3) { \ long __res; \ __asm__ volatile ("push %%ebx; movl %2,%%ebx; int $0x80; pop %%ebx" \ : "=a" (__res) \ : "0" (__NR_##name),"ri" ((long)(arg1)),"c" ((long)(arg2)), \ "d" ((long)(arg3)) : "memory" ); \__syscall_return(type,__res); \ } #define __syscall_return(type, res) \ do { \ if ((unsigned long)(res) >= (unsigned long)(-125)) { \errno = -(res); \ res = -1; \ } \ return (type) (res); \ } while (0)
这里给大伙说明下,Linux 中的系统调用是用寄存器来传递参数的,这些参数需要按照从左到右的顺序依次存入到不同的通用寄存器(除 esp)中。其中,寄存器 eax 用来保存子功能号,ebx 保存第 1 个参数,ecx 保存第 2 个参数,edx 保存第 3 个参数,esi 保存第 4 个参数,edi 保存第 5 个参数。传递参数还可以用栈(内存); 不知道您想过没有,为什么 Linux 用寄存器来传递参数,而不用栈?用寄存器快?肯定是这样的,没有哪个操作系统愿意更慢。不过这个“快”可不是出于存储介质方面的考虑,而是用寄存器传参的步骤少一些,听我慢慢道来。用户进程执行 int 0x80 时还处于用户态,编译器根据 c 调用约定,系统调用所用的参数会被压到用户栈中,这是 3 特权级栈。当 int 0x80 执行后,任务陷入内核态,此时进入了 0 特权级,因此需要用到 0 特权级栈,但系统调用的参数还在 3 特权级的栈中,为了获取用户栈地址,还得在 0 特权级栈中获取处理器自动压入的用户栈的 SS 和 esp 寄存器的值,然后再次从用户栈中获取参数。您看,光传递参数就涉及到了多次内存访问的情况,内存比寄存器要慢,而且步骤很麻烦
宏_syscall 和库函数 syscall 相比,syscall 实现更灵活,对用户来说任何参数个数的系统调用都统一用一种形式,用户只要记住 syscall 就可以了,而宏_syscall 的实现比较死板,针对每种参数个数的系统调用都要有单独的形式,因此支持的参数数量必然有限,而且用户要记住 7 种形式_syscall[0-6],调用时除了输入实参外,还要输入实参的类型,确实有些麻烦,此外这个宏会引发安全漏洞(有兴趣可自行检索相关资料),故必然会被 syscall 取代。
C.2 系统调用的实现 这里仿照 宏_stscall来实现,下面是大体步骤:
(1)用中断门实现系统调用,效仿 Linux 用 0x80 号中断作为系统调用的入口 。 (2)在 IDT 中安装 0x80 号中断对应的描述符,在该描述符中注册系统调用对应的中断处理例程 。 (3)建立系统调用子功能表 syscall_table,利用 eax 寄存器中的子功能号在该表中索引相应的处理函数 。 (4)用宏实现用户空间系统调用接口_syscall,最大支持 3 个参数的系统调用,故只需要完成_syscall[0-3] (其中寄存器传递参数,eax 为子功能号,ebx保存第 1 个参数,ecx 保存第 2 个参数,edx 保存第 3 个参数)
C2.1 增加0x80中断描述符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #define IDT_DESC_CNT 0x81 extern uint32_t syscall_handler (void ) ; static void idt_desc_init (void ) { int i,lastindex = IDT_DESC_CNT-1 ; for (i=0 ;i<IDT_DESC_CNT;i++) { make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]); } make_idt_desc(&idt[lastindex], IDT_DESC_ATTR_DPL3,syscall_handler); put_str("idt_desc_init done\n" ); }
C2.2 实现系统调用接口 在lib/user下创建文件syscall.c,来实现系统调用接口,核心是利用内联汇编传参并处罚中断,优点就是简单容易实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include "syscall.h" #define _syscall0(NUMBER)({ \ int retval; \ asm volatile( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER) \ : "memory" \ ); \ retval; \ }) #define _syscall1(NUMBER, ARG1) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER), "b" (ARG1) \ : "memory" \ ); \ retval; \ }) #define _syscall2(NUMBER, ARG1, ARG2) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER), "b" (ARG1), "c" (ARG2) \ : "memory" \ ); \ retval; \ }) #define _syscall3(NUMBER, ARG1, ARG2, ARG3) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER), "b" (ARG1), "c" (ARG2), "d" (ARG3) \ : "memory" \ ); \ retval; \ })
C2.3 增加 0x80 号中断处理例程 在kernel.S中添加syscall_ handler函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 [bits 32 ] extern syscall_table section .textglobal syscall_handler syscall_handler: push 0 push ds push es push fs push gs pushad push 0x80 push edx push ecx push ebx call [syscall_table + eax *4 ] add esp ,12 mov [esp +8 *4 ],eax jmp intr_exit
然后修改thread.c/h,在结构体添加pid变量/锁,并且增加初始化和获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 typedef int16_t pid_t ;struct lock pid_lock ; struct task_struct { uint32_t * self_kstack; pid_t pid; enum task_status status ; uint8_t priority; char name[16 ]; uint8_t ticks; uint32_t elapsed_ticks; struct list_elem general_tag ; struct list_elem all_list_tag ; uint32_t * pgdir; struct virtual_addr userprog_vaddr ; uint32_t stack_magic; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static pid_t allocate_pid (void ) { lock_acquire(&pid_lock); static pid_t next_pid = 0 ; next_pid++; lock_release(&pid_lock); return next_pid; } void thread_init (void ) { put_str("thread_init start\n" ); list_init(&thread_ready_list); list_init(&thread_all_list); lock_init(&pid_lock); make_main_thread(); put_str("thread_init done" ); }
C2.4 初始化系统调用和实现 sys_getpid 下面在/mouse/userprog下添加syscall-init.c/h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include "syscall-init.h" #include "stdint.h" #include "thread.h" #include "syscall.h" #include "print.h" #define syscall_nr 32 typedef void * syscall; syscall syscall_table[syscall_nr]; uint32_t sys_getpid (void ) { return running_thread()->pid; } void syscall_init (void ) { put_str("syscall_init start\n" ); syscall_table[SYS_GETPID] = sys_getpid; put_str("syscall_init done\n" ); }
1 2 3 4 5 6 7 8 9 10 11 #ifndef __USERPROG_SYSCALL_INIT_H #define __USERPROG_SYSCALL_INIT_H #include "stdint.h" uint32_t sys_getpid (void ) ;void syscall_init (void ) ;#endif
C2.5 添加系统调用 getpid 现在来完善前面写的syscall.c,同时添加syscall.h,来实现我们的第一个系统调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef __LIB_USER_SYSCALL_H #define __LIB_USER_SYSCALL_H #include "stdint.h" enum SYSCALL_NR { SYS_GETPID }; uint32_t getpid (void ) ;#endif
1 2 3 4 5 6 7 8 9 10 11 #include "syscall.h" uint32_t getpid (void ) { return _syscall0(SYS_GETPID); }
C2.6 在用户进程中的系统调用 注意将之前的初始化添加到init.c文件中,同时更新相关子Makefile文件,这里选择在主函数中模拟验证系统调用结果,分别用户内核函数中调用获取pid的函数,然后通过内核线程输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include "print.h" #include "init.h" #include "thread.h" #include "interrupt.h" #include "console.h" #include "process.h" #include "syscall.h" #include "syscall-init.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(); process_execute(u_prog_a, "user_prog_a" ); process_execute(u_prog_b, "user_prog_b" ); thread_start("k_thread_a" , 31 , k_thread_a, "argA " ); thread_start("k_thread_b" , 31 , k_thread_b, "argB " ); intr_enable(); while (1 ); return 0 ; } void k_thread_a (void * arg) { char * para = arg; while (1 ) { console_put_str("ua:" ); console_put_int(test_var_a); console_put_str(" ka:" ); console_put_int(sys_getpid()); console_put_str(" " ); } } void k_thread_b (void * arg) { char * para = arg; while (1 ) { console_put_str("ub:" ); console_put_int(test_var_b); console_put_str(" kb:" ); console_put_int(sys_getpid()); console_put_str(" " ); } } void u_prog_a (void ) { while (1 ) { test_var_a = getpid(); } } void u_prog_b (void ) { while (1 ) { test_var_b = getpid(); } }
到这里,如果屏幕上打印出四个任务(除了主函数)的pid的话,那么就成功啦!
C.3 让用户进程说话
函数占用的也是静态内存,因此也得提前告诉编译器自己占用的内存大小。为了在编译时获取函数调用时所需要的内存空间(这通常是在栈中分配内存单元),编译器要求提供函数声明,声明中描述了函数参数的个数及类型,编译器用它们来计算参数所占据的栈空间。因此编译器不关心函数声明中参数的名称,它只关心参数个数及类型(您懂的,函数声明中的参数可以不包括参数名,但必须包括类型),编译器用这两个信息才能确定为函数在栈中分配的内存大小。重点来了,函数并不是在堆中分配内存,因此它需要提前确定内存空间,这通常取决于参数的个数及类型大小,但编译器却允许函数的参数个数不固定(可变参数),怎么看上去显得那么“动态”?其实可变参数的这种“动态”只是一种幻想,本质上还是静态,这一切得益于编译器采用 C 调用约定来处理函数的传参方式。C调用约定规定:由调用者把参数以从右向左的顺序压入栈中,并且由调用者清理堆栈中的参数 。我们拿格式化输出函数 printf(char* format, arg1, arg2,…)举例,其中的参数 format 就是大伙儿再熟悉不过的包含“%类型字符”的字符串
既然参数是由调用者压入的,调用者当然知道栈中压入了几个参数,参数占用了多少空间,因此无论函数的参数个数是否固定,采用 C 调用约定,调用者都能完好地回收栈空间,不必担心栈溢出等问题
如何知道栈中有多少个参数呢,如何找到它们呢?其实答案全在格式化字符串中,此字符串通常称为 format,在格式化字符串中的字符’%’便是在栈中寻找可变参数的依据,紧跟’%’后面的是类型字符,类型字符表示数据类型和进制相关的内容。格式化字符串中有多少’%’,就在栈中找多少次参数,尽管用户(程序员)输入’%’的数量可以和参数个数不一致,但那是用户自己的事,除非用户愿意搬起石头砸自己的脚,编译器也不会检查它们的数量是否匹配,因为参数处理与否是函数自己的行为,由函数体内的代码决定。总之正常情况下,用户传入可变参数的数量应与 format 中字符’%’的数量匹配 ,以 format 中的’%’作为参数的线索,每找到一个’%’,就到栈中去找一次参数
下面直接通过代码来了解什么是可变参数:
C3.1 实现系统调用write printf 函数来完成的,它是标准 io 函数,printf 函数是“格式化”“输出”函数,但它只是个外壳,真正起到“格式化”作用的是vsprintf函数,真正起“输出”作用的是write系统调用
下面我们来实现一个简易版的write,因为标准实现还需要fd(文件描述符),这个要等到后面学习文件系统的时候才能实现
首先,现在syscall.h中添加新的子功能号:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef __LIB_USER_SYSCALL_H #define __LIB_USER_SYSCALL_H #include "stdint.h" enum SYSCALL_NR { SYS_GETPID, SYS_WRITE }; uint32_t getpid (void ) ;uint32_t write (char * str) ;#endif
1 2 3 4 5 6 7 8 uint32_t write (char * str) { return _syscall1(SYS_WRITE,str); }
同时在syscall_table中注册
1 2 3 4 5 6 7 8 9 10 11 12 void syscall_init (void ) { put_str("syscall_init start\n" ); syscall_table[SYS_GETPID] = sys_getpid; syscall_table[SYS_WRITE] = sys_write; put_str("syscall_init done\n" ); }
1 2 3 4 5 6 7 8 9 10 11 #ifndef __USERPROG_SYSCALL_INIT_H #define __USERPROG_SYSCALL_INIT_H #include "stdint.h" uint32_t sys_getpid (void ) ;void syscall_init (void ) ;#endif
实现方法很简单,就是使用console_put_str输出str,然后通过strlen(str)返回长度
这里就不测试了,下面来实现printf;
C3.2 实现printf int printf(const char *format, ...) 是我们 C 语言标准输出函数,前面说过,printf 是 vsprintf 和 write 的封装,下面我们还需要实现 vprintf 和对可变参数解析的3个宏以及转换函数itoa,本节的目标是使 printf 支持十六进制输出,即完成“%x”的功能
这里简单说一下我的理解:
printf或者可变字符的原理就是,编译器会通过将参数从右向左压入栈,然后我读取第一个参数format,然后取地址再强转成char*,就得到了栈的地址,然后偏移4个单位(32位的char*是这样的,如果想要更严谨,可以选择使用一个内存对齐的宏),得到第一个参数的位置,然后通过读取字符串中的%字符,判断%后面的内容(应该读取什么类型),来按顺序/变量大小读取刚刚获得的栈,然后再拼接到一个字符串中,就实现了”可变参数”,而参数的具体还得取决栈/缓冲区的大小,并不是真的无限可变
下面创建文件/lib/stdio.c/h,具体内容大家可以阅读注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 #include "stdio.h" #include "stdint.h" #include "string.h" #include "syscall.h" #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) #define va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v)) #define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))) #define va_end(ap) (ap = (va_list)0) static void itoa (uint32_t value,char ** buf_ptr_addr,uint8_t base) { uint32_t m = value % base; uint32_t i = value / base; if (i) { itoa(i,buf_ptr_addr,base); } if (m < 10 ) { *((*buf_ptr_addr)++) = m + '0' ; } else { *((*buf_ptr_addr)++) = m - 10 + 'A' ; } } uint32_t vsprintf (char * str,const char * format,va_list ap) { char * buf_ptr = str; const char * index_ptr = format ; char index_char = *index_ptr; int32_t arg_int; char * arg_str; while (index_char) { if (index_char != '%' ) { *(buf_ptr++) = index_char; index_char = *(++index_ptr); continue ; } index_char = *(++index_ptr); switch (index_char) { case 'x' : { arg_int = va_arg(ap,int ); itoa(arg_int,&buf_ptr,16 ); index_char = *(++index_ptr); break ; } case 'c' : { *(buf_ptr++) = va_arg(ap,char ); index_char = *(++index_ptr); break ; } case 's' : { arg_str = va_arg(ap,char *); strcpy (buf_ptr,arg_str); buf_ptr += strlen (arg_str); index_char = *(++index_ptr); break ; } case 'd' : { arg_int = va_arg(ap,int ); if (arg_int < 0 ) { arg_int = 0 - arg_int; *buf_ptr++ = '-' ; } itoa(arg_int,&buf_ptr,10 ); index_char = *(++index_ptr); break ; } } } return strlen (buf_ptr); } uint32_t printf (const char * format,...) { va_list args; va_start(args,format); char buf[1024 ] = {0 }; vsprintf (buf,format,args); va_end(args); return write(buf); } uint32_t sprintf (char * buf, const char * format, ...) { va_list args; uint32_t retval; va_start(args,format); retval = vsprintf (buf,format,args); va_end(args); return retval; }
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __LIB_STDIO_H #define __LIB_STDIO_H #include "stdint.h" typedef char * va_list;uint32_t printf (const char * str,...) ;uint32_t vsprintf (char * str,const char * format,va_list ap) ;uint32_t sprintf (char * buf, const char * format, ...) ;#endif
最后我们在主函数中测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include "print.h" #include "init.h" #include "thread.h" #include "interrupt.h" #include "console.h" #include "process.h" #include "syscall.h" #include "syscall-init.h" #include "stdio.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(); int a = -2 ; char b = 'c' ; char * eee = "mouse" ; printf ("Hello,Word %x %d %c %s\n" ,getpid(),a,b,eee); intr_enable(); while (1 ); return 0 ; } void k_thread_a (void * arg) { char * para = arg; while (1 ) { console_put_str("ua:" ); console_put_int(test_var_a); console_put_str(" ka:" ); console_put_int(sys_getpid()); console_put_str(" " ); } } void k_thread_b (void * arg) { char * para = arg; while (1 ) { console_put_str("ub:" ); console_put_int(test_var_b); console_put_str(" kb:" ); console_put_int(sys_getpid()); console_put_str(" " ); } } void u_prog_a (void ) { while (1 ) { test_var_a = getpid(); } } void u_prog_b (void ) { while (1 ) { test_var_b = getpid(); } }
注意别忘了在Makefile中添加新增的文件,然后make mouse,运行之后就可以看到屏幕上正常显示便是成功啦,但注意我们现在还不支持浮点数
C.4 完善堆内存管理 完善堆内存管理也就是来实现malloc的接口以及底层实现
C4.1 malloc 底层原理 这里要引用一个新的名词:“arena”,这里还是引用书中部分句子
arena 是由“一大块内存”被划分成无数“小内存块”的内存仓库,我们在原有内存管理系统的基础上实现 arena,大伙儿知道,原有系统只能分配 4KB 粒度的内存页框,因此 arena 的这“一大块内存”也是通过 malloc_page 获得的以 4KB 为粒度的内存,根据请求的内存量的大小,arena 的大小也许是 1 个页框,也许是多个页框,随后再将它们平均拆分成多个小内存块。按内存块的大小,可以划分出多种不同规格的 arena,比如一种 arena 中全是 16 字节大小的内存块,故它只响应 16 字节以内的内存分配,另一种arena 中全是 32 字节的内存块,故它只响应 32 字节以内的内存分配。我们平时调用 malloc 申请内存时,操作系统返回的地址其实就是某个内存块的起始地址,操作系统会根据 malloc 申请的内存大小来选择不同规格的内存块。因此,为支持多种容量内存块的分配,我们要提前建立好多种不同容量内存块的 arena
arena 是个提供内存分配的数据结构,它分为两部分,一部分是元信息,用来描述自己内存池中空闲内存块数量,这其中包括内存块描述符指针(后面介绍),通过它可以间接获知本 arena 所包含内存块的规格大小,此部分占用的空间是固定的,约为 12 字节。另一部分就是内存池区域,这里面有无数的内存块,此部分占用 arena 大量的空间。我们把每个内存块命名为 mem_block,它们是内存分配粒度更细的资源,最终为用户分配的就是这其中的一个内存块。在咱们的实现中,针对小内存块的 arena 占用 1 页框内存,除了元信息外的剩下的内存被平均分成多个小内存块
尽管 arena 用小内存块来满足小内存量的分配,但实际上,arena 为内存分配提供了统一的入口,无论申请的内存量是多大,都可以用同一个 arena 来分配内存。小内存块的容量虽然有几种规格,但毕竟是为满足“小”内存量分配的,最大内存块容量不会超过 1024 字节,如果申请的内存量较大,超过 1024 字节,单独的一个小内存块无法满足需求时,这时候您可能想,将多个内存块组合到一起,肯定能满足需求,团结力量大嘛。方法虽具有可行性,但还是太麻烦了,动态维护内存块的信息会增加编程复杂性,这似乎有些像 Linux 的 buddy 系统啦。其实咱们的应用很简单,根本用不着那么麻烦,处理大内存请求时也会创建个 arena,但不会再将它拆分成小内存块,而是直接将整块大内存分配出去,确实有些简单粗暴,但很有效。故此类 arena 没有对应的内存块描述符,元信息中的内存块描述符指针为空
当申请的内存大于 1024 字节时,因此我们对大内存的定义就是大于1024 字节。为什么要以 1024 为界限呢?有这样一个提前,就是用于处理小内存块时,我们为 arena 分配 1 页框也就是 4KB 大小的内存,我们已经介绍过了,每个 arena 都分为两部分,一部分是占用空间很少的元信息,除元信息外的剩余部分才用于内存块的划分,因此,真正用于内存块的部分不足 4KB。内存块是平均划分的,所以最大的内存块肯定要小于 2KB,这里我们以 2 为底的指数方程来划分内存块,因此最大的内存块是1024 字节,也就是说对内存块规格为 1024 字节的 arena 来说,它只有 3 个内存块,每个都是 1024 字节,剩余的部分就浪费了。咱们这里的内存块以 16 字节为起始,向上依次是 32 字节、64 字节、128 字节、256字节、512 字节、1024 字节,总共 7 种规格 ,因此,内存块描述符也就这 7 种。再次强调一下,每种 arena中只有一种规格的内存块,并不是同时包含多种规格,比如要么该 arena 中全是 16 字节大小的内存块,要么全是 512 字节的内存块 。对于小内存块来说,系统为 arena 分配的内存总共为 4KB,因此,不同规格arena 中的内存块数量也是不同的,举例来说,假设 arena 元信息大小为 12 字节,对于内存块规格 16 字节的 arena,其包括的内存块数量是(4096-12)/16,对于内存块规格 128 字节的 arena,其包括的内存块数量是(4096-12)/128
下面还是在代码中理解:
C4.2 底层初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include "list.h" struct mem_block { struct list_elem free_elem ; }; struct mem_block_desc { uint32_t block_size; uint32_t blocks_per_arena; struct list free_list ; }; #define DESC_CNT 7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 struct arena { struct mem_block_desc * desc ; uint32_t cnt; bool large; }; struct mem_block_desc k_block_descs [DESC_CNT ]; struct pool kernel_pool , user_pool ; struct virtual_addr kernel_vaddr ; void block_desc_init (struct mem_block_desc* desc_array) { uint16_t desc_idx,block_size = 16 ; for (desc_idx = 0 ;desc_idx < DESC_CNT;desc_idx++) { desc_array[desc_idx].block_size = block_size; desc_array[desc_idx].blocks_per_arena = (PG_SIZE - sizeof (struct arean))/block_size; list_init(&desc_array[desc_idx].free_list); block_size *=2 ; } } void mem_init () { put_str("mem_init start\n" ); uint32_t mem_bytes_total = (*(uint32_t *)(0xb00 )); lock_init(&kernel_pool.lock); lock_init(&user_pool.lock); mem_pool_init(mem_bytes_total); block_desc_init(k_block_descs); put_str("mem_init done\n" ); }
C4.3 实现sts_malloc 内存资源需要在需要的时候由系统动态创建,创建它的函数就是sys_malloc,下面要在thread.h中对pcb略微改动一下
为了实现用户进程的堆内存管理,在 pcb 中增加了内存块描述符数组 u_block_desc[DESC_CNT],同理,它也要初始化,所以要在process.c中的process_execute中进行初始化工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct task_struct { uint32_t * self_kstack; pid_t pid; enum task_status status ; uint8_t priority; char name[16 ]; uint8_t ticks; uint32_t elapsed_ticks; struct list_elem general_tag ; struct list_elem all_list_tag ; uint32_t * pgdir; struct virtual_addr userprog_vaddr ; struct mem_block_desc u_block_desc [DESC_CNT ]; uint32_t stack_magic; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void process_execute (void * filename,char *name) { 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(); block_desc_init(thread->u_block_desc); 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); }
然后是对内存管理方面的修改memory.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 static struct mem_block* arena2block (struct arena* a,uint32_t idx) { return (struct mem_block*) ((uint32_t )a + sizeof (struct arena) + idx * a->desc->block_size); } static struct arena* block2arena (struct mem_block* b) { return (struct arena*)((uint32_t )b & 0xfffff000 ); } void * sys_malloc (uint32_t size) { enum pool_flags PF ; struct pool * mem_pool ; uint32_t pool_size; struct mem_block_desc * descs ; struct task_struct * cur_thread = running_thread(); if (cur_thread->pgdir == NULL ) { PF = PF_KERNEL; pool_size = kernel_pool.pool_size; mem_pool = &kernel_pool; descs = k_block_descs; } else { PF = PF_USER; pool_size = user_pool.pool_size; mem_pool = &user_pool; descs = cur_thread->u_block_desc; } if (!(size > 0 && size < pool_size)) { return NULL ; } struct arena * a ; struct mem_block * b ; lock_acquire(&mem_pool->lock); if (size>1024 ) { uint32_t page_cnt = DIV_ROUND_UP(size + sizeof (struct arena),PG_SIZE); a = malloc_page(PF,page_cnt); if (a!= NULL ) { memset (a,0 ,page_cnt * PG_SIZE); a->desc = NULL ; a->cnt = page_cnt; a->large = true ; lock_release(&mem_pool->lock); return (void *)(a+1 ); } else { lock_release(&mem_pool->lock); return NULL ; } } else { uint8_t desc_idx; for (desc_idx = 0 ; desc_idx < DESC_CNT ; desc_idx++) { if (size <= descs[desc_idx].block_size) { break ; } } if (list_empty(&descs[desc_idx].free_list)) { a = malloc_page(PF,1 ); if (a == NULL ) { lock_release(&mem_pool->lock); return NULL ; } memset (a,0 ,PG_SIZE); a->desc = &descs[desc_idx]; a->large = false ; a->cnt = descs[desc_idx].blocks_per_arena; uint32_t block_idx; enum intr_status old_status = intr_disable(); for (block_idx = 0 ;block_idx < descs[desc_idx].blocks_per_arena;block_idx++) { b = arena2block(a,block_idx); ASSERT(!elem_find(&a->desc->free_list, &b->free_elem)); list_append(&a->desc->free_list,&b->free_elem); } intr_set_status(old_status); } b = elem2entry(struct mem_block,free_elem, list_pop(&(descs[desc_idx].free_list))); memset (b,0 ,descs[desc_idx].block_size); a = block2arena(b); a->cnt--; lock_release(&mem_pool->lock); return (void *)b; } }
然后我们在头文件添加对应的声明,最后就是对这个函数进行测试了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include "print.h" #include "init.h" #include "thread.h" #include "interrupt.h" #include "console.h" #include "process.h" #include "syscall.h" #include "syscall-init.h" #include "stdio.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("k_thread_a" , 31 , k_thread_a, "argA " ); thread_start("k_thread_b" , 31 , k_thread_b, "argB " ); intr_enable(); while (1 ); return 0 ; } void k_thread_a (void * arg) { char * para = arg; void * addr = sys_malloc(33 ); console_put_str(" I am thread_a, sys_malloc(33), addr is 0x" ); console_put_int((int )addr); console_put_char('\n' ); while (1 ); } void k_thread_b (void * arg) { char * para = arg; void * addr = sys_malloc(63 ); console_put_str(" I am thread_b, sys_malloc(63), addr is 0x" ); console_put_int((int )addr); console_put_char('\n' ); while (1 ); } void u_prog_a (void ) { while (1 ) { test_var_a = getpid(); } } void u_prog_b (void ) { while (1 ) { test_var_b = getpid(); } }
在两个内核线程中分别申请33,63字节的内存,按照我们写的逻辑,都会选择分配64字节的内存,因此 sys_malloc 会创建规格为 64 字节的 arena,然后把它拆分成 64 字节的内存块,由于是第 1 次申请内存且只申请了一种内存块,故系统中只存在这一个 arena,那么我们的结果如果相差16进制的4,也就是十进制的64字节,说明它给分配的就是64字节,符合预期
I am thread_b, sys_malloc(63), addr is OxC010204C I am thread_a, sys_malloc(33), addr is OxC010200C
C4.4 内存的释放 内存管理系统不仅能分配内存,还应该能回收内存,这是最基本的内存管理机制
回忆一下:内存的使用情况都是通过位图来管理的,因此,无论内存的分配或释放,本质上都是在设置相关位图中的相应位,都是在读写位图。回收物理地址就是将物理内存池位图中的相应位清 0,无需将该 4KB 物理页框逐字节清 0。回收虚拟地址就是将虚拟内存池位图中的相应位清 0。分配则是相反的,也就是将位图中相应位置为 1 即可
在之前,我们申请的内存是通过malloc_page中,分别经历在虚拟地址池和物理地址池中分配地址(位图同理) ,在页表中完成虚拟地址和物理地址的映射
而释放内存刚好和这个相反,我们首先要在物理地址池释放物理页地址(位图同理) ,在页表中去掉虚拟地址的映射 (将虚拟地址对应 pte 的 P 位置 0 page_table_ pte_remove),最后在虚拟地址池中释放虚拟地址(vaddr_remove) 下面我们将这几个步骤封装到函数mfree_page中
开始搓代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 void pfree (uint32_t pg_phy_addr) { struct pool * mem_pool ; uint32_t bit_idx = 0 ; if (pg_phy_addr >= user_pool.phy_addr_start) { mem_pool = &user_pool; bit_idx = (pg_phy_addr - user_pool.phy_addr_start)/PG_SIZE; } else { mem_pool = &kernel_pool; bit_idx = (pg_phy_addr - kernel_pool.phy_addr_start)/PG_SIZE; } bitmap_set(&mem_pool->pool_bitmap,bit_idx,0 ); } static void page_table_pte_remove (uint32_t vaddr) { uint32_t * pte = pte_ptr(vaddr); *pte &= ~PG_P_1; asm volatile ("invlpg %0" ::"m" (vaddr):"memory" ) ; } static void vaddr_remove (enum pool_flags pf, void * _vaddr, uint32_t pg_cnt) { uint32_t bit_idx_start = 0 , vaddr = (uint32_t )_vaddr, cnt = 0 ; if (pf == PF_KERNEL) { bit_idx_start = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE; while (cnt < pg_cnt) { bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start + cnt++, 0 ); } } else { struct task_struct* cur_thread = running_thread(); bit_idx_start =(vaddr - cur_thread->userprog_vaddr.vaddr_start) / PG_SIZE; while (cnt < pg_cnt) { bitmap_set(&cur_thread->userprog_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 0 ); } } } void mfree_page (enum pool_flags pf, void * _vaddr, uint32_t pg_cnt) { uint32_t pg_phy_addr; uint32_t vaddr = (int32_t )_vaddr,page_cnt = 0 ; ASSERT(pg_cnt>=1 && vaddr%PG_SIZE == 0 ); pg_phy_addr = addr_v2p(vaddr); ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= 0x102000 ); if (pg_phy_addr >= user_pool.phy_addr_start) { vaddr -= PG_SIZE; while (page_cnt < pg_cnt) { vaddr += PG_SIZE; pg_phy_addr = addr_v2p(vaddr); ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= user_pool.phy_addr_start); pfree(pg_phy_addr); page_table_pte_remove(vaddr); page_cnt++; } vaddr_remove(pf, _vaddr, pg_cnt); } else { vaddr -= PG_SIZE; while (page_cnt < pg_cnt) { vaddr += PG_SIZE; pg_phy_addr = addr_v2p(vaddr); ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= kernel_pool.phy_addr_start && pg_phy_addr < user_pool.phy_addr_start); pfree(pg_phy_addr); page_table_pte_remove(vaddr); page_cnt++; } vaddr_remove(pf, _vaddr, pg_cnt); } }
C4.5 实现sys_free 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 void sys_free (void * ptr) { ASSERT(ptr != NULL ); if (ptr != NULL ) { enum pool_flags PF ; struct pool * mem_pool ; if (running_thread()->pgdir == NULL ) { ASSERT((uint32_t )ptr >= K_HEAP_START); PF = PF_KERNEL; mem_pool = &kernel_pool; } else { PF = PF_USER; mem_pool = &user_pool; } lock_acquire(&mem_pool->lock); struct mem_block * b = ptr; struct arena * a = block2arena(b); ASSERT(a->large == 0 || a->large == 1 ); if (a->desc == NULL && a->large == true ) { mfree_page(PF, a, a->cnt); } else { list_append(&a->desc->free_list, &b->free_elem); if (++a->cnt == a->desc->blocks_per_arena) { uint32_t block_idx; for (block_idx = 0 ;block_idx < a->desc->blocks_per_arena; block_idx++) { struct mem_block * b = arena2block(a, block_idx); ASSERT(elem_find(&a->desc->free_list, &b->free_elem)); list_remove(&b->free_elem); } mfree_page(PF, a, 1 ); } } lock_release(&mem_pool->lock); } }
下面还是在主函数中进行测试,这里在主函数的两个内核线程中进程1000次的获取和释放内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 #include "print.h" #include "init.h" #include "thread.h" #include "interrupt.h" #include "console.h" #include "process.h" #include "syscall-init.h" #include "syscall.h" #include "stdio.h" #include "memory.h" void k_thread_a (void *) ;void k_thread_b (void *) ;void u_prog_a (void ) ;void u_prog_b (void ) ;int main (void ) { put_str("I am kernel\n" ); init_all(); intr_enable(); thread_start("k_thread_a" , 8 , k_thread_a, "I am thread_a" ); thread_start("k_thread_b" , 8 , k_thread_b, "I am thread_b " ); while (1 ); return 0 ; } void k_thread_a (void * arg) { char * para = arg; void * addr1; void * addr2; void * addr3; void * addr4; void * addr5; void * addr6; void * addr7; console_put_str(" thread_a start\n" ); int max = 1000 ; while (max-- > 0 ) { printf ("a: %d\n" ,1000 -max); int size = 128 ; addr1 = sys_malloc(size); size *= 2 ; addr2 = sys_malloc(size); size *= 2 ; addr3 = sys_malloc(size); sys_free(addr1); addr4 = sys_malloc(size); size *= 2 ; size *= 2 ; size *= 2 ; size *= 2 ; size *= 2 ; size *= 2 ; size *= 2 ; addr5 = sys_malloc(size); addr6 = sys_malloc(size); sys_free(addr5); size *= 2 ; addr7 = sys_malloc(size); sys_free(addr6); sys_free(addr7); sys_free(addr2); sys_free(addr3); sys_free(addr4); } console_put_str(" thread_a end\n" ); while (1 ); } void k_thread_b (void * arg) { char * para = arg; void * addr1; void * addr2; void * addr3; void * addr4; void * addr5; void * addr6; void * addr7; void * addr8; void * addr9; int max = 1000 ; console_put_str(" thread_b start\n" ); while (max-- > 0 ) { printf ("b: %d\n" ,1000 -max); int size = 9 ; addr1 = sys_malloc(size); size *= 2 ; addr2 = sys_malloc(size); size *= 2 ; sys_free(addr2); addr3 = sys_malloc(size); sys_free(addr1); addr4 = sys_malloc(size); addr5 = sys_malloc(size); addr6 = sys_malloc(size); sys_free(addr5); size *= 2 ; addr7 = sys_malloc(size); sys_free(addr6); sys_free(addr7); sys_free(addr3); sys_free(addr4); size *= 2 ; size *= 2 ; size *= 2 ; addr1 = sys_malloc(size); addr2 = sys_malloc(size); addr3 = sys_malloc(size); addr4 = sys_malloc(size); addr5 = sys_malloc(size); addr6 = sys_malloc(size); addr7 = sys_malloc(size); addr8 = sys_malloc(size); addr9 = sys_malloc(size); sys_free(addr1); sys_free(addr2); sys_free(addr3); sys_free(addr4); sys_free(addr5); sys_free(addr6); sys_free(addr7); sys_free(addr8); sys_free(addr9); } console_put_str(" thread_b end\n" ); while (1 ); } void u_prog_a (void ) { char * name = "prog_a" ; printf (" I am %s, my pid:%d%c" , name, getpid(),'\n' ); while (1 ); } void u_prog_b (void ) { char * name = "prog_b" ; printf (" I am %s, my pid:%d%c" , name, getpid(), '\n' ); while (1 ); }
我们通过查看内存池位图来验证,在k_thread_a位置打断点
1 2 nm build/kernel/kernel.bin |grep -P 'k_thread_a'
1 2 3 lb 0xc000155f x/3 0xc009a000
我们可以在第一次进入断点查看一次位图,然后再过程中和结束后都查看一下位图,如果结束之后,位图的值一样,就算是获取释放成功啦
1 2 3 4 5 6 7 8 <bochs:1> lb 0xc000155f <bochs:2> c (0) Breakpoint 1, 0xc000155f in ?? () Next at t=18231903 (0) [0x00000000155f] 0008:c000155f (unk. ctxt): push ebp ; 55 <bochs:3> x/3 0xc009a000 [bochs]: 0xc009a000 <bogus+ 0>: 0x00000003 0x00000000 0x00000000
这里的0x00000003,0x3 的二进制是 11,这表示用了 2 个页框,原因是创建两个线程时各用了 1 个页框做其 pcb,因此位图的使用情况与预期相符,等运行结束,也就是输出thread_a/b end的时候,应该和这个数字一样,并且中途如果暂停,位图的值应该会有不同的值
下面就来实现系统调用吧…….
C4.6 实现系统调用malloc && free 这里我们和书中一样,将这两个接口放到syscall.h文件中(Linux是放在stdlib.h中)
下面直接上代码,和之前的系统调用差不多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef __LIB_USER_SYSCALL_H #define __LIB_USER_SYSCALL_H #include "stdint.h" enum SYSCALL_NR { SYS_GETPID, SYS_WRITE, SYS_MALLOC, SYS_FREE }; uint32_t getpid (void ) ;uint32_t write (char * str) ;void * malloc (uint32_t size) ;void free (void * ptr) ;#endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include "syscall.h" #define _syscall0(NUMBER)({ \ int retval; \ asm volatile( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER) \ : "memory" \ ); \ retval; \ }) #define _syscall1(NUMBER, ARG1) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER), "b" (ARG1) \ : "memory" \ ); \ retval; \ }) #define _syscall2(NUMBER, ARG1, ARG2) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER), "b" (ARG1), "c" (ARG2) \ : "memory" \ ); \ retval; \ }) #define _syscall3(NUMBER, ARG1, ARG2, ARG3) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER), "b" (ARG1), "c" (ARG2), "d" (ARG3) \ : "memory" \ ); \ retval; \ }) uint32_t getpid (void ) { return _syscall0(SYS_GETPID); } uint32_t write (char * str) { return _syscall1(SYS_WRITE,str); } void * malloc (uint32_t size) { return (void *)_syscall1(SYS_MALLOC,size); } void free (void * ptr) { _syscall1(SYS_FREE,ptr); }
然后是更新数组syscall_table
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include "syscall-init.h" #include "stdint.h" #include "thread.h" #include "syscall.h" #include "print.h" #include "console.h" #include "string.h" #define syscall_nr 32 typedef void * syscall; syscall syscall_table[syscall_nr]; uint32_t sys_getpid (void ) { return running_thread()->pid; } uint32_t sys_write (char * str) { console_put_str(str); return strlen (str); } void syscall_init (void ) { put_str("syscall_init start\n" ); syscall_table[SYS_GETPID] = sys_getpid; syscall_table[SYS_WRITE] = sys_write; syscall_table[SYS_MALLOC] = sys_malloc; syscall_table[SYS_FREE] = sys_free; put_str("syscall_init done\n" ); }
下面来测试一下吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #include "print.h" #include "init.h" #include "thread.h" #include "interrupt.h" #include "console.h" #include "process.h" #include "syscall-init.h" #include "syscall.h" #include "stdio.h" #include "memory.h" void k_thread_a (void *) ;void k_thread_b (void *) ;void u_prog_a (void ) ;void u_prog_b (void ) ;int main (void ) { put_str("I am kernel\n" ); init_all(); intr_enable(); process_execute(u_prog_a, "u_prog_a" ); process_execute(u_prog_b, "u_prog_b" ); thread_start("k_thread_a" , 31 , k_thread_a, "I am thread_a" ); thread_start("k_thread_b" , 31 , k_thread_b, "I am thread_b" ); while (1 ); return 0 ; } void k_thread_a (void * arg) { void * addr1 = sys_malloc(256 ); void * addr2 = sys_malloc(255 ); void * addr3 = sys_malloc(254 ); console_put_str(" thread_a malloc addr:0x" ); console_put_int((int )addr1); console_put_char(',' ); console_put_int((int )addr2); console_put_char(',' ); console_put_int((int )addr3); console_put_char('\n' ); int cpu_delay = 100000 ; while (cpu_delay-- > 0 ); sys_free(addr1); sys_free(addr2); sys_free(addr3); while (1 ); } void k_thread_b (void * arg) { void * addr1 = sys_malloc(256 ); void * addr2 = sys_malloc(255 ); void * addr3 = sys_malloc(254 ); console_put_str(" thread_b malloc addr:0x" ); console_put_int((int )addr1); console_put_char(',' ); console_put_int((int )addr2); console_put_char(',' ); console_put_int((int )addr3); console_put_char('\n' ); int cpu_delay = 100000 ; while (cpu_delay-- > 0 ); sys_free(addr1); sys_free(addr2); sys_free(addr3); while (1 ); } void u_prog_a (void ) { void * addr1 = malloc (256 ); void * addr2 = malloc (255 ); void * addr3 = malloc (254 ); printf (" prog_a malloc addr:0x%x,0x%x,0x%x\n" , (int )addr1, (int )addr2, (int )addr3); int cpu_delay = 100000 ; while (cpu_delay-- > 0 ); free (addr1); free (addr2); free (addr3); while (1 ); } void u_prog_b (void ) { void * addr1 = malloc (256 ); void * addr2 = malloc (255 ); void * addr3 = malloc (254 ); printf (" prog_b malloc addr:0x%x,0x%x,0x%x\n" , (int )addr1, (int )addr2, (int )addr3); int cpu_delay = 100000 ; while (cpu_delay-- > 0 ); free (addr1); free (addr2); free (addr3); while (1 ); }
本次 main.c 中启了 4 个任务,分别是 2 个用户进程和 2 个内核线程。它们分别都申请了 256、255、254 字节大小的内存,因此它们对应的内存块规格都应该是 256 字节。所有内核线程共享内存空间,因此线程函数 k_thread_a 和 k_thread_b 所申请的内存应该会有地址累加的情况 。用户进程拥有独立的内存空间,因此在申请内存时,都会从自己的堆空间从头算起,并不会产生地址累加的情况。256 的十六进制形式是0x100,比较方便查看地址累加的情况,这就是我们选择规格为 256 字节内存块的原因
下面是正确运行结果:
1 2 3 4 prog_a mal1oc addr:0x804800C,Ox804810C,Ox804820C prog_b malloc addr:Ox804800C,0x804810C,0x804820C thread_a malloc addr:0xC013400C,C013410C,C013420C thread_b malloc addr:0xC013430C,C013440C,C013450C
终于,内存分配到此结束~
D 编写硬盘驱动程序 之前我们写的东西一直都在第一课配环境创建的硬盘中,我们和书中一样,把那个作为启动盘,仅仅存放内核,现在我们再创建一个80M的硬盘来存储文件系统
D.1 硬盘及分区表 创建硬盘还是利用bochs的命令bximage,此命令在bochs安装目/bin/下
D1.1 创建从盘及获取安装的磁盘数 下面给出步骤(原书中图片–画横线的位置是要输入的内容–回车用中文表示)
然后会得到一串内容ata0-master: type=disk, path="hd80M.img", mode=flat, cylinders=162, heads=16, spt=63,我们需要添加到配置文件中,同时要把ata0-master(主盘)修改为ata0-slave(从盘),其他的参数不变
修改后的文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 megs: 32 romimage: file=/home/mouse/OS_mouse/tool/bochs/share/bochs/BIOS-bochs-latest vgaromimage: file=/home/mouse/OS_mouse/tool/bochs/share/bochs/VGABIOS-lgpl-latest boot: disk log : bochs.out mouse: enabled=0 keyboard_mapping: enabled=1, map=/home/mouse/OS_mouse/tool/bochs/share/bochs/keymaps/x11-pc-us.map ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 ata0-master: type =disk, path="hd60M.img" , mode=flat, cylinders=121, heads=16, spt=63 ata0-slave: type =disk, path="hd80M.img" , mode=flat, cylinders=162, heads=16, spt=63
在修改上面文件之前,我们可以通过指令来验证是否添加成功,因为硬盘是通过BIOS来识别的
添加之前,通过指令xp/b 0x475来读取磁盘的数量,再修改文件之前,输出的结果应该是0x01,添加之后如果数字变成离开0x02,就说明添加成功啦
ps:这是通过BIOS来识别写入的,所以要先c,运行BIOS程序之后,才能通过指令读取,否则读出来肯定全都是0x00
D1.2 创建磁盘分区表及简介
文件系统是运行在操作系统中的软件模块,是操作系统提供的一套管理磁盘文件读写的方法和数据组织、存储形式,因此,文件系统=数据结构+算法,哈哈,所以它是程序 。它的管理对象是文件,管辖范围是分区,因此它建立在分区的基础上,每个分区都可以有不同的文件系统
本节的任务是将刚刚创建的磁盘进行分区,使用fdisk工具
(1)硬盘容量=单片容量×磁头数。 (2)单片容量=每磁道扇区数×磁道数×512 字节。 磁道数又等于柱面数,因此将公式 2 代入公式 1 后: 硬盘容量=每磁道扇区数×柱面数×512 字节×磁头数 ata0-slave: type=disk, path=”hd80M.img”, mode=flat, cylinders=162(柱面数), heads=16(磁头), spt=63(每磁道扇区数)
书中这里介绍了磁盘的具体组成和分区的详细内容,这里就只简略写几句
分区是逻辑上划分磁盘空间的方式,归根结底是人为地将硬盘上的柱面扇区划分成不同的分组,每个分组都是单独的分区。各分区都有“描述符”来描述分区本身所在硬盘上的起止界限等信息,在硬盘的MBR 中有个 64 字节“固定大小”的数据结构,这就是著名的分区表,分区表中的每个表项就是一个分区的“描述符”,表项大小是 16 字节,因此 64 字节的分区表总共可容纳 4 个表项,这就是为什么硬盘仅支持 4 个分区的原因(当初硬盘制造者认为,一台机器上顶多安装 4 个操作系统,每个操作系统各占 1 个分区,所以硬盘支持 4 个分区足矣)……….
下面来实操一下分区:
因为fdisk的版本有所不同,所以这里需要指定兼容模式,并且打开柱面设置才可以与书中展示的一致,整体命令如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 fdisk -c="dos" -u="cylinders" ./hd80M.img x c 162 h 16 r n p 1 1 32 n e 4 33 162 p n l 33 50 n 51 75 n 76 90 n 91 120 n 121 162 p l t 5 66 6 66 7 66 8 66 9 66 p w fdisk -l hd80M.img
磁盘分区表(Disk Partition Table)简称 DPT,是由多个分区元信息汇成的表,表中每一个表项都对应一个分区,主要记录各分区的起始扇区地址,大小界限等
分区表是由分区工具如 fdisk 创建的,但却是给操作系统使用的。听我这么一说,大伙儿不要误以为操作系统很“弱”,其实操作系统也可以创建分区表,它有底层硬件的一切操作权限,无所不能,只是在通常情况下操作系统直接安装在某个分区上,所以分区表要在内核安装之前建立好,因此分区工具通常独立于操作系统。有了分区表,操作系统(的文件系统)可以根据各表项中的信息对硬盘进行分区管理,只要按照表项中的信息访问磁盘,就不会出现分区越界的情况。分区表既然称为“表”,这表示各个表项的数据结构一致,因此磁盘分区表就是个数组,此数组长度固定为 4,数组元素是分区元信息的结构
最初的磁盘分区表位于 MBR 引导扇区中,下面是它的结构:
(1)主引导记录 MBR –(偏移 0~0x1BD 的空间,共计 446 字节大小) (2)磁盘分区表 DPT –(偏移 0x1BE~0x1FD 的空间,共64个字节) (3)结束魔数 55AA ,表示此扇区为主引导扇区,里面包含控制程序
来解决原来所支持的分区数太少的问题。大伙儿知道,“分区表的长度固定”才是问题的症结所在。按理来说,扩展分区中包含多少个逻辑分区,扩展分区的分区表中就该有多少表项,可是任何时候新事物的发展都要把“向上兼容”当成头等大事,它既要兼容此固定长度为 4 个分区的分区表,又要突破固定分区数的限制,这似乎有点为难,该怎样设计扩展分区的分区表呢?一个两全其美的方案是视这个扩展分区为总扩展分区,将它划分成多个子扩展分区,每个子扩展分区“在逻辑上”相当于硬盘,因此每个子扩展分区都可以有 1 个分区表 。这样一来,各个分区表的长度依然固定为 4,但是允许有无限多个分区表,分区表项多了,自然支持的分区数就多了。如何将这些分区表组织到一起呢?扩展分区表采用链式结构,将所有子扩展分区的分区表串在一起,形成可容纳无限个分区表的单向链表 。链表是要有结点的,这里的每个分区表就是结点,一般的链表结点除了包括数据外,还必须要包括下一个结点的地址,分区表也采用了这种结构,其表项就分为两部分,一部分是描述逻辑分区的信息,另一部分是描述下一个子扩展分区的地址
要想使用分区,就离不开分区表,逻辑分区也是分区,为了使用它,也需要有元信息来描述它的范围、边界、类型等信息,因此在子扩展分区中也要有分区表来描述这些逻辑分区。分区表本身也要在子扩展分区中占磁盘空间,因此实际情况是每个子扩展分区的空间并不是只有逻辑分区,在个子扩展分区中最开始的扇区(剧透一下,此扇区称为 EBR 引导扇区,马上要介绍它)用于存储此子扩展分区中的分区表,此扇区中的内容也是前 446 字节是引导程序,中间 64 字节是分区表,后 2 字节是 0x55 和 0xAA,您看,它同 MBR 引导扇区的结构相同 。紧随其后的是空闲的一部分扇区,其余剩下的大部分扇区才被用作存储数据的分区,即逻辑分区。因此,子扩展分区包含逻辑分区
扩展分区被划分出多个子扩展分区,每个子扩展分区都有自己的分区表,所以子扩展分区在逻辑上相当于单独的硬盘,各分区表在各个子扩展分区最开始的扇区中,该扇区同MBR 引导扇区结构相同,由于是经扩展分区划分出来的,所以它们称为 EBR,即扩展引导记录
EBR 中分区表的第一分区表项用来描述所包含的逻辑分区的元信息 ,第二分区表项用来描述下一个子扩展分区的地址 ,第三、四表项未用到 。位于 EBR 中的分区表相当于链表中的结点,第一个分区表项存的是分区数据,第二个分区表项存的是后继分区的指针 值得一提的是这两个分区表项都是指向一个分区的起始,起始地址都是个扇区地址,只不过第一个分区表项指向的是该逻辑分区最开始的扇区,此扇区称为操作系统引导扇区,即 OBR 引导扇区 。第二个分区表项指向下一个子扩展分区的 EBR 引导扇区
子扩展分区是在总扩展分区中创建的,子扩展分区的偏移扇区理应以总扩展分区的绝对扇区 LBA 地址为基准,因此,“子扩展分区的绝对扇区 LBA 地址=总扩展分区绝对扇区 LBA 地址+子扩展分区的偏移扇区”。
逻辑分区是在子扩展分区中创建的,逻辑分区的偏移扇区理应以子扩展分区的绝对扇区 LBA 地址为基准,因此,“逻辑分区的绝对扇区 LBA 地址=子扩展分区绝对扇区 LBA 地址+逻辑分区偏移扇区”。这里的子扩展分区就是当前子扩展分区
书中有配图,可以更好的理解,这里就不多说了,理论部分到此为止,写写代码吧:
D.2 硬盘初始化 首先是打开硬盘的中断信号(8259A从片的IRQ14上),主盘和从盘共享同一个中断接口,通过设置硬盘控制器的 device 寄存器中第 4 位的 dev 位指定是哪个触发
第 2 个 ata 通道接在 8259A 从片的 IRQ15 上,该 ata 通道上可支持两个硬盘。来自 8259A从片的中断是由 8259A 主片帮忙向处理器传达的,8259A 从片是级联在 8259A 主片的 IRQ2 接口的,因此为了让处理器也响应来自 8259A 从片的中断,屏蔽中断寄存器必须也把 IRQ2 打开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 static void pic_init (void ) { outb (PIC_M_CTRL, 0x11 ); outb (PIC_M_DATA, 0x20 ); outb (PIC_M_DATA, 0x04 ); outb (PIC_M_DATA, 0x01 ); outb (PIC_S_CTRL, 0x11 ); outb (PIC_S_DATA, 0x28 ); outb (PIC_S_DATA, 0x02 ); outb (PIC_S_DATA, 0x01 ); outb (PIC_M_DATA, 0xf8 ); outb (PIC_S_DATA, 0xbf ); put_str(" pic_init done\n" ); }
然后为了更方便实现调试输出,所以这里实现内核使用的printf,也就是printk,我们将它定义在lib/kernel/stdio-kernel.c中,同时,这里将va_end等宏移动到stdio.h文件中,方便这个文件调用,否则编译会有问题
1 2 3 4 5 6 7 8 9 10 11 #ifndef __LIB_KERNEL_STDIO_KERNEL_H #define __LIB_KERNEL_STDIO_KERNEL_H #include "stdint.h" void printk (const char * format,...) ;#endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include "stdio-kernel.h" #include "stdio.h" #include "console.h" void printk (const char * format,...) { va_list args; va_start(args,format); char buf[1024 ] = {0 }; vsprintf (buf,format,args); va_end(args); console_put_str(buf); }
下面介绍和硬盘相关的数据结构了,它定义在device/ide.h中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #ifndef __DEVICE_IDE_H #define __DEVICE_IDE_H #include "stdint.h" #include "sync.h" #include "bitmap.h" struct partition { uint32_t start_lba; uint32_t sec_cnt; struct disk * my_disk ; struct list_elem part_tag ; char name[8 ]; struct super_block * sb ; struct bitmap block_bitmap ; struct bitmap inode_bitmap ; struct list open_inodes ; }; struct disk { char name[8 ]; struct ide_channel * my_channel ; uint8_t dev_no; struct partition prim_parts [4]; struct partition logic_parts [8]; }; struct ide_channel { char name[8 ]; uint16_t port_base; uint8_t irq_no; struct lock lock ; bool expecting_intr; struct semaphore disk_done ; struct disk devices [2]; }; void ide_init (void ) ;extern uint8_t channel_cnt;extern struct ide_channel channels [];#endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #include "ide.h" #include "sync.h" #include "stdio.h" #include "stdio-kernel.h" #include "interrupt.h" #include "memory.h" #include "debug.h" #include "string.h" #define reg_data(channel) (channel->port_base + 0) #define reg_error(channel) (channel->port_base + 1) #define reg_sect_cnt(channel) (channel->port_base + 2) #define reg_lba_l(channel) (channel->port_base + 3) #define reg_lba_m(channel) (channel->port_base + 4) #define reg_lba_h(channel) (channel->port_base + 5) #define reg_dev(channel) (channel->port_base + 6) #define reg_status(channel) (channel->port_base + 7) #define reg_cmd(channel) (reg_status(channel)) #define reg_alt_status(channel) (channel->port_base + 0x206) #define reg_ctl(channel) reg_alt_status(channel) #define BIT_STAT_BSY 0x80 #define BIT_STAT_DRDY 0x40 #define BIT_STAT_DRQ 0x8 #define BIT_DEV_MBS 0xa0 #define BIT_DEV_LBA 0x40 #define BIT_DEV_DEV 0x10 #define CMD_IDENTIFY 0xec #define CMD_READ_SECTOR 0x20 #define CMD_WRITE_SECTOR 0x30 #define max_lba ((80*1024*1024/512) - 1) uint8_t channel_cnt; struct ide_channel channels [2]; void ide_init () { printk("ide_init start\n" ); uint8_t hd_cnt = *((uint8_t *)(0x475 )); ASSERT(hd_cnt > 0 ); channel_cnt = DIV_ROUND_UP(hd_cnt, 2 ); struct ide_channel * channel ; uint8_t channel_no = 0 ; while (channel_no < channel_cnt) { channel = &channels[channel_no]; sprintf (channel->name, "ide%d" , channel_no); switch (channel_no) { case 0 : channel->port_base = 0x1f0 ; channel->irq_no = 0x20 + 14 ; break ; case 1 : channel->port_base = 0x170 ; channel->irq_no = 0x20 + 15 ; break ; } channel->expecting_intr = false ; lock_init(&channel->lock); sema_init(&channel->disk_done, 0 ); channel_no++; } printk("ide_init done\n" ); }
基础部分的数据结构到这里就初始化完了
D.3 实现 thread_yield 和 idle 线程 这里还要实现一些其他的基础部件,比如thread_yield
thread_yield 定义在 thread.c 中,它的功能是主动把 CPU 使用权让出来,它与 thread_block 的区别是thread_yield 执行后任务的状态是 TASK_READY,即让出 CPU 后,它会被加入到就绪队列中,下次还能继续被调度器调度执行,而 thread_block 执行后任务的状态是 TASK_BLOCKED,需要被唤醒后才能加入到就绪队列,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include "global" struct task_struct * idle_thread ; static void idle (void * arg UNUSED) { while (1 ) { thread_block(TASK_BLOCKED); asm volatile ("sti; hlt" : : : "memory" ) ; } } void thread_yield (void ) { struct task_struct * cur = running_thread(); enum intr_status old_status = intr_disable(); ASSERT(!elem_find(&thread_ready_list, &cur->general_tag)); list_append(&thread_ready_list, &cur->general_tag); cur->status = TASK_READY; schedule(); intr_set_status(old_status); } void thread_init (void ) { put_str("thread_init start\n" ); list_init(&thread_ready_list); list_init(&thread_all_list); lock_init(&pid_lock); make_main_thread(); idle_thread = thread_start("idle" , 10 , idle, NULL ); put_str("thread_init done" ); }
添加的idle线程中有个指令hlt,这个指令的功能让处理器停止执行指令,也就是将处理器挂起 (并不是类似“jmp $”那样空兜 CPU,CPU 利用率 100%),使处理器得到休息,CPU 利用率一下子就掉下来了,在那一小段时间 CPU 利用率为 0。处理器已经停止运行,因此并不会再产生内部异常,唯一能唤醒处理器的就是外部中断 ,当外部发生后,处理器恢复执行后面的指令。处理器需要被唤醒,必须要保证在开中断的情况下执行 hlt,因此内联汇编代码中,先执行 sti,再执行 hlt。顺便说一下,idle_thread 在第一次创建时会被加入到就绪队列,因此会执行一次,然后阻塞
同时,这里用了一个宏,定义在global.h文件中,用来防止编译器警告的(未使用的变量)
1 #define UNUSED __attribute__ ((unused))
D.4 实现简单的休眠 这里还是基础部件,实现简单的休眠
硬盘和 CPU 是相互独立的个体,它们各自并行执行,但由于硬盘是低速设备,其在处理请求时往往消耗很长的时间(不过手册上说最慢的情况也能在 31 秒之内完成),为避免浪费 CPU 资源,在等待硬盘操作的过程中最好把 CPU 主动让出来,让 CPU 去执行其他任务,为实现这种“明智”的行为,我们在 timer.c中定义休眠函数,当然这只是简易版,精度不是很高,能达到目的就可以了
这里的休眠实现就是通过不断的调用thread_yield,将自己添加到就绪列表中,直到达到休眠的时间再继续于运行后面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include "timer.h" #include "io.h" #include "print.h" #include "thread.h" #include "list.h" #include "interrupt.h" #include "debug.h" #define IRQ0_FREQUENCY 100 #define INPUT_FREQUENCY 1193180 #define COUNTER0_VALUE INPUT_FREQUENCY / IRQ0_FREQUENCY #define CONTRER0_PORT 0x40 #define COUNTER0_NO 0 #define COUNTER_MODE 2 #define READ_WRITE_LATCH 3 #define PIT_CONTROL_PORT 0x43 #define mil_seconds_per_intr (1000 / IRQ0_FREQUENCY) uint32_t ticks; static void frequency_set (uint8_t counter_port,\ uint8_t counter_no,\ uint8_t rwl,\ uint8_t counter_mode,\ uint16_t counter_value) { outb(PIT_CONTROL_PORT, (uint8_t )(counter_no << 6 | rwl << 4 | counter_mode << 1 )); outb(counter_port, (uint8_t )counter_value); outb(counter_port, (uint8_t )counter_value >> 8 ); } static void intr_timer_handler (void ) { struct task_struct * cur_thread = running_thread(); ASSERT(cur_thread->stack_magic == 0x22332233 ); cur_thread->elapsed_ticks++; ticks++; if (cur_thread->ticks == 0 ) { schedule(); } else { cur_thread->ticks--; } } static void ticks_to_sleep (uint32_t sleep_ticks) { uint32_t start_tick = ticks; while (ticks - start_tick < sleep_ticks) { thread_yield(); } } void mtime_sleep (uint32_t m_seconds) { uint32_t sleep_ticks = DIV_ROUND_UP(m_seconds, mil_seconds_per_intr); ASSERT(sleep_ticks > 0 ); ticks_to_sleep(sleep_ticks); } void timer_init () { put_str("timer_init start\n" ); frequency_set(CONTRER0_PORT, \ COUNTER0_NO, \ READ_WRITE_LATCH,\ COUNTER_MODE, \ COUNTER0_VALUE); register_handler(0x20 ,intr_timer_handler); put_str("timer_init done\n" ); }
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __TIMER_H #define __TIMER_H #include "stdint.h" void timer_init () ;void mtime_sleep (uint32_t m_seconds) ;#endif
D.5 完善硬盘驱动程序 & 获取硬盘信息,扫描分区表 下面是驱动的完整部分,因为涉及大量书中的概念等,这里只给出代码和测试结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #ifndef __DEVICE_IDE_H #define __DEVICE_IDE_H #include "stdint.h" #include "sync.h" #include "list.h" #include "bitmap.h" struct partition { uint32_t start_lba; uint32_t sec_cnt; struct disk * my_disk ; struct list_elem part_tag ; char name[8 ]; struct super_block * sb ; struct bitmap block_bitmap ; struct bitmap inode_bitmap ; struct list open_inodes ; }; struct disk { char name[8 ]; struct ide_channel * my_channel ; uint8_t dev_no; struct partition prim_parts [4]; struct partition logic_parts [8]; }; struct ide_channel { char name[8 ]; uint16_t port_base; uint8_t irq_no; struct lock lock ; bool expecting_intr; struct semaphore disk_done ; struct disk devices [2]; }; void intr_hd_handler (uint8_t irq_no) ;void ide_init (void ) ;extern uint8_t channel_cnt;extern struct ide_channel channels [];extern struct list partition_list ;void ide_read (struct disk* hd, uint32_t lba, void * buf, uint32_t sec_cnt) ;void ide_write (struct disk* hd, uint32_t lba, void * buf, uint32_t sec_cnt) ;#endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 #include "ide.h" #include "sync.h" #include "io.h" #include "stdio.h" #include "stdio-kernel.h" #include "interrupt.h" #include "memory.h" #include "debug.h" #include "console.h" #include "timer.h" #include "string.h" #include "list.h" #define reg_data(channel) (channel->port_base + 0) #define reg_error(channel) (channel->port_base + 1) #define reg_sect_cnt(channel) (channel->port_base + 2) #define reg_lba_l(channel) (channel->port_base + 3) #define reg_lba_m(channel) (channel->port_base + 4) #define reg_lba_h(channel) (channel->port_base + 5) #define reg_dev(channel) (channel->port_base + 6) #define reg_status(channel) (channel->port_base + 7) #define reg_cmd(channel) (reg_status(channel)) #define reg_alt_status(channel) (channel->port_base + 0x206) #define reg_ctl(channel) reg_alt_status(channel) #define BIT_STAT_BSY 0x80 #define BIT_STAT_DRDY 0x40 #define BIT_STAT_DRQ 0x8 #define BIT_DEV_MBS 0xa0 #define BIT_DEV_LBA 0x40 #define BIT_DEV_DEV 0x10 #define CMD_IDENTIFY 0xec #define CMD_READ_SECTOR 0x20 #define CMD_WRITE_SECTOR 0x30 #define max_lba ((80*1024*1024/512) - 1) uint8_t channel_cnt; struct ide_channel channels [2]; int32_t ext_lba_base = 0 ;uint8_t p_no = 0 , l_no = 0 ; struct list partition_list ; struct partition_table_entry { uint8_t bootable; uint8_t start_head; uint8_t start_sec; uint8_t start_chs; uint8_t fs_type; uint8_t end_head; uint8_t end_sec; uint8_t end_chs; uint32_t start_lba; uint32_t sec_cnt; } __attribute__ ((packed)); struct boot_sector { uint8_t other[446 ]; struct partition_table_entry partition_table [4]; uint16_t signature; } __attribute__ ((packed)); static void select_disk (struct disk* hd) { uint8_t reg_device = BIT_DEV_MBS | BIT_DEV_LBA; if (hd->dev_no == 1 ) { reg_device |= BIT_DEV_DEV; } outb(reg_dev(hd->my_channel), reg_device); } static void select_sector (struct disk* hd, uint32_t lba, uint8_t sec_cnt) { ASSERT(lba <= max_lba); struct ide_channel * channel = hd->my_channel; outb(reg_sect_cnt(channel), sec_cnt); outb(reg_lba_l(channel), lba); outb(reg_lba_m(channel), lba >> 8 ); outb(reg_lba_h(channel), lba >> 16 ); outb(reg_dev(channel), BIT_DEV_MBS | BIT_DEV_LBA | (hd->dev_no == 1 ? BIT_DEV_DEV : 0 ) | lba >> 24 ); } static void cmd_out (struct ide_channel* channel, uint8_t cmd) { channel->expecting_intr = true ; outb(reg_cmd(channel), cmd); } static void read_from_sector (struct disk* hd, void * buf, uint8_t sec_cnt) { uint32_t size_in_byte; if (sec_cnt == 0 ) { size_in_byte = 256 * 512 ; } else { size_in_byte = sec_cnt * 512 ; } insw(reg_data(hd->my_channel), buf, size_in_byte / 2 ); } static void write2sector (struct disk* hd, void * buf, uint8_t sec_cnt) { uint32_t size_in_byte; if (sec_cnt == 0 ) { size_in_byte = 256 * 512 ; } else { size_in_byte = sec_cnt * 512 ; } outsw(reg_data(hd->my_channel), buf, size_in_byte / 2 ); } static bool busy_wait (struct disk* hd) { struct ide_channel * channel = hd->my_channel; uint16_t time_limit = 30 * 1000 ; while (time_limit -= 10 >= 0 ) { if (!(inb(reg_status(channel)) & BIT_STAT_BSY)) { return (inb(reg_status(channel)) & BIT_STAT_DRQ); } else { mtime_sleep(10 ); } } return false ; } void ide_read (struct disk* hd, uint32_t lba, void * buf, uint32_t sec_cnt) { ASSERT(lba <= max_lba); ASSERT(sec_cnt > 0 ); lock_acquire (&hd->my_channel->lock); select_disk(hd); uint32_t secs_op; uint32_t secs_done = 0 ; while (secs_done < sec_cnt) { if ((secs_done + 256 ) <= sec_cnt) { secs_op = 256 ; } else { secs_op = sec_cnt - secs_done; } select_sector(hd, lba + secs_done, secs_op); cmd_out(hd->my_channel, CMD_READ_SECTOR); sema_down(&hd->my_channel->disk_done); if (!busy_wait(hd)) { char error[64 ]; sprintf (error, "%s read sector %d failed!!!!!!\n" , hd->name, lba); PANIC(error); } read_from_sector(hd, (void *)((uint32_t )buf + secs_done * 512 ), secs_op); secs_done += secs_op; } lock_release(&hd->my_channel->lock); } void ide_write (struct disk* hd, uint32_t lba, void * buf, uint32_t sec_cnt) { ASSERT(lba <= max_lba); ASSERT(sec_cnt > 0 ); lock_acquire (&hd->my_channel->lock); select_disk(hd); uint32_t secs_op; uint32_t secs_done = 0 ; while (secs_done < sec_cnt) { if ((secs_done + 256 ) <= sec_cnt) { secs_op = 256 ; } else { secs_op = sec_cnt - secs_done; } select_sector(hd, lba + secs_done, secs_op); cmd_out(hd->my_channel, CMD_WRITE_SECTOR); if (!busy_wait(hd)) { char error[64 ]; sprintf (error, "%s write sector %d failed!!!!!!\n" , hd->name, lba); PANIC(error); } write2sector(hd, (void *)((uint32_t )buf + secs_done * 512 ), secs_op); sema_down(&hd->my_channel->disk_done); secs_done += secs_op; } lock_release(&hd->my_channel->lock); } static void swap_pairs_bytes (const char * dst, char * buf, uint32_t len) { uint8_t idx; for (idx = 0 ; idx < len; idx += 2 ) { buf[idx + 1 ] = *dst++; buf[idx] = *dst++; } buf[idx] = '\0' ; } static void identify_disk (struct disk* hd) { char id_info[512 ]; select_disk(hd); cmd_out(hd->my_channel, CMD_IDENTIFY); sema_down(&hd->my_channel->disk_done); if (!busy_wait(hd)) { char error[64 ]; sprintf (error, "%s identify failed!!!!!!\n" , hd->name); PANIC(error); } read_from_sector(hd, id_info, 1 ); char buf[64 ]; uint8_t sn_start = 10 * 2 , sn_len = 20 , md_start = 27 * 2 , md_len = 40 ; swap_pairs_bytes(&id_info[sn_start], buf, sn_len); printk(" disk %s info:\n SN: %s\n" , hd->name, buf); memset (buf, 0 , sizeof (buf)); swap_pairs_bytes(&id_info[md_start], buf, md_len); printk(" MODULE: %s\n" , buf); uint32_t sectors = *(uint32_t *)&id_info[60 * 2 ]; printk(" SECTORS: %d\n" , sectors); printk(" CAPACITY: %dMB\n" , sectors * 512 / 1024 / 1024 ); } static void partition_scan (struct disk* hd, uint32_t ext_lba) { struct boot_sector * bs = sys_malloc(sizeof (struct boot_sector)); ide_read(hd, ext_lba, bs, 1 ); uint8_t part_idx = 0 ; struct partition_table_entry * p = bs->partition_table; while (part_idx++ < 4 ) { if (p->fs_type == 0x5 ) { if (ext_lba_base != 0 ) { partition_scan(hd, p->start_lba + ext_lba_base); } else { ext_lba_base = p->start_lba; partition_scan(hd, p->start_lba); } } else if (p->fs_type != 0 ) { if (ext_lba == 0 ) { hd->prim_parts[p_no].start_lba = ext_lba + p->start_lba; hd->prim_parts[p_no].sec_cnt = p->sec_cnt; hd->prim_parts[p_no].my_disk = hd; list_append(&partition_list, &hd->prim_parts[p_no].part_tag); sprintf (hd->prim_parts[p_no].name, "%s%d" , hd->name, p_no + 1 ); p_no++; ASSERT(p_no < 4 ); } else { hd->logic_parts[l_no].start_lba = ext_lba + p->start_lba; hd->logic_parts[l_no].sec_cnt = p->sec_cnt; hd->logic_parts[l_no].my_disk = hd; list_append(&partition_list, &hd->logic_parts[l_no].part_tag); sprintf (hd->logic_parts[l_no].name, "%s%d" , hd->name, l_no + 5 ); l_no++; if (l_no >= 8 ) return ; } } p++; } sys_free(bs); } static bool partition_info (struct list_elem* pelem, int arg UNUSED) { struct partition * part = elem2entry(struct partition, part_tag, pelem); printk(" %s start_lba:0x%x, sec_cnt:0x%x\n" ,part->name, part->start_lba, part->sec_cnt); return false ; } void intr_hd_handler (uint8_t irq_no) { ASSERT(irq_no == 0x2e || irq_no == 0x2f ); uint8_t ch_no = irq_no - 0x2e ; struct ide_channel * channel = &channels[ch_no]; ASSERT(channel->irq_no == irq_no); if (channel->expecting_intr) { channel->expecting_intr = false ; sema_up(&channel->disk_done); inb(reg_status(channel)); } } void ide_init () { printk("ide_init start\n" ); uint8_t hd_cnt = *((uint8_t *)(0x475 )); ASSERT(hd_cnt > 0 ); list_init(&partition_list); channel_cnt = DIV_ROUND_UP(hd_cnt, 2 ); struct ide_channel * channel ; uint8_t channel_no = 0 , dev_no = 0 ; while (channel_no < channel_cnt) { channel = &channels[channel_no]; sprintf (channel->name, "ide%d" , channel_no); switch (channel_no) { case 0 : channel->port_base = 0x1f0 ; channel->irq_no = 0x20 + 14 ; break ; case 1 : channel->port_base = 0x170 ; channel->irq_no = 0x20 + 15 ; break ; } channel->expecting_intr = false ; lock_init(&channel->lock); sema_init(&channel->disk_done, 0 ); register_handler(channel->irq_no, intr_hd_handler); while (dev_no < 2 ) { struct disk * hd = &channel->devices[dev_no]; hd->my_channel = channel; hd->dev_no = dev_no; sprintf (hd->name, "sd%c" , 'a' + channel_no * 2 + dev_no); identify_disk(hd); if (dev_no != 0 ) { partition_scan(hd, 0 ); } p_no = 0 , l_no = 0 ; dev_no++; } dev_no = 0 ; channel_no++; } printk("\n all partition info\n" ); list_traversal(&partition_list, partition_info, (int )NULL ); printk("ide_init done\n" ); }
最后将ide_init函数添加到总初始化中,然后运行就可以看到扫描硬盘的结果,应该和之前创建的一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include "init.h" #include "print.h" #include "interrupt.h" #include "timer.h" #include "memory.h" #include "thread.h" #include "console.h" #include "tss.h" #include "syscall-init.h" #include "keyboard.h" #include "stdio-kernel.h" #include "ide.h" void init_all () { put_str("init_all start\n" ); idt_init(); mem_init(); thread_init(); timer_init(); keyboard_init(); tss_init(); syscall_init(); console_init(); intr_enable(); ide_init(); printk("init_all done\n" ); }
运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ide_init start disk sda info: SN:BXHDOO011 MODULE: Generic 1234 SECTORS: 121968 CAPACITY: 59MB disk sdb info: SN: BXHD00012 MODULE: Generic 1234 SECTORS: 163296 CAPACITY: 79MB all partition info sdb1 start_lba:Ox3F, sec_cnt:Ox7DC1 sdb5 start_Iba:Ox7E3F, sec_cnt:Ox46A1 sdb6 start_Iba:OxC51F, sec_cnt:0x6231 sdb7 start_Iba:0x1278F, seC_cnt:Ox3AD1 sdb8 start_Iba:Ox1629F, sec_cnt:Ox75E1 sdb9 start_Iba:Ox1D8BF, sec_cnt:OxA521 ide_init done init_all done
到此结束,这里涉及大量的硬盘相关概念知识在书中,大家可以自己阅读
E 文件系统前的小结 ok呀,也是只剩下两章就要结束这本书了,学校不让我去实习我就只能窝在这里学操作系统了,太难受了……………..
参考文献
[操作系统真象还原 (郑纲) (Z-Library)] — 大家可以自己在网上查找相关资源
留言
有问题请指出,你可以选择以下方式:
在下方评论区留言
邮箱留言