Hi,
I have multiple kernel tasks (or process, as you want) and I am wondering how to switch between them.
When we switch from different ring-level tasks we can use iret and put ss and esp on the stack then use iret.
But if there is no level change (like timer interrupt -> kernel task) these parameters are ignored.
I would like to do for instance :
push myNextCS
push myNextEIP
mov ss, nextSS
mov esp, nextESP
Retf
But as you might guess, editing esp and ss will make the last pushes innefective for my retf.
Also, as there is no ring level change editing the TSS will do nothing.
Am I missing something ?
Context switch for kernel tasks
Re: Context switch for kernel tasks
My code for loading a task differs between operation modes (V86, protected mode, long mode) and is also different based on which ring the thread should be resumed to. I save the register state (including ss:esp & cs:eip) in the thread block, and based on this I can select the proper method to resume the thread.
Something like this:
Something like this:
Code: Select all
test dword ptr ds:p_rflags,20000h
jnz load_vm
;
test ds:p_cs,3
jnz load_pm_app
load_kernel:
mov ax,ds:p_ss
mov ss,ax
mov esp,dword ptr ds:p_rsp
...
load_pm_app:
mov ax,ds:p_kernel_ss
mov ss,ax
mov esp,ds:p_kernel_esp
...
load_vm:
mov ax,ds:p_kernel_ss
mov ss,ax
mov esp,ds:p_kernel_esp
...
Last edited by rdos on Fri Sep 10, 2021 6:32 am, edited 1 time in total.
Re: Context switch for kernel tasks
So in protected mode, how do you switch between your ring 0 tasks ? 0->0
Re: Context switch for kernel tasks
Thank you for this code snippet, I see how to update your stack but how do you actually jmp to the new code segment and eip ?
Re: Context switch for kernel tasks
Here is the full load code:
Code: Select all
test dword ptr ds:p_rflags,20000h
jnz load_vm
;
test ds:p_cs,3
jnz load_pm_app
load_kernel:
mov ax,ds:p_ss
mov ss,ax
mov esp,dword ptr ds:p_rsp
;
xor ax,ax
push dword ptr ds:p_rflags
push ax
push ds:p_cs
push dword ptr ds:p_rip
;
mov ecx,dword ptr ds:p_rcx
mov edx,dword ptr ds:p_rdx
mov ebx,dword ptr ds:p_rbx
mov ebp,dword ptr ds:p_rbp
mov esi,dword ptr ds:p_rsi
mov edi,dword ptr ds:p_rdi
;
mov ax,ds:p_es
verr ax
jz load_kernel_es
;
xor ax,ax
load_kernel_es:
mov es,ax
;
mov ax,ds:p_fs
verr ax
jz load_kernel_fs
;
xor ax,ax
load_kernel_fs:
mov fs,ax
;
mov ax,ds:p_gs
verr ax
jz load_kernel_gs
;
xor ax,ax
load_kernel_gs:
mov gs,ax
;
mov ax,ds:p_ds
verr ax
jz load_kernel_ds
;
xor ax,ax
load_kernel_ds:
push ax
mov eax,dword ptr ds:p_rax
pop ds
iretd
load_pm_app:
mov ax,ds:p_kernel_ss
mov ss,ax
mov esp,ds:p_kernel_esp
;
xor ax,ax
push ax
push ds:p_ss
push dword ptr ds:p_rsp
push dword ptr ds:p_rflags
push ax
push ds:p_cs
push dword ptr ds:p_rip
;
mov ecx,dword ptr ds:p_rcx
mov edx,dword ptr ds:p_rdx
mov ebx,dword ptr ds:p_rbx
mov ebp,dword ptr ds:p_rbp
mov esi,dword ptr ds:p_rsi
mov edi,dword ptr ds:p_rdi
;
mov ax,ds:p_es
verr ax
jz load_pm_app_es
;
xor ax,ax
load_pm_app_es:
mov es,ax
;
mov ax,ds:p_fs
verr ax
jz load_pm_app_fs
;
xor ax,ax
load_pm_app_fs:
mov fs,ax
;
mov ax,ds:p_gs
verr ax
jz load_pm_app_gs
;
xor ax,ax
load_pm_app_gs:
mov gs,ax
;
mov ax,ds:p_ds
verr ax
jz load_pm_app_ds
;
xor ax,ax
load_pm_app_ds:
push ax
mov eax,dword ptr ds:p_rax
pop ds
iretd
load_vm:
mov ax,ds:p_kernel_ss
mov ss,ax
mov esp,ds:p_kernel_esp
;
xor ax,ax
push ax
push ds:p_gs
push ax
push ds:p_fs
push ax
push ds:p_ds
push ax
push ds:p_es
push ax
push ds:p_ss
push dword ptr ds:p_rsp
push dword ptr ds:p_rflags
push ax
push ds:p_cs
push dword ptr ds:p_rip
;
mov eax,dword ptr ds:p_rax
mov ecx,dword ptr ds:p_rcx
mov edx,dword ptr ds:p_rdx
mov ebx,dword ptr ds:p_rbx
mov ebp,dword ptr ds:p_rbp
mov esi,dword ptr ds:p_rsi
mov edi,dword ptr ds:p_rdi
iretd
Re: Context switch for kernel tasks
The save code looks like this:
Code: Select all
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;
; NAME: SaveCurrentThread
;
; DESCRIPTION: Save state of current thread
;
; PARAMETERS: Stack, return IP
;
; RETURNS: SS:SP Core stack
; FS Core selector
; ES, GS Clear
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SaveCurrentThread Proc near
push fs
push ds
push eax
push edx
;
call LockCore
sti
;
GetSystemTime
mov ds,fs:cs_curr_thread
sub eax,fs:cs_last_lsb
add ds:p_lsb_tics,eax
adc ds:p_msb_tics,0
add fs:cs_lsb_tics,eax
adc fs:cs_msb_tics,0
;
pushfd
pop eax
or ax,200h
mov dword ptr ds:p_rflags,eax
;
pushf
pop ax
and ax,NOT 100h
push ax
popf
;
mov dword ptr ds:p_rcx,ecx
mov dword ptr ds:p_rbx,ebx
mov dword ptr ds:p_rbp,ebp
mov dword ptr ds:p_rsi,esi
mov dword ptr ds:p_rdi,edi
mov ds:p_es,es
mov ds:p_cs,cs
mov ds:p_ss,ss
mov ds:p_gs,gs
;
pop dword ptr ds:p_rdx
pop eax
mov dword ptr ds:p_rax,eax
;
pop ds:p_ds
pop ds:p_fs
pop bp
pop dx
movzx edx,dx
mov dword ptr ds:p_rip,edx
mov dword ptr ds:p_rsp,esp
;
lss esp,fword ptr fs:cs_stack_offset
call cs:fpu_save_proc
mov edx,dword ptr ds:p_rdx
push bp
;
xor bp,bp
mov ds,bp
mov es,bp
mov gs,bp
ret
SaveCurrentThread Endp
-
- Member
- Posts: 5563
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Context switch for kernel tasks
The typical OS design looks like this example on the wiki. Switching tasks is done by switching kernel stacks. If the task is ring 0, the new stack will contain a return value that will pick up the ring 0 thread where it left off. If the new task is ring 3, the new stack will contain a return value that will pick up in ring 0 right before the appropriate code to return to ring 3.
You may also want to look at this example on the wiki.
You may also want to look at this example on the wiki.
Re: Context switch for kernel tasks
That's certainly possible. That's typically how my design works too when the SaveThread function is used, which is the normal case when a thread is blocked.Octocontrabass wrote:The typical OS design looks like this example on the wiki. Switching tasks is done by switching kernel stacks. If the task is ring 0, the new stack will contain a return value that will pick up the ring 0 thread where it left off. If the new task is ring 3, the new stack will contain a return value that will pick up in ring 0 right before the appropriate code to return to ring 3.
You may also want to look at this example on the wiki.
However, the kernel debugging interface will save the register state rather than just block. So, if you single step some code, the trap gate will save the register state in the thread block, and if the thread switches to the application, it will save application state rather than kernel state. That's when the load of application context is used. The kernel debugger uses this so it can trace threads in kernel or in the application by simply using the thread block. The user can also directly modify register context before doing a trace. This interface can also trace V86 code when accessing the BIOS or running DOS applications.
The application debugger works in a similar way, although it must use the application interrupts & exception structure the ABI defines. Still, the application debugger will read register state for a debugged thread from the thread control block and not from some other memory area or the stack. This design is also why the application debugger can seemlessly trace code into kernel space and show the correct register state & source.
Another advantage of this design is that application threads can be created directly using application register state and then start their execution in user space not in kernel space.
When a thread gets an exception it cannot handle, the register state is saved in the exception handler and the thread is moved to a special "debug" list. When a debugged thread is restarted it is moved to the ready queue.
This is how the code to save the register state looks like when called from an execption: (ebp will point to some registers stored by the exception handler, including eflags, ss:esp and cs:eip, ds, ebp, eax & ebx)
Code: Select all
push fs
mov al,[ebp].trap_exc_nr
mov ds:p_fault_vector,al
mov eax,[ebp].trap_err
mov dword ptr ds:p_fault_code,eax
mov dword ptr ds:p_fault_code+4,0
;
mov eax,[ebp].trap_eax
mov dword ptr ds:p_rax,eax
mov eax,[ebp].trap_ebx
mov dword ptr ds:p_rbx,eax
mov dword ptr ds:p_rcx,ecx
mov dword ptr ds:p_rdx,edx
mov dword ptr ds:p_rsi,esi
mov dword ptr ds:p_rdi,edi
mov eax,[ebp].trap_ebp
mov dword ptr ds:p_rbp,eax
;
mov eax,[ebp].trap_eflags
mov dword ptr ds:p_rflags,eax
mov ax,[ebp].trap_cs
mov ds:p_cs,ax
mov eax,[ebp].trap_eip
mov dword ptr ds:p_rip,eax
;
pop si
test dword ptr [ebp].trap_eflags,20000h
jnz debug_vm
debug_pm:
mov al,[ebp].trap_cs
test al,3
jz debug_kernel
;
mov ax,[ebp].trap_ss
mov ds:p_ss,ax
mov eax,[ebp].trap_esp
mov dword ptr ds:p_rsp,eax
jmp debug_pm_common
debug_kernel:
mov ax,ss
mov ds:p_ss,ax
mov eax,ebp
add eax,trap_esp
mov dword ptr ds:p_rsp,eax
debug_pm_common:
mov ax,[ebp].trap_pds
mov ds:p_ds,ax
mov ax,es
mov ds:p_es,ax
mov ds:p_fs,si
mov ax,gs
mov ds:p_gs,ax
jmp debug_save_ok
debug_vm:
mov ax,[ebp].trap_gs
mov ds:p_gs,ax
mov ax,[ebp].trap_fs
mov ds:p_fs,ax
mov ax,[ebp].trap_ds
mov ds:p_ds,ax
mov ax,[ebp].trap_es
mov ds:p_es,ax
mov ax,[ebp].trap_ss
mov ds:p_ss,ax
mov eax,[ebp].trap_esp
mov dword ptr ds:p_rsp,eax
debug_save_ok: