The task switch method in sharix is software switch, so it is mainly about context switch and the change of the register value and there's nothing about TSS segments at the first time. When it is time to add the user process model in sharix, the TSS will be needed because we need it to change the esp0 and ss0 to change the privilege from ring3 to ring0.
init process structure
// process's state in his life cycle enum proc_state { PROC_UNINIT = 0, // uninitialized PROC_SLEEPING, // sleeping PROC_RUNNABLE, // runnable(maybe running) PROC_ZOMBIE, // almost dead, and wait parent proc to reclaim his resource }; struct proc_struct { enum proc_state state; int pid; uintptr_t kstack; // kernel stack struct proc_struct *parent; struct mm_struct *mm; struct context context; struct trapframe *tf; uintptr_t cr3; // address of PDT list_entry_t list_link; };
idleproc
kernel memory & registers
In entry.S, CS has been set $KERNEL_CS, and DS & ES & SS has been set KERNEL_DS, The stack space is as follows:
.data .align PGSIZE .globl bootstack bootstack: .space KSTACKSIZE .global bootstacktop bootstacktop:
- eip is changing as the program goes.
- mm is the boot_pgdir
- cr3 is boot_cr3
eax 0x10 16 ecx 0x3 3 edx 0xc0108d99 -1072656999 ebx 0x10094 65684 esp 0xc0121fbc 0xc0121fbc ebp 0xc0121ff8 0xc0121ff8 esi 0x10094 65684 edi 0x807c 32892 eip 0xc01075bd 0xc01075bd < cpu_idle> eflags 0x286 [ PF SF IF ] cs 0x8 8 ss 0x10 16 ds 0x10 16 es 0x10 16 fs 0x23 35 gs 0x23 35
several process state
To clarify the process switch, there is only two kernel threads now in sharix. One is idleproc and another is thread1. The process thread1 is used to test the correctness of process, and it is created by kernel_thread function as follows:
int pid = kernel_thread(thread1, NULL); struct proc_struct * proc = find_proc(pid); print_proc(proc); proc_run(proc);
1) process idle structure value:
------------------- [info process] --------------- state: runnable pid: 0 need_resched: 0 kstack: c0121000 ------------- contex --------------- eip: 0 esp: 0 ebx: 0 ecx: 0 edx: 0 esi: 0 edi: 0 ebp: 0 cr3: 7fdf000 time_slice: 0
2) created pid1 process value:
------------------- [info process] --------------- state: uninitialized pid: 1 need_resched: 0 kstack: c7e51000 //proc->kstack = (uintptr_t)kmalloc(PGSIZE); parent pid: 0 ------------- contex --------------- eip: c01073a0 < forkret> esp: c7e51fbc //proc->context.esp = (uintptr_t)(proc->tf); ebx: 0 ecx: 0 edx: 0 esi: 0 edi: 0 ebp: 0 trapframe at 0xc7e51fbc //proc->tf = (struct trapframe *)(proc->kstack + PGSIZE) - 1 edi 0x00000000 esi 0x00000000 ebp 0x00000000 oesp 0x00000000 ebx 0xc010790f < thread1> edx 0x00000000 ecx 0x00000000 eax 0x00000000 es 0x----0010 ds 0x----0010 trap 0x00000000 err 0x00000000 eip 0xc01000dc < kernel_thread_entry> cs 0x----0008 flag 0x00000200 cr3: 7fdf000 time_slice: 0
proc_run then switch_to
struct context { uint32_t eip; uint32_t esp; uint32_t ebx; uint32_t ecx; uint32_t edx; uint32_t esi; uint32_t edi; uint32_t ebp; }; // notice that from right to left push // 8(%esp) is to 4(%esp) is from // call means push eip and jmp, so (%esp) is eip void switch_to(struct context *from, struct context *to); .text .globl switch_to switch_to: # switch_to(from, to) # save from's registers movl 4(%esp), %eax # eax points to from popl 0(%eax) # save eip to from.eip movl %esp, 4(%eax) movl %ebx, 8(%eax) movl %ecx, 12(%eax) movl %edx, 16(%eax) movl %esi, 20(%eax) movl %edi, 24(%eax) movl %ebp, 28(%eax) # restore to's registers movl 4(%esp), %eax # not 8(%esp): popped return address already # eax now points to to movl 28(%eax), %ebp movl 24(%eax), %edi movl 20(%eax), %esi movl 16(%eax), %edx movl 12(%eax), %ecx movl 8(%eax), %ebx movl 4(%eax), %esp pushl 0(%eax) # push eip ret
The switch_to is likely to break the procedure of function call and return. Because a call means a push eip and then jmp, the to_switch use this to change the eip to return so as to change the process to run. Because there is nothing about the privilege, it will be fine to switch process like this.
kern_thread_entry:
Now %eip = kern_thread_entry and %ebx = thread1, %edx = arg, so the kern_thread_entry is used to enter the thread1. But why it bothers so much to design such a complex procedure, because if we just set the eip as thread1, we still can switch to function thread1. The reason for this is that it is a little complex to do some work after a thread has returned. So there is kernel_thread_entry to call *%ebx and the use do_exit to do the exit work.
.text .globl kernel_thread_entry kernel_thread_entry: # void kernel_thread(void) pushl %edx # push arg call *%ebx # call fn pushl %eax # save the return value of fn(arg) call do_exit # call do_exit to terminate current thread