Greetings.
I apologize for the fact that this post was delayed so much, but it was not possible to write earlier. In this issue, let's start talking about multitasking for our system.
First, let's solve one important question: what kind of multitasking will we implement? There is hardware, there is software multitasking ...
At first it seems that the hardware is better, because after all, Intel clearly tried to make this mechanism 'fly', but there are pitfalls. First, it is slower. How? Ask Intel engineers. Secondly, everyone has probably already read that with hardware multitasking we must use TSS (Task-State Segment), whose descriptors are stored in the GDT, which can accommodate ... (drum roll) ... 8192 descriptors. It may seem that this is enough, but on the server (yes, yes, we dream) this may not be enough. In principle, this is not important for us, but we will do it conscientiously - software multitasking.
In this issue, I propose to consider only the mechanism for switching tasks.
Now let's talk about what we need to do.
1) Come up with some kind of TSS replacement.
2) Decide how to implement address space for processes.
3) Consider switching tasks.
Let's implement preemptive multitasking, that is, we will do this: by the tick of the timer (which is triggered by default every 18.2 times per second), we will switch tasks. Instead of TSS, you can enter a structure in which the state of the process will be saved. The address space for each process is static (you need to start somewhere, right?). That is, roughly speaking, we take a piece of RAM and divide it into N equal parts.
Now you can begin to implement.
To begin, let's introduce a replacement TSS; Let it proudly called TSS_struct and looks like this:
TSS_struct:
0: privilage level (0|3)
1: ESP (Ring0)
5: CR3
9: EIP
13: EFLAGS
17: EAX
21: ECX
25: EDX
29: EBX
33: ESP (Ring3)
37: EBP
41: ESI
45: EDI
49: ES
51: CS
53: SS
55: DS
57: FS
59: GS
61: LDT_selector
63-256 - free
')
Now we will implement the function that we will call with each tick of the timer.
We need to keep the state of the old task, find the next task, load the new one. For marking TSS_struct'ov we will use 10 bytes, where each bit will denote 1 task. It is also worth considering 2 situations that will affect the stack:
1) The level of privilege does not change;
2) is changing;
In the first version, the stack will look like this:
;|EFLAGS
;|CS
;|EIP <---- ESP
;V
In the second so:
;|SS
;|ESP
;|EFLAGS
;|CS
;|EIP <---- ESP
;V
Note that only those registers that are simply necessary for the execution of an interrupt handler change their values.
In this issue, I propose to consider only Ring0. Ring (1 | 2 | 3) we have not yet considered, and the behavior there will be different, so we restrict ourselves.
Before we start writing the code, let me tell you where the slippery places are, where I personally stalled, and, often, for a long time.
1) The most elementary: incorrectly specify the return address, which we push into the stack.
2) Do not set the IF (Interrupt Flag) flag in EFLAGS. Ie, masked interrupts are prohibited, and you can forget about switching tasks.
3) It is important not to be mistaken in the search function for the next task. Despite the simplicity there can breed beetles. Personally, I drove her separately through Olga, so that the result was guaranteed to be correct.
4) If you decide not to jump from procedure to procedure, but to act calmly and calmly via call, do not forget about the stack! In general, the stack, in my opinion, is the most slippery place in this business.
Now consider the abbreviated context switching procedure. Why abbreviated? You need to show multitasking, so it will not write a lot of code here now. We have a demo.
Okay, let's start with the fact that we need to describe a new task. Since it is executed in Ring0, the stack and the value of all segment registers will be left alone. Just put the data to return. This is just a demonstration! It should not generally have the proud name create_task. Just put the values ​​for the loading procedure and set the bit in the bitmap of the busy TSS_structs. So:
create_task:
mov ax,20h; - .
mov es,ax
mov [es:100h+9],dword task;EIP
pushfd ;EFLAGS
pop eax
mov [es:100h+13],eax;EFLAGS
mov [es:100h+51],word 8h ;CS – Ring0,
bts word [bysi_TSS_map],6 ;
mov ax,10h;
mov es,ax
ret
The selector used here is 20h. I have an area for storing TSS_structs. Still. Why set the 6th bit? And the snag is. Code that is already being executed should also become .... challenge Therefore it is necessary to mark this 7th bit:
bts word [bysi_TSS_map],7
Now let's look at the procedures for saving and switching contexts, searching for a new task.
Everything is simple and easy here: we calculate the address of TSS_struct by the task number, look for a new one, read data from its TSS_struct and jump to the new task.
So, from the PIT handler, jump to the task switching procedure - task_switch:
task_switch:
mov [temp_1],eax
mov [temp_2],es
xor eax,eax
mov ax,20h
mov es,ax
call calculate_TSS_struct_address ;: EDI –
jmp store_context
Where are the storage variables:
temp_1 dd 0;EAX
temp_2 dw 0;ES
We will need to calculate the address in the future. So we issue in the form of procedures.
calculate_TSS_struct_address:
push eax
push ebx
mov eax,[cur_task_num]; dword' .
mov ebx,100h
mul ebx
pop ebx
mov edi,eax;EDI – TSS_struct
pop eax
ret
Now consider the process of saving the context. Banalism - on the bias we put the values. Everything.
store_context:
;mov eax,cr3 ;
;mov [es:edi+5],eax
pushfd
pop eax
mov [es:edi+13],eax;EFLAGS
mov [es:edi+21],ecx
mov [es:edi+25],edx
mov [es:edi+29],ebx
mov [es:edi+37],ebp
mov [es:edi+41],esi
mov [es:edi+45],edi
mov ax,[temp_2]
mov [es:edi+49],ax;ES
mov eax,[temp_1]
mov [es:edi+17],eax
mov [es:edi+53],ss
mov [es:edi+55],ds
mov [es:edi+57],fs
mov [es:edi+59],gs
;sldt ax ;
;mov [es:edi+61],ax
pop eax
mov [es:edi+9],eax;EIP
;
pop ax
mov [es:edi+51],ax;CS
popfd ;EFLAGS
jmp find_next_task
The next function searches for the next victim in the bitmap and returns its address to EDI.
find_next_task:
xor edx,edx
mov eax,[cur_task_num]
mov ecx,8
div ecx
;EAX -
;EDX - ''
test edx,edx
jnz .norm
mov edi,7
jmp .step
.norm: mov edi,8
.step: sub edi,edx;real bit #
mov edx,edi
mov edi,eax
.cycle:
bt word [bysi_TSS_map+edi],dx
jc .found
cmp dx,0
je .inc_byte
dec dx
jmp .cycle
.inc_byte:
cmp edi,10; - 800- .
je .error
inc edi
mov dx,7
jmp .cycle
.found:
push edx
mov eax,8
mul edi
pop edx
mov di,7
sub di,dx
add eax,edi
mov [cur_task_num],eax
call calculate_TSS_struct_address
jmp load_context
.error:
mov dx,7; .
xor edi,edi
jmp .cycle
Now there is only a loading context.
load_context:
;mov eax,[es:edi+5];CR3 -
;mov cr3,eax
;mov esp,[es:edi+1];
;mov ss,[es:edi+53]
mov ecx,[es:edi+21]
mov edx,[es:edi+25]
mov ebx,[es:edi+29]
mov ebp,[es:edi+37]
mov esi,[es:edi+41]
mov edi,[es:edi+45]
;mov ds,[es:edi+55]; .
;mov fs,[es:edi+57]
;mov gs,[es:edi+59]
; '' ( iretd)
push dword [es:edi+13];EFLAGS
push word [es:edi+51] ;CS
push dword [es:edi+9];EIP
jmp timer.s_t
Here it is worth paying attention. As we remember, when interrupting the stack (in this case, the utility one), 3 values ​​are stored (the privilege level does not change). Before the transition, we push the 'coordinates' of the new task onto the stack, transfer control to the PIT handler and do iretd. Are our torments over? Nearly. In the context of the new task, sti needs to be done to enable interrupts. That's all. And you were afraid!
The handler for the timer will look as follows:
timer:
;........
.s_t:
push ax ; EOI
mov al,20h
out 20h,al
out 0a0h,al
pop ax
iretd ;
Now we introduce the task.
Remember, before this, we have code ending up with jmp $?
Now you can put an increase in the character. Visually and quickly.
And the task that we mentioned before can be represented as
task:
sti
inc byte [gs:0]
jmp task
Now we collect all this together and admire the result.
So. Here is the introductory article and ended.
Implemented the similarity of multitasking. Tasks do not have their own stack, their segments and local descriptor tables ... There is still a lot of work to be done. Here are the topics for future releases. I apologize for my English ('bysi' = 'busy'; it's just a long time to fix it everywhere in the code).