What is the problem with this code? Tasks and scheduling
Posted: Wed Apr 28, 2021 1:32 pm
This is not a very good post and I am aware of that but I couldn't think why there would be a problem with this and how I may improve this. I also have some questions about tasking in general. Thank you in advance.
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:
I have a lot of fields but most are useless at this point. I will probably use all at one point so I added a bunch. Now I define 2 tasks. One is for the kernel and the other is a random task.
Here's what I thought about initialization. I will do all scheduling in IRQ so I already have the state (registers) of the current running task. So I define the kernel task and set appropriate values. I set it as RUNNING as we are already in the kernel, meaning all the registers need to be saved and reloaded later. I set the other task as NOTRUN, meaning we never run it, meaning all of its state registers are 0 including RIP.
Here's the scheduler:
We run this in IRQ 0 handler like this:
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.
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.