Compare commits

...

13 Commits
ch4 ... master

Author SHA1 Message Date
ridethepig
ce1293fd48 firework and report 2022-11-30 10:21:29 +08:00
ridethepig
f886476139 forkbomb over 10W, god knows still some conflicts? 2022-11-23 22:54:45 +08:00
ridethepig
2f336e536b forkbomb 2W 2022-11-23 22:15:57 +08:00
ridethepig
9f77409a4f testwait.bin ok 2022-11-23 17:09:34 +08:00
ridethepig
359c4ab5e2 fork.c fin 2022-11-23 10:34:00 +08:00
ridethepig
52a5d090ea get familiar with new code and babystep in fork 2022-11-22 00:54:03 +08:00
ridethepig
a9e2c29c26 before lab6 2022-11-21 23:07:57 +08:00
ridethepig
890505eecb lab5 update report 2022-11-04 13:59:53 +08:00
ridethepig
32b816a4e1 lab5 extra task ok 2022-11-04 13:57:40 +08:00
ridethepig
f13d09f336 lab5 report 2022-11-04 12:33:43 +08:00
ridethepig
9c17cfaecf delay syscall ok 2022-10-30 01:53:43 +08:00
ridethepig
6c1c2e05ad load program ok 2022-10-30 00:18:22 +08:00
ridethepig
dd540f15d0 before lab5 2022-10-29 19:48:58 +08:00
80 changed files with 3462 additions and 764 deletions

View File

@ -6,7 +6,8 @@
"${workspaceFolder}/inc"
],
"defines": [],
"cStandard": "gnu99"
"cStandard": "gnu99",
"compilerArgs": ["-m32"]
}
],
"version": 4

View File

@ -1,10 +1,11 @@
{
"editor.detectIndentation": false,
"editor.tabSize": 8,
"editor.tabSize": 4,
"editor.insertSpaces": false,
"files.associations": {
"*.h": "c",
"*.c": "c",
"*.asm": "nasm"
"*.asm": "nasm",
"Makefrag": "makefile"
}
}

Binary file not shown.

View File

@ -65,27 +65,36 @@ OBJDIRS :=
all:
# xv6黑科技获取编译命令如果命令较新则重新编译所有文件
.PRECIOUS: $(OBJDIR)/.vars.%
.PRECIOUS: $(OBJDIR)/.vars.% \
$(OBJDIR)/kern/%.o $(OBJDIR)/kern/%.d \
$(OBJDIR)/user/%.o $(OBJDIR)/user/%.d \
$(OBJDIR)/lib/%.o $(OBJDIR)/lib/kern/%.o $(OBJDIR)/lib/user/%.o \
$(OBJDIR)/lib/%.d $(OBJDIR)/lib/kern/%.d $(OBJDIR)/lib/user/%.d
$(OBJDIR)/.vars.%: FORCE
@echo "$($*)" | cmp -s $@ || echo "$($*)" > $@
.PHONY: FORCE
# 导入两个文件两个文件分别编写方便管理也让主makefile更加舒服
include boot/Makefrag
include lib/Makefrag
include kern/Makefrag
include user/Makefrag
# FAT32镜像文件
IMAGE = $(OBJDIR)/kern/a.img
IMAGE = $(OBJDIR)/a.img
$(IMAGE): $(OBJDIR)/boot/boot.bin $(OBJDIR)/boot/loader.bin $(OBJDIR)/kern/kernel.bin
$(IMAGE): $(OBJDIR)/boot/boot.bin $(OBJDIR)/boot/loader.bin $(OBJDIR)/kern/kernel.bin $(USER_BINS)
@stat $@ 1>/dev/null 2>/dev/null || dd if=/dev/zero of=$@ bs=512 count=102400
@mkfs.vfat -F 32 -s8 $@
@dd if=$< of=$@ bs=1 count=423 seek=90 conv=notrunc
@dd if=$< of=$@ bs=1 count=422 seek=90 conv=notrunc
@sudo mount -o loop $@ /mnt
@sudo cp $(OBJDIR)/boot/loader.bin /mnt -v
@sudo cp $(OBJDIR)/kern/kernel.bin /mnt -v
@sudo cp $(USER_BINS) /mnt
@sudo umount /mnt
all: $(IMAGE)
clean:
@rm -rf $(OBJDIR)

View File

@ -11,9 +11,6 @@ LoaderSegPhyAddr equ 10000h ; LOADER.BIN 被加载到的位置 ----
BaseOfStack equ OffsetOfLoader
PageDirBase equ 100000h ; 页目录开始地址: 1M
PageTblBase equ 101000h ; 页表开始地址: 1M + 4K
[SECTION .text.s16]
ALIGN 16
[BITS 16]
@ -163,9 +160,12 @@ LABEL_PM_START:
;
; 启动分页机制 --------------------------------------------------------------
PageDirBase equ 100000h ; 页目录开始地址: 1M
PageTblBase equ 101000h ; 页表开始地址: 1M + 4K
VirtAddrBase1 equ 0h ; 线性地址的0~128MB映射到0~128MB的物理地址
VirtAddrBase2 equ 0c0000000h; 线性地址的3GB~3GB+128MB映射到0~128MB的物理地址
SetupPaging:
; 根据内存大小计算应初始化多少PDE以及多少页表
xor edx, edx
mov eax, 8000000h ; qemu虚拟机默认内存128MB这里简化实现省去了实模式代码里中断向BIOS询问可用内存大小的步骤
mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
div ebx
@ -178,29 +178,45 @@ SetupPaging:
; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
; 首先初始化页目录
mov ax, SelectorFlatRW
mov es, ax
InitDirPages1:
; 初始化0~128MB的根目录页表
mov edi, PageDirBase ; 此段首地址为 PageDirBase
xor eax, eax
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .1
; 再初始化所有页表
InitDirPages2:
; 初始化3GB + 0 ~ 3GB + 128MB的根目录页表
mov ecx, [esp]
mov edi, PageDirBase + 0c00h
.1:
stosd
add eax, 4096
loop .1
InitTblPages1:
; 再初始化所有0~128MB页表
pop eax ; 页表个数
mov ebx, 1024 ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE个数 = 页表个数 * 1024
push ecx
mov edi, PageTblBase ; 此段首地址为 PageTblBase
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
.1:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .2
loop .1
InitTblPages2:
pop ecx
mov eax, PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .1
mov eax, PageDirBase
mov cr3, eax

View File

@ -1,7 +1,7 @@
#include "type.h"
#include "x86.h"
#include "elf.h"
#include "fat32.h"
#include <type.h>
#include <elf.h>
#include <fat32.h>
#include <x86.h>
/*
*

View File

@ -1,8 +1,6 @@
#ifndef MINIOS_ASSERT_H
#define MINIOS_ASSERT_H
#include "stdio.h"
void _warn(const char*, int, const char*, ...);
void _panic(const char*, int, const char*, ...) __attribute__((noreturn));

View File

@ -1,7 +1,7 @@
#ifndef MINIOS_ELF_H
#define MINIOS_ELF_H
#include "type.h"
#include <type.h>
#define ELF_MAGIC 0x464C457FU /* "\x7FELF" in little endian */

139
inc/errno.h Normal file
View File

@ -0,0 +1,139 @@
#ifndef MINIOS_ERRNO_H
#define MINIOS_ERRNO_H
#define EPERM 1
#define ENOENT 2
#define ESRCH 3
#define EINTR 4
#define EIO 5
#define ENXIO 6
#define E2BIG 7
#define ENOEXEC 8
#define EBADF 9
#define ECHILD 10
#define EAGAIN 11
#define ENOMEM 12
#define EACCES 13
#define EFAULT 14
#define ENOTBLK 15
#define EBUSY 16
#define EEXIST 17
#define EXDEV 18
#define ENODEV 19
#define ENOTDIR 20
#define EISDIR 21
#define EINVAL 22
#define ENFILE 23
#define EMFILE 24
#define ENOTTY 25
#define ETXTBSY 26
#define EFBIG 27
#define ENOSPC 28
#define ESPIPE 29
#define EROFS 30
#define EMLINK 31
#define EPIPE 32
#define EDOM 33
#define ERANGE 34
#define EDEADLK 35
#define ENAMETOOLONG 36
#define ENOLCK 37
#define ENOSYS 38
#define ENOTEMPTY 39
#define ELOOP 40
#define EWOULDBLOCK EAGAIN
#define ENOMSG 42
#define EIDRM 43
#define ECHRNG 44
#define EL2NSYNC 45
#define EL3HLT 46
#define EL3RST 47
#define ELNRNG 48
#define EUNATCH 49
#define ENOCSI 50
#define EL2HLT 51
#define EBADE 52
#define EBADR 53
#define EXFULL 54
#define ENOANO 55
#define EBADRQC 56
#define EBADSLT 57
#define EDEADLOCK EDEADLK
#define EBFONT 59
#define ENOSTR 60
#define ENODATA 61
#define ETIME 62
#define ENOSR 63
#define ENONET 64
#define ENOPKG 65
#define EREMOTE 66
#define ENOLINK 67
#define EADV 68
#define ESRMNT 69
#define ECOMM 70
#define EPROTO 71
#define EMULTIHOP 72
#define EDOTDOT 73
#define EBADMSG 74
#define EOVERFLOW 75
#define ENOTUNIQ 76
#define EBADFD 77
#define EREMCHG 78
#define ELIBACC 79
#define ELIBBAD 80
#define ELIBSCN 81
#define ELIBMAX 82
#define ELIBEXEC 83
#define EILSEQ 84
#define ERESTART 85
#define ESTRPIPE 86
#define EUSERS 87
#define ENOTSOCK 88
#define EDESTADDRREQ 89
#define EMSGSIZE 90
#define EPROTOTYPE 91
#define ENOPROTOOPT 92
#define EPROTONOSUPPORT 93
#define ESOCKTNOSUPPORT 94
#define EOPNOTSUPP 95
#define ENOTSUP EOPNOTSUPP
#define EPFNOSUPPORT 96
#define EAFNOSUPPORT 97
#define EADDRINUSE 98
#define EADDRNOTAVAIL 99
#define ENETDOWN 100
#define ENETUNREACH 101
#define ENETRESET 102
#define ECONNABORTED 103
#define ECONNRESET 104
#define ENOBUFS 105
#define EISCONN 106
#define ENOTCONN 107
#define ESHUTDOWN 108
#define ETOOMANYREFS 109
#define ETIMEDOUT 110
#define ECONNREFUSED 111
#define EHOSTDOWN 112
#define EHOSTUNREACH 113
#define EALREADY 114
#define EINPROGRESS 115
#define ESTALE 116
#define EUCLEAN 117
#define ENOTNAM 118
#define ENAVAIL 119
#define EISNAM 120
#define EREMOTEIO 121
#define EDQUOT 122
#define ENOMEDIUM 123
#define EMEDIUMTYPE 124
#define ECANCELED 125
#define ENOKEY 126
#define EKEYEXPIRED 127
#define EKEYREVOKED 128
#define EKEYREJECTED 129
#define EOWNERDEAD 130
#define ENOTRECOVERABLE 131
#define ERFKILL 132
#define EHWPOISON 133
#endif

View File

@ -1,7 +1,7 @@
#ifndef MINIOS_FAT32_H
#define MINIOS_FAT32_H
#include "type.h"
#include <type.h>
struct BPB {
u8 BS_jmpBoot[3];

8
inc/kern/exec.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef MINIOS_KERN_EXEC_H
#define MINIOS_KERN_EXEC_H
#include <kern/process.h>
ssize_t kern_exec(PROCESS_0 *p_proc, const char *pathname);
#endif /* MINIOS_KERN_EXEC_H */

8
inc/kern/exit.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef MINIOS_KERN_EXIT_H
#define MINIOS_KERN_EXIT_H
#include <kern/process.h>
ssize_t kern_exit(PROCESS_0 *p_proc, int exit_code);
#endif /* MINIOS_KERN_EXIT_H */

9
inc/kern/fork.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef MINIOS_KERN_FORK_H
#define MINIOS_KERN_FORK_H
#include <type.h>
#include <kern/process.h>
ssize_t kern_fork(PROCESS_0 *p_father);
#endif /* MINIOS_KERN_FORK_H */

11
inc/kern/fs.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef MINIOS_KERN_FS_H
#define MINIOS_KERN_FS_H
#include <type.h>
ssize_t kern_read(int fd, void *buf, size_t count);
ssize_t kern_write(int fd, const void *buf, size_t count);
ssize_t read_file(const char *filename, void *dst);
#endif /* MINIOS_KERN_FS_H */

9
inc/kern/keyboard.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef MINIOS_KERN_KEYBOARD_H
#define MINIOS_KERN_KEYBOARD_H
#include <type.h>
void add_keyboard_buf(u8 ch);
u8 read_keyboard_buf(void);
#endif /* MINIOS_KERN_KEYBOARD_H */

View File

@ -1,7 +1,7 @@
#ifndef MINIOS_KEYMAP_H
#define MINIOS_KEYMAP_H
#ifndef MINIOS_KERN_KEYMAP_H
#define MINIOS_KERN_KEYMAP_H
#include "type.h"
#include <type.h>
#define NR_SCAN_CODES 128
@ -72,9 +72,9 @@
#define RIGHT (0x28 + FLAG_EXT) /* Right */
/* ACPI keys */
#define POWER (0x29 + FLAG_EXT) /* Power */
#define SLEEP (0x2A + FLAG_EXT) /* Sleep */
#define WAKE (0x2B + FLAG_EXT) /* Wake Up */
// #define POWER (0x29 + FLAG_EXT) /* Power */
// #define SLEEP (0x2A + FLAG_EXT) /* Sleep */
// #define WAKE (0x2B + FLAG_EXT) /* Wake Up */
/* Num Pad */
#define PAD_SLASH (0x2C + FLAG_EXT) /* / */
@ -320,4 +320,4 @@ WWW Refresh E0, 67 E0, E7
WWW Favorites E0, 66 E0, E6
*=====================================================================================*/
#endif /* MINIOS_KEYMAP_H */
#endif /* MINIOS_KERN_KEYMAP_H */

13
inc/kern/kmalloc.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef MINIOS_KERN_KMALLOC_H
#define MINIOS_KERN_KMALLOC_H
#include <type.h>
void phy_free_4k(phyaddr_t v);
phyaddr_t phy_malloc_4k(void);
void phy_init_4k();
void kfree(void *v);
void * kmalloc(size_t n);
#endif /* MINIOS_KERN_KMALLOC_H */

12
inc/kern/pmap.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef MINIOS_KERN_PMAP_H
#define MINIOS_KERN_PMAP_H
#include <type.h>
#include <kern/process.h>
void map_kern(u32 cr3, struct page_node **page_list);
void map_elf(PROCESS_0 *p_proc, void *elf_addr);
void map_stack(PROCESS_0 *p_proc);
void recycle_pages(struct page_node *page_list);
#endif /* MINIOS_KERN_PMAP_H */

109
inc/kern/process.h Normal file
View File

@ -0,0 +1,109 @@
#ifndef MINIOS_KERN_PROCESS_H
#define MINIOS_KERN_PROCESS_H
#include <type.h>
#include <mmu.h>
/* pcb中存储用户态进程的寄存器信息 */
struct user_context { /* proc_ptr points here ↑ Low */
u32 gs; /* ┓ │ */
u32 fs; /* ┃ │ */
u32 es; /* ┃ │ */
u32 ds; /* ┃ │ */
u32 edi; /* ┃ │ */
u32 esi; /* ┣ pushed by save() │ */
u32 ebp; /* ┃ │ */
u32 kernel_esp; /* <- 'popad' will ignore it │ */
u32 ebx; /* ┃ ↑栈从高地址往低地址增长 */
u32 edx; /* ┃ │ */
u32 ecx; /* ┃ │ */
u32 eax; /* ┛ │ */
u32 retaddr; /* return address for assembly code save() │ */
u32 eip; /* ┓ │ */
u32 cs; /* ┃ │ */
u32 eflags; /* ┣ these are pushed by CPU during interrupt │ */
u32 esp; /* ┃ │ */
u32 ss; /* ┛ ┷High */
};
struct kern_context {
u32 eflags;
u32 edi;
u32 esi;
u32 ebp;
u32 ebx;
u32 edx;
u32 ecx;
u32 eax;
u32 esp;
};
enum proc_statu {IDLE, READY, SLEEP, ZOMBIE, INITING};
typedef struct s_proc PROCESS_0;
/*
*
* cr3laddr被置为-1
* 线
*/
struct page_node {
struct page_node *nxt;
phyaddr_t paddr;
uintptr_t laddr;
};
/*
*
*/
struct son_node {
struct son_node *pre;
struct son_node *nxt;
PROCESS_0 *p_son;
};
struct tree_node {
PROCESS_0 *p_fa;
struct son_node *sons;
};
/* pcb */
struct s_proc {
struct user_context user_regs;
struct kern_context kern_regs;
u32 lock;
enum proc_statu statu;
u32 pid;
phyaddr_t cr3;
struct page_node *page_list;
int exit_code;
int priority;
int ticks;
struct tree_node fork_tree;
};
#define KERN_STACKSIZE (8 * KB)
typedef union u_proc {
PROCESS_0 pcb; // 内核pcb
char _pad[KERN_STACKSIZE]; // pcb往上剩下的空间用于内核栈
}PROCESS; // 通过union控制每个进程占8kb空间
/* 指向当前进程pcb的指针 */
// kern/main.c
extern PROCESS *p_proc_ready;
/* pcb表 */
#define PCB_SIZE 20
// kern/main.c
extern PROCESS proc_table[];
// 内核栈切换上下文函数(汇编接口)
void switch_kern_context(
struct kern_context *current_context,
struct kern_context *next_context
);
// 处理函数
u32 kern_get_pid(PROCESS_0 *p_proc);
#endif /* MINIOS_KERN_PROCESS_H */

View File

@ -1,7 +1,7 @@
#ifndef MINIOS_PROTECT_H
#define MINIOS_PROTECT_H
#ifndef MINIOS_KERN_PROTECT_H
#define MINIOS_KERN_PROTECT_H
#include "type.h"
#include <type.h>
/* 存储段描述符/系统段描述符 */
typedef struct s_descriptor { /* 共 8 个字节 */
@ -89,6 +89,9 @@ typedef struct s_gate
#define INT_VECTOR_IRQ0 0x20
#define INT_VECTOR_IRQ8 0x28
/* 系统调用 */
#define INT_VECTOR_SYSCALL 0x80
/* 权限 */
#define PRIVILEGE_KRNL 0
#define PRIVILEGE_TASK 1
@ -126,6 +129,7 @@ init_gate(GATE *p_gate, u8 desc_type, void *handler, u8 privilage)
#define LDT_SIZE GDT_SIZE
#define IDT_SIZE 256
// kern/start.c
extern DESCRIPTOR gdt[GDT_SIZE];
extern DESCRIPTOR ldt[LDT_SIZE];
extern GATE idt[IDT_SIZE];
@ -163,6 +167,7 @@ typedef struct s_tss {
}TSS;
/* 全局的tss数据结构 */
// kern/start.c
extern TSS tss;
/* 描述符表 */
@ -170,7 +175,7 @@ struct Pseudodesc {
u16 pd_lim; // Limit
u32 pd_base; // Base address
} __attribute__ ((packed));
// kern/start.c
extern struct Pseudodesc gdt_ptr, idt_ptr;
#endif /* MINIOS_PROTECT_H */
#endif /* MINIOS_KERN_PROTECT_H */

6
inc/kern/sche.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef MINIOS_KERN_SCHE_H
#define MINIOS_KERN_SCHE_H
void schedule(void);
#endif

10
inc/kern/stdio.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef MINIOS_KERN_STDIO_H
#define MINIOS_KERN_STDIO_H
#include <stdio.h>
// lib/kern/terminal.c
int kprintf(const char *fmt, ...);
int vkprintf(const char *fmt, va_list);
#endif /* MINIOS_KERN_STDIO_H */

35
inc/kern/syscall.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef MINIOS_KERN_SYSCALL_H
#define MINIOS_KERN_SYSCALL_H
#include <syscall.h>
#include <type.h>
// 由于会出现系统底层函数复用的情况
// miniOS 系统调用分成三层函数调用
// sys_* 外层函数,放进系统调用表中
// do_* 中间函数,留着用于参数处理
// kern_* 底层函数,实际处理函数
// 系统调用处理函数表
// kern/syscall.c
extern ssize_t (*syscall_table[])(void);
// kern/time.c
ssize_t do_get_ticks(void);
// kern/process.c
ssize_t do_get_pid(void);
// kern/exec.c
ssize_t do_exec(const char *pathname);
// kern/exit.c
ssize_t do_exit(int status);
// kern/fork.c
ssize_t do_fork(void);
// kern/wait.c
ssize_t do_wait(int *wstatus);
// kern/fs.c
ssize_t do_read(int fd, void *buf, size_t count);
ssize_t do_write(int fd, const void *buf, size_t count);
// 不告诉你这个实现在哪
ssize_t do_fork_ack(void);
#endif /* MINIOS_KERN_SYSCALL_H */

9
inc/kern/time.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef MINIOS_KERN_TIME_H
#define MINIOS_KERN_TIME_H
#include <type.h>
void timecounter_inc(void);
size_t kern_get_ticks(void);
#endif /* MINIOS_KERN_TIME_H */

View File

@ -1,5 +1,5 @@
#ifndef MINIOS_TRAP_H
#define MINIOS_TRAP_H
#ifndef MINIOS_KERN_TRAP_H
#define MINIOS_KERN_TRAP_H
#define INT_M_CTL 0x20 /* I/O port for interrupt controller <Master> */
#define INT_M_CTLMASK 0x21 /* setting bits in this port disables ints <Master> */
@ -19,13 +19,12 @@
#define PRINTER_IRQ 7
#define AT_WINI_IRQ 14 /* at winchester */
/* 一个用于标记是否有重入内核的标志 */
extern int k_reenter;
/* 执行用户进程的入口(汇编接口) */
void restart();
/* 导入中断处理函数(汇编接口) */
// 系统调用
void int_syscall();
// 异常
void divide_error();
void single_step_exception();
@ -65,6 +64,36 @@ void hwint15();
void enable_irq(int irq);
void disable_irq(int irq);
// kern/main.c
/* 标志着内核是否初始化完成 */
extern bool init_kernel;
static inline void
disable_int()
{
asm volatile("cli");
}
static inline void
enable_int()
{
asm volatile("sti");
}
#define DISABLE_INT() \
{ \
u32 IF_BIT = read_eflags() & FL_IF; \
if (IF_BIT != 0) \
disable_int();
#define ENABLE_INT() \
if (IF_BIT != 0) \
enable_int(); \
}
/* 系统调用实际处理函数(C接口) */
void syscall_handler(void);
/* 异常中断实际处理函数(C接口) */
void exception_handler(int vec_no, int err_code, int eip,
int cs, int eflags);
@ -72,8 +101,9 @@ void exception_handler(int vec_no, int err_code, int eip,
/* 外设中断实际处理函数(C接口) */
void default_interrupt_handler(int irq);
void clock_interrupt_handler(int irq);
void keyboard_interrupt_handler(int irq);
void kb_interrupt_handler(int irq);
/* 外设中断实际处理函数表 */
// kern/trap.c
extern void (*irq_table[])(int);
#endif /* MINIOS_TRAP_H */
#endif /* MINIOS_KERN_TRAP_H */

8
inc/kern/wait.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef MINIOS_KERN_WAIT_H
#define MINIOS_KERN_WAIT_H
#include <type.h>
ssize_t kern_wait(int *wstatus);
#endif /* MINIOS_KERN_WAIT_H */

View File

@ -1,9 +0,0 @@
#ifndef MINIOS_KEYBOARD_H
#define MINIOS_KEYBOARD_H
#include "type.h"
#define KEYBOARD_BUF_REG 0x60
void add_keyboard_buf(u8 ch);
#endif /* MINIOS_KEYBOARD_H */

124
inc/mmu.h Normal file
View File

@ -0,0 +1,124 @@
#ifndef MINIOS_MMU_H
#define MINIOS_MMU_H
#include <type.h>
// 内核并不是恒等映射
// 3GB~3GB+128MB的线性地址被映射到0~128MB的物理地址上
// kernel程序的运行在3GB~3GB+128MB的线性地址上
// 低3GB线性地址用于存放用户程序
#define KERNBASE 0xC0000000
#define K_PHY2LIN(x) ((x)+KERNBASE)
#define K_LIN2PHY(x) ((x)-KERNBASE)
#define KB 1024u
#define MB (1024u * KB)
#define GB (1024u * MB)
// A linear address 'la' has a three-part structure as follows:
//
// +--------10------+-------10-------+---------12----------+
// | Page Directory | Page Table | Offset within Page |
// | Index | Index | |
// +----------------+----------------+---------------------+
// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/
// \---------- PGNUM(la) ----------/
//
// The PDX, PTX, PGOFF, and PGNUM macros decompose linear addresses as shown.
// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la),
// use PGADDR(PDX(la), PTX(la), PGOFF(la)).
// page number field of address
#define PGNUM(la) (((uintptr_t) (la)) >> PTXSHIFT)
// page directory index
#define PDX(la) ((((uintptr_t) (la)) >> PDXSHIFT) & 0x3FF)
// page table index
#define PTX(la) ((((uintptr_t) (la)) >> PTXSHIFT) & 0x3FF)
// offset in page
#define PGOFF(la) (((uintptr_t) (la)) & 0xFFF)
// construct linear address from indexes and offset
#define PGADDR(d, t, o) ((void*) ((d) << PDXSHIFT | (t) << PTXSHIFT | (o)))
// Page directory and page table constants.
#define NPDENTRIES 1024 // page directory entries per page directory
#define NPTENTRIES 1024 // page table entries per page table
#define PGSIZE 4096 // bytes mapped by a page
#define PGSHIFT 12 // log2(PGSIZE)
#define PTSIZE (PGSIZE*NPTENTRIES) // bytes mapped by a page directory entry
#define PTSHIFT 22 // log2(PTSIZE)
#define PTXSHIFT 12 // offset of PTX in a linear address
#define PDXSHIFT 22 // offset of PDX in a linear address
// Page table/directory entry flags.
#define PTE_P 0x001 // Present
#define PTE_W 0x002 // Writeable
#define PTE_U 0x004 // User
#define PTE_PWT 0x008 // Write-Through
#define PTE_PCD 0x010 // Cache-Disable
#define PTE_A 0x020 // Accessed
#define PTE_D 0x040 // Dirty
#define PTE_PS 0x080 // Page Size
#define PTE_G 0x100 // Global
// Address in page table or page directory entry
#define PTE_ADDR(pte) ((phyaddr_t) (pte) & ~0xFFF)
#define PTE_FLAG(pte) ((size_t) (pte) & 0xFFF)
// Control Register flags
#define CR0_PE 0x00000001 // Protection Enable
#define CR0_MP 0x00000002 // Monitor coProcessor
#define CR0_EM 0x00000004 // Emulation
#define CR0_TS 0x00000008 // Task Switched
#define CR0_ET 0x00000010 // Extension Type
#define CR0_NE 0x00000020 // Numeric Errror
#define CR0_WP 0x00010000 // Write Protect
#define CR0_AM 0x00040000 // Alignment Mask
#define CR0_NW 0x20000000 // Not Writethrough
#define CR0_CD 0x40000000 // Cache Disable
#define CR0_PG 0x80000000 // Paging
#define CR4_PCE 0x00000100 // Performance counter enable
#define CR4_MCE 0x00000040 // Machine Check Enable
#define CR4_PSE 0x00000010 // Page Size Extensions
#define CR4_DE 0x00000008 // Debugging Extensions
#define CR4_TSD 0x00000004 // Time Stamp Disable
#define CR4_PVI 0x00000002 // Protected-Mode Virtual Interrupts
#define CR4_VME 0x00000001 // V86 Mode Extensions
// Eflags register
#define FL_CF 0x00000001 // Carry Flag
#define FL_PF 0x00000004 // Parity Flag
#define FL_AF 0x00000010 // Auxiliary carry Flag
#define FL_ZF 0x00000040 // Zero Flag
#define FL_SF 0x00000080 // Sign Flag
#define FL_TF 0x00000100 // Trap Flag
#define FL_IF 0x00000200 // Interrupt Flag
#define FL_DF 0x00000400 // Direction Flag
#define FL_OF 0x00000800 // Overflow Flag
#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask
#define FL_IOPL_0 0x00000000 // IOPL == 0
#define FL_IOPL_1 0x00001000 // IOPL == 1
#define FL_IOPL_2 0x00002000 // IOPL == 2
#define FL_IOPL_3 0x00003000 // IOPL == 3
#define FL_NT 0x00004000 // Nested Task
#define FL_RF 0x00010000 // Resume Flag
#define FL_VM 0x00020000 // Virtual 8086 mode
#define FL_AC 0x00040000 // Alignment Check
#define FL_VIF 0x00080000 // Virtual Interrupt Flag
#define FL_VIP 0x00100000 // Virtual Interrupt Pending
#define FL_ID 0x00200000 // ID flag
// Page fault error codes
#define FEC_PR 0x1 // Page fault caused by protection violation
#define FEC_WR 0x2 // Page fault caused by a write
#define FEC_U 0x4 // Page fault occured while in user mode
#endif /* MINIOS_MMU_H */

View File

@ -1,44 +0,0 @@
#ifndef MINIOS_PROCESS_H
#define MINIOS_PROCESS_H
#include "type.h"
/* pcb中存储用户态进程的寄存器信息 */
typedef struct s_stackframe { /* proc_ptr points here ↑ Low */
u32 gs; /* ┓ │ */
u32 fs; /* ┃ │ */
u32 es; /* ┃ │ */
u32 ds; /* ┃ │ */
u32 edi; /* ┃ │ */
u32 esi; /* ┣ pushed by save() │ */
u32 ebp; /* ┃ │ */
u32 kernel_esp; /* <- 'popad' will ignore it │ */
u32 ebx; /* ┃ ↑栈从高地址往低地址增长 */
u32 edx; /* ┃ │ */
u32 ecx; /* ┃ │ */
u32 eax; /* ┛ │ */
u32 retaddr; /* return address for assembly code save() │ */
u32 eip; /* ┓ │ */
u32 cs; /* ┃ │ */
u32 eflags; /* ┣ these are pushed by CPU during interrupt │ */
u32 esp; /* ┃ │ */
u32 ss; /* ┛ ┷High */
}STACK_FRAME;
/* pcb */
typedef struct s_proc {
STACK_FRAME regs; /* process' registers saved in stack frame */
u32 pid; /* process id passed in from MM */
char p_name[16]; /* name of the process */
}PROCESS;
#ifdef HAPPY_SNAKE
#define PCB_SIZE 1
#else
#define PCB_SIZE 3
#endif
/* 指向当前进程pcb的指针 */
extern PROCESS *p_proc_ready;
/* pcb表 */
extern PROCESS proc_table[PCB_SIZE];
#endif

View File

@ -1,24 +1,17 @@
#ifndef MINIOS_STDIO_H
#define MINIOS_STDIO_H
#include "type.h"
#include "stdarg.h"
#include <type.h>
#include <stdarg.h>
#ifndef NULL
#define NULL ((void *) 0)
#endif /* NULL */
// lib/printfmt.c
// lib/kern/printfmt.c
void printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...);
void vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list);
int snprintf(char *str, int size, const char *fmt, ...);
int vsnprintf(char *str, int size, const char *fmt, va_list);
// lib/terminal.c
int kprintf(const char *fmt, ...);
int vkprintf(const char *fmt, va_list);
// kern/keyboard.c
u8 getch(void);
#endif /* MINIOS_STDIO_H */

View File

@ -1,7 +1,7 @@
#ifndef MINIOS_STRING_H
#define MINIOS_STRING_H
#include "type.h"
#include <type.h>
int strlen(const char *s);
int strnlen(const char *s, size_t size);

15
inc/syscall.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef MINIOS_SYSCALL_H
#define MINIOS_SYSCALL_H
// 系统调用号
#define _NR_get_ticks 0
#define _NR_get_pid 1
#define _NR_read 2
#define _NR_write 3
#define _NR_exec 4
#define _NR_fork 5
#define _NR_wait 6
#define _NR_exit 7
#define _NR_fork_ack 8
#endif /* MINIOS_SYSCALL_H */

View File

@ -1,12 +1,12 @@
#ifndef MINIOS_TERMINAL_H
#define MINIOS_TERMINAL_H
#include "type.h"
#include <type.h>
/*
*
*/
#define TERMINAL_ADDR 0xB8000
#define TERMINAL_ADDR 0xC0B8000
#define TERMINAL_COLUMN 80
#define TERMINAL_ROW 25
#define TERMINAL_SIZE ((TERMINAL_ROW) * (TERMINAL_COLUMN))

View File

@ -1,15 +0,0 @@
#ifndef MINIOS_TIME_H
#define MINIOS_TIME_H
#include "type.h"
void timecounter_inc();
size_t clock();
void change_8253Counter0(int hertz);
#define TIMER0 0x40
#define TIMER_MODEREG 0x43
#define TIMER_MODE 0x34
#define TIMER_FREQ 1193182L
#endif

View File

@ -4,17 +4,18 @@
#ifndef NULL
#define NULL ((void*) 0)
#endif
// #define HAPPY_SNAKE
typedef _Bool bool;
enum { false, true };
typedef long long i64;
typedef unsigned long long u64;
typedef int i32;
typedef unsigned int u32;
typedef unsigned int u32;
typedef short i16;
typedef unsigned short u16;
typedef unsigned short u16;
typedef char i8;
typedef unsigned char u8;
typedef unsigned char u8;
typedef i32 intptr_t;
typedef u32 uintptr_t;

18
inc/user/stdio.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef MINIOS_USER_STDIO_H
#define MINIOS_USER_STDIO_H
#include <stdio.h>
#define STDIN 0
#define STDOUT 1
// lib/user/stdio.c
int printf(const char *fmt, ...);
int vprintf(const char *fmt, va_list);
u8 getch(void);
u8 getchar(void);
void fflush(void);
#endif /* MINIOS_USER_STDIO_H */

24
inc/user/syscall.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef MINIOS_USER_SYSCALL_H
#define MINIOS_USER_SYSCALL_H
#include <type.h>
#include <syscall.h>
ssize_t syscall0(size_t NR_syscall);
ssize_t syscall1(size_t NR_syscall, size_t p1);
ssize_t syscall2(size_t NR_syscall, size_t p1, size_t p2);
ssize_t syscall3(size_t NR_syscall, size_t p1, size_t p2, size_t p3);
ssize_t syscall4(size_t NR_syscall, size_t p1, size_t p2, size_t p3, size_t p4);
ssize_t syscall5(size_t NR_syscall, size_t p1, size_t p2, size_t p3, size_t p4, size_t p5);
ssize_t get_ticks(void);
ssize_t get_pid(void);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t exec(const char *pathname);
ssize_t fork(void);
ssize_t exit(int status);
ssize_t wait(int *wstatus);
ssize_t fork_ack(void);
#endif

14
inc/user/wait.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef MINIOS_USER_WAIT_H
#define MINIOS_USER_WAIT_H
#define WEXITSTATUS(s) (((s) & 0xff00) >> 8)
#define WTERMSIG(s) ((s) & 0x7f)
#define WSTOPSIG(s) WEXITSTATUS(s)
#define WCOREDUMP(s) ((s) & 0x80)
#define WIFEXITED(s) (!WTERMSIG(s))
#define WIFSTOPPED(s) ((short)((((s)&0xffff)*0x10001)>>8) > 0x7f00)
#define WIFSIGNALED(s) (((s)&0xffff)-1U < 0xffu)
#define WIFCONTINUED(s) ((s) == 0xffff)
#endif /* MINIOS_USER_WAIT_H */

109
inc/x86.h
View File

@ -1,7 +1,7 @@
#ifndef MINIOS_X86_H
#define MINIOS_X86_H
#include "type.h"
#include <type.h>
static inline u8
inb(int port)
@ -100,15 +100,112 @@ outl(int port, u32 data)
}
static inline void
disable_int()
lcr0(u32 val)
{
asm volatile("cli");
asm volatile("movl %0,%%cr0" : : "r" (val));
}
static inline u32
rcr0(void)
{
u32 val;
asm volatile("movl %%cr0,%0" : "=r" (val));
return val;
}
static inline u32
rcr2(void)
{
u32 val;
asm volatile("movl %%cr2,%0" : "=r" (val));
return val;
}
static inline void
enable_int()
lcr3(u32 val)
{
asm volatile("sti");
asm volatile("movl %0,%%cr3" : : "r" (val));
}
#endif
static inline u32
rcr3(void)
{
u32 val;
asm volatile("movl %%cr3,%0" : "=r" (val));
return val;
}
static inline void
lcr4(u32 val)
{
asm volatile("movl %0,%%cr4" : : "r" (val));
}
static inline u32
rcr4(void)
{
u32 cr4;
asm volatile("movl %%cr4,%0" : "=r" (cr4));
return cr4;
}
static inline void
tlbflush(void)
{
u32 cr3;
asm volatile("movl %%cr3,%0" : "=r" (cr3));
asm volatile("movl %0,%%cr3" : : "r" (cr3));
}
static inline u32
read_eflags(void)
{
u32 eflags;
asm volatile("pushfl; popl %0" : "=r" (eflags));
return eflags;
}
static inline void
write_eflags(u32 eflags)
{
asm volatile("pushl %0; popfl" : : "r" (eflags));
}
static inline u32
read_ebp(void)
{
u32 ebp;
asm volatile("movl %%ebp,%0" : "=r" (ebp));
return ebp;
}
static inline u32
read_esp(void)
{
u32 esp;
asm volatile("movl %%esp,%0" : "=r" (esp));
return esp;
}
static inline u64
read_tsc(void)
{
u64 tsc;
asm volatile("rdtsc" : "=A" (tsc));
return tsc;
}
static inline u32
xchg(volatile u32 *addr, u32 newval)
{
u32 result;
// The + in "+m" denotes a read-modify-write operand.
asm volatile("lock; xchgl %0, %1"
: "+m" (*addr), "=a" (result)
: "1" (newval)
: "cc");
return result;
}
#endif /* MINIOS_X86_H */

View File

@ -5,46 +5,49 @@
OBJDIRS += kern
KERN_ENTRY_ADDR := 0x200000
KERN_ENTRY_ADDR := 0xC0200000
KERN_SRCFILES:= kern/astart.asm \
kern/atrap.asm \
kern/keyboard.c \
kern/aswitch.asm \
kern/exec.c \
kern/exit.c \
kern/fork.c \
kern/fork_ack.c \
kern/fs.c \
kern/main.c \
kern/pmap.c \
kern/process.c \
kern/sche.c \
kern/start.c \
kern/syscall.c \
kern/time.c \
kern/trap.c \
lib/libch4Core.a\
lib/terminal.c \
kern/wait.c \
lib/kern/terminal.c \
lib/kern/keyboard.c \
lib/kern/kmalloc.c \
lib/printfmt.c \
lib/string.c
KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES))
KERN_OBJFILES := $(patsubst %.asm, $(OBJDIR)/%.o, $(KERN_OBJFILES))
KERN_OBJFILES := $(patsubst $(OBJDIR)/lib/%, $(OBJDIR)/kern/%, $(KERN_OBJFILES))
$(OBJDIR)/kern/fork_ack.o: lib/kern/fork_ack.o
@echo + cp $<
@mkdir -p $(@D)
@cp $< $(@D)
$(OBJDIR)/kern/%.o: kern/%.c $(OBJDIR)/.vars.CFLAGS
@echo + cc $<
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/kern/%.o: lib/%.c $(OBJDIR)/.vars.CFLAGS
@echo + cc $<
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/kern/%.o: kern/%.asm $(OBJDIR)/.vars.CFLAGS
@echo + as obj $<
@mkdir -p $(@D)
@$(AS) -f elf -o $@ $<
$(OBJDIR)/kern/%.a: lib/%.a $(OBJDIR)/.vars.CFLAGS
@echo + cp lib $<
@mkdir -p $(@D)
@cp $< $(@D)
$(OBJDIR)/kern/kernel.bin: $(KERN_OBJFILES) $(OBJDIR)/.vars.LDFLAGS
@echo + ld $@
@$(LD) $(LDFLAGS) -s -Ttext $(KERN_ENTRY_ADDR) -o $@ $(KERN_OBJFILES) $(GCC_LIB)
@$(LD) $(LDFLAGS) -Ttext $(KERN_ENTRY_ADDR) -o $(OBJDIR)/kern/kernel.dbg $(KERN_OBJFILES) $(GCC_LIB)
all: $(OBJDIR)/kern/a.img

View File

@ -45,10 +45,13 @@ SELECTOR_TSS equ 020h
SELECTOR_LDT equ 028h
; 导入函数
; kern/start.c
extern cstart
; kern/main.c
extern kernel_main
; 导入全局变量
; kern/start.c
extern gdt_ptr
extern idt_ptr
@ -68,6 +71,8 @@ _start:
call cstart ; 在此函数中改变了gdt_ptr让它指向新的GDT
lgdt [gdt_ptr] ; 装载GDT
mov ax, SELECTOR_KERNEL_GS
mov gs, ax ; 刷新gs缓存因为在cstart中发生了更新
mov ax, SELECTOR_LDT; 装载LDT
lldt ax
mov ax, SELECTOR_TSS; 装载TSS

59
kern/aswitch.asm Normal file
View File

@ -0,0 +1,59 @@
[BITS 32]
[section .text]
P_STACKBASE equ 0
EFLAGSREG equ P_STACKBASE
EDIREG equ EFLAGSREG + 4
ESIREG equ EDIREG + 4
EBPREG equ ESIREG + 4
EBXREG equ EBPREG + 4
EDXREG equ EBXREG + 4
ECXREG equ EDXREG + 4
EAXREG equ ECXREG + 4
ESPREG equ EAXREG + 4
global switch_kern_context
;void switch_kern_context(
; struct kern_context *current_context,
; struct kern_context *next_context
;)
; 程序语义在kern/process.c的schedule函数有讲
switch_kern_context:
push ebp
mov ebp, esp
push eax
push ebx
mov eax, [ebp + 8]
mov ebx, [ebp + 12]
call .inner_switch
pop ebx
pop eax
pop ebp
ret
.inner_switch:
mov [eax + ESPREG], esp
lea esp, [eax + ESPREG]
push eax
push ecx
push edx
push ebx
push ebp
push esi
push edi
pushf
mov esp, ebx
popf
pop edi
pop esi
pop ebp
pop ebx
pop edx
pop ecx
pop eax
pop esp
ret

View File

@ -2,13 +2,13 @@
; 导入
extern StackTop ; kern/astart.asm
extern tss ; inc/protect.h
extern k_reenter ; inc/trap.h
extern exception_handler ; inc/trap.h
extern irq_table ; inc/trap.h
extern p_proc_ready ; inc/process.h
extern tss ; inc/kern/protect.h
extern exception_handler ; inc/kern/trap.h
extern irq_table ; inc/kern/trap.h
extern syscall_handler ; inc/kern/trap.h
extern p_proc_ready ; inc/kern/process.h
; 具体看inc/process.h pcb维护的寄存器表
; 具体看inc/kern/process.h pcb维护的寄存器表
P_STACKBASE equ 0
GSREG equ P_STACKBASE
FSREG equ GSREG + 4
@ -30,39 +30,32 @@ ESPREG equ EFLAGSREG + 4
SSREG equ ESPREG + 4
P_STACKTOP equ SSREG + 4
extern to_kern_stack ; kern/process.c
save:
pushad ; `.
push ds ; |
push es ; | 保存原寄存器值
push fs ; |
push gs ; /
mov dx, ss
mov ds, dx
mov es, dx
mov eax, esp ;eax = 进程表起始地址
inc dword [k_reenter] ;k_reenter++;
cmp dword [k_reenter], 0 ;if(k_reenter ==0)
jne .1 ;{
mov esp, StackTop ; mov esp, StackTop <--切换到内核栈
push restart ; push restart
jmp [eax + RETADR - P_STACKBASE]; return;
.1: ;} else { 已经在内核栈,不需要再切换
push restart_reenter ; push restart_reenter
jmp [eax + RETADR - P_STACKBASE]; return;
;}
; 具体看inc/protect.h TSS维护的寄存器表
TSS3_S_SP0 equ 4
push ds ; |
push es ; | 保存原寄存器值
push fs ; |
push gs ; /
mov dx, ss
mov ds, dx
mov es, dx
mov eax, esp
test dword [eax + CSREG - P_STACKBASE], 3
jz .1 ; 根据段寄存器的状态信息判断是否发生内核重入
mov esp, StackTop ; 如果没有发生内核重入就意味着要到用户栈了先将esp移入临时内核栈
push eax ; 传入进程表首地址信息
call to_kern_stack ; 这个函数之后esp变为进程内核栈
pop eax ; 恢复进程表首地址信息这个是restart第一句话要用的
.1:
push eax ; 将restart要用的如果在内核栈重入pop esp不会有任何变化
push restart ; 否则eax在进程表首地址pop esp会使esp移动到用户栈栈底
jmp [eax + RETADR - P_STACKBASE]
restart:
mov esp, [p_proc_ready]
lea eax, [esp + P_STACKTOP]
mov dword [tss + TSS3_S_SP0], eax
restart_reenter:
cli
dec dword [k_reenter]
pop esp ; 获悉当前的esp该到哪不用管现在是否要回用户态语义在save中有解释
pop gs
pop fs
pop es
@ -71,6 +64,14 @@ restart_reenter:
add esp, 4
iretd
; 系统调用
int_syscall:
call save
sti
call syscall_handler
cli
ret
EOI equ 0x20
INT_M_CTL equ 0x20 ; I/O port for interrupt controller <Master>
INT_M_CTLMASK equ 0x21 ; setting bits in this port disables ints <Master>
@ -97,9 +98,18 @@ INT_S_CTLMASK equ 0xA1 ; setting bits in this port disables ints <Slave>
ret
%endmacro
; 时钟中断采取全程关中断的模式,时钟中断相当重要,不允许被打扰
ALIGN 16
hwint00: ; Interrupt routine for irq 0 (the clock).
hwint_master 0
call save
mov al, EOI
out INT_M_CTL, al
push 0
call [irq_table + 0]
pop ecx
ret
ALIGN 16
hwint01: ; Interrupt routine for irq 1 (keyboard)
@ -220,6 +230,35 @@ general_protection:
push 13 ; vector_no = D
jmp exception
page_fault:
push eax
mov eax, [esp + 4]
mov [StackTop - 1024], eax
pop eax
pushad ; `.
push ds ; |
push es ; | 保存原寄存器值
push fs ; |
push gs ; /
mov dx, ss
mov ds, dx
mov es, dx
mov eax, esp
test dword [eax + CSREG - P_STACKBASE], 3
jz .1 ; 根据段寄存器的状态信息判断是否发生内核重入
mov esp, StackTop ; 如果没有发生内核重入就意味着要到用户栈了先将esp移入临时内核栈
push eax ; 传入进程表首地址信息
call to_kern_stack ; 这个函数之后esp变为进程内核栈
pop eax ; 恢复进程表首地址信息这个是restart第一句话要用的
.1:
mov ecx, [eax + EFLAGSREG - P_STACKBASE]
push ecx
mov ecx, [eax + CSREG - P_STACKBASE]
push ecx
mov ecx, [eax + EIPREG - P_STACKBASE]
push ecx
mov ebx, [StackTop - 1024]
push ebx
push 14 ; vector_no = E
jmp exception
copr_error:
@ -234,6 +273,8 @@ exception:
; 一堆符号导出,没别的
global restart
; 系统调用
global int_syscall
; 异常处理
global divide_error
global single_step_exception

150
kern/exec.c Normal file
View File

@ -0,0 +1,150 @@
#include <assert.h>
#include <errno.h>
#include <elf.h>
#include <string.h>
#include <x86.h>
#include <kern/fs.h>
#include <kern/exec.h>
#include <kern/kmalloc.h>
#include <kern/pmap.h>
#include <kern/protect.h>
#include <kern/sche.h>
#include <kern/syscall.h>
#include <kern/trap.h>
static inline void
init_segment_regs(PROCESS_0 *p_proc)
{
p_proc->user_regs.cs = (SELECTOR_FLAT_C & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->user_regs.ds = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->user_regs.es = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->user_regs.fs = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->user_regs.ss = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->user_regs.gs = (SELECTOR_VIDEO & SA_RPL_MASK & SA_TI_MASK)
| RPL_USER;
}
static inline void
init_pagetbl(PROCESS_0 *p_proc)
{
phyaddr_t new_cr3 = phy_malloc_4k();
memset((void *)K_PHY2LIN(new_cr3), 0, PGSIZE);
struct page_node *new_page_list = kmalloc(sizeof(struct page_node));
new_page_list->nxt = NULL;
new_page_list->paddr = new_cr3;
new_page_list->laddr = -1;
map_kern(new_cr3, &new_page_list);
// 这里需要特别注意的是,替换用户页表这种危险行为
// 无论如何都是要关中断的,不允许中间有任何调度
// 否则很有可能换到一半,啪,一个中断进来调度了
// 调度回来时要加载cr3然后惊喜地发现page fault了
struct page_node *old_page_list;
DISABLE_INT();
old_page_list = p_proc->page_list;
p_proc->cr3 = new_cr3;
p_proc->page_list = new_page_list;
lcr3(p_proc->cr3);
ENABLE_INT();
// 最后记得回收进程页面资源
recycle_pages(old_page_list);
}
ssize_t
kern_exec(PROCESS_0 *p_proc, const char *pathname)
{
ssize_t ret = 0;
// 路径名的地址必须在内核中的地址
// 因为exec会回收用户程序的页表
// 这会导致程序中的地址发生缺页触发page fault
assert((uintptr_t)pathname >= K_PHY2LIN(0));
// 进来先给自己上个锁
while (xchg(&p_proc->lock, 1) == 1)
schedule();
if ((ret = read_file(pathname, (void *)K_PHY2LIN(48 * MB))) < 0)
goto free;
// 这里有一个特判如果不是elf文件会返回ENOEXEC
if (*(uintptr_t *)K_PHY2LIN(48 * MB) != ELF_MAGIC) {
ret = -ENOEXEC;
goto free;
}
assert(p_proc->statu == READY);
// 初始化用户寄存器
memset(&p_proc->user_regs, 0, sizeof(p_proc->user_regs));
init_segment_regs(p_proc);
p_proc->user_regs.eflags = 0x1202; /* IF=1, IOPL=1 */
// 初始化页表
init_pagetbl(p_proc);
// 将elf加载到文件指定的地址
map_elf(p_proc, (void *)K_PHY2LIN(48 * MB));
// 初始化用户栈空间
map_stack(p_proc);
free:
// 最后记得释放锁
xchg(&p_proc->lock, 0);
return ret;
}
/*
* src路径名转换为段目录项名到dst
* -ENOENT
*/
static inline ssize_t
translate_pathname(char *dst, const char *src)
{
assert(strlen(dst) == 11);
char *st = (char *)src;
char *ed = st + strlen(st);
char *dot = ed;
for (char *c = st ; *c ; c++) {
if (*c == '.')
dot = c;
}
if (dot - st > 8)
return -ENOENT;
memcpy(dst, st, dot - st);
if (ed - dot - 1 > 3)
return -ENOENT;
memcpy(dst + 8, dot + 1, ed == dot ? 0 : ed - dot - 1);
for (char *c = dst ; *c ; c++) {
if ('a' <= *c && *c <= 'z')
*c += 'A' - 'a';
}
return 0;
}
ssize_t
do_exec(const char *pathname)
{
ssize_t ret = 0;
char *name_cpy;
// 原来路径名转短目录项名的工作可以交给do_exec来做
name_cpy = kmalloc(12);
memset(name_cpy, ' ', 11);
name_cpy[11] = '\0';
// 当然如果转文件名失败了会返回-ENOENT
if ((ret = translate_pathname(name_cpy, pathname)) < 0)
goto free;
ret = kern_exec(&p_proc_ready->pcb, name_cpy);
free:
if (name_cpy)
kfree(name_cpy);
return ret;
}

117
kern/exit.c Normal file
View File

@ -0,0 +1,117 @@
#include <assert.h>
#include <x86.h>
#include <kern/exit.h>
#include <kern/kmalloc.h>
#include <kern/pmap.h>
#include <kern/sche.h>
#include <kern/syscall.h>
#include <kern/trap.h>
static void
awake_father_and_become_zombie(PROCESS_0 *p_proc)
{
PROCESS_0 *p_fa;
// 由于我们已经协商了上锁的顺序,上锁顺序是从父亲到儿子
// 但是这里我们必须锁了自己才能知道父进程是谁
// 所以这里我们换了一种做法,不再一个劲的等锁
// 如果父进程上锁失败,就直接将自己的锁释放拱手让人
// 这样做有个好处,要么两个锁同时被上掉,要么两个锁同时被释放
// 这也是一个非常有趣的实现方法
// 而真实情况是将大锁拆小锁不可能一个pcb就一个大锁保着这样又浪费效率又难写
while (1) {
if (xchg(&p_proc->lock, 1) == 1)
goto loop;
p_fa = p_proc->fork_tree.p_fa;
if (xchg(&p_fa->lock, 1) == 1)
goto free;
break;
free:
xchg(&p_proc->lock, 0);
loop:
schedule();
}
// 这两句assert防止其他奇奇怪怪的状态出现
assert(p_proc->statu == READY);
assert(p_fa->statu == READY || p_fa->statu == SLEEP);
p_proc->statu = ZOMBIE;
p_fa->statu = READY;
xchg(&p_fa->lock, 0);
xchg(&p_proc->lock, 0);
}
static void
transfer_orphans(PROCESS_0 *p_proc)
{
PROCESS_0 *p_init = &proc_table[0].pcb;
// 上锁顺序为:初始进程->当前进程->子进程
while (xchg(&p_init->lock, 1) == 1)
schedule();
while (xchg(&p_proc->lock, 1) == 1)
schedule();
for (struct son_node *p = p_proc->fork_tree.sons ; p ;) {
PROCESS_0 *p_son = p->p_son;
struct son_node *p_nxt = p->nxt;
// 上子进程的锁,因为需要修改子进程的父进程信息(移到初始进程下)
while (xchg(&p_son->lock, 1) == 1)
schedule();
// 将子进程的进程树信息做修改
// 将节点node移到初始进程的链表头处
p_son->fork_tree.p_fa = p_init;
// 确保这个是双向链表头
assert(p->pre == NULL);
// 接下来就是一坨又臭又长的链表操作部分
if (p->nxt != NULL)
p->nxt->pre = p->pre;
p->nxt = p_init->fork_tree.sons;
if (p->nxt != NULL)
p->nxt->pre = p;
p_init->fork_tree.sons = p;
// 最后释放子进程的锁
xchg(&p_son->lock, 0);
p = p_nxt;
}
// 在移交完后当前进程的子进程信息会被清空
p_proc->fork_tree.sons = NULL;
// 初始进程可能在休眠,而且子进程可能是僵尸进程,需要将初始进程唤醒
// 初始进程会一直调用wait系统调用回收僵尸子进程
assert(p_init->statu == READY || p_init->statu == SLEEP);
p_init->statu = READY;
free:
xchg(&p_proc->lock, 0);
xchg(&p_init->lock, 0);
}
ssize_t
kern_exit(PROCESS_0 *p_proc, int exit_code)
{
// 托孤,将所有子进程转移到初始进程下
transfer_orphans(p_proc);
// 上锁修改exit code
while (xchg(&p_proc->lock, 1) == 1)
schedule();
p_proc->exit_code = exit_code;
xchg(&p_proc->lock, 0);
// 下面两个操作会修改进程的状态,
// 这是非常危险的,最好用开关中断保护上
DISABLE_INT();
// 这个函数干了两件事,唤醒父进程,将自己状态置为僵尸进程
// 关中断就相当于两件事同时干了
awake_father_and_become_zombie(p_proc);
// 在触发了调度之后这个进程在被回收之前永远无法被调度到
schedule();
ENABLE_INT();
panic("exit failed!");
}
ssize_t
do_exit(int status)
{
// 为什么这个参数这么奇怪?你可能需要读读手册
return kern_exit(&p_proc_ready->pcb, (status & 0xFF) << 8);
}

217
kern/fork.c Normal file
View File

@ -0,0 +1,217 @@
#include <assert.h>
#include <x86.h>
#include <errno.h>
#include <string.h>
#include <kern/fork.h>
#include <kern/syscall.h>
#include <kern/trap.h>
#include <kern/pmap.h>
#include <kern/kmalloc.h>
#include <kern/sche.h>
#include <kern/stdio.h>
// get the flag of a lin addr in page table
static inline u32 get_flag(uintptr_t laddr, u32 cr3) {
assert(PGOFF(laddr) == 0);
uintptr_t *pde_ptr = (uintptr_t *)K_PHY2LIN(cr3);
assert((pde_ptr[PDX(laddr)] & PTE_P) != 0);
phyaddr_t pte_phy = PTE_ADDR(pde_ptr[PDX(laddr)]);
uintptr_t *pte_ptr = (uintptr_t *)K_PHY2LIN(pte_phy);
assert((pte_ptr[PTX(laddr)] & PTE_P) != 0);
return PTE_FLAG(pte_ptr[PTX(laddr)]);
}
static phyaddr_t
alloc_phy_page(struct page_node **page_list)
{
phyaddr_t paddr = phy_malloc_4k();
struct page_node *new_node = kmalloc(sizeof(struct page_node));
new_node->nxt = *page_list;
new_node->paddr = paddr;
new_node->laddr = -1;
*page_list = new_node;
return paddr;
}
static phyaddr_t
lin_mapping_phy(u32 cr3,
struct page_node **page_list,
uintptr_t laddr,
u32 pte_flag)
{
assert(PGOFF(laddr) == 0);
uintptr_t *pde_ptr = (uintptr_t *)K_PHY2LIN(cr3);
if ((pde_ptr[PDX(laddr)] & PTE_P) == 0) {
phyaddr_t pte_phy = alloc_phy_page(page_list);
memset((void *)K_PHY2LIN(pte_phy), 0, PGSIZE);
pde_ptr[PDX(laddr)] = pte_phy | PTE_P | PTE_W | PTE_U;
}
phyaddr_t pte_phy = PTE_ADDR(pde_ptr[PDX(laddr)]);
uintptr_t *pte_ptr = (uintptr_t *)K_PHY2LIN(pte_phy);
phyaddr_t page_phy;
if ((pte_ptr[PTX(laddr)] & PTE_P) != 0)
warn("fork alloc: this page was mapped before, laddr: %x", laddr);
page_phy = alloc_phy_page(page_list);
(*page_list)->laddr = laddr;
pte_ptr[PTX(laddr)] = page_phy | pte_flag;
return page_phy;
}
// modified from pmap.c::map_elf
// first alloc a new page list, together with a new cr3(with page directory page)
// second map kern
// third iterate over parent pages,
// for each parent page(except page table pages, child has its own) , map a page in child proc
// then copy content of this page to child
// finally, set child->page_list to this brand new one and set cr3
// SET: child's page list and cr3
static inline void copy_parent_pages(PROCESS_0 *child, PROCESS_0 *parent){
phyaddr_t new_cr3 = phy_malloc_4k();
memset((void *)K_PHY2LIN(new_cr3), 0, PGSIZE);
struct page_node *new_page_list = kmalloc(sizeof(struct page_node));
new_page_list->nxt = NULL;
new_page_list->paddr = new_cr3;
new_page_list->laddr = -1;
// maybe redundant, let it be for now
map_kern(new_cr3, &new_page_list);
// iterate over parent pages
for (struct page_node* pa_page = parent->page_list; pa_page != NULL; pa_page = pa_page->nxt) {
// no need to take care of non-physical page, we have our own page table and cr3
if (pa_page->laddr == -1) continue;
// first alloc a new page for the same laddr, and set flag same as parent
phyaddr_t new_paddr = lin_mapping_phy(new_cr3, &new_page_list, pa_page->laddr, get_flag(pa_page->laddr, parent->cr3));
// copy parent page to child page using paddr
memcpy((void*)K_PHY2LIN(new_paddr), (const void*)K_PHY2LIN(pa_page->paddr), PGSIZE);
// kprintf("%x -> %x\n", K_PHY2LIN(pa_page->paddr), K_PHY2LIN(new_paddr));
}
child->page_list = new_page_list;
child->cr3 = new_cr3;
}
ssize_t
kern_fork(PROCESS_0 *p_fa)
{
// 这可能是你第一次实现一个比较完整的功能,你可能会比较畏惧
// 但是放心,别怕,先别想自己要实现一个这么大的东西而毫无思路
// 这样你在焦虑的同时也在浪费时间,就跟你在实验五中被页表折磨一样
// 人在触碰到未知的时候总是害怕的,这是天性,所以请你先冷静下来
// fork系统调用会一步步引导你写出来不会让你本科造火箭的
// panic("Unimplement! CALM DOWN!");
// 推荐是边写边想,而不是想一车然后写,这样非常容易计划赶不上变化
//? before all, lock father
while (xchg(&p_fa->lock, 1) == 1)
schedule();
// fork的第一步你需要找一个空闲IDLE的进程作为你要fork的子进程
PROCESS *proc_child = NULL;
int i = 0;
//? maybe cli will be better when preserving a proc resource
DISABLE_INT();
for (i = 1; i < PCB_SIZE; ++ i) {
if (proc_table[i].pcb.statu == IDLE) {
proc_table[i].pcb.statu = INITING;
proc_child = &proc_table[i];
break;
}
}
ENABLE_INT();
if (proc_child == NULL) {
// NO hell. no free proc_table found.
xchg(&p_fa->lock, 0);
return -EAGAIN;
}
assert(proc_child->pcb.lock == 0);
while (xchg(&proc_child->pcb.lock, 1) == 1)
schedule();
PROCESS_0 *p_child = &proc_child->pcb;
// 再之后你需要做的是好好阅读一下pcb的数据结构搞明白结构体中每个成员的语义
// 别光扫一遍,要搞明白这个成员到底在哪里被用到了,具体是怎么用的
// 可能exec和exit系统调用的代码能够帮助你对pcb的理解不先理解好pcb你fork是无从下手的
// panic("Unimplement! read pcb");
// 在阅读完pcb之后终于可以开始fork工作了
// 本质上相当于将父进程的pcb内容复制到子进程pcb中
// 但是你需要想清楚,哪些应该复制到子进程,哪些不应该复制,哪些应该子进程自己初始化
// 其中有三个难点
// 1. 子进程"fork"的返回值怎么处理?(需要你对系统调用整个过程都掌握比较清楚,如果非常清晰这个问题不会很大)
// 2. 子进程内存如何复制别傻乎乎地复制父进程的cr3本质上相当于与父进程共享同一块内存
// 而共享内存肯定不符合fork的语义这样一个进程写内存某块地方会影响到另一个进程这个东西需要你自己思考如何复制父进程的内存
// 3. 在fork结束后肯定会调度到子进程那么你怎么保证子进程能够正常进入用户态
// (你肯定会觉得这个问题问的莫名其妙的,只能说你如果遇到那一块问题了就会体会到这个问题的重要性,
// 这需要你对调度整个过程都掌握比较清楚)
//? Start to COPY
//? 1. COPY PCB
// p_fa shares the same base addr as its padding
// anyways, copy the whole proc_table item(8K?)
DISABLE_INT(); // make sure this process is never interrupted
// memset(proc_child, 0, sizeof(PROCESS)); // clear child's kernel stack //? seem to be useless
// the commented fields below will be set later
// p_child->cr3
p_child->exit_code = p_fa->exit_code;
// p_child->fork_tree
// p_child->kern_regs
// p_child->lock
// p_child->page_list
p_child->pid = i;
p_child->priority = p_fa->priority;
p_child->statu = INITING; //! important!!! if you copied this from parent, haha, waste one hour
p_child->ticks = p_fa->ticks;
p_child->user_regs = p_fa->user_regs;
ENABLE_INT(); // TODO: may be a useless atomic block, try to remove and test again
//? 2. ALLOC PAGES AND COPY PHYSICAL MEMORY
copy_parent_pages(p_child, p_fa);
// panic("Unimplement! soul torture1");
//? 3. SET restart point
// //! maybe, kern stack different, use offset relevant to stack base addr to set child's kern esp
// u32 esp_off = p_fa->kern_regs.esp - (u32)p_fa;
// p_child->kern_regs.esp = (u32)p_child + esp_off;
// u32 ebp_off = p_fa->kern_regs.ebp - (u32)p_fa;
// p_child->kern_regs.ebp = (u32)p_child + ebp_off;
// kprintf("%x %x\n", p_child->kern_regs.esp, p_fa->kern_regs.esp);
// kprintf("%x %x\n", p_child->user_regs.kernel_esp, p_fa->user_regs.kernel_esp);
//! kern_ctx is not part of our FSM model of user process, never copy it from parent
//! or 1 another hour will be wasted
// just set it as a new process from scratch, directly jump to restart
p_child->kern_regs.esp = (u32)(proc_child + 1) - 8;
*(u32 *)(p_child->kern_regs.esp + 0) = (u32)restart;
*(u32 *)(p_child->kern_regs.esp + 4) = (u32)p_child;
p_child->user_regs.eax = 0; // eax as the retval
// 别忘了维护进程树,将这对父子进程关系添加进去
// ? maintain process tree
p_child->fork_tree.p_fa = p_fa;
// malloc a son_node
struct son_node* p_son = (struct son_node*)kmalloc(sizeof(struct son_node));
p_son->p_son = p_child;
// head insert into parent's son list
p_son->pre = NULL;
p_son->nxt = p_fa->fork_tree.sons;
if (p_fa->fork_tree.sons != NULL) {
p_son->nxt->pre = p_son;
}
p_fa->fork_tree.sons = p_son;
// 最后你需要将子进程的状态置为READY说明fork已经好了子进程准备就绪了
p_child->statu = READY;
xchg(&p_child->lock, 0);
xchg(&p_fa->lock, 0);
// 在你写完fork代码时先别急着运行跑先要对自己来个灵魂拷问
// 1. 上锁上了吗?所有临界情况都考虑到了吗?(永远要相信有各种奇奇怪怪的并发问题)
// 2. 所有错误情况都判断到了吗错误情况怎么处理RTFM->`man 2 fork`
// 3. 你写的代码真的符合fork语义吗
// panic("Unimplement! soul torture");
// ? return to parent proc by the normal syscall return machanism
return p_child->pid;
}
ssize_t
do_fork(void)
{
return kern_fork(&p_proc_ready->pcb);
}

182
kern/fs.c Normal file
View File

@ -0,0 +1,182 @@
#include <assert.h>
#include <errno.h>
#include <fat32.h>
#include <mmu.h>
#include <string.h>
#include <x86.h>
#include <kern/keyboard.h>
#include <kern/fs.h>
#include <kern/stdio.h>
#include <kern/syscall.h>
/*
*
* assert让fd为00
*
*/
ssize_t
kern_read(int fd, void *buf, size_t count)
{
assert(fd == 0);
char *s = buf;
for (size_t i = 0 ; i < count ; i++) {
char c = read_keyboard_buf();
if (c == -1)
return i;
*s++ = c;
}
return count;
}
ssize_t
do_read(int fd, void *buf, size_t count)
{
assert(buf < (void *)KERNBASE);
assert(buf + count < (void *)KERNBASE);
return kern_read(fd, buf, count);
}
/*
*
* assert让fd为11
*
*/
ssize_t
kern_write(int fd, const void *buf, size_t count)
{
assert(fd == 1);
const char *s = buf;
for (size_t i = 0 ; i < count ; i++)
kprintf("%c", *s++);
return count;
}
ssize_t
do_write(int fd, const void *buf, size_t count)
{
assert(buf < (void *)KERNBASE);
assert(buf + count < (void *)KERNBASE);
return kern_write(fd, buf, count);
}
#define SECTSIZE 512
static void
waitdisk(void)
{
// wait for disk reaady
while ((inb(0x1F7) & 0xC0) != 0x40)
/* do nothing */;
}
static void
readsect(void *dst, u32 offset)
{
// wait for disk to be ready
waitdisk();
outb(0x1F2, 1); // count = 1
outb(0x1F3, offset);
outb(0x1F4, offset >> 8);
outb(0x1F5, offset >> 16);
outb(0x1F6, (offset >> 24) | 0xE0);
outb(0x1F7, 0x20); // cmd 0x20 - read sectors
// wait for disk to be ready
waitdisk();
// read a sector
insl(0x1F0, dst, SECTSIZE/4);
}
u32 fat_start_sec;
u32 data_start_sec;
u32 fat_now_sec;
struct BPB bpb;
/*
*
*/
static u32
get_next_clus(u32 current_clus)
{
u32 sec = current_clus * 4 / SECTSIZE;
u32 off = current_clus * 4 % SECTSIZE;
static u32 buf[SECTSIZE / 4];
if (fat_now_sec != fat_start_sec + sec) {
readsect(buf, fat_start_sec + sec);
fat_now_sec = fat_start_sec + sec;
}
return buf[off / 4];
}
/*
*
* dst开头的位置
*/
static void *
read_data_sec(void *dst, u32 current_clus)
{
current_clus -= 2;
current_clus *= bpb.BPB_SecPerClus;
current_clus += data_start_sec;
for (int i = 0 ; i < bpb.BPB_SecPerClus ; i++, dst += SECTSIZE)
readsect(dst, current_clus + i);
return dst;
}
/*
* filename读取文件到dst处
* filename要求是短目录项名11
* dst推荐是3GB + 48MB
*/
ssize_t
read_file(const char *filename, void *dst)
{
assert(strlen(filename) == 11);
readsect(&bpb, 0);
fat_start_sec = bpb.BPB_RsvdSecCnt;
data_start_sec = fat_start_sec + bpb.BPB_FATSz32 * bpb.BPB_NumFATs;
u32 root_clus = bpb.BPB_RootClus;
u32 file_clus = 0;
assert(bpb.BPB_BytsPerSec == SECTSIZE && bpb.BPB_SecPerClus == 8);
static char buf[SECTSIZE * 8];
// 遍历目录项获取文件起始簇号
while (root_clus < 0x0FFFFFF8) {
void *read_end = read_data_sec((void *)buf, root_clus);
for (struct Directory_Entry *p = (void *)buf
; (void *)p < read_end ; p++) {
if (strncmp(p->DIR_Name, filename, 11) == 0) {
assert(p->DIR_FileSize <= 16 * MB);
file_clus = (u32)p->DIR_FstClusHI << 16 |
p->DIR_FstClusLO;
break;
}
}
if (file_clus != 0)
break;
root_clus = get_next_clus(root_clus);
}
if (file_clus == 0)
return -ENOENT;
// 读入文件
while (file_clus < 0x0FFFFFF8) {
dst = read_data_sec(dst, file_clus);
file_clus = get_next_clus(file_clus);
}
return 0;
}

View File

@ -1,10 +0,0 @@
//
// Created by ASUS on 2022/10/3.
//
#ifndef CH3_GAME_H
#define CH3_GAME_H
void startGame();
#endif //CH3_GAME_H

View File

@ -1,58 +0,0 @@
#include "keymap.h"
#include "stdio.h"
#include "type.h"
#include "x86.h"
#define KB_INBUF_SIZE 4
typedef struct kb_inbuf {
u8* p_head;
u8* p_tail;
int count;
u8 buf[KB_INBUF_SIZE];
} KB_INPUT;
static KB_INPUT kb_input = {
.p_head = kb_input.buf,
.p_tail = kb_input.buf,
.count = 0,
};
/*
* ch这个字符放进内核的字符缓冲区
*/
void
add_keyboard_buf(u8 ch)
{
if (kb_input.count < KB_INBUF_SIZE) {
*(kb_input.p_head) = ch;
kb_input.p_head ++;
if (kb_input.p_head == kb_input.buf + KB_INBUF_SIZE) {
kb_input.p_head = kb_input.buf;
}
kb_input.count ++;
}
}
/*
* -1
*
*/
u8
getch(void)
{
if (kb_input.count == 0)
return -1;
else {
// disable_int();
// in user mode, cli and sti will only cause protection exception
u8 val = *(kb_input.p_tail);
kb_input.p_tail ++;
if (kb_input.p_tail == kb_input.buf + KB_INBUF_SIZE) {
kb_input.p_tail = kb_input.buf;
}
kb_input.count --;
// enable_int();
return val;
}
}

View File

@ -1,117 +1,56 @@
#include "assert.h"
#include "stdio.h"
#include "string.h"
#include "process.h"
#include "protect.h"
#include "type.h"
#include "trap.h"
#include "x86.h"
#include "time.h"
#include "keyboard.h"
#include "game.h"
#include <assert.h>
#include <x86.h>
/*
*
*/
void TestA()
{
int i = 0;
u8 ch;
while(1){
if ((ch = getch()) != 255) {
kprintf("%c", ch);
}
// kprintf("A%d.",i++);
for (int j = 0 ; j < 5e7 ; j++)
;//do nothing
}
}
#include <kern/stdio.h>
#include <kern/syscall.h>
#include <kern/protect.h>
#include <kern/process.h>
#include <kern/trap.h>
#include <kern/kmalloc.h>
void TestB()
{
int i = 0;
while(1){
// kprintf("B%d.",i++);
for (int j = 0 ; j < 5e7 ; j++)
;//do nothing
}
}
// 标志着内核是否处理完成
bool init_kernel;
void TestC()
{
int i = 0;
while(1){
// kprintf("C%d.",i++);
for (int j = 0 ; j < 5e7 ; j++)
;//do nothing
}
}
// 每个栈空间有4kb大
#define STACK_PREPROCESS 0x1000
#define STACK_TOTSIZE STACK_PREPROCESS * PCB_SIZE
// 用户栈(直接在内核开一个临时数组充当)
char process_stack[STACK_TOTSIZE];
// 指向当前进程pcb的指针
PROCESS *p_proc_ready;
// pcb表
PROCESS proc_table[PCB_SIZE];
#ifdef HAPPY_SNAKE
void (*entry[]) = {
startGame
};
char pcb_name[][16] = {
"SNAKE"
};
#else
void (*entry[]) = {
TestA,
TestB,
TestC,
};
char pcb_name[][16] = {
"TestA",
"TestB",
"TestC",
};
#endif
/*
* main函数
*
*/
void kernel_main()
void kernel_main(void)
{
kprintf("---start kernel main---\n");
PROCESS *p_proc = proc_table;
char *p_stack = process_stack;
for (int i = 0 ; i < PCB_SIZE ; i++, p_proc++) {
strcpy(p_proc->p_name, pcb_name[i]);
p_proc->regs.cs = (SELECTOR_FLAT_C & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.ds = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.es = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.fs = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.ss = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.gs = (SELECTOR_VIDEO & SA_RPL_MASK & SA_TI_MASK)
| RPL_USER;
p_proc->regs.eip = (u32)entry[i];
p_stack += STACK_PREPROCESS;
p_proc->regs.esp = (u32)p_stack;
p_proc->regs.eflags = 0x1202; /* IF=1, IOPL=1 */
}
phy_init_4k(); // init phy_malloc/free_4k management
p_proc_ready = proc_table;
change_8253Counter0(1000);
enable_irq(CLOCK_IRQ);
enable_irq(KEYBOARD_IRQ);
restart();
PROCESS_0 *p_proc = &p_proc_ready->pcb;
p_proc->statu = READY;
// kern regs
p_proc->kern_regs.esp = (u32)(p_proc_ready + 1) - 8;
// 保证切换内核栈后执行流进入的是restart函数。
*(u32 *)(p_proc->kern_regs.esp + 0) = (u32)restart;
// 这里是因为restart要用`pop esp`确认esp该往哪里跳。
*(u32 *)(p_proc->kern_regs.esp + 4) = (u32)p_proc;
// 初始化进程树
p_proc->fork_tree.p_fa = NULL;
p_proc->fork_tree.sons = NULL;
// 初始化ticks
p_proc->priority = p_proc->ticks = 1;
// 在实现了exec系统调用后main函数的负担就少了
// 只需要调用一个函数接口就能实现进程的加载
if (do_exec("initproc.bin") < 0)
panic("init process failed");
// 切换tss
tss.esp0 = (u32)(&p_proc->user_regs + 1);
lcr3(p_proc->cr3);
init_kernel = true;
// 开个无用的kern_context存当前执行流的寄存器上下文之后就没用了直接放在临时变量中
struct kern_context idle;
switch_kern_context(&idle, &p_proc->kern_regs);
assert(0);
}

154
kern/pmap.c Normal file
View File

@ -0,0 +1,154 @@
#include <assert.h>
#include <elf.h>
#include <string.h>
#include <mmu.h>
#include <kern/kmalloc.h>
#include <kern/pmap.h>
/*
* page_list页面信息
*
*/
static phyaddr_t
alloc_phy_page(struct page_node **page_list)
{
phyaddr_t paddr = phy_malloc_4k();
struct page_node *new_node = kmalloc(sizeof(struct page_node));
new_node->nxt = *page_list;
new_node->paddr = paddr;
new_node->laddr = -1;
*page_list = new_node;
return paddr;
}
/*
* MINIOS中比较通用的页表映射函数
* laddr处的虚拟页面映射到物理地址为paddrpaddr为-1
* pte_flag置位到页表项PTE_P | PTE_W | PTE_U
* page_list这个链表中
*/
static void
lin_mapping_phy(u32 cr3,
struct page_node **page_list,
uintptr_t laddr,
phyaddr_t paddr,
u32 pte_flag)
{
assert(PGOFF(laddr) == 0);
uintptr_t *pde_ptr = (uintptr_t *)K_PHY2LIN(cr3);
if ((pde_ptr[PDX(laddr)] & PTE_P) == 0) {
phyaddr_t pte_phy = alloc_phy_page(page_list);
memset((void *)K_PHY2LIN(pte_phy), 0, PGSIZE);
pde_ptr[PDX(laddr)] = pte_phy | PTE_P | PTE_W | PTE_U;
}
phyaddr_t pte_phy = PTE_ADDR(pde_ptr[PDX(laddr)]);
uintptr_t *pte_ptr = (uintptr_t *)K_PHY2LIN(pte_phy);
phyaddr_t page_phy;
if (paddr == (phyaddr_t)-1) {
if ((pte_ptr[PTX(laddr)] & PTE_P) != 0)
return;
page_phy = alloc_phy_page(page_list);
(*page_list)->laddr = laddr;
} else {
if ((pte_ptr[PTX(laddr)] & PTE_P) != 0)
warn("this page was mapped before, laddr: %x", laddr);
assert(PGOFF(paddr) == 0);
page_phy = paddr;
}
pte_ptr[PTX(laddr)] = page_phy | pte_flag;
}
/*
*
* 3GB ~ 3GB + 128MB的线性地址映射到0 ~ 128MB的物理地址
*/
void
map_kern(u32 cr3, struct page_node **page_list)
{
for (phyaddr_t paddr = 0 ; paddr < 128 * MB ; paddr += PGSIZE) {
lin_mapping_phy(cr3,
page_list,
K_PHY2LIN(paddr),
paddr,
PTE_P | PTE_W | PTE_U);
}
}
/*
* elf文件信息将数据搬迁到指定位置
* eip的置位
*
*/
void
map_elf(PROCESS_0 *p_proc, void *elf_addr)
{
assert(p_proc->lock != 0);
struct Elf *eh = (struct Elf *)elf_addr;
struct Proghdr *ph = (struct Proghdr *)(elf_addr + eh->e_phoff);
for (int i = 0 ; i < eh->e_phnum ; i++, ph++) {
if (ph->p_type != PT_LOAD)
continue;
uintptr_t st = ROUNDDOWN(ph->p_va, PGSIZE);
uintptr_t en = ROUNDUP(st + ph->p_memsz, PGSIZE);
for (uintptr_t laddr = st ; laddr < en ; laddr += PGSIZE) {
u32 pte_flag = PTE_P | PTE_U;
if ((ph->p_flags & ELF_PROG_FLAG_WRITE) != 0)
pte_flag |= PTE_W;
lin_mapping_phy(p_proc->cr3,
&p_proc->page_list,
laddr,
(phyaddr_t)-1,
pte_flag);
}
memcpy( (void *)ph->p_va,
(const void *)eh + ph->p_offset,
ph->p_filesz);
memset( (void *)ph->p_va + ph->p_filesz,
0,
ph->p_memsz - ph->p_filesz);
}
p_proc->user_regs.eip = eh->e_entry;
}
/*
* 访
* (0xbffff000~0xc0000000)
* esp寄存器放置好
*/
void
map_stack(PROCESS_0 *p_proc)
{
assert(p_proc->lock != 0);
lin_mapping_phy(p_proc->cr3,
&p_proc->page_list,
K_PHY2LIN(-PGSIZE),
(phyaddr_t)-1,
PTE_P | PTE_W | PTE_U);
p_proc->user_regs.esp = K_PHY2LIN(0);
}
/*
* page_list回收所有的页面
*/
void
recycle_pages(struct page_node *page_list)
{
for (struct page_node *prevp, *p = page_list ; p ;) {
phy_free_4k(p->paddr);
prevp = p, p = p->nxt;
kfree(prevp);
}
}

47
kern/process.c Normal file
View File

@ -0,0 +1,47 @@
#include <x86.h>
#include <kern/process.h>
#include <kern/sche.h>
#include <kern/syscall.h>
/*
*
* save函数保存完用户寄存器上下文后
* minios的进程栈是与pcb是绑定的
* pcb
* 使8KB7KB多
*
* C语言这知道
* C语言里面写一个内联汇编实现便
*/
void
to_kern_stack(u32 ret_esp)
{
asm volatile (
"add %1, %0\n\t"
"mov %0, %%esp\n\t"
"push %2\n\t"
"jmp *4(%%ebp)\n\t":
: "a"((u32)p_proc_ready->_pad)
, "b"((u32)sizeof(p_proc_ready->_pad))
, "c"(ret_esp)
);
}
u32
kern_get_pid(PROCESS_0 *p_proc)
{
u32 ret = 0;
while (xchg(&p_proc->lock, 1) == 1)
schedule();
ret = p_proc->pid;
free:
xchg(&p_proc->lock, 0);
return ret;
}
ssize_t
do_get_pid(void)
{
return (ssize_t)kern_get_pid(&p_proc_ready->pcb);
}

48
kern/sche.c Normal file
View File

@ -0,0 +1,48 @@
#include <x86.h>
#include <kern/process.h>
#include <kern/protect.h>
#include <kern/trap.h>
/*
*
*/
void
schedule(void)
{
// 如果中断没关,一定!一定!一定!要关中断保证当前执行流操作的原子性
DISABLE_INT();
PROCESS *p_cur_proc = p_proc_ready;
PROCESS *p_next_proc = p_proc_ready;
asm volatile ("":::"memory");
static bool in_sche;
if (in_sche)
goto free;
in_sche = true;
do {
if (++p_next_proc >= proc_table + PCB_SIZE)
p_next_proc = proc_table;
if (p_cur_proc == p_next_proc &&
p_next_proc->pcb.statu != READY) {
asm volatile("sti\n\thlt\n\tcli":::"memory");
}
} while (p_next_proc->pcb.statu != READY);
p_proc_ready = p_next_proc;
// 切换进程页表和tss
lcr3(p_proc_ready->pcb.cr3);
tss.esp0 = (u32)(&p_proc_ready->pcb.user_regs + 1);
in_sche = false;
switch_kern_context(
&p_cur_proc->pcb.kern_regs,
&p_next_proc->pcb.kern_regs);
asm volatile ("":::"memory");
free:
ENABLE_INT();
}

View File

@ -1,17 +1,17 @@
#include "type.h"
#include "protect.h"
#include "stdio.h"
#include "assert.h"
#include "trap.h"
#include "x86.h"
#include <assert.h>
#include <mmu.h>
#include <x86.h>
#include <kern/protect.h>
#include <kern/stdio.h>
#include <kern/trap.h>
struct Pseudodesc gdt_ptr, idt_ptr;
DESCRIPTOR gdt[GDT_SIZE];
DESCRIPTOR ldt[LDT_SIZE];
GATE idt[IDT_SIZE];
TSS tss;
TSS tss;
/*
* 使CPU核休眠
@ -55,7 +55,7 @@ _warn(const char *file, int line, const char *fmt,...)
* tss和ldt的两个的全局描述符
*/
static void
init_gdt()
init_gdt(void)
{
init_segment(&gdt[0], 0, 0, 0);
// 代码段(cs)
@ -63,7 +63,8 @@ init_gdt()
// 数据段(ds,es,fs,ss)
init_segment(&gdt[2], 0, 0xfffff, DA_DRW | DA_32 | DA_LIMIT_4K);
// 显存段(gs)
init_segment(&gdt[3], 0xb8000, 0xffff, DA_DRW | DA_DPL3);
init_segment(&gdt[3], K_PHY2LIN(0xb8000), 0x8, DA_DRW | DA_DPL3 |
DA_32 | DA_LIMIT_4K);
tss.ss0 = SELECTOR_FLAT_RW;
tss.iobase = sizeof(tss); /* 没有I/O许可位图 */
// tss段
@ -79,7 +80,7 @@ init_gdt()
* 使ldt
*/
static void
init_ldt()
init_ldt(void)
{
init_segment(&ldt[0], 0, 0, 0);
// 代码段(cs)
@ -95,7 +96,7 @@ init_ldt()
* idt表
*/
static void
init_idt()
init_idt(void)
{
// 异常处理
init_gate(idt + INT_VECTOR_DIVIDE, DA_386IGate,
@ -163,6 +164,9 @@ init_idt()
hwint14, PRIVILEGE_KRNL);
init_gate(idt + INT_VECTOR_IRQ8 + 7, DA_386IGate,
hwint15, PRIVILEGE_KRNL);
// 系统调用
init_gate(idt + INT_VECTOR_SYSCALL, DA_386IGate,
int_syscall, PRIVILEGE_USER);
idt_ptr.pd_base = (u32)idt;
idt_ptr.pd_lim = sizeof(idt) - 1;
@ -172,7 +176,7 @@ init_idt()
* 8259A
*/
static void
init_8259A()
init_8259A(void)
{
outb(INT_M_CTL, 0x11); // Master 8259, ICW1.
outb(INT_S_CTL, 0x11); // Slave 8259, ICW1.
@ -193,10 +197,23 @@ init_8259A()
outb(INT_S_CTLMASK, 0xFF); // Slave 8259, OCW1.
}
static void
init_irq(void)
{
// 开启时钟中断
enable_irq(CLOCK_IRQ);
// 调整时钟频率至1000Hz
outb(0x43, 0x34);
outb(0x40, 1193&0xff);
outb(0x40, 1193>>8);
// 开启键盘中断
enable_irq(KEYBOARD_IRQ);
}
/*
*
*/
void cstart()
void cstart(void)
{
// 清屏并将光标置为开头
// ANSI转义序列由\x1b(ascii码27为Esc)开头的序列
@ -214,5 +231,7 @@ void cstart()
init_8259A();
kprintf("idt ");
init_idt();
kprintf("irq");
init_irq();
kprintf("---\n");
}

147
kern/syscall.c Normal file
View File

@ -0,0 +1,147 @@
#include <assert.h>
#include <kern/fs.h>
#include <kern/process.h>
#include <kern/syscall.h>
#include <kern/time.h>
static ssize_t sys_get_ticks(void);
static ssize_t sys_get_pid(void);
static ssize_t sys_read(void);
static ssize_t sys_write(void);
static ssize_t sys_exec(void);
static ssize_t sys_fork(void);
static ssize_t sys_wait(void);
static ssize_t sys_exit(void);
static ssize_t sys_fork_ack(void);
ssize_t (*syscall_table[])(void) = {
[_NR_get_ticks] sys_get_ticks,
[_NR_get_pid] sys_get_pid,
[_NR_read] sys_read,
[_NR_write] sys_write,
[_NR_exec] sys_exec,
[_NR_fork] sys_fork,
[_NR_wait] sys_wait,
[_NR_exit] sys_exit,
[_NR_fork_ack] sys_fork_ack,
};
/*
*
* 使6
* 0 1 2 3 4 5
* ebx ecx edx esi edi ebp
*/
static u32
get_arg(int order)
{
switch (order) {
case 0:
return p_proc_ready->pcb.user_regs.ebx;
case 1:
return p_proc_ready->pcb.user_regs.ecx;
case 2:
return p_proc_ready->pcb.user_regs.edx;
case 3:
return p_proc_ready->pcb.user_regs.esi;
case 4:
return p_proc_ready->pcb.user_regs.edi;
case 5:
return p_proc_ready->pcb.user_regs.ebp;
default:
panic("invalid order! order: %d", order);
}
}
/*
* ssize_t get_ticks(void)
*
*/
static ssize_t
sys_get_ticks(void)
{
return do_get_ticks();
}
/*
* int get_pid(void)
*
*/
static ssize_t
sys_get_pid(void)
{
return do_get_pid();
}
/*
* ssize_t read(int fd, void *buf, size_t count)
*
* buf为开头的长度为count字节的缓冲区
*/
static ssize_t
sys_read(void)
{
return do_read(get_arg(0), (void *)get_arg(1), get_arg(2));
}
/*
* ssize_t write(int fd, const void *buf, size_t count)
*
* buf为开头的长度为count字节的缓冲区
*/
static ssize_t
sys_write(void)
{
return do_write(get_arg(0), (const void *)get_arg(1), get_arg(2));
}
/*
* ssize_t exec(const char *pathname)
* pathname对应的ELF文件
*/
static ssize_t
sys_exec(void)
{
return do_exec((const void *)get_arg(0));
}
/*
* ssize_t fork(void)
*
*/
static ssize_t
sys_fork(void)
{
return do_fork();
}
/*
* ssize_t wait(int *wstatus)
* exitwstatus接收子进程的返回码
*/
static ssize_t
sys_wait(void)
{
return do_wait((int *)get_arg(0));
}
/*
* ssize_t exit(int status)
* 退status作为返回码传递给父进程
*/
static ssize_t
sys_exit(void)
{
return do_exit(get_arg(0));
}
/*
* ssize_t fork_ack(void)
* forkbomb.c所用fork的次数
*
*/
static ssize_t
sys_fork_ack(void)
{
return do_fork_ack();
}

View File

@ -1,6 +1,7 @@
#include "type.h"
#include "time.h"
#include "x86.h"
#include <type.h>
#include <kern/syscall.h>
#include <kern/time.h>
static size_t timecounter;
@ -8,7 +9,7 @@ static size_t timecounter;
*
*/
void
timecounter_inc()
timecounter_inc(void)
{
timecounter++;
}
@ -17,20 +18,13 @@ timecounter_inc()
*
*/
size_t
clock()
kern_get_ticks(void)
{
return timecounter;
}
void
change_8253Counter0(int hertz) {
outb(TIMER_MODEREG, TIMER_MODE);
outb(TIMER0, (u8)(TIMER_FREQ / hertz));
outb(TIMER0, (u8)((TIMER_FREQ / hertz) >> 8));
/*
8253's counter is 2bytes long
port 0x43 be the Mode Control Register
Orange book writes 00_11_010_0 into this reg, which means
SelectCounter0 | RW low byte first and then high byte | rate generator mode | binary counter
*/
ssize_t
do_get_ticks(void)
{
return (ssize_t)kern_get_ticks();
}

View File

@ -1,11 +1,15 @@
#include "assert.h"
#include "process.h"
#include "stdio.h"
#include "time.h"
#include "trap.h"
#include "x86.h"
#include "keymap.h"
#include "keyboard.h"
#include <assert.h>
#include <x86.h>
#include <kern/keyboard.h>
#include <kern/keymap.h>
#include <kern/process.h>
#include <kern/protect.h>
#include <kern/sche.h>
#include <kern/stdio.h>
#include <kern/syscall.h>
#include <kern/time.h>
#include <kern/trap.h>
/*
*
@ -14,7 +18,7 @@ int k_reenter;
void (*irq_table[16])(int) = {
clock_interrupt_handler,
keyboard_interrupt_handler,
kb_interrupt_handler,
default_interrupt_handler,
default_interrupt_handler,
default_interrupt_handler,
@ -73,59 +77,110 @@ default_interrupt_handler(int irq)
void
exception_handler(int vec_no, int err_code, int eip, int cs, int eflags)
{
char err_description[][64] = { "#DE Divide Error",
"#DB RESERVED",
"— NMI Interrupt",
"#BP Breakpoint",
"#OF Overflow",
"#BR BOUND Range Exceeded",
"#UD Invalid Opcode (Undefined Opcode)",
"#NM Device Not Available (No Math Coprocessor)",
"#DF Double Fault",
" Coprocessor Segment Overrun (reserved)",
"#TS Invalid TSS",
"#NP Segment Not Present",
"#SS Stack-Segment Fault",
"#GP General Protection",
"#PF Page Fault",
"— (Intel reserved. Do not use.)",
"#MF x87 FPU Floating-Point Error (Math Fault)",
"#AC Alignment Check",
"#MC Machine Check",
"#XF SIMD Floating-Point Exception"
};
panic("\x1b[H\x1b[2JException! --> %s\nEFLAGS: %x CS: %x EIP: %x\nError code: %x",
err_description[vec_no], eflags, cs, eip, err_code);
static char err_description[][64] = {
"#DE Divide Error",
"#DB RESERVED",
"— NMI Interrupt",
"#BP Breakpoint",
"#OF Overflow",
"#BR BOUND Range Exceeded",
"#UD Invalid Opcode (Undefined Opcode)",
"#NM Device Not Available (No Math Coprocessor)",
"#DF Double Fault",
" Coprocessor Segment Overrun (reserved)",
"#TS Invalid TSS",
"#NP Segment Not Present",
"#SS Stack-Segment Fault",
"#GP General Protection",
"#PF Page Fault",
"— (Intel reserved. Do not use.)",
"#MF x87 FPU Floating-Point Error (Math Fault)",
"#AC Alignment Check",
"#MC Machine Check",
"#XF SIMD Floating-Point Exception"
};
#define PANIC_STR_SIZE 256
static char fmtstr[PANIC_STR_SIZE];
if (vec_no == INT_VECTOR_PAGE_FAULT) {
char *p_str = fmtstr;
p_str += snprintf(
p_str, PANIC_STR_SIZE - (p_str - fmtstr) - 1,
"\x1b[H\x1b[2JOh, you receive a page fault!\n");
p_str += snprintf(
p_str, PANIC_STR_SIZE - (p_str - fmtstr) - 1,
"CS: %%x EIP: %%x You tried to access the address: %%x\n");
if ((err_code & FEC_PR) == 0) {
p_str += snprintf(
p_str, PANIC_STR_SIZE - (p_str - fmtstr) - 1,
"You tried to access a nonexistent page!\n");
}
if ((err_code & FEC_WR) != 0) {
p_str += snprintf(
p_str, PANIC_STR_SIZE - (p_str - fmtstr) - 1,
"You tried to write in this page!\n");
} else {
p_str += snprintf(
p_str, PANIC_STR_SIZE - (p_str - fmtstr) - 1,
"You tried to read in this page!\n");
}
if ((err_code & FEC_U) != 0) {
p_str += snprintf(
p_str, PANIC_STR_SIZE - (p_str - fmtstr) - 1,
"You tried to access a page in user mode!\n");
}
panic(fmtstr, cs, eip, rcr2());
} else {
snprintf(fmtstr, PANIC_STR_SIZE - 1,
"\x1b[H\x1b[2JException! --> "
"%%s\nEFLAGS: %%x CS: %%x EIP: %%x\nError code: %%x");
panic(fmtstr, err_description[vec_no],
eflags, cs, eip, err_code);
}
}
/*
*
*
*
* !!!
* !!!
* !!!
*
*/
void
clock_interrupt_handler(int irq)
{
static unsigned int _sched_count = 0;
// kprintf("i%d", clock());
timecounter_inc();
_sched_count ++;
if (p_proc_ready == proc_table) {
_sched_count ++;
if (_sched_count >= 20) {
p_proc_ready ++;
_sched_count = 0;
}
} else {
p_proc_ready ++;
}
if (p_proc_ready >= proc_table + PCB_SIZE) {
p_proc_ready = proc_table;
// 当一个函数的时间片全用完时
if (--p_proc_ready->pcb.ticks == 0) {
p_proc_ready->pcb.ticks = p_proc_ready->pcb.priority;
schedule();
}
}
void keyboard_interrupt_handler(int irq) {
// kprintf("K");
u8 scode = inb(KEYBOARD_BUF_REG);
if (scode < sizeof(keymap) && keymap[scode] >= 'a' && keymap[scode] <= 'z') {
add_keyboard_buf(keymap[scode]); //only keep a-z MAKE CODE
// kprintf("%c", keymap[scode]);
}
/*
*
*/
void
kb_interrupt_handler(int irq)
{
u32 ch = (u32)inb(0x60);
if ((ch & 0x80) != 0)
return;
ch = keymap[ch];
if (0x20 <= ch && ch <= 0x7e)
add_keyboard_buf(ch);
if (ch == ENTER)
add_keyboard_buf('\n');
}
/*
*
*/
void
syscall_handler(void)
{
int syscall_id = p_proc_ready->pcb.user_regs.eax;
ssize_t ret = (*syscall_table[syscall_id])();
p_proc_ready->pcb.user_regs.eax = ret;
}

101
kern/wait.c Normal file
View File

@ -0,0 +1,101 @@
#include <assert.h>
#include <mmu.h>
#include <errno.h>
#include <x86.h>
#include <kern/syscall.h>
#include <kern/wait.h>
#include <kern/process.h>
#include <kern/pmap.h>
#include <kern/sche.h>
#include <kern/kmalloc.h>
/*
The wait() system call suspends execution of the calling thread
until one of its children terminates.
RETURN VALUE
on success, returns the process ID of the terminated child;
on failure, -1 is returned.
! however the testwait.bin expects a valued return other than simply -1
*/
ssize_t
kern_wait(int *wstatus)
{
again:
while (xchg(&p_proc_ready->pcb.lock, 1) == 1)
schedule();
PROCESS_0 *p_fa = &p_proc_ready->pcb;
int child_pid = -1;
struct son_node* p_son = p_fa->fork_tree.sons;
if (p_son == NULL) {
xchg(&p_fa->lock, 0);
return -ECHILD;
}
assert(p_son->pre == NULL); // make sure it is the head of the list
/*
struct s_proc {
user_regs; // let it be
kern_regs; // let it be
lock; // get and release before kfree
statu; // set to IDLE
pid; // keep it util return
cr3; // let it be
page_list; // recycle it
exit_code; // set to wstatus
priority; // let it be
ticks; // let it be
fork_tree; // already cleared
};
*/
while (p_son != NULL) {
if (p_son->p_son->statu == ZOMBIE) {
while (xchg(&p_son->p_son->lock, 1) == 1)
schedule();
// just use this value, which is '(status & 0xFF) << 8' in exit.c::do_exit
if (wstatus != NULL)
*wstatus = p_son->p_son->exit_code;
// recycle_pages in pmap.c
recycle_pages(p_son->p_son->page_list);
// remove p_son from p_fa's son list
if (p_son->pre != NULL) {
p_son->pre->nxt = p_son->nxt;
}
else {
p_fa->fork_tree.sons = p_son->nxt;
}
if (p_son->nxt != NULL) {
p_son->nxt->pre = p_son->pre;
}
// keep p_son's pid for retval, 'cause the pointer will get freed before return
child_pid = p_son->p_son->pid;
kfree(p_son);
// free it in proc table, 'cause in fork I judge free by statu
xchg(&proc_table[child_pid].pcb.lock, 0);
proc_table[child_pid].pcb.statu = IDLE; // ! set status be the last state, or die
goto done;
}
p_son = p_son->nxt;
}
xchg(&p_fa->lock, 0);
p_fa->statu = SLEEP; //! sleep after release lock, or it will deadlock
schedule();
goto again;
// 接下来就是你自己的实现了,我们在设计的时候这段代码不会有太大问题
// 在实现完后你任然要对自己来个灵魂拷问
// 1. 上锁上了吗?所有临界情况都考虑到了吗?(永远要相信有各种奇奇怪怪的并发问题)
// 2. 所有错误情况都判断到了吗错误情况怎么处理RTFM->`man 2 wait`
// 3. 是否所有的资源都正确回收了?
// 4. 你写的代码真的符合wait语义吗
done:
xchg(&p_fa->lock, 0);
// panic("Unimplement! soul torture");
return child_pid;
}
ssize_t
do_wait(int *wstatus)
{
assert((uintptr_t)wstatus < KERNBASE);
assert((uintptr_t)wstatus + sizeof(wstatus) < KERNBASE);
return kern_wait(wstatus);
}

BIN
lab6-finish.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

31
lib/Makefrag Normal file
View File

@ -0,0 +1,31 @@
OBJDIRS += lib
$(OBJDIR)/lib/%.o: lib/%.c $(OBJDIR)/.vars.CFLAGS
@echo + cc $<
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/lib/kern/%.o: lib/kern/%.c $(OBJDIR)/.vars.CFLAGS
@echo + cc $<
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/lib/user/%.o: lib/user/%.c $(OBJDIR)/.vars.CFLAGS
@echo + cc $<
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/lib/%.o: lib/%.asm
@echo + as obj $<
@mkdir -p $(@D)
@$(AS) -f elf -o $@ $<
$(OBJDIR)/lib/kern/%.o: lib/kern/%.asm
@echo + as obj $<
@mkdir -p $(@D)
@$(AS) -f elf -o $@ $<
$(OBJDIR)/lib/user/%.o: lib/user/%.asm
@echo + as obj $<
@mkdir -p $(@D)
@$(AS) -f elf -o $@ $<

BIN
lib/kern/fork_ack.o Normal file

Binary file not shown.

71
lib/kern/keyboard.c Normal file
View File

@ -0,0 +1,71 @@
#include <type.h>
#include <x86.h>
#include <kern/keymap.h>
#include <kern/trap.h>
#include <kern/sche.h>
#define KB_INBUF_SIZE 1024
typedef struct kb_inbuf {
u32 lock;
u8* p_head;
u8* p_tail;
int count;
u8 buf[KB_INBUF_SIZE];
} KB_INPUT;
static KB_INPUT kb_input = {
.p_head = kb_input.buf,
.p_tail = kb_input.buf,
};
/*
* ch这个字符放进内核的字符缓冲区
*/
void
add_keyboard_buf(u8 ch)
{
// 上个自旋锁,保证线程安全(不建议用中断)计组课上讲过,相信大家的记忆力
while (xchg(&kb_input.lock, 1) == 1)
schedule();
if (kb_input.count == KB_INBUF_SIZE)
goto free;
*kb_input.p_tail++ = ch;
if (kb_input.p_tail == kb_input.buf + KB_INBUF_SIZE)
kb_input.p_tail = kb_input.buf;
kb_input.count++;
free:
xchg(&kb_input.lock, 0);
}
/*
* -1
*
*/
u8
read_keyboard_buf(void)
{
u8 res;
// 上个自旋锁,保证线程安全(不建议用中断)计组课上讲过,相信大家的记忆力
while (xchg(&kb_input.lock, 1) == 1)
schedule();
if (kb_input.count == 0) {
res = -1;
goto free;
}
res = *kb_input.p_head++;
if (kb_input.p_head == kb_input.buf + KB_INBUF_SIZE)
kb_input.p_head = kb_input.buf;
kb_input.count--;
free:
xchg(&kb_input.lock, 0);
return res;
}

159
lib/kern/kmalloc.c Normal file
View File

@ -0,0 +1,159 @@
#include <assert.h>
#include <mmu.h>
#include <x86.h>
#include <kern/stdio.h>
#include <kern/kmalloc.h>
#include <kern/sche.h>
#include <kern/trap.h>
static u32 phy_malloc_4k_lock;
static phyaddr_t phy_malloc_4k_p = 96 * MB;
// 96MB~128MB = 0x0600_0000 ~ 0x0800_0000
// 32MB = 32 * 1024 / 4 = 8192 PAGES
#define PHY_START (96 * MB)
#define PHY_END (128 * MB)
#define PHY_PAGENUM ((PHY_END - PHY_START) / PGSIZE)
// map them to 8192 slots
#define PHY_INDEX(paddr) ((paddr - PHY_START) >> 12)
#define PHY_PADDR(idx) ((idx << 12) + PHY_START)
// we have a head index point to the lowest free slot
// if slot[i] is free, slot[i] = the index of the next free slot
// else, slot[i] = -1
// when initialize, slots are initialized to {1, 2, 3, ...}
// when alloc, head mov to slot[i], then set slot[i] = 0
// when free, set slot[i]=head, head = i
static int phy_free_head;
static int phy_free_table[PHY_PAGENUM];
void phy_init_4k() {
for (int i = 0; i < PHY_PAGENUM - 1; ++ i) {
phy_free_table[i] = i + 1;
}
phy_free_table[PHY_PAGENUM-1] = -1;
phy_free_head = 0;
}
/*
* free_4k的代码
*
*
*/
void
phy_free_4k(phyaddr_t paddr)
{
assert(paddr % PGSIZE == 0);
assert(96 * MB <= paddr && paddr < 128 * MB);
while(xchg(&phy_malloc_4k_lock, 1) == 1)
schedule();
phy_free_table[PHY_INDEX(paddr)] = phy_free_head;
phy_free_head = PHY_INDEX(paddr);
xchg(&phy_malloc_4k_lock, 0);
}
/*
* 4kb
* 96MB~128MB
*/
phyaddr_t
phy_malloc_4k(void)
{
while(xchg(&phy_malloc_4k_lock, 1) == 1)
schedule();
assert(0 <= phy_free_head && phy_free_head < PHY_PAGENUM);
phyaddr_t paddr = PHY_PADDR(phy_free_head);
phy_free_head = phy_free_table[phy_free_head];
phy_free_table[PHY_INDEX(paddr)] = -1;
// assert(phy_malloc_4k_p < 128 * MB);
// phyaddr_t paddr = phy_malloc_4k_p;
// phy_malloc_4k_p += PGSIZE;
free:
xchg(&phy_malloc_4k_lock, 0);
return paddr;
}
struct kmalloc_header {
struct kmalloc_header *ptr;
size_t size;
};
static struct kmalloc_header *freePtr;
static u32 malloc_lock;
/*
* kmalloc申请的内存
*/
void
kfree(void *v)
{
struct kmalloc_header *bp, *p;
bp = (struct kmalloc_header*)v - 1;
while(xchg(&malloc_lock, 1) == 1)
schedule();
for (p = freePtr; !(bp > p && bp < p->ptr); p = p->ptr) {
if (p >= p->ptr && (bp > p || bp < p->ptr)) {
break;
}
}
if (bp + bp->size == p->ptr) {
bp->size += p->ptr->size;
bp->ptr = p->ptr->ptr;
} else {
bp->ptr = p->ptr;
}
if (p + p->size == bp) {
p->size += bp->size;
p->ptr = bp->ptr;
} else {
p->ptr = bp;
}
freePtr = p;
free:
xchg(&malloc_lock, 0);
}
/*
* n字节
* 64MB~96MB
*/
void *
kmalloc(size_t n)
{
struct kmalloc_header *p, *prevP;
size_t nUnits;
void *ret;
while(xchg(&malloc_lock, 1) == 1)
schedule();
nUnits = (n + sizeof(struct kmalloc_header) - 1)
/ sizeof(struct kmalloc_header) + 1;
if ((prevP = freePtr) == 0) {
freePtr = prevP = (void *)K_PHY2LIN(64 * MB);
freePtr->ptr = freePtr;
freePtr->size = (32 * MB) / sizeof(struct kmalloc_header);
}
for (p = prevP->ptr ; ; prevP = p, p = p->ptr) {
if (p->size >= nUnits) {
if (p->size == nUnits) {
prevP->ptr = p->ptr;
} else {
p->size -= nUnits;
p += p->size;
p->size = nUnits;
}
freePtr = prevP;
ret = (void*)(p + 1);
goto free;
}
if (p == freePtr) {
panic("malloc failed!");
}
}
free:
xchg(&malloc_lock, 0);
return ret;
}

View File

@ -1,10 +1,13 @@
#include "assert.h"
#include "stdio.h"
#include "string.h"
#include "stdarg.h"
#include "terminal.h"
#include "type.h"
#include "x86.h"
#include <assert.h>
#include <mmu.h>
#include <string.h>
#include <stdarg.h>
#include <type.h>
#include <x86.h>
#include <kern/stdio.h>
#include <kern/trap.h>
#include <terminal.h>
inline static void
write_to_terminal(u16 disp_pos, u16 content)
@ -353,11 +356,13 @@ kprintfputch(int ch, struct kprintfbuf *b)
int
vkprintf(const char *fmt, va_list ap)
{
TTY.cnt = 0;
int rc;
vprintfmt((void*)kprintfputch, &TTY, fmt, ap);
int rc = TTY.cnt;
DISABLE_INT();
TTY.cnt = 0;
vprintfmt((void*)kprintfputch, &TTY, fmt, ap);
rc = TTY.cnt;
ENABLE_INT();
return rc;
}

Binary file not shown.

View File

@ -1,7 +1,7 @@
#include "type.h"
#include "stdio.h"
#include "string.h"
#include "stdarg.h"
#include <type.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
/*
* xv6中抄过来的printf

40
lib/user/assert.c Normal file
View File

@ -0,0 +1,40 @@
#include <assert.h>
#include <user/stdio.h>
/*
* 使
*/
void
_panic(const char *file, int line, const char *fmt,...)
{
va_list ap;
va_start(ap, fmt);
printf("\x1b[0m\x1b[91muser panic at %s:%d: ", file, line);
vprintf(fmt, ap);
printf("\n\x1b[0m");
va_end(ap);
fflush();
// 休眠CPU核直接罢工
while(1)
/* do nothing */;
}
/*
* panic
*/
void
_warn(const char *file, int line, const char *fmt,...)
{
va_list ap;
va_start(ap, fmt);
printf("\x1b[0m\x1b[93muser warning at %s:%d: ", file, line);
vprintf(fmt, ap);
printf("\n\x1b[0m");
va_end(ap);
fflush();
}

10
lib/user/astart.asm Normal file
View File

@ -0,0 +1,10 @@
[section .text]
extern main
extern exit
global _start
_start: ; 第一条指令
call main
push eax
call exit

BIN
lib/user/firework.bin Normal file

Binary file not shown.

115
lib/user/stdio.c Normal file
View File

@ -0,0 +1,115 @@
#include <x86.h>
#include <user/stdio.h>
#include <user/syscall.h>
#define PRINTFBUF_SIZE 4096
struct printfbuf {
u32 lock;
char buf[PRINTFBUF_SIZE];
char *buf_p;
int cnt;
};
static struct printfbuf printfb = {
.buf_p = printfb.buf
};
static void
printfputch(int ch, struct printfbuf *b)
{
b->cnt++;
*b->buf_p++ = (char)ch;
if (ch == '\n' || b->buf_p == b->buf + PRINTFBUF_SIZE) {
write(STDOUT, b->buf, b->buf_p - b->buf);
b->buf_p = b->buf;
}
}
int
vprintf(const char *fmt, va_list ap)
{
struct printfbuf *b = &printfb;
// 上个自旋锁,保证线程安全(不建议用中断)计组课上讲过,相信大家的记忆力
while (xchg(&b->lock, 1) == 1);
b->cnt = 0;
vprintfmt((void *)printfputch, b, fmt, ap);
int rc = b->cnt;
xchg(&b->lock, 0);
return rc;
}
int
printf(const char *fmt, ...)
{
va_list ap;
int rc;
va_start(ap, fmt);
rc = vprintf(fmt, ap);
va_end(ap);
return rc;
}
/*
* printf的缓冲区全写出去
*/
void
fflush(void)
{
struct printfbuf *b = &printfb;
while (xchg(&b->lock, 1) == 1);
write(STDOUT, b->buf, b->buf_p - b->buf);
b->buf_p = b->buf;
xchg(&b->lock, 0);
}
#define GETCHBUG_SIZE 1024
struct getchbuf {
u32 lock;
u8 buf[GETCHBUG_SIZE];
u8 *st, *en;
};
static struct getchbuf getchb = {
.st = getchb.buf,
.en = getchb.buf,
};
u8
getch(void)
{
struct getchbuf *b = &getchb;
// 上个自旋锁,保证线程安全(不建议用中断)计组课上讲过,相信大家的记忆力
while (xchg(&b->lock, 1) == 1);
if (b->st == b->en) {
b->st = b->en = b->buf;
b->en += read(STDIN, b->buf, sizeof(b->buf));
}
u8 rc = b->st == b->en ? -1 : *b->st++;
xchg(&b->lock, 0);
return rc;
}
u8
getchar(void)
{
u8 rc = -1;
while ((rc = getch()) == (u8)-1);
return rc;
}

134
lib/user/syscall.c Normal file
View File

@ -0,0 +1,134 @@
#include <user/syscall.h>
ssize_t
syscall0(size_t NR_syscall)
{
ssize_t ret;
asm volatile("int $0x80"
: "=a"(ret)
: "a" (NR_syscall)
: "cc", "memory");
return ret;
}
ssize_t
syscall1(size_t NR_syscall, size_t p1)
{
ssize_t ret;
asm volatile("int $0x80"
: "=a"(ret)
: "a" (NR_syscall),
"b" (p1)
: "cc", "memory");
return ret;
}
ssize_t
syscall2(size_t NR_syscall, size_t p1, size_t p2)
{
ssize_t ret;
asm volatile("int $0x80"
: "=a"(ret)
: "a" (NR_syscall),
"b" (p1),
"c" (p2)
: "cc", "memory");
return ret;
}
ssize_t
syscall3(size_t NR_syscall, size_t p1, size_t p2, size_t p3)
{
ssize_t ret;
asm volatile("int $0x80"
: "=a"(ret)
: "a" (NR_syscall),
"b" (p1),
"c" (p2),
"d" (p3)
: "cc", "memory");
return ret;
}
ssize_t
syscall4(size_t NR_syscall, size_t p1, size_t p2,
size_t p3, size_t p4)
{
ssize_t ret;
asm volatile("int $0x80"
: "=a"(ret)
: "a" (NR_syscall),
"b" (p1),
"c" (p2),
"d" (p3),
"S" (p4)
: "cc", "memory");
return ret;
}
ssize_t
syscall5(size_t NR_syscall, size_t p1, size_t p2,
size_t p3, size_t p4, size_t p5)
{
ssize_t ret;
asm volatile("int $0x80"
: "=a"(ret)
: "a" (NR_syscall),
"b" (p1),
"c" (p2),
"d" (p3),
"S" (p4),
"D" (p5)
: "cc", "memory");
return ret;
}
ssize_t
get_ticks(void)
{
return syscall0(_NR_get_ticks);
}
ssize_t
get_pid(void)
{
return syscall0(_NR_get_pid);
}
ssize_t
read(int fd, void *buf, size_t count)
{
return syscall3(_NR_read, fd, (size_t)buf, count);
}
ssize_t
write(int fd, const void *buf, size_t count)
{
return syscall3(_NR_write, fd, (size_t)buf, count);
}
ssize_t
exec(const char *pathname)
{
return syscall1(_NR_exec, (size_t)pathname);
}
ssize_t
fork(void)
{
return syscall0(_NR_fork);
}
ssize_t
exit(int status)
{
return syscall1(_NR_exit, status);
}
ssize_t
wait(int *wstatus)
{
return syscall1(_NR_wait, (size_t)wstatus);
}
ssize_t
fork_ack(void)
{
return syscall0(_NR_fork_ack);
}

36
user/Makefrag Normal file
View File

@ -0,0 +1,36 @@
OBJDIRS += user
USER_LIBSRCS:= lib/printfmt.c \
lib/string.c \
lib/user/assert.c \
lib/user/astart.asm \
lib/user/stdio.c \
lib/user/syscall.c \
USER_LIBOBJS := $(patsubst %.c, $(OBJDIR)/%.o, $(USER_LIBSRCS))
USER_LIBOBJS := $(patsubst %.asm, $(OBJDIR)/%.o, $(USER_LIBOBJS))
USER_SRCS := user/testfork.c \
user/testwait.c \
user/shell.c \
user/initproc.c \
user/forkbomb.c \
user/firework.bin \
USER_OBJS := $(patsubst %.c, $(OBJDIR)/%.o, $(USER_SRCS))
USER_BINS := $(patsubst %.bin, $(OBJDIR)/%.bin, $(USER_SRCS))
USER_BINS := $(patsubst %.c, $(OBJDIR)/%.bin, $(USER_BINS))
$(OBJDIR)/user/%.o: user/%.c $(OBJDIR)/.vars.CFLAGS
@echo + cc $<
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/user/firework.bin: lib/user/firework.bin
@mkdir -p $(@D)
@cp $< $(@D)
$(OBJDIR)/user/%.bin: $(OBJDIR)/user/%.o $(USER_LIBOBJS) $(OBJDIR)/.vars.LDFLAGS
@echo + ld $@
@$(LD) $(LDFLAGS) -o $@ $< $(USER_LIBOBJS) $(GCC_LIB)

47
user/forkbomb.c Normal file
View File

@ -0,0 +1,47 @@
#include <user/stdio.h>
#include <user/syscall.h>
static size_t random_seed;
static size_t rand() { return (((random_seed = random_seed * 214013L + 2531011L) >> 16) & 0x7fff);}
void bomb_salve(void)
{
fork_ack();
random_seed = get_ticks() & 0x7fff;
for (int i = 0 ; i < get_pid() ; i++)
rand();
for (int i = 0 ; i < (rand() & 0xf) ; i++) {
int pid = fork();
if (pid == 0)
bomb_salve();
for (int i = 0 ; i < rand() ; i++)
; // do nothing
}
if (rand() % 2 == 1) {
while (wait(NULL) >= 0);
}
exit(0);
}
void bomb_master(void)
{
random_seed = get_ticks() & 0x7fff;
while (1) {
int pid = fork();
if (pid == 0)
bomb_salve();
for (int i = 0 ; i < rand() ; i++)
; // do nothing
while (wait(NULL) >= 0);
}
}
int main()
{
bomb_master();
}

22
user/initproc.c Normal file
View File

@ -0,0 +1,22 @@
#include <assert.h>
#include <user/stdio.h>
#include <user/syscall.h>
int main()
{
ssize_t pid = fork();
if (pid == 0) {
int ret = exec("shell.bin");
panic("exec failed! errno: %d", ret);
}
if (pid < 0)
panic("init process fork failed errno: %d", pid);
int wstatus;
while (1) {
pid = wait(&wstatus);
}
}

52
user/shell.c Normal file
View File

@ -0,0 +1,52 @@
#include <assert.h>
#include <string.h>
#include <user/stdio.h>
#include <user/wait.h>
#include <user/syscall.h>
int main() {
int childStatus = 0;
while (1) {
printf("\x1b[32mMINIOS\x1b[0m$ ");
fflush();
int cntInput = 0;
char input[512] = {0};
while (1) {
char c = getchar();
printf("%c",c);
fflush();
if (c != '\n') {
input[cntInput++] = c;
} else {
input[cntInput++] = '\0';
break;
}
}
if (strcmp(input, "?") == 0) {
printf("%d\n",WEXITSTATUS(childStatus));
continue;
}
int ForkRetVal = fork();
if (ForkRetVal < 0) {
printf("fork failed!\n");
continue;
}
if (ForkRetVal == 0) {
exec(input);
printf("exec failed!\n");
exit(1);
}
int wait_pid;
wait_pid = wait(&childStatus);
if ((wait_pid = wait(&childStatus)) >= 0) {
// do nothing
}
}
return 0;
}

22
user/testfork.c Normal file
View File

@ -0,0 +1,22 @@
#include <user/stdio.h>
#include <user/syscall.h>
int main()
{
int pid = fork();
if (pid) {
while (1) {
printf("\x1b[93mI'm fa, son pid = %d\x1b[0m", pid);
fflush();
for (int i = 0 ; i < (int)1e8 ; i++)
;//do nothing
}
} else {
while (1) {
printf("I'm son");
fflush();
for (int i = 0 ; i < (int)1e8 ; i++)
;//do nothing
}
}
}

115
user/testwait.c Normal file
View File

@ -0,0 +1,115 @@
#include <assert.h>
#include <errno.h>
#include <user/stdio.h>
#include <user/syscall.h>
#include <user/wait.h>
void test_fork_wait1(void)
{
ssize_t pid = fork();
assert(pid >= 0);
if (pid == 0)
exit(114);
int wstatus;
assert(pid == wait(&wstatus));
assert(WEXITSTATUS(wstatus) == 114);
printf("\x1b[92mtest_fork_wait1 passed!\x1b[0m\n");
}
void test_fork_wait2(void)
{
ssize_t pid = fork();
assert(pid >= 0);
if (pid == 0)
exit(514);
assert(pid == wait(NULL));
printf("\x1b[92mtest_fork_wait2 passed!\x1b[0m\n");
}
void test_empty_wait(void)
{
assert(wait(NULL) == -ECHILD);
printf("\x1b[92mtest_empty_wait passed!\x1b[0m\n");
}
void test_fork_limit(void)
{
int xor_sum = 0;
for (int i = 1 ; i <= 17 ; i++) {
ssize_t pid = fork();
assert(pid >= 0);
if (pid == 0)
exit(0);
xor_sum ^= pid;
}
assert(fork() == -EAGAIN);
int wait_cnt = 0, wait_pid;
while ((wait_pid = wait(NULL)) >= 0)
wait_cnt++, xor_sum ^= wait_pid;
assert(wait_cnt == 17);
assert(xor_sum == 0);
printf("\x1b[92mtest_fork_limit passed!\x1b[0m\n");
}
void test_wait_is_sleeping(void)
{
ssize_t rt_pid = get_pid();
for (int i = 1 ; i <= 17 ; i++) {
ssize_t pid = fork();
assert(pid >= 0);
if (pid > 0) {
int wstatus;
assert(pid == wait(&wstatus));
assert(WEXITSTATUS(wstatus) == 42);
if (get_pid() != rt_pid)
exit(42);
break;
}
}
if (get_pid() != rt_pid) {
ssize_t la_ticks = get_ticks();
for (int i = 1 ; i <= (int)5e6 ; i++) {
ssize_t now_ticks = get_ticks();
assert(now_ticks - la_ticks <= 2);
la_ticks = now_ticks;
}
exit(42);
}
printf("\x1b[92mtest_wait_is_sleeping passed!\x1b[0m\n");
}
int main()
{
test_fork_wait1();
test_fork_wait2();
test_empty_wait();
test_fork_limit();
test_wait_is_sleeping();
printf("\x1b[92mall tests passed!\x1b[0m\n");
}

270
实验六-v0.1.md Normal file
View File

@ -0,0 +1,270 @@
<div align='center'>
<font size='6'>实验六 fork和进程树</font>
</div>
<div align='center'>
<font size='4'>谷建华</font>
</div>
<div align='center'>
<font size='4'>2022-11-14 v0.1</font>
</div>
#### 实验目的
1. 学习fork进程复制的原理和实现细节.
2. 学习进程树以理解进程间父子关系.
3. 学习简单的并发控制,内存资源管理.
#### 实验预习内容
1. fork、wait、exit系统调用基本语义和相互依赖关系.
2. 进程树结构.
3. 并发控制,理解互斥锁,关中断对内核执行流的影响.
#### 实验内容
1. 编写fork系统调用.
(1) 编写fork系统调用,使调用者能够复制进程.
(2) 在fork基本语义的实现的同时,需要考虑一种边界情况,当内核pcb资源满了这次实验内核pcb资源只有20个无法再分配时如何处理这个需要你自行阅读手册`man 2 fork`这个2什么意思可以看手册的手册`man man`,它会告诉你答案)了解怎么处理这个细节.
(3) exec系统调用已经由我们实现好了,修改`main.c`中的`do_exec`函数调用参数变为`"testfork.bin"`,测试fork是否如预期复制了进程,复制出的两个进程并发着运行.
2. 编写wait系统调用.
(1) 编写wait系统调用,使调用者在调用时会遍历它的所有子进程,如果有僵尸子进程则将其回收,将子进程状态返回,否则该进程变成睡眠状态直到有子进程exit将其唤醒.
(2) 你在写wait基本语义的同时,需要考虑一种边界情况,当调用者进程没有子进程时如何处理?这个需要你自行阅读手册`man 2 wait`了解怎么处理这个细节.
(3) exit系统调用已经由我们实现好了,修改`main.c`中的`do_exec`函数调用参数变为`"initproc.bin"`,这是OS的初始进程,它会fork出一个新进程并exec成`"shell.bin"`执行shell程序后永远不停地执行wait系统,shell大家做课内实验的时候已经实现了,现在你需要以内核的视角实现以支持shell的系统调用.
(4) `testwait.bin`是一个用于检测你实现的fork、wait的功能正确性的用户程序,你可以通过shell调用它,如果不爆panic则你实现的功能全通过.
3. fork炸弹测试
(1) 如果光实现功能,那还算简单,但是实际上操作系统很大的难点不在于实现**出**功能,而是实现**对**功能,能够经受系统中运行的各种复杂环境的考验,并发控制和资源管理对一个操作系统尤为重要.
(2) `forkbomb.bin`是一个用于测试fork、wait和exit的程序,里面会不断随机调用fork、wait和exit系统调用达成fork炸弹的目的,你需要成功运行fork系统调用20000两万内核pcb的数量**必须**为20个,内核中会有一段专门的程序检测fork的次数.测试通过的效果图如下:
![](lab6-finish.png)
(3) (自我提高内容,自己想做就做,不用写进报告,**禁止内卷**)上面的两万次还算小打小闹,检验内核程序基本的并发控制和资源管理,而这次需要你成功运行fork系统调用2000000两百万次,预计耗时若干小时,这基本上能将你实现的系统调用的并发问题都暴露出来,虽然关中断很有效,但是希望你在做这个实验的时候能尽量少关,尽可能将你的并发问题暴露出来,如果你通过了这个测试就能看到我们准备的小彩蛋.
#### 实验总结
1. 进程树算是相当经典的描述进程关系的框架,但是如果不允许用进程树这个框架,意味着没有父子进程关系,你会怎么去设计进程的创建与销毁?(开放性答案,不要觉得自己做法低级,放心大胆往上写,任何言之有理的答案都行)
2. 在这次实验的时候你遇到了什么离谱的并发bug可以写没有,如果没有,那么恭喜你,你是一个严谨的人,你考虑问题非常周全)
#### 实验指导
##### fork-状态机模型
经过前几个实验的实践,我们从一个内核的视角看向用户程序可以逐渐隐约感觉到用户程序是一个状态机.具体什么意思,可以看回pcb中存储的用户寄存器.
```c
struct user_context {
u32 gs;
u32 fs;
u32 es;
u32 ds;
u32 edi;
u32 esi;
u32 ebp;
u32 kernel_esp;
u32 ebx;
u32 edx;
u32 ecx;
u32 eax;
u32 retaddr;
u32 eip;
u32 cs;
u32 eflags;
u32 esp;
u32 ss;
};
```
`cs,ds,es,fs,gs,ss,eax,ebx,ecx,edx,esi,edi,ebp,esi,eip,eflags`这些用户寄存器就可以描述用户当前的运行状态,再结合用户程序的页表pcb中的cr3变量,也就是用户程序能够访问的内存.这些对于一个用户程序来说就是它能够**直接**控制的所有信息.其余信息需要通过系统调用向内核申请服务获悉.
用户程序在执行的时候pc执行的汇编命令可以先简单抽象为三种形式
1. 修改寄存器
2. 修改内存
3. 系统调用等其余指令
也就是说如果不考虑第三种形式(下面都暂时不考虑),仅考虑用户程序由前两种形式的汇编指令组成,给用户进程指定的用户寄存器信息和内存,这个用户进程的执行过程可以**唯一确定**.
举个简单的例子,假设一个人要涂一面墙(内存),人手里有把刷子(寄存器),人可以进行下面两个操作之一(汇编指令):
1. 将刷子的颜色调成指定颜色
2. 往墙上某个区域用刷子涂上颜色,墙上原有的部分的颜色会被覆盖
假设给人固定好初始的刷子颜色、墙面颜色(颜色可能五花八门)和操作序列,那么每一次操作后墙面颜色和刷子颜色是可以**唯一确定**的.可以通过数学归纳法证明经过若干次操作后墙面颜色和刷子颜色我们也是可以**唯一确定**的.
所以对于一个用户进程,可以将它的寄存器信息和内存信息抽象成用户程序的<u>状态</u>,而汇编指令就是一个映射方法,它将用户程序从一个<u>状态</u>映射到另一个<u>状态</u>.只要给定初始寄存器信息和内存信息,并确定执行流(汇编指令序列),那么执行流中任意一环的程序<u>状态</u>我们都是可以**唯一确定**的,这就是**状态机模型**.
接下来回到第三种形式的汇编指令`系统调用等其余指令`,第三种形式指令本质上也是对用户寄存器和内存修改的指令,系统调用根据pcb中的信息反馈给用户,由于pcb对用户是不可见的,所以在用户视角下这个指令是不可预测的,意味着在用户视角下这条指令会发生什么是不可唯一确定的.而当我们将视角从用户移到内核,甚至硬件(某些指令是跟硬件交互的),当我们知道的信息越多,用户指令执行后的状态总是可以**唯一确定**的.
##### fork-状态机复制
在讲完状态机模型后fork就比较好理解了,我们根据状态机模型可知,如果给定确定的寄存器信息和内存信息,用户程序的执行状态是可以唯一确定的(假设没有系统调用这种对用户程序来说**不可唯一确定**的指令).
fork本质上就是对**状态机的复制**,当用户进程fork的时候,内核会新创建一个pcb,将当前进程的所有状态机信息原封不动地复制一份,也就是将当前进程的用户寄存器和内存原封不动复制一份给新的进程.
在复制完状态机信息后产生了两个进程,这两个进程的内存信息和寄存器信息**完全一样**,所以之后的执行流,用户程序状态机也会**完全一致**(直到第一次遇到系统调用这种指令),这样就通过一个进程fork出两个进程.
##### fork-进程树
fork能够一模一样复制一份进程的状态机,但是实际上这终究是两个独立的进程,调用fork系统调用的进程被称为父进程,在fork中新创建的进程称为子进程.这样除了初始进程pid号为0,每个进程都有它的父进程,从数据结构的视角下进程之间的父子关系能够构成一个以0号进程为根的进程树.
这样进程每次执行fork后,该进程在进程树上会新增一个儿子节点,这个儿子节点指向的进程是fork新被创建的进程.
##### fork-父子进程返回值
这属于是大家在做上课就已经需要搞明白的东西,为了能够区分fork完后进程是父进程还是子进程,从它的返回值判断,如果返回值为0,则执行流获悉它是子进程,否则返回值为子进程的pid号,执行流获悉它是父进程.
##### exit
上面提到的进程树不只是用于描述进程之间的关系,与之关系密切的是exit和wait系统调用,相信大家在课内实验写简易shell的时候用到了fork,exec,wait这三个系统调用,然后还有一个系统调用exit被隐式调用了,被隐藏在了main函数中的返回值中.
##### exit-用户程序执行的终点
我们先随便编写一个`hello_world`程序,然后通过`strace`命令查看执行所用到的系统调用.
```
$ ls
hello_world hello_world.c
$ cat ./hello_world.c
#include <stdio.h>
int main()
{
puts("hello world!");
return 114;
}
$ strace ./hello_world
... # 太长了就不展示上面的了
write(1, "hello world!\n", 13hello world!
) = 13
exit_group(114) = ?
+++ exited with 114 +++
```
可以看到用户程序在执行完exitexit_group系统调用与线程相关,但这次实验没有线程的概念,所以可以简单理解为exit后用户程序就停止运行了,而main中的返回值114也写入到exit系统调用的参数中了.
##### exit-程序的第一条指令和最后一条指令
在我们初学编程的时候肯定会有一个潜意识就是程序的第一条指令在main函数,这对初学者来说肯定是这样的,也很符合我们的直觉,觉得第一条指令在main函数.
不过这只是编译器给我们的错觉,实际上在进入main函数之前和结束main函数跳出之后程序实际上还是做了很多事的,在进入main函数之前会初始化很多东西,在结束了main函数跳出之后程序需要将很多在缓冲区的数据及时推送到内核,最后调用exit系统调用结束程序的执行.
在MINIOS中,用户程序的开始并不在main函数,由于`ld`工具默认`_start`是elf文件运行的程序入口,所以在`lib/user/astart.asm`存放着真正的用户程序入口,就是简单的调用了main,等main函数退出后eax寄存器存放着的是main函数的返回值,将eax作为参数调用exit函数结束程序的运行.虽然这个`_start`函数非常简单,但是还是能够作为一个例子证明main函数并不是程序运行的入口.
```nasm
[section .text]
extern main
extern exit
global _start
_start: ; 第一条指令
call main
push eax
call exit
```
##### exit-具体语义
通过实例可以发现exit系统调用实际上是一个进程的终止,通过翻阅linux手册`man 2 _exit`这个2什么意思可以看手册的手册`man man`,它会告诉你答案).里面有三段话能够帮助我们理解exit系统调用的具体语义.
```
The function _exit() terminates the calling process "immediately". Any
open file descriptors belonging to the process are closed. Any chil
dren of the process are inherited by init(1) (or by the nearest "sub
reaper" process as defined through the use of the prctl(2)
PR_SET_CHILD_SUBREAPER operation). The process's parent is sent a
SIGCHLD signal.
The value status & 0xFF is returned to the parent process as the
process's exit status, and can be collected using one of the wait(2)
family of calls.
These functions do not return.
```
从这段话中可以知道实现exit系统调用需要注意的事情
+ exit没有返回值,执行完后进程结束它的生命周期.
+ 如果有子进程还未被回收,这些子进程全部会被转移到初始进程下成为初始进程的子进程.(否则这个进程退出后这些子进程的父进程就不见了,这个肯定是不允许的,而初始进程肯定是活着的,交给它刚刚好)
+ 父进程会收到一个SIGCHLD信号信号这个实验不谈,可以简单理解为唤醒父进程).
+ exit中的参数会取其低8位这是很多博客不会讲到的,只有查手册才能搞明白具体的语义)作为返回参数传递给父进程.
+ 父进程需要调用wait这类的系统调用获取该进程的exit参数.
通过这些信息可以提炼出exit这个系统调用需要做的事:
+ 变成僵尸进程,状态置为ZOMBIE进程的生命周期已经结束了,它已经是个将死之"人"了,但是没有被父进程回收.所以就成了僵尸,不能运行但是就占着一部分资源).
+ 将所有子进程的父进程变成初始进程(学术名词叫托孤,还是挺有诗意的).
+ 处理exit参数.
+ 将部分资源回收(某些资源回收需要父进程完成).
+ 唤醒父进程.
##### wait
理解wait最重要的是需要先理解exit,wait这个系统调用的语义就如它的英文语义一样,就一个字:“等”.这个系统调用实际上就是在等待任意子进程exit,如果有子进程状态是ZOMBIE就回收子进程资源返回子进程状态.如果当前没有子进程可以回收,就进入SLEEP状态等待子进程exit唤醒该进程.
如果你顺着上面的指导和exit的语义思考可以得出对wait系统调用的实现的一个模糊的映像,可以知道它大概需要做什么,但是可能一些实现细节不是很清楚.不过在这次不能一步步全部引导你什么地方要做什么,这次需要你自己去查手册`man 2 wait`去仔细阅读理解wait这个系统调用的具体语义设计出一个符合语义的系统调用程序,要学会查手册,不是全部读一遍跟背诵默写一样,而是挑你需要的看,大概需要看50~60行英文.
##### 并发
指导的最后一个部分交给并发部分,并发这玩意在上课的前几节讲概念的时候你们会感觉还好,并发就是一段时间内同时做几件事.但是真正遇到并发问题的时候你们就不会这么想了,并发能够带来非常多的问题,可以说是很多是让你摸不着头脑的问题.
因为MINIOS内核态允许中断,所以很多时候在执行内核程序时会发生调度,这会导致多个内核进程在一个时间段同时执行的并发场景,这会导致一些非常诡异的并发问题.
讲一个实验设计中的例子,还记得实验五中`phy_malloc_4k`这个函数吗?在设计初期为了防止多个内核进程同时访问这个函数上了一个锁保证分配不会因为并发问题打架:
```c
phyaddr_t
phy_malloc_4k(void)
{
assert(phy_malloc_4k_p < 128 * MB);
while(xchg(&phy_malloc_4k_lock, 1) == 1)
schedule();
phyaddr_t addr = phy_malloc_4k_p;
phy_malloc_4k_p += PGSIZE;
free:
xchg(&phy_malloc_4k_lock, 0);
return addr;
}
```
但是在测试的时候发生了一件诡异的事,这个函数分配了一个128MB~128MB+4KB的物理页面出去然后内核在用的时候触发了page fault.在设计的时候挠了半天头,想不明白怎么能分配这么一个离谱的页面.
在仔细想了想之后搞明白了出问题的原因,第一个进程在争取到锁之后在`phy_malloc_4k_p += PGSIZE;`这句执行之前触发了调度,然后第二个进程在调用这个函数的时候成功躲过了assert语句的判断.调度回第一个进程把`phy_malloc_4k_p`更新,相当于第二个进程在执行assert语句的时候拿的是变量的旧值,没有拿新值判断,这个时候`phy_malloc_4k_p`的值可能是128MB,所以导致能够分配128MB~128MB+4KB这么一个离谱的物理页面.
所以正确的做法是将这个assert函数移到锁的临界区内,保证在临界区内值的判断是正确的.
```c
phyaddr_t
phy_malloc_4k(void)
{
while(xchg(&phy_malloc_4k_lock, 1) == 1)
schedule();
assert(phy_malloc_4k_p < 128 * MB);
phyaddr_t addr = phy_malloc_4k_p;
phy_malloc_4k_p += PGSIZE;
free:
xchg(&phy_malloc_4k_lock, 0);
return addr;
}
```
##### 并发-一把大锁保平安
在并发面前,很多事情非常难以预料,我们人脑计算能力是非常渺小的,无法考虑那么全面.能够做的只能一把大锁保平安,简单来说,如果一个执行流需要访问/修改某一公共资源时,就需要给公共资源加上一个锁使用`xchg`原子指令进行上互斥锁进入临界区,防止公共资源同时被其他执行流访问/修改的情况.
##### 并发-多把大锁保平安?
在实际实践的时候,执行流可能需要访问多个公共资源,比如exit就需要访问三个pcb的锁,fork和wait需要访问两个pcb的锁,这就意味我们可能要上多把锁.
如果上一把互斥锁那还好处理,执行流要么进入临界区要么没进去,如果上多把锁就又涉及到上锁这个行为本身的并发问题,考虑一个情况:
两个执行流,第一个要先给a上锁再给b上锁,第二个要先给b上锁再给a上锁,然后一个可能的情况就是第一个执行流给a上完锁之后马上发生了调度,然后调度到第二个执行流,第二个执行流给b上完锁了发现a被上锁了于是就在等,然后调度回第一个执行流,第一个执行流发现b被上了锁,于是也在等.于是这两个执行流等到天荒地老也没等到对面释放锁.
对于这个问题的解决有一种办法,就是协商好上锁顺序,如果两个执行流都按照ab或ba的上锁顺序就能保证至少一个执行流能够运作,当一个执行流执行完释放完锁后另一个执行流再上锁.
##### 并发-实验六的上锁顺序
对于此次实验来说,需要上多把锁的场景有fork,exit和wait,这些场景都需要对进程pcb上锁,所以这里需要协商规范一下上锁顺序.
上锁规范是以进程树的0号进程为根,上锁顺序从根到叶子,就是说对于一个进程如果它,它祖宗,它儿子都需要被上锁,那么上锁顺序必须为祖宗->该节点->儿子.这种上锁方式能够保证上锁的节点在进程树中是深度递增的,无论什么情况都能保证至少一个执行流能够正常运行,不过这里暂且不证明.

View File

@ -1,305 +0,0 @@
<div align='center'>
<font size='6'>实验四 中断</font>
</div>
<div align='center'>
<font size='4'>谷建华</font>
</div>
<div align='center'>
<font size='4'>2022-10-14 v0.3</font>
</div>
#### 实验目的
1. 学习中断描述符,中断处理全流程(特别是执行流、堆栈、权限的切换),包括中断描述符表IDT的功能和设置
2. 学习时钟中断和键盘中断的处理过程
3. 学习分时任务调度
#### 实验预习内容
1. 中断描述表IDT
2. 8259A设置外设中断
3. 分时任务调度原理
4. 键盘中断的字模的获取和处理
#### 实验内容
1. 验证时钟中断的发生
(1) 编译运行实验默认给的源码,观察并分析现象
(2) 修改时钟中断处理程序,使之从输出`#`变成输出`i*(*为第几次中断)`,编译运行后观察并分析现象
2. 修改时钟中断触发时的调度机制,让某个进程被分配到更少的时间片达成进程饥饿的目的(进程必须得活着的能够输出,饥饿不等于删掉进程),编译运行后观察并分析现象.
3. 阅读Orange第6章内容,修改硬件时钟频率,使之大约以1000Hz的频率触发时钟中断,编译运行后观察证实你新修改的频率要比原来的快(这个频率很难测量,只能通过对比的方式估计)
4. 阅读Orange第7章内容并结合实验参考,添加键盘中断,要求如下:
(1) 使之能够正确识别`a`\~`z`不需要结合shift键区分大小写,就算是shift+`a`也识别为`a`而不是`A`,其余字符一概丢掉.将识别出来的字符通过调用`keyboard.c`中的`add_keyboard_buf`函数将字符添加到字符缓冲区中(需学生自行实现该函数).
(2) 修改`keyboard.c`中的`getch`函数,这是一个非阻塞的函数,即当缓冲区中有字符未输出时,输出缓冲区队头该字符,并将缓冲区队头字符弹出,当缓冲区中没有字符时输出255u8意义下的-1.
(3) 我们准备了一个贪吃蛇(没错,有了时钟中断和键盘中断就能做一个小游戏了!),修改时钟频率至1000Hz,删除时钟中断处理函数中的所有`kprintf`,在`kernel_main`中仅创建一个单进程,进程入口函数会在`game.h`中给出.最后重新编译kernel就可以游玩了不用将这一步写进报告.
#### 实验总结
1. 操作系统内核的初始化阶段都完成哪些主要功能?
2. 刚刚进入时钟中断处理程序时系统用的是哪个堆栈,中断号是多少?执行流在哪个位置?在执行中断服务程序的过程中堆栈发生哪些变化?`call [irq_tabel + 4 * %1]`的功能是什么?
3. 外设(时钟、键盘等)中断的中断号是由谁决定的?在哪里决定的?
#### 实验参考
在前几个实验,我们一直是在内核态,而我们的进程一般都是在用户态下执行的,这样进程做出出格的事情也不会伤到内核.那么接下来需要研究`kernel_main`函数是怎么进入用户态.这次实验的重点是从`restart`函数出发进入到用户态,然后又因为中断回到内核态这一整个过程.
##### 1. 中断初始化
###### 如何区分当前执行流为用户态和内核态
平时都说用户态,内核态,但是怎么区分执行流目前的状态靠的就是段寄存器,可以发现段描述符的大小刚好是8字节,所以存储在段寄存器中的段选择子值假设是$ x $,那么$ \lfloor\frac{x}{8}\rfloor $就能够描述选择的是第几个段,即在二进制角度看段选择子的低三位就没有被用上,所以硬件工程师就考虑把这些位利用上,第0~1位用于权限,第2位用于标识段是全局段还是局部段.
对于权限的划分各位需要阅读Orange教材,这里不细展开,总之靠着段选择子的第0~1位可以划分当前段的权限,当权限为用户态时执行流CS就是用户态.
###### LDT初始化
LDTlocal descriptor table全称局部描述符表,跟GDT很类似,为什么需要LDT是因为在之前可能不同的任务它们的段寄存器可能会不同,为了区分,每个任务有它自己的独一套LDT,这样切换不同任务时标识更容易些.虽然理论上每个任务都有自己的独一套LDT,但是都是$ 2^{32} $寻址,限制都是靠分页做的(这个我们下个实验再说).所以我们只需要加载一次ldt就能满足所有用户态进程的需求.
为了方便大家理解段之间的区别,这里我们约定GDT里面全是存储内核态会用到的段描述符除了显存段,LDT里面存储用户态会用到的段描述符.
```C
// 这句话初始化了ldt的段描述符
init_segment(&gdt[5], (u32)ldt, sizeof(ldt) - 1, DA_LDT);
// 在加载了gdt后,ldt就可以通过传入段选择子的方法加载
lgdt [gdt_ptr] ; 使用新的GDT
lldt [SELECTOR_LDT] ; SELECTOR_LDT = 0x28 = 40 = 5 * 8
```
###### 中断
这一次实验我们要开始处理中断了,平时课上也讲过,执行流在用户态的时候肯定不能放心一直将执行流交给用户态用户不能关中断IF位,要不然就永远无法响应中断了).比如时钟中断,每次执行固定时长后硬件会向内核发送一次中断,在触发中断异常/用户请求系统调用的时候能够回到内核态,不同的触发方式会执行不同的处理函数,那么如何区分这些不同的触发方式就需要IDT了.
###### IDT初始化
IDTInterrupt Descriptor Table中断描述符表会根据中断的形式决定进入内核的中断处理函数,而区分形式是靠中断号判断,根据中断号找到对应的门描述符,根据中断描述符找到对应的中断处理函数入口和加载对应的CS段寄存器,将执行流交给内核.
类似gdt, idt需要通过`lidt`命令将idt表的数据结构载入,该数据结构与上一个实验的`gdt_ptr`一致.
```nasm
lidt [idt_ptr]
```
###### 8259A初始化
8259A简单来说是外设中断的实际处理硬件,时钟中断,键盘中断,鼠标中断等都是靠它给内核发送信号触发中断,它也需要初始化与中断描述符之间的联系.
##### 2. 中断处理过程
###### 进入用户态
在内核中,每个进程需要维护一个用于存放进程用户态当前状态的寄存器表,当用户态因为某些原因陷入内核态时用户态的当前所有寄存器信息就存放在寄存器表中,当从内核态又回到用户态时就根据寄存器表恢复用户态当前的状态.
```C
typedef struct s_stackframe {
u32 gs;
u32 fs;
u32 es;
u32 ds;
u32 edi;
u32 esi;
u32 ebp;
u32 kernel_esp;
u32 ebx;
u32 edx;
u32 ecx;
u32 eax;
u32 retaddr;
u32 eip;
u32 cs;
u32 eflags;
u32 esp;
u32 ss;
}STACK_FRAME;
```
这个就是我们这个实验会用到寄存器表,`gs`在低地址,`ss`在高地址,接下来结合源码分析进入用户态这个过程中寄存器的变化.
在`kernel_main`中,我们需要对寄存器做一次初始化:
```c
p_proc->regs.cs = (SELECTOR_FLAT_C & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.ds = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.es = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.fs = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.ss = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK)
| SA_TIL | RPL_USER;
p_proc->regs.gs = (SELECTOR_VIDEO & SA_RPL_MASK & SA_TI_MASK)
| RPL_USER;
p_proc->regs.eip = (u32)entry[i];
p_stack += STACK_PREPROCESS;
p_proc->regs.esp = (u32)p_stack;
p_proc->regs.eflags = 0x1202; /* IF=1, IOPL=1 */
```
这里可以看到初始化的段寄存器中除了`gs`都有`SA_TIL`标志位,它的实际值是4,即二进制意义下的第2位,标志着这个段是选择的是ldt中的段,而ldt中的段都是用户态权限的,所以在进入到用户态时执行流权限就自动切换到用户态.
再之后就是eip,这是执行流的寄存器,esp用于分配栈,eflags用于初始化flags信息.
在`kernel_main`初始化完后会调用`restart`函数进入用户态,这是一个汇编接口函数,关键的代码如下:
```nasm
restart:
mov esp, [p_proc_ready]
lea eax, [esp + P_STACKTOP]
mov dword [tss + TSS3_S_SP0], eax
restart_reenter: ; 我们的代码从这里开始分析,上面的等下会讲
cli
dec dword [k_reenter]
pop gs
pop fs
pop es
pop ds
popad
add esp, 4
iretd
```
先是关中断,我们肯定不希望在恢复用户态寄存器信息时被意外的中断干扰,再接下来是`k_reenter`减1这个本参考不讲,自行阅读源码),之后开始恢复寄存器信息.先是恢复`gs`\~`ds`一共四个段寄存器信息.再是恢复`edi`\~`eax`这八个寄存器,需要注意的是`kernel_esp`比较特殊,它实际上不起恢复作用,因为现在的`esp`还不是用户态`esp`,而且`popad`指令会略过`esp`的恢复.再是`esp`加4跳过`retaddr`(这个变量是用于`save`这个函数,它存储的是`call save`时`ret`的地址),最后调用`iret`将`eip`\~`ss`这五个寄存器恢复(为什么让这五个寄存器单独用特殊指令恢复原因是这五个与执行流密切相关),由于eflags中IF位被置1中断被重新打开.
###### 返回内核态
执行流肯定不能一直留在用户态,在接受中断的时候需要再次陷入内核态.再次陷入内核态后,硬件保证了在进入中断时eflags的中断IF位为0,不会受到其余中断的影响,这个时候内核调用了`call`函数保存`eax`\~`gs`寄存器(为什么不保存`ss`\~`eip`这五个寄存器在第3部分会讲到
```nasm
save:
pushad ; `.
push ds ; |
push es ; | 保存原寄存器值
push fs ; |
push gs ; /
mov dx, ss
mov ds, dx
mov es, dx
mov eax, esp ;eax = 进程表起始地址
inc dword [k_reenter] ;k_reenter++;
cmp dword [k_reenter], 0 ;if(k_reenter ==0)
jne .1 ;{
mov esp, StackTop ; mov esp, StackTop <--切换到内核栈
push restart ; push restart
jmp [eax + RETADR - P_STACKBASE]; return;
.1: ;} else { 已经在内核栈,不需要再切换
push restart_reenter ; push restart_reenter
jmp [eax + RETADR - P_STACKBASE]; return;
;}
```
在进入`call`函数中,`ret`的返回地址被存入了寄存器表中`retaddr`的位置,然后调用了`pushad`将`eax`\~`edi`存入寄存器表中,最后将其余段寄存器存入表中,这段代码最后的两个jmp是值得讲的,这个时候别傻乎乎用`ret`指令,返回地址实际上`retaddr`中存着,`ret`的话会把`restart`或`restart_reenter`当返回地址了.
###### 屏蔽中断和置EOI
```nasm
in al, INT_M_CTLMASK ; `.
or al, (1 << %1) ; | 屏蔽当前中断
out INT_M_CTLMASK, al ; /
mov al, EOI ; `. 置EOI位
out INT_M_CTL, al ; /
sti ; CPU在响应中断的过程中会自动关中断,这句之后就允许响应新的中断
```
在保存完寄存器后,需要修改中断掩码使得不再相应相同类型的中断,保证在内核中不会被同类中断干扰.然后还得向`INT_M_CTL`端口发送EOI信号,告诉硬件已经做好准备了,可以接受下一个中断了(有可能在`sti`之后马上又被下一个中断打扰).
###### 重新进入用户态
再接下来就是中断处理程序的调用了,在处理完中断后就可以准备返回用户态了:
```nasm
cli
in al, INT_M_CTLMASK ; `.
and al, ~(1 << %1) ; | 恢复接受当前中断
out INT_M_CTLMASK, al ; /
ret
```
跟上节做的事情相反,将目标中断恢复接受,然后使用ret指令,还记得`save`函数里面的`push restart`和`push restart_reenter`两个指令吗这个push的地址值就是为了`ret`准备的,`ret`过后会重新回到`restart`,然后最终回到用户态.
##### 3. TSS机制
TSS书上写的很玄乎,很难理解,但是实际上TSS没有那么难,举个实例就可以很清晰的知道TSS的作用,假设我们在用户态执行的程序突然受到一个中断要返回内核态,那么这个时候肯定不能就着用户态的esp存储寄存器信息,需要切换到一个特定的栈(内核栈)存储寄存器信息,那么这个内核栈的ss和esp需要预先存储到一个特定地方用于进入内核态时切换没错,需要段寄存器,因为用户的段寄存器是低权限的,如果访问内核栈会违反保护模式qemu直接重开,你会看到终端不断闪现boot信息,而这个存放的位置就是TSS,TSS里面存放很多数据,看起来很吓人,但是实际上现在我们只会使用其中的`ss0`和`esp0`0是内核权限级,当从用户态进入到内核态时,ss和esp会切换内核态的对应寄存器,这个时候就能正常执行内核程序.
TSS是一个段,存放在gdt表中标识为`DA_386TSS`.下面是gdt表中TSS段的初始化
```c
tss.ss0 = SELECTOR_FLAT_RW; //ss0的初始化在这里完成
tss.iobase = sizeof(tss); /* 没有I/O许可位图 */
init_segment(&gdt[4], (u32)&tss, sizeof(tss) - 1, DA_386TSS);
```
在初始化完TSS段之后需要通过`ltr`加载TSS选择子让硬件知晓TSS段.
```nasm
_start:
; 把 esp 从 LOADER 挪到 KERNEL
mov esp, StackTop ; 堆栈在 bss 段中
call cstart ; 在此函数中改变了gdt_ptr,让它指向新的GDT
lgdt [gdt_ptr] ; 使用新的GDT
lldt [SELECTOR_LDT]
lidt [idt_ptr]
jmp SELECTOR_KERNEL_CS:csinit
csinit: ; “这个跳转指令强制使用刚刚初始化的结构”——<<OS:D&I 2nd>> P90.
xor eax, eax
mov ax, SELECTOR_TSS ; 选择TSS选择子
ltr ax
jmp kernel_main
```
在每次调用`restart`函数的时候,TSS中的`sp0`寄存器赋值为进程存储的寄存器表的顶部地址,这样保证进入内核态之后第一个压入的寄存器的值对应的是寄存器表中的`ss`.
```nasm
restart:
mov esp, [p_proc_ready]
lea eax, [esp + P_STACKTOP]
mov dword [tss + TSS3_S_SP0], eax
restart_reenter:
...
```
这样当再次陷入内核态时,首先将TSS中的`esp0`和`ss0`赋值到`esp`和`ss`寄存器,再之后`eip`\~`ss`这五个寄存器(其中`esp`和`ss`是用户态下的,虽然从逻辑上感觉不可思议,但是硬件总是可行的)会被压入栈中.
##### 4. 时钟中断
时钟中断反正也挺简单的,Orange书上也有写,也就一个固定频率的晶振电路,触发指定次后向OS发送一个中断信号,这个时候执行流需要陷入内核然后处理时钟中断处理程序.
##### 5. 键盘中断
这又是书上讲的很玄乎的一部分,但是实际上没那么玄乎,实验用不到那么多,在接受到键盘中断后,我们实际上需要解决两个问题如何获取键盘输入的扫描码如何将扫描码解析成正常ASCII码字符解析后的ASCII码字符怎么用
从键盘输入上获取扫描码这个问题比较简单书上也讲了,存储在标号为`0x60`的端口里,可以通过inbin_byte函数将端口的值读出来.如果不及时读出来,你再怎么摁键盘也不会触发键盘中断,可以理解为第一次输入的扫描码直接把端口霸占住了,不让其他扫描码进来.
但是实际上我们读入的是扫描码,是键盘上的一种编码,而我们需要将这种编码进行一步映射将扫描码映射成我们熟悉的ASCII字符,我们从端口里读入的是一个字节的数据,能表达0\~255之间的数,而ASCII字符仅能表达0\~127之间的数.最高位没有被用到,所以被硬件工程师重新利用,要知道我们摁下键盘实际上分摁下和弹起两个操作,对于同一个字符,它摁下与弹起的扫描码的区别在于摁下是最高位置0,而弹起是最高位置1,如果一直摁下会一直发送摁下的扫描码.对于`a`\~`z`这些字符,我们摁一次键会收到两个扫描码(摁下和弹起),但是在我们的日常理解里我们只关心摁下这个操作,所以在这次实验中,我们需要忽略掉弹起的扫描码,只关心摁下的扫描码,接下来需要解决的就是一个映射问题,`inc/keymap.h`里存放着一张扫描码到ASCII码的转换表,通过这张转换表就可以直接将扫描码转化为ASCII字符,这里并不需要考虑shift,ctrl这种特殊的控制字符,只需要实现实验要求的功能即可.
在获取完ASCII码字符后,我们肯定不能把辛苦得来的字符丢掉,但是我们并不知道用户程序什么时候会来索取字符,所以需要一个缓冲区存储字符,在`kern/keyboard.c`放着一个简单的缓冲区:
```c
#define KB_INBUF_SIZE 4
typedef struct kb_inbuf {
u8* p_head;
u8* p_tail;
int count;
u8 buf[KB_INBUF_SIZE];
} KB_INPUT;
static KB_INPUT kb_input = {
.p_head = kb_input.buf,
.p_tail = kb_input.buf,
.count = 0,
};
```
这个数据结构本质上是一个队列,其中`p_head`指的是缓冲区的队首字符,`p_tail`指的是缓冲区的队尾字符,`count`是当前存储的字符数量,`buf`是缓冲区.需要注意的是让缓冲区满的时候所有添加的字符需要丢弃.
当有这么一个缓冲区,触发键盘中断将解析来的字符存放到缓冲区中,当有用户程序需要索取时将缓冲区的队首字符弹出交给用户程序,就完成了整个交互.