- Added paging_clone_directory_from(): deep-copies user-space pages so parent and child have independent memory. Kernel pages are shared. - Fixed process_fork() to accept registers_t* for accurate child state, and to clone from the parent's page directory (not the kernel's). - Refactored process_exit() to properly context-switch to next process using new process_switch_to_user assembly stub (loads full registers_t and performs iret), instead of halting unconditionally. - Fixed sys_waitpid() to use proper blocking: marks process BLOCKED, invokes scheduler, and resumes with exit code when child dies. - Added SYSCALL_SWITCHED mechanism to prevent syscall_handler from clobbering the next process's EAX after a context switch. - Created fork-test user app that validates fork + waitpid. - Added docs/fork.md with architecture documentation. Tested: fork-test creates child, both print messages, parent waits for child exit (code 7), parent reaps and exits (code 0). hello-world also verified to still work correctly after the process_exit refactor.
202 lines
4.3 KiB
ArmAsm
202 lines
4.3 KiB
ArmAsm
.section .text
|
|
.global idt_load
|
|
.type idt_load, @function
|
|
idt_load:
|
|
mov 4(%esp), %eax
|
|
lidt (%eax)
|
|
ret
|
|
|
|
/* Common ISR stub */
|
|
isr_common_stub:
|
|
pusha
|
|
|
|
/* Save segment registers */
|
|
mov %ds, %ax
|
|
push %eax
|
|
|
|
/* Load kernel data segment */
|
|
mov $0x10, %ax
|
|
mov %ax, %ds
|
|
mov %ax, %es
|
|
mov %ax, %fs
|
|
mov %ax, %gs
|
|
|
|
/* Push pointer to stack structure as argument for C handler */
|
|
push %esp
|
|
call isr_handler
|
|
add $4, %esp /* Clean up pushed pointer */
|
|
|
|
/* Restore segment registers */
|
|
pop %eax
|
|
mov %eax, %ds
|
|
mov %eax, %es
|
|
mov %eax, %fs
|
|
mov %eax, %gs
|
|
|
|
popa
|
|
add $8, %esp /* Cleans up error code and ISR number */
|
|
iret
|
|
|
|
/* Macro for exceptions with NO Error Code */
|
|
.macro ISR_NOERRCODE num
|
|
.global isr\num
|
|
.type isr\num, @function
|
|
isr\num:
|
|
cli
|
|
push $0
|
|
push $\num
|
|
jmp isr_common_stub
|
|
.endm
|
|
|
|
/* Macro for exceptions WITH Error Code */
|
|
.macro ISR_ERRCODE num
|
|
.global isr\num
|
|
.type isr\num, @function
|
|
isr\num:
|
|
cli
|
|
push $\num
|
|
jmp isr_common_stub
|
|
.endm
|
|
|
|
ISR_NOERRCODE 0
|
|
ISR_NOERRCODE 1
|
|
ISR_NOERRCODE 2
|
|
ISR_NOERRCODE 3
|
|
ISR_NOERRCODE 4
|
|
ISR_NOERRCODE 5
|
|
ISR_NOERRCODE 6
|
|
ISR_NOERRCODE 7
|
|
ISR_ERRCODE 8
|
|
ISR_NOERRCODE 9
|
|
ISR_ERRCODE 10
|
|
ISR_ERRCODE 11
|
|
ISR_ERRCODE 12
|
|
ISR_ERRCODE 13
|
|
ISR_ERRCODE 14
|
|
ISR_NOERRCODE 15
|
|
ISR_NOERRCODE 16
|
|
ISR_ERRCODE 17
|
|
ISR_NOERRCODE 18
|
|
ISR_NOERRCODE 19
|
|
ISR_NOERRCODE 20
|
|
ISR_NOERRCODE 21
|
|
ISR_NOERRCODE 22
|
|
ISR_NOERRCODE 23
|
|
ISR_NOERRCODE 24
|
|
ISR_NOERRCODE 25
|
|
ISR_NOERRCODE 26
|
|
ISR_NOERRCODE 27
|
|
ISR_NOERRCODE 28
|
|
ISR_NOERRCODE 29
|
|
ISR_ERRCODE 30
|
|
ISR_NOERRCODE 31
|
|
/* Macro for IRQs (Hardware Interrupts) */
|
|
.macro ISR_IRQ num, idt_index
|
|
.global irq\num
|
|
.type irq\num, @function
|
|
irq\num:
|
|
cli
|
|
push $0
|
|
push $\idt_index
|
|
jmp isr_common_stub
|
|
.endm
|
|
|
|
/* Hardware Interrupts */
|
|
ISR_IRQ 0, 32
|
|
ISR_IRQ 1, 33
|
|
ISR_IRQ 2, 34
|
|
ISR_IRQ 3, 35
|
|
ISR_IRQ 4, 36
|
|
ISR_IRQ 5, 37
|
|
ISR_IRQ 6, 38
|
|
ISR_IRQ 7, 39
|
|
ISR_IRQ 8, 40
|
|
ISR_IRQ 9, 41
|
|
ISR_IRQ 10, 42
|
|
ISR_IRQ 11, 43
|
|
ISR_IRQ 12, 44
|
|
ISR_IRQ 13, 45
|
|
ISR_IRQ 14, 46
|
|
ISR_IRQ 15, 47
|
|
|
|
/*
|
|
* INT 0x80 - System call entry point.
|
|
* Uses the same isr_common_stub so the register layout matches registers_t.
|
|
*/
|
|
.global isr128
|
|
.type isr128, @function
|
|
isr128:
|
|
cli
|
|
push $0 /* Fake error code */
|
|
push $0x80 /* Interrupt number 128 */
|
|
jmp isr_common_stub
|
|
|
|
/*
|
|
* tss_flush - Load the Task Register with the TSS selector.
|
|
* TSS is GDT entry 5, selector = 5*8 = 0x28. With RPL=0: 0x28.
|
|
*/
|
|
.global tss_flush
|
|
.type tss_flush, @function
|
|
tss_flush:
|
|
mov $0x28, %ax
|
|
ltr %ax
|
|
ret
|
|
|
|
/*
|
|
* enter_usermode - Switch to Ring 3 user mode via iret.
|
|
* void enter_usermode(uint32_t eip, uint32_t esp);
|
|
*
|
|
* Builds an iret frame on the stack:
|
|
* SS = 0x23 (user data)
|
|
* ESP = user stack pointer
|
|
* EFLAGS = IF=1
|
|
* CS = 0x1B (user code)
|
|
* EIP = user entry point
|
|
*/
|
|
.global enter_usermode
|
|
.type enter_usermode, @function
|
|
enter_usermode:
|
|
mov 4(%esp), %ecx /* user EIP */
|
|
mov 8(%esp), %edx /* user ESP */
|
|
|
|
/* Set data segment registers to user data segment */
|
|
mov $0x23, %ax
|
|
mov %ax, %ds
|
|
mov %ax, %es
|
|
mov %ax, %fs
|
|
mov %ax, %gs
|
|
|
|
/* Build iret frame */
|
|
push $0x23 /* SS (user data) */
|
|
push %edx /* ESP (user stack) */
|
|
pushf /* EFLAGS */
|
|
orl $0x200, (%esp) /* Ensure IF (Interrupt Flag) is set */
|
|
push $0x1B /* CS (user code) */
|
|
push %ecx /* EIP (entry point) */
|
|
iret
|
|
|
|
/*
|
|
* process_switch_to_user - Restore full register state and iret to user mode.
|
|
* void process_switch_to_user(registers_t *regs);
|
|
*
|
|
* Used by process_exit to context-switch to the next process when the normal
|
|
* interrupt-return path isn't available (because we're not returning through
|
|
* an ISR stub). Loads all registers from the registers_t struct and performs
|
|
* iret to enter user mode.
|
|
*/
|
|
.global process_switch_to_user
|
|
.type process_switch_to_user, @function
|
|
process_switch_to_user:
|
|
movl 4(%esp), %esp /* Point ESP to the registers_t struct */
|
|
|
|
/* Restore segment register (ds → all data segments) */
|
|
pop %eax
|
|
mov %ax, %ds
|
|
mov %ax, %es
|
|
mov %ax, %fs
|
|
mov %ax, %gs
|
|
|
|
popa /* Restore EAX-EDI */
|
|
addl $8, %esp /* Skip int_no and err_code */
|
|
iret /* Pops EIP, CS, EFLAGS, UserESP, SS */
|