添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
分类: 电源管理子系统

1. 前言

在计算机系统中,CPU的功能是执行程序,总结起来就是我们在教科书上学到的:取指、译码、执行。那么问题来了,如果没有程序要执行,CPU要怎么办?也许您会说,停掉就是了啊。确实,是要停掉,但何时停、怎么停,却要仔细斟酌,因为实际的软硬件环境是非常复杂的。

我们回到Linux kernel上,Linux系统中,CPU被两类程序占用:一类是进程(或线程),也称进程上下文;另一类是各种中断、异常的处理程序,也称中断上下文。

进程的存在,是用来处理事务的,如读取用户输入并显示在屏幕上。而事务总有处理完的时候,如用户不再输入,也没有新的内容需要在屏幕上显示。此时这个进程就可以让出CPU,但会随时准备回来(如用户突然有按键动作)。同理,如果系统没有中断、异常事件,CPU就不会花时间在中断上下文。

在Linux kernel中,这种CPU的无所事事的状态,被称作idle状态,而cpuidle framework,就是为了管理这种状态。

注:cpuidle framework系列文章会以ARM64作为示例平台,由于ARM64刚刚发布不久,较早版本的kernel没有相关的代码,因此选用了最新的 3.18-rc4 版本的kernel。

2. 功能概述

曾经有过一段时间,Linux kernel的cpu idle框架是非常简单的,简单到driver工程师只需要在“include\asm-arm\arch-xxx\system.h”中定义一个名字为arch_idle的inline函数,并在该函数中调用kernel提供的cpu_do_idle接口,就Okay了,剩下的实现kernel全部帮我们做了,如下:

   1: static inline void arch_idle(void)
   3:         cpu_do_idle();

以蜗蜗之前使用过的一个ARM926的单核CPU为例(内核版本为Linux2.6.23),cpuidle的处理过程是:
B start_kernel(arch\arm\kernel\head-common.S)
start_kernel->rest_init(init\main.c)
;系统初始化完成后,将第一个进程(init)变为idle进程,
;以下都是在进程的循环中,周而复始…

cpu_idle->default_idle(arch\arm\kernel\process.c)
arch_idle(include\asm-arm\arch-xxx\system.h)
cpu_do_idle(include/asm-arm/cpu-single.h)
cpu_arm926_do_idle(arch/arm/mm/proc-arm926.S)
mcr     p15, 0, r0, c7, c0, 4           @ Wait for interrupt ;WFI指令

idle进程的存在,是为了解决“何时idle”的问题。

我们知道,Linux系统运行的基础是进程调度,而所有进程都不再运行时,称作cpu idle。但是,怎么判断这种状态呢?kernel采用了一个比较简单的方法:在init进程(系统的第一个进程)完成初始化任务之后,将其转变为idle进程,由于该进程的优先级是最低的,所以当idle进程被调度到时,则说明系统的其它进程不再运行了,也即CPU idle了。最终,由idle进程调用idle指令(这里为WFI),让CPU进入idle状态。

ARM WFI和WFE指令 ”中介绍过,WFI Wakeup events会把CPU从WFI状态唤醒,通常情况下,这些events是一些中断事件,因此CPU唤醒后会执行中断handler,在handler中会wakeup某些进程,在handler返回的时候进行调度,当没有其他进程需要调度执行的时候,调度器会恢复idle进程的执行,当然,idle进程不做什么,继续进入idle状态,等待下一次的wakeup。

2)WFI

WFI用于解决“怎么idle”的问题。

一般情况下,ARM CPU idle时,可以使用WFI指令,把CPU置为Wait for interrupt状态。该状态下,至少(和具体ARM core的实现有关,可参考“ ARM WFI和WFE指令 ”)会把ARM core的clock关闭,以节省功耗。

也许您会觉得,上面的过程挺好了,为什么还要开发cpuide framework?蜗蜗的理解是:

ARM CPU的设计越来越复杂,对省电的要求也越来越苛刻,因而很多CPU会从 “退出时的延迟” “idle状态下的功耗” 两个方面考虑,设计多种idle级别。对延迟较敏感的场合,可以使用低延迟、高功耗的idle;对延迟不敏感的场合,可以使用高延迟、低功耗的idle。

而软件则需要根据应用场景,在恰当的时候,选择一个合适的idle状态。而选择的策略是什么,就不是那么简单了。这就是cpuidle framework的存在意义(我们可以根据下面cpuidle framework的软件架构,佐证这一点)。

3. 软件架构

Linux kernel中,cpuidle framework位于“drivers/cpuidle”文件夹中,包含cpuidle core、cpuidle governors和cpuidle drivers三个模块,再结合位于kernel sched中的cpuidle entry,共同完成cpu的idle管理。软件架构如下图: cpuidle framework

1)kernel schedule模块

位于kernel\sched\idle.c中,负责实现idle线程的通用入口(cpuidle entry)逻辑,包括idle模式的选择、idle的进入等等。

2)cpuidle core

cpuidle core负责实现cpuidle framework的整体框架,主要功能包括:

根据cpuidle的应用场景,抽象出cpuidle device、cpuidle driver、cpuidle governor三个实体;

以函数调用的形式,向上层sched模块提供接口;

以sysfs的形式,向用户空间提供接口;

向下层的cpuidle drivers模块,提供统一的driver注册和管理接口;

向下层的governors模块,提供统一的governor注册和管理接口。

cpuidle core的代码主要包括:cpuidle.c、driver.c、governor.c、sysfs.c。

3)cpuidle drivers

负责idle机制的实现,即:如何进入idle状态,什么条件下会退出,等等。

不同的architecture、不同的CPU core,会有不同的cpuidle driver,平台驱动的开发者,可以在cpuidle core提供的框架之下,开发自己的cpuidle driver。代码主要包括:cpuidle-xxx.c。

4)cpuidle governors

Linux kernel的framework有两种比较固定的抽象模式:

模式1,provider/consumer模式,interrupt、clock、timer、regulator等大多数的framework是这种模式。它的特点是,这个硬件模块是为其它一个或多个模块服务的,因而framework需要从对上(consumer)和对下(provider)两个角度进行软件抽象;

模式2,driver/governor模式,本文所描述的cpuidle framework即是这种模式。它的特点是:硬件(或者该硬件所对应的驱动软件)可以提供多种可选“方案”(这里即idle level),“方案”的实现(即机制),由driver负责,但是到底选择哪一种“方案”(即策略),则由另一个模块负责(即这里所说的governor)。

模式2的解释可能有点抽象,把它放到cpuidle的场景里面,就很容易理解了:

前面讲过,很多CPU提供了多种idle级别(即上面所说的“方案”),这些idle 级别的主要区别是“idle时的功耗”和“退出时延迟”。cpuidle driver(机制)负责定义这些idle状态(每一个状态的功耗和延迟分别是多少),并实现进入和退出相关的操作。最终,cpuidle driver会把这些信息告诉governor,由governor根据具体的应用场景,决定要选用哪种idle状态(策略)。

kernel中,cpuidle governor都位于governors/目录下。

4. 软件流程

在阅读本章之前,还请读者先阅读如下三篇文章:

Linux cpuidle framework(2)_cpuidle core

Linux cpuidle framework(3)_ARM64 generic CPU idle driver

Linux cpuidle framework(4)_menu governor

前面提到过,kernel会在系统启动完成后,在init进程(或线程)中,处理cpuidle相关的事情。大致的过程是这样的(kernel启动相关的分析,会在其它文章中详细介绍):

首先需要说明的是,在SMP(多核)系统中,CPU启动的过程是:

1)先启动主CPU,启动过程和传统的单核系统类似:stext-->start_kernel-->rest_init-->cpu_startup_entry

2)启动其它CPU,可以有多种方式,例如CPU hotplug等,启动过程为:secondary_startup-->__secondary_switched-->secondary_start_kernel-->cpu_startup_entry

上面的代码位于./arch/arm64/kernel/head.S、init/main.c等等,感兴趣的读者可以自行参考。最终都会殊途同归,运行至cpu_startup_entry接口,该接口位于kernel/sched/idle.c中,负责处理CPU idle的事情,流程如下(暂时忽略一些比较难理解的分支,如cpu idle poll等)。

cpu_startup_entry流程:

cpu_startup_entry
arch_cpu_idle_prepare,进行idle前的准备工作,ARM64中没有实现
cpu_idle_loop,进入cpuidle的主循环
如果系统当前不需要调度(!need_resched()),执行后续的动作
local_irq_disable,关闭irq中断
arch_cpu_idle_enter,arch相关的cpuidle enter,ARM64中没有实现
cpuidle_idle_call,main idle function
cpuidle_select,通过cpuidle governor,选择一个cpuidle state
cpuidle_enter,通过cpuidle state,进入该idle状态
中断产生,idle返回(注意,此时irq是被禁止的,因此CPU不能响应产生中断的事件)
cpuidle_reflect,通知cpuidle governor,更新状态
local_irq_enable,使能中断,响应中断事件,跳转到对应的中断处理函数

arch_cpu_idle_exit,和enter类似,ARM64没有实现 G
2019-05-13 00:09

hi wowo,
看了您的cpuidle framework系列,受益匪浅。现在有个实际场景:我们的线程运行结束后进入某个特定的cpuidle state时(或从该cpuidle state恢复时,具体未知),性能会跑得异常差,其他idle state均正常。请问:
1. 目前想到的解决方案是将sys/devices/system/cpu/cpux/cpuidle下的disable写成1。这种方法在实际中是否有明显已知的问题?
2. 内核是否有提供某个API来控制某个cpuidle state的使能状态?
3. 是否有其他解决该问题的思路?
psg
2018-05-07 15:04
wowo,你好
有遇到过偶发关机时无缘无故死机的问题,没有panic等异常log,多次测试发现取消CONFIG_CPU_IDLE内核配置项就不会出现,有试过把kernel hacking的一些调试选项打开,问题也不会出现。
1)遇到这种问题该如何分析定位?
2)关闭该内核选项会对产品造成什么后果?
wowo
2018-05-08 09:11
@psg:能查到死在什么状态了吗?实在没办法,可以把CONFIG_CPU_IDLE的关键代码一点点屏蔽一下,查查是哪里导致的问题。
如果连idle都没法开,这个cpu就太水了。
至于后果,就是功耗、发热之类的可能会上去吧。
psg
2018-05-08 15:06
@wowo:这个问题最主要没有错误信息,我只能一点一点在函数里面加printk,每次出现问题的时候最后的打印是在关机过程中usb控制器的shutdown函数里面,但每次的打印都不是完整的,一句话可能输出了一半。
例如这种:
[ 110.908373] sunxi-ehci 1c1c000.ehci2-controller: shutdown
[ 110.914589] drivers/usb/host/ehci_sunxi.c sunxi_ehci_
或者这种:
[ 177.662155] [ohci2-controller]: ohci shutdown end
[ 177.667322] sunxi-ehci 1c1c000.
或者这种:
[ 47.538096] drivers/usb/host/sunxi_hci.c close_clock 406
[ 47.544242] drivers/usb/host/ohci_sunxi.c sunxi_ohci_hcd_

这问题还偶发,自动化测试一两个小时才出现一次。实在找不到好的对策或者继续排查的办法。后来对比原厂的改动才动到IDEL的配置的,他们的代码里面早就取消了这个config
zozowit
2015-11-26 11:11
请问,“由于该进程的优先级是最低的,所以当idle进程被调度到时,则说明系统的其它进程不再运行了,也即CPU idle了”,这个进程的优先级是最低的,是在哪里有设置吗?
wowo
2015-11-27 16:46
@zozowit:抱歉,评论太多把您这个漏了……
Kernel调度器对idle thread有特殊照顾,具体可参考kernel/sched/core.c:
static void set_load_weight(struct task_struct *p)
{
int prio = p->static_prio - MAX_RT_PRIO;
struct load_weight *load = &p->se.load;

/*
* SCHED_IDLE tasks get minimal weight:
*/
if (p->policy == SCHED_IDLE) {
load->weight = scale_load(WEIGHT_IDLEPRIO);
load->inv_weight = WMULT_IDLEPRIO;
return;
}

load->weight = scale_load(prio_to_weight[prio]);
load->inv_weight = prio_to_wmult[prio];
}
zozowit
2015-12-01 17:53
@wowo:感谢解答,还有一点请您赐教,SCHED_IDLE是在哪里设定的?
wowo
2015-12-02 10:34
@zozowit:抱歉,我为上面的回复以及文中的表述道歉,关于idle进程的优先级,应该是这样的(对进程管理的理解有限,可能还有错误):
1. idle进程的优先级并不是最低的,实际上,它的用户优先级(user nice)是0,正常情况下,算得上系统中最高的了。
2. idle进程并不会参与调度,因此当无进程调度的时候执行idle进程,是调度器保证的,这一点并不依赖它的优先级。
3. idle进程的优先级怎么来的?
a)start_kernel后kernel执行的第一个进程,不是由do_fork动态生成的,而是静态定义的(也就是传说中的init_task),init_task的优先级为(include/linux/init_task.h):
.prio           = MAX_PRIO-20,                                  \
.static_prio    = MAX_PRIO-20,                                  \
.normal_prio    = MAX_PRIO-20,
也就是说,全部是120,转换为user nice为0,user nice的范围是-20~19,值越大优先越低,因此init_task的优先级算是比较高的了。
b)随后的kernel运行过程中,都是在init_task的上下文中,最后init_task会变成boot cpu的idle线程,因此boot cpu的idle线程的优先级就继承了init_task的了。
c)对于其它CPU来说,执行如下操作(具体可参考”http://www.wowotech.net/pm_subsystem/cpu_hotplug.html“):rest_init-->kernel_init-->kernel_init_freeable-->smp_init—>idle_threads_init—>idle_init-->fork_idle-->copy_process-->dup_task_struct(current)
这个过程会两次task copy:第一次是在reset_init中,调用kernel_thread fork kernel_init线程;第二次是fork_idle时将current task copy到idle task中,因此,最终非boot cpu的idle task的优先级,也是继承自init_task。
4. 关于这个标记--SCHED_IDLE,现在kernel的调度器貌似没有使用的。

进程管理的东西太复杂了,看着战战巍巍的,希望高手指正!也希望有高手把进程管理的东西给大家分析一下。
zozowit
2015-12-02 10:43
@wowo:您好,恕我愚笨。请问“ idle进程并不会参与调度,因此当无进程调度的时候执行idle进程,是调度器保证的”这个是否有代码可以阅读。另外之前您列出的代码,看上去确实是把这个优先级调低诶?
/*
* SCHED_IDLE tasks get minimal weight:
*/
if (p->policy == SCHED_IDLE) {
load->weight = scale_load(WEIGHT_IDLEPRIO);
load->inv_weight = WMULT_IDLEPRIO;
return;
}
wowo
2015-12-02 10:52
@zozowit:调度器的行为,我也没有细看,太复杂了。
我之前贴出的这一段代码,确实是这个逻辑,但我找了现在kernel的代码,没有发现哪个进程使用SCHED_IDLE这个policy,可能是kernel提供的一种方法吧,只是现在没有人用。
wowo
2015-12-02 11:42
@zozowit:确实,idle作为一个单独的schedule class了,
const struct sched_class idle_sched_class
至于schedule class的含义,我就不了解了。
Foreveryoung
2016-07-01 17:40
@wowo:crash64> sym _sched_class
symbol not found: _sched_class
possible alternatives:
ffffffc0009cfd28 (r) fake_sched_class
ffffffc0009d08d8 (R) idle_sched_class
ffffffc0009d0ac8 (R) fair_sched_class
ffffffc0009d0d00 (R) rt_sched_class
ffffffc0009d0ed8 (R) dl_sched_class
ffffffc0009d1000 (R) stop_sched_class
crash64> struct sched_class ffffffc0009d08d8
struct sched_class {
next = 0x0,
enqueue_task = 0x0,
dequeue_task = 0xffffffc0000d7d08 <dequeue_task_idle>,
yield_task = 0x0,
yield_to_task = 0x0,
check_preempt_curr = 0xffffffc0000d7cdc <check_preempt_curr_idle>,
pick_next_task = 0xffffffc0000d7b90 <pick_next_task_idle>,
put_prev_task = 0xffffffc0000d7cb0 <put_prev_task_idle>,
select_task_rq = 0xffffffc0000d7b64 <select_task_rq_idle>,
migrate_task_rq = 0x0,
post_schedule = 0x0,
task_waking = 0x0,
task_woken = 0x0,
set_cpus_allowed = 0x0,
rq_online = 0x0,
rq_offline = 0x0,
set_curr_task = 0xffffffc0000d7be8 <set_curr_task_idle>,
task_tick = 0xffffffc0000d7bd0 <task_tick_idle>,
task_fork = 0x0,
task_dead = 0x0,
switched_from = 0x0,
switched_to = 0xffffffc0000d7c70 <switched_to_idle>,
prio_changed = 0xffffffc0000d7c34 <prio_changed_idle>,
get_rr_interval = 0xffffffc0000d7c00 <get_rr_interval_idle>,
update_curr = 0xffffffc0000d7c1c <update_curr_idle>,
task_move_group = 0x0
}
整个体系好像有六种sched_class,芯片架构为arm64 V8,大神清楚分别对应什么么?
pingchangxin
2015-10-26 20:39
请教一个问题,现在内核支持supend to RAM,这个流程会在pm.c中有***_pm_enter,里面有PM_SUSPEND_STANDY或者PM_SUPEND_MEM,而现在cpuidle,应该也可以在**_enter_idle里面可以根据此时的硬件状态,来做现在是WFI,还是进deep sleep,总感觉这两个有点重合,不知道,你理解的这两者是怎么结合起来的,谢谢!
wowo
2015-10-26 20:49
@pingchangxin:我的理解是:
STR和cpuidle,不是同一个事物,它们的目标不同,不具有可比性。
如果碰巧,它们通过相同的手段达到目的,这只是一个假象而已。
例如,系统进入STR状态时,就不会有idle的概念(idle线程被冻结了)。
所以,您的问题:怎么结合的?我觉得应该不会结合。
pingchangxin
2015-10-27 13:50
@wowo:谢谢你,可能我被这个假象糊弄了,总觉得他们直接是不是有什么猫腻,呵呵
linuxer
2015-10-19 10:37
@cym:系统有多少个cpu core,就有多少个idle进程,BSP的idle进程的pid是0,其他的cpu的idle进程是在init进程(pid=1)中初始化的,因此secondary cpu的idle进程和BSP 的idle进程的pid是不同的。
wowo
2015-10-19 11:06
@linuxer:关于CPU的idle线程的创建过程,可以参考“http://www.wowotech.net/pm_subsystem/cpu_hotplug.html”中有关的章节。

idle thread的创建过程是:fork_idle-->copy_process

我对进程管理看的不多,不知道问题的答案(是,或者不是),但根据这个过程,我觉得所有的idle thread的pid应该是一个,即init_struct_pid。linuxer,是不是这样呢?
linuxer
2015-10-19 12:05
@wowo:我也没有仔细看,我的直觉是觉得fork一个进程当然要分配一个pid了,要不还是等晚上有时间仔细看看代码好了
linuxer
2015-10-19 12:27
@wowo:你说的是对的,我又看进去copy_process的代码,其中有一段:

if (pid != &init_struct_pid) {
pid = alloc_pid(p->nsproxy->pid_ns_for_children);
if (IS_ERR(pid)) {
retval = PTR_ERR(pid);
goto bad_fork_cleanup_io;
}
}

看起来如果是init_struct_pid就不再分配pid了。
cym
2015-10-19 13:50
@linuxer:谢谢众大虾!
Foreveryoung
2016-07-01 17:46
@cym:crash64> ps |grep "swapper"
>     0      0   0  ffffffc000f80160  RU   0.0       0      0  [swapper/0]
>     0      0   1  ffffffc13ca00c00  RU   0.0       0      0  [swapper/1]
>     0      0   2  ffffffc13ca01800  RU   0.0       0      0  [swapper/2]
>     0      0   3  ffffffc13ca02400  RU   0.0       0      0  [swapper/3]
>     0      0   4  ffffffc13ca03000  RU   0.0       0      0  [swapper/4]
>     0      0   5  ffffffc13ca03c00  RU   0.0       0      0  [swapper/5]
>     0      0   6  ffffffc13ca04800  RU   0.0       0      0  [swapper/6]
>     0      0   7  ffffffc13ca05400  RU   0.0       0      0  [swapper/7]
pid都是0,每个cpu上独立的线程,相互之间也扯不上什么关系吧,谈不上线程组,这个swapper线程就是所谓的idle线程。
wowo
2016-07-01 21:12
@Foreveryoung:是的。
tim
2015-03-03 11:07
如果 cpu 进入的 idle 状态是 WFI(C0 state),则退出的流程是,先是 cpu 退出 WFI 状态,此时中断是被关闭的,所以不会响应中断,代码继续执行,当然执行的代码是 idle 线程,之后打开中断,执行中断处理函数。
如果 cpu 进入是其他状态(C1/C2 state),退出时,则是 cpu 会重新上电,然后从 BROM 运行,之后是 mcpm_entry_point 函数 -> cpu_resume 函数。此时 cpu 的上下文已经恢复,上下文还是之前的 idle 线程,之后的流程与 C0 类似。
linuxer
2014-12-18 10:12
有个问题探讨一下:
你文章中说到:“当任意事件把CPU从idle状态唤醒时,接着运行idle进程,idle进程会判断是否有其它进程需要运行,如果有则发起一次调度,将CPU让给其它进程。”

当ARM处理器调用WFI指令进入idle状态的时候,可以唤醒它的至少应该包括下面的场景:
(1)来自其他处理器的SEV指令
(2)送达本处理器上的中断

当是中断唤醒的场景的时候,是否应该立刻执行中断handler?难道还是要先运行idle进程?我猜想应该先执行中断handler,在handler中会wakeup某些进程,在handler返回的时候进行调度(而不是idle进程判断是否要有其他进程需要运行),当没有其他进程需要调度执行的时候,调度器会恢复idle进程的执行,当然,idle进程不做什么,继续进入idle状态,等待下一次的wakeup
wowo
2014-12-18 12:30
@linuxer:传统的使用WFI的idle,确实应该是这样子啊,还是你总结的好,哈哈,等等我把这段话抄上去。
对于后面比较复杂的idle framework,是否会存在像suspend/resume那样的场景,可以在idle时suspend,然后返回时继续往下执行,这个还不太清楚,要往下继续看。

PS:WFE才会有SEV指令,WFI没有。
linuxer
2014-12-18 13:08
@wowo:呵呵~~我看错了,WFE是用在spin lock中, 我刚才还想在idle的时候使用WFE怪怪的,原来是自己看错了。
wowo
2015-01-20 23:31
@linuxer:我更新了这篇文章的最后一节,结果还真和我们想象的不一样,在cpuidle framework中,进入idle时,是关中断的,因此从idle回来后,只能先回到idle thread,打开中断后,才能执行中断handler。
Y
2015-07-15 22:54
@wowo:从C1被中断唤醒后,是继续执行 enter(C1)的下一条指令,不需要软件参与吗? 我看到楼上有兄弟说从BROM开始重新执行? 到底哪个是对的
thanks
wowo
2015-07-16 08:44
@Y:idle指令执行之后,CPU要做那些事情,idle回来之前,CPU要做哪些事情,都是和具体的平台有关的。不同平台,有不同的实现。
因此,回到您的问题,哪个都有可能是对的。这也是这篇文章没有涉及这部分内容的原因。
但有一点是可以保证的,对cpuidle framework本身,它“看到的”,是一致的,即:从哪里进去,就从哪里出来。
shoujixiaodao
2015-07-22 11:12
@wowo:进入idle时,是关中断的,因此从idle回来后,只能先回到idle thread,打开中断后,才能执行中断handler
----有点小疑惑,既然关中断了。中断还能唤醒WFI状态吗?多谢!
wowo
2015-07-22 13:17
@shoujixiaodao:可以的,具体可参考linuxer同学在这里的回复:http://www.wowotech.net/?post=81#2191
shoujixiaodao
2015-07-22 13:48
@wowo:学习力,涨姿势。多谢!
Allen
2017-07-15 21:40
@shoujixiaodao:4种方法
The WFI instruction has the effect of suspending execution until the core is woken up by one of the following conditions:
• An IRQ interrupt, even if the CPSR I-bit is set.
• An FIQ interrupt, even if the CPSR F-bit is set.
• An asynchronous abort.
• A Debug Entry request, even if JTAG Debug is disabled.
bsp
2020-08-20 17:24
@Allen:In Cortex A series WFI would be executed  and the processor would enter the standby mode.

Interrupts which are pending would not prevent processor to suspend execution.

When the processor is in WFI and an interrupt occurs:
This wakes up the processor  and the processor jumps to the interrupt handler associated with that interrupt.

But when CPSR.I is disabled  this just wakes up the processor  and the processor starts executing the next instruction from the program counter.
  • SuiTang
    请教下大神,蓝牙Beacon的Local Name可以重复吗...
  • huozi
    @testtest:下面 nothing 的提问中应该回答了...
  • huozi
    @passenger:是的,在ldrex后是会被打断,但返回...
  • hdzhang
    CMWQ机制引入后我发现会有个问题,创建UNBOUND类型的...
  • 北葵依旧菜
    感谢博主,博主多年前的文章在今天依旧熠熠生辉,解答了很多疑惑
  • jqdeng
    @新手:curr的确是从rb tree拿下来了,但是on_r...
  • Linux内核分析(25) 统一设备模型(15) 电源管理子系统(43) 中断子系统(15) 进程管理(31) 内核同步机制(26) GPIO子系统(5) 时间子系统(14) 通信类协议(7) 内存管理(31) 图形子系统(2) 文件系统(5) TTY子系统(6)
  • 在PowerShell中使用Vim
  • Linux I2C framework(3)_I2C consumer
  • Concurrency Managed Workqueue之(一):workqueue的基本概念
  • MMC/SD/SDIO介绍
  • X-009-KERNEL-Linux kernel的移植(Bubblegum-96平台)
  • 2024年2月(1)
  • 2023年5月(1)
  • 2022年10月(1)
  • 2022年8月(1)
  • 2022年6月(1)
  • 2022年5月(1)
  • 2022年4月(2)
  • 2022年2月(2)
  • 2021年12月(1)
  • 2021年11月(5)
  • 2021年7月(1)
  • 2021年6月(1)
  • 2021年5月(3)
  • 2020年3月(3)
  • 2020年2月(2)
  • 2020年1月(3)
  • 2019年12月(3)
  • 2019年5月(4)
  • 2019年3月(1)
  • 2019年1月(3)
  • 2018年12月(2)
  • 2018年11月(1)
  • 2018年10月(2)
  • 2018年8月(1)
  • 2018年6月(1)
  • 2018年5月(1)
  • 2018年4月(7)
  • 2018年2月(4)
  • 2018年1月(5)
  • 2017年12月(2)
  • 2017年11月(2)
  • 2017年10月(1)
  • 2017年9月(5)
  • 2017年8月(4)
  • 2017年7月(4)
  • 2017年6月(3)
  • 2017年5月(3)
  • 2017年4月(1)
  • 2017年3月(8)
  • 2017年2月(6)
  • 2017年1月(5)
  • 2016年12月(6)
  • 2016年11月(11)
  • 2016年10月(9)
  • 2016年9月(6)
  • 2016年8月(9)
  • 2016年7月(5)
  • 2016年6月(8)
  • 2016年5月(8)
  • 2016年4月(7)
  • 2016年3月(5)
  • 2016年2月(5)
  • 2016年1月(6)
  • 2015年12月(6)
  • 2015年11月(9)
  • 2015年10月(9)
  • 2015年9月(4)
  • 2015年8月(3)
  • 2015年7月(7)
  • 2015年6月(3)
  • 2015年5月(6)
  • 2015年4月(9)
  • 2015年3月(9)
  • 2015年2月(6)
  • 2015年1月(6)
  • 2014年12月(17)
  • 2014年11月(8)
  • 2014年10月(9)
  • 2014年9月(7)
  • 2014年8月(12)
  • 2014年7月(6)
  • 2014年6月(6)
  • 2014年5月(9)
  • 2014年4月(9)
  • 2014年3月(7)
  • 2014年2月(3)
  • 2014年1月(4)
  •