首页 Linux Linux wait返回及timer_create问题处理

Linux wait返回及timer_create问题处理

前言
ASP站长网前段时间查一个问题,发现应用层在使用wait函数时,在没有等到信号的情况下,wait函数返回了,并且返回值为0,没有超时及异常提示,不符合常理,跟进后发现,虽然c库代码编写不够严谨,但根源是应用层代码对timer_create的不当使用,引入了隐患。在这做一个分析,作为以后分析同类问题的参考。
 
一、 wait函数不合理返回问题
如下面代码,在postAndWait函数中,先把task queue进处理队列,然后调用wait等待task处理完成发送信号,接着在run函数中运行task及发送信号,当wait函数收到信号后,正常返回,这为正常的运行流程。但发现有时出现了在run中,task还没运行,也没有发送信号,wait函数就已经返回,并且返回值为0(success)。
 
frameworks\base\libs\hwui\renderthread\ RenderProxy.cpp
 
void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) {
    void* retval;
    task->setReturnPtr(&retval);
    SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
    AutoMutex _lock(mSyncMutex);
    mRenderThread.queue(&syncTask);    // queue task
    mSyncCondition.wait(mSyncMutex);  // 等待task运行完成发送信号
    return retval;   
// 若在task还没运行,wait就返回,task被释放,task运行线程不知道task被释放,一到task运行就出问题
}
 
frameworks\base\libs\hwui\renderthread\ RenderTask.cpp
 
void SignalingRenderTask::run() {
    mTask->run();        // task的运行
    mLock->lock();
    mSignal->signal();      // 发送信号给wait
    mLock->unlock();
}
 
二、wait不合理返回分析
跟进内核代码发现,当wait函数在等待时,wait所在的线程被挂起,正常情况下,当task的运行线程给wait所在的线程发送信号后,wait所在的线程被设置为可运行状态,等待系统调度运行并正常返回,wait函数调用路径及返回如下,调用路径如绿色标示的,返回点如紫色标示。(发送信号流程的代码位置与wait流程代码处于相同文件中,可自行跟踪)
system\core\include\utils\ Condition.h
 
inline status_t Condition::wait(Mutex& mutex) {
    return -pthread_cond_wait(&mCond, &mutex.mMutex);
}
 
bionic\libc\bionic\ Pthread_cond.cpp
 
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex) {
  return __pthread_cond_timedwait(cond, mutex, NULL,  COND_GET_CLOCK(cond->value));
}
 
bionic\libc\bionic\ Pthread_cond.cpp
 
__LIBC_HIDDEN__
int __pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, const timespec* abstime, clockid_t clock) {
  timespec ts;
  timespec* tsp;
 
  if (abstime != NULL) {        // 没有设置超时时间,不走这里
    if (__timespec_from_absolute(&ts, abstime, clock) < 0) {
      return ETIMEDOUT;
    }
    tsp = &ts;
  } else {
    tsp = NULL;
  }
 
  return __pthread_cond_timedwait_relative(cond, mutex, tsp);
}
 
bionic\libc\bionic\ Pthread_cond.cpp
 
__LIBC_HIDDEN__
int __pthread_cond_timedwait_relative(pthread_cond_t* cond, pthread_mutex_t* mutex, const timespec* reltime) {
  int old_value = cond->value;
 
  pthread_mutex_unlock(mutex);
  int status = __futex_wait_ex(&cond->value, COND_IS_SHARED(cond->value), old_value, reltime);
  pthread_mutex_lock(mutex);
 
  if (status == -ETIMEDOUT) {
    return ETIMEDOUT;
  }
 
  return 0;
}
bionic\libc\private\ Bionic_futex.h
static inline int __futex_wait_ex(volatile void* ftx, bool shared, int value, const struct timespec* timeout) {
  return __futex(ftx, shared ? FUTEX_WAIT : FUTEX_WAIT_PRIVATE, value, timeout);
}
 
bionic\libc\private\ Bionic_futex.h
 
static inline __always_inline int __futex(volatile void* ftx, int op, int value, const struct timespec* timeout) {
  // Our generated syscall assembler sets errno, but our callers (pthread functions) don't want to.
  int saved_errno = errno;
  int result = syscall(__NR_futex, ftx, op, value, timeout);
  if (__predict_false(result == -1)) {
    result = -errno;
    errno = saved_errno;
  }
  return result;
}
 
kernel\kernel\ Futex.c
 
SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val,
        struct timespec __user *, utime, u32 __user *, uaddr2,
        u32, val3)
{
    struct timespec ts;
    ktime_t t, *tp = NULL;
    u32 val2 = 0;
    int cmd = op & FUTEX_CMD_MASK;
 
    if (utime && (cmd == FUTEX_WAIT || cmd == FUTEX_LOCK_PI ||
              cmd == FUTEX_WAIT_BITSET ||
              cmd == FUTEX_WAIT_REQUEUE_PI)) {
        if (copy_from_user(&ts, utime, sizeof(ts)) != 0)
            return -EFAULT;
        if (!timespec_valid(&ts))
            return -EINVAL;
 
        t = timespec_to_ktime(ts);
        if (cmd == FUTEX_WAIT)
            t = ktime_add_safe(ktime_get(), t);
        tp = &t;
    }
    /*
    * requeue parameter in 'utime' if cmd == FUTEX_*_REQUEUE_*.
    * number of waiters to wake in 'utime' if cmd == FUTEX_WAKE_OP.
    */
    if (cmd == FUTEX_REQUEUE || cmd == FUTEX_CMP_REQUEUE ||
        cmd == FUTEX_CMP_REQUEUE_PI || cmd == FUTEX_WAKE_OP)
        val2 = (u32) (unsigned long) utime;
 
    return do_futex(uaddr, op, val, tp, uaddr2, val2, val3);
}
 
kernel\kernel\ Futex.c
 
long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
        u32 __user *uaddr2, u32 val2, u32 val3)
{
    int cmd = op & FUTEX_CMD_MASK;
    unsigned int flags = 0;
 
    if (!(op & FUTEX_PRIVATE_FLAG))
        flags |= FLAGS_SHARED;
 
    if (op & FUTEX_CLOCK_REALTIME) {
        flags |= FLAGS_CLOCKRT;
        if (cmd != FUTEX_WAIT_BITSET && cmd != FUTEX_WAIT_REQUEUE_PI)
            return -ENOSYS;
    }
 
    switch (cmd) {
    case FUTEX_LOCK_PI:
    case FUTEX_UNLOCK_PI:
    case FUTEX_TRYLOCK_PI:
    case FUTEX_WAIT_REQUEUE_PI:
    case FUTEX_CMP_REQUEUE_PI:
        if (!futex_cmpxchg_enabled)
            return -ENOSYS;
    }
 
    switch (cmd) {
    case FUTEX_WAIT:
        val3 = FUTEX_BITSET_MATCH_ANY;
    case FUTEX_WAIT_BITSET:
        return futex_wait(uaddr, flags, val, timeout, val3);
    case FUTEX_WAKE:
        val3 = FUTEX_BITSET_MATCH_ANY;
    case FUTEX_WAKE_BITSET:
        return futex_wake(uaddr, flags, val, val3);
    case FUTEX_REQUEUE:
        return futex_requeue(uaddr, flags, uaddr2, val, val2, NULL, 0);
    case FUTEX_CMP_REQUEUE:
        return futex_requeue(uaddr, flags, uaddr2, val, val2, &val3, 0);
    case FUTEX_WAKE_OP:
        return futex_wake_op(uaddr, flags, uaddr2, val, val2, val3);
    case FUTEX_LOCK_PI:
        return futex_lock_pi(uaddr, flags, val, timeout, 0);
    case FUTEX_UNLOCK_PI:
        return futex_unlock_pi(uaddr, flags);
    case FUTEX_TRYLOCK_PI:
        return futex_lock_pi(uaddr, flags, 0, timeout, 1);
    case FUTEX_WAIT_REQUEUE_PI:
        val3 = FUTEX_BITSET_MATCH_ANY;
        return futex_wait_requeue_pi(uaddr, flags, val, timeout, val3,
                        uaddr2);
    case FUTEX_CMP_REQUEUE_PI:
        return futex_requeue(uaddr, flags, uaddr2, val, val2, &val3, 1);
    }
    return -ENOSYS;
}
 
kernel\kernel\ Futex.c
 
static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
              ktime_t *abs_time, u32 bitset)
{
    struct hrtimer_sleeper timeout, *to = NULL;
    struct restart_block *restart;
    struct futex_hash_bucket *hb;
    struct futex_q q = futex_q_init;
    int ret;
 
    if (!bitset)
        return -EINVAL;
    q.bitset = bitset;
 
    if (abs_time) {
        to = &timeout;
 
        hrtimer_init_on_stack(&to->timer, (flags & FLAGS_CLOCKRT) ?
                      CLOCK_REALTIME : CLOCK_MONOTONIC,
                      HRTIMER_MODE_ABS);
        hrtimer_init_sleeper(to, current);
        hrtimer_set_expires_range_ns(&to->timer, *abs_time,
                        current->timer_slack_ns);
    }
 
 
retry:
    /*
    * Prepare to wait on uaddr. On success, holds hb lock and increments
    * q.key refs.
    */
 
    ret = futex_wait_setup(uaddr, val, flags, &q, &hb);
    if (ret)
        goto out;
 
    /* queue_me and wait for wakeup, timeout, or a signal. */
    futex_wait_queue_me(hb, &q, to);
 
    /* If we were woken (and unqueued), we succeeded, whatever. */
    ret = 0;
    /* unqueue_me() drops q.key ref */
    if (!unqueue_me(&q)) {                 
        /* unqueue_me返回值情况 */
        /* 1 – if the futex_q was still queued (and we removed unqueued it); */
        /* 0 – if the futex_q was already removed by the waking thread(发送信号唤醒的情况) */
 
        goto out;                        // 正常等到信号后返回走这里
    }
    ret = -ETIMEDOUT;
    if (to && !to->task) {
        goto out;
    }
 
    /*
    * We expect signal_pending(current), but we might be the
    * victim of a spurious wakeup as well.
    */
    if (!signal_pending(current)) {
        trace_printk("retry\n");
        goto retry;
    }
 
    ret = -ERESTARTSYS;
    if (!abs_time) {
        goto out;
    }
 
    restart = ¤t_thread_info()->restart_block;
    restart->fn = futex_wait_restart;
    restart->futex.uaddr = uaddr;
    restart->futex.val = val;
    restart->futex.time = abs_time->tv64;
    restart->futex.bitset = bitset;
    restart->futex.flags = flags | FLAGS_HAS_TIMEOUT;
 
    ret = -ERESTART_RESTARTBLOCK;
 
out:
    if (to) {
        hrtimer_cancel(&to->timer);
        destroy_hrtimer_on_stack(&to->timer);
    }
 
    return ret;            // 正常返回值为0
}
 
kernel\kernel\ Futex.c
 
static void futex_wait_queue_me(struct futex_hash_bucket *hb, struct futex_q *q,
                struct hrtimer_sleeper *timeout)
{
    /*
    * The task state is guaranteed to be set before another task can
    * wake it. set_current_state() is implemented using set_mb() and
    * queue_me() calls spin_unlock() upon completion, both serializing
    * access to the hash list and forcing another memory barrier.
    */
    set_current_state(TASK_INTERRUPTIBLE);
    queue_me(q, hb);
 
    /* Arm the timer */
    if (timeout) {
        hrtimer_start_expires(&timeout->timer, HRTIMER_MODE_ABS);
        if (!hrtimer_active(&timeout->timer))
            timeout->task = NULL;
    }
 
    /*
    * If we have been removed from the hash list, then another task
    * has tried to wake us, and we can skip the call to schedule().
    */
    if (likely(!plist_node_empty(&q->list))) {
        /*
        * If the timer has already expired, current will already be
        * flagged for rescheduling. Only call schedule if there
        * is no timeout, or if it has yet to expire.
        */
        if (!timeout || timeout->task) {
            freezable_schedule();       
        }
    }
    __set_current_state(TASK_RUNNING);
}

关于作者: dawei

【声明】:九江站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。

热门文章