www.pudn.com > COS0.0.1.rar > thread.c
/*
thread.c - multi-threading support
Author: Paul Barker
Part of: COS
Created: 31/08/04
Last Modified: 05/11/04
Copyright (C) 2004 Paul Barker
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
(See file "Copying")
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
// implemented in thr.s
extern void Schedule(interrupt_state_t* state);
extern u32_t Get_EFLAGS();
// pointer to the current thread
static kthread_t* volatile pThisThread;
// queue of runnable threads
static thread_queue_t tqRun;
// defined in int.s, fake return address for creating a thread
extern char FakeReturnAddr;
/////////////////////////////////////////////////////////////////////
// Thread Queues
bool_t thread_queue_empty(thread_queue_t* tq)
{
thread_node_t* p = tq->head;
return (p && p->thread);
}
void thread_queue_dump(thread_queue_t* tq)
{
thread_node_t* p = tq->head;
TRACE(("thread_queue_dump %x Head=%x\n", tq, p));
while (p)
{
TRACE(("Node %x : thread=%x, next=%x\n", p, p->thread, p->next));
p = p->next;
}
}
kthread_t* thread_queue_head(thread_queue_t* tq)
{
thread_node_t* p = tq->head;
if (!p)
return pThisThread;
tq->head = p->next; // detach the node
kthread_t* thr = p->thread;
kfree(p);
return thr;
}
void queue_thread(kthread_t* thr)
{
thread_queue_t* q = thr->queue;
TRACE(("QueueThread %x in %x\n", thr, q));
if (!(q->head))
{
q->head = kalloc(sizeof(thread_node_t));
q->head->thread = thr;
q->head->next = NULL;
return;
}
thread_node_t* p = q->head;
while (p->next)
p = p->next; // find end of queue
// last node will have a thread
p->next = kalloc(sizeof(thread_node_t));
p->next->thread = thr;
p->next->next = NULL;
}
/////////////////////////////////////////////////////////////////////
// Private functions
// called by low-level scheduler
// assume the current thread is already queued before the interrupt
// currently we only deal with Yield() so this is true
kthread_t* do_sched()
{
thread_queue_dump(&tqRun);
// assume we always have a runnable thread, for now
kthread_t* thr = thread_queue_head(&tqRun);
TRACE(("Schedule, thr=%x esp=%x\n", thr, thr->esp));
pThisThread = thr;
return thr; // assembly code expects this in eax
}
// setup a child thread
void add_child_thread(kthread_t* thr)
{
kthread_t* tmp = thr->parent->child;
if (tmp == NULL)
{
thr->parent->child = thr;
return;
}
// find end of child threads
while (tmp->next)
tmp = tmp->next;
tmp->next = thr;
}
/////////////////////////////////////////////////////////////////////
// Public functions
kthread_t* get_current_thread()
{
return pThisThread;
}
void yield()
{
TRACE(("Yield()\n"));
queue_thread(pThisThread);
__asm__ __volatile__ ("int $0x50"::);
}
void thread_init()
{
TRACE(("InitThread()\n"));
// setup the current thread
pThisThread = kalloc(sizeof(kthread_t));
pThisThread->Stack = 0xA000; // 40k
pThisThread->StackSize = 0x5000; // 24k
pThisThread->esp = NULL; // invalid until we store it in a stack switch
pThisThread->uTicks = START_TICKS; // assume we just started running
pThisThread->parent = pThisThread->next = pThisThread->child = NULL;
// no parent or child, no other threads allowed at this level
pThisThread->queue = &tqRun;
install_handler(0x50, &Schedule);
init_timer();
g_kernel_state |= THREAD_ENABLED;
TRACE(("InitThread() done\n"));
}
// TODO: currently processor will become lost in hyperspace if fn returns
kthread_t* spawn_thread(thread_function_t fn)
{
TRACE(("SpawnThread(fn=%x)\n", fn));
kthread_t* thr = kalloc(sizeof(kthread_t));
thr->Stack = (iptr_t)kalloc(8192); // 2 page stack (8k)
thr->StackSize = 8192;
dword_t* p = (dword_t*)(thr->Stack + 8188); // top dword of stack
count_t i;
// setup stack as if an interrupt happened
*p-- = Get_EFLAGS() | (1 << 9); // eflags with interrupt flag forced
*p-- = KERNEL_CS; // cs
*p-- = (dword_t)fn; // eip
for (i = 0; i < 8; i++)
*p-- = 0; // fill errorCode, intNum and registers with zeroes
*p-- = (thr->Stack + 8188); // C frame pointer in ebp
for (i = 0; i < 4; i++)
*p-- = KERNEL_DS; //fill segment selectors with kernel data segment
--p; // Stack now looks as if we were in an interrupt handler
*p = (dword_t)&FakeReturnAddr; // return address for function call, see int.s
thr->esp = (iptr_t)p;
thr->parent = pThisThread;
thr->next = thr->child = NULL;
add_child_thread(thr);
thr->queue = &tqRun;
queue_thread(thr);
return thr;
}
// wait on a given thread queue
void wait(thread_queue_t* tq)
{
TRACE(("Thread %x waiting on queue %x\n", pThisThread, tq));
assert_ptr(tq);
pThisThread->queue = tq;
yield();
}
// wake first thread on a queue, returns number of threads woken
count_t wake_one(thread_queue_t* tq)
{
assert_ptr(tq);
kthread_t* thr = thread_queue_head(tq);
// silently fail on an empty thread queue
if (thr != pThisThread)
{
TRACE(("Waking thread %x from queue %x\n", thr, tq));
thr->queue = &tqRun;
queue_thread(thr);
return 1;
}
return 0;
}
// wake all threads on a queue, returns number of threads woken
count_t wake_all(thread_queue_t* tq)
{
assert_ptr(tq);
kthread_t* thr;
count_t i = 0;
TRACE(("Waking all threads from queue %x\n", tq));
// silently fail on an empty thread queue
while (!thread_queue_empty(tq))
{
thr = thread_queue_head(tq);
TRACE(("\tWaking Thread %x\n", thr));
thr->queue = &tqRun;
queue_thread(thr);
i++;
}
TRACE(("WakeAll() Done\n"));
return i;
}
//////////////////////////////////////////////////////////////////////////
// Mutex functions, based on GeekOS implementation
void do_mlock(mutex_t* m)
{
cli();
assert_ptr(m);
assert(m->owner != pThisThread);
// wait for the mutex to become free
while (m->state == MUTEX_LOCKED)
wait(&(m->queue));
m->state = MUTEX_LOCKED;
m->owner = pThisThread;
sti();
}
void do_munlock(mutex_t* m)
{
cli();
assert_ptr(m);
assert(m->owner == pThisThread);
assert(m->state == MUTEX_LOCKED);
m->state = MUTEX_UNLOCKED;
m->owner = NULL;
wake_one(&m->queue);
sti();
}
void minit(mutex_t* m)
{
assert_ptr(m);
m->state = MUTEX_UNLOCKED;
m->owner = NULL;
m->queue.head = NULL;
}
/*
TODO: this needs some serious attention
change mutex code to use test_and_set
*/