T
TGOS
I was thinking about it for a while, a mutex written in C and without
disabling any interrupts. Is it possible?
typdef struct mutex {
unsigned int owner1;
unsigend int owner2;
} *mutex;
void mutex_init(mutex *m) {
m->owner1 = 0;
m->owner2 = 0;
}
void mutex_lock(mutex *m) {
int *owner1;
int *owner2;
int currentThread;
currentThread = getThreadIdOfCurrentThread();
owner1 = &(m->owner1);
owner2 = &(m->owner2);
while ( (*owner1 += currentThread) != currentThread &
/* Note the &, not &&, that is important */
(*owner2 ^= currentThread) != currentThread) ) {
*owner1 -= currentThread;
*owner2 ^= currentThread;
mutex_sleep(m);
}
}
I believe that no matter where a context-switch takes place, the code
should be safe, with one exception: It may happen that two threads, trying
to lock the mutex are both sent to sleep, although nobody has the mutex
locked. So mutex_sleep() may not be an enternal sleep, that is only
interrupted by a wake up. Instead it should be a sleep that wakes up on
it's own after some ms.
ADD and XOR are atomic operations. Either they succeed or they don't. If
both owers are 0, both will have the ID of the running thread. Otherwise
they will have some other, arbitrary value, with the current thread
falling into the while loop and restoring the old values again. This is
possible because ADD and XOR are both reversible operations, so you can
easily undo them without backing up the original value. The ADD may cause
a register overflow, but the SUB will then cause an underflow and the
result is correct again.
Why are there two operations? Why not just XOR or just ADD? Well, with ADD
I can think of a scenario with 4 threads, where one is locking the mutex
and then the 3 other try to lock it. Depending on ID, order of tries and
twice a very evil context switch in the middle of the while condition, a
situation may occur where 2 threads fail to lock it, but the third one
believes it has locked the mutex, athough another thread still holds the
lock. A similar situation can occur with XOR. But that both fail is very
unlikely, if not impossible (I'm not good engouh at math to proof or
disproof that it is or isn't possible; it's just an assumption). So it may
not be a *perfect* mutex, but we are also not using perfect OSes. Every OS
has one or more possible dead lock situations and they have no dead lock
detection either (if such a situation occurs, only resetting the system
will resolve it), still we live with it.
Unlock is very easy:
unlock(mutex *m) {
int currentThread;
currentThread = getThreadIdOfCurrentThread();
m->owner1 -= currentThread;
m->owner2 ^= currentThread;
mutex_wakeUp(m);
}
Using 64 bit for the add would make it even more stable, but 64 bit add is
not atomic on 32 bit systems, is it?
disabling any interrupts. Is it possible?
typdef struct mutex {
unsigned int owner1;
unsigend int owner2;
} *mutex;
void mutex_init(mutex *m) {
m->owner1 = 0;
m->owner2 = 0;
}
void mutex_lock(mutex *m) {
int *owner1;
int *owner2;
int currentThread;
currentThread = getThreadIdOfCurrentThread();
owner1 = &(m->owner1);
owner2 = &(m->owner2);
while ( (*owner1 += currentThread) != currentThread &
/* Note the &, not &&, that is important */
(*owner2 ^= currentThread) != currentThread) ) {
*owner1 -= currentThread;
*owner2 ^= currentThread;
mutex_sleep(m);
}
}
I believe that no matter where a context-switch takes place, the code
should be safe, with one exception: It may happen that two threads, trying
to lock the mutex are both sent to sleep, although nobody has the mutex
locked. So mutex_sleep() may not be an enternal sleep, that is only
interrupted by a wake up. Instead it should be a sleep that wakes up on
it's own after some ms.
ADD and XOR are atomic operations. Either they succeed or they don't. If
both owers are 0, both will have the ID of the running thread. Otherwise
they will have some other, arbitrary value, with the current thread
falling into the while loop and restoring the old values again. This is
possible because ADD and XOR are both reversible operations, so you can
easily undo them without backing up the original value. The ADD may cause
a register overflow, but the SUB will then cause an underflow and the
result is correct again.
Why are there two operations? Why not just XOR or just ADD? Well, with ADD
I can think of a scenario with 4 threads, where one is locking the mutex
and then the 3 other try to lock it. Depending on ID, order of tries and
twice a very evil context switch in the middle of the while condition, a
situation may occur where 2 threads fail to lock it, but the third one
believes it has locked the mutex, athough another thread still holds the
lock. A similar situation can occur with XOR. But that both fail is very
unlikely, if not impossible (I'm not good engouh at math to proof or
disproof that it is or isn't possible; it's just an assumption). So it may
not be a *perfect* mutex, but we are also not using perfect OSes. Every OS
has one or more possible dead lock situations and they have no dead lock
detection either (if such a situation occurs, only resetting the system
will resolve it), still we live with it.
Unlock is very easy:
unlock(mutex *m) {
int currentThread;
currentThread = getThreadIdOfCurrentThread();
m->owner1 -= currentThread;
m->owner2 ^= currentThread;
mutex_wakeUp(m);
}
Using 64 bit for the add would make it even more stable, but 64 bit add is
not atomic on 32 bit systems, is it?