summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorJake Mannens <jake72360@gmail.com>2018-07-25 01:17:39 +1000
committerJake Mannens <jake72360@gmail.com>2018-07-25 01:17:39 +1000
commit45819c035d0b92275de68e559f066cbe50996926 (patch)
tree0ae32cffc4567dee891ecca9e5162731b341d1a0 /kernel
parenta0aa45ea6f4027dd4080cf1456a2fc5d9e94f87b (diff)
Fixed a bug in switch_to() in which the value of EBX was not popped
prior to return. This meant that switching to the same task did not abort properly as the incorrect return address was popped off the stack. Fixed a bug where the task register was not initialized before the scheduler. This meant that on the first task switch, the CPU would dump it's current state to a random address (0 most likely), potentially corrupting important data. This has been corrected by introducing a 'garbage TSS' (and associated descriptor in the GDT) which is selected before the scheduler is initialized as a safe place for the data to be written. Modified the scheduler so that it now waits indefinitely until a task becomes ready to run. This fixes the possible bug where the scheduler won't re-schedule the currently running task if it is the only task on the system. Add signal handling capabilities to the kernel. The bulk of this is present in the subroutine check_signals() defined in traps.s. This function is called on every timer tick and system call prior to userspace return. The subroutine operates by pushing fake state information onto the kernel's stack, then using it to return to userspace. Prior to this, the subroutine pushes the return address 0xFFFFE000 onto the user's stack. This address corresponds to the unmapped page located between the top of the user's stack (lower) and the kernel's stack page (upper). When the user's signal handler tries to return, it will cause a page fault that will be used as a notification mechanism to inform the kernel that the signal handler is done. The kernel will then switch to the originally pushed state information and use it to return the task to the original execution state. Due to it's nature, check_signals() must only be called prior to exiting the kernel since it may not return. Added the header file 'signal.h' which defines (most) of the POSIX signals as well as the prototype for the signal() function. Added the 'signal' element to the task structure. This field is a bitmap of all currently pending signals. Added the 'sig_handlers' element to the task structure. This is an array of all user-defined signal handlers. Currently, a value of 0 indicates the default handler should be used whilst any other value is considered to be the address of a userspace signal handler. The ability to ignore a signal is not yet present but will be added sometime soon. Added the sys_signal system call to register a signal. Added the stub function sighandler_default() to sched.c which handles all signals not caught by the user.
Diffstat (limited to 'kernel')
-rw-r--r--kernel/asm.s1
-rw-r--r--kernel/boot.s32
-rw-r--r--kernel/memory.c1
-rw-r--r--kernel/sched.c19
-rw-r--r--kernel/sys.c21
-rw-r--r--kernel/timer.s3
-rw-r--r--kernel/traps.s146
7 files changed, 201 insertions, 22 deletions
diff --git a/kernel/asm.s b/kernel/asm.s
index aced9b4..8b5c231 100644
--- a/kernel/asm.s
+++ b/kernel/asm.s
@@ -15,6 +15,7 @@ switch_to:
mov ebx, [ctask]
cmp ebx, [ebp+12]
jne .skip
+ pop ebx
pop ebp
ret
.skip:
diff --git a/kernel/boot.s b/kernel/boot.s
index 96f0e43..21b248c 100644
--- a/kernel/boot.s
+++ b/kernel/boot.s
@@ -11,6 +11,8 @@ extern syscall_init
extern timer_init
extern traps_init
+%define NRTASKS 64
+
; Multiboot header
section .mboothdr
mboot_hdr:
@@ -80,16 +82,22 @@ gdt:
db 0xCF
db 0x00
; empty TSS slots for our C scheduler
- times 64 dq 0
+ times NRTASKS dq 0
+ ; garbage TSS descriptor
+ dq 0
.end:
gdtp:
dw (gdt.end-gdt-1)
dd gdt
+garbage_tss:
+ times 104 db 0
+
flush_gdt:
push ebp
mov ebp, esp
+ push ebx
; now, load the GDT
lgdt [gdtp]
mov ax, 0x10
@@ -100,6 +108,28 @@ flush_gdt:
mov ss, ax
jmp 0x08:.end
.end:
+ ; finally, we load the task register with
+ ; a pointer to our dummy TSS. this is
+ ; necessary since the CPU *must* save the
+ ; previous task state *somewhere* before
+ ; switching tasks. to avoid overwriting
+ ; any sensitive information, we reserve
+ ; 104 bytes for a *garbage* TSS.
+ mov eax, 5
+ add eax, NRTASKS
+ shl eax, 3
+ mov ebx, eax
+ add ebx, gdt
+ mov edx, garbage_tss
+ mov dword [ebx], 103 ; limit - 1
+ mov [ebx+2], dx
+ shr edx, 16
+ mov [ebx+4], dl
+ mov [ebx+7], dh
+ mov byte [ebx+5], 0x89
+ mov byte [ebx+6], 0x00
+ ltr ax
+ pop ebx
pop ebp
ret
diff --git a/kernel/memory.c b/kernel/memory.c
index 465b44f..0c73970 100644
--- a/kernel/memory.c
+++ b/kernel/memory.c
@@ -85,6 +85,7 @@ void *map_page(void *page) {
/*
* invalidate any cached entries for
* the mapped page window.
+ * TODO: skip this on i386
*/
__asm__ ("invlpg %0" :: "m" (mapped_page));
diff --git a/kernel/sched.c b/kernel/sched.c
index efbb80a..529db61 100644
--- a/kernel/sched.c
+++ b/kernel/sched.c
@@ -1,6 +1,9 @@
#include <asm/system.h>
+#include <kernel/con.h>
+#include <kernel/kernel.h>
#include <kernel/memory.h>
#include <kernel/sched.h>
+#include <signal.h>
#include <string.h>
#include <sys/types.h>
@@ -144,14 +147,12 @@ struct task_struct *create_proc(void *bin, size_t len) {
/* load in the user binary */
memcpy(USRSTART, bin, len);
- /*
- * here we initialize the task struct.
- * currently, we only setup state information
- * necessary to begin execution.
- */
+ /* initialize the task struct */
memset(task, 0, sizeof(struct task_struct));
task->pid = nextpid;
task->state = TSTATE_RUNNING;
+
+ /* initialize the task's state */
task->tss.cr3 = (uint32_t) pdir;
task->tss.cs = 0x1B;
task->tss.ds = 0x23;
@@ -185,7 +186,6 @@ void sched_init(void) {
create_proc(&_usrbin_start, (size_t) &_usrbin_size);
create_proc(&_usrbin_start, (size_t) &_usrbin_size);
- create_proc(&_usrbin_start, (size_t) &_usrbin_size);
switch_to(0, &tasks[0]);
}
@@ -198,11 +198,12 @@ void reschedule(void) {
return;
n = ctaskn;
- for(i = 0; i < NRTASKS; i++) {
+ while(1) {
n = (n + 1) % NRTASKS;
if(tasks[n].pid && tasks[n].state == TSTATE_RUNNING)
break;
}
+
switch_to(n, &tasks[n]);
}
@@ -217,3 +218,7 @@ void wake_up(struct task_struct **task) {
(*task)->state = TSTATE_RUNNING;
*task = NULL;
}
+
+void sighandler_default(uint32_t sig) {
+ printk("[kernel] Handling signal 0x%02x for PID 0x%04x\n", sig, ctask->pid);
+}
diff --git a/kernel/sys.c b/kernel/sys.c
index 8c33fdf..86fae41 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -1,13 +1,20 @@
#include <errno.h>
#include <kernel/con.h>
#include <kernel/sched.h>
+#include <signal.h>
#include <stdint.h>
+#include <sys/types.h>
#include <time.h>
#include <unistd.h>
extern uint32_t ticks;
int sys_puts(char *s) {
+ uint32_t sigs = 5;
+ printk("[kernel] Setting signals %01x for PID 0x%04x\n", sigs, ctask->pid);
+ ctask->signal |= sigs;
+
+ printk("[%04x] ", ctask->pid);
return printk(s);
}
@@ -27,6 +34,20 @@ void *sys_getpdir(void) {
return (void*) pdir;
}
+void *sys_signal(int sig, void (*func)(int)) {
+ if(sig < 0 || sig >= NRSIG)
+ return NULL;
+
+ /* SIGKILL and SIGSTOP cannot be caught */
+ if(sig == SIGKILL || sig == SIGSTOP)
+ return NULL;
+
+ printk("Task %x registered new signal handler 0x%08x for signal %01x\n", ctask->pid, (uint32_t) func, sig);
+ ctask->sig_handlers[sig] = func;
+
+ return func;
+}
+
int sys_dummy(void) {
return -ENOSYS;
}
diff --git a/kernel/timer.s b/kernel/timer.s
index 760bcd7..c3af34b 100644
--- a/kernel/timer.s
+++ b/kernel/timer.s
@@ -1,5 +1,6 @@
global ticks
global timer_init
+extern check_signals
extern register_isr
extern sched_tick
@@ -24,6 +25,8 @@ tick_handler:
; call the scheduler
call sched_tick
+ ; handle any pending signals before returning to normal execution
+ call check_signals
; restore the data segment selectors
pop ax
diff --git a/kernel/traps.s b/kernel/traps.s
index e084ac8..73e7f64 100644
--- a/kernel/traps.s
+++ b/kernel/traps.s
@@ -3,13 +3,24 @@
; as well as the system call interface.
;
+global check_signals
global syscall_init
global traps_init
extern call_table
+extern ctask
extern idt
extern panic
extern printk
extern register_isr
+extern sighandler_default
+
+; here we define offsets into task_struct.
+; if task_struct is modified, this must be
+; too.
+
+%define ts_signal 6
+%define ts_sig_handlers 10
+%define ts_tss_esp0 142
%macro SAVE 0
pusha
@@ -64,19 +75,6 @@ extern register_isr
iret
%endmacro
-%macro RESTORE_SYS 0
- ; preserve our modified EAX (return value for syscalls)
- mov [esp+30], eax
- ; restore the data segment selectors
- pop ax
- mov ds, ax
- mov es, ax
- mov fs, ax
- mov gs, ax
- popa
- iret
-%endmacro
-
traps:
dd exc_div
dd exc_debug
@@ -204,6 +202,34 @@ exc_gprot:
exc_pagef:
SAVE_ERR
+ ; here we check if the page fault was
+ ; caused by a process trying to return
+ ; from a signal handler (this should cause
+ ; a page fault for accessing page
+ ; 0xFFFFE000 during the handler's
+ ; execution).
+
+ ; check if the offending address is 0xFFFFE000
+ mov eax, cr2
+ cmp eax, 0xFFFFE000
+ jne .skip
+ ; check if we're actually handling a signal
+ mov eax, [ctask]
+ mov eax, [eax+ts_tss_esp0]
+ cmp eax, 0xFFFFFFFF
+ je .skip
+ ; final checks that a user-initiated read caused the fault
+ mov eax, ebx
+ and eax, 6
+ cmp eax, 4
+ jne .skip
+ jmp sigret
+.skip:
+
+ ; if the above conditions are false, a
+ ; signal handler was not trying to return,
+ ; so we handle the page fault normally
+
push ebx
mov ebx, cr2
push ebx
@@ -243,6 +269,9 @@ exc_reserved:
syscall_handler:
SAVE
+ ; push the arguments onto the stack, then
+ ; calculate the offset for the appropriate
+ ; handler function and call it
push edx
push ecx
push ebx
@@ -251,9 +280,98 @@ syscall_handler:
add eax, call_table
call [eax]
add esp, 12
+ ; preserve the syscall's return value
+ mov [esp+30], eax
+ mov eax, esp
+ ; check pending signals and handle them (if any)
+ push eax
+ call check_signals
+ add esp, 4
- RESTORE_SYS
+ RESTORE
+; void check_signals(void *saved_state)
+check_signals:
+ push ebp
+ mov ebp, esp
+ push ebx
+ push esi
+ ; abort if a signal is already being handled
+ mov eax, [ctask]
+ mov ebx, [eax+ts_tss_esp0]
+ cmp ebx, 0xFFFFFFFF
+ jne .end
+ ; check if there are any pending signals
+ ; if so, we'll resume at the signal
+ ; handler if a custom handler has been
+ ; defined, otherwise, we run the default
+ ; handler.
+ mov edx, [eax+ts_signal]
+ cmp edx, 0
+ je .end
+ ; find which signal has been triggered (then clear it's flag)
+ bsf ecx, edx
+ mov ebx, 1
+ shl ebx, cl
+ xor ebx, 0xFFFFFFFF
+ and dword [eax+ts_signal], ebx
+ mov ebx, ecx
+ ; calculate offset to the handler function
+ shl ecx, 2
+ add ecx, eax
+ add ecx, ts_sig_handlers
+ cmp dword [ecx], 0
+ jne .custom_handler
+ ; call the default handler
+ push ebx
+ call sighandler_default
+ add esp, 4
+ jmp .end
+.custom_handler:
+ ; save our stack pointer
+ mov esi, [ebp+8]
+ mov [eax+ts_tss_esp0], esi
+ ; here, we push the return address onto
+ ; the user's stack. we give a bogus return
+ ; address of 0xFFFFE000 (the second-last
+ ; page) to deliberately trigger a page
+ ; fault that will alert us when the signal
+ ; handler tried to return.
+ mov ebx, [ebp+8]
+ mov esi, ebx
+ add esi, 46
+ mov esi, [esi]
+ sub esi, 4
+ mov dword [esi], 0xFFFFE000
+ ; push a new state onto our stack
+ push dword [ebx+50] ; SS
+ push dword esi ; ESP
+ push dword [ebx+42] ; EFLAGS
+ push dword [ebx+38] ; CS
+ push dword [ecx] ; EIP
+ push dword 0 ; EAX
+ push dword 0 ; ECX
+ push dword 0 ; EDX
+ push dword 0 ; EBX
+ push dword 0 ; ESP
+ push dword [ebx+10] ; EBP
+ push dword 0 ; ESI
+ push dword 0 ; EDI
+ push word [ebx] ; DS
+ RESTORE
+.end:
+ pop esi
+ pop ebx
+ pop ebp
+ ret
+
+sigret:
+ call check_signals
+ ; restore our original stack frame and reset the TSS's ESP0
+ mov esp, [eax+ts_tss_esp0]
+ mov dword [eax+ts_tss_esp0], 0xFFFFFFFF
+ ; finally, perform the normal syscall return
+ RESTORE
syscall_init:
push ebp