Kernel Threads

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
jeandaniel
Posts: 14
Joined: Wed Nov 14, 2007 3:26 pm
Location: Paris, France

Kernel Threads

Post by jeandaniel »

Hi, i have made a basic kernel in C++.
I have implemented irqs ans my sheduler class.
my clock launch a method that look for the next thread to launch.
when this method found one, it save the state of the current thread ( in a struct that is a private object in my thread class). and load the other thread.
It works fine with 2 threads but this case crash :

the 1st thread is the main program, the second one is this function :

Code: Select all

void fonc(){
	int cpt = 0;
	*((unsigned char*)0xB8001) = 0xF;
	for(; ; )
	{
		cpt += 1;
		cpt%=10;
		*((unsigned char*)0xB8000) = tab2[cpt]; 
	}
}
this function is halt by the clock that relaunch the main program, this works fine (the number on the screen is lock ), when the clock wants to relaunch the second thread, it launches it but the number doesn't change ...
this is my structure and my save and reload methods

Code: Select all

void Thread::reload()
{
	asm("mov %0, %%gs" :: "r" (context.gs));
	asm("mov %0, %%fs" :: "r" (context.fs));
	asm("mov %0, %%es" :: "r" (context.es));
	asm("mov %0, %%ds" :: "r" (context.ds));
	asm("mov %0, %%eax" :: "r" (context.eax));
	asm("mov %0, %%ebx" :: "r" (context.ebx));
	asm("mov %0, %%ecx" :: "r" (context.ecx));
	asm("mov %0, %%edx" :: "r" (context.edx));
	asm("mov %0, %%esi" :: "r" (context.esi));
	asm("mov %0, %%edi" :: "r" (context.edi));
	asm("mov %0, %%esp" :: "r" (context.esp));
	asm("mov %0, %%ebp" :: "r" (context.ebp));
	asm("mov %0, %%ss" :: "r" (context.ss));
}

void Thread::save(){
	asm("mov %%gs, %0" : "=r" (context.gs));
	asm("mov %%fs, %0" : "=r" (context.fs));
	asm("mov %%es, %0" : "=r" (context.es));
	asm("mov %%ds, %0" : "=r" (context.ds));
	asm("mov %%eax, %0" : "=r" (context.eax));
	asm("mov %%ebx, %0" : "=r" (context.ebx));
	asm("mov %%ecx, %0" : "=r" (context.ecx));
	asm("mov %%edx, %0" : "=r" (context.edx));
	asm("mov %%esi, %0" : "=r" (context.esi));
	asm("mov %%edi, %0" : "=r" (context.edi));
	asm("mov %%esp, %0" : "=r" (context.esp));
	asm("mov %%ebp, %0" : "=r" (context.ebp));
	asm("mov %%ss, %0" : "=r" (context.ss));
}

typedef struct 
	{
		unsigned long gs;
		unsigned long fs;
		unsigned long es;
		unsigned long ds;

		unsigned long eax;
		unsigned long ebx;
		unsigned long ecx;
		unsigned long edx;

        unsigned long esp;
		unsigned long ebp;
		unsigned long esi;
		unsigned long edi;
		unsigned long eip;
		unsigned long ss;
		
		unsigned long cs;
		unsigned long eflag;
		
	} jmp_buf;

class Thread{

	private:
		jmp_buf context;
...
:(

[Edited by Brendan - Added code tags due to popular demand!]
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

Post by mystran »

It's hard to say where the problem might be, and honestly I should be sleeping, but I thought I'd give a totally unrelated piece of advice: use [ code] tags (without the space after [ and you'll get your code to behave:

Code: Select all


 like

    this

The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Post by JamesM »

It would well be worth disassembling your resulting kernel binary and seeing what those thread functions compile to. The reason I say this is you set EAX here...

Code: Select all

 asm("mov %0, %%ds" :: "r" (context.ds)); 
   asm("mov %0, %%eax" :: "r" (context.eax)); 
   asm("mov %0, %%ebx" :: "r" (context.ebx)); 
   asm("mov %0, %%ecx" :: "r" (context.ecx)); 
   asm("mov %0, %%edx" :: "r" (context.edx));
But what you should know (you do now!) is that the compiler uses EAX as a temporary register to access member variables of classes. The line

Code: Select all

asm("mov %0, %%ebx" :: "r"(context.ecx));
compiles something like:

Code: Select all

mov $0x???, %eax
mov 4(%eax), %ebx
So it is quite likely that eax is being overwritten during that function. Disassemble it and rearrange the operations (so that for example you set eax LAST) so that this doesn't happen.
jeandaniel
Posts: 14
Joined: Wed Nov 14, 2007 3:26 pm
Location: Paris, France

Post by jeandaniel »

thx but nothing happened :(
I think i forget to save something but i don't know what :(
any idea ?
User avatar
liquid.silver
Member
Member
Posts: 46
Joined: Sat Jun 30, 2007 12:07 pm
Location: South Africa
Contact:

Post by liquid.silver »

Can you please show the code for the scheduler. I think that's where the problem might reside. Are you using an emulator?

Edit: Does the first thread work after a context switch? Try establish if it does.
jeandaniel
Posts: 14
Joined: Wed Nov 14, 2007 3:26 pm
Location: Paris, France

Post by jeandaniel »

hi,
my schredule function is :

Code: Select all

// nasm
n_irq0:
	cli
	call idt_irq0
	mov al,0x20
	out 0x20,al
	sti
	iret
then

Code: Select all

// C
void idt_irq0(void) {
	time++;
	if((time%501) == 0)
		schedule(adresseSysteme);
	if((time%100) == 0)
	{
		updateClock(); /* add one seconde to the clock */
	}
}

Code: Select all

// C++
void schedule(void * adresseSys){
	(static_cast<System *>(adresseSys))->schedule();
}

void System::schedule()  /* search for the thread to launch and launch it */
{
	vid << vid.blue << "schedule0 :" << tab[numThreadEnCours] << std::endl;
	if(nbThreads > 0)
	{
			vid << vid.blue << "schedule1 :" << tab[numThreadEnCours] << std::endl;
			int threadAvant = numThreadEnCours;
			numThreadEnCours = (numThreadEnCours + 1) % nbThreads;
			vid << vid.blue << "schedule2 :" << tab[numThreadEnCours] << std::endl;
			//vid << vid.green << "win" << std::endl;				
			thread[threadAvant].save();
			vid << vid.red << "thread:" << tab[threadAvant] << vid.blue << "saved" << std::endl;
			vid << vid.red << "launching thread:" << tab[numThreadEnCours] << std::endl;
			outb(0x20,0x20);
			__asm__("sti");
			thread[numThreadEnCours].start(vid,nbThreads);
			vid << vid.red << "thread:" << tab[numThreadEnCours] << vid.green << "executed" << std::endl;
	}
}

bool Thread::start(Video &v,int& nb){
	if(startBool) /* already launched */
	{
		v << v.red << "reload" <<std::endl;
		reload();
	}
	else
	{
		startBool = true;
		/* launching function */
		v << v.red << "call" <<std::endl;
		callOffset();
		//hello();
		startBool = false;
		v << v.red << "here" <<std::endl;
	}
	return true;
}

void Thread::setStartBool(bool val)
{
	startBool = val;
}

void Thread::setArgs(void (* call)(),unsigned int prio){
	callOffset = call;
	priority = prio;
}

/* for context switching */
void Thread::reload()
{
	asm("mov %0, %%eax" :: "r" (context.eax));
	asm("mov %0, %%gs" :: "r" (context.gs));
	asm("mov %0, %%fs" :: "r" (context.fs));
	asm("mov %0, %%es" :: "r" (context.es));
	asm("mov %0, %%ds" :: "r" (context.ds));
	asm("mov %0, %%ebx" :: "r" (context.ebx));
	asm("mov %0, %%ecx" :: "r" (context.ecx));
	asm("mov %0, %%edx" :: "r" (context.edx));
	asm("mov %0, %%esi" :: "r" (context.esi));
	asm("mov %0, %%edi" :: "r" (context.edi));
	asm("mov %0, %%esp" :: "r" (context.esp));
	asm("mov %0, %%ebp" :: "r" (context.ebp));
	asm("mov %0, %%ss" :: "r" (context.ss));
	asm("mov %0, %%cs" :: "r" (context.cs));
}

void Thread::save(){
	asm("mov %%eax, %0" : "=r" (context.eax));
	asm("mov %%gs, %0" : "=r" (context.gs));
	asm("mov %%fs, %0" : "=r" (context.fs));
	asm("mov %%es, %0" : "=r" (context.es));
	asm("mov %%ds, %0" : "=r" (context.ds));
	asm("mov %%ebx, %0" : "=r" (context.ebx));
	asm("mov %%ecx, %0" : "=r" (context.ecx));
	asm("mov %%edx, %0" : "=r" (context.edx));
	asm("mov %%esi, %0" : "=r" (context.esi));
	asm("mov %%edi, %0" : "=r" (context.edi));
	asm("mov %%esp, %0" : "=r" (context.esp));
	asm("mov %%ebp, %0" : "=r" (context.ebp));
	asm("mov %%ss, %0" : "=r" (context.ss));
	asm("mov %%cs, %0" : "=r" (context.cs));
}
And yes i use an emulator i'm using VmWare (fusion on mac)
User avatar
liquid.silver
Member
Member
Posts: 46
Joined: Sat Jun 30, 2007 12:07 pm
Location: South Africa
Contact:

Post by liquid.silver »

I think i see what the problem is. You change some of the register before you save them and after you load them. I'm not too familiar with C, so i can't say how to fix it. My approach was to use a stack and then just swap the stacks of each context thread. For example that code in assembly that outputs to a port uses al. You need to do some serious restructuring.

Edit: also you didn't save the eflags register
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

Post by mystran »

IMHO stack switching is much easier to do if you don't try to separate "save state" from "load state" because then you don't have to worry about what happens between those two...

I personally use a function like:

Code: Select all

// This is pretty simple. Basicly, we push all callee-saved registers
// into stack, then switch ESP, and then pop all the registers.
//
// It's written in inline-asm so that we know the exact stack-layout
// and can initialize new threads easily.
//
// The function prototype is:
//     volatile void cpu_switch_stack(void *oldstack, void *newstack);
//
asm (".global cpu_switch_stack;"
        "cpu_switch_stack:"
        /* load arguments */
        "movl 4(%esp), %eax;" // oldstack
        "movl 8(%esp), %edx;" // newstack
        /* save all registers */
        "pushl %ebx;"
        "pushl %esi;"
        "pushl %edi;"
        "pushl %ebp;"
        /* do the stack switch */
        "movl %esp, (%eax);"
        "movl (%edx), %esp;"
        /* restore all registers */
        "popl %ebp;"
        "popl %edi;"
        "popl %esi;"
        "popl %ebx;"
        "ret;");
In short, it doesn't bother with caller-saved registers, since the compiler will already save those when calling the function. That also means some registers to work with. The two pointers are to the beginning of allocated stacks (which btw happen to be pointers to my thread structures as well, but that's not important) and that allows saving/loading the correct ESP to/from the beginning of the allocated stack.

So, say, if a stack (at address S) is 4k, the at thread creation ESP is initialized at S+4k, and grows towards S. At S we can thus save the value of ESP when the stack is inactive. When thread is created, this is S+4k (well a couple of words higher 'cos there's the register defaults that will be popped when switching to the thread first time, and a catcher frame in case the thread-function returns) and after the thread has called scheduler, it's whatever value ESP had after pushing the variables..

There are other ways to do it ofcourse.. but I've been quite happy with the above. :)
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Post by JamesM »

Code: Select all

void Thread::save(){ 
   asm("mov %%eax, %0" : "=r" (context.eax)); 
   asm("mov %%gs, %0" : "=r" (context.gs)); 
   asm("mov %%fs, %0" : "=r" (context.fs)); 
   asm("mov %%es, %0" : "=r" (context.es)); 
   asm("mov %%ds, %0" : "=r" (context.ds)); 
   asm("mov %%ebx, %0" : "=r" (context.ebx)); 
   asm("mov %%ecx, %0" : "=r" (context.ecx)); 
   asm("mov %%edx, %0" : "=r" (context.edx)); 
   asm("mov %%esi, %0" : "=r" (context.esi)); 
   asm("mov %%edi, %0" : "=r" (context.edi)); 
   asm("mov %%esp, %0" : "=r" (context.esp)); 
   asm("mov %%ebp, %0" : "=r" (context.ebp)); 
   asm("mov %%ss, %0" : "=r" (context.ss)); 
   asm("mov %%cs, %0" : "=r" (context.cs)); 
}
You didn't even do what I suggested! Did you even dissassemble the resulting binary and take a look at it? My *ahem* suggestion may not fix your problem (it may be elsewhere) but I am telling you that your code as it is won't work. So saying "your suggestion didn't work" without any explanation and rolling back your code isn't going to help. Try dissassembling the binary, like I said before, and inspect what that code gets compiled to. It's quite possible some registers are being reloaded then trashed as temporary storage.
Post Reply