Page 1 of 1

Help in Tasking

Posted: Wed Feb 10, 2016 8:46 am
by ashishkumar4
I need some help in tasking stuff :/ because simply I don't understand a lot of stuff, neither cant I find it on the net :/
these are:
1. How to get the EIP of the STACK before switching the stack so that I can save it for the next cycle (scheduling)
2. How to actually Do CONTEXT switching because I tried these: Changing the Stack as well as moving the EIP to somewhere else (Because I don't know how to save the EIP location, what should I do :/ )
Also I tried initializing the STACK of every process like this :

Code: Select all

  stack=kmalloc_a(4096)+4096;  // ugh.. hope this works..
  memset((void*)stack-4096,0,4096);
  NewTask->eip=func;

  	// processor data
  	*--stack = 0x202;	// EFLAGS
  	*--stack = 0x08;	// CS
  	*--stack = func;	// EIP
    NewTask->eip=stack;

    *--stack = 0;		//
    *--stack = 0;
  	// pusha
  	*--stack = 0;		// EDI
  	*--stack = 0;		// ESI
  	*--stack = 0;		// EBP
  	*--stack = 0;		// NULL
  	*--stack = 0;		// EBX
  	*--stack = 0;		// EDX
  	*--stack = 0;		// ECX
  	*--stack = 0;		// EAX

  	// data segments
  	*--stack = 0;	// DS
  	*--stack = 0;	// ES
  	*--stack = 0;	// FS
  	*--stack = 0;	// GS*/

  NewTask->esp = stack; // Just initialize the stack :D
And just saving the old stack and loading the new one by:
asm volatile("mov %%esp,%0;" ::"r"(old_task->esp));
asm volatile("mov %0,%%esp;" : "=r"(current_task->esp));

But this dosent make any effect :/ I have but the task switching function in a IRQ timer and this function just does the above thing + switching between current_task .

Help me please in implementing multitasking ( A simple multitasking, nothing else).

Re: Help in Tasking

Posted: Wed Feb 10, 2016 9:52 am
by SpyderTL
You are on the right track.

You should not need to keep track of the EIP register, because it is pushed onto the stack when your IRQ 0 handler is called. That is how IRET knows where to resume processing when your IRQ handler is finished.

So the EIP that you want to return to is already on the stack. When you swap your current ESP with the new ESP, the "saved" EIP should be sitting at the top of the stack, ready for you to call IRET and "resume" that thread.

All you need to keep track of is the list of thread ESP values, so that you can find the "next" thread stack to load.

If you are clever, you can actually store this information on the stack, as well, but I'm not sure I would recommend going that route. I tried it and it was fairly difficult keeping everything up-to-date.

Hopefully this helps. Let us know if you have any other questions.

EDIT: When you say "But this dosent make any effect :/", do you mean that only one thread is running?

EDIT 2: What is the purpose of the two lines directly above the "// pusha" line? I don't have these two lines in my code, I don't think. But I'm using PUSHA, in assembly.

Re: Help in Tasking

Posted: Wed Feb 10, 2016 10:01 am
by Combuster
1: The code you showed only makes sense in the context of creating a thread, not suspending or resuming a thread. At least two-thirds of the code is still missing.
2: You don't need to save EIP manually. (hint: where is it?)
3: Don't touch ESP within a C function, because by the time you get there you never know what evil the compiler has done to your stack.

Re: Help in Tasking

Posted: Wed Feb 10, 2016 11:17 am
by ashishkumar4
Thanks Spyder and combuster
Lol spyeder I think that was one of the mistakes why my code wasn't working. Not its working and threads are running
BUT there is another problem now :/
I have made this task linked list's last node to point to the first node so that whenever switch_task function is called, which increments the current node and saves the last esp, and the cycle reaches to the last task, it again goes to the first task and so on.
But this thing only works for one cycle that is, My tasking starts from task1, completes a part of it, then goes to task2, then task 3 and then task4 which is the last task. now till here, everything was fine. But from here, it should go to task1 again which it isn't able to :/ I saved all the things as it should be. Here is some modified code:

Code: Select all


  NewTask->esp=kmalloc_a(4096);  // ugh.. hope this works..
  memset((void*)NewTask->esp,0,4096);
  NewTask->StackTop=NewTask->esp;
  stack=base=NewTask->esp+4096;
  *--stack = 0x202; // eflags
	*--stack = 0x8; // cs
	*--stack = (uint32_t)func; // eip
	*--stack = 0; // eax
	*--stack = 0; // ebx
	*--stack = 0; // ecx;
	*--stack = 0; //edx
	*--stack = 0; //esi
	*--stack = 0; //edi
	*--stack = base; //ebp
	*--stack = 0x10; // ds
	*--stack = 0x10; // fs
	*--stack = 0x10; // es
	*--stack = 0x10; // gs
  NewTask->ss=0x10;
  NewTask->esp = stack; // Just initialize the stack :D
and the switch task function:

Code: Select all


void switch_task()
{
    //printf("\nSwitching Task\n");
    asm volatile("cli");
    task_t* old_task;
    old_task=current_task;
    current_task=current_task->next;
    if (!current_task)
    {
      asm volatile("sti");
        return; 
      }
    //printf("\n Getting CPU State");	
       asm volatile("push %eax");
    ///*
	  asm volatile("push %ebx");
  	asm volatile("push %ecx");
  	asm volatile("push %edx");
  	asm volatile("push %esi");
  	asm volatile("push %edi");
  	asm volatile("push %ebp");
  	asm volatile("push %ds");
  	asm volatile("push %es");
  	asm volatile("push %fs");
  	asm volatile("push %gs");
  	asm volatile("mov %%esp, %%eax":"=a"(old_task->esp));
//*/
  	asm volatile("mov %%eax, %%esp": :"a"(current_task->esp));
  	asm volatile("pop %gs");
  	asm volatile("pop %fs");
  	asm volatile("pop %es");
  	asm volatile("pop %ds");
  	asm volatile("pop %ebp");
  	asm volatile("pop %edi");
  	asm volatile("pop %esi");
  	//asm volatile("out %%al, %%dx": :"d"(0x20), "a"(0x20)); // send EoI to master PIC
  	asm volatile("pop %edx");
  	asm volatile("pop %ecx");
  	asm volatile("pop %ebx");
  	asm volatile("pop %eax");
  	asm volatile("iret");
}
I think the stack is left in some awkward state while switching tasks :/
and Combuster sir, it would be a big thing for me even if I can make this thing work, resuming threads is a later thing for me.
And by that line did you mean to use assembly instead of inline assembly in my code above :p lol ok :p
Help me :/

Re: Help in Tasking

Posted: Wed Feb 10, 2016 8:50 pm
by tsdnz
Hi, I am in 64 bit mode long, QWORDS/UINT64 only for me, but I hope this helps you out.

I have several methods for switching processors in my OS.
One being an Interrupt.

I have built a custom Interrupt handler for each CPU, this builds the Interrupt structure I require so it is always the same and I can modify it as I wish. Code Below.
Only showing a little of what it does, but this should be complete enough for this reply.

Code: Select all

	FIL void tCPU::WriteInterruptCode(BYTE Interrupt, InterruptHandlerProc* HandlerAddress, QWORD AttachConditions)
	{
		BYTE* b = this->Interrupt[Interrupt].Code;

		// cli - Disable Interrupts
		*b = 0xfa; b += sizeof(BYTE);

		if ((AttachConditions & tAttachConditions::acPushGPRsPassStackAddressCallHandler) == tAttachConditions::acPushGPRsPassStackAddressCallHandler)
		{
			// push rax
			*b = 0x50; b += sizeof(BYTE);

			// push rbx
			*b = 0x53; b += sizeof(BYTE);

			// push rcx
			*b = 0x51; b += sizeof(BYTE);

			// push rdx
			*b = 0x52; b += sizeof(BYTE);

			// push rsi
			*b = 0x56; b += sizeof(BYTE);

			// push rdi
			*b = 0x57; b += sizeof(BYTE);

			// push rbp
			*b = 0x55; b += sizeof(BYTE);

			// push r8
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x50; b += sizeof(BYTE);

			// push r9
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x51; b += sizeof(BYTE);

			// push r10
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x52; b += sizeof(BYTE);

			// push r11
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x53; b += sizeof(BYTE);

			// push r12
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x54; b += sizeof(BYTE);

			// push r13
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x55; b += sizeof(BYTE);

			// push r14
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x56; b += sizeof(BYTE);

			// push r15
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x57; b += sizeof(BYTE);

			// movabs rax, HandlerAddress
			*b = 0x48; b += sizeof(BYTE);
			*b = 0xB8; b += sizeof(BYTE);
			*(QWORD*)b = (QWORD)HandlerAddress; b += sizeof(QWORD);

			// mov rdi, rsp
			*b = 0x48; b += sizeof(BYTE);
			*b = 0x89; b += sizeof(BYTE);
			*b = 0xe7; b += sizeof(BYTE);

			// call rax
			*b = 0xFF; b += sizeof(BYTE);
			*b = 0xD0; b += sizeof(BYTE);

			// movabs rax, LocalAPICAddress + 0xB0
			*b = 0x48; b += sizeof(BYTE);
			*b = 0xB8; b += sizeof(BYTE);
			*(QWORD*)b = (QWORD)LocalAPICAddress + 0xB0; b += sizeof(QWORD);

			// pop r15
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x5f; b += sizeof(BYTE);

			// pop r14
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x5e; b += sizeof(BYTE);

			// pop r13
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x5d; b += sizeof(BYTE);

			// pop r12
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x5c; b += sizeof(BYTE);

			// pop r11
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x5b; b += sizeof(BYTE);

			// pop r10
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x5a; b += sizeof(BYTE);

			// pop r9
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x59; b += sizeof(BYTE);

			// pop r8
			*b = 0x41; b += sizeof(BYTE);
			*b = 0x58; b += sizeof(BYTE);

			// pop rbp
			*b = 0x5d; b += sizeof(BYTE);

			// pop rdi
			*b = 0x5f; b += sizeof(BYTE);

			// mov DWORD PTR [rax], 0x0
			*b = 0xc7; b += sizeof(BYTE);
			*b = 0x00; b += sizeof(BYTE);
			*(DWORD*)b = 0; b += sizeof(DWORD);

			// pop rsi
			*b = 0x5e; b += sizeof(BYTE);

			// pop rdx
			*b = 0x5a; b += sizeof(BYTE);

			// pop rcx
			*b = 0x59; b += sizeof(BYTE);

			// pop rbx
			*b = 0x5B; b += sizeof(BYTE);

			// pop rax
			*b = 0x58; b += sizeof(BYTE);

			// iretq
			*b = 0x48; b += sizeof(BYTE);
			*b = 0xCF;

			return;
		}
... Some of my other stuff, which is not needed to be shown here
This is called like this.

Code: Select all

FIL void tCPU::AttachInterrupt(BYTE Interrupt, const char* InterruptName, InterruptHandlerProc* HandlerAddress, QWORD AttachConditions)
	{
		this->Interrupt[Interrupt].InterruptName = InterruptName;
		WriteInterruptCode(Interrupt, HandlerAddress, AttachConditions);
		ChangeInterruptPointerAddress(Interrupt);
	}
, and you will need this code for complete-ness.

Code: Select all

	FIL void tCPU::ChangeInterruptPointerAddress_Direct(BYTE Interrupt, BYTE* Address)
	{
		QWORD IDT = (QWORD)&this->IDTE[Interrupt];
		*(volatile  WORD*)(IDT + 0) = ((QWORD)Address >> 0)  & 0xFFFF;
		*(volatile  WORD*)(IDT + 6) = ((QWORD)Address >> 16) & 0xFFFF;
		*(volatile DWORD*)(IDT + 8) = ((QWORD)Address >> 32) & 0xFFFFFFFF;
	}
	FIL void tCPU::ChangeInterruptPointerAddress(BYTE Interrupt)
	{
		ChangeInterruptPointerAddress_Direct(Interrupt, this->Interrupt[Interrupt].Code);
	}
So... what does the above do....
When an Interrupt happens it calls the code created by "WriteInterruptCode", IDT is setup with "ChangeInterruptPointerAddress_Direct". Note Interrupts are disabled before I call these, Not shown!
The inserted code from the function "WriteInterruptCode" pushes all the registers on the stack and sets up RDI = RSP.
This code then calls my c++ code, which looks like this.

Code: Select all

void Kernel::ProcessAPICTimer_CPU0()  { register QWORD* InterruptPushStack asm("rdi");  ABIT.System.OS_GenericOS.CPU[ 0]->ProcessAPICTimer(InterruptPushStack, ABIT.CPUs.CPU[ 0]); }
Basically rdi is passed to this function as parameter and passed to my master function.
Using this method I do not need to find out what CPU was interrupted and I know where the stack-top was with all the registers.
So I can use a structure like this.

Code: Select all

	union Packed() tGPR
	{
		struct Packed()
		{
			QWORD	gpr[20];
		};

		struct Packed()
		{
			QWORD	r15;
			QWORD	r14;
			QWORD	r13;
			QWORD	r12;
			QWORD	r11;
			QWORD	r10;
			QWORD	r9;
			QWORD	r8;
			QWORD	rbp;
			QWORD	rdi;
			QWORD	rsi;
			QWORD	rdx;
			QWORD	rcx;
			QWORD	rbx;
			QWORD	rax;
			QWORD	rip;
			QWORD	CS;
			QWORD	RFLAGS;
			QWORD	rsp;
			QWORD	SS;
		};
	};
I am then free to modify the registers, copy the registers, read the registers, save the RSP, or what ever I ike to do.
One example is I check to make sure an interrupt was called from user space by doing this.

Code: Select all

// Only call if interrupted from Userspace, check RIP
	if (*(InterruptPushStack + 15) >= Util.GetVirtualAddress_4k(Userspace_Start_PML4E, 0, 0, 0))
	{
		__SYSCALL_CheckAccountFromInterrupt(InterruptPushStack, CPU, (Kernel::tSystem::tOS_GenericOS::tCPU::tIPAccounts::tAccount*)CPU->Generic_CurrentAccountWithSomethingToDo);
	}
.
I could have casted, but I used "InterruptPushStack + 15" instead.

It really is endless once you get into it, good luck..

Re: Help in Tasking

Posted: Wed Feb 10, 2016 9:30 pm
by gerryg400
Why didn't you use an assembler to write your interrupt code ?

Re: Help in Tasking

Posted: Wed Feb 10, 2016 10:41 pm
by tsdnz
gerryg400 wrote:Why didn't you use an assembler to write your interrupt code ?
Good question. Main reason is I change HandlerAddresses.

Code: Select all

// movabs rax, HandlerAddress
         *b = 0x48; b += sizeof(BYTE);
         *b = 0xB8; b += sizeof(BYTE);
         *(QWORD*)b = (QWORD)HandlerAddress; b += sizeof(QWORD);

Re: Help in Tasking

Posted: Wed Feb 10, 2016 11:13 pm
by neon
Hello,
I think the stack is left in some awkward state while switching tasks
You are declaring local variables in switch_task which will be placed on the stack right after the cpu context. So when you save and restore it, the return cs:eip will not be what you expect it to be. Don't use local variables unless you compensate for it - you are issuing iret before removing any local variables from the stack.

In addition...
1. You are mixing up ret and iret. Don't use return in interrupt handlers. Also, send EOI (see note [2]).
2. EOI commands should be left to the drivers that actually serve the hardware interrupts.
3. Interrupt chaining should be used to allow other drivers a chance to run.
4. Make sure compiler optimizations are not causing problems with the expected code or use assembly language instead.
5. Don't allocate kernel stacks on the heap. Its fine for now, but you should consider better options later.

Re: Help in Tasking

Posted: Thu Feb 11, 2016 12:02 am
by Brendan
Hi,
tsdnz wrote:Hi, I am in 64 bit mode long, QWORDS/UINT64 only for me, but I hope this helps you out.
A) That's hideously bad/unmaintainable

B) For IRQs the "HandlerAddress" should be the same for all IRQs; and it's better for to have a common assembly stub that calls a generic (C, C++) IRQ handler that handles things like how many completely different/unrelated device drivers happen to be sharing that IRQ

C) For all other types of interrupts it's better to have a specific assembly stub for each thing and not have a generic assembly stub (unless you're writing a tutorial and don't want to complicate it by doing things right)

D) Interrupts never have anything to do with task switching in the first place (unless the kernel/scheduler is a massive design failure).


Cheers,

Brendan

Re: Help in Tasking

Posted: Thu Feb 11, 2016 12:10 am
by Brendan
Hi,
ashishkumar4 wrote:and the switch task function:
Never, under any circumstances, do anything in inline assembly that touches or modifies the stack, or relies on any specific stack layout. The stack belongs to the compiler and it will do whatever it likes with its stack; it is not yours to mess with, you gave up the right to touch the stack when you chose to use a compiler.

You must use external assembly and not inline assembly for the (tiny) piece of code that does the final task switch.


Cheers,

Brendan

Re: Help in Tasking

Posted: Thu Feb 11, 2016 1:45 am
by tsdnz
ashishkumar4 wrote:and the switch task function:
Is your interrupt stack the same as the user-space stack?
If it is you have a possibility that your users code could push the stack to the bottom.
Then your interrupt is run.
And your interrupt does a push, and boom, no stack space left. Kernel crashes.

Alistair

Re: Help in Tasking

Posted: Thu Feb 11, 2016 3:50 am
by ashishkumar4
Lol so many replies. Ok let me consider them one by one, removing all the local variables and things that can affect the stack
and also trying to switch to assembly language instead of inline asm.
But I have a doubt now because as I don't trust myself much :p can someone please help me make a task switch function in assembly that can take the stack address of a task and save the old stack of the old task before switching and switch the state based on the stack address it took.
means I want a replacement for my switch_task() func in assembly :/ please help me because I have already failed in this terribly several times :/

Re: Help in Tasking

Posted: Thu Feb 11, 2016 4:56 am
by Brendan
Hi,
ashishkumar4 wrote:But I have a doubt now because as I don't trust myself much :p can someone please help me make a task switch function in assembly that can take the stack address of a task and save the old stack of the old task before switching and switch the state based on the stack address it took.
means I want a replacement for my switch_task() func in assembly :/ please help me because I have already failed in this terribly several times :/
Start by writing a "switch_to_task(taskID)" function in assembly (not inline assembly). This code needs to match the calling conventions you're using. You'll notice that for all calling conventions some registers are preserved and some aren't. You only need to save the registers that are preserved (the caller will save the rest before calling your code). Psuedo code goes like this:

Code: Select all

switch_to_task:
    push preserved_register1
    push preserved_register2
    push preserved_register3
    push preserved_register4
    set previous task's "thread data block, kernel stack top" field to whatever the stack top is now
    load stack from new task's "thread data block, kernel stack top" field
    pop preserved_register4
    pop preserved_register3
    pop preserved_register2
    pop preserved_register1
    ret
Note: This is just initial code to start with. Later you might want to do a little more; like loading CR3.

Once you've implemented this; write code to create a kernel thread; and use it to spawn a second kernel thread. Test your code by making one kernel thread switch directly to the other, and by making the other kernel thread switch directly back.

When that's done and works fine; write a "go_to_task(taskID)" wrapper in C or C++ if you like. This allows you to add more functionality in C later (e.g. code to keep track of the time each task has consumed, etc).

Next implement a "do_task_switch(void)" function. This would search for a task to switch to and then call "go_to_task(taskID)". Initially just implement a round robin scheduler because it's easy. You can replace it with something good later.

Once that's done modify your kernel threads so they both just call "do_task_switch()".

Next, implement a "block_task(reason)" function. This would remove the task from the scheduler's list of tasks and call "go_to_task(taskID)".

Next, implement an "unblock_task(taskID)" function. This would add the task to the scheduler's list of tasks; then check if the unblocked task is higher priority than the currently running task. If the task that was unblocked is lower priority you do nothing. If the task that was unblocked is higher priority you call "go_to_task(taskID)" to switch immediately to the higher priority task (without the unnecessary overhead of "do_task_switch(void)").

Now go back and replace "do_task_switch(void)" with something good (e.g. that understands task priorities, etc).

Finally (depending on what sort of scheduler you implemented); when there's 2 or more tasks at the same priority (and not when there's only one task at that priority) and you're switching to one of those tasks, make it so that "do_task_switch(void)" sets a time-out, and "block_task()" cancels the time-out. If the time out expires before it's cancelled (e.g. its a CPU bound task that gobbled too much time without blocking) you call "do_task_switch()".


Cheers,

Brendan

Re: Help in Tasking

Posted: Thu Feb 11, 2016 6:52 am
by ashishkumar4
Thanks :D that's so great of you :D implementing this. If got any doubt, would reply here :D

Re: Help in Tasking

Posted: Thu Feb 11, 2016 12:04 pm
by tsdnz
Brendan wrote:Hi,
Nice reply Brendan. Great starting point.