Page 1 of 1

need idea on thread_exit()

Posted: Tue May 01, 2012 7:16 am
by bluemoon
First, some description on what I wanted to do:

I create a process or kthread, with a thread control block that has an initial construction on stack so that it will "resume" to thread's entry and upon return it will call thread_exit(TCB*)

On 32-bit system it looks like this on the initial stack:
[TCB* tcb]
[TCB* tcb]
[thread_exit]
[thread_func]

So, upon context switch, it will "return" to thread_func, and thread_func will return to thread_exit, the interface will be:

Code: Select all

void thread_func(TCB* tcb) {
   // do some stuff
}
void thread_exit(TCB* tcb) {
//...
}
It all work fine.

However, on x86_64 the AMD ABI uses register to pass parameter, and it's not preserved across function calls.
I then come up with ugly hack that:
stack construction:
[TCB* tcb]
[thread_exit]
[thread_func]
RDI := tcb

Once context switch it successfully "return" to thread_func, and thread_func returns to thread_exit,
but to retrieve TCB* I now doing this:

Code: Select all

void kthread_test(PROCESS* process) {
    for (int i=0; i<(int)process->pid; i++) {
        kprintf ("KMAIN : thread(%d): %d\n", process->pid, i);
        scheduler_sleep(1000);
    }
}
void process_exit (void) { // PROCESS *process ) {
    PROCESS *process;
    __asm volatile ("pop %0" : "=A"(process) );
    if ( process == NULL ) return;
    process->flags |= PROCESS_FLAG_ZOMBIE;
    for(;;) {
        scheduler_sleep(0); // yield
    }
} 
It works, but the assembly pop it is ugly and make this code architecture specific. I wonder if there is more elegant way to do this?

PS. for simplicity I current test with PROCESS* which contain only single thread.

Re: need idea on thread_exit()

Posted: Tue May 01, 2012 7:25 am
by gerryg400
I'm not sure I understand. The problem is that you can't pass the TCB* parameter to thread_exit ?

Re: need idea on thread_exit()

Posted: Tue May 01, 2012 7:27 am
by bluemoon
yes.

Re: need idea on thread_exit()

Posted: Tue May 01, 2012 7:35 am
by gerryg400
When my threads are created, the kernel puts a return address on the stack but I use a small assembly function that moves the value to the correct reg like this. Note that in my case the parameter to thread_exit is the value return by the thread function. Yours will be slightly different.

Code: Select all

.code64
.text
        .globl    __pthread_return_func

__pthread_return_func:

        /* This function is 'called' when a thread returns. On x86 a pointer
         * to it is on the stack of every thread and is popped off when the
         * thread entry function returns.
         *
         * We will call pthread_exit.
         *
         * The only other thing we need to do is move the value that would
         * have been returned, from the return register (eax on i386,
         * rax on x86_64) to wherever the first function parameter would be
         * (stack on i386, rdi on x86_64)
         */

        movq    %rax, %rdi
        callq   pthread_exit
In your case you would probably use a popq then a movq.

Re: need idea on thread_exit()

Posted: Tue May 01, 2012 7:52 am
by Rudster816
Have you thought of just storing a pointer to the PROCESS* structure for the active thread on a particular processor in global variable? Any kernel routine could access it to check process privileges, make changes, etc, without having to access it the pointer from the stack.

e.g.

Code: Select all

active_process[THIS_CPU]->flags |= PROCESS_FLAG_ZOMBIE;

You'll completely get rid of any ABI assumptions, because many other ABI's (not just AMD64 SystemV) use registers to pass parameters.

Re: need idea on thread_exit()

Posted: Tue May 01, 2012 7:58 am
by bluemoon
@Rudster816: sounds good. I have such global variable for the scheduler anyway. It solve the ABI issue.

Code: Select all

void process_exit (void) {
    PROCESS* process = scheduler_current();
    process->flags |= PROCESS_FLAG_ZOMBIE;
    for(;;) {
        scheduler_sleep(0); // yield
    }
}

Re: need idea on thread_exit()

Posted: Tue May 01, 2012 4:07 pm
by Combuster
The alternative would be to set up the frame for just the kernel thread, and have the thread explicitly call exit. Exit would then terminate the thread as a whole so that the return into nothingness doesn't get executed. The resulting application would end up like the following:

Code: Select all

void thread_func(TCB* tcb) {
   
   // do some stuff

   thread_exit(tcb);
}
void thread_exit(TCB* tcb) {
   //...
}
It's not possible to write a thread creation routine without ABI specifics, because processor state must be explicitly manipulated in all cases. Globals allow for only one kthread, and fixing that with TLS requires much more ABI-specific support code. Killing the kthread is similarly ABI-specific plus it shouldn't significantly care about what stack state it happens in.

Re: need idea on thread_exit()

Posted: Tue May 01, 2012 5:54 pm
by AndrewAPrice
My instinct would tell me to make a single thread entry point function that calls their thread function, and upon returning exits the thread:

Code: Select all

typedef void (*ThreadFunc)  (void *userData);
void ThreadEntryPoint(ThreadFunc threadFunc, void *userData)
{
   threadFunc(userData);
   ThreadExit();
}
Then the user could do something like:

Code: Select all

void work(void *userData) {
    printf("Hello from thread %i\n", threadNo);
    // do work
    printf("I'm going to disappear when I return!\n");
}

// somewhere later
int threadNo;
for(threadNo = 0; threadNo < 10; threadNo++)
    syscall_CreateThread(&work, threadNo);

Re: need idea on thread_exit()

Posted: Wed May 02, 2012 12:22 am
by rdos
I really dislike the idea of keeping scheduler-related information (TCB) on the stack for a thread to thrash. Kernel thread or not.

Re: need idea on thread_exit()

Posted: Wed May 02, 2012 1:06 am
by xenos
My approach is very similar to Rudster816's suggestion. Whenever a thread calls ExitThread (or ExitProcess), it doesn't need to pass its thread ID or even a pointer to the TCB as a parameter, since the scheduler simply knows which thread is currently running and requesting to be terminated. My threads / processes need to call ExitThread / ExitProcess explicitly, because the kernel does no a priori know whether only one thread or the whole process (which may contain more than one thread) should be terminated when the thread function returns, so it cannot decide whether to push ExitThread or ExitProcess as a return address onto the stack.