Lecture 3: running binaries in userspace.

This commit is contained in:
Alexander Myltsev 2023-01-31 13:11:09 +04:00
parent 7d1b88ec00
commit 1679518a28
16 changed files with 303 additions and 9 deletions

View File

@ -24,7 +24,8 @@ LDKERNELFLAGS = --script=script.ld
endif endif
OBJECTS = kernel.o console.o drivers/vga.o drivers/uart.o drivers/keyboard.o \ OBJECTS = kernel.o console.o drivers/vga.o drivers/uart.o drivers/keyboard.o \
cpu/idt.o cpu/vectors.o lib/mem.o cpu/idt.o cpu/gdt.o cpu/swtch.o cpu/vectors.o lib/mem.o proc.o lib/string.o \
fs/fs.o
run: image.bin run: image.bin
qemu-system-i386 -drive format=raw,file=$< -serial mon:stdio qemu-system-i386 -drive format=raw,file=$< -serial mon:stdio
@ -76,8 +77,8 @@ debug-nox: image.bin
-ex "break _start" \ -ex "break _start" \
-ex "continue" -ex "continue"
fs.img: kernel.bin tools/mkfs fs.img: kernel.bin tools/mkfs user/false user/greet user/div0
tools/mkfs $@ $< tools/mkfs $@ $< user/false user/greet user/div0
LDFLAGS=-m elf_i386 LDFLAGS=-m elf_i386

View File

@ -1,4 +1,4 @@
#pragma once #pragma once
void printk(const char* msg); void printk(const char* msg);
void panic(const char* msg); _Noreturn void panic(const char* msg);

65
cpu/gdt.c Normal file
View File

@ -0,0 +1,65 @@
#include "gdt.h"
#include "../lib/string.h"
#include <stdint.h>
struct seg_desc_t {
uint16_t lim_15_0; // Low bits of segment limit
uint16_t base_15_0; // Low bits of segment base address
uint8_t base_23_16; // Middle bits of segment base address
uint8_t type : 4; // Segment type (see STA_ constants)
uint8_t s : 1; // 0 = system, 1 = application
uint8_t dpl : 2; // Descriptor Privilege Level
uint8_t p : 1; // Present
uint8_t lim_19_16 : 4; // High bits of segment limit
uint8_t avl : 1; // Unused (available for software use)
uint8_t rsv1 : 1; // Reserved
uint8_t db : 1; // 0 = 16-bit segment, 1 = 32-bit segment
uint8_t g : 1; // Granularity: limit scaled by 4K when set
uint8_t base_31_24; // High bits of segment base address
} __attribute__((packed));
#define SEG(type, base, lim, dpl) (struct seg_desc_t) \
{ ((lim) >> 12) & 0xffff, (uint)(base) & 0xffff, \
((uint)(base) >> 16) & 0xff, type, 1, dpl, 1, \
(uint)(lim) >> 28, 0, 0, 1, 1, (uint)(base) >> 24 }
#define SEG16(type, base, lim, dpl) (struct seg_desc_t) \
{ (lim) & 0xffff, (uint)(base) & 0xffff, \
((uint)(base) >> 16) & 0xff, type, 1, dpl, 1, \
(uint)(lim) >> 16, 0, 0, 1, 0, (uint)(base) >> 24 }
struct seg_desc_t seg_desc[6];
void init_seg_desc() {
seg_desc[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, 0);
seg_desc[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0);
seg_desc[SEG_UCODE] = SEG(STA_X|STA_R, USER_BASE, 0xffffffff - USER_BASE, DPL_USER);
seg_desc[SEG_UDATA] = SEG(STA_W, USER_BASE, 0xffffffff - USER_BASE, DPL_USER);
}
struct gdt_desc_t {
uint16_t size;
void* ptr;
} __attribute__((packed));
void load_gdt() {
init_seg_desc();
struct gdt_desc_t gdt_desc;
gdt_desc.size = sizeof(seg_desc) - 1;
gdt_desc.ptr = seg_desc;
asm("lgdt %0": : "m"(gdt_desc));
}
void switchuvm(struct taskstate *tss, void* esp) {
memset(tss, 0, sizeof(*tss));
seg_desc[SEG_TSS] = SEG16(STS_T32A, tss, sizeof(*tss)-1, 0);
seg_desc[SEG_TSS].s = 0;
tss->ss0 = SEG_KDATA << 3;
tss->esp0 = (uint)esp;
// setting IOPL=0 in eflags *and* iomb beyond the tss segment limit
// forbids I/O instructions (e.g., inb and outb) from user space
tss->iomb = (ushort) 0xFFFF;
asm("ltr %0": : "r"((ushort)(SEG_TSS << 3)));
}

View File

@ -1,5 +1,7 @@
#include "isr.h" #include "isr.h"
#include "gdt.h" #include "gdt.h"
#include "../syscall.h"
#include "../proc.h"
#include "../drivers/port.h" #include "../drivers/port.h"
#include "../console.h" #include "../console.h"
@ -47,6 +49,7 @@ void init_idt() {
for (int i = 0; i < IDT_HANDLERS; i++) { for (int i = 0; i < IDT_HANDLERS; i++) {
set_idt_gate(i, 0, default_handlers[i], 0); set_idt_gate(i, 0, default_handlers[i], 0);
} }
set_idt_gate(T_SYSCALL, 1, default_handlers[T_SYSCALL], DPL_USER);
} }
const char * const exception_messages[] = { const char * const exception_messages[] = {
@ -82,6 +85,26 @@ void register_interrupt_handler(uint8_t i, isr_t handler) {
} }
void trap(registers_t *r) { void trap(registers_t *r) {
if (r->int_no == T_SYSCALL) {
switch (r->eax) {
case SYS_exit:
if (r->ebx == 0) {
printk("* success\n");
} else {
printk("* failure\n");
}
killproc();
case SYS_greet:
printk("Hello world!\n");
r->eax = 0;
break;
default:
printk("Unknown syscall\n");
r->eax = -1;
}
return;
}
if (r->int_no < 32) { if (r->int_no < 32) {
const char* msg = "Reserved"; const char* msg = "Reserved";
if (r->int_no < ARRLEN(exception_messages)) { if (r->int_no < ARRLEN(exception_messages)) {
@ -90,6 +113,7 @@ void trap(registers_t *r) {
panic(msg); panic(msg);
} }
/* Handle the interrupt in a more modular way */
if (interrupt_handlers[r->int_no] != 0) { if (interrupt_handlers[r->int_no] != 0) {
isr_t handler = interrupt_handlers[r->int_no]; isr_t handler = interrupt_handlers[r->int_no];
handler(r); handler(r);

20
cpu/swtch.S Normal file
View File

@ -0,0 +1,20 @@
// swtch(void** oldstack, void* newstack)
.global swtch
swtch:
mov 4(%esp), %eax // eax holds "oldstack"
mov 8(%esp), %ecx
push %ebx
push %ebp
push %esi
push %edi
mov %esp, (%eax) // save stack ptr to "oldstack"
mov %ecx, %esp // use "newstack" as stack ptr
pop %edi
pop %esi
pop %ebp
pop %ebx
ret

12
fs/fs.c Normal file
View File

@ -0,0 +1,12 @@
#include "fs.h"
#include "../lib/string.h"
#include "../drivers/ata.h"
#include "../console.h"
int stat(const char* name, struct stat *buf) {
panic("stat not implemented");
}
int read_file(const char* name, void* buf, uint32_t bufsize) {
panic("read_file not implemented");
}

View File

@ -1,19 +1,43 @@
asm(".asciz \"kernel start\\n\"");
#include "console.h" #include "console.h"
#include "drivers/vga.h"
#include "drivers/uart.h"
#include "drivers/keyboard.h"
#include "cpu/isr.h" #include "cpu/isr.h"
#include "cpu/gdt.h"
#include "drivers/keyboard.h"
#include "drivers/vga.h"
#include "drivers/ata.h"
#include "drivers/misc.h"
#include "drivers/uart.h"
#include "fs/fs.h"
#include "lib/string.h"
#include "proc.h"
void _start() { void _start() {
uartinit(); load_gdt();
init_keyboard(); init_keyboard();
uartinit();
load_idt(); load_idt();
sti(); sti();
vga_clear_screen(); vga_clear_screen();
printk("\nYABLOKO\n> "); printk("YABLOKO\n");
printk("\n> ");
while (1) { while (1) {
if (kbd_buf_size > 0 && kbd_buf[kbd_buf_size-1] == '\n') {
if (!strncmp("halt\n", kbd_buf, kbd_buf_size)) {
qemu_shutdown();
} else if (!strncmp("run ", kbd_buf, 4)) {
kbd_buf[kbd_buf_size-1] = '\0';
const char* cmd = kbd_buf + 4;
run_elf(cmd);
} else {
printk("unknown command, try: halt | run CMD");
}
kbd_buf_size = 0;
printk("\n> ");
}
asm("hlt"); asm("hlt");
} }
} }

20
lib/string.c Normal file
View File

@ -0,0 +1,20 @@
#include "string.h"
int strncmp(const char* s1, const char* s2, size_t size) {
while (size && *s1 && *s2 && *s1 == *s2) {
size--;
s1++;
s2++;
}
if (!size) {
return 0;
}
return (unsigned char)(*s1) - (unsigned char)(*s2);
}
void memset(void* b, char c, size_t len) {
char* p = b;
for (size_t i = 0; i < len; ++i) {
p[i] = c;
}
}

7
lib/string.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
typedef unsigned size_t;
void kmemmove(char* dst, char* src, size_t size);
int strncmp(const char* s1, const char* s2, size_t size);
void memset(void* b, char c, size_t len);

75
proc.c Normal file
View File

@ -0,0 +1,75 @@
#include "elf.h"
#include "proc.h"
#include "fs/fs.h"
#include "cpu/gdt.h"
#include "cpu/isr.h"
#include "lib/mem.h"
#include "lib/string.h"
#include "console.h"
struct context {
// matches the behavior of swtch()
uint32_t edi, esi, ebp, ebx;
uint32_t eip; // return address for swtch()
};
struct kstack {
uint32_t space[400];
struct context context;
registers_t trapframe;
char bottom[];
};
struct task {
struct taskstate tss;
struct kstack stack;
};
struct vm {
void *kernel_thread;
void *user_thread;
struct task *user_task;
} *vm;
void trapret();
void swtch(void** oldstack, void* newstack);
void run_elf(const char* name) {
if (!vm) {
vm = kmalloc(sizeof(struct vm));
vm->user_task = kmalloc(sizeof(struct task));
switchuvm(&vm->user_task->tss, vm->user_task->stack.bottom);
}
if (read_file(name, (void*)USER_BASE, 100 << 20) <= 0) {
printk(name);
printk(": file not found\n");
return;
}
Elf32_Ehdr *hdr = (void*)USER_BASE;
struct kstack *u = &vm->user_task->stack;
memset(u, 0, sizeof(*u));
u->context.eip = (uint32_t)trapret;
registers_t *tf = &u->trapframe;
tf->eip = hdr->e_entry;
tf->cs = (SEG_UCODE << 3) | DPL_USER;
tf->ds = (SEG_UDATA << 3) | DPL_USER;
tf->es = tf->ds;
tf->fs = tf->ds;
tf->gs = tf->ds;
tf->ss = tf->ds;
tf->eflags = FL_IF;
tf->useresp = USER_STACK_BASE;
// initialization done, now switch to the process
swtch(&vm->kernel_thread, &u->context);
// process has finished
}
_Noreturn void killproc() {
void* task_stack;
swtch(&task_stack, vm->kernel_thread);
__builtin_unreachable();
}

4
proc.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
void run_elf(const char* name);
_Noreturn void killproc();

9
syscall.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
enum {
T_SYSCALL = 0x84,
SYS_exit = 0,
SYS_greet = 1,
};
int syscall(int call, int arg);

18
user/crt.c Normal file
View File

@ -0,0 +1,18 @@
#include "../syscall.h"
int main();
int syscall(int call, int arg) {
asm("int $0x84": "+a"(call) : "b"(arg));
return call;
}
_Noreturn
void _exit(int exit_status) {
syscall(SYS_exit, exit_status);
__builtin_unreachable();
}
void _start() {
_exit(main());
}

5
user/div0.c Normal file
View File

@ -0,0 +1,5 @@
int main(void) {
volatile int x = 1, y = 0;
x /= y;
return 0;
}

3
user/false.c Normal file
View File

@ -0,0 +1,3 @@
int main() {
return 1;
}

7
user/greet.c Normal file
View File

@ -0,0 +1,7 @@
#include "../syscall.h"
int main(void) {
syscall(SYS_greet, 0);
syscall(SYS_greet, 0);
return 0;
}