A 前情提要 在此之前,第一部分我们完成了基础的环境配置,bochs配置 ,以及MBR,loader 的的基础编写,成功的进入了保护模式 并且开启了内存分页功能
第二部分完成了对内联汇编 ,中断初始化 ,定时器初始化 ,也实现了基础打印函数 *
这一部分主要学习内存管理/线程/同步 相关内容
ps:如果参考本系列文章来实操,需要结合《操作系统真象还原》一起观看,否则会缺失很多细节
B 实现 assert 断言 断言其实就是在 C 语言中学过的 assert
在我们系统中,我们实现两种断言,一种是为内核系统使用的ASSERT,另一种是为用户进程使用的assert,用户进程离现在还早,咱们本节先实现专供内核使用的 ASSERT。
一方面,当内核运行中出现问题时,多属于严重的错误,着实没必要再运行下去了。另一方面,断言在输出报错信息时,屏幕输出不应该被其他进程干扰,这样咱们才能专注于报错信息。综上两点原因,
ASSERT 排查出错误后,最好在关中断的情况下打印报错信息。内核运行时,为了通过时钟中断定时调度其他任务,大部分情况下中断是打开的,如何在开中断的情况下把中断关闭,这就是我们等会要实现的内容
首先实现开关中断 ,这里我们在interrupt.c和interrupt.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 #ifndef __INTERRUPUT_H #define __INTERRUPUT_H #include "stdint.h" typedef void * intr_handler;void idt_init () ;enum intr_status { INTR_OFF, INTR_ON }; enum intr_status intr_enable () ; enum intr_status intr_disable () ; enum intr_status intr_get_status () ; enum intr_status intr_set_status (enum intr_status status) ; #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 #include "interrupt.h" #include "stdint.h" #include "global.h" #include "print.h" #include "io.h" #define IDT_DESC_CNT 0x33 #define PIC_M_CTRL 0x20 #define PIC_M_DATA 0x21 #define PIC_S_CTRL 0xa0 #define PIC_S_DATA 0xa1 #define EFLAGS_IF 0X00000200 #define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR)) struct gate_desc { uint16_t func_offset_low_word; uint16_t selector; uint8_t dcount; uint8_t attribute; uint16_t func_offset_high_word; }; static void make_idt_desc (struct gate_desc* p_gdesc,uint8_t attr, intr_handler function) ;static struct gate_desc idt [IDT_DESC_CNT ]; extern intr_handler intr_entry_table[IDT_DESC_CNT]; static void exception_init (void ) ; static void general_intr_handler (uint8_t vec_nr) ; const char * intr_name[IDT_DESC_CNT]; intr_handler idt_table[IDT_DESC_CNT]; 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, 0xfe ); outb (PIC_S_DATA, 0xff ); put_str(" pic_init done\n" ); } static void make_idt_desc (struct gate_desc* p_gdesc,uint8_t attr, intr_handler function) { p_gdesc->func_offset_low_word = (uint32_t )function & 0x0000FFFF ; p_gdesc->selector = SELECTOR_K_CODE; p_gdesc->dcount = 0 ; p_gdesc->attribute = attr; p_gdesc->func_offset_high_word = ((uint32_t )function & 0Xffff0000 ) >>16 ; } static void idt_desc_init (void ) { int i; for (i=0 ;i<IDT_DESC_CNT;i++) { make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]); } put_str("idt_desc_init done\n" ); } static void general_intr_handler (uint8_t vec_nr) { if (vec_nr == 0x27 || vec_nr == 0x2f ) { return ; } put_str("int vector : 0x" ); put_int(vec_nr); put_str("\n" ); } static void page_fault_handler (uint8_t vec_nr) { if (vec_nr == 0x27 || vec_nr == 0x2f ) { return ; } put_str("int vector : 0x" ); put_int(vec_nr); put_str("\n" ); uint32_t faulting_address; asm volatile ("mov %%cr2, %0" : "=r" (faulting_address)) ; put_str("Page fault at address: 0x" ); put_int(faulting_address); put_str("\n" ); } static void exception_init (void ) { int i; for (i=0 ;i<IDT_DESC_CNT;i++) { idt_table[i] = general_intr_handler; intr_name[i] = "unknown" ; } idt_table[14 ] = page_fault_handler; intr_name[0 ] = "#DE Divide Error" ; intr_name[1 ] = "#DB Debug Exception" ; intr_name[2 ] = "NMI Interrupt" ; intr_name[3 ] = "#BP Breakpoint Exception" ; intr_name[4 ] = "#OF Overflow Exception" ; intr_name[5 ] = "#BR BOUND Range Exceeded Exception" ; intr_name[6 ] = "#UD Invalid Opcode Exception" ; intr_name[7 ] = "#NM Device Not Available Exception" ; intr_name[8 ] = "#DF Double Fault Exception" ; intr_name[9 ] = "Coprocessor Segment Overrun" ; intr_name[10 ] = "#TS Invalid TSS Exception" ; intr_name[11 ] = "#NP Segment Not Present" ; intr_name[12 ] = "#SS Stack Fault Exception" ; intr_name[13 ] = "#GP General Protection Exception" ; intr_name[14 ] = "#PF Page-Fault Exception" ; intr_name[16 ] = "#MF x87 FPU Floating-Point Error" ; intr_name[17 ] = "#AC Alignment Check Exception" ; intr_name[18 ] = "#MC Machine-Check Exception" ; intr_name[19 ] = "#XF SIMD Floating-Point Exception" ; } enum intr_status intr_enable () { enum intr_status old_status ; if (INTR_ON == intr_get_status()) { old_status = INTR_ON; return old_status; } else { old_status = INTR_OFF; asm volatile ("sti" ) ; return old_status; } } enum intr_status intr_disable () { enum intr_status old_status ; if (INTR_ON == intr_get_status()) { old_status = INTR_ON; asm volatile ("cli" : : : "memory" ) ; return old_status; } else { old_status = INTR_OFF; return old_status; } } enum intr_status intr_set_status (enum intr_status status) { return (status & INTR_ON) ?intr_enable() : intr_disable(); } enum intr_status intr_get_status () { uint32_t eflags = 0 ; GET_EFLAGS(eflags); return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF; } void idt_init () { put_str("idt_init start\n" ); idt_desc_init(); exception_init(); pic_init(); uint64_t idt_operand = ((sizeof (idt) - 1 ) | ((uint64_t )((uint32_t )idt << 16 ))); asm volatile ("lidt %0" ::"" (idt_operand)) ; put_str("idt_init done\n" ); }
然后来实现断言功能 : 我们来仿照C语言来实现,ASSERT(条件表达式) 如果表达式成立则什么都不做,如果不成立则打印出错信息并停止执行,在路径/mouse/kernel下添加debug.c debug.h文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef __KERNEL_DEBUG_H #define __KERNEL_DEBUG_H void panic_spin (char * filename,int line,const char * func,const char * condition) ; #define PANIC(...) panic_spin (__FILE__, __LINE__, __func__, __VA_ARGS__) #ifdef NDEBUG #define ASSERT(CONDITION) ((VOID) 0 ) #else #define ASSERT(CONDITION) if (CONDITION){} else { PANIC(#CONDITION); } #endif #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include "debug.h" #include "print.h" #include "interrupt.h" void panic_spin (char * filename,int line,const char * func,const char * condition) { intr_disable(); put_str("\n\n\n!!!!! error !!!!!\n" ); put_str("filename:" ); put_str((char *)filename);put_str("\n" ); put_str("line:0x" );put_int(line);put_str("\n" ); put_str("function:" ); put_str((char *)func);put_str("\n" ); put_str("condition:" );put_str((char *)condition);put_str("\n" ); while (1 ); }
最后修改主函数,主动进入断言:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include "print.h" #include "init.h" #include "interrupt.h" #include "debug.h" int main () { put_str("I am kernel!\n" ); init_all(); ASSERT(1 == 2 ); while (1 ); return 0 ; }
然后make make img 运行就能看到相关提示了,比如函数名,错误行号,错误的内容等
C 实现字符串操作函数 为了后面的开发更加方便,这里实现与字符串相关的函数,此后这里的函数会被经常用到
这里在lib文件下建立 string.c文件,Makefile 文件和之前一样,已经在上一章中介绍过了,注意解除Makefile里面的注释,让编译器编译string.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 #include "string.h" #include "global.h" #include "debug.h" void memset (void * dst_, uint8_t value, uint32_t size) { ASSERT(dst_ != NULL ); uint8_t * dst = (uint8_t *)dst_; while (size-- > 0 ) *dst++ = value; } void memcpy (void * dst_, const void * src_, uint32_t size) { ASSERT(dst_ != NULL && src_ != NULL ); uint8_t * dst = dst_; const uint8_t * src = src_; while (size-- > 0 ) *dst++ = *src++; } int memcmp (const void * a_, const void * b_, uint32_t size) { const char * a = a_; const char * b = b_; ASSERT(a != NULL || b != NULL ); while (size-- > 0 ) { if (*a != *b) { return *a > *b ? 1 : -1 ; } a++; b++; } return 0 ; } char * strcpy (char * dst_, const char * src_) { ASSERT(dst_ != NULL && src_ != NULL ); char * r = dst_; while ((*dst_++ = *src_++)); return r; } uint32_t strlen (const char * str) { ASSERT(str != NULL ); const char * p = str; while (*p++); return (p - str - 1 ); } int8_t strcmp (const char * a, const char * b) { ASSERT(a != NULL && b != NULL ); while (*a != 0 && *a == *b) { a++; b++; } return *a < *b ? -1 : *a > *b; } char * strchr (const char * str, const uint8_t ch) { ASSERT(str != NULL ); while (*str != 0 ) { if (*str == ch) { return (char *)str; } str++; } return NULL ; } char * strrchr (const char * str, const uint8_t ch) { ASSERT(str != NULL ); const char * last_char = NULL ; while (*str != 0 ) { if (*str == ch) { last_char = str; } str++; } return (char *)last_char; } char * strcat (char * dst_, const char * src_) { ASSERT(dst_ != NULL && src_ != NULL ); char * str = dst_; while (*str++); --str; while ((*str++ = *src_++)); return dst_; } uint32_t strchrs (const char * str, uint8_t ch) { ASSERT(str != NULL ); uint32_t ch_cnt = 0 ; const char * p = str; while (*p != 0 ) { if (*p == ch) { ch_cnt++; } p++; } return ch_cnt; }
同时这里要添加 NULL 的定义,可以先暂时添加到 stdint.h文件中:
编译写入运行后大家就可以在主函数中调用这些函数来测试,比如 strlen("1234"),然后用put_int输出验证等等
D 位图 bitmap 及其函数的实现 位图,也就是 bitmap,广泛用于资源管理,是一种管理资源的方式、手段。“资源”包括很多,比如内存或硬盘,对于此类大容量资源的管理一般都会采用位图的方式
位是指 bit,即字节中的位,1 字节中有 8 个位。图是指 map,map 这个词在很久之前就介绍过啦,地图本质上就是映射的意思,映射,即对应关系。综合起来,位图就是用字节中的 1 位来映射其他单位大小的资源,按位与资源之间是一对一的对应关系
总结一下,位图相当于一组资源的映射。位图中的每一位和被管理的单位资源都是一对一的关系,故位图主要用于管理容量较大的资源
这里还是通过实际的位图代码来理解,这里在mouse/lib/kernel/下创建 bitmap.c bitmap.h文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef __LIB_KERNEL_BITMAP_H #define __LIB_KERNEL_BITMAP_H #include "global.h" #define BITMAP_MASK 1 struct bitmap { uint32_t btmp_bytes_len; uint8_t * bits; }; void bitmap_init (struct bitmap* btmp) ;bool bitmap_scan_test (struct bitmap* btmp,uint32_t bit_idx) ;int bitmap_scan (struct bitmap* btmp,uint32_t cnt) ;void bitmap_set (struct bitmap* btmp,uint32_t bit_idx,int8_t value) ;#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 #include "bitmap.h" #include "string.h" #include "print.h" #include "interrupt.h" #include "debug.h" void bitmap_init (struct bitmap* btmp) { memset (btmp->bits,0 ,btmp->btmp_bytes_len); } bool bitmap_scan_test (struct bitmap* btmp,uint32_t bit_idx) { uint32_t byte_idx = bit_idx/8 ; uint32_t bit_odd = bit_idx%8 ; return (btmp->bits[byte_idx]) & (BITMAP_MASK << bit_odd); } int bitmap_scan (struct bitmap* btmp,uint32_t cnt) { uint32_t idx_byte = 0 ; while ((0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)) { idx_byte++; } ASSERT(idx_byte < btmp->btmp_bytes_len); if (idx_byte == btmp->btmp_bytes_len) { return -1 ; } int idx_bit = 0 ; while ((uint8_t )(BITMAP_MASK << idx_bit) & btmp->bits[idx_byte]) { idx_bit++; } int bit_idx_start = idx_byte*8 + idx_bit; if (cnt == 1 ) { return bit_idx_start; } uint32_t bit_left = (btmp->btmp_bytes_len*8 - bit_idx_start); uint32_t next_bit = bit_idx_start+1 ; uint32_t count = 1 ; bit_idx_start = -1 ; while (bit_left-- > 0 ) { if (!(bitmap_scan_test(btmp,next_bit))) { count++; } else { count = 0 ; } if (count == cnt) { bit_idx_start = next_bit - cnt +1 ; break ; } next_bit++; } return bit_idx_start; } void bitmap_set (struct bitmap* btmp,uint32_t bit_idx,int8_t value) { ASSERT((value == 0 ) || (value ==1 )); uint32_t byte_idx = bit_idx/8 ; uint32_t bit_odd = bit_idx %8 ; if (value) { btmp->bits[byte_idx] |= (BITMAP_MASK <<bit_odd); } else { btmp->bits[byte_idx] &= ~(BITMAP_MASK <<bit_odd); } }
注意还要添加Makefile里面的c资源bitmap.c,下面就开始初入内存管理吧:
E 内存管理系统
用户程序所占用的内存空间是由操作系统分配的,内存是如何分配的并且该给用户进程分配多少字节呢?这就是咱们要解决的问题。 所以,从现在起,咱们要循序渐进地实现内存管理系统,一直到函数 malloc 和 free 的完成
E.1 内存池规划
这里我们首先来规划物理内存池,这里将它分成两部分,一部分称为用户物理内存池 ,一部分称为内核物理内存池 (只给操作系统使用)
同时,每次分配内存也是按单位大小来获取,这个单位的大小是 4KB,也称为页,所以内存池管理的是一个个为4KB的内存块,从内存池中获取的内存大小至少为 4KB 或者为 4KB 的倍数(当然,以后会实现更颗粒度的内存管理)
前情回顾:
在分页机制下程序中的地址都是虚拟地址,虚拟地址的范围取决于地址总线的宽度,咱们是在 32 位环境下,所以虚拟地址空间为 4GB。除了地址空间比较大以外,分页机制的另一个好处是每个任务都有自己的 4GB 虚拟地址空间,也就是各程序中的虚拟地址不会与其他程序冲突,都可以为相同的虚拟地址,不仅用户进程是这样,内核也是;同时虚拟地址池的单位也是4KB
同时这里也是分为用户虚拟地址池 ,内核虚拟地址池
下面还是直接动手实践: 在/mouse/kernel目录下建立 memory.h memory.c,有关内存管理的代码都放在这里,同时记得要在Makefile 里面添加东西:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #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; }; extern struct pool kernel_pool , user_pool ;void mem_init (void ) ;#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 #include "memory.h" #include "stdint.h" #include "print.h" #define PG_SIZE 4096 #define MEM_BITMAP_BASE 0xc009a000 #define K_HEAP_START 0xc0100000 struct pool { struct bitmap pool_bitmap ; uint32_t phy_addr_start; uint32_t pool_size; }; struct pool kernel_pool , user_pool ; struct virtual_addr kernel_vaddr ; static void mem_pool_init (uint32_t all_mem) { put_str(" mem_pool_init start\n" ); uint32_t page_table_size = PG_SIZE * 256 ; uint32_t used_mem = page_table_size + 0x100000 ; 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(user_pool.phy_addr_start); put_str("\n" ); bitmap_init(&kernel_pool.pool_bitmap); bitmap_init(&user_pool.pool_bitmap); 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" ); }
最后将mem_init()函数添加到 init.c中即可,同时如果编译报错bool未定义 的话可以在stdint.h中添加定义如下:
1 2 3 4 5 6 7 #define BOOL int #define bool int #define true 1 #define false 0 #define TRUE 1 #define FALSE 0 #define NULL ((void*)0)
然后make make img ,运行就可以看到结果大致如下:
Mosuel am kernel! init all idt init start idt desc init done pic init done idt init done timer init start timer init donemem init startmem pool init start kernel pool bitmap _start:c009A000 kernel pool >phy_addr_start:200000user pool bitmap start:c009A1E0 user pool phy_addr start:1100000mem >pool init donemem init done
大家可以仔细看看代码中的注释,下面就是如何来分配内存了
E.2 分配页内存 有了内存池之后,就是分配内存了,我们先来学习如何分配页内存(支持一次分配 n 个页的内存,即n*4096 字节)
这里分为了三步,也是我们等会要实现的
(1)首先处理高 10 位的 pde 索引,从而处理器得到页表物理地址。 (2)其次处理中间 10 位的 pte 索引,进而处理器得到普通物理页的物理地址。 (3)最后是把低 12 位作为普通物理页的页内偏移地址,此偏移地址加上物理页的物理地址,得到的地址之和便是最终的物理地址,处理器到此物理地址上进行读写操作。
下面还是修改之前的两个文件:
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 #include "memory.h" #include "stdint.h" #include "print.h" #include "string.h" #include "bitmap.h" #include "debug.h" #define PG_SIZE 4096 #define PDE_IDX(addr) ((addr & 0xffc00000) >> 22) #define PTE_IDX(addr) ((addr & 0x003FF000) >> 12) #define MEM_BITMAP_BASE 0xc009a000 #define K_HEAP_START 0xc0100000 struct pool { struct bitmap pool_bitmap ; uint32_t phy_addr_start; uint32_t pool_size; }; struct pool kernel_pool , user_pool ; struct virtual_addr kernel_vaddr ; 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 { } return (void *)vaddr_start; } uint32_t * pte_ptr (uint32_t vaddr) { uint32_t * pte = (uint32_t *)(0xffc00000 + \ ((vaddr & 0xffc00000 ) >> 10 ) + \ PTE_IDX(vaddr) * 4 ); return pte; } uint32_t * pde_ptr (uint32_t vaddr) { uint32_t * pde = (uint32_t *)((0xfffff000 ) + PDE_IDX(vaddr) * 4 ); return pde; } 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 = ((bit_idx * PG_SIZE) + m_pool->phy_addr_start); return (void *)page_phyaddr; } static void page_table_add (void * _vaddr, void * _page_phyaddr) { uint32_t vaddr = (uint32_t )_vaddr, page_phyaddr = (uint32_t )_page_phyaddr; uint32_t * pde = pde_ptr(vaddr); uint32_t * pte = pte_ptr(vaddr); if (*pde & 0x00000001 ) { ASSERT(!(*pte & 0x00000001 )); 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); memset ((void *)((int )pte & 0xfffff000 ), 0 , PG_SIZE); *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); } } void * malloc_page (enum pool_flags pf, uint32_t pg_cnt) { ASSERT(pg_cnt > 0 && pg_cnt < 3840 ); void * vaddr_start = vaddr_get(pf, pg_cnt); if (vaddr_start == NULL ) { return NULL ; } uint32_t vaddr = (uint32_t )vaddr_start, cnt = pg_cnt; struct pool * mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool; while (cnt-- > 0 ) { void * page_phyaddr = palloc(mem_pool); if (page_phyaddr == NULL ) { return NULL ; } page_table_add((void *)vaddr, page_phyaddr); vaddr += PG_SIZE; } return vaddr_start; } void * get_kernel_pages (uint32_t pg_cnt) { void * vaddr = malloc_page(PF_KERNEL, pg_cnt); if (vaddr != NULL ) { memset (vaddr, 0 , pg_cnt * PG_SIZE); } return vaddr; } static void mem_pool_init (uint32_t all_mem) { put_str(" mem_pool_init start\n" ); uint32_t page_table_size = PG_SIZE * 256 ; uint32_t used_mem = page_table_size + 0x100000 ; 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(user_pool.phy_addr_start); put_str("\n" ); bitmap_init(&kernel_pool.pool_bitmap); bitmap_init(&user_pool.pool_bitmap); 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" ); }
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 #ifndef __KERNEL_MEMORY_H #define __KERNEL_MEMORY_H #include "stdint.h" #include "bitmap.h" enum pool_flags { PF_KERNEL = 1 , PF_USER = 2 }; #define PG_P_1 1 #define PG_P_0 0 #define PG_RW_R 0 #define PG_RW_W 2 #define PG_US_S 0 #define PG_US_U 4 struct virtual_addr { struct bitmap vaddr_bitmap ; uint32_t vaddr_start; }; extern struct pool kernel_pool , user_pool ;void mem_init (void ) ;void * get_kernel_pages (uint32_t pg_cnt) ;#endif
下面说一下大概流程:
首先在mem_init 函数中通过读取0xb00地址处的内容,来获取总内存大小,这个是之前我们写的loader.S文件中自动获取的,然后调用mem_pool_init进行实际的内存池初始化;
实际的内存管理主要分为几个部分:
内存池的初始化(主要包括先计算实际页表+低端1MB使用的空间,然后得到剩余的可用内存),同时也要给位图(用来管理这些位图)单独分配一块内存(0xc009a00),从而可以规划内核物理内存池,用户物理内存池的,虚拟内存池等的起始地址
然后是虚拟地址分配的函数:这里分为:先用位图扫描算法查找空闲的虚拟页,然后标记已经使用,最后返回虚拟地址
物理页的分配函数: 同样是扫描物理页的位图,然后计算对应的物理地址并返回
最重要的就是建立虚拟地址到物理地址的映射,并且当虚拟地址对应的物理地址对应的页目录项不存在的时候,也要分配一个物理页作为新的页表
这里总结一下我们申请内存的流程:
用户使用 malloc 等函数来申请 ,然后系统分配一个虚拟地址 (但是暂时不分配实际的物理内存),然后操作系统如果是第一次访问这个新分配的虚拟地址的时候,发现如果还没有分配页表项 ,这个时候操作系统寻找一个新的物理页创建一个页表 ,然后建立当时分配的虚拟地址和页表(物理地址)之间的映射
F 线程 这里首先介绍线程,进程在后面介绍
F.1 什么是线程 还是引用书中的介绍:
在处理器数量不变的情况下,多任务操作系统采用了一种称为多道程序设计的方式,使处理器在所有任务之间来回切换,这样就给用户一种所有任务并行运行的错觉,通过任务调度器 来实现
任务调度器就是操作系统中用于把任务轮流调度上处理器运行的一个软件模块,它是操作系统的一部分。调度器在内核中维护一个任务表(也称进程表、线程表或调度表),然后按照一定的算法,从任务表中选择一个任务,然后把该任务放到处理器上运行,当任务运行的时间片到期后,再从任务表中找另外一个任务放到处理器上运行,周而复始,让任务表中的所有任务都有机会运行。正是因为有了调度器,多任务操作系统才能得以实现,它是多任务系统的核心,它的好坏直接影响了系统的效率
优点是可以让每个任务都可以”同时”执行,缺点也有,那就是每次切换任务的时候也是需要耗费时间的,也就是会是整体的执行时间变长了一些,但这个代价可以使得一些重要紧急的任务及时完成
处理器只知道加电后按照程序计数器中的地址不断地执行下去,在不断执行的过程中,我们把程序计数器中的下一条指令地址所组成的执行轨迹称为程序的控制执行流,让我们再深入描述一下。执行流就是一段逻辑上独立的指令区域,是人为给处理器安排的处理单元。指令是具备“能动性”的数据,因此只有指令才有“执行”的能力,它相当于是动作的发出者,由它指导处理器产生相应的行为。指令是由处理器来执行的,它引领处理器“前进”的方向,用“流”来表示处理器中程序计数器的航向,借此比喻处理器依次把此区域中的指令执行完后,所形成的像河流一样曲直不一的执行轨迹、执行路径(由顺序执行指令及跳转指令导致)执行流是独立的,它的独立性体现在每个执行流都有自己的栈、一套自己的寄存器映像和内存资源,这是 Intel 处理器在硬件上规定的,其实这正是执行流的上下文环境
F.2 进程的身份证–PCB 虽然我们知道了大概得多任务操作系统,但是这里会有很多问题
(1)要加载一个任务上处理器运行,任务由哪来?也就是说,调度器从哪里才能找到该任务? (2)即使找到了任务,任务要在系统中运行,其所需要的资源从哪里获得? (3)即使任务已经变成进程运行了,此进程应该运行多久呢?总不能让其独占处理器吧。 (4)即使知道何时将其换下处理器,那当前进程所使用的这一套资源(寄存器内容)应该存在哪里? (5)进程被换下的原因是什么?下次调度器还能把它换上处理器运行吗? (6)前面都说过了,进程独享地址空间,它的地址空间在哪里? 当然还有很多很多问题
为了解决这些问题,操作系统为每个进程提供了一个PCB,即程序控制块(Process Control Block),用它来记录与这个进程相关的信息,比如优先级、PID、进程状态等等,同时PCB的具体格式取决于操作系统到功能复杂度
最后,操作系统单独维护一个进程表,将所有的PCB结构加载到这个表,然后由调度器来找对应的PCB,从而获取相关的信息,将寄存器映像(里面会保存进程的”现场”)加载到处理,然后新的进程就开始运行了,同时新进程的栈使用的也是PCB里面的栈
通过上面的描述,会发现PCB一般都很大,通常都是以页为单位,我们的这个简易操作系统比较小,就只占一页
F.3 实现线程的方式 这里有两种方式,一种是内核实现(0特权级),一种是用户实现(3特权级),但是两种都是为了用户进程服务的,所以线程必须可以运行用户的代码。下面主要介绍一下二者的优缺点:
优点 :
首先是容易移植,即便移动到一个不支持线程的操作系统上,同样也可以支持线程的用户程序
因为是用户程序自己实现,所以可以根据实际情况为某些线程加权调度
将线程的寄存器映像装载到 CPU 时,可以在用户空间完成,即不用陷入到内核态,这样就免去了进入内核时的入栈及出栈操作
缺点 :
进程中的某个线程出现了阻塞(通常由系统调用造成),但是操作系统并不知道这个进程中有线程,于是将整个进程挂起,这样所有的线程都无法运行了
同时用户实现的时候,没有保险的机制使处理让出使用权给子线程,只能调用类似 pthread_yield 或 pthread_exit 之类的方法使线程发扬“高风亮节”让出处理器使用权,此类方法通过回调方式触发进程内的线程调度器,让调度器有机会选择进程内的其他线程上处理器运行
同时虽然减少了陷入内核态的步骤,相当于提速了,但是整个进程占用的处理器的时间片是有限的,再分给线程,那其实反而抵消了内部调度带来的提速
注意,这里所说的“实现线程”是指由内核提供原生线程机制,用户进程中不再单独实现
优点 :
相比在用户空间中实现线程,内核提供的线程相当于让进程多占了处理器资源,比如系统中运行有进程 A 和一传统型进程 B,此时进程 A 中显式创建了 3 个线程,这样一来,进程 A 加上主线程便有了 4 个线程,加上进程 B,内核调度器眼中便有了 5 个独立的执行流,尽管其中 4 个都属于进程 A,但对调度器来说这 4个线程和进程一样被调度,因此调度器调度完一圈后,进程 A 使用了 80%的处理器资源,这才是真正的提速
实现了较大幅度的提速,理由如上
当某一个线程阻塞的时候,因为是由内核空间实现的,所以只会阻塞这一个线程,其它线程并不受影响,也相当于提速
缺点 :
用户进程需要通过系统调用陷入内核,这多少增加了一些现场保护的栈操作,这还是会消耗 些处理器时间,但和上面的大幅度提速相比,这不算什么大事
以上就是两种实现方式,这里我们选择使用内核空间实现,还比较快 ~
F.4 在内核空间实现线程 下面先来构造 PCB 相关的基础部分,这里添加一个文件夹thread和两个文件thread.h thread.c,下面还是直接看代码吧,我注释都写在代码里面了 同时Makefile也要更改,以后我会直接将修改的Makfile和添加的Makefile放在代码后面,大家可以自己展开参考代码:
F4.1 PCB/线程的实现 最后的效果就是可以一直打印创建的线程传入的参数,但现在还不能多线程,请大家慢慢往下看
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 #ifndef __KERNEL_THREAD_H #define __KERNEL_THREAD_H #include "stdint.h" typedef void thread_func (void *) ;enum task_status { TASK_RUNNING, TASK_READY, TASK_BLOCKED, TASK_WAITNG, TASK_HANGING, TASK_DIED }; struct intr_stack { uint32_t vec_no; uint32_t edi; uint32_t esi; uint32_t ebp; uint32_t esp_dummy; uint32_t ebx; uint32_t edx; uint32_t ecx; uint32_t eax; uint32_t gs; uint32_t fs; uint32_t es; uint32_t ds; uint32_t err_code; void (*eip) (void ); uint32_t cs; uint32_t eflags; void * esp; uint32_t ss; }; struct thread_stack { uint32_t ebp; uint32_t ebx; uint32_t edi; uint32_t esi; void (*eip) (thread_func* func, void * func_arg); void (*unused_retaddr); thread_func* function; void * func_arg; }; struct task_struct { uint32_t * self_kstack; enum task_status status ; uint8_t priority; char name[16 ]; uint32_t stack_magic; }; void thread_create (struct task_struct* pthread,thread_func function,void *func_arg) ;void init_thread (struct task_struct* pthread,char *name,int prio) ;struct task_struct* thread_start (char * name, int prio, thread_func function, void * func_arg) ;#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 #include "thread.h" #include "string.h" #include "stdint.h" #include "global.h" #include "memory.h" #define PG_SIZE 4096 static void kernel_thread (thread_func* function,void * func_arg) { function(func_arg); } 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->esi = kthread_stack->edi = 0 ; } void init_thread (struct task_struct* pthread,char *name,int prio) { memset (pthread,0 ,sizeof (*pthread)); strcpy (pthread->name,name); pthread->status = TASK_RUNNING; pthread->priority = prio; pthread->self_kstack = (uint32_t *)((uint32_t )pthread + PG_SIZE); pthread->stack_magic = 0x22332233 ; } struct task_struct* thread_start (char * name, int prio, thread_func function, void * func_arg) { struct task_struct * thread = get_kernel_pages(1 ); init_thread(thread,name,prio); thread_create(thread,function,func_arg); asm volatile ("movl %0, %%esp; pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; ret" : : "g" (thread->self_kstack) : "memory" ) ; return thread; }
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 #include "print.h" #include "init.h" #include "interrupt.h" #include "debug.h" #include "string.h" #include "memory.h" #include "thread.h" void k_thread_one (void * arg) ;int main () { put_str("I am kernel!\n" ); init_all(); thread_start("k_thread_one" ,31 ,k_thread_one,"argA" ); return 0 ; } void k_thread_one (void * arg) { char * para = arg; while (1 ) { put_str(arg); } }
1 2 3 4 5 6 7 8 THREAD_SRCS := thread.c THREAD_ASMS :=
主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 .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 -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/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) ) ALL_C_SRCS := $(KERNEL_SRCS) $(LIB_KERNEL_SRCS) $(LIB_USER_SRCS) $(DEVICE_SRCS) $(LIB_SRCS) $(THREAD_SRCS) ALL_ASMS := $(KERNEL_ASMS) $(LIB_KERNEL_ASMS) $(LIB_USER_ASMS) $(DEVICE_ASMS) $(LIB_ASMS) $(THREAD_ASMS) OBJS_KERNEL := \ $(patsubst $(MOUSE_DIR) /%.c,$(BUILD_KERNEL_DIR) /%.o,$(filter $(MOUSE_DIR) /%.c,$(ALL_C_SRCS) ) ) \ $(patsubst $(MOUSE_DIR) /%.S,$(BUILD_KERNEL_DIR) /%.o,$(filter $(MOUSE_DIR) /%.S,$(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 $@ $< 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!"
在学习多线程调度之前,我们还要学一个数据结构,就是大家数据结构学过的,双向链表,用来维护内核中的各种队列(比如进程的就绪队列,锁的等待队列等等……)
F4.2 双向链表 话不多说,直接开写,位置放在内核库下,也就是/mouse/lib/kernel,创建 lsit.c list.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 #include "global.h" #include "stdint.h" #ifndef __LIB_KERNEL_LIST_H #define __LIB_KERNEL_LIST_H #define offset(struct_type,member) (int)(&((struct_type*)0)->member) #define elem2entry(struct_type, struct_member_name, elem_ptr) \ (struct_type*)((int)elem_ptr - offset(struct_type, struct_member_name)) struct list_elem { struct list_elem * prev ; struct list_elem * next ; }; struct list { struct list_elem head ; struct list_elem tail ; }; typedef bool (function) (struct list_elem*, int arg) ;void list_init (struct list *) ;void list_insert_before (struct list_elem* before, struct list_elem* elem) ;void list_push (struct list * plist, struct list_elem* elem) ;void list_iterate (struct list * plist) ;void list_append (struct list * plist, struct list_elem* elem) ; void list_remove (struct list_elem* pelem) ;struct list_elem* list_pop (struct list * plist) ;bool list_empty (struct list * plist) ;uint32_t list_len (struct list * plist) ;struct list_elem* list_traversal (struct list * plist, function func, int arg) ;bool elem_find (struct list * plist, struct list_elem* obj_elem) ;#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 #include "list.h" #include "interrupt.h" #include "stdint.h" void list_init (struct list * list ) { list ->head.prev = NULL ; list ->head.next = &list ->tail; list ->tail.prev = &list ->head; list ->tail.next = NULL ; } void list_insert_before (struct list_elem* before,struct list_elem* elem) { enum intr_status old_status = intr_disable(); before->prev->next = elem; elem->prev = before->prev; elem->next = before; before->prev = elem; intr_set_status(old_status); } void list_push (struct list * plist, struct list_elem* elem) { list_insert_before(plist->head.next, elem); } void list_append (struct list * plist, struct list_elem* elem) { list_insert_before(&plist->tail, elem); } void list_remove (struct list_elem* pelem) { enum intr_status old_status = intr_disable(); pelem->prev->next = pelem->next; pelem->next->prev = pelem->prev; intr_set_status(old_status); } struct list_elem* list_pop (struct list * plist) { struct list_elem * elem = plist->head.next; list_remove(elem); return elem; } bool elem_find (struct list * plist, struct list_elem* obj_elem) { struct list_elem * elem = plist->head.next; while (elem != &plist->tail) { if (elem == obj_elem) { return true ; } elem = elem->next; } return false ; } struct list_elem* list_traversal (struct list * plist, function func, int arg) { struct list_elem * elem = plist->head.next; if (list_empty(plist)) { return NULL ; } while (elem != &plist->tail) { if (func(elem, arg)) { return elem; } elem = elem->next; } return NULL ; } uint32_t list_len (struct list * plist) { struct list_elem * elem = plist->head.next; uint32_t length = 0 ; while (elem != &plist->tail) { length++; elem = elem->next; } return length; } bool list_empty (struct list * plist) { return (plist->head.next == &plist->tail ? true : false ); }
在实现双向链表之后就可以来进一步完善thread.c和thread.h了
F4.3 简单优先级调度基础 & 任务调度器 这里添加了很多内容,我这里还是直接给出最终的文件,相比于之前的文件大家可以参考注释理解,这里只做简要说明:这个代码只要自己认真写/读一遍,基本都能理解原理
完整的调度过程需要三部分的配合 (1)时钟中断处理函数 (2)调度器 schedule (3)任务切换函数 switch_to
所以这里还是涉及到中断处理函数,包括注册等等,也同样写在下面并给出完整文件(主要修改interrupt.c general_intr_handler() timer.c,然后添加了时钟的中断处理函数等),同时优化了打印异常信息时候的打印方式(添加了指定位置的清屏),也添加了设置光标的函数print.S
第三步的任务切换函数还涉及到对上下文的保护,我们创建一个文件switch.S,放在thread文件夹下;
其中上下文保护分为两个部分,第一部分是保存任务进入中断前的全部寄存器,目的是能让任务恢复到中断前;第二部分是保存这 4 个寄存器:esi、edi、ebx 和 ebp,目的是让任务恢复执行在任务切换发生时剩下尚未执行的内核代码,保证顺利走到退出中断的出口,利用第一部分保护的寄存器环境彻底恢复任务,这里第一部分已经在kernel.S文件中由intr%1entry实现
最后在init中添加初始化,在主函数中添加两个进程编译运行后进行验证:
F.5 源码 & 修改部分 话不多说,全是代码,在每个代码块上面简要说明修改添加了什么~
添加修改光标位置的函数 set_cursor( )
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 TI_GDT equ 0 RPL0 equ 0 SELECTOR_VIDEO equ (0X0003 <<3 ) + TI_GDT + RPL0 [bits 32 ] section .textput_int_buffer dq 0 global put_int put_int: pushad mov ebp , esp mov eax , [ebp +4 * 9 ] mov edx , eax mov edi , 7 mov ecx , 8 mov ebx , put_int_buffer .16based_4bits: and edx , 0x0000000F cmp edx , 9 jg .is_A2F add edx , '0' jmp .store .is_A2F: sub edx , 10 add edx , 'A' .store: mov [ebx +edi ], dl dec edi shr eax , 4 mov edx , eax loop .16based_4bits .ready_to_print: inc edi .skip_prefix_0: cmp edi , 8 je .full0 .go_on_skip: mov cl , [put_int_buffer+edi ] inc edi cmp cl , '0' je .skip_prefix_0 dec edi jmp .put_each_num .full0: mov cl , '0' .put_each_num: push ecx call put_char add esp , 4 inc edi mov cl , [put_int_buffer+edi ] cmp edi , 8 jl .put_each_num popad ret global put_str put_str: push ebx push ecx xor ecx ,ecx mov ebx ,[esp +12 ] .goon: mov cl ,[ebx ] cmp cl ,0 jz .str_over push ecx call put_char add esp ,4 inc ebx jmp .goon .str_over: pop ebx pop ecx ret global put_char put_char: pushad mov ax ,SELECTOR_VIDEO mov gs ,ax mov dx ,0x03d4 mov al ,0x0e out dx ,al mov dx ,0x03d5 in al ,dx mov ah ,al mov dx , 0x03d4 mov al , 0x0f out dx , al mov dx , 0x03d5 in al , dx mov bx ,ax mov ecx ,[esp +36 ] cmp cl ,0xd jz .is_carriage_return cmp cl , 0xa jz .is_line_feed cmp cl ,0x8 jz .is_backspace jmp .put_other .is_backspace: dec bx shl bx ,1 mov byte [gs :bx ],0x20 inc bx mov byte [gs :bx ], 0x07 shr bx ,1 jmp .set_cursor .put_other: shl bx ,1 mov byte [gs :bx ],cl inc bx mov byte [gs :bx ], 0x07 shr bx ,1 inc bx cmp bx , 2000 jl .set_cursor .is_line_feed: .is_carriage_return: xor dx ,dx mov ax ,bx mov si ,80 div si sub bx ,dx .is_carriage_return_end: add bx ,80 cmp bx ,2000 .is_line_feed_end: jl .set_cursor .roll_screen: cld mov ecx ,960 mov esi ,0xc00b80a0 mov edi ,0xc00b8000 rep movsd mov ebx , 3840 mov ecx , 80 .cls: mov word [gs :ebx ],0x0720 add ebx ,2 loop .cls mov bx ,1920 .set_cursor: mov dx , 0x03d4 mov al , 0x0e out dx , al mov dx , 0x03d5 mov al , bh out dx , al mov dx , 0x03d4 mov al , 0x0f out dx , al mov dx , 0x03d5 mov al , bl out dx , al .put_char_done: popad ret global set_cursor set_cursor: pushad mov bx ,[esp +36 ] mov dx , 0x03d4 mov al , 0x0e out dx , al mov dx , 0x03d5 mov al , bh out dx , al mov dx , 0x03d4 mov al , 0x0f out dx , al mov dx , 0x03d5 mov al , bl out dx , al popad ret
注意在print.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 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 #include "interrupt.h" #include "stdint.h" #include "global.h" #include "debug.h" #include "print.h" #include "io.h" #define IDT_DESC_CNT 0x33 #define PIC_M_CTRL 0x20 #define PIC_M_DATA 0x21 #define PIC_S_CTRL 0xa0 #define PIC_S_DATA 0xa1 #define EFLAGS_IF 0X00000200 #define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR)) struct gate_desc { uint16_t func_offset_low_word; uint16_t selector; uint8_t dcount; uint8_t attribute; uint16_t func_offset_high_word; }; static void make_idt_desc (struct gate_desc* p_gdesc,uint8_t attr, intr_handler function) ;static struct gate_desc idt [IDT_DESC_CNT ]; extern intr_handler intr_entry_table[IDT_DESC_CNT]; static void exception_init (void ) ; static void general_intr_handler (uint8_t vec_nr) ; const char * intr_name[IDT_DESC_CNT]; intr_handler idt_table[IDT_DESC_CNT]; 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, 0xfe ); outb (PIC_S_DATA, 0xff ); put_str(" pic_init done\n" ); } static void make_idt_desc (struct gate_desc* p_gdesc,uint8_t attr, intr_handler function) { p_gdesc->func_offset_low_word = (uint32_t )function & 0x0000FFFF ; p_gdesc->selector = SELECTOR_K_CODE; p_gdesc->dcount = 0 ; p_gdesc->attribute = attr; p_gdesc->func_offset_high_word = ((uint32_t )function & 0Xffff0000 ) >>16 ; } static void idt_desc_init (void ) { int i; for (i=0 ;i<IDT_DESC_CNT;i++) { make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]); } put_str("idt_desc_init done\n" ); } static void general_intr_handler (uint8_t vec_nr) { if (vec_nr == 0x27 || vec_nr == 0x2f ) { return ; } set_cursor(0 ); int cursor_pos = 0 ; while (cursor_pos < 320 ) { put_char(' ' ); cursor_pos++; } set_cursor(0 ); put_str("!!!!!!! excetion message begin !!!!!!!!\n" ); set_cursor(88 ); put_str((char *)intr_name[vec_nr]); if (vec_nr == 14 ) { int page_fault_vaddr = 0 ; asm ("movl %%cr2, %0" : "=r" (page_fault_vaddr)); put_str("\npage fault addr is " );put_int(page_fault_vaddr); } put_str("\n!!!!!!! excetion message end !!!!!!!!\n" ); while (1 ); } static void page_fault_handler (uint8_t vec_nr) { if (vec_nr == 0x27 || vec_nr == 0x2f ) { return ; } put_str("int vector : 0x" ); put_int(vec_nr); put_str("\n" ); uint32_t faulting_address; asm volatile ("mov %%cr2, %0" : "=r" (faulting_address)) ; put_str("Page fault at address: 0x" ); put_int(faulting_address); put_str("\n" ); } static void exception_init (void ) { int i; for (i=0 ;i<IDT_DESC_CNT;i++) { idt_table[i] = general_intr_handler; intr_name[i] = "unknown" ; } intr_name[0 ] = "#DE Divide Error" ; intr_name[1 ] = "#DB Debug Exception" ; intr_name[2 ] = "NMI Interrupt" ; intr_name[3 ] = "#BP Breakpoint Exception" ; intr_name[4 ] = "#OF Overflow Exception" ; intr_name[5 ] = "#BR BOUND Range Exceeded Exception" ; intr_name[6 ] = "#UD Invalid Opcode Exception" ; intr_name[7 ] = "#NM Device Not Available Exception" ; intr_name[8 ] = "#DF Double Fault Exception" ; intr_name[9 ] = "Coprocessor Segment Overrun" ; intr_name[10 ] = "#TS Invalid TSS Exception" ; intr_name[11 ] = "#NP Segment Not Present" ; intr_name[12 ] = "#SS Stack Fault Exception" ; intr_name[13 ] = "#GP General Protection Exception" ; intr_name[14 ] = "#PF Page-Fault Exception" ; intr_name[16 ] = "#MF x87 FPU Floating-Point Error" ; intr_name[17 ] = "#AC Alignment Check Exception" ; intr_name[18 ] = "#MC Machine-Check Exception" ; intr_name[19 ] = "#XF SIMD Floating-Point Exception" ; } enum intr_status intr_enable () { enum intr_status old_status ; if (INTR_ON == intr_get_status()) { old_status = INTR_ON; return old_status; } else { old_status = INTR_OFF; asm volatile ("sti" ) ; return old_status; } } enum intr_status intr_disable () { enum intr_status old_status ; if (INTR_ON == intr_get_status()) { old_status = INTR_ON; asm volatile ("cli" : : : "memory" ) ; return old_status; } else { old_status = INTR_OFF; return old_status; } } enum intr_status intr_set_status (enum intr_status status) { return (status & INTR_ON) ?intr_enable() : intr_disable(); } enum intr_status intr_get_status () { uint32_t eflags = 0 ; GET_EFLAGS(eflags); return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF; } void register_handler (uint8_t vector_no, intr_handler function) { idt_table[vector_no] = function; } void idt_init () { put_str("idt_init start\n" ); idt_desc_init(); exception_init(); pic_init(); uint64_t idt_operand = ((sizeof (idt) - 1 ) | ((uint64_t )((uint32_t )idt << 16 ))); asm volatile ("lidt %0" ::"" (idt_operand)) ; put_str("idt_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 #ifndef __INTERRUPUT_H #define __INTERRUPUT_H #include "stdint.h" typedef void * intr_handler;void idt_init () ;void register_handler (uint8_t vector_no, intr_handler function) ;enum intr_status { INTR_OFF, INTR_ON }; enum intr_status intr_enable () ; enum intr_status intr_disable () ; enum intr_status intr_get_status () ; enum intr_status intr_set_status (enum intr_status status) ; #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 #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 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--; } } 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 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 #include "thread.h" #include "string.h" #include "stdint.h" #include "global.h" #include "memory.h" #include "interrupt.h" #include "print.h" #include "debug.h" #define PG_SIZE 4096 struct task_struct * main_thread ; 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) ; struct task_struct* running_thread () { uint32_t esp; asm volatile ("mov %%esp,%0" :"=g" (esp)) ; return (struct task_struct*)(esp & 0xfffff000 ); } static void kernel_thread (thread_func* function,void * func_arg) { intr_enable(); function(func_arg); } 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->esi = kthread_stack->edi = 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; } 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 = 0x22332233 ; } struct task_struct* thread_start (char * name, int prio, thread_func function, void * func_arg) { 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); return thread; } static void make_main_thread (void ) { main_thread = running_thread(); init_thread(main_thread,"main" ,31 ); 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) { 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; switch_to(cur,next); } void thread_init (void ) { put_str("thread_init start\n" ); list_init(&thread_ready_list); list_init(&thread_all_list); make_main_thread(); put_str("thread_init done" ); }
添加时间调度器的声明,以及完善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 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 #ifndef __KERNEL_THREAD_H #define __KERNEL_THREAD_H #include "stdint.h" #include "list.h" #include "debug.h" typedef void thread_func (void *) ;enum task_status { TASK_RUNNING, TASK_READY, TASK_BLOCKED, TASK_WAITNG, TASK_HANGING, TASK_DIED }; struct intr_stack { uint32_t vec_no; uint32_t edi; uint32_t esi; uint32_t ebp; uint32_t esp_dummy; uint32_t ebx; uint32_t edx; uint32_t ecx; uint32_t eax; uint32_t gs; uint32_t fs; uint32_t es; uint32_t ds; uint32_t err_code; void (*eip) (void ); uint32_t cs; uint32_t eflags; void * esp; uint32_t ss; }; struct thread_stack { uint32_t ebp; uint32_t ebx; uint32_t edi; uint32_t esi; void (*eip) (thread_func* func, void * func_arg); void (*unused_retaddr); thread_func* function; void * func_arg; }; 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; uint32_t stack_magic; }; void thread_create (struct task_struct* pthread,thread_func function,void *func_arg) ;void init_thread (struct task_struct* pthread,char *name,int prio) ;struct task_struct* thread_start (char * name, int prio, thread_func function, void * func_arg) ;struct task_struct* running_thread () ;void schedule () ; void thread_init (void ) ; #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 [bits 32 ] section .textglobal switch_toswitch_to: push esi push edi push ebx push ebp mov eax , [esp + 20 ] mov [eax ], esp mov eax , [esp + 24 ] mov esp , [eax ] pop ebp pop ebx pop edi pop esi ret
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include "init.h" #include "print.h" #include "interrupt.h" #include "timer.h" #include "memory.h" #include "thread.h" void init_all () { put_str("init_all\n" ); idt_init(); timer_init(); mem_init(); thread_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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include "print.h" #include "init.h" #include "interrupt.h" #include "debug.h" #include "string.h" #include "memory.h" #include "thread.h" void k_thread_one (void * arg) ;void k_thread_two (void * arg) ;int main () { put_str("I am kernel!\n" ); init_all(); thread_start("k_thread_one" ,31 ,k_thread_one,"argA" ); thread_start("k_thread_two" ,31 ,k_thread_two,"argB" ); intr_enable(); while (1 ) { put_str("Main" ); } return 0 ; } void k_thread_one (void * arg) { char * para = arg; while (1 ) { put_str(arg); } } void k_thread_two (void * arg) { char * para = arg; while (1 ) { put_str(arg); } }
注意,在编译的时候如果有报错,可以检查是不是缺少头文件包含,或者是声明等等
然后直接运行,就可以发现三个任务可以交替运行(交替打印出不同的字符串),但是会发现两个问题:
有时候三个打印的字符串可能会错开、有空格、间断、等等
会发生报错,#GP General Protection Exception,一般保护性异常
这两个都会在下一章的同步机制 中讲解,所以下一章见~
G. 同步 这里从上一节出现的报错,以及错误混乱现象来入手,其实主要是因为在打印的逻辑中:
获取光标
光标转化为字节地址,在该地址写入字符
更新光标
这三个任务其实必须要一起执行,否则如果中途被切换了就会出现打印错误的打印情况,这其实就是原子(不可再分)的体现,所以这里我们可以通过开关中断的情况来保证原子性
为了方便,这里只用在put_str的前后添加开关中断,这里修改主函数:
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 #include "print.h" #include "init.h" #include "interrupt.h" #include "debug.h" #include "string.h" #include "memory.h" #include "thread.h" void k_thread_one (void * arg) ;void k_thread_two (void * arg) ;int main () { put_str("I am kernel!\n" ); init_all(); thread_start("k_thread_one" ,31 ,k_thread_one,"AAAA " ); thread_start("k_thread_two" ,8 ,k_thread_two,"BBBB " ); while (1 ) { intr_disable(); put_str("MMMM " ); intr_enable(); } return 0 ; } void k_thread_one (void * arg) { char * para = arg; while (1 ) { intr_disable(); put_str(arg); intr_enable(); } } void k_thread_two (void * arg) { char * para = arg; while (1 ) { intr_disable(); put_str(arg); intr_enable(); } }
其实这里还有一个问题,是我遇到的,就是如果输出的字符串是5位,比如”11111”,三个任务输出长度都需要一样,那么可以正常执行 但是如果修改长度,则会报错,不限于错误操作码等
初步原因是因为恰好5位的时候避开了同时写入么?这里还没有找到原因,打算通过完成锁机制后再查看 (好像找到了,vaddr_get,这个函数之前写的有问题,现在已经修改了)
当正确添加锁机制(使用互斥锁实现控制台输出)之后便没有了问题了
同时检查代码的时候发现之前idt_init中有个有个位置写错了,在下面指出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void idt_init () { put_str("idt_init start\n" ); idt_desc_init(); exception_init(); pic_init(); uint64_t idt_operand = ((sizeof (idt) - 1 ) | ((uint64_t )(uint32_t )idt << 16 )); asm volatile ("lidt %0" : : "m" (idt_operand)) ; put_str("idt_init done\n" ); }
G.1 锁/信号量 这个概念相信很多人都了解过,这里还是简要介绍一下,然后具体看代码:
G1.1 概念
信号量就是个计数器,它的计数值是自然数,用来记录所积累信号的数量。这里的信号是个泛指,取决于信号量的实际应用环境
因此对信号量的加法操作是用 up 表示,减法操作是用 down 表示。下面介绍下这两种操作。增加操作 up 包括两个微操作。 (1)将信号量的值加 1。 (2)唤醒在此信号量上等待的线程。 减少操作 down 包括三个子操作。 (1)判断信号量是否大于 0。 (2)若信号量大于 0,则将信号量减 1。 (3)若信号量等于 0,当前线程将自己阻塞,以在此信号量上等待。 信号量是个全局共享变量,up 和 down 又都是读写这个全局变量的操作,而且它们都包含一系列的子操作,因此它们必须都是原子操作
G1.2 实现线程的阻塞与唤醒 还是在thread.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 void thread_block (enum task_status stat) { ASSERT( (stat == TASK_BLOCKED) || (stat == TASK_WAITNG) ||(stat == TASK_HANGING) ); enum intr_status old_status = intr_disable(); struct task_struct * cur_thread = running_thread(); cur_thread->status = stat; schedule(); intr_set_status(old_status); } void thread_unblock (struct task_struct* pthread) { enum intr_status old_status = intr_disable(); ASSERT( (pthread->status == TASK_BLOCKED) || (pthread->status == TASK_WAITNG) ||(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); }
1 2 3 4 5 void thread_block (enum task_status stat) ;void thread_unblock (struct task_struct* pthread) ;
G1.3 锁的实现 下面来实现锁,要添加两个文件,在thread文件夹下添加sync.c stnc.h,实现一个简单的信号量 (只有0和1)
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 #include "sync.h" #include "stdint.h" #include "interrupt.h" void sema_init (struct semaphore* psema,uint8_t value) { psema->value = value; list_init(&psema->waiters); } void lock_init (struct lock* plock) { plock->holder = NULL ; plock->holder_repeat_nr = 0 ; sema_init(&plock->semaphore,1 ); } void sema_down (struct semaphore* psema) { enum intr_status old_status = intr_disable(); while (psema->value == 0 ) { ASSERT(!elem_find(&psema->waiters,&running_thread()->general_tag)); if (elem_find(&psema->waiters, &running_thread()->general_tag)) { PANIC("sema_down: thread blocked has been in waiters_list\n" ); } list_append(&psema->waiters, &running_thread()->general_tag); thread_block(TASK_BLOCKED); } psema->value--; ASSERT(psema->value == 0 ); intr_set_status(old_status); } void sema_up (struct semaphore* psema) { enum intr_status old_status = intr_disable(); ASSERT(psema->value == 0 ); if (!list_empty(&psema->waiters)) { struct task_struct * thread_blocked = elem2entry(struct task_struct, general_tag, list_pop(&psema->waiters)); thread_unblock(thread_blocked); } psema->value++; ASSERT(psema->value == 1 ); intr_set_status(old_status); } void lock_acquire (struct lock* plock) { if (plock->holder != running_thread()) { sema_down(&plock->semaphore); plock->holder = running_thread(); ASSERT(plock->holder_repeat_nr == 0 ); plock->holder_repeat_nr = 1 ; } else { plock->holder_repeat_nr++; } } void lock_release (struct lock* plock) { ASSERT(plock->holder == running_thread()); if (plock->holder_repeat_nr >1 ) { plock->holder_repeat_nr--; return ; } ASSERT(plock->holder_repeat_nr == 1 ); plock->holder = NULL ; plock->holder_repeat_nr = 0 ; sema_up(&plock->semaphore); }
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 #ifndef __THREAD_SYNC_H #define __THREAD_SYNC_H #include "thread.h" #include "list.h" #include "stdint.h" struct semaphore { uint8_t value; struct list waiters ; }; struct lock { struct task_struct * holder ; struct semaphore semaphore ; uint32_t holder_repeat_nr; }; #include "sync.h" #include "stdint.h" #include "interrupt.h" void sema_init (struct semaphore* psema,uint8_t value) ;void lock_init (struct lock* plock) ;void sema_down (struct semaphore* psema) ;void sema_up (struct semaphore* psema) ;void lock_acquire (struct lock* plock) ;void lock_release (struct lock* plock) ;#endif
G.2 用锁实现终端输出 这里写的终端,它不是真正意义上的终端,甚至连伪终端都算不上,我们只是通过它让输出变得更整洁。您看到了,它就是对各种锁操作的封装,完全就是锁的应用
我们将它写在device文件夹下添加console.h console.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 #include "console.h" #include "sync.h" #include "print.h" #include "stdint.h" #include "thread.h" static struct lock console_lock ; void console_init () { lock_init(&console_lock); } void console_acquire () { lock_acquire(&console_lock); } void console_release () { lock_release(&console_lock); } void console_put_str (char * str) { console_acquire(); put_str(str); console_release(); } void console_put_char (uint8_t char_asci) { console_acquire(); put_char(char_asci); console_release(); } void console_put_int (uint32_t num) { console_acquire(); put_int(num); console_release(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef __DEVICE_CONSOLE_H #define __DEVICE_CONSOLE_H #include "stdint.h" void console_init () ;void console_acquire () ;void console_release () ;void console_put_str (char * str) ;void console_put_char (uint8_t char_asci) ;void console_put_int (uint32_t num) ;#endif
最后我们添加主函数,将原本的put_str 改成 console_put_str,验证结果:
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 #include "print.h" #include "init.h" #include "interrupt.h" #include "debug.h" #include "string.h" #include "memory.h" #include "thread.h" #include "console.h" void k_thread_one (void * arg) ;void k_thread_two (void * arg) ;int main () { put_str("I am kernel!\n" ); init_all(); thread_start("k_thread_one" ,31 ,k_thread_one,"AA " ); thread_start("k_thread_two" ,8 ,k_thread_two,"BBBB " ); intr_enable(); while (1 ) { console_put_str("MMMM " ); } return 0 ; } void k_thread_one (void * arg) { char * para = arg; while (1 ) { console_put_str(arg); } } void k_thread_two (void * arg) { char * para = arg; while (1 ) { console_put_str(arg); } }
最后还是编译写入运行,会发现不会发生报错,并且运行的顺序不再是固定的,而是”随机”的,符合任务阻塞的现象,并且更改字符串长度后依旧可以正常运行,以后我们的输出就使用这个新的函数
ok呀,终端到此结束,虽然书中表示不会更新,但说不定会找点别的更新进去,因为后面打算看看linux的其它版本的源码来继续完善这个简单的操作系统
H 输入的实现 既然前面实现了终端的输出,那么肯定少不了输入,这里开始实现输入,即从键盘中获取输入的字符
H.1 原理简介
键盘是个独立的设备,在它内部有个叫作键盘编码器的芯片,通常是 Intel 8048 或兼容芯片,它的作用是:每当键盘上发生按键操作,它就向键盘控制器报告哪个键被按下,按键是否弹起。
这个键盘控制器可并不在键盘内部,它在主机内部的主板上,通常是 Intel 8042 或兼容芯片,它的作用是接收来自键盘编码器的按键信息,将其解码后保存,然后向中断代理发中断,之后处理器执行相应的中断处理程序读入 8042 处理保存过的按键信息
无论是按下键,或是松开键,当键的状态改变后,键盘中的 8048 芯片把按键对应的扫描码(通码或断码)发送到主板上的 8042 芯片,由 8042 处理后保存在自己的寄存器中,然后向 8259A 发送中断信号,这样处理器便去执行键盘中断处理程序,将 8042 处理过的扫描码从它的寄存器中读取出来,继续进行下一步处理
这个中断处理程序就是需要我们编写的,我们只能得到键的扫描码 ,并不会得到ASCII码,然后我们可以将他们俩转化,然后再将它交给put_char函数即可
当然书中还介绍了键盘扫描码的分类,这里就不多说了,我们直接来进行实战环节即可,因为这个也算是科普了
H.2 测试中断程序 在测试键盘的中断程序之前,我们先来添加优化一下之前写的代码,首先是”kernel.S”文件中,添加新的中断,因为我们之前只写到了0x20,同时修改宏IDT_DESC_CNT,因为我们的总数发生了变化
其实是为了方便演示,所以我们要暂时关闭掉时钟的中断,只打开键盘的中断,所以我们现在还要写8259A的中断屏蔽寄存器(pic_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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 VECTOR 0X00 ,ZERO VECTOR 0x01 ,ZERO VECTOR 0x02 ,ZERO VECTOR 0x03 ,ZERO VECTOR 0x04 ,ZERO VECTOR 0x05 ,ZERO VECTOR 0x06 ,ZERO VECTOR 0x07 ,ZERO VECTOR 0x08 ,ERROR_CODE VECTOR 0x09 ,ZERO VECTOR 0x0a ,ERROR_CODE VECTOR 0x0b ,ERROR_CODE VECTOR 0x0c ,ZERO VECTOR 0x0d ,ERROR_CODE VECTOR 0x0e ,ERROR_CODE VECTOR 0x0f ,ZERO VECTOR 0x10 ,ZERO VECTOR 0x11 ,ERROR_CODE VECTOR 0x12 ,ZERO VECTOR 0x13 ,ZERO VECTOR 0x14 ,ZERO VECTOR 0x15 ,ZERO VECTOR 0x16 ,ZERO VECTOR 0x17 ,ZERO VECTOR 0x18 ,ERROR_CODE VECTOR 0x19 ,ZERO VECTOR 0x1a ,ERROR_CODE VECTOR 0x1b ,ERROR_CODE VECTOR 0x1c ,ZERO VECTOR 0x1d ,ERROR_CODE VECTOR 0x1e ,ERROR_CODE VECTOR 0x1f ,ZERO VECTOR 0x20 ,ZERO VECTOR 0x21 ,ZERO VECTOR 0x22 ,ZERO VECTOR 0x23 ,ZERO VECTOR 0x24 ,ZERO VECTOR 0x25 ,ZERO VECTOR 0x26 ,ZERO VECTOR 0x27 ,ZERO VECTOR 0x28 ,ZERO VECTOR 0x29 ,ZERO VECTOR 0x2a ,ZERO VECTOR 0x2b ,ZERO VECTOR 0x2c ,ZERO VECTOR 0x2d ,ZERO VECTOR 0x2e ,ZERO VECTOR 0x2f ,ZERO
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 "interrupt.h" #include "stdint.h" #include "global.h" #include "debug.h" #include "print.h" #include "io.h" #define IDT_DESC_CNT 0x33 #define PIC_M_CTRL 0x20 #define PIC_M_DATA 0x21 #define PIC_S_CTRL 0xa0 #define PIC_S_DATA 0xa1 #define EFLAGS_IF 0x00000200 #define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR)) struct gate_desc { uint16_t func_offset_low_word; uint16_t selector; uint8_t dcount; uint8_t attribute; uint16_t func_offset_high_word; }; static void make_idt_desc (struct gate_desc* p_gdesc,uint8_t attr, intr_handler function) ;static struct gate_desc idt [IDT_DESC_CNT ]; extern intr_handler intr_entry_table[IDT_DESC_CNT]; static void exception_init (void ) ; static void general_intr_handler (uint8_t vec_nr) ; const char * intr_name[IDT_DESC_CNT]; intr_handler idt_table[IDT_DESC_CNT]; 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_S_DATA,0xfd ); outb(PIC_S_DATA, 0xff ); put_str(" pic_init done\n" ); }
然后来开始写键盘的测试驱动,放在device文件夹下,创建keyboard.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 #include "stdint.h" #include "print.h" #include "io.h" #include "global.h" #include "interrupt.h" #define KBD_BUF_PORT 0x60 static void intr_keyboard_handler (void ) { put_char('k' ); uint8_t scancode = inb(KBD_BUF_PORT); put_int(scancode); return ; } void keyboard_init () { put_str("Keyboard init start\n" ); register_handler(0x21 ,intr_keyboard_handler); put_str("Keyboard init done\n" ); }
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __DEVICE_KEYBOARD_H #define __DEVICE_KEYBOARD_H #include "stdint.h" static void intr_keyboard_handler (void ) ;void keyboard_init () ;#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 #include "print.h" #include "init.h" #include "interrupt.h" #include "debug.h" #include "string.h" #include "memory.h" #include "thread.h" #include "console.h" void k_thread_one (void * arg) ;void k_thread_two (void * arg) ;int main () { put_str("I am kernel!\n" ); init_all(); intr_enable(); while (1 ) { } return 0 ; } void k_thread_one (void * arg) { char * para = arg; while (1 ) { console_put_str(arg); } } void k_thread_two (void * arg) { char * para = arg; while (1 ) { console_put_str(arg); } }
修改完之后主要还要修改对应的Makefile文件,添加编译的c文件,主函数中取消对多任务的注册,写成死循环即可
然后编译写入运行,对着终端按下键盘,就会发现屏幕上有输出(扫描码)
H.3 编写键盘驱动 下面的驱动主要通过扫描码到ASCII码的转化来实现,来编写一个映射表实现键盘的读入
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 #include "keyboard.h" #include "print.h" #include "interrupt.h" #include "io.h" #include "global.h" #define KBD_BUF_PORT 0x60 #define esc '\033' #define backspace '\b' #define tab '\t' #define enter '\r' #define delete '\177' #define char_invisible 0 #define ctrl_l_char char_invisible #define ctrl_r_char char_invisible #define shift_l_char char_invisible #define shift_r_char char_invisible #define alt_l_char char_invisible #define alt_r_char char_invisible #define caps_lock_char char_invisible #define shift_l_make 0x2a #define shift_r_make 0x36 #define alt_l_make 0x38 #define alt_r_make 0xe038 #define alt_r_break 0xe0b8 #define ctrl_l_make 0x1d #define ctrl_r_make 0xe01d #define ctrl_r_break 0xe09d #define caps_lock_make 0x3a static bool ctrl_status, shift_status, alt_status, caps_lock_status, ext_scancode;static char keymap[][2 ] = { {0 , 0 }, {esc, esc}, {'1' , '!' }, {'2' , '@' }, {'3' , '#' }, {'4' , '$' }, {'5' , '%' }, {'6' , '^' }, {'7' , '&' }, {'8' , '*' }, {'9' , '(' }, {'0' , ')' }, {'-' , '_' }, {'=' , '+' }, {backspace, backspace}, {tab, tab}, {'q' , 'Q' }, {'w' , 'W' }, {'e' , 'E' }, {'r' , 'R' }, {'t' , 'T' }, {'y' , 'Y' }, {'u' , 'U' }, {'i' , 'I' }, {'o' , 'O' }, {'p' , 'P' }, {'[' , '{' }, {']' , '}' }, {enter, enter}, {ctrl_l_char, ctrl_l_char}, {'a' , 'A' }, {'s' , 'S' }, {'d' , 'D' }, {'f' , 'F' }, {'g' , 'G' }, {'h' , 'H' }, {'j' , 'J' }, {'k' , 'K' }, {'l' , 'L' }, {';' , ':' }, {'\'' , '"' }, {'`' , '~' }, {shift_l_char, shift_l_char}, {'\\' , '|' }, {'z' , 'Z' }, {'x' , 'X' }, {'c' , 'C' }, {'v' , 'V' }, {'b' , 'B' }, {'n' , 'N' }, {'m' , 'M' }, {',' , '<' }, {'.' , '>' }, {'/' , '?' }, {shift_r_char, shift_r_char}, {'*' , '*' }, {alt_l_char, alt_l_char}, {' ' , ' ' }, {caps_lock_char, caps_lock_char} }; static void intr_keyboard_handler (void ) { bool ctrl_down_last = ctrl_status; bool shift_down_last = shift_status; bool caps_lock_last = caps_lock_status; bool break_code; uint16_t scancode = inb(KBD_BUF_PORT); if (scancode == 0xe0 ) { ext_scancode = true ; return ; } if (ext_scancode) { scancode = ((0xe000 ) | scancode); ext_scancode = false ; } break_code = ((scancode & 0x0080 ) != 0 ); if (break_code) { uint16_t make_code = (scancode &= 0xff7f ); if (make_code == ctrl_l_make || make_code == ctrl_r_make) { ctrl_status = false ; } else if (make_code == shift_l_make || make_code == shift_r_make) { shift_status = false ; } else if (make_code == alt_l_make || make_code == alt_r_make) { alt_status = false ; } return ; } else if ((scancode > 0x00 && scancode < 0x3b ) || \ (scancode == alt_r_make) || \ (scancode == ctrl_r_make)) { bool shift = false ; if ((scancode < 0x0e ) || (scancode == 0x29 ) || \ (scancode == 0x1a ) || (scancode == 0x1b ) || \ (scancode == 0x2b ) || (scancode == 0x27 ) || \ (scancode == 0x28 ) || (scancode == 0x33 ) || \ (scancode == 0x34 ) || (scancode == 0x35 )) { if (shift_down_last) { shift = true ; } } else { if (shift_down_last && caps_lock_last) { shift = false ; } else if (shift_down_last || caps_lock_last) { shift = true ; } else { shift = false ; } } uint8_t index = (scancode &= 0x00ff ); char cur_char = keymap[index][shift]; if (cur_char) { put_char(cur_char); return ; } if (scancode == ctrl_l_make || scancode == ctrl_r_make) { ctrl_status = true ; } else if (scancode == shift_l_make || scancode == shift_r_make) { shift_status = true ; } else if (scancode == alt_l_make || scancode == alt_r_make) { alt_status = true ; } else if (scancode == caps_lock_make) { caps_lock_status = !caps_lock_status; } } else { put_str("unknown key\n" ); } } void keyboard_init () { put_str("keyboard init start\n" ); register_handler(0x21 , intr_keyboard_handler); put_str("keyboard init done\n" ); }
头文件没有更改,运行后可以实现基本的输入
H.4 环形输入缓冲区 但是真正的中断还要实现输入缓冲区 ,因为我们通常在输入一串指令,然后以回车来结束,在结束之前我们就需要找一个缓冲区来存储这些
缓冲区是多个线程共同使用的共享内存,线程在并行访问它时难免会乱套,我们不能指望线程们会老老实实排好队,以串行的方式逐个使用缓冲区。缓冲区大小无关紧要,问题的关键在于缓冲区操作上,因此最好是在缓冲区的操作方法上下功夫,保证对缓冲区是互斥访问,并且不会对其过度使用,从而确保不会使缓冲区遭到破坏。也就是说,只要我们能够设计出合理的缓冲区操作方式,就能够解决生产者与消费者问题
对于缓冲区的访问,我们提供两个指针,一个是头指针,用于往缓冲区中写数据,另一个是尾指针,用于从缓冲区中读数据。每次通过头指针往缓冲区中写入一个数据后,使头指针加 1 指向缓冲区中下一个可写入数据的地址,每次通过尾指针从缓冲区中读取一个数据后,使尾指针加 1 指向缓冲区中下一个可读入数据的地址,也就是说,缓冲区相当于一个队列 ,数据在队列头被写入,在队尾处被读出
下面来具体实现一下,创建ioqueue.c/h,也放在device下:
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 #include "ioqueue.h" #include "stdint.h" #include "debug.h" #include "interrupt.h" #include "global.h" void ioqueue_init (struct ioqueue* ioq) { lock_init(&ioq->lock); ioq->producer = ioq->consumer = NULL ; ioq->head = ioq->tail= 0 ; } static int32_t next_pos (int32_t pos) { return (pos+1 ) % bufsize; } bool ioq_full (struct ioqueue* ioq) { ASSERT(intr_get_status() == INTR_OFF); return (next_pos(ioq->head) == ioq->tail); } bool ioq_empty (struct ioqueue* ioq) { ASSERT(intr_get_status() == INTR_OFF); return (ioq->head == ioq->tail); } static void ioq_wait (struct task_struct** waiter) { ASSERT(*waiter == NULL && waiter !== NULL ); *waiter == running_thread(); thread_block(TASK_BLOCKED); } static void wakeup (struct task_struct** waiter) { ASSERT(*waiter == NULL ); thread_unblock(*waiter); *waiter = NULL ; } char ioq_getchar (struct ioqueue* ioq) { ASSERT(intr_get_status() == INTR_OFF); while (ioq_empty(ioq)) { lock_acquire(&ioq->lock); ioq_wait(&ioq->consumer); lock_release(&ioq->lock); } char byte = ioq->buf[ioq->tail]; ioq->tail = next_pos(ioq->tail); if (ioq->producer != NULL ) { wakeup(&ioq->producer); } return byte; } void ioq_putchar (struct ioqueue* ioq, char byte) { ASSERT(intr_get_status() == INTR_OFF); while (ioq_full(ioq)) { lock_acquire(&ioq->lock); ioq_wait(&ioq->producer); lock_release(&ioq->lock); } ioq->buf[ioq->head] = byte; ioq->head = next_pos(ioq->head); if (ioq->consumer != NULL ) { wakeup(&ioq->consumer); } }
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 #ifndef __DEVICE_IPQUEUE_H #define __DEVICE_IPQUEUE_H #include "thread.h" #include "sync.h" #include "stdint.h" #define bufsize 64 struct ioqueue { struct lock lock ; struct task_struct * producer ; struct task_struct * consumer ; char buf[bufsize]; int32_t head; int32_t tail; }; void ioqueue_init (struct ioqueue* ioq) ;static int32_t next_pos (int32_t pos) ;bool ioq_full (struct ioqueue* ioq) ;bool ioq_empty (struct ioqueue* ioq) ;char ioq_getchar (struct ioqueue* ioq) ;void ioq_putchar (struct ioqueue* ioq, char byte) ;#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 struct ioqueue kbd_buf ; if (cur_char) { if (!ioq_full(&kbd_buf)) { put_char(cur_char); ioq_putchar(&kbd_buf,cur_char); } return ; } void keyboard_init () { put_str("keyboard init start\n" ); ioqueue_init(&kbd_buf); register_handler(0x21 , intr_keyboard_handler); put_str("keyboard init done\n" ); }
这样重新编译写入运行之后,实现的效果就是如果一直输入,会发现输入63个字符之后,就无法继续输入了(屏幕上的显示就是键盘程序中put_char(cur_char);的体现),符合代码里面的缓冲区写满的现象
H.5 测试生产者和消费实例 这里为了测试,我们使用另外两个任务来作为消费者来读取缓冲区的内容,所以这里我们还要重新打开时钟的中断 ,同时在keyboard中添加对缓冲区的外部声明 ,使得外部的函数可以对其进行访问,最后在main函数中添加消费者的线程 ,记得要删除之前测试添加的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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, 0xfc ); outb(PIC_S_DATA, 0xff ); put_str(" pic_init done\n" ); }
1 2 3 4 5 6 7 8 9 10 #ifndef __DEVICE_KEYBOARD_H #define __DEVICE_KEYBOARD_H extern struct ioqueue kbd_buf ; void keyboard_init () ;#endif
这里要删除这个临时的输出,显示效果会更好
1 2 3 4 5 6 7 8 9 10 if (cur_char) { if (!ioq_full(&kbd_buf)) { ioq_putchar(&kbd_buf,cur_char); } return ; }
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 #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 "ioqueue.h" #include "keyboard.h" void k_thread_one (void * arg) ;void k_thread_two (void * arg) ;int main () { put_str("I am kernel!\n" ); init_all(); thread_start("k_thread_one" ,31 ,k_thread_one,"A__:" ); thread_start("k_thread_two" ,8 ,k_thread_two,"B__:" ); intr_enable(); while (1 ) { ; } return 0 ; } void k_thread_one (void * arg) { char * para = arg; while (1 ) { enum intr_status old_status = intr_disable(); if (!ioq_empty(&kbd_buf)) { console_put_str(para); char byte = ioq_getchar(&kbd_buf); console_put_char(byte); } intr_set_status(old_status); } } void k_thread_two (void * arg) { char * para = arg; while (1 ) { enum intr_status old_status = intr_disable(); if (!ioq_empty(&kbd_buf)) { console_put_str(para); char byte = ioq_getchar(&kbd_buf); console_put_char(byte); } intr_set_status(old_status); } }
编译写入运行之后的效果是,如果我长按一个键,那么屏幕上会显示出A__: 和 B__: 和按下的字符交替
这一章就到此结束了
I 用户进程前的小结 本篇主要学了内存管理,内核进程,实现了信号量和锁,同时编写了键盘的输入输出驱动,整体代码量增多了,但是主要是一些数据结构,比如说队列,位图,用来实现唤醒缓冲区,就绪队列等等
下面就是来实现用户进程,实现安全隔离……
参考文献
[操作系统真象还原 (郑纲) (Z-Library)] — 大家可以自己在网上查找相关资源
留言
有问题请指出,你可以选择以下方式:
在下方评论区留言
邮箱留言