• AndroidO bluedroid alarm 机制分析


    bluedroid的alarm 机制实现在osi/osi/src/alarm.cc 中:

    这里面实现了很多的接口:

    alarm_t* alarm_new(const char* name);
    alarm_t* alarm_new_periodic(const char* name) ;
    static alarm_t* alarm_new_internal(const char* name, bool is_periodic) ;
    void alarm_free(alarm_t* alarm);
    void alarm_set(alarm_t* alarm, period_ms_t interval_ms, alarm_callback_t cb,
                   void* data) ;
    void alarm_cancel(alarm_t* alarm);
    void alarm_cleanup(void);
    static bool lazy_initialize(void) ;

     我们先看一个使用alarm 的事例:

    在hci_layer.cc 文件中关于hci_module_start_up 的实现:

     startup_timer = alarm_new("hci.startup_timer");
    ...
     alarm_set(startup_timer, startup_timeout_ms, startup_timer_expired, NULL); 

     当startup_timeout_ms 时间到达的时候,如果startup_timer还没有被取消的话,那么startup_timer_expired函数将会被执行。

    我们具体看看 其实现的过程:

    我们先看看新建定时器的代码:

    alarm_t* alarm_new(const char* name) { return alarm_new_internal(name, false); }
    static alarm_t* alarm_new_internal(const char* name, bool is_periodic) {
      // Make sure we have a list we can insert alarms into.
      if (!alarms && !lazy_initialize()) { //最初alarm 还没有初始化,需要执行初始化流程
        CHECK(false);  // if initialization failed, we should not continue
        return NULL;
      }
    
      alarm_t* ret = static_cast<alarm_t*>(osi_calloc(sizeof(alarm_t)));
    
      ret->callback_mutex = new std::recursive_mutex;
      ret->is_periodic = is_periodic;
      ret->stats.name = osi_strdup(name);
      // NOTE: The stats were reset by osi_calloc() above
    
      return ret;
    }

    我们继续看看 lazy_initialize  是如何 初始化的:

    static bool lazy_initialize(void) {
    
      bool timer_initialized = false;
      bool wakeup_timer_initialized = false;
    
      std::lock_guard<std::mutex> lock(alarms_mutex);
    
      alarms = list_new(NULL);
    
      if (!timer_create_internal(CLOCK_ID, &timer)) goto error;//timer_create
      timer_initialized = true;
    
      if (!timer_create_internal(CLOCK_ID_ALARM, &wakeup_timer)) goto error;
      wakeup_timer_initialized = true;
    
      alarm_expired = semaphore_new(0);//新建信号量
    
      default_callback_thread =
          thread_new_sized("alarm_default_callbacks", SIZE_MAX);//新建线程
    
      thread_set_rt_priority(default_callback_thread, THREAD_RT_PRIORITY);//提高线程优先级
      default_callback_queue = fixed_queue_new(SIZE_MAX);//新建队列
    
      alarm_register_processing_queue(default_callback_queue,
                                      default_callback_thread);//将线程和队列绑定
    
      dispatcher_thread_active = true;
      dispatcher_thread = thread_new("alarm_dispatcher");//新建定时器分发线程,该线程不停运行while(true)
    
      thread_set_rt_priority(dispatcher_thread, THREAD_RT_PRIORITY);
      thread_post(dispatcher_thread, callback_dispatch, NULL);//运行callback_dispatch
      return true;
    
    error:
      ...
      return false;
    }

     这里有两个线程,一个是dispatcher_thread,它一直轮询,如果有定时器到期那么他就将这个定时器放到一个(当初创建啊定时器的时候关联的队列)特定的队列里面,默认的就是default_callback_queue ,然后由另外一个线程default_callback_thread 来处理该队列里面的已经expire的定时器.

    我们看看timer_create_internal的实现:

    static bool timer_create_internal(const clockid_t clock_id, timer_t* timer) {
      CHECK(timer != NULL);
    
      struct sigevent sigevent;
      // create timer with RT priority thread
      pthread_attr_t thread_attr;
      pthread_attr_init(&thread_attr);
      pthread_attr_setschedpolicy(&thread_attr, SCHED_FIFO);
      struct sched_param param;
      param.sched_priority = THREAD_RT_PRIORITY;
      pthread_attr_setschedparam(&thread_attr, &param);
    
      memset(&sigevent, 0, sizeof(sigevent));
      sigevent.sigev_notify = SIGEV_THREAD;
      sigevent.sigev_notify_function = (void (*)(union sigval))timer_callback;//发送信号量
      sigevent.sigev_notify_attributes = &thread_attr;
      if (timer_create(clock_id, &sigevent, timer) == -1) {  //创建定时器
        /*错误处理*/
        }
        return false;
      }
    
      return true;
    }

    这里我们注意到,当定时器到期的时候,会执行timer_callback,其就是发送了一个alarm_expired的信号量:

    static void timer_callback(UNUSED_ATTR void* ptr) {
      semaphore_post(alarm_expired);
    }

    这里有发送信号量,那么一定有一个地方会等待这个信号量,就是在定时器的不断等待的线程里面:

    static void callback_dispatch(UNUSED_ATTR void* context) {
      while (true) {
        semaphore_wait(alarm_expired);//一直循环等待信号量
    ...
        }
    }

     lazy_initialize   初始化完成之后,定时器并没有启动,只是创建了定时器.那是在哪里启动的呢?根据我们上面展示出的使用示例,在alarm_set 肯定是有启动定时器的操作的:

    void alarm_set(alarm_t* alarm, period_ms_t interval_ms, alarm_callback_t cb,
                   void* data) {
      alarm_set_on_queue(alarm, interval_ms, cb, data, default_callback_queue);
    }

    这里发现 调用alarm_set 传入的 队列都是default_callback_queue,

    void alarm_set_on_queue(alarm_t* alarm, period_ms_t interval_ms,
                            alarm_callback_t cb, void* data, fixed_queue_t* queue) {
      CHECK(queue != NULL);
      alarm_set_internal(alarm, interval_ms, cb, data, queue);
    }
    // Runs in exclusion with alarm_cancel and timer_callback.
    static void alarm_set_internal(alarm_t* alarm, period_ms_t period,
                                   alarm_callback_t cb, void* data,
                                   fixed_queue_t* queue) {
    
      std::lock_guard<std::mutex> lock(alarms_mutex);
    
      alarm->creation_time = now();
      alarm->period = period;
      alarm->queue = queue;
      alarm->callback = cb;
      alarm->data = data;
    
      schedule_next_instance(alarm);
      alarm->stats.scheduled_count++;
    }

    上面是对定时器进行封装以及赋值,然后调用schedule_next_instance 来启动 定时器:

    // Must be called with |alarms_mutex| held
    static void schedule_next_instance(alarm_t* alarm) {
      // If the alarm is currently set and it's at the start of the list,
      // we'll need to re-schedule since we've adjusted the earliest deadline.
      bool needs_reschedule =
          (!list_is_empty(alarms) && list_front(alarms) == alarm);//如果alarms 队列第一个元素就是这个定时器,那么需要重启schedule
      if (alarm->callback) remove_pending_alarm(alarm);//取出所有的pending,重复的alarm
    
      // Calculate the next deadline for this alarm
      period_ms_t just_now = now();
      period_ms_t ms_into_period = 0;
      if ((alarm->is_periodic) && (alarm->period != 0))
        ms_into_period = ((just_now - alarm->creation_time) % alarm->period);
      alarm->deadline = just_now + (alarm->period - ms_into_period);
    
      // Add it into the timer list sorted by deadline (earliest deadline first).//以下是给alarm排序,插入到某个合适的问题,最近的alarm 排在第一个
      if (list_is_empty(alarms) ||
          ((alarm_t*)list_front(alarms))->deadline > alarm->deadline) {
        list_prepend(alarms, alarm);
      } else {
        for (list_node_t* node = list_begin(alarms); node != list_end(alarms);
             node = list_next(node)) {
          list_node_t* next = list_next(node);
          if (next == list_end(alarms) ||
              ((alarm_t*)list_node(next))->deadline > alarm->deadline) {
            list_insert_after(alarms, node, alarm);
            break;
          }
        }
      }
    
      // If the new alarm has the earliest deadline, we need to re-evaluate our
      // schedule.
      if (needs_reschedule ||
          (!list_is_empty(alarms) && list_front(alarms) == alarm)) {
        reschedule_root_alarm();
      }
    }

    上面主要就是将alarm插入到 alarms 列表中,等待schedule,如果当前这个alarm 就是最紧迫的alarm,那么就会立即进行 schedule.

    我们看看其实现reschedule_root_alarm;

    // NOTE: must be called with |alarms_mutex| held
    static void reschedule_root_alarm(void) {
      CHECK(alarms != NULL);
    
      const bool timer_was_set = timer_set;
      alarm_t* next;
      int64_t next_expiration;
    
      // If used in a zeroed state, disarms the timer.
      struct itimerspec timer_time;
      memset(&timer_time, 0, sizeof(timer_time));
    
      if (list_is_empty(alarms)) goto done;
    
      next = static_cast<alarm_t*>(list_front(alarms));
      next_expiration = next->deadline - now();
      if (next_expiration < TIMER_INTERVAL_FOR_WAKELOCK_IN_MS) {//如果deadline<3s>
    
        timer_time.it_value.tv_sec = (next->deadline / 1000);
        timer_time.it_value.tv_nsec = (next->deadline % 1000) * 1000000LL;
    
    /*下面设置最长的wake_up 是为了减少删除该timer的开销,可以略过*/
        struct itimerspec end_of_time;
        memset(&end_of_time, 0, sizeof(end_of_time));
        end_of_time.it_value.tv_sec = (time_t)(1LL << (sizeof(time_t) * 8 - 2));
        timer_settime(wakeup_timer, TIMER_ABSTIME, &end_of_time, NULL);
      } else {
        // WARNING: do not attempt to use relative timers with *_ALARM clock IDs
        // in kernels before 3.17 unless you have the following patch:
        // https://lkml.org/lkml/2014/7/7/576
        struct itimerspec wakeup_time;
        memset(&wakeup_time, 0, sizeof(wakeup_time));
    
        wakeup_time.it_value.tv_sec = (next->deadline / 1000);
        wakeup_time.it_value.tv_nsec = (next->deadline % 1000) * 1000000LL;
        if (timer_settime(wakeup_timer, TIMER_ABSTIME, &wakeup_time, NULL) == -1)
          LOG_ERROR(LOG_TAG, "%s unable to set wakeup timer: %s", __func__,
                    strerror(errno));
      }
    
    done:
      timer_set =
          timer_time.it_value.tv_sec != 0 || timer_time.it_value.tv_nsec != 0;
      if (timer_was_set && !timer_set) {
        wakelock_release();
      }
    
      if (timer_settime(timer, TIMER_ABSTIME, &timer_time, NULL) == -1)
        LOG_ERROR(LOG_TAG, "%s unable to set timer: %s", __func__, strerror(errno));//设置定时器
    
      // If next expiration was in the past (e.g. short timer that got context
      // switched) then the timer might have diarmed itself. Detect this case and
      // work around it by manually signalling the |alarm_expired| semaphore.
      //
      // It is possible that the timer was actually super short (a few
      // milliseconds) and the timer expired normally before we called
      // |timer_gettime|. Worst case, |alarm_expired| is signaled twice for that
      // alarm. Nothing bad should happen in that case though since the callback
      // dispatch function checks to make sure the timer at the head of the list
      // actually expired.
      if (timer_set) {
        struct itimerspec time_to_expire;
        timer_gettime(timer, &time_to_expire);
        if (time_to_expire.it_value.tv_sec == 0 &&
            time_to_expire.it_value.tv_nsec == 0) {
    
          semaphore_post(alarm_expired);//如果定时器的时机已经到了,那么直接发送信号量
        }
      }
    }

    代码是实现是 在离expire 不到3s的时候启动定时器.

    当定时器时间到的时候,发动alarm_expired的信号.

    我们接下来看看 定时器的 已经到期的处理流程:上面我们已经知道,线程dispatcher_thread一直轮询,我们看看其实现:

    // Function running on |dispatcher_thread| that performs the following:
    //   (1) Receives a signal using |alarm_exired| that the alarm has expired
    //   (2) Dispatches the alarm callback for processing by the corresponding
    // thread for that alarm.
    static void callback_dispatch(UNUSED_ATTR void* context) {
      while (true) {
        semaphore_wait(alarm_expired);//等待expire的信号量
        if (!dispatcher_thread_active) break;
    
        std::lock_guard<std::mutex> lock(alarms_mutex);
        alarm_t* alarm;
    
        // Take into account that the alarm may get cancelled before we get to it.
        // We're done here if there are no alarms or the alarm at the front is in
        // the future. Exit right away since there's nothing left to do.
        if (list_is_empty(alarms) ||
            (alarm = static_cast<alarm_t*>(list_front(alarms)))->deadline > now()) {
          reschedule_root_alarm();
          continue;
        }
    
        list_remove(alarms, alarm);//remove 该alarm 从队列中
    
        if (alarm->is_periodic) {
          alarm->prev_deadline = alarm->deadline;
          schedule_next_instance(alarm);
          alarm->stats.rescheduled_count++;
        }
        reschedule_root_alarm();//去启动下一个定时器
    
        // Enqueue the alarm for processing
        fixed_queue_enqueue(alarm->queue, alarm);//将该expire的定时器放到相应队列等待处理
      }
    
      LOG_DEBUG(LOG_TAG, "%s Callback thread exited", __func__);
    }

    这个函数做的事情,就像其名字一样,收到expire的信号之后,做一个dispatch的动作,我们接下来看看放置到队列之后如何处理的.

    这里我们还要看一下当时队列和线程绑定的情况:

    void alarm_register_processing_queue(fixed_queue_t* queue, thread_t* thread) {
      CHECK(queue != NULL);
      CHECK(thread != NULL);
    
      fixed_queue_register_dequeue(queue, thread_get_reactor(thread),
                                   alarm_queue_ready, NULL);
    }

    我们看看alarm_queue_ready:

    static void alarm_queue_ready(fixed_queue_t* queue, UNUSED_ATTR void* context) {
      CHECK(queue != NULL);
    
      std::unique_lock<std::mutex> lock(alarms_mutex);
      alarm_t* alarm = (alarm_t*)fixed_queue_try_dequeue(queue);
      if (alarm == NULL) {
        return;  // The alarm was probably canceled
      }
    
      //
      // If the alarm is not periodic, we've fully serviced it now, and can reset
      // some of its internal state. This is useful to distinguish between expired
      // alarms and active ones.
      //
      alarm_callback_t callback = alarm->callback;
      void* data = alarm->data;
      period_ms_t deadline = alarm->deadline;
      if (alarm->is_periodic) {
        // The periodic alarm has been rescheduled and alarm->deadline has been
        // updated, hence we need to use the previous deadline.
        deadline = alarm->prev_deadline;
      } else {
        alarm->deadline = 0;
        alarm->callback = NULL;
        alarm->data = NULL;
        alarm->queue = NULL;
      }
    
      std::lock_guard<std::recursive_mutex> cb_lock(*alarm->callback_mutex);
      lock.unlock();
    
      period_ms_t t0 = now();
      callback(data);//执行callback 函数,并reset alarm
      period_ms_t t1 = now();
    
      // Update the statistics
      CHECK(t1 >= t0);
      period_ms_t delta = t1 - t0;
      update_scheduling_stats(&alarm->stats, t0, deadline, delta);
    }

    上面流程的核心 就是 取出队列中的alarm,并执行其中的callback,也就是我们开定时器的时候的回调函数.

     关于定时器的介绍,就到这里.

  • 相关阅读:
    [Vue] #常见开发场景 #条件渲染#手动挂载 #组件方法的值驱动执行
    [JS高程] Typed Array 定型数组
    [JS高程]JavaScript中的RegExp对象
    [JS高程] Map 和 Set
    [JS高程] JavsScript 常用数组方法总结
    [JS高程] 字符串模式匹配方法
    自动化集成:Pipeline整合Docker容器
    自动化集成:Docker容器入门简介
    自动化集成:Jenkins管理工具详解
    分布式系统中,权限设计实践
  • 原文地址:https://www.cnblogs.com/libs-liu/p/9549219.html
Copyright © 2020-2023  润新知