| 
									
										
										
										
											2017-05-14 13:35:29 -03:00
										 |  |  | ================ | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | Futex Requeue PI | 
					
						
							| 
									
										
										
										
											2017-05-14 13:35:29 -03:00
										 |  |  | ================ | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | Requeueing of tasks from a non-PI futex to a PI futex requires | 
					
						
							|  |  |  | special handling in order to ensure the underlying rt_mutex is never | 
					
						
							|  |  |  | left without an owner if it has waiters; doing so would break the PI | 
					
						
							|  |  |  | boosting logic [see rt-mutex-desgin.txt] For the purposes of | 
					
						
							|  |  |  | brevity, this action will be referred to as "requeue_pi" throughout | 
					
						
							|  |  |  | this document.  Priority inheritance is abbreviated throughout as | 
					
						
							|  |  |  | "PI". | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Motivation | 
					
						
							|  |  |  | ---------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Without requeue_pi, the glibc implementation of | 
					
						
							|  |  |  | pthread_cond_broadcast() must resort to waking all the tasks waiting | 
					
						
							|  |  |  | on a pthread_condvar and letting them try to sort out which task | 
					
						
							|  |  |  | gets to run first in classic thundering-herd formation.  An ideal | 
					
						
							|  |  |  | implementation would wake the highest-priority waiter, and leave the | 
					
						
							|  |  |  | rest to the natural wakeup inherent in unlocking the mutex | 
					
						
							|  |  |  | associated with the condvar. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-14 13:35:29 -03:00
										 |  |  | Consider the simplified glibc calls:: | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-14 13:35:29 -03:00
										 |  |  | 	/* caller must lock mutex */ | 
					
						
							|  |  |  | 	pthread_cond_wait(cond, mutex) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		lock(cond->__data.__lock); | 
					
						
							|  |  |  | 		unlock(mutex); | 
					
						
							|  |  |  | 		do { | 
					
						
							|  |  |  | 		unlock(cond->__data.__lock); | 
					
						
							|  |  |  | 		futex_wait(cond->__data.__futex); | 
					
						
							|  |  |  | 		lock(cond->__data.__lock); | 
					
						
							|  |  |  | 		} while(...) | 
					
						
							|  |  |  | 		unlock(cond->__data.__lock); | 
					
						
							|  |  |  | 		lock(mutex); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-14 13:35:29 -03:00
										 |  |  | 	pthread_cond_broadcast(cond) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		lock(cond->__data.__lock); | 
					
						
							|  |  |  | 		unlock(cond->__data.__lock); | 
					
						
							|  |  |  | 		futex_requeue(cond->data.__futex, cond->mutex); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | Once pthread_cond_broadcast() requeues the tasks, the cond->mutex | 
					
						
							|  |  |  | has waiters. Note that pthread_cond_wait() attempts to lock the | 
					
						
							|  |  |  | mutex only after it has returned to user space.  This will leave the | 
					
						
							|  |  |  | underlying rt_mutex with waiters, and no owner, breaking the | 
					
						
							|  |  |  | previously mentioned PI-boosting algorithms. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In order to support PI-aware pthread_condvar's, the kernel needs to | 
					
						
							|  |  |  | be able to requeue tasks to PI futexes.  This support implies that | 
					
						
							|  |  |  | upon a successful futex_wait system call, the caller would return to | 
					
						
							|  |  |  | user space already holding the PI futex.  The glibc implementation | 
					
						
							| 
									
										
										
										
											2017-05-14 13:35:29 -03:00
										 |  |  | would be modified as follows:: | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-14 13:35:29 -03:00
										 |  |  | 	/* caller must lock mutex */ | 
					
						
							|  |  |  | 	pthread_cond_wait_pi(cond, mutex) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		lock(cond->__data.__lock); | 
					
						
							|  |  |  | 		unlock(mutex); | 
					
						
							|  |  |  | 		do { | 
					
						
							|  |  |  | 		unlock(cond->__data.__lock); | 
					
						
							|  |  |  | 		futex_wait_requeue_pi(cond->__data.__futex); | 
					
						
							|  |  |  | 		lock(cond->__data.__lock); | 
					
						
							|  |  |  | 		} while(...) | 
					
						
							|  |  |  | 		unlock(cond->__data.__lock); | 
					
						
							|  |  |  | 		/* the kernel acquired the mutex for us */ | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-14 13:35:29 -03:00
										 |  |  | 	pthread_cond_broadcast_pi(cond) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		lock(cond->__data.__lock); | 
					
						
							|  |  |  | 		unlock(cond->__data.__lock); | 
					
						
							|  |  |  | 		futex_requeue_pi(cond->data.__futex, cond->mutex); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | The actual glibc implementation will likely test for PI and make the | 
					
						
							|  |  |  | necessary changes inside the existing calls rather than creating new | 
					
						
							|  |  |  | calls for the PI cases.  Similar changes are needed for | 
					
						
							|  |  |  | pthread_cond_timedwait() and pthread_cond_signal(). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Implementation | 
					
						
							|  |  |  | -------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In order to ensure the rt_mutex has an owner if it has waiters, it | 
					
						
							|  |  |  | is necessary for both the requeue code, as well as the waiting code, | 
					
						
							|  |  |  | to be able to acquire the rt_mutex before returning to user space. | 
					
						
							|  |  |  | The requeue code cannot simply wake the waiter and leave it to | 
					
						
							|  |  |  | acquire the rt_mutex as it would open a race window between the | 
					
						
							|  |  |  | requeue call returning to user space and the waiter waking and | 
					
						
							|  |  |  | starting to run.  This is especially true in the uncontended case. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The solution involves two new rt_mutex helper routines, | 
					
						
							|  |  |  | rt_mutex_start_proxy_lock() and rt_mutex_finish_proxy_lock(), which | 
					
						
							|  |  |  | allow the requeue code to acquire an uncontended rt_mutex on behalf | 
					
						
							|  |  |  | of the waiter and to enqueue the waiter on a contended rt_mutex. | 
					
						
							|  |  |  | Two new system calls provide the kernel<->user interface to | 
					
						
							| 
									
										
										
										
											2015-01-16 20:27:57 +01:00
										 |  |  | requeue_pi: FUTEX_WAIT_REQUEUE_PI and FUTEX_CMP_REQUEUE_PI. | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | FUTEX_WAIT_REQUEUE_PI is called by the waiter (pthread_cond_wait() | 
					
						
							|  |  |  | and pthread_cond_timedwait()) to block on the initial futex and wait | 
					
						
							|  |  |  | to be requeued to a PI-aware futex.  The implementation is the | 
					
						
							|  |  |  | result of a high-speed collision between futex_wait() and | 
					
						
							|  |  |  | futex_lock_pi(), with some extra logic to check for the additional | 
					
						
							|  |  |  | wake-up scenarios. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-16 20:27:57 +01:00
										 |  |  | FUTEX_CMP_REQUEUE_PI is called by the waker | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | (pthread_cond_broadcast() and pthread_cond_signal()) to requeue and | 
					
						
							|  |  |  | possibly wake the waiting tasks. Internally, this system call is | 
					
						
							|  |  |  | still handled by futex_requeue (by passing requeue_pi=1).  Before | 
					
						
							|  |  |  | requeueing, futex_requeue() attempts to acquire the requeue target | 
					
						
							|  |  |  | PI futex on behalf of the top waiter.  If it can, this waiter is | 
					
						
							|  |  |  | woken.  futex_requeue() then proceeds to requeue the remaining | 
					
						
							|  |  |  | nr_wake+nr_requeue tasks to the PI futex, calling | 
					
						
							|  |  |  | rt_mutex_start_proxy_lock() prior to each requeue to prepare the | 
					
						
							|  |  |  | task as a waiter on the underlying rt_mutex.  It is possible that | 
					
						
							|  |  |  | the lock can be acquired at this stage as well, if so, the next | 
					
						
							|  |  |  | waiter is woken to finish the acquisition of the lock. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-16 20:27:57 +01:00
										 |  |  | FUTEX_CMP_REQUEUE_PI accepts nr_wake and nr_requeue as arguments, but | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | their sum is all that really matters.  futex_requeue() will wake or | 
					
						
							|  |  |  | requeue up to nr_wake + nr_requeue tasks.  It will wake only as many | 
					
						
							|  |  |  | tasks as it can acquire the lock for, which in the majority of cases | 
					
						
							|  |  |  | should be 0 as good programming practice dictates that the caller of | 
					
						
							|  |  |  | either pthread_cond_broadcast() or pthread_cond_signal() acquire the | 
					
						
							| 
									
										
										
										
											2015-01-16 20:27:57 +01:00
										 |  |  | mutex prior to making the call. FUTEX_CMP_REQUEUE_PI requires that | 
					
						
							| 
									
										
										
										
											2009-05-07 15:40:14 -07:00
										 |  |  | nr_wake=1.  nr_requeue should be INT_MAX for broadcast and 0 for | 
					
						
							|  |  |  | signal. |