022567d3a0
of waiting threads by ID (instead of just the queue head and tail pointers). git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@18688 a95241bf-73f2-0310-859d-f6bbb57e9c96
1247 lines
30 KiB
C
1247 lines
30 KiB
C
/*
|
|
* Copyright 2002-2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
|
|
* Distributed under the terms of the MIT License.
|
|
*
|
|
* Copyright 2001, Travis Geiselbrecht. All rights reserved.
|
|
* Distributed under the terms of the NewOS License.
|
|
*/
|
|
|
|
/* Semaphore code */
|
|
|
|
|
|
#include <OS.h>
|
|
|
|
#include <sem.h>
|
|
#include <kernel.h>
|
|
#include <kscheduler.h>
|
|
#include <ksignal.h>
|
|
#include <smp.h>
|
|
#include <int.h>
|
|
#include <arch/int.h>
|
|
#include <debug.h>
|
|
#include <thread.h>
|
|
#include <team.h>
|
|
#include <vfs.h>
|
|
#include <vm_low_memory.h>
|
|
#include <vm_page.h>
|
|
#include <boot/kernel_args.h>
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
//#define TRACE_SEM
|
|
#ifdef TRACE_SEM
|
|
# define TRACE(x) dprintf x
|
|
#else
|
|
# define TRACE(x) ;
|
|
#endif
|
|
|
|
#define DEBUG_LAST_ACQUIRER
|
|
|
|
struct sem_entry {
|
|
sem_id id;
|
|
spinlock lock; // protects only the id field when unused
|
|
union {
|
|
// when slot in use
|
|
struct {
|
|
int32 count;
|
|
struct thread_queue queue;
|
|
char *name;
|
|
team_id owner; // if set to -1, means owned by a port
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
thread_id last_acquirer;
|
|
#endif
|
|
} used;
|
|
|
|
// when slot unused
|
|
struct {
|
|
sem_id next_id;
|
|
struct sem_entry *next;
|
|
} unused;
|
|
} u;
|
|
};
|
|
|
|
static const int32 kMaxSemaphores = 65536;
|
|
static int32 sMaxSems = 4096;
|
|
// Final value is computed based on the amount of available memory
|
|
static int32 sUsedSems = 0;
|
|
|
|
static struct sem_entry *sSems = NULL;
|
|
static bool sSemsActive = false;
|
|
static struct sem_entry *sFreeSemsHead = NULL;
|
|
static struct sem_entry *sFreeSemsTail = NULL;
|
|
|
|
static spinlock sem_spinlock = 0;
|
|
#define GRAB_SEM_LIST_LOCK() acquire_spinlock(&sem_spinlock)
|
|
#define RELEASE_SEM_LIST_LOCK() release_spinlock(&sem_spinlock)
|
|
#define GRAB_SEM_LOCK(s) acquire_spinlock(&(s).lock)
|
|
#define RELEASE_SEM_LOCK(s) release_spinlock(&(s).lock)
|
|
|
|
static int remove_thread_from_sem(struct thread *thread, struct sem_entry *sem,
|
|
struct thread_queue *queue, status_t acquireStatus);
|
|
|
|
struct sem_timeout_args {
|
|
thread_id blocked_thread;
|
|
sem_id blocked_sem_id;
|
|
int32 sem_count;
|
|
};
|
|
|
|
|
|
static int
|
|
dump_sem_list(int argc, char **argv)
|
|
{
|
|
const char *name = NULL;
|
|
team_id owner = -1;
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
thread_id last = -1;
|
|
#endif
|
|
int32 i;
|
|
|
|
if (argc > 2) {
|
|
if (!strcmp(argv[1], "team") || !strcmp(argv[1], "owner"))
|
|
owner = strtoul(argv[2], NULL, 0);
|
|
else if (!strcmp(argv[1], "name"))
|
|
name = argv[2];
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
else if (!strcmp(argv[1], "last"))
|
|
last = strtoul(argv[2], NULL, 0);
|
|
#endif
|
|
} else if (argc > 1)
|
|
owner = strtoul(argv[1], NULL, 0);
|
|
|
|
kprintf("sem id count team"
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
" last"
|
|
#endif
|
|
" name\n");
|
|
|
|
for (i = 0; i < sMaxSems; i++) {
|
|
struct sem_entry *sem = &sSems[i];
|
|
if (sem->id < 0
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
|| (last != -1 && sem->u.used.last_acquirer != last)
|
|
#endif
|
|
|| (name != NULL && strstr(sem->u.used.name, name) == NULL)
|
|
|| (owner != -1 && sem->u.used.owner != owner))
|
|
continue;
|
|
|
|
kprintf("%p %6lx %5ld %6lx "
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
"%6lx "
|
|
#endif
|
|
" %s\n", sem, sem->id, sem->u.used.count,
|
|
sem->u.used.owner,
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
sem->u.used.last_acquirer > 0 ? sem->u.used.last_acquirer : 0,
|
|
#endif
|
|
sem->u.used.name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
dump_sem(struct sem_entry *sem)
|
|
{
|
|
kprintf("SEM: %p\n", sem);
|
|
kprintf("id: %#lx\n", sem->id);
|
|
if (sem->id >= 0) {
|
|
kprintf("name: '%s'\n", sem->u.used.name);
|
|
kprintf("owner: 0x%lx\n", sem->u.used.owner);
|
|
kprintf("count: 0x%lx\n", sem->u.used.count);
|
|
kprintf("queue: ");
|
|
if (sem->u.used.queue.head != NULL) {
|
|
struct thread *thread = sem->u.used.queue.head;
|
|
while (thread != NULL) {
|
|
kprintf(" %lx", thread->id);
|
|
thread = thread->queue_next;
|
|
}
|
|
kprintf("\n");
|
|
} else
|
|
kprintf(" -\n");
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
kprintf("last acquired by: 0x%lx\n", sem->u.used.last_acquirer);
|
|
#endif
|
|
} else {
|
|
kprintf("next: %p\n", sem->u.unused.next);
|
|
kprintf("next_id: %#lx\n", sem->u.unused.next_id);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
dump_sem_info(int argc, char **argv)
|
|
{
|
|
bool found = false;
|
|
addr_t num;
|
|
int32 i;
|
|
|
|
if (argc < 2) {
|
|
kprintf("sem: not enough arguments\n");
|
|
return 0;
|
|
}
|
|
|
|
num = strtoul(argv[1], NULL, 0);
|
|
|
|
if (IS_KERNEL_ADDRESS(num)) {
|
|
dump_sem((struct sem_entry *)num);
|
|
return 0;
|
|
} else if (num > 0) {
|
|
uint32 slot = num % sMaxSems;
|
|
if (sSems[slot].id != (int)num) {
|
|
kprintf("sem 0x%lx (%ld) doesn't exist!\n", num, num);
|
|
return 0;
|
|
}
|
|
|
|
dump_sem(&sSems[slot]);
|
|
return 0;
|
|
}
|
|
|
|
// walk through the sem list, trying to match name
|
|
for (i = 0; i < sMaxSems; i++) {
|
|
if (sSems[i].u.used.name != NULL
|
|
&& strcmp(argv[1], sSems[i].u.used.name) == 0) {
|
|
dump_sem(&sSems[i]);
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
kprintf("sem \"%s\" doesn't exist!\n", argv[1]);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static inline void
|
|
clear_thread_queue(struct thread_queue *queue)
|
|
{
|
|
queue->head = queue->tail = NULL;
|
|
}
|
|
|
|
|
|
/** \brief Appends a semaphore slot to the free list.
|
|
*
|
|
* The semaphore list must be locked.
|
|
* The slot's id field is not changed. It should already be set to -1.
|
|
*
|
|
* \param slot The index of the semaphore slot.
|
|
* \param nextID The ID the slot will get when reused. If < 0 the \a slot
|
|
* is used.
|
|
*/
|
|
|
|
static void
|
|
free_sem_slot(int slot, sem_id nextID)
|
|
{
|
|
struct sem_entry *sem = sSems + slot;
|
|
// set next_id to the next possible value; for sanity check the current ID
|
|
if (nextID < 0)
|
|
sem->u.unused.next_id = slot;
|
|
else
|
|
sem->u.unused.next_id = nextID;
|
|
// append the entry to the list
|
|
if (sFreeSemsTail)
|
|
sFreeSemsTail->u.unused.next = sem;
|
|
else
|
|
sFreeSemsHead = sem;
|
|
sFreeSemsTail = sem;
|
|
sem->u.unused.next = NULL;
|
|
}
|
|
|
|
|
|
status_t
|
|
sem_init(kernel_args *args)
|
|
{
|
|
area_id area;
|
|
int32 i;
|
|
|
|
TRACE(("sem_init: entry\n"));
|
|
|
|
// compute maximal number of semaphores depending on the available memory
|
|
// 128 MB -> 16384 semaphores, 448 kB fixed array size
|
|
// 256 MB -> 32768, 896 kB
|
|
// 512 MB and more -> 1.75 MB
|
|
i = vm_page_num_pages() / 2;
|
|
while (sMaxSems < i && sMaxSems < kMaxSemaphores)
|
|
sMaxSems <<= 1;
|
|
|
|
// create and initialize semaphore table
|
|
area = create_area("sem_table", (void **)&sSems, B_ANY_KERNEL_ADDRESS,
|
|
sizeof(struct sem_entry) * sMaxSems, B_FULL_LOCK,
|
|
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
|
|
if (area < 0)
|
|
panic("unable to allocate semaphore table!\n");
|
|
|
|
memset(sSems, 0, sizeof(struct sem_entry) * sMaxSems);
|
|
for (i = 0; i < sMaxSems; i++) {
|
|
sSems[i].id = -1;
|
|
free_sem_slot(i, i);
|
|
}
|
|
|
|
// add debugger commands
|
|
add_debugger_command("sems", &dump_sem_list, "Dump a list of all active semaphores");
|
|
add_debugger_command("sem", &dump_sem_info, "Dump info about a particular semaphore");
|
|
|
|
TRACE(("sem_init: exit\n"));
|
|
|
|
sSemsActive = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** Creates a semaphore with the given parameters.
|
|
* Note, the team_id is not checked, it must be correct, or else
|
|
* that semaphore might not be deleted.
|
|
* This function is only available from within the kernel, and
|
|
* should not be made public - if possible, we should remove it
|
|
* completely (and have only create_sem() exported).
|
|
*/
|
|
|
|
sem_id
|
|
create_sem_etc(int32 count, const char *name, team_id owner)
|
|
{
|
|
struct sem_entry *sem = NULL;
|
|
cpu_status state;
|
|
sem_id id = B_NO_MORE_SEMS;
|
|
char *tempName;
|
|
size_t nameLength;
|
|
|
|
if (sSemsActive == false)
|
|
return B_NO_MORE_SEMS;
|
|
|
|
if (sUsedSems == sMaxSems) {
|
|
// The vnode cache may have collected lots of semaphores.
|
|
// Freeing some unused vnodes should improve our situation.
|
|
// TODO: maybe create a generic "low resources" handler, instead
|
|
// of only the specialised low memory thing?
|
|
vfs_free_unused_vnodes(B_LOW_MEMORY_WARNING);
|
|
}
|
|
if (sUsedSems == sMaxSems) {
|
|
// try again with more enthusiasm
|
|
vfs_free_unused_vnodes(B_LOW_MEMORY_CRITICAL);
|
|
}
|
|
if (sUsedSems == sMaxSems)
|
|
return B_NO_MORE_SEMS;
|
|
|
|
if (name == NULL)
|
|
name = "unnamed semaphore";
|
|
|
|
nameLength = strlen(name) + 1;
|
|
nameLength = min(nameLength, B_OS_NAME_LENGTH);
|
|
tempName = (char *)malloc(nameLength);
|
|
if (tempName == NULL)
|
|
return B_NO_MEMORY;
|
|
strlcpy(tempName, name, nameLength);
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LIST_LOCK();
|
|
|
|
// get the first slot from the free list
|
|
sem = sFreeSemsHead;
|
|
if (sem) {
|
|
// remove it from the free list
|
|
sFreeSemsHead = sem->u.unused.next;
|
|
if (!sFreeSemsHead)
|
|
sFreeSemsTail = NULL;
|
|
|
|
// init the slot
|
|
GRAB_SEM_LOCK(*sem);
|
|
sem->id = sem->u.unused.next_id;
|
|
sem->u.used.count = count;
|
|
clear_thread_queue(&sem->u.used.queue);
|
|
sem->u.used.name = tempName;
|
|
sem->u.used.owner = owner;
|
|
id = sem->id;
|
|
RELEASE_SEM_LOCK(*sem);
|
|
|
|
atomic_add(&sUsedSems, 1);
|
|
}
|
|
|
|
RELEASE_SEM_LIST_LOCK();
|
|
restore_interrupts(state);
|
|
|
|
if (!sem)
|
|
free(tempName);
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
sem_id
|
|
create_sem(int32 count, const char *name)
|
|
{
|
|
return create_sem_etc(count, name, team_get_kernel_team_id());
|
|
}
|
|
|
|
|
|
status_t
|
|
delete_sem(sem_id id)
|
|
{
|
|
struct thread_queue releaseQueue;
|
|
int32 releasedThreads;
|
|
struct thread *thread;
|
|
cpu_status state;
|
|
int32 slot;
|
|
char *name;
|
|
|
|
if (sSemsActive == false)
|
|
return B_NO_MORE_SEMS;
|
|
if (id < 0)
|
|
return B_BAD_SEM_ID;
|
|
|
|
slot = id % sMaxSems;
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
|
|
if (sSems[slot].id != id) {
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
restore_interrupts(state);
|
|
TRACE(("delete_sem: invalid sem_id %ld\n", id));
|
|
return B_BAD_SEM_ID;
|
|
}
|
|
|
|
releasedThreads = 0;
|
|
clear_thread_queue(&releaseQueue);
|
|
|
|
// free any threads waiting for this semaphore
|
|
while ((thread = thread_dequeue(&sSems[slot].u.used.queue)) != NULL) {
|
|
thread->state = B_THREAD_READY;
|
|
thread->sem.acquire_status = B_BAD_SEM_ID;
|
|
thread->sem.count = 0;
|
|
thread_enqueue(thread, &releaseQueue);
|
|
releasedThreads++;
|
|
}
|
|
|
|
sSems[slot].id = -1;
|
|
name = sSems[slot].u.used.name;
|
|
sSems[slot].u.used.name = NULL;
|
|
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
|
|
// append slot to the free list
|
|
GRAB_SEM_LIST_LOCK();
|
|
free_sem_slot(slot, id + sMaxSems);
|
|
atomic_add(&sUsedSems, -1);
|
|
RELEASE_SEM_LIST_LOCK();
|
|
|
|
if (releasedThreads > 0) {
|
|
GRAB_THREAD_LOCK();
|
|
while ((thread = thread_dequeue(&releaseQueue)) != NULL) {
|
|
scheduler_enqueue_in_run_queue(thread);
|
|
}
|
|
scheduler_reschedule();
|
|
RELEASE_THREAD_LOCK();
|
|
}
|
|
|
|
restore_interrupts(state);
|
|
|
|
free(name);
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
/** Called from a timer handler. Wakes up a semaphore */
|
|
|
|
static int32
|
|
sem_timeout(timer *data)
|
|
{
|
|
struct sem_timeout_args *args = (struct sem_timeout_args *)data->entry.prev;
|
|
struct thread *thread;
|
|
int slot;
|
|
int state;
|
|
struct thread_queue wakeupQueue;
|
|
|
|
thread = thread_get_thread_struct(args->blocked_thread);
|
|
if (thread == NULL)
|
|
return B_HANDLED_INTERRUPT;
|
|
slot = args->blocked_sem_id % sMaxSems;
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
|
|
TRACE(("sem_timeout: called on 0x%x sem %ld, tid %ld\n", data, args->blocked_sem_id, args->blocked_thread));
|
|
|
|
if (sSems[slot].id != args->blocked_sem_id) {
|
|
// this thread was not waiting on this semaphore
|
|
panic("sem_timeout: thid %ld was trying to wait on sem %ld which doesn't exist!\n",
|
|
args->blocked_thread, args->blocked_sem_id);
|
|
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
restore_interrupts(state);
|
|
return B_HANDLED_INTERRUPT;
|
|
}
|
|
|
|
clear_thread_queue(&wakeupQueue);
|
|
remove_thread_from_sem(thread, &sSems[slot], &wakeupQueue, B_TIMED_OUT);
|
|
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
|
|
GRAB_THREAD_LOCK();
|
|
// put the threads in the run q here to make sure we dont deadlock in sem_interrupt_thread
|
|
while ((thread = thread_dequeue(&wakeupQueue)) != NULL) {
|
|
scheduler_enqueue_in_run_queue(thread);
|
|
}
|
|
RELEASE_THREAD_LOCK();
|
|
|
|
restore_interrupts(state);
|
|
|
|
return B_INVOKE_SCHEDULER;
|
|
}
|
|
|
|
|
|
status_t
|
|
acquire_sem(sem_id id)
|
|
{
|
|
return switch_sem_etc(-1, id, 1, 0, 0);
|
|
}
|
|
|
|
|
|
status_t
|
|
acquire_sem_etc(sem_id id, int32 count, uint32 flags, bigtime_t timeout)
|
|
{
|
|
return switch_sem_etc(-1, id, count, flags, timeout);
|
|
}
|
|
|
|
|
|
status_t
|
|
switch_sem(sem_id toBeReleased, sem_id toBeAcquired)
|
|
{
|
|
return switch_sem_etc(toBeReleased, toBeAcquired, 1, 0, 0);
|
|
}
|
|
|
|
|
|
status_t
|
|
switch_sem_etc(sem_id semToBeReleased, sem_id id, int32 count,
|
|
uint32 flags, bigtime_t timeout)
|
|
{
|
|
int slot = id % sMaxSems;
|
|
int state;
|
|
status_t status = B_OK;
|
|
|
|
if (sSemsActive == false)
|
|
return B_NO_MORE_SEMS;
|
|
|
|
if (!kernel_startup && !are_interrupts_enabled())
|
|
panic("acquire_sem_etc: called with interrupts disabled for sem %#lx\n", id);
|
|
|
|
if (id < 0)
|
|
return B_BAD_SEM_ID;
|
|
if (count <= 0
|
|
|| (flags & (B_RELATIVE_TIMEOUT | B_ABSOLUTE_TIMEOUT)) == (B_RELATIVE_TIMEOUT | B_ABSOLUTE_TIMEOUT)) {
|
|
return B_BAD_VALUE;
|
|
}
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
|
|
if (sSems[slot].id != id) {
|
|
TRACE(("acquire_sem_etc: bad sem_id %ld\n", id));
|
|
status = B_BAD_SEM_ID;
|
|
goto err;
|
|
}
|
|
|
|
// ToDo: the B_CHECK_PERMISSION flag should be made private, as it
|
|
// doesn't have any use outside the kernel
|
|
if ((flags & B_CHECK_PERMISSION) != 0
|
|
&& sSems[slot].u.used.owner == team_get_kernel_team_id()) {
|
|
dprintf("thread %ld tried to acquire kernel semaphore %ld.\n",
|
|
thread_get_current_thread_id(), id);
|
|
status = B_NOT_ALLOWED;
|
|
goto err;
|
|
}
|
|
|
|
if (sSems[slot].u.used.count - count < 0 && (flags & B_RELATIVE_TIMEOUT) != 0
|
|
&& timeout <= 0) {
|
|
// immediate timeout
|
|
status = B_WOULD_BLOCK;
|
|
goto err;
|
|
}
|
|
|
|
if ((sSems[slot].u.used.count -= count) < 0) {
|
|
// we need to block
|
|
struct thread *thread = thread_get_current_thread();
|
|
timer timeout_timer; // stick it on the stack, since we may be blocking here
|
|
struct sem_timeout_args args;
|
|
|
|
TRACE(("acquire_sem_etc(id = %ld): block name = %s, thread = %p,"
|
|
" name = %s\n", id, sSems[slot].u.used.name, thread, thread->name));
|
|
|
|
// do a quick check to see if the thread has any pending signals
|
|
// this should catch most of the cases where the thread had a signal
|
|
if (((flags & B_CAN_INTERRUPT) && (thread->sig_pending & ~thread->sig_block_mask) != 0)
|
|
|| ((flags & B_KILL_CAN_INTERRUPT)
|
|
&& (thread->sig_pending & KILL_SIGNALS))) {
|
|
sSems[slot].u.used.count += count;
|
|
status = B_INTERRUPTED;
|
|
goto err;
|
|
}
|
|
|
|
if ((flags & (B_RELATIVE_TIMEOUT | B_ABSOLUTE_TIMEOUT)) == 0)
|
|
timeout = B_INFINITE_TIMEOUT;
|
|
|
|
thread->next_state = B_THREAD_WAITING;
|
|
thread->sem.flags = flags;
|
|
thread->sem.blocking = id;
|
|
thread->sem.acquire_count = count;
|
|
thread->sem.count = min(-sSems[slot].u.used.count, count);
|
|
// store the count we need to restore upon release
|
|
thread->sem.acquire_status = B_NO_ERROR;
|
|
thread_enqueue(thread, &sSems[slot].u.used.queue);
|
|
|
|
if (timeout != B_INFINITE_TIMEOUT) {
|
|
TRACE(("sem_acquire_etc: setting timeout sem for %Ld usecs, semid %d, tid %d\n",
|
|
timeout, id, thread->id));
|
|
|
|
// set up an event to go off with the thread struct as the data
|
|
args.blocked_sem_id = id;
|
|
args.blocked_thread = thread->id;
|
|
args.sem_count = count;
|
|
|
|
// ToDo: another evil hack: pass the args into timer->entry.prev
|
|
timeout_timer.entry.prev = (qent *)&args;
|
|
add_timer(&timeout_timer, &sem_timeout, timeout,
|
|
flags & B_RELATIVE_TIMEOUT ?
|
|
B_ONE_SHOT_RELATIVE_TIMER : B_ONE_SHOT_ABSOLUTE_TIMER);
|
|
}
|
|
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
|
|
if (semToBeReleased >= B_OK)
|
|
release_sem_etc(semToBeReleased, 1, B_DO_NOT_RESCHEDULE);
|
|
|
|
GRAB_THREAD_LOCK();
|
|
// check again to see if a signal is pending.
|
|
// it may have been delivered while setting up the sem, though it's pretty unlikely
|
|
if (((flags & B_CAN_INTERRUPT)
|
|
&& (thread->sig_pending & ~thread->sig_block_mask) != 0)
|
|
|| ((flags & B_KILL_CAN_INTERRUPT)
|
|
&& (thread->sig_pending & KILL_SIGNALS))) {
|
|
struct thread_queue wakeupQueue;
|
|
// ok, so a tiny race happened where a signal was delivered to this thread while
|
|
// it was setting up the sem. We can only be sure a signal wasn't delivered
|
|
// here, since the threadlock is held. The previous check would have found most
|
|
// instances, but there was a race, so we have to handle it. It'll be more messy...
|
|
clear_thread_queue(&wakeupQueue);
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
if (sSems[slot].id == id) {
|
|
remove_thread_from_sem(thread, &sSems[slot], &wakeupQueue, B_INTERRUPTED);
|
|
}
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
while ((thread = thread_dequeue(&wakeupQueue)) != NULL) {
|
|
scheduler_enqueue_in_run_queue(thread);
|
|
}
|
|
// fall through and reschedule since another thread with a higher priority may have been woken up
|
|
}
|
|
scheduler_reschedule();
|
|
RELEASE_THREAD_LOCK();
|
|
|
|
if (timeout != B_INFINITE_TIMEOUT) {
|
|
if (thread->sem.acquire_status != B_TIMED_OUT) {
|
|
// cancel the timer event, the sem may have been deleted or interrupted
|
|
// with the timer still active
|
|
cancel_timer(&timeout_timer);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
if (thread->sem.acquire_status >= B_OK)
|
|
sSems[slot].u.used.last_acquirer = thread_get_current_thread_id();
|
|
#endif
|
|
|
|
restore_interrupts(state);
|
|
|
|
TRACE(("acquire_sem_etc(id = %ld): exit block name = %s, "
|
|
"thread = %p (%s)\n", id, sSems[slot].u.used.name, thread,
|
|
thread->name));
|
|
return thread->sem.acquire_status;
|
|
} else {
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
sSems[slot].u.used.last_acquirer = thread_get_current_thread_id();
|
|
#endif
|
|
}
|
|
|
|
err:
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
restore_interrupts(state);
|
|
|
|
#if 0
|
|
if (status == B_NOT_ALLOWED)
|
|
_user_debugger("Thread tried to acquire kernel semaphore.");
|
|
#endif
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
status_t
|
|
release_sem(sem_id id)
|
|
{
|
|
return release_sem_etc(id, 1, 0);
|
|
}
|
|
|
|
|
|
status_t
|
|
release_sem_etc(sem_id id, int32 count, uint32 flags)
|
|
{
|
|
struct thread_queue releaseQueue;
|
|
int32 slot = id % sMaxSems;
|
|
cpu_status state;
|
|
status_t status = B_OK;
|
|
|
|
if (sSemsActive == false)
|
|
return B_NO_MORE_SEMS;
|
|
if (id < 0)
|
|
return B_BAD_SEM_ID;
|
|
if (count <= 0 && (flags & B_RELEASE_ALL) == 0)
|
|
return B_BAD_VALUE;
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
|
|
if (sSems[slot].id != id) {
|
|
TRACE(("sem_release_etc: invalid sem_id %ld\n", id));
|
|
status = B_BAD_SEM_ID;
|
|
goto err;
|
|
}
|
|
|
|
// ToDo: the B_CHECK_PERMISSION flag should be made private, as it
|
|
// doesn't have any use outside the kernel
|
|
if ((flags & B_CHECK_PERMISSION) != 0
|
|
&& sSems[slot].u.used.owner == team_get_kernel_team_id()) {
|
|
dprintf("thread %ld tried to release kernel semaphore.\n", thread_get_current_thread()->id);
|
|
status = B_NOT_ALLOWED;
|
|
goto err;
|
|
}
|
|
|
|
#ifdef DEBUG_LAST_ACQUIRER
|
|
sSems[slot].u.used.last_acquirer = -1;
|
|
#endif
|
|
|
|
// clear out a queue we will use to hold all of the threads that we will have to
|
|
// put back into the run list. This is done so the thread lock wont be held
|
|
// while this sems lock is held since the two locks are grabbed in the other
|
|
// order in sem_interrupt_thread.
|
|
clear_thread_queue(&releaseQueue);
|
|
|
|
if (flags & B_RELEASE_ALL) {
|
|
count = -sSems[slot].u.used.count;
|
|
|
|
// is there anything to do for us at all?
|
|
if (count == 0)
|
|
goto err;
|
|
}
|
|
|
|
while (count > 0) {
|
|
int delta = count;
|
|
if (sSems[slot].u.used.count < 0) {
|
|
struct thread *thread = thread_lookat_queue(&sSems[slot].u.used.queue);
|
|
|
|
delta = min(count, thread->sem.count);
|
|
thread->sem.count -= delta;
|
|
if (thread->sem.count <= 0) {
|
|
// release this thread
|
|
thread = thread_dequeue(&sSems[slot].u.used.queue);
|
|
thread_enqueue(thread, &releaseQueue);
|
|
thread->state = B_THREAD_READY;
|
|
thread->sem.count = 0;
|
|
}
|
|
} else if (flags & B_RELEASE_IF_WAITING_ONLY)
|
|
break;
|
|
|
|
sSems[slot].u.used.count += delta;
|
|
count -= delta;
|
|
}
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
|
|
// pull off any items in the release queue and put them in the run queue
|
|
if (releaseQueue.head != NULL) {
|
|
struct thread *thread;
|
|
|
|
GRAB_THREAD_LOCK();
|
|
while ((thread = thread_dequeue(&releaseQueue)) != NULL) {
|
|
// temporarily place thread in a run queue with a higher priority to boost it up
|
|
thread->next_priority = thread->priority >= B_FIRST_REAL_TIME_PRIORITY ?
|
|
thread->priority : thread->priority + 1;
|
|
scheduler_enqueue_in_run_queue(thread);
|
|
}
|
|
if ((flags & B_DO_NOT_RESCHEDULE) == 0)
|
|
scheduler_reschedule();
|
|
|
|
RELEASE_THREAD_LOCK();
|
|
}
|
|
goto outnolock;
|
|
|
|
err:
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
outnolock:
|
|
restore_interrupts(state);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
status_t
|
|
get_sem_count(sem_id id, int32 *thread_count)
|
|
{
|
|
int slot;
|
|
int state;
|
|
|
|
if (sSemsActive == false)
|
|
return B_NO_MORE_SEMS;
|
|
if (id < 0)
|
|
return B_BAD_SEM_ID;
|
|
if (thread_count == NULL)
|
|
return EINVAL;
|
|
|
|
slot = id % sMaxSems;
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
|
|
if (sSems[slot].id != id) {
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
restore_interrupts(state);
|
|
TRACE(("sem_get_count: invalid sem_id %ld\n", id));
|
|
return B_BAD_SEM_ID;
|
|
}
|
|
|
|
*thread_count = sSems[slot].u.used.count;
|
|
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
restore_interrupts(state);
|
|
|
|
return B_NO_ERROR;
|
|
}
|
|
|
|
|
|
/** Fills the thread_info structure with information from the specified
|
|
* thread.
|
|
* The thread lock must be held when called.
|
|
*/
|
|
|
|
static void
|
|
fill_sem_info(struct sem_entry *sem, sem_info *info, size_t size)
|
|
{
|
|
info->sem = sem->id;
|
|
info->team = sem->u.used.owner;
|
|
strlcpy(info->name, sem->u.used.name, sizeof(info->name));
|
|
info->count = sem->u.used.count;
|
|
|
|
// ToDo: not sure if this is the latest holder, or the next
|
|
// holder...
|
|
if (sem->u.used.queue.head != NULL)
|
|
info->latest_holder = sem->u.used.queue.head->id;
|
|
else
|
|
info->latest_holder = -1;
|
|
}
|
|
|
|
|
|
/** The underscore is needed for binary compatibility with BeOS.
|
|
* OS.h contains the following macro:
|
|
* #define get_sem_info(sem, info) \
|
|
* _get_sem_info((sem), (info), sizeof(*(info)))
|
|
*/
|
|
|
|
status_t
|
|
_get_sem_info(sem_id id, struct sem_info *info, size_t size)
|
|
{
|
|
status_t status = B_OK;
|
|
int state;
|
|
int slot;
|
|
|
|
if (!sSemsActive)
|
|
return B_NO_MORE_SEMS;
|
|
if (id < 0)
|
|
return B_BAD_SEM_ID;
|
|
if (info == NULL || size != sizeof(sem_info))
|
|
return B_BAD_VALUE;
|
|
|
|
slot = id % sMaxSems;
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
|
|
if (sSems[slot].id != id) {
|
|
status = B_BAD_SEM_ID;
|
|
TRACE(("get_sem_info: invalid sem_id %ld\n", id));
|
|
} else
|
|
fill_sem_info(&sSems[slot], info, size);
|
|
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
restore_interrupts(state);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/** The underscore is needed for binary compatibility with BeOS.
|
|
* OS.h contains the following macro:
|
|
* #define get_next_sem_info(team, cookie, info) \
|
|
* _get_next_sem_info((team), (cookie), (info), sizeof(*(info)))
|
|
*/
|
|
|
|
status_t
|
|
_get_next_sem_info(team_id team, int32 *_cookie, struct sem_info *info, size_t size)
|
|
{
|
|
int state;
|
|
int slot;
|
|
bool found = false;
|
|
|
|
if (!sSemsActive)
|
|
return B_NO_MORE_SEMS;
|
|
if (_cookie == NULL || info == NULL || size != sizeof(sem_info))
|
|
return B_BAD_VALUE;
|
|
|
|
if (team == B_CURRENT_TEAM)
|
|
team = team_get_current_team_id();
|
|
/* prevents sSems[].owner == -1 >= means owned by a port */
|
|
if (team < 0 || !team_is_valid(team))
|
|
return B_BAD_TEAM_ID;
|
|
|
|
slot = *_cookie;
|
|
if (slot >= sMaxSems)
|
|
return B_BAD_VALUE;
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LIST_LOCK();
|
|
|
|
while (slot < sMaxSems) {
|
|
if (sSems[slot].id != -1 && sSems[slot].u.used.owner == team) {
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
if (sSems[slot].id != -1 && sSems[slot].u.used.owner == team) {
|
|
// found one!
|
|
fill_sem_info(&sSems[slot], info, size);
|
|
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
slot++;
|
|
found = true;
|
|
break;
|
|
}
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
}
|
|
slot++;
|
|
}
|
|
RELEASE_SEM_LIST_LOCK();
|
|
restore_interrupts(state);
|
|
|
|
if (!found)
|
|
return B_BAD_VALUE;
|
|
|
|
*_cookie = slot;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
set_sem_owner(sem_id id, team_id team)
|
|
{
|
|
int state;
|
|
int slot;
|
|
|
|
if (sSemsActive == false)
|
|
return B_NO_MORE_SEMS;
|
|
if (id < 0)
|
|
return B_BAD_SEM_ID;
|
|
if (team < 0 || !team_is_valid(team))
|
|
return B_BAD_TEAM_ID;
|
|
|
|
slot = id % sMaxSems;
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
|
|
if (sSems[slot].id != id) {
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
restore_interrupts(state);
|
|
TRACE(("set_sem_owner: invalid sem_id %ld\n", id));
|
|
return B_BAD_SEM_ID;
|
|
}
|
|
|
|
// ToDo: this is a small race condition: the team ID could already
|
|
// be invalid at this point - we would lose one semaphore slot in
|
|
// this case!
|
|
// The only safe way to do this is to prevent either team (the new
|
|
// or the old owner) from dying until we leave the spinlock.
|
|
sSems[slot].u.used.owner = team;
|
|
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
restore_interrupts(state);
|
|
|
|
return B_NO_ERROR;
|
|
}
|
|
|
|
|
|
/** Wake up a thread that's blocked on a semaphore
|
|
* this function must be entered with interrupts disabled and THREADLOCK held
|
|
*/
|
|
|
|
status_t
|
|
sem_interrupt_thread(struct thread *thread)
|
|
{
|
|
struct thread_queue wakeupQueue;
|
|
int32 slot;
|
|
|
|
TRACE(("sem_interrupt_thread: called on thread %p (%d), blocked on sem 0x%x\n",
|
|
thread, thread->id, thread->sem.blocking));
|
|
|
|
if (thread->state != B_THREAD_WAITING || thread->sem.blocking < 0)
|
|
return B_BAD_VALUE;
|
|
if ((thread->sem.flags & B_CAN_INTERRUPT) == 0
|
|
&& ((thread->sem.flags & B_KILL_CAN_INTERRUPT) == 0
|
|
|| (thread->sig_pending & KILL_SIGNALS) == 0)) {
|
|
return B_NOT_ALLOWED;
|
|
}
|
|
|
|
slot = thread->sem.blocking % sMaxSems;
|
|
|
|
GRAB_SEM_LOCK(sSems[slot]);
|
|
|
|
if (sSems[slot].id != thread->sem.blocking) {
|
|
panic("sem_interrupt_thread: thread 0x%lx sez it's blocking on sem 0x%lx, but that sem doesn't exist!\n", thread->id, thread->sem.blocking);
|
|
}
|
|
|
|
clear_thread_queue(&wakeupQueue);
|
|
if (remove_thread_from_sem(thread, &sSems[slot], &wakeupQueue, B_INTERRUPTED) != B_OK) {
|
|
panic("sem_interrupt_thread: thread 0x%lx not found in sem 0x%lx's wait queue\n",
|
|
thread->id, thread->sem.blocking);
|
|
}
|
|
|
|
RELEASE_SEM_LOCK(sSems[slot]);
|
|
|
|
while ((thread = thread_dequeue(&wakeupQueue)) != NULL) {
|
|
scheduler_enqueue_in_run_queue(thread);
|
|
}
|
|
|
|
return B_NO_ERROR;
|
|
}
|
|
|
|
|
|
/** Forcibly removes a thread from a semaphores wait queue. May have to wake up
|
|
* other threads in the process. All threads that need to be woken up are added
|
|
* to the passed in thread_queue.
|
|
* Must be called with semaphore lock held.
|
|
*/
|
|
|
|
static int
|
|
remove_thread_from_sem(struct thread *thread, struct sem_entry *sem,
|
|
struct thread_queue *queue, status_t acquireStatus)
|
|
{
|
|
// remove the thread from the queue and place it in the supplied queue
|
|
if (thread_dequeue_id(&sem->u.used.queue, thread->id) != thread)
|
|
return B_ENTRY_NOT_FOUND;
|
|
|
|
sem->u.used.count += thread->sem.acquire_count;
|
|
thread->state = thread->next_state = B_THREAD_READY;
|
|
thread->sem.acquire_status = acquireStatus;
|
|
thread_enqueue(thread, queue);
|
|
|
|
// now see if more threads need to be woken up
|
|
while (sem->u.used.count > 0
|
|
&& thread_lookat_queue(&sem->u.used.queue) != NULL) {
|
|
int32 delta = min(thread->sem.count, sem->u.used.count);
|
|
|
|
thread->sem.count -= delta;
|
|
if (thread->sem.count <= 0) {
|
|
thread = thread_dequeue(&sem->u.used.queue);
|
|
thread->state = thread->next_state = B_THREAD_READY;
|
|
thread_enqueue(thread, queue);
|
|
}
|
|
sem->u.used.count -= delta;
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
/** this function cycles through the sem table, deleting all the sems that are owned by
|
|
* the passed team_id
|
|
*/
|
|
|
|
int
|
|
sem_delete_owned_sems(team_id owner)
|
|
{
|
|
int state;
|
|
int i;
|
|
int count = 0;
|
|
|
|
// ToDo: that looks horribly inefficient - maybe it would be better
|
|
// to have them in a list in the team
|
|
|
|
if (owner < 0)
|
|
return B_BAD_TEAM_ID;
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LIST_LOCK();
|
|
|
|
for (i = 0; i < sMaxSems; i++) {
|
|
if (sSems[i].id != -1 && sSems[i].u.used.owner == owner) {
|
|
sem_id id = sSems[i].id;
|
|
|
|
RELEASE_SEM_LIST_LOCK();
|
|
restore_interrupts(state);
|
|
|
|
delete_sem(id);
|
|
count++;
|
|
|
|
state = disable_interrupts();
|
|
GRAB_SEM_LIST_LOCK();
|
|
}
|
|
}
|
|
|
|
RELEASE_SEM_LIST_LOCK();
|
|
restore_interrupts(state);
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
int32
|
|
sem_max_sems(void)
|
|
{
|
|
return sMaxSems;
|
|
}
|
|
|
|
|
|
int32
|
|
sem_used_sems(void)
|
|
{
|
|
return sUsedSems;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
sem_id
|
|
_user_create_sem(int32 count, const char *userName)
|
|
{
|
|
char name[B_OS_NAME_LENGTH];
|
|
|
|
if (userName == NULL)
|
|
return create_sem_etc(count, NULL, team_get_current_team_id());
|
|
|
|
if (!IS_USER_ADDRESS(userName)
|
|
|| user_strlcpy(name, userName, B_OS_NAME_LENGTH) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
return create_sem_etc(count, name, team_get_current_team_id());
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_delete_sem(sem_id id)
|
|
{
|
|
return delete_sem(id);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_acquire_sem(sem_id id)
|
|
{
|
|
return switch_sem_etc(-1, id, 1, B_CAN_INTERRUPT | B_CHECK_PERMISSION, 0);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_acquire_sem_etc(sem_id id, int32 count, uint32 flags, bigtime_t timeout)
|
|
{
|
|
return switch_sem_etc(-1, id, count, flags | B_CAN_INTERRUPT | B_CHECK_PERMISSION, timeout);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_switch_sem(sem_id releaseSem, sem_id id)
|
|
{
|
|
return switch_sem_etc(releaseSem, id, 1, B_CAN_INTERRUPT | B_CHECK_PERMISSION, 0);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_switch_sem_etc(sem_id releaseSem, sem_id id, int32 count, uint32 flags, bigtime_t timeout)
|
|
{
|
|
return switch_sem_etc(releaseSem, id, count, flags | B_CAN_INTERRUPT | B_CHECK_PERMISSION, timeout);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_release_sem(sem_id id)
|
|
{
|
|
return release_sem_etc(id, 1, B_CHECK_PERMISSION);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_release_sem_etc(sem_id id, int32 count, uint32 flags)
|
|
{
|
|
return release_sem_etc(id, count, flags | B_CHECK_PERMISSION);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_get_sem_count(sem_id id, int32 *userCount)
|
|
{
|
|
status_t status;
|
|
int32 count;
|
|
|
|
if (userCount == NULL || !IS_USER_ADDRESS(userCount))
|
|
return B_BAD_ADDRESS;
|
|
|
|
status = get_sem_count(id, &count);
|
|
if (status == B_OK && user_memcpy(userCount, &count, sizeof(int32)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_get_sem_info(sem_id id, struct sem_info *userInfo, size_t size)
|
|
{
|
|
struct sem_info info;
|
|
status_t status;
|
|
|
|
if (userInfo == NULL || !IS_USER_ADDRESS(userInfo))
|
|
return B_BAD_ADDRESS;
|
|
|
|
status = _get_sem_info(id, &info, size);
|
|
if (status == B_OK && user_memcpy(userInfo, &info, size) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_get_next_sem_info(team_id team, int32 *userCookie, struct sem_info *userInfo,
|
|
size_t size)
|
|
{
|
|
struct sem_info info;
|
|
int32 cookie;
|
|
status_t status;
|
|
|
|
if (userCookie == NULL || userInfo == NULL
|
|
|| !IS_USER_ADDRESS(userCookie) || !IS_USER_ADDRESS(userInfo)
|
|
|| user_memcpy(&cookie, userCookie, sizeof(int32)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
status = _get_next_sem_info(team, &cookie, &info, size);
|
|
|
|
if (status == B_OK) {
|
|
if (user_memcpy(userInfo, &info, size) < B_OK
|
|
|| user_memcpy(userCookie, &cookie, sizeof(int32)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_set_sem_owner(sem_id id, team_id team)
|
|
{
|
|
return set_sem_owner(id, team);
|
|
}
|