Hi,
uma wrote:I have a sleep function where each thread is put to sleep using busy wait. I want to now use semaphores instead of busy wait. How do i make a thread block using semaphores for a certain amount of time ?
Somewhere in your scheduler you will have code to determine which task to give CPU time to next. This code must not choose any task that is blocked for any reason.
Your scheduler will use some sort of data structure/s (maybe a list or queue of tasks, maybe some sort of tree, maybe multiple queues, whatever). If these data structures include tasks that are blocked, then scheduler has to check if task/s are blocked or not and skip them if they are. A more efficient way is to avoid that, and only put tasks that are not blocked in the scheduler's data structures, so that the scheduler doesn't need to check if a task is blocked or not.
Now...
Currently you want a thread to block for an amount of time. Sooner or later you'll want a thread to block waiting for some sort of IPC (message, pipe, whatever), or waiting for IO (a file, a network connection, etc). Then you might want something like "block until a debugger running in a different process says to unblock", or "block until virtual memory manager gets a page from swap space".
Basically; there's many reasons for a task to block. Waiting for a mutex or semaphore is just another reason to block, but it has nothing to do with any of the other reasons.
Also, at some point you will need to combine the "reasons for blocking" in various ways. There are 2 ways that they may be combined - either you're waiting for any one of a group of reasons (e.g. "wait until network packet arrives or a time-out expires, whichever happens first"); or you're waiting for all of a group of reasons (e.g. "wait until network packet arrives and a certain amount of time has passed, and don't unblock until both have happened").
Let's consider "waiting for all of a group of reasons" first (as it's easier). Please note that this includes a "group of 1 reason" (e.g. waiting for time to pass and nothing else). Now, imagine if each task has a set of flags; where each flag represents a different reason for blocking - one flag for "waiting for time", one flag for "waiting for data from swap", one flag for "waiting for IO", etc. If you want to wait for "all of a group of reasons" you set the flag for each reason, then various things (e.g. the timer, the file system, whatever) clear the flags when each of the things happens, and when all flags are clear the task isn't waiting for anything anymore.
With this in mind your scheduler can have 2 functions - one to set "reasons for blocking" flags and one to clear them. These 2 functions take care of the blocking/unblocking. For example:
Code: Select all
void setBlockedFlags(thread_data *thread, uint32_t flags) {
if(thread->blockedFlags == 0) {
// Thread wasn't blocked before, so it has to be removed from data structures that the
// scheduler uses to keep track of "ready to run" tasks here.
}
thread->blockedFlags |= flags;
}
Code: Select all
void clearBlockedFlags(thread_data *thread, uint32_t flags) {
if(thread->blockedFlags == 0) {
return; // Thread wasn't blocked so do nothing
}
thread->blockedFlags &= ~flags;
if(thread->blockedFlags == 0) {
// Thread is now unblocked, so it has to be added to the data structures that the
// scheduler uses to keep track of "ready to run" tasks here.
}
}
With this in mind, your "sleep()" function may add the task to a list of tasks waiting for time and do "setBlockedFlags(thread, WAITING_FOR_TIME);". Eventually (when the time has passed) your timer code would remove the task from the list of tasks waiting for time and do "clearBlockedFlags(thread, WAITING_FOR_TIME);".
That only leaves "waiting for one of a group of reasons". This isn't that hard to add - the code might look a little like this:
Code: Select all
void setBlockedFlags(thread_data *thread, uint32_t allFlags, uint32_t anyFlags) {
if(anyFlags != 0) {
thread->blockedForAnyFlags = anyFlags;
allFlags |= BLOCKED_FOR_ANY_MASTER_FLAG;
}
if(thread->blockedForAllFlags == 0) {
// Thread wasn't blocked before, so it has to be removed from data structures that the
// scheduler uses to keep track of "ready to run" tasks here.
}
thread->blockedFlags |= allFlags;
}
Code: Select all
void clearBlockedFlags(thread_data *thread, uint32_t flags) {
if( (thread->blockedForAnyFlags & flags) != 0) {
// One of the "blocked for any" flags is being unset, so they all get unset
thread->blockedForAnyFlags = 0;
// And the master flag needs to be unset too
flags |= BLOCKED_FOR_ANY_MASTER_FLAG;
}
if(thread->blockedFlags == 0) {
return; // Thread wasn't blocked so do nothing
}
thread->blockedFlags &= ~flags;
if(thread->blockedFlags == 0) {
// Thread is now unblocked, so it has to be added to the data structures that the
// scheduler uses to keep track of "ready to run" tasks here.
}
}
This actually allows some "combinations of combinations". For a random example, it could easily handle "block until debugger says it's OK to continue AND mutex is acquired AND (networking packet arrives OR time-out expires)".
Cheers,
Brendan