So I recently started writing a scheduler for basic tasks. I haven't even switched to ring 3. I am just trying to run some basic tasks in kernel mode. Now here s my task structure:
Code: Select all
typedef struct task
{
uint64_t PID;
uint64_t entry;
uint64_t privilege;
uint64_t taskState;
char * name;
char * description;
task_registers_t state;
uint64_t kernel_stack;
uint64_t user_stack;
heap_t heap;
pml4_t * pml4;
struct task * next;
} __attribute__((packed)) task_t;
enum taskStates{
NOTRUN = 0,
RUNNING = 1,
WAITING = 2,
BLOCKED = 3,
DELETE = 4
};
Code: Select all
int task1main() // This is the random task
{
printk("Hello world");
exit(0); // This basically marks the task as DELETE (meaning we should delete it at one point) and just halts
}
void initTasking()
{
task_t *newTask = kalloc(¤tHeap, sizeof(task_t));
task_t *kernelTask = kalloc(¤tHeap, sizeof(task_t));
//printk("%x %x", newTask, kernelTask);
newTask->PID = 2;
newTask->entry = &task1main;
newTask->privilege = 0;
newTask->name = "TestTask";
newTask->taskState = NOTRUN;
newTask->description = "Test";
newTask->next = 0;
kernelTask->PID = 0;
kernelTask->entry = 0x10000;
kernelTask->privilege = 0;
kernelTask->name = "Kernel";
kernelTask->taskState = RUNNING;
kernelTask->description = "Kernel";
kernelTask->next = newTask;
currentTask = kernelTask;
taskQueue = kernelTask;
isTasking = 1;
}
Here's the scheduler:
Code: Select all
void schedule(task_registers_t *currentState)
{
if(isTasking == 1)
{
if( currentTask->taskState == RUNNING)
{
currentTask->taskState = WAITING; // make the current process waiting
memcpy(¤tTask->state, currentState, sizeof(task_registers_t)); // save the state
task_t* nextTask;
if(currentTask->next != 0)
nextTask = currentTask->next;
else
nextTask = taskQueue;
if(nextTask->taskState == NOTRUN) // if it has never been run before
{
nextTask->taskState = RUNNING; // lets run it
nextTask->state.ss = currentTask->state.ss;
nextTask->state.cs = currentTask->state.cs;
nextTask->state.rflags = currentTask->state.rflags;
nextTask->state.rsp = currentTask->state.rsp;
nextTask->state.rbp = currentTask->state.rbp;
nextTask->state.rip = nextTask->entry; // get the next tasks states rip to be its entry
memcpy(currentState, &nextTask->state, sizeof(task_registers_t)); // write it
//printk(" A process wants to be run : %s", nextTask->name);
currentTask = nextTask;
return;
}
else if(nextTask->taskState == WAITING) // if it is waiting to be run
{
nextTask->taskState = RUNNING; // lets run it
//printk(" A process wants to be running : %s %x %x ", nextTask->name, nextTask->state->rip, currentTask->state->rip);
memcpy(currentState, &nextTask->state, sizeof(task_registers_t)); // copy its state and put it into frame
//printRegisters(nextTask->state);
currentTask = nextTask;
return;
}
}
}
else
return;
}
Code: Select all
void irq0_handler(interrupt_frame_t* frame)
{
EnvironmentTick++;
//printRegisters((task_registers_t*)frame);
schedule((task_registers_t*)frame);
default_irq_handler(frame);
}
Here's what this code does: If the current task is running (which we assume it does) then first mark it WAITING and save the currents registers to the task's state field. Then let's check the next task. If it is 0, we are at the end of the linked list, so lets go back to the start of the queue. If it is not 0, then fetch that task. If the next task is NOTRUN, set its parameters to the parameters of the current task. This is required since if we don't supply a cs, ss, rsp and etc we get a GPF. Then set the registers to the next task's registers and return. The IRQ will pick up the values from the frame and put them to the locations necessary so no problem after this. If the task has been run atleast once and is WAITING then just load its state into frame. Finally set the currentTask as the nextTask so that we can switch to another task the next time.
Problems about this code:
1. If I don't put while(1) at the end of the task I am running I get invalid OPCode error, meaning the code jumps to random places.
2. If I, instead of printing one line, print a single letter in a loop and add a delay, I get alternating letters of output (which is expected), like for example if I say
while(1) {printk("b"); for(int i = 0; i<1000000; i++);} to kernel
and
while(1) {printk("c"); for(int i = 0; i<1000000; i++);} to the other task
I get alternating letters like bcbcbbcbcbcbbcbcbcbcbcbcbcbcbcbcb
which is expected considering the switch is successful. However after one point I suddenly get a page fault in RIP 0x7C which is interesting cause my code shouldn't be jumping to random places.
Questions about this code:
1. Is this a good way to go about scheduling?
2. Is there obvious bugs in the code that you see and I am too blind/ignorant to notice?
3. How am I "supposed" to exit the task and how am I supposed to kill it?
General questions about tasking:
1. How do I apply these to ring 3?
2. What is kernel stack per task? and why do we need that?
As far as I understood it is for storing "things" for a task while they are executing kernel code i.e. an interrupt or a system call.
I would be more than happy to post more code if you require it.
Thank you once more.