Before I named the data structure ‘Universal Data Structure’ shortly ‘UDS’, I think General is more properly instead of Universal.

So I changed UDS to GDS.

I implemented Context Switching and Task for multitasking that was not yet supported.

It doesn’t look massive. But It is task switching definitely.

Result

Before we define “Task” we need to check out about Context.

This Context, it was leaned OS in college.

It’s Process and Task’s executed status.

Context is just Register stored values and Stack.

Context Switching is literally switching, that would execute here and there.

The task is work that can be processed separately.

In Task, Code and Data can be shared but Stack and Context keep separation.

Latency time will be increased when you use timeslice instead of the batch process.

So I’ll use Timeslice multitasking.

Timeslice multitasking is context switching every millisecond by IRQ0 interrupt.

//shell.c
static TCB g_task[2] = {0.};
static QWORD test_stack[1024] = {0,};

void TaskTask()
{
    int iteration = 0;
    while(1)
    {
        _Printf("[%d] Message from test task Press any key to switching\n",iteration++);
        _GetCh();
        ContextSwitch(&g_task[1].Context, &g_task[0].Context);
    }

}

void Command_CreateTask(const char* _Parameter)
{
    KEYDATA key;
    InitTask(&g_task[1],1,0, TaskTask, test_stack, sizeof(test_stack));
    
    int iteration = 0;
    while(1)
    {
        _Printf("[%d] message from shell Press any key to switching\n", iteration++);
        if(_GetCh() =='q')
            break;
        ContextSwitch(&g_task[0].Context, &g_task[1].Context);  
        }

}

CreatTask function, it works for the creation of task and initialization.

Context Switching, switching context function.

Task Control Block, it is data-structure for context.

And Defining offsets and context.

I created Directory “Tasking”

Tasking/Task.h:

#ifndef __TASK_H__
#define __TASK_H__

#include <Types.h>

//SS, RSP, RFLAGS, CS, RIP..  Context
#define CONTEXT_REGISTER_COUNT     25
#define CONTEXT_REGISTER_SIZE       8

#define CONTEXT_OFFSET_GS           0
#define CONTEXT_OFFSET_FS           1
#define CONTEXT_OFFSET_ES           2
#define CONTEXT_OFFSET_DS           3
#define CONTEXT_OFFSET_R15          4
#define CONTEXT_OFFSET_R14          5
#define CONTEXT_OFFSET_R13          6
#define CONTEXT_OFFSET_R12          7
#define CONTEXT_OFFSET_R11          8
#define CONTEXT_OFFSET_R10          9
#define CONTEXT_OFFSET_R9           10
#define CONTEXT_OFFSET_R8           11
#define CONTEXT_OFFSET_RSI          12
#define CONTEXT_OFFSET_RDI          13
#define CONTEXT_OFFSET_RDX          14
#define CONTEXT_OFFSET_RCX          15
#define CONTEXT_OFFSET_RBX          16
#define CONTEXT_OFFSET_RAX          17
#define CONTEXT_OFFSET_RBP          18
#define CONTEXT_OFFSET_RIP          19
#define CONTEXT_OFFSET_CS           20
#define CONTEXT_OFFSET_RFLAG        21
#define CONTEXT_OFFSET_RSP          22
#define CONTEXT_OFFSET_SS           23

#pragma pack(push,1)
typedef struct __CONTEXT_STRUCT
{
    QWORD Registers[CONTEXT_REGISTER_COUNT];
} CONTEXT;

typedef struct __TCB_STRUCT
{
    CONTEXT Context;
    QWORD ID;
    QWORD Flags;

    void* StackAddress;
    QWORD StackSize;
} TCB;
#pragma pack(pop)

void InitTask(TCB* _Tcb, QWORD _ID, QWORD _Flags, QWORD _EntryPointAddress, void* _StackAddress, QWORD _StackSize);

//Link Assembly File
void ContextSwitch(CONTEXT* _CurrentContext, CONTEXT* _NextContext);
#endif /*__TASK_H__*/   

It has many definitions, but there are just index and registers.

It’ll be great for statically assigning value in the array.

And than Context (It is just register-values)

And TCB (It can be Task Control Block and Thread Control Block)

TCB contain Context, ID, Flags, Stack Address, and Stack size

Tasing/Task.c:


#include "Task.h"
#include <Utility/Memory.h>
#include <Descriptor/GDT.h>
void InitTask(TCB* _Tcb, QWORD _ID, QWORD _Flags, QWORD _EntryPointAddress, void* _StackAddress, QWORD _StackSize)
{
    _MemSet(_Tcb->Context.Registers, 0, sizeof(_Tcb->Context.Registers));
    
    //초기화 과정의 RSP, RBP 해당 Task의 Stack Pointer 기 떄문에 + Size 
    _Tcb->Context.Registers[CONTEXT_OFFSET_RSP] = (QWORD)_StackAddress + _StackSize;
    _Tcb->Context.Registers[CONTEXT_OFFSET_RBP] = (QWORD)_StackAddress + _StackSize;
    
    //Segment Register Setup in Context 
    _Tcb->Context.Registers[CONTEXT_OFFSET_CS] = GDT_KERNEL_CODE_SEGMENT;
    _Tcb->Context.Registers[CONTEXT_OFFSET_DS] = GDT_KERNEL_DATA_SEGMENT;
    _Tcb->Context.Registers[CONTEXT_OFFSET_ES] = GDT_KERNEL_DATA_SEGMENT;
    _Tcb->Context.Registers[CONTEXT_OFFSET_FS] = GDT_KERNEL_DATA_SEGMENT;
    _Tcb->Context.Registers[CONTEXT_OFFSET_GS] = GDT_KERNEL_DATA_SEGMENT;
    _Tcb->Context.Registers[CONTEXT_OFFSET_SS] = GDT_KERNEL_DATA_SEGMENT;
    
    //Next run instruction setup
    _Tcb->Context.Registers[CONTEXT_OFFSET_RIP] = _EntryPointAddress;

    // 0 NT  IOPL  OF DF IF TF SF ZF 0  AF 0  PF 1  CF
    // 0  0  0  0  0  0  1  0  0  0  0  0  0  0  0  0
    _Tcb->Context.Registers[CONTEXT_OFFSET_RFLAG] |= 0x0200;   
    //Setup TCB Block
    _Tcb->ID            = _ID;
    _Tcb->StackAddress  = _StackAddress;
    _Tcb->StackSize     = _StackSize;
    _Tcb->Flags         = _Flags;
 }

It is just function for the initialize of members.

Segment register kernel segment and initialize register’s initial values.

And next code of execution assigns in RIP register(PC, Program counter)’s EntryPoint.

And ContextSwitch function linked with assembly code.


[BITS 64]

global ContextSwitch

SECTION .text   

%macro SAVECONTEXT 0
    push rbp
    push rax
    push rbx
    push rcx
    push rdx
    push rdi
    push rsi
    push r8
    push r9
    push r10
    push r11
    push r12
    push r13
    push r14
    push r15

    mov ax, ds
    push rax
    mov ax, es
    push rax
    push fs
    push gs

    mov ax, 0x10
    mov ds,ax
    mov es,ax
    mov gs,ax
    mov fs,ax

%endmacro


%macro LOADCONTEXT 0
    pop gs
    pop fs
    pop rax
    mov es, ax
    pop rax
    mov ds, ax

    pop r15
    pop r14
    pop r13
    pop r12
    pop r11
    pop r10
    pop r9
    pop r8
    pop rsi
    pop rdi
    pop rdx
    pop rcx
    pop rbx
    pop rax
    pop rbp

%endmacro

ContextSwitch:
    push rbp
    mov rbp, rsp
    ;Push RFLAGS in stack
    pushfq
    ;if _CurrentContext is NULL
    cmp rdi,0
    je .LoadContext
    popfq
    push rax    ;For use Context Offset 

    ;Save SS RSP RFLAGS CS RIP Registers
    mov ax, ss
    mov qword[rdi + 23 * 8], rax
    mov rax, rbp

    ;reset value to before, RSP Contextswitch  -(ReturnAddress + RBP)
    add rax, 16
    mov qword[rdi + 22 * 8], rax

    ;Push FLAGS in stack
    pushfq
    pop rax
    mov qword[rdi + 21 * 8], rax
    
    ;Save Cs Segment
    mov ax, cs
    mov qword[rdi + 20 * 8], rax

    mov rax, qword[rbp + 8]
    mov qword[rdi + 19 * 8], rax

    pop rax
    pop rbp
    ;Context->Registers
    add rdi, 19*8
    mov rsp, rdi
    sub rdi, 19*8

    SAVECONTEXT

.LoadContext:
    mov rsp, rsi
    LOADCONTEXT
    iretq

If the first parameter Null(0) it is ShellTask.

Jump to .LoadContext

If working like that, We will context load by the second parameter.

If not, it would save context.

And then load by the second parameter.

So It’s done of context-switching.

Huh.