futex.c

来自「Kernel code of linux kernel」· C语言 代码 · 共 2,098 行 · 第 1/4 页

C
2,098
字号
		 * change again after the spin_lock() but only if it was		 * already changed before the spin_lock().  It cannot,		 * however, change back to the original value.  Therefore		 * we can detect whether we acquired the correct lock.		 */		if (unlikely(lock_ptr != q->lock_ptr)) {			spin_unlock(lock_ptr);			goto retry;		}		WARN_ON(plist_node_empty(&q->list));		plist_del(&q->list, &q->list.plist);		BUG_ON(q->pi_state);		spin_unlock(lock_ptr);		ret = 1;	}	drop_futex_key_refs(&q->key);	return ret;}/* * PI futexes can not be requeued and must remove themself from the * hash bucket. The hash bucket lock (i.e. lock_ptr) is held on entry * and dropped here. */static void unqueue_me_pi(struct futex_q *q){	WARN_ON(plist_node_empty(&q->list));	plist_del(&q->list, &q->list.plist);	BUG_ON(!q->pi_state);	free_pi_state(q->pi_state);	q->pi_state = NULL;	spin_unlock(q->lock_ptr);	drop_futex_key_refs(&q->key);}/* * Fixup the pi_state owner with the new owner. * * Must be called with hash bucket lock held and mm->sem held for non * private futexes. */static int fixup_pi_state_owner(u32 __user *uaddr, struct futex_q *q,				struct task_struct *newowner,				struct rw_semaphore *fshared){	u32 newtid = task_pid_vnr(newowner) | FUTEX_WAITERS;	struct futex_pi_state *pi_state = q->pi_state;	struct task_struct *oldowner = pi_state->owner;	u32 uval, curval, newval;	int ret, attempt = 0;	/* Owner died? */	if (!pi_state->owner)		newtid |= FUTEX_OWNER_DIED;	/*	 * We are here either because we stole the rtmutex from the	 * pending owner or we are the pending owner which failed to	 * get the rtmutex. We have to replace the pending owner TID	 * in the user space variable. This must be atomic as we have	 * to preserve the owner died bit here.	 *	 * Note: We write the user space value _before_ changing the	 * pi_state because we can fault here. Imagine swapped out	 * pages or a fork, which was running right before we acquired	 * mmap_sem, that marked all the anonymous memory readonly for	 * cow.	 *	 * Modifying pi_state _before_ the user space value would	 * leave the pi_state in an inconsistent state when we fault	 * here, because we need to drop the hash bucket lock to	 * handle the fault. This might be observed in the PID check	 * in lookup_pi_state.	 */retry:	if (get_futex_value_locked(&uval, uaddr))		goto handle_fault;	while (1) {		newval = (uval & FUTEX_OWNER_DIED) | newtid;		curval = cmpxchg_futex_value_locked(uaddr, uval, newval);		if (curval == -EFAULT)			goto handle_fault;		if (curval == uval)			break;		uval = curval;	}	/*	 * We fixed up user space. Now we need to fix the pi_state	 * itself.	 */	if (pi_state->owner != NULL) {		spin_lock_irq(&pi_state->owner->pi_lock);		WARN_ON(list_empty(&pi_state->list));		list_del_init(&pi_state->list);		spin_unlock_irq(&pi_state->owner->pi_lock);	}	pi_state->owner = newowner;	spin_lock_irq(&newowner->pi_lock);	WARN_ON(!list_empty(&pi_state->list));	list_add(&pi_state->list, &newowner->pi_state_list);	spin_unlock_irq(&newowner->pi_lock);	return 0;	/*	 * To handle the page fault we need to drop the hash bucket	 * lock here. That gives the other task (either the pending	 * owner itself or the task which stole the rtmutex) the	 * chance to try the fixup of the pi_state. So once we are	 * back from handling the fault we need to check the pi_state	 * after reacquiring the hash bucket lock and before trying to	 * do another fixup. When the fixup has been done already we	 * simply return.	 */handle_fault:	spin_unlock(q->lock_ptr);	ret = futex_handle_fault((unsigned long)uaddr, fshared, attempt++);	spin_lock(q->lock_ptr);	/*	 * Check if someone else fixed it for us:	 */	if (pi_state->owner != oldowner)		return 0;	if (ret)		return ret;	goto retry;}/* * In case we must use restart_block to restart a futex_wait, * we encode in the 'flags' shared capability */#define FLAGS_SHARED  1static long futex_wait_restart(struct restart_block *restart);static int futex_wait(u32 __user *uaddr, struct rw_semaphore *fshared,		      u32 val, ktime_t *abs_time, u32 bitset){	struct task_struct *curr = current;	DECLARE_WAITQUEUE(wait, curr);	struct futex_hash_bucket *hb;	struct futex_q q;	u32 uval;	int ret;	struct hrtimer_sleeper t;	int rem = 0;	if (!bitset)		return -EINVAL;	q.pi_state = NULL;	q.bitset = bitset; retry:	futex_lock_mm(fshared);	ret = get_futex_key(uaddr, fshared, &q.key);	if (unlikely(ret != 0))		goto out_release_sem;	hb = queue_lock(&q);	/*	 * Access the page AFTER the futex is queued.	 * Order is important:	 *	 *   Userspace waiter: val = var; if (cond(val)) futex_wait(&var, val);	 *   Userspace waker:  if (cond(var)) { var = new; futex_wake(&var); }	 *	 * The basic logical guarantee of a futex is that it blocks ONLY	 * if cond(var) is known to be true at the time of blocking, for	 * any cond.  If we queued after testing *uaddr, that would open	 * a race condition where we could block indefinitely with	 * cond(var) false, which would violate the guarantee.	 *	 * A consequence is that futex_wait() can return zero and absorb	 * a wakeup when *uaddr != val on entry to the syscall.  This is	 * rare, but normal.	 *	 * for shared futexes, we hold the mmap semaphore, so the mapping	 * cannot have changed since we looked it up in get_futex_key.	 */	ret = get_futex_value_locked(&uval, uaddr);	if (unlikely(ret)) {		queue_unlock(&q, hb);		/*		 * If we would have faulted, release mmap_sem, fault it in and		 * start all over again.		 */		futex_unlock_mm(fshared);		ret = get_user(uval, uaddr);		if (!ret)			goto retry;		return ret;	}	ret = -EWOULDBLOCK;	if (uval != val)		goto out_unlock_release_sem;	/* Only actually queue if *uaddr contained val.  */	queue_me(&q, hb);	/*	 * Now the futex is queued and we have checked the data, we	 * don't want to hold mmap_sem while we sleep.	 */	futex_unlock_mm(fshared);	/*	 * There might have been scheduling since the queue_me(), as we	 * cannot hold a spinlock across the get_user() in case it	 * faults, and we cannot just set TASK_INTERRUPTIBLE state when	 * queueing ourselves into the futex hash.  This code thus has to	 * rely on the futex_wake() code removing us from hash when it	 * wakes us up.	 */	/* add_wait_queue is the barrier after __set_current_state. */	__set_current_state(TASK_INTERRUPTIBLE);	add_wait_queue(&q.waiters, &wait);	/*	 * !plist_node_empty() is safe here without any lock.	 * q.lock_ptr != 0 is not safe, because of ordering against wakeup.	 */	if (likely(!plist_node_empty(&q.list))) {		if (!abs_time)			schedule();		else {			hrtimer_init_on_stack(&t.timer, CLOCK_MONOTONIC,						HRTIMER_MODE_ABS);			hrtimer_init_sleeper(&t, current);			t.timer.expires = *abs_time;			hrtimer_start(&t.timer, t.timer.expires,						HRTIMER_MODE_ABS);			if (!hrtimer_active(&t.timer))				t.task = NULL;			/*			 * the timer could have already expired, in which			 * case current would be flagged for rescheduling.			 * Don't bother calling schedule.			 */			if (likely(t.task))				schedule();			hrtimer_cancel(&t.timer);			/* Flag if a timeout occured */			rem = (t.task == NULL);			destroy_hrtimer_on_stack(&t.timer);		}	}	__set_current_state(TASK_RUNNING);	/*	 * NOTE: we don't remove ourselves from the waitqueue because	 * we are the only user of it.	 */	/* If we were woken (and unqueued), we succeeded, whatever. */	if (!unqueue_me(&q))		return 0;	if (rem)		return -ETIMEDOUT;	/*	 * We expect signal_pending(current), but another thread may	 * have handled it for us already.	 */	if (!abs_time)		return -ERESTARTSYS;	else {		struct restart_block *restart;		restart = &current_thread_info()->restart_block;		restart->fn = futex_wait_restart;		restart->futex.uaddr = (u32 *)uaddr;		restart->futex.val = val;		restart->futex.time = abs_time->tv64;		restart->futex.bitset = bitset;		restart->futex.flags = 0;		if (fshared)			restart->futex.flags |= FLAGS_SHARED;		return -ERESTART_RESTARTBLOCK;	} out_unlock_release_sem:	queue_unlock(&q, hb); out_release_sem:	futex_unlock_mm(fshared);	return ret;}static long futex_wait_restart(struct restart_block *restart){	u32 __user *uaddr = (u32 __user *)restart->futex.uaddr;	struct rw_semaphore *fshared = NULL;	ktime_t t;	t.tv64 = restart->futex.time;	restart->fn = do_no_restart_syscall;	if (restart->futex.flags & FLAGS_SHARED)		fshared = &current->mm->mmap_sem;	return (long)futex_wait(uaddr, fshared, restart->futex.val, &t,				restart->futex.bitset);}/* * Userspace tried a 0 -> TID atomic transition of the futex value * and failed. The kernel side here does the whole locking operation: * if there are waiters then it will block, it does PI, etc. (Due to * races the kernel might see a 0 value of the futex too.) */static int futex_lock_pi(u32 __user *uaddr, struct rw_semaphore *fshared,			 int detect, ktime_t *time, int trylock){	struct hrtimer_sleeper timeout, *to = NULL;	struct task_struct *curr = current;	struct futex_hash_bucket *hb;	u32 uval, newval, curval;	struct futex_q q;	int ret, lock_taken, ownerdied = 0, attempt = 0;	if (refill_pi_state_cache())		return -ENOMEM;	if (time) {		to = &timeout;		hrtimer_init_on_stack(&to->timer, CLOCK_REALTIME,				      HRTIMER_MODE_ABS);		hrtimer_init_sleeper(to, current);		to->timer.expires = *time;	}	q.pi_state = NULL; retry:	futex_lock_mm(fshared);	ret = get_futex_key(uaddr, fshared, &q.key);	if (unlikely(ret != 0))		goto out_release_sem; retry_unlocked:	hb = queue_lock(&q); retry_locked:	ret = lock_taken = 0;	/*	 * To avoid races, we attempt to take the lock here again	 * (by doing a 0 -> TID atomic cmpxchg), while holding all	 * the locks. It will most likely not succeed.	 */	newval = task_pid_vnr(current);	curval = cmpxchg_futex_value_locked(uaddr, 0, newval);	if (unlikely(curval == -EFAULT))		goto uaddr_faulted;	/*	 * Detect deadlocks. In case of REQUEUE_PI this is a valid	 * situation and we return success to user space.	 */	if (unlikely((curval & FUTEX_TID_MASK) == task_pid_vnr(current))) {		ret = -EDEADLK;		goto out_unlock_release_sem;	}	/*	 * Surprise - we got the lock. Just return to userspace:	 */	if (unlikely(!curval))		goto out_unlock_release_sem;	uval = curval;	/*	 * Set the WAITERS flag, so the owner will know it has someone	 * to wake at next unlock	 */	newval = curval | FUTEX_WAITERS;	/*	 * There are two cases, where a futex might have no owner (the	 * owner TID is 0): OWNER_DIED. We take over the futex in this	 * case. We also do an unconditional take over, when the owner	 * of the futex died.	 *	 * This is safe as we are protected by the hash bucket lock !	 */	if (unlikely(ownerdied || !(curval & FUTEX_TID_MASK))) {		/* Keep the OWNER_DIED bit */		newval = (curval & ~FUTEX_TID_MASK) | task_pid_vnr(current);		ownerdied = 0;		lock_taken = 1;	}	curval = cmpxchg_futex_value_locked(uaddr, uval, newval);	if (unlikely(curval == -EFAULT))		goto uaddr_faulted;	if (unlikely(curval != uval))		goto retry_locked;	/*	 * We took the lock due to owner died take over.	 */	if (unlikely(lock_taken))		goto out_unlock_release_sem;	/*	 * We dont have the lock. Look up the PI state (or create it if	 * we are the first waiter):	 */	ret = lookup_pi_state(uval, hb, &q.key, &q.pi_state);	if (unlikely(ret)) {		switch (ret) {		case -EAGAIN:			/*			 * Task is exiting and we just wait for the			 * exit to complete.			 */			queue_unlock(&q, hb);			futex_unlock_mm(fshared);			cond_resched();			goto retry;		case -ESRCH:			/*			 * No owner found for this futex. Check if the			 * OWNER_DIED bit is set to figure out whether			 * this is a robust futex or not.			 */			if (get_futex_value_locked(&curval, uaddr))				goto uaddr_faulted;			/*			 * We simply start over in case of a robust			 * futex. The code above will take the futex			 * and return happy.			 */			if (curval & FUTEX_OWNER_DIED) {				ownerdied = 1;				goto retry_locked;			}		default:			goto out_unlock_release_sem;		}	}	/*	 * Only actually queue now that the atomic ops are done:	 */	queue_me(&q, hb);	/*	 * Now the futex is queued and we have checked the data, we	 * don't want to hold mmap_sem while we sleep.	 */	futex_unlock_mm(fshared);	WARN_ON(!q.pi_state);	/*	 * Block on the PI mutex:	 */	if (!trylock)		ret = rt_mutex_timed_lock(&q.pi_state->pi_mutex, to, 1);	else {		ret = rt_mutex_trylock(&q.pi_state->pi_mutex);		/* Fixup the trylock return value: */		ret = ret ? 0 : -EWOULDBLOCK;	}	futex_lock_mm(fshared);	spin_lock(q.lock_ptr);	if (!ret) {		/*		 * Got the lock. We might not be the anticipated owner		 * if we did a lock-steal - fix up the PI-state in		 * that case:		 */		if (q.pi_state->owner != curr)			ret = fixup_pi_state_owner(uaddr, &q, curr, fshared);	} else {		/*		 * Catch the rare case, where the lock was released		 * when we were on the way back before we locked the		 * hash bucket.		 */		if (q.pi_state->owner == curr) {			/*			 * Try to get the rt_mutex now. This might			 * fail as some other task acquired the			 * rt_mutex after we removed ourself from the			 * rt_mutex waiters list.			 */

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?