Android蓝牙源码分析——GKI定时器

GKI定时器初始化在gki_ulinux.c中的GKI_init中,如下:

void GKI_init(void)
{
    ......

    struct sigevent sigevent;
    memset(&sigevent, 0, sizeof(sigevent));
    sigevent.sigev_notify = SIGEV_THREAD;
    sigevent.sigev_notify_function = (void (*)(union sigval))bt_alarm_cb;
    sigevent.sigev_value.sival_ptr = NULL;
    if (timer_create(CLOCK_REALTIME, &sigevent, &posix_timer) == -1) {
        ALOGE("%s unable to create POSIX timer: %s", __func__, strerror(errno));
        timer_created = false;
    } else {
        timer_created = true;
    }
}

这里调用系统API创建了一个定时器,但是此时定时器还没开始计时。这里指定了SIGEV_THREAD表示当定时器到时内核就会创建一个线程执行回调,这里是bt_alarm_cb。

我们可以设想一下,如果我们自己设计一个定时器模块该如何做?首先会有一堆定时任务,我们可以创建很多个timer来分别定时,也可以只创建一个timer来管理所有定时任务。只不过需要将这些任务从近到远排好序,先给timer设置为最近的一个,最近的到时了再设置为下一个最近的,这样一直到所有定时任务都处理完为止。但是GKI的定时结构更复杂一些,因为GKI可以支持多个task,每个task都有多个计时任务队列,每个任务队列都有所不同,比如有的队列计时精度较低,以秒为单位,而有的精度要求较高,以毫秒为单位。这种情况下,GKI采取的方式是轮询,即每个任务队列都有一个轮询周期,在一个轮询结束时去检查队列中有没有超时的任务,有就依次回调这些任务的超时处理。然后再开始下一波轮询。

我们接下来从三个方面来分析GKI:

  • 如何启动定时器
  • 如何添加定时任务的
  • 定时器到时如何处理

先来看看定时器的启动,在gki_time.c中的GKI_start_timer中,如下:

void GKI_start_timer (UINT8 tnum, INT32 ticks, BOOLEAN is_continuous)
{
    INT32   reload;
    INT32   orig_ticks;
    UINT8   task_id = GKI_get_taskid();

    if (ticks <= 0)
        ticks = 1;

    orig_ticks = ticks;     /* save the ticks in case adjustment is necessary */

    /* If continuous timer, set reload, else set it to 0 */
    if (is_continuous)
        reload = ticks;
    else
        reload = 0;

    GKI_disable();

    /* Add the time since the last task timer update.
    ** Note that this works when no timers are active since
    ** both OSNumOrigTicks and OSTicksTilExp are 0.
    */
    if (INT32_MAX - (gki_cb.com.OSNumOrigTicks - gki_cb.com.OSTicksTilExp) > ticks)
    {
        ticks += gki_cb.com.OSNumOrigTicks - gki_cb.com.OSTicksTilExp;
    }
    else
        ticks = INT32_MAX;

    switch (tnum)
    {
        case TIMER_0:
            gki_cb.com.OSTaskTmr0R[task_id] = reload;
            gki_cb.com.OSTaskTmr0 [task_id] = ticks;
            break;
    }

    /* Only update the timeout value if it is less than any other newly started timers */
    gki_adjust_timer_count (orig_ticks);

    GKI_enable();
}

GKI中的task不止一个,我们已知的有btif task和btu task,这两个task跑在不同的线程。每个task都有自己的定时任务队列,即便如此,他们
都共用GKI的全局定时器。这里传入的参数tnum表示timer的编号,类似于GKI中的mailbox。GKI中每个task可以有多个mailbox,发送消息可以指定发送到哪个mailbox,每个mailbox下都有一个消息队列。这里timer也类似,每个task可以有多个定时任务队列,这里指定要启动哪个队列的计时。传入的参数ticks表示计时时长,is_continuous表示是一次性的还是连续的,所谓连续就是说任务到时再继续重新计时,也就是轮询。

要注意的是每时每刻都有可能有新的定时任务进来,那指定一个基准就非常重要了,这个基准就是当前正在进行的定时任务的起点。所以这里要对ticks进行调整,传入的ticks是从当前开始算起的,所以要调整为以当前正在进行的定时任务的起点为基准,而当前定时任务已经进行了多久呢?结果是OSNumOrigTicks - OSTicksTilExp,所以要给ticks加上这个差,接下来给这个计时轮询设置到OSTaskTmr中,将来启动计时会用到。

接下来再看gki_adjust_timer_count,这个函数的意思是如果当前新到的这个定时任务比系统当前的这个任务更早到时的话,需要调整一下。如下:

void gki_adjust_timer_count (INT32 ticks)
{
    if (ticks > 0)
    {
        /* See if the new timer expires before the current first expiration */
        if (gki_cb.com.OSNumOrigTicks == 0 || (ticks < gki_cb.com.OSTicksTilExp && gki_cb.com.OSTicksTilExp > 0))
        {
            gki_cb.com.OSNumOrigTicks = (gki_cb.com.OSNumOrigTicks - gki_cb.com.OSTicksTilExp) + ticks;
            gki_cb.com.OSTicksTilExp = ticks;
            alarm_service_reschedule();
        }
    }

    return;
}

这里调整完了还得再调alarm_service_reschedule重新设置一下定时器,这个函数里除了设置定时器之外,还会考虑要不要马上持有wake lock。如果离计时的deadline还早的话,如果持有wake lock就先释放掉。如果离deadline在3s内,如果没有持有wake lock就马上申请。

接下来再来看如何添加定时任务,以gatt_start_rsp_timer为例,这个函数用于等待请求的回调:

void gatt_start_rsp_timer(UINT16 clcb_idx)
{
    tGATT_CLCB *p_clcb = &gatt_cb.clcb[clcb_idx];
    UINT32 timeout = GATT_WAIT_FOR_RSP_TOUT;
    p_clcb->rsp_timer_ent.param  = (TIMER_PARAM_TYPE)p_clcb;
    if (p_clcb->operation == GATTC_OPTYPE_DISCOVERY &&
        p_clcb->op_subtype == GATT_DISC_SRVC_ALL)
    {
        timeout = GATT_WAIT_FOR_DISC_RSP_TOUT;
    }
    btu_start_timer (&p_clcb->rsp_timer_ent, BTU_TTYPE_ATT_WAIT_FOR_RSP,
                     timeout);
}

这里主要是调用了btu_start_timer,传入了计时器超时,计时任务的timer entry,这个entry要挂到btu task计时任务队列下的,此外还带上了计时的类型。我们看btu_start_timer的实现,如下:

void btu_start_timer (TIMER_LIST_ENT *p_tle, UINT16 type, UINT32 timeout)
{
    BT_HDR *p_msg;
    GKI_disable();
    /* if timer list is currently empty, start periodic GKI timer */
    if (btu_cb.timer_queue.p_first == NULL)
    {
        /* if timer starts on other than BTU task */
        if (GKI_get_taskid() != BTU_TASK)
        {
            /* post event to start timer in BTU task */
            if ((p_msg = (BT_HDR *)GKI_getbuf(BT_HDR_SIZE)) != NULL)
            {
                p_msg->event = BT_EVT_TO_START_TIMER;
                GKI_send_msg (BTU_TASK, TASK_MBOX_0, p_msg);
            }
        }
        else
        {
            /* Start free running 1 second timer for list management */
            GKI_start_timer (TIMER_0, GKI_SECS_TO_TICKS (1), TRUE);
        }
    }

    GKI_remove_from_timer_list (&btu_cb.timer_queue, p_tle);

    p_tle->event = type;
    p_tle->ticks = timeout;
    p_tle->ticks_initial = timeout;

    GKI_add_to_timer_list (&btu_cb.timer_queue, p_tle);
    GKI_enable();
}

如果btu task的计时任务队列为空,表示当前btu task的计时轮询已经停掉了,所以要启动当前btu task的计时轮询,这里的轮询周期是1s。

好了,我们最后来看计时任务到时会如何处理。文章开头时我们提到定时器到时会执行bt_alarm_cb回调,我们看这个函数:

static void bt_alarm_cb(void *data)
{
    UINT32 ticks_taken = 0;

    alarm_service.timer_last_expired_us = now_us();
    if (alarm_service.timer_last_expired_us > alarm_service.timer_started_us)
    {
        ticks_taken = GKI_MS_TO_TICKS((alarm_service.timer_last_expired_us
                                       - alarm_service.timer_started_us) / 1000);
    }

    GKI_timer_update(ticks_taken > alarm_service.ticks_scheduled
                   ? ticks_taken : alarm_service.ticks_scheduled);
}

这里首先要算出alarm service经过的时间,然后用这个时间去更新计时器,我们看GKI_timer_update,如下:

void GKI_timer_update (INT32 ticks_since_last_update)
{
    UINT8   task_id;
    long    next_expiration;        /* Holds the next soonest expiration time after this update */

    gki_cb.com.OSTicksTilExp -= ticks_since_last_update;

    ......

    if (gki_cb.com.OSTicksTilExp > 0)
    {
        ......
        return;
    }

    next_expiration = GKI_NO_NEW_TMRS_STARTED;

    gki_cb.com.OSNumOrigTicks -= gki_cb.com.OSTicksTilExp;

    GKI_disable();

    for (task_id = 0; task_id < GKI_MAX_TASKS; task_id++)
    {
        ......

         /* If any timer is running, decrement */
        if (gki_cb.com.OSTaskTmr0[task_id] > 0)
        {
            gki_cb.com.OSTaskTmr0[task_id] -= gki_cb.com.OSNumOrigTicks;

            if (gki_cb.com.OSTaskTmr0[task_id] <= 0)
            {
                /* Reload timer and set Timer 0 Expired event mask */
                gki_cb.com.OSTaskTmr0[task_id] = gki_cb.com.OSTaskTmr0R[task_id];
                GKI_send_event (task_id, TIMER_0_EVT_MASK);
            }
        }

        /* Check to see if this timer is the next one to expire */
        if (gki_cb.com.OSTaskTmr0[task_id] > 0 && gki_cb.com.OSTaskTmr0[task_id] < next_expiration)
            next_expiration = gki_cb.com.OSTaskTmr0[task_id];
    }

    /* Set the next timer experation value if there is one to start */
    if (next_expiration < GKI_NO_NEW_TMRS_STARTED)
    {
        gki_cb.com.OSTicksTilExp = gki_cb.com.OSNumOrigTicks = next_expiration;
    }
    else
    {
        gki_cb.com.OSTicksTilExp = gki_cb.com.OSNumOrigTicks = 0;
    }

    // Set alarm service for next alarm.
    alarm_service_reschedule();

    GKI_enable();

    return;
}

函数比较长,首先更新OSTicksTilExp,这个表示当前任务还有多久到时,既然时间已经又过了那么久,所以这里应该减掉这个时间差。如果OSTicksTilExp仍然大于0,表示任务还未到时,则直接返回。否则先更新一下OSNumOrigTicks,因为OSTicksTilExp为负表示任务已经超时一段时间了,由于OSNumOrigTicks表示任务执行时长,所以应该算上这一段超过的时间。

更新完GKI的定时器参数后,该更新各task的定时器参数了,包括OSWaitTmr和OSTaskTmr。不过OSWaitTmr我找来找去没发现有什么用处,我们这里只看OSTaskTmr。这里OSTaskTmr是在哪里设置的呢?在启动task的计时轮询的时候,就是GKI_start_timer中。我们在设置这个值的时候,基准是当时正在计时任务的起始点,所以现在这个任务既然已经到时了,那么OSTaskTmr也应该减掉一个时间差,即为OSNumOrigTicks。

如果OSTaskTmr小于0,表示这个计时轮询一轮已经结束了,该启动下一轮了。这里调用GKI_send_event发送了一个event出去,我们看这个事件处理逻辑,如下:

if (event & TIMER_0_EVT_MASK) {
    GKI_update_timer_list (&btu_cb.timer_queue, 1);

    while (!GKI_timer_queue_is_empty(&btu_cb.timer_queue)) {
        TIMER_LIST_ENT *p_tle = GKI_timer_getfirst(&btu_cb.timer_queue);
        if (p_tle->ticks != 0)
            break;

        GKI_remove_from_timer_list(&btu_cb.timer_queue, p_tle);

        switch (p_tle->event) {
            ......

            case BTU_TTYPE_ATT_WAIT_FOR_RSP:
                gatt_rsp_timeout(p_tle);
                break;

            ......
        }
    }

    /* if timer list is empty stop periodic GKI timer */
    if (btu_cb.timer_queue.p_first == NULL)
    {
        GKI_stop_timer(TIMER_0);
    }
}

这个GKI_update_timer_list (&btu_cb.timer_queue, 1);我暂时没看懂,理论上不应该传1的,这个定时轮询周期是1s,应该是100个ticks才对。更新计时任务队列中每个任务的ticks,都减掉这个时间差,如果小于0就表示任务到时了,下面就可以依次处理了。在while循环中依次取队列头,如果ticks为0表示队列中该任务已经到时了,就把该任务从队列中删除,然后根据该任务的event来做不同处理。

回到GKI_timer_update,如果OSTaskTmr大于0,表示这个队列的轮询还没到,当前到时的可能是别的队列的轮询,所以暂时不做处理。这个next_expiration表示所有队列中最先超时的,然后GKI全局的OSTicksTilExp和OSNumOrigTicks都设为该值。表示从当前开始,下一个任务总时长是OSNumOrigTicks,且当前距离该任务超时还剩OSTicksTilExp。然后再调alarm_service_reschedule函数重新设置timer。

你可能感兴趣的:(Android,Android蓝牙,android,蓝牙,源码,GKI,Bluedroid)