Help in Tasking

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
ashishkumar4
Member
Member
Posts: 73
Joined: Wed Dec 23, 2015 10:42 pm

Help in Tasking

Post 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).
The best method for accelerating a computer is the one that boosts it by 9.8 m/s2.
My OS : https://github.com/AshishKumar4/Aqeous
User avatar
SpyderTL
Member
Member
Posts: 1074
Joined: Sun Sep 19, 2010 10:05 pm

Re: Help in Tasking

Post 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.
Last edited by SpyderTL on Wed Feb 10, 2016 10:03 am, edited 1 time in total.
Project: OZone
Source: GitHub
Current Task: LIB/OBJ file support
"The more they overthink the plumbing, the easier it is to stop up the drain." - Montgomery Scott
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Help in Tasking

Post 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.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
ashishkumar4
Member
Member
Posts: 73
Joined: Wed Dec 23, 2015 10:42 pm

Re: Help in Tasking

Post 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 :/
The best method for accelerating a computer is the one that boosts it by 9.8 m/s2.
My OS : https://github.com/AshishKumar4/Aqeous
tsdnz
Member
Member
Posts: 333
Joined: Sun Jun 16, 2013 4:09 am

Re: Help in Tasking

Post 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..
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: Help in Tasking

Post by gerryg400 »

Why didn't you use an assembler to write your interrupt code ?
If a trainstation is where trains stop, what is a workstation ?
tsdnz
Member
Member
Posts: 333
Joined: Sun Jun 16, 2013 4:09 am

Re: Help in Tasking

Post 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);
User avatar
neon
Member
Member
Posts: 1567
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: Help in Tasking

Post 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.
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Help in Tasking

Post 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
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Help in Tasking

Post 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
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
tsdnz
Member
Member
Posts: 333
Joined: Sun Jun 16, 2013 4:09 am

Re: Help in Tasking

Post 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
ashishkumar4
Member
Member
Posts: 73
Joined: Wed Dec 23, 2015 10:42 pm

Re: Help in Tasking

Post 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 :/
The best method for accelerating a computer is the one that boosts it by 9.8 m/s2.
My OS : https://github.com/AshishKumar4/Aqeous
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Help in Tasking

Post 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
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
ashishkumar4
Member
Member
Posts: 73
Joined: Wed Dec 23, 2015 10:42 pm

Re: Help in Tasking

Post by ashishkumar4 »

Thanks :D that's so great of you :D implementing this. If got any doubt, would reply here :D
The best method for accelerating a computer is the one that boosts it by 9.8 m/s2.
My OS : https://github.com/AshishKumar4/Aqeous
tsdnz
Member
Member
Posts: 333
Joined: Sun Jun 16, 2013 4:09 am

Re: Help in Tasking

Post by tsdnz »

Brendan wrote:Hi,
Nice reply Brendan. Great starting point.
Post Reply