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 = ¤t_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 = ¤t->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 + -
显示快捷键?