早期的内核版本中,进程的调度基于一个称之为tick的时钟滴答,通常使用时钟中断来定时地产生tick信号,每次tick定时中断都会进行进程的统计和调度,并对tick进行计数,记录在一个jiffies变量中,定时器的设计也是基于jiffies。这时候的内核代码中,几乎所有关于时钟的操作都是在machine级的代码中实现,很多公共的代码要在每个平台上重复实现。随后,随着通用时钟框架的引入,内核需要支持高精度的定时器,为此,通用时间框架为定时器硬件定义了一个标准的接口:clock_event_device,machine级的代码只要按这个标准接口实现相应的硬件控制功能,剩下的与平台无关的特性则统一由通用时间框架层来实现。

/*****************************************************************************************************/

声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!

/*****************************************************************************************************/

1.  时钟事件软件架构

本系列文章的第一节中,我们曾经讨论了时钟源设备:clocksource,现在又来一个时钟事件设备:clock_event_device,它们有何区别?看名字,好像都是给系统提供时钟的设备,实际上,clocksource不能被编程,没有产生事件的能力,它主要被用于timekeeper来实现对真实时间进行精确的统计,而clock_event_device则是可编程的,它可以工作在周期触发或单次触发模式,系统可以对它进行编程,以确定下一次事件触发的时间,clock_event_device主要用于实现普通定时器和高精度定时器,同时也用于产生tick事件,供给进程调度子系统使用。时钟事件设备与通用时间框架中的其他模块的关系如下图所示:

图1.1   clock_event_device软件架构

  • 与clocksource一样,系统中可以存在多个clock_event_device,系统会根据它们的精度和能力,选择合适的clock_event_device对系统提供时钟事件服务。在smp系统中,为了减少处理器间的通信开销,基本上每个cpu都会具备一个属于自己的本地clock_event_device,独立地为该cpu提供时钟事件服务,smp中的每个cpu基于本地的clock_event_device,建立自己的tick_device,普通定时器和高精度定时器。
  • 在软件架构上看,clock_event_device被分为了两层,与硬件相关的被放在了machine层,而与硬件无关的通用代码则被集中到了通用时间框架层,这符合内核对软件的设计需求,平台的开发者只需实现平台相关的接口即可,无需关注复杂的上层时间框架。
  • tick_device是基于clock_event_device的进一步封装,用于代替原有的时钟滴答中断,给内核提供tick事件,以完成进程的调度和进程信息统计,负载平衡和时间更新等操作。

2.  时钟事件设备相关数据结构

2.1  struct clock_event_device

时钟事件设备的核心数据结构是clock_event_device结构,它代表着一个时钟硬件设备,该设备就好像是一个具有事件触发能力(通常就是指中断)的clocksource,它不停地计数,当计数值达到预先编程设定的数值那一刻,会引发一个时钟事件中断,继而触发该设备的事件处理回调函数,以完成对时钟事件的处理。clock_event_device结构的定义如下:

[cpp] view plain copy

  1. struct clock_event_device {
  2. void            (*event_handler)(struct clock_event_device *);
  3. int         (*set_next_event)(unsigned long evt,
  4. struct clock_event_device *);
  5. int         (*set_next_ktime)(ktime_t expires,
  6. struct clock_event_device *);
  7. ktime_t         next_event;
  8. u64         max_delta_ns;
  9. u64         min_delta_ns;
  10. u32         mult;
  11. u32         shift;
  12. enum clock_event_mode   mode;
  13. unsigned int        features;
  14. unsigned long       retries;
  15. void            (*broadcast)(const struct cpumask *mask);
  16. void            (*set_mode)(enum clock_event_mode mode,
  17. struct clock_event_device *);
  18. unsigned long       min_delta_ticks;
  19. unsigned long       max_delta_ticks;
  20. const char      *name;
  21. int         rating;
  22. int         irq;
  23. const struct cpumask    *cpumask;
  24. struct list_head    list;
  25. } ____cacheline_aligned;

event_handler  该字段是一个回调函数指针,通常由通用框架层设置,在时间中断到来时,machine底层的的中断服务程序会调用该回调,框架层利用该回调实现对时钟事件的处理。

set_next_event  设置下一次时间触发的时间,使用类似于clocksource的cycle计数值(离现在的cycle差值)作为参数。

set_next_ktime  设置下一次时间触发的时间,直接使用ktime时间作为参数。

max_delta_ns  可设置的最大时间差,单位是纳秒。

min_delta_ns  可设置的最小时间差,单位是纳秒。

mult shift  与clocksource中的类似,只不过是用于把纳秒转换为cycle。

mode  该时钟事件设备的工作模式,两种主要的工作模式分别是:

  • CLOCK_EVT_MODE_PERIODIC  周期触发模式,设置后按给定的周期不停地触发事件;
  • CLOCK_EVT_MODE_ONESHOT  单次触发模式,只在设置好的触发时刻触发一次;

set_mode  函数指针,用于设置时钟事件设备的工作模式。

rating  表示该设备的精度等级。

list  系统中注册的时钟事件设备用该字段挂在全局链表变量clockevent_devices上。

2.2  全局变量clockevent_devices

系统中所有注册的clock_event_device都会挂在该链表下面,它在kernel/time/clockevents.c中定义:

[cpp] view plain copy

  1. static LIST_HEAD(clockevent_devices);
2.3  全局变量clockevents_chain

通用时间框架初始化时会注册一个通知链(NOTIFIER),当系统中的时钟时间设备的状态发生变化时,利用该通知链通知系统的其它模块。

[cpp] view plain copy

  1. /* Notification for clock events */
  2. static RAW_NOTIFIER_HEAD(clockevents_chain);

3.  clock_event_device的初始化和注册

每一个machine,都要定义一个自己的machine_desc结构,该结构定义了该machine的一些最基本的特性,其中需要设定一个sys_timer结构指针,machine级的代码负责定义sys_timer结构,sys_timer的声明很简单:

[cpp] view plain copy

  1. struct sys_timer {
  2. void            (*init)(void);
  3. void            (*suspend)(void);
  4. void            (*resume)(void);
  5. #ifdef CONFIG_ARCH_USES_GETTIMEOFFSET
  6. unsigned long       (*offset)(void);
  7. #endif
  8. };

通常,我们至少要定义它的init字段,系统初始化阶段,该init回调会被调用,该init回调函数的主要作用就是完成系统中的clocksource和clock_event_device的硬件初始化工作,以samsung的exynos4为例,在V3.4内核的代码树中,machine_desc的定义如下:

[cpp] view plain copy

  1. MACHINE_START(SMDK4412, "SMDK4412")
  2. /* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
  3. /* Maintainer: Changhwan Youn <chaos.youn@samsung.com> */
  4. .atag_offset    = 0x100,
  5. .init_irq   = exynos4_init_irq,
  6. .map_io     = smdk4x12_map_io,
  7. .handle_irq = gic_handle_irq,
  8. .init_machine   = smdk4x12_machine_init,
  9. .timer      = &exynos4_timer,
  10. .restart    = exynos4_restart,
  11. MACHINE_END

定义的sys_timer是exynos4_timer,它的定义和init回调定义如下:

[cpp] view plain copy

  1. static void __init exynos4_timer_init(void)
  2. {
  3. if (soc_is_exynos4210())
  4. mct_int_type = MCT_INT_SPI;
  5. else
  6. mct_int_type = MCT_INT_PPI;
  7. exynos4_timer_resources();
  8. exynos4_clocksource_init();
  9. exynos4_clockevent_init();
  10. }
  11. struct sys_timer exynos4_timer = {
  12. .init       = exynos4_timer_init,
  13. };

exynos4_clockevent_init函数显然是初始化和注册clock_event_device的合适时机,在这里,它注册了一个rating为250的clock_event_device,并把它指定给cpu0:

[cpp] view plain copy

  1. static struct clock_event_device mct_comp_device = {
  2. .name       = "mct-comp",
  3. .features       = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
  4. .rating     = 250,
  5. .set_next_event = exynos4_comp_set_next_event,
  6. .set_mode   = exynos4_comp_set_mode,
  7. };
  8. ......
  9. static void exynos4_clockevent_init(void)
  10. {
  11. clockevents_calc_mult_shift(&mct_comp_device, clk_rate, 5);
  12. ......
  13. mct_comp_device.cpumask = cpumask_of(0);
  14. clockevents_register_device(&mct_comp_device);
  15. setup_irq(EXYNOS4_IRQ_MCT_G0, &mct_comp_event_irq);
  16. }

因为这个阶段其它cpu核尚未开始工作,所以该clock_event_device也只是在启动阶段给系统提供服务,实际上,因为exynos4是一个smp系统,具备2-4个cpu核心,前面说过,smp系统中,通常会使用各个cpu的本地定时器来为每个cpu单独提供时钟事件服务,继续翻阅代码,在系统初始化的后段,kernel_init会被调用,它会调用smp_prepare_cpus,其中会调用percpu_timer_setup函数,在arch/arm/kernel/smp.c中,为每个cpu定义了一个clock_event_device:

[cpp] view plain copy

  1. /*
  2. * Timer (local or broadcast) support
  3. */
  4. static DEFINE_PER_CPU(struct clock_event_device, percpu_clockevent);

percpu_timer_setup最终会调用exynos4_local_timer_setup函数完成对本地clock_event_device的初始化工作:

[cpp] view plain copy

  1. static int __cpuinit exynos4_local_timer_setup(struct clock_event_device *evt)
  2. {
  3. ......
  4. evt->name = mevt->name;
  5. evt->cpumask = cpumask_of(cpu);
  6. evt->set_next_event = exynos4_tick_set_next_event;
  7. evt->set_mode = exynos4_tick_set_mode;
  8. evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
  9. evt->rating = 450;
  10. clockevents_calc_mult_shift(evt, clk_rate / (TICK_BASE_CNT + 1), 5);
  11. ......
  12. clockevents_register_device(evt);
  13. ......
  14. enable_percpu_irq(EXYNOS_IRQ_MCT_LOCALTIMER, 0);
  15. ......
  16. return 0;
  17. }

由此可见,每个cpu的本地clock_event_device的rating是450,比启动阶段的250要高,显然,之前注册给cpu0的精度要高,系统会用本地clock_event_device替换掉原来分配给cpu0的clock_event_device,至于怎么替换?我们先停一停,到这里我们一直在讨论machine级别的初始化和注册,让我们回过头来,看看框架层的初始化。在继续之前,让我们看看整个clock_event_device的初始化的调用序列图:

图3.1  clock_event_device的系统初始化

由上面的图示可以看出,框架层的初始化步骤很简单,又start_kernel开始,调用tick_init,它位于kernel/time/tick-common.c中,也只是简单地调用clockevents_register_notifier,同时把类型为notifier_block的tick_notifier作为参数传入,回看2.3节,clockevents_register_notifier注册了一个通知链,这样,当系统中的clock_event_device状态发生变化时(新增,删除,挂起,唤醒等等),tick_notifier中的notifier_call字段中设定的回调函数tick_notify就会被调用。接下来start_kernel调用了time_init函数,该函数通常定义在体系相关的代码中,正如前面所讨论的一样,它主要完成machine级别对时钟系统的初始化工作,最终通过clockevents_register_device注册系统中的时钟事件设备,把每个时钟时间设备挂在clockevent_device全局链表上,最后通过clockevent_do_notify触发框架层事先注册好的通知链,其实就是调用了tick_notify函数,我们主要关注CLOCK_EVT_NOTIFY_ADD通知,其它通知请自行参考代码,下面是tick_notify的简化版本:

[cpp] view plain copy

  1. static int tick_notify(struct notifier_block *nb, unsigned long reason,
  2. void *dev)
  3. {
  4. switch (reason) {
  5. case CLOCK_EVT_NOTIFY_ADD:
  6. return tick_check_new_device(dev);
  7. case CLOCK_EVT_NOTIFY_BROADCAST_ON:
  8. case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
  9. case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
  10. ......
  11. case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
  12. case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
  13. ......
  14. case CLOCK_EVT_NOTIFY_CPU_DYING:
  15. ......
  16. case CLOCK_EVT_NOTIFY_CPU_DEAD:
  17. ......
  18. case CLOCK_EVT_NOTIFY_SUSPEND:
  19. ......
  20. case CLOCK_EVT_NOTIFY_RESUME:
  21. ......
  22. }
  23. return NOTIFY_OK;
  24. }

可见,对于新注册的clock_event_device,会发出CLOCK_EVT_NOTIFY_ADD通知,最终会进入函数:tick_check_new_device,这个函数比对当前cpu所使用的与新注册的clock_event_device之间的特性,如果认为新的clock_event_device更好,则会进行切换工作。下一节将会详细的讨论该函数。到这里,每个cpu已经有了自己的clock_event_device,在这以后,框架层的代码会根据内核的配置项(CONFIG_NO_HZ、CONFIG_HIGH_RES_TIMERS),对注册的clock_event_device进行不同的设置,从而为系统的tick和高精度定时器提供服务,这些内容我们留在本系列的后续文章进行讨论。

4.  tick_device

当内核没有配置成支持高精度定时器时,系统的tick由tick_device产生,tick_device其实是clock_event_device的简单封装,它内嵌了一个clock_event_device指针和它的工作模式:

[cpp] view plain copy

  1. struct tick_device {
  2. struct clock_event_device *evtdev;
  3. enum tick_device_mode mode;
  4. };

在kernel/time/tick-common.c中,定义了一个per-cpu的tick_device全局变量,tick_cpu_device:

[cpp] view plain copy

  1. /*
  2. * Tick devices
  3. */
  4. DEFINE_PER_CPU(struct tick_device, tick_cpu_device);

前面曾经说过,当machine的代码为每个cpu注册clock_event_device时,通知回调函数tick_notify会被调用,进而进入tick_check_new_device函数,下面让我们看看该函数如何工作,首先,该函数先判断注册的clock_event_device是否可用于本cpu,然后从per-cpu变量中取出本cpu的tick_device:

[cpp] view plain copy

  1. static int tick_check_new_device(struct clock_event_device *newdev)
  2. {
  3. ......
  4. cpu = smp_processor_id();
  5. if (!cpumask_test_cpu(cpu, newdev->cpumask))
  6. goto out_bc;
  7. td = &per_cpu(tick_cpu_device, cpu);
  8. curdev = td->evtdev;

如果不是本地clock_event_device,会做进一步的判断:如果不能把irq绑定到本cpu,则放弃处理,如果本cpu已经有了一个本地clock_event_device,也放弃处理:

[cpp] view plain copy

  1. if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu))) {
  2. ......
  3. if (!irq_can_set_affinity(newdev->irq))
  4. goto out_bc;
  5. ......
  6. if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
  7. goto out_bc;
  8. }

反之,如果本cpu已经有了一个clock_event_device,则根据是否支持单触发模式和它的rating值,决定是否替换原来旧的clock_event_device:

[cpp] view plain copy

  1. if (curdev) {
  2. if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) &&
  3. !(newdev->features & CLOCK_EVT_FEAT_ONESHOT))
  4. goto out_bc;  // 新的不支持单触发,但旧的支持,所以不能替换
  5. if (curdev->rating >= newdev->rating)
  6. goto out_bc;  // 旧的比新的精度高,不能替换
  7. }

在这些判断都通过之后,说明或者来cpu还没有绑定tick_device,或者是新的更好,需要替换:

[cpp] view plain copy

  1. if (tick_is_broadcast_device(curdev)) {
  2. clockevents_shutdown(curdev);
  3. curdev = NULL;
  4. }
  5. clockevents_exchange_device(curdev, newdev);
  6. tick_setup_device(td, newdev, cpu, cpumask_of(cpu));

上面的tick_setup_device函数负责重新绑定当前cpu的tick_device和新注册的clock_event_device,如果发现是当前cpu第一次注册tick_device,就把它设置为TICKDEV_MODE_PERIODIC模式,如果是替换旧的tick_device,则根据新的tick_device的特性,设置为TICKDEV_MODE_PERIODIC或TICKDEV_MODE_ONESHOT模式。可见,在系统的启动阶段,tick_device是工作在周期触发模式的,直到框架层在合适的时机,才会开启单触发模式,以便支持NO_HZ和HRTIMER。

5.  tick事件的处理--最简单的情况

clock_event_device最基本的应用就是实现tick_device,然后给系统定期地产生tick事件,通用时间框架对clock_event_device和tick_device的处理相当复杂,因为涉及配置项:CONFIG_NO_HZ和CONFIG_HIGH_RES_TIMERS的组合,两个配置项就有4种组合,这四种组合的处理都有所不同,所以这里我先只讨论最简单的情况:

  • CONFIG_NO_HZ == 0;
  • CONFIG_HIGH_RES_TIMERS == 0;

在这种配置模式下,我们回到上一节的tick_setup_device函数的最后:

[cpp] view plain copy

  1. if (td->mode == TICKDEV_MODE_PERIODIC)
  2. tick_setup_periodic(newdev, 0);
  3. else
  4. tick_setup_oneshot(newdev, handler, next_event);

因为启动期间,第一个注册的tick_device必然工作在TICKDEV_MODE_PERIODIC模式,所以tick_setup_periodic会设置clock_event_device的事件回调字段event_handler为tick_handle_periodic,工作一段时间后,就算有新的支持TICKDEV_MODE_ONESHOT模式的clock_event_device需要替换,再次进入tick_setup_device函数,tick_setup_oneshot的handler参数也是之前设置的tick_handle_periodic函数,所以我们考察tick_handle_periodic即可:

[cpp] view plain copy

  1. void tick_handle_periodic(struct clock_event_device *dev)
  2. {
  3. int cpu = smp_processor_id();
  4. ktime_t next;
  5. tick_periodic(cpu);
  6. if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
  7. return;
  8. next = ktime_add(dev->next_event, tick_period);
  9. for (;;) {
  10. if (!clockevents_program_event(dev, next, false))
  11. return;
  12. if (timekeeping_valid_for_hres())
  13. tick_periodic(cpu);
  14. next = ktime_add(next, tick_period);
  15. }
  16. }

该函数首先调用tick_periodic函数,完成tick事件的所有处理,如果是周期触发模式,处理结束,如果工作在单触发模式,则计算并设置下一次的触发时刻,这里用了一个循环,是为了防止当该函数被调用时,clock_event_device中的计时实际上已经经过了不止一个tick周期,这时候,tick_periodic可能被多次调用,使得jiffies和时间可以被正确地更新。tick_periodic的代码如下:

[cpp] view plain copy

  1. static void tick_periodic(int cpu)
  2. {
  3. if (tick_do_timer_cpu == cpu) {
  4. write_seqlock(&xtime_lock);
  5. /* Keep track of the next tick event */
  6. tick_next_period = ktime_add(tick_next_period, tick_period);
  7. do_timer(1);
  8. write_sequnlock(&xtime_lock);
  9. }
  10. update_process_times(user_mode(get_irq_regs()));
  11. profile_tick(CPU_PROFILING);
  12. }

如果当前cpu负责更新时间,则通过do_timer进行以下操作:

  • 更新jiffies_64变量;
  • 更新墙上时钟;
  • 每10个tick,更新一次cpu的负载信息;

调用update_peocess_times,完成以下事情:

  • 更新进程的时间统计信息;
  • 触发TIMER_SOFTIRQ软件中断,以便系统处理传统的低分辨率定时器;
  • 检查rcu的callback;
  • 通过scheduler_tick触发调度系统进行进程统计和调度工作;

Linux时间子系统之四:定时器的引擎:clock_event_device的更多相关文章

  1. Linux时间子系统之四:定时器的引擎:clock_event_device【转】

    本文转载自:http://blog.csdn.net/droidphone/article/details/8017604 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[+] ...

  2. Linux时间子系统之四:Timer在用户和内核空间流程

    用户空间应用中创建一个Timer(alarm/setitimer/POSIX Timer等等),然后程序继续执行: 内核进入创建/设置Timer系统调用,开始计时,在超时后通过何种方式通知用户空间: ...

  3. Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现

    转自:http://blog.csdn.net/droidphone/article/details/8074892 上一篇文章,我介绍了传统的低分辨率定时器的实现原理.而随着内核的不断演进,大牛们已 ...

  4. Linux时间子系统之五:低分辨率定时器的原理和实现

    专题文档汇总目录 Notes:低精度timer在内核中的数据结构以及API接口:低精度timer精巧高效的分组,使用cascade进行定时器移位,组内Timer FIFO:低精度Timer的初始化流程 ...

  5. Linux时间子系统专题汇总

    关于Linux时间子系统有两个系列文章讲的非常好,分别是WowoTech和DroidPhone. 还有两本书分别是介绍: Linux用户空间时间子系统<Linux/UNIX系统编程手册>的 ...

  6. Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)

    在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工 ...

  7. Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)【转】

    转自:http://blog.csdn.net/droidphone/article/details/8112948 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 数据结 ...

  8. Linux时间子系统之(十七):ARM generic timer驱动代码分析

    专题文档汇总目录 Notes:ARM平台Clock/Timer架构:System counter.Timer以及两者之间关系:Per cpu timer通过CP15访问,System counter通 ...

  9. Linux时间子系统(十七) ARM generic timer驱动代码分析

    一.前言 关注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驱动工程师应该会注意到timer硬件的演化过程.在单 ...

随机推荐

  1. 一个类搞定UIScrollView那些事儿

    前言 UIScrollView可以说是我们在日常编程中使用频率最多.扩展性最好的一个类,根据不同的需求和设计,我们都能玩出花来,当然有一些需求是大部分应用通用的,今天就聊一下以下需求,在一个categ ...

  2. 嵌入式C语言位运算之清位置位

    如题,在嵌入式开发中,掌握位运算是节省开发时间和提高开发效率的一种高效方式. 我们不得不去熟悉如何快速掌握位运算这种高效的技巧,接下来看看程序.. #include <stdio.h> # ...

  3. Erlang和Web

    Erlang和Web 本文译自: http://ninenines.eu/docs/en/cowboy/1.0/guide/erlang_web/ Web是并发的 当你访问一个网站,很少有并发.几个连 ...

  4. TCP连接建立系列 — 服务端接收ACK段(二)

    本文主要分析:三次握手中最后一个ACK段到达时,服务器端的处理路径. 内核版本:3.6 Author:zhangskd @ csdn blog 创建新sock 协议族相关的操作函数,我们要看的是TCP ...

  5. hbase thrift 定义

    /*  * Licensed to the Apache Software Foundation (ASF) under one  * or more contributor license agre ...

  6. Aho-Corasick算法学习

    1.概述 Aho-Corasick自动机算法(简称AC自动机)1975年产生于贝尔实验室.该算法应用有限自动机巧妙地将字符比较转化为了状态转移.此算法有两个特点,一个是扫描文本时完全不需要回溯,另一个 ...

  7. 【50】java 匿名内部类剖析

    匿名内部类介绍: 匿名内部类也就是没有名字的内部类 正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写 但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口 匿名内部类的声明 ...

  8. DB Query Analyzer 5.05 is released, 65 articles concerned have been published

    DB Query Analyzer 5.05 is released, 65 articles concerned have been published DB Query Analyzer is p ...

  9. leetCode(66)-Excel Sheet Column Title

    题目: Given a positive integer, return its corresponding column title as appear in an Excel sheet. For ...

  10. LeetCode(65)-Power of Four

    题目: Given an integer (signed 32 bits), write a function to check whether it is a power of 4. Example ...