Critical sections in userspace without syscalls?
Critical sections in userspace without syscalls?
Is it possible to do this in a multitasking, SMP OS? If it is, what does the code look like?
Some alternatives that I've discarded / though about:
The ordinary spinlock-code would only work if the application can disable scheduling or interrupts. If a task gets the spinlock, and then is preempted, the spinlock will never be released, so this will fail. It will also deadlock if the scheduler selects a new task that tries to get the same spinlock.
Another alternative might be to use a variable in user-space, and do a lock sub var,1 / lock add var,1. If this suceeds (CY set), there will be no syscals, but as soon as it fails, syscalls will be needed. This might work (I had a working version before SMP, but it should be possible to make SMP safe).
A special issue is that userspace tampering with section content should not be potentially dangerous to kernel.
So has somebody managed to do this, and if so, how?
Some alternatives that I've discarded / though about:
The ordinary spinlock-code would only work if the application can disable scheduling or interrupts. If a task gets the spinlock, and then is preempted, the spinlock will never be released, so this will fail. It will also deadlock if the scheduler selects a new task that tries to get the same spinlock.
Another alternative might be to use a variable in user-space, and do a lock sub var,1 / lock add var,1. If this suceeds (CY set), there will be no syscals, but as soon as it fails, syscalls will be needed. This might work (I had a working version before SMP, but it should be possible to make SMP safe).
A special issue is that userspace tampering with section content should not be potentially dangerous to kernel.
So has somebody managed to do this, and if so, how?
Re: Critical sections in userspace without syscalls?
It depends on the lock is shared across multiple process or not.
For critical section in user space, do you mean an API for user space to perform lock? If yes, this lock usually does not required to share across multiple process, and you can just use ordinary spinlock without the need of disable interrupt.
If the lock need to be shared across process and kernel, you need regular kernel locks; but in situation requiring such lock you usually better deal with other mechanism like IPC and queue.
For critical section in user space, do you mean an API for user space to perform lock? If yes, this lock usually does not required to share across multiple process, and you can just use ordinary spinlock without the need of disable interrupt.
If the lock need to be shared across process and kernel, you need regular kernel locks; but in situation requiring such lock you usually better deal with other mechanism like IPC and queue.
Re: Critical sections in userspace without syscalls?
It's not shared between processes, but it is shared between multiple threads in the same process.bluemoon wrote:It depends on the lock is shared across multiple process or not.
Yes. I need to protect data shared between multiple userspace threads running in the same process. It would be preferable if no syscalls were needed.bluemoon wrote:For critical section in user space, do you mean an API for user space to perform lock?
How? Look at the following scenario:bluemoon wrote:If yes, this lock usually does not required to share across multiple process, and you can just use ordinary spinlock without the need of disable interrupt.
thread1:
RequestLock
Scheduler changes thread
thread2:
RequestLock ; this results in a deadlock
This is how I have implemented it today. I allocate the section resources in kernel, and have a EnterSection and LeaveSection syscall. This works well, but requires 2 syscalls for protecting global data in userspace.bluemoon wrote:If the lock need to be shared across process and kernel, you need regular kernel locks; but in situation requiring such lock you usually better deal with other mechanism like IPC and queue.
- Owen
- Member
- Posts: 1700
- Joined: Fri Jun 13, 2008 3:21 pm
- Location: Cambridge, United Kingdom
- Contact:
Re: Critical sections in userspace without syscalls?
Its not possible to implement without system calls (Well, not unless using a user-mode scheduler). The best implementation is the Futex, and I wrote a long post about them in 2010 so that I would have a good technical overview to refer people to
- 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:
Re: Critical sections in userspace without syscalls?
Bull. If it's preempted, it will also be rescheduled and the release code will be reached eventually.If a task gets the spinlock, and then is preempted, the spinlock will never be released
Furthermore, Owen is definitely wrong here: You can implement any mutual exclusion solely in userland. Pick any sample algorithm from wikipedia and it will work perfectly fine.
The only problem is that waiting for such a lock will inherently be a busy wait, and you will need scheduler intervention to prevent cycles being wasted on that - you really want to spend a system call on that when the thread having the lock is not currently scheduled. Since lock contention is a bad thing and should be prevented in the using code, there should not be any need to call the kernel in the vast majority of cases.
Owen already posted a link to a sample good implementation, so I won't need to do that.
- gravaera
- Member
- Posts: 737
- Joined: Tue Jun 02, 2009 4:35 pm
- Location: Supporting the cause: Use \tabs to indent code. NOT \x20 spaces.
Re: Critical sections in userspace without syscalls?
Yo:
If you need serialization of access to a shared resource, then you need locking -- simple as that. If you need to ensure that threads trying for the lock don't spin doing nothing, then you need to sleep them; ergo you need scheduler intervention. If not, then you can do it all in userspace.
--Peace out,
gravaera
You're making a few mistakes overall; the only time a process needs kernel assistance to handle locking is when the lock sleeps all threads that fail to acquire it -- in that case you need the scheduler's intervention to put the thread to sleep. For a lock that does not sleep acquisition-failed threads (like a spinlock) it can be fully done in userspace. There are two "standard" behaviours for locks: locks that sleep on acquisition failure, and locks that wait ("spin") on failure.rdos wrote: It's not shared between processes, but it is shared between multiple threads in the same process.
There's a small problem caused by people using "critical section" and "shared data" interchangeably (as you did in the thread title). A critical section is a code path which should not be interrupted mid-execution, or else the operation being performed will be at risk of failure. An example is writing command sequences to hardware registers. Even when you are doing this in a single-processor environment, the registers may be timing sensitive, or may "time out", or some other similar anomaly if you allowed the execution stream to be preempted before the writes have been completed. The aim in protecting a "critical section" isn't necessarily to prevent simultaneous access or multiple writes. Many times a resource which is written to by a critical section also needs to be protected from simultaneous writes though, so the two concepts often overlap, but they aren't technically the same. If all you need to do is enforce a critical section, then you only need to provide "CLI" to userspace.Yes. I need to protect data shared between multiple userspace threads running in the same process. It would be preferable if no syscalls were needed.
If you need serialization of access to a shared resource, then you need locking -- simple as that. If you need to ensure that threads trying for the lock don't spin doing nothing, then you need to sleep them; ergo you need scheduler intervention. If not, then you can do it all in userspace.
This is actually not a deadlock, but what is known as a "livelock". There will be progress made as soon as the thread that holds the lock is rescheduled. The most important thing is that there is progress being made toward the lock being released by the holder, so this is not yet a deadlock.Code: Select all
thread1: RequestLock Scheduler changes thread thread2: RequestLock ; this results in a deadlock
--Peace out,
gravaera
17:56 < sortie> Paging is called paging because you need to draw it on pages in your notebook to succeed at it.
Re: Critical sections in userspace without syscalls?
I think bts can be used instead of cmpxchg with no adverse side effects. The states are slightly changed. Bit 0 in the state is the "busy bit", and thus state 2 is changed to state 3 (has the busy bit set). Luckily, rcr will go through the correct states (3 -> 1 -> 0).
Code: Select all
lock:
lock bts val,0
jnc done
retry:
mov al,3
xchg al,val
or al,al
jz done
futex_wait(&val, 3):
jmp retry
done:
ret
unlock:
clc
lock rcr val,1
jnc done
xor al,al
mov val,al
futex_wake(&val, 1)
done:
Last edited by rdos on Mon Jul 23, 2012 5:44 am, edited 1 time in total.
Re: Critical sections in userspace without syscalls?
On 486+ you can use xadd.
On 386 you may need a spinlock(lock xchg/bts)+integer, which itself packed into single word, to form a multi-value semaphore - it sound slow, but it's doggy 386 anyway.
On 386 you may need a spinlock(lock xchg/bts)+integer, which itself packed into single word, to form a multi-value semaphore - it sound slow, but it's doggy 386 anyway.
- 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:
Re: Critical sections in userspace without syscalls?
Interruption is the least of your problems:gravaera wrote:A critical section is a code path which should not be interrupted mid-execution, or else the operation being performed will be at risk of failure.
Wikipedia wrote:In concurrent programming a critical section is a piece of code that accesses a shared resource (data structure or device) that must not be concurrently accessed by more than one thread of execution
SF and ZF collectively determine positive, negative, or zero. CF specifically determines the 0 -> -1 transition, so for a locked sub you have four distinct outcomes you can act on. A lock add only has three because CF will equal ZF, lock inc/dec preserve CF and therefore are limited to the first three cases as well (which is why you want to use sub instead of dec).I think atomic_dec would be lock sub val,1, and this would set ZR flag if the result becomes 0, and CY flag if the result becomes -1
Write out the cases then shift around the associated values to solve the rest of the puzzle
Re: Critical sections in userspace without syscalls?
Rdos, I also used that doc as a reference to develop my futures. Unfortunately that document doesn't explain exactly what part the kernel plays in getting futures to work correctly. So this is what I came up with. I would be interested to see whether you agree with this logic.
FutexWait()
FutexWake()
FutexWait()
Code: Select all
1. Lock the futex queue.
2a. IF (the user-space variable no longer indicates that queueing is required AND there is no-one in the futex queue)
unlock the futex queue
return to user space for a retry (original thread will retry)
2b. ELSEIF (the user-space variable no longer indicates that queueing is required AND there is someone in the futex queue)
add the current thread to the tail of the futex queue
schedule the thread at the head of the futex queue
unlock the futex queue
return to user space for a retry (long waiting thread will retry)
2c. ELSE
add the current thread to the tail of the futex queue
3. Unlock the futex queue
4. Schedule
Code: Select all
1. Lock the futex queue.
2. IF (there is a thread in the futex queue)
schedule the thread
3. Unlock the futex queue
If a trainstation is where trains stop, what is a workstation ?
Re: Critical sections in userspace without syscalls?
I happened to edit the post instead of replying.
Here is the x86 method from above:
Here is the x86 method from above:
Code: Select all
lock:
lock bts val,0
jnc done
retry:
mov al,3
xchg al,val
or al,al
jz done
futex_wait(&val, 3):
jmp retry
done:
ret
unlock:
clc
lock rcr val,1
jnc done
xor al,al
mov val,al
futex_wake(&val, 1)
done:
Re: Critical sections in userspace without syscalls?
rdos, is the following atomic ? I'm afraid I don't know enough asm to be sure.
It needs to be atomic so that 2 cores don't swap a 0 out at the same time. Perhaps you need a lock prefix there.
[edit] I just read that lock is implied with this instruction.
Code: Select all
xchg al,val
[edit] I just read that lock is implied with this instruction.
If a trainstation is where trains stop, what is a workstation ?
Re: Critical sections in userspace without syscalls?
At least I agree that this isn't properly described, which probably has to do with more interest in using the Futex in Linux than implementing the kernel functionality.gerryg400 wrote:Rdos, I also used that doc as a reference to develop my futures. Unfortunately that document doesn't explain exactly what part the kernel plays in getting futures to work correctly. So this is what I came up with. I would be interested to see whether you agree with this logic.
In my possible implementation, there is really only one value passed to the futex entry-point (3), so I suspect that I would want to do it like this (value is not passed as a parameter):gerryg400 wrote: FutexWait()Code: Select all
1. Lock the futex queue. 2a. IF (the user-space variable no longer indicates that queueing is required AND there is no-one in the futex queue) unlock the futex queue return to user space for a retry (original thread will retry) 2b. ELSEIF (the user-space variable no longer indicates that queueing is required AND there is someone in the futex queue) add the current thread to the tail of the futex queue schedule the thread at the head of the futex queue unlock the futex queue return to user space for a retry (long waiting thread will retry) 2c. ELSE add the current thread to the tail of the futex queue 3. Unlock the futex queue 4. Schedule
Code: Select all
kernel_lock_futex:
LockKernel ; take an exclusive lock
mov al,val
cmp al,3
jnz unlock_scheduler
Block
jmp done
unlock_scheduler:
UnlockKernel
done:
ret
That does seem pretty reasonablegerryg400 wrote: FutexWake()Code: Select all
1. Lock the futex queue. 2. IF (there is a thread in the futex queue) schedule the thread 3. Unlock the futex queue
Re: Critical sections in userspace without syscalls?
I don't bother to pass a value to the kernel either. I just check the highest bit. Actually my user space futex variable is different from the one in the article as well. It contains the following fields
I have the tid in the lock to allow recursive locks and/or to check for deadlock. It also means that the kernel can check the owner for when I get around to implementing priority inheritance. I haven't put a whole lot of thought into that yet though.
Code: Select all
bit31: waiter bit
bit30: spare
bits29 to bit0: thread id
If a trainstation is where trains stop, what is a workstation ?
Re: Critical sections in userspace without syscalls?
Actually, by a lucky circumstance, it seems like cmpxchg can be replaced by bts on bit 0, and atomic_dec can be replaced by rcr. This means there are states 0, 1 and 3 (instead of 0, 1 and 2), and it works on 386.bluemoon wrote:On 486+ you can use xadd.
On 386 you may need a spinlock(lock xchg/bts)+integer, which itself packed into single word, to form a multi-value semaphore - it sound slow, but it's doggy 386 anyway.