Recursive Mutexes for SMP
Recursive Mutexes for SMP
I have an object X with a function A that locks X. I have another object Y that accesses X by locking it. While it has the lock, it calls A.
Using pure mutexes this situation deadlocks, and the normal solution is recursive mutexes. However, I don't know any implementation of recursive mutexes that doesn't rely on a preexisting thread structure and scheduler, and I need to use my locks at a level where such abstractions don't exist yet.
Anyone have any ideas?
Using pure mutexes this situation deadlocks, and the normal solution is recursive mutexes. However, I don't know any implementation of recursive mutexes that doesn't rely on a preexisting thread structure and scheduler, and I need to use my locks at a level where such abstractions don't exist yet.
Anyone have any ideas?
- Kevin McGuire
- Member
- Posts: 843
- Joined: Tue Nov 09, 2004 12:00 am
- Location: United States
- Contact:
I thought it would work just like a normal mutex except:
If owner attempts to relock the mutex then success, and a counter is incremented. If owner attempts a unlock then the counter is decremented.
So it just worked like a normal mutex with a counter?
Just like the PTHREAD_MUTEX_RECURSIVE in the POSIX threading support in LIBC.
If owner attempts to relock the mutex then success, and a counter is incremented. If owner attempts a unlock then the counter is decremented.
So it just worked like a normal mutex with a counter?
Just like the PTHREAD_MUTEX_RECURSIVE in the POSIX threading support in LIBC.
Last edited by Kevin McGuire on Tue May 29, 2007 5:46 pm, edited 2 times in total.
- Kevin McGuire
- Member
- Posts: 843
- Joined: Tue Nov 09, 2004 12:00 am
- Location: United States
- Contact:
- Kevin McGuire
- Member
- Posts: 843
- Joined: Tue Nov 09, 2004 12:00 am
- Location: United States
- Contact:
- Kevin McGuire
- Member
- Posts: 843
- Joined: Tue Nov 09, 2004 12:00 am
- Location: United States
- Contact:
Oh. I just realized the problem. Try locking it with a value other than one to signal who owns it.
0x0 is free
0x1 - 0xFFFFFFFF is locked.
Then you can determine if you have already locked it. You need to look around since I know there is information in this forum and maybe the Wiki on how to build a identification number for each processor in a SMP architecture.
Brendan would be really helpful for your question. I am sorry.
0x0 is free
0x1 - 0xFFFFFFFF is locked.
Then you can determine if you have already locked it. You need to look around since I know there is information in this forum and maybe the Wiki on how to build a identification number for each processor in a SMP architecture.
Brendan would be really helpful for your question. I am sorry.
- Colonel Kernel
- Member
- Posts: 1437
- Joined: Tue Oct 17, 2006 6:06 pm
- Location: Vancouver, BC, Canada
- Contact:
I will avoid the question entirely by saying that you should re-design your control flow so that recursive mutexes aren't necessary. ![Smile :)](./images/smilies/icon_smile.gif)
Also, I would call them recursive spinlocks, since to me a mutex implies blocking a thread, and at that level there is no "blocking" and there are no "threads" yet.
![Smile :)](./images/smilies/icon_smile.gif)
Also, I would call them recursive spinlocks, since to me a mutex implies blocking a thread, and at that level there is no "blocking" and there are no "threads" yet.
Top three reasons why my OS project died:
- Too much overtime at work
- Got married
- My brain got stuck in an infinite loop while trying to design the memory manager
So I should redesign my control flow so that an "owner" object never calls the "owned" object while it holds the lock of that object? That seems to defeat the purpose of both object orientation and locks, though I agree with the principle that Better Algorithms are Best.Colonel Kernel wrote:I will avoid the question entirely by saying that you should re-design your control flow so that recursive mutexes aren't necessary.
Anyway, I looked it up in the Intel Bible. If your processor supports the CPUID instruction, it will return the Initial Local APIC ID, a guaranteed-unique identifier for each processor, in bits 24-31 of EBX when CPUID is run with EAX=1. Some short assembler processing turns this into a processor identifier my recursive spinlocks can use.
Code: Select all
function ProcessorID(): longword; assembler;
label
_return;
asm
mov Result,0
pushf
pop eax
shr eax,21
and eax,1
cmp eax,0
je _return
mov eax,1
cpuid
shr ebx,24
mov Result,ebx
_return: nop
end ['EAX','EBX'];
- Colonel Kernel
- Member
- Posts: 1437
- Joined: Tue Oct 17, 2006 6:06 pm
- Location: Vancouver, BC, Canada
- Contact:
You should make either the object itself or its client responsible for locking, not both.Crazed123 wrote:So I should redesign my control flow so that an "owner" object never calls the "owned" object while it holds the lock of that object? That seems to defeat the purpose of both object orientation and locks, though I agree with the principle that Better Algorithms are Best.
Top three reasons why my OS project died:
- Too much overtime at work
- Got married
- My brain got stuck in an infinite loop while trying to design the memory manager
- Combuster
- Member
- Posts: 9301
- Joined: Wed Oct 18, 2006 3:45 am
- Libera.chat IRC: [com]buster
- Location: On the balcony, where I can actually keep 1½m distance
- Contact:
Well, the advantage of a recursive lock is that you don't need to worry that the caller has done the locking correctly, or that you accidentally get into a recursion.
Recursive locks are easy to implement given an existing spinlock:
(untested)
Recursive locks are easy to implement given an existing spinlock:
(untested)
Code: Select all
typedef struct
{
spinlock_t * s;
thread_t owner;
int lockcount;
} recursive_lock_t;
void lock_recursive(recursive_lock_t * lock)
{
if (lock->owner == threadid)
lock->lockcount++;
else {
lock_spinlock(lock->s);
lock->owner = threadid;
}
}
void unlock_recursive(recursive_lock_t * lock)
{
if (lock->lockcount > 0)
lock->lockcount--;
else {
lock->owner = 0;
unlock_spinlock(lock->s);
}
}
Hi,
For spinlocks, I'd recommend something like (untested):
Of course I've stuffed things up often enough to know the value of automated "stuffed it up" detection.
For the original problem, just create Function B that does the same as Function A without locking, and then make object Y used Function B instead of Function A if it's already got the lock. If code size is a problem, then you could convert Function A into a simple wrapper (i.e. "get lock, call functionB, release lock").
I'd also seriously consider Colonel Kernel's advice: You should make either the object itself or its client responsible for locking, not both..
I'm also curious why different objects access the same data in the first place - it doesn't seem very "OOP" to me (but then I've always prefered the "all data is private data" style of OOP)...
Cheers,
Brendan
Given the choice between recursive spinlocks with dubious design, and bug detection, I'll choose bug detection every time.Combuster wrote:Recursive locks are easy to implement given an existing spinlock:
For spinlocks, I'd recommend something like (untested):
Code: Select all
%macro GET_LOCK 1
%ifdef DEBUGGING
push eax
mov eax,[gs:CPUdata.currentThreadID]
%endif
lock bts [%1],0
je %%gotLock
%%wait:
pause
%ifdef DEBUGGING
cmp [%1 + 4],eax
jne %%noPanic
push %1
push DEADLOCK_DETECTED
jmp kernel_panic
%%noPanic:
%endif
test [%1],1
jne %%wait
lock bts [%1],0
jne %%wait
%%gotLock:
%ifdef DEBUGGING
mov [%1 + 4],eax
pop eax
%endif
%endmacro
%macro FREE_LOCK 1
%ifdef DEBUGGING
push eax
mov eax,[gs:CPUdata.currentThreadID]
cmp [%1 + 4],eax
je %%noPanic
push %1
push FREE_LOCK_WITHOUT_GET_LOCK
jmp kernel_panic
%%noPanic:
%endif
mov dword [%1],0
%ifdef DEBUGGING
mov [%1 + 4],IMPOSSIBLE_THREAD_ID
pop eax
%endif
%endmacro
For the original problem, just create Function B that does the same as Function A without locking, and then make object Y used Function B instead of Function A if it's already got the lock. If code size is a problem, then you could convert Function A into a simple wrapper (i.e. "get lock, call functionB, release lock").
I'd also seriously consider Colonel Kernel's advice: You should make either the object itself or its client responsible for locking, not both..
I'm also curious why different objects access the same data in the first place - it doesn't seem very "OOP" to me (but then I've always prefered the "all data is private data" style of OOP)...
Cheers,
Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.