News for hackers. Stuff that matters.
Re:Landing a new syscall, part 1: What is futex? by dpratt71 on Wednesday February 09, 2022 @02:15PM (#30277050)
Stupid question: Why isn't such logic incorporated into the call itself?
Re:Landing a new syscall, part 1: What is futex? by ww520 on Wednesday February 09, 2022 @02:35PM (#30277326)
Good question. It's for performance reason. Calling into kernel is expensive. Lock acquired in the user mode is much faster than lock acquired in kernel.

A typical lock acquisition using futex looks like:

  (a) while !compare_and_swap(&lock, 0, 1) // see if flag is free as 0, lock it as 1  
  (b)     futex(&lock, WAIT, 1)            // sleep until flag changes from 1  
(a) runs in user mode and it's very fast. It's just one CMPXCHG assembly instruction. If the lock is free, it's acquired in one instruction as 1 (locked).

If the lock is not free, then do the expensive call into the kernel to sleep via futex at (b). Futex() helps in detecting the change of the value while putting the thread to sleep, to avoid hogging the CPU.

Re:Landing a new syscall, part 1: What is futex? by RustyRussell on Wednesday February 09, 2022 @05:51PM (#30279811)
Importantly, prior to this (and, hell, even since) state of the art was to try atomically, then yield (or sleep(0)) then try usleep.

The kernel had no idea what was going on, so had no idea how to schedule such a thing. It particularly didn't know to wake you when the lock (which it has no idea about) became available.

Re:Landing a new syscall, part 1: What is futex? by chrchang523 on Wednesday February 09, 2022 @03:15PM (#30277842)
It is worth noting here that this "one assembly instruction" is not that cheap. The hardware on a multicore system does have to perform some locking under the hood to execute that instruction. But yes, it still has enough of an advantage over calling into the kernel to justify the additional usage complexity.
Re:Landing a new syscall, part 1: What is futex? by rrauenza on Wednesday February 09, 2022 @09:59PM (#30281938)
And on ccnuma systems, you end up getting memory contention and huuuuuge memory latencies, as well for the data also residing in the same cacheline. Often we would align and isolate these locks within a cacheline blocke... which also wastes a lot of space if you have a lot of these (I was consulting on an enterprise app that had millions I think. It made a big difference in the software's memory footprint.)
Re:Landing a new syscall, part 1: What is futex? by chacham15 on Wednesday February 09, 2022 @03:43PM (#30278215)
Im pretty sure the same construct can be implemented without the compare:

  int lock = 0;

  void AcquireLock(int *lock){
    while (ATOMIC_SWAP(lock, 1)){
      sleep(10); //or futex or w/e
    }
  }

  void ReleaseLock(int *lock){
    ATOMIC_SWAP(lock, 0);
  }
Re:Landing a new syscall, part 1: What is futex? by ww520 on Wednesday February 09, 2022 @03:51PM (#30278317)
The 'lock' variable is shared among threads. Compare is needed to avoid stomping on the lock acquired by another thread.
Re:Landing a new syscall, part 1: What is futex? by dragontamer on Wednesday February 09, 2022 @07:21PM (#30280794)
No it isn't.

If lock = 1, you set the lock to 1 (aka do nothing).

If the lock is 0, you know it is unlocked and know you succeeded in acquiring the lock.

If many threads try to atomic swap, only one of them gets the zero.

------

The real issue here is the subtle memory barrier bug in that code.

Re:Landing a new syscall, part 1: What is futex? by ww520 on Wednesday February 09, 2022 @08:28PM (#30281344)
You are right. XCHG can do the job to acquire the lock. At least on x86, XCHG does lock the cache line on the address of the variable, so it should be ok.
Re:Landing a new syscall, part 1: What is futex? by ww520 on Wednesday February 09, 2022 @03:53PM (#30278336)
Yes, CMPXCHG is a relatively more expensive instruction. In x86 it is a memory barrier instruction.
Re:Landing a new syscall, part 1: What is futex? by derefr on Wednesday February 09, 2022 @03:56PM (#30278364)
The gettimeofday(3) vDSO is pure-userspace code. Why not, then, a futex(3) vDSO that does a while + compare_and_swap(2) in userspace, but then contains a real call to the futex(3) syscall?
Re:Landing a new syscall, part 1: What is futex? by tialaramex on Wednesday February 09, 2022 @05:52PM (#30279817)
This part of the optimisation is well-known and had been for years before futex was invented, so there's no need to provide it as a vDSO or any other special work, userspace can (and was ready to) do that part anyway.

The futex is clever because previously the heavyweight locking is in the kernel, but all you actually needed was this very flimsy wait feature. BeOS for example, has a "Benaphore" construction where you initialise an expensive heavyweight lock but you try the userspace trick before needing to actually take it. So that looks superficially just as good as a futex...

... and then you realise oh, the underlying locks need kernel RAM to store their state, so the OS can only allow each process to have so many of them and thus you can't have so many Benaphores, they were expensive after all. But a futex doesn't use any kernel resources when you aren't calling the OS, Linux doesn't mind if your program uses a billion futexes, that's fine, any particular thread can only be waiting on one of them, and Linux is only tracking the waiting.

Re:Landing a new syscall, part 1: What is futex? by derefr on Wednesday February 09, 2022 @05:58PM (#30279903)
> This part of the optimisation is well-known and had been for years before futex was invented, so there's no need to provide it as a vDSO or any other special work, userspace can (and was ready to) do that part anyway.

I guess, to me, the semantics of futex(3) on their own seem ill-formed, without the while loop + cmpxchg being part of them. It feels like the user shouldn't have access to raw "low-level" calls to futex(..., FUTEX_WAIT), since there's only one useful way to use those calls, and it's in combination with other code, where that code is theoretically possible to get wrong. It's an API that doesn't cleanly encapsulate its own concerns.

I suppose I'm used to thinking with "building reusable crypto"-tinted glasses: with crypto libraries, you never want to expose a primitive that needs to be used a certain way to be sound; because people are inevitably going to use it wrong, and that's inevitably going to result in exploitation. Instead, you can just expose a higher-level primitive, that inherently can only be used that one correct way, with no means to ask it to do anything else.

Of course, there's nothing inherently dangerous about calling futex(..., FUTEX_WAIT) without the associated code. (You just get a race condition. But a race condition in userspace doesn't corrupt the kernel or anything.) So I suppose this kind of thinking is meaningless here.

Re:Landing a new syscall, part 1: What is futex? by gpderetta on Thursday February 10, 2022 @11:54AM (#30288966)
For a long time the user in fact did not have (easy) access to it. glibc has started to provide a syscall wrapper only very recently, before you had to use syscall directly.

The reason is that the CAS is not part of the interface but it is left to the implementor is that the specific logic is very much application specific and in fact there is not only one way to use it. As discussed elsethread mutexes are only one of the many application of futexes.

Re:Landing a new syscall, part 1: What is futex? by ww520 on Wednesday February 09, 2022 @04:11PM (#30278537)
What you described is what the phread_mutex_lock() does exactly, which is in user space. Application programmers don't deal with futex directly, they call phread_mutex_lock/phread_mutex_unlock.
Re:Landing a new syscall, part 1: What is futex? by yencabulator on Friday February 11, 2022 @03:02PM (#30306325)
The non-syscall parts of futex can be inlined, for great benefit.

gettimeofday calls code in the vDSO to avoid codifying in the kernel ABI the much more complex, architecture-dependent, data format.

Re:Landing a new syscall, part 1: What is futex? by ww520 on Wednesday February 09, 2022 @06:38PM (#30280395)
Futex puts the thread to sleep and wakes it up. Accessing the OS scheduler requires kernel access anyway. The memory access of the flag can be done in user or kernel space.
Re:Landing a new syscall, part 1: What is futex? by oofabz on Wednesday February 09, 2022 @08:10PM (#30281199)
Incorporating thread-safety into individual calls is not sufficient, because thread-safety is not composable. A sequence of calls to thread-safe operations is not itself thread-safe. Since you need locks in the outermost application level, it would be wasteful and unnecessary to also lock inside each individual call.

This non-composability is why multithreading is so difficult. A library can't abstract away these concerns for you, unless the entire multithreaded operation is one call to the library, which requires the library to know and design for your exact use case. If you want to do something a library didn't plan for, you are required to handle the thread synchronization yourself.

Re:Landing a new syscall, part 1: What is futex? by 58028641 on Wednesday February 09, 2022 @09:09PM (#30281628)
Haskell’s STM offers composable concurrency