购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.1 Linux wakeup source的设计与实现

在本节,我们着重对Linux内核中的wakeup source机制进行分析,包括wakeup source机制的架构设计概览、模块功能详解、配置信息解析、主要数据结构、主要函数分析、函数工作时序等。

2.1.1 架构设计概览

wakeup source在Linux内核低功耗软件栈中的位置如图2-1所示。

wakeup source模块可以与内核中的其他模块或者上层服务交互,并最终体现在wakeup source模块中对睡眠锁的控制上。

2.1.2 模块功能详解

在Linux内核中,wakeup source是睡眠流程中各个组件关于是否同意本业务睡眠的一套投票机制。整套机制的处理逻辑基本上是围绕combined_event_count变量展开的,在此变量中,高16位记录系统已处理的所有的唤醒事件总数,低16位记录在处理中的唤醒事件总数。每次持锁时,处理中的唤醒事件记录会加1(低16位);每次释放锁时,处理中的唤醒事件记录会减1(低16位),同时已处理的唤醒事件记录会加1(高16位)。对于每次系统是否能够进入睡眠,通过是否有正在处理中的唤醒事件来判断。该模块实现的主要功能有:

1)持锁功能;

2)释放锁功能;

3)注册锁功能;

4)去注册锁功能;

5)查询激活状态锁个数功能。

图2-1 wakeup source在低功耗软件栈中的位置

2.1.3 配置信息解析

1)wakeup source功能受宏CONFIG_PM_SLEEP控制,如果需要使能该功能,则必须将CONFIG_PM_SLEEP设置为y。

2)相关实现在drivers\base\power\wakeup.c文件中。

3)相关函数声明在include\linux\pm_wakeup.h中。

2.1.4 主要数据结构

1.wakeup_source结构体

结构体原型定义如下:

成员变量说明如下。

name:顾名思义,即该wakeup source的名字,方便记录查看。

id:wakeup source模块给本wakeup source分配的ID。

entry:链表结构,用于把本wakeup source节点维护到系统wakeup source的全局链表中。

lock:保护本结构体变量访问所使用的互斥锁。

wakeirq:与本wakeup source绑定的唤醒中断相关的结构体,用户可以自行把指定中断与wakeup source绑定。

timer:超时时使用,比如定义本wakeup source为超时锁,指定在一定时间后释放锁。

timer_expires:要设置的定时器的超时时间。

total_time:记录本wakeup source激活的总时长。

max_time:在wakeup source激活的历史中,最长一次的激活时间。

last_time:最近一次访问本wakeup source的时间。

prevent_sleep_time:因为本wakeup source导致的阻止autosleep进入睡眠的总时间。

event_count:如果是本wakeup source被持锁,则event_count会加1并作为维测记录。

active_count:注意持锁接口是可以在上次没有释放锁时再次调用的,每次调用持锁接口时event_count会加1,但是active_count只会在第一次激活时加1。

relax_count:每次释放锁时,该值会加1,与active_count一一对应。

expire_count:对应的超时锁超时的次数。

wakeup_count:伴随event_count一起增加,受events_check_enabled使能标记控制。

dev:与wakeup source绑定的设备。

active:标记是否处于激活状态。

autosleep_enabled:标记autosleep是否使能。

2.combined_event_count变量

用法:

该变量是1个组合计数变量,高16位记录唤醒事件的总数,低16位记录正在处理中的唤醒事件总数。系统根据正在处理中的唤醒事件来判断是否可以进入睡眠。

3.wakeup_sources变量

用法:

所有通过调用wakeup_source_register注册的wakeup source全部维护在该链表中,以便系统进行维护。

2.1.5 主要函数分析

本节介绍wakeup.c文件中的主要函数,因为函数本身比较简单,所以不再做过多介绍,感兴趣的读者可以参考源码实现。

在wakeup.c中,对外接口通常是成对出现的,比如:

1)wakeup_source_register与wakeup_source_unregister,分别表示注册与去注册一个wakeup source。

2)pm_stay_awake与pm_relax,针对device类型对象提供的持锁和释放锁接口。

3)__pm_stay_awake与__pm_relax,针对wakeup_source类型对象提供的持锁和释放锁接口。

其中2)和3)两组接口在对应场景中配套使用即可。

1.wakeup_source_register

该函数的功能是创建dev设备中的wakeup source,并把创建的wakeup source添加到全局链表wakeup_sources中,方便后续维护。

函数原型如下:

其中,入参struct device*dev为要创建wakeup source的设备,const char*name为要创建的wakeup source的名字。如果返回值为struct wakeup_source指针,说明struct wakeup_source类型的对象创建成功;如果返回值为NULL,说明创建失败。

wakeup_source_register的具体实现代码如下:

实现中,先调用wakeup_source_create进行目标wakeup source的创建。如果创建失败,则返回NULL;如果创建成功,则调用wakeup_source_add将其添加到全局链表wakeup_source中。

2.wakeup_source_unregister

该函数的功能是删除注册的wakeup source并释放其占用的系统资源。

函数原型如下:

其中入参*ws表示需要去注册的wakeup source。该函数无返回值。

wakeup_source_unregister的具体实现代码如下:

在入参有效的情况下,调用内部接口wakeup_source_remove,把wakeup source从全局链表中摘除,调用wakeup_source_destroy释放wakeup source占用的系统资源。

3.pm_stay_awake

该函数的功能是上锁设备对应的wakeup source,阻止系统睡眠。

函数原型如下:

其中入参*dev表示需要持锁的设备。该函数无返回值。

pm_stay_awake的具体实现代码如下:

可以发现,该函数还是通过调用__pm_stay_awake接口来操作dev对应的wakeup source;其实这里也可以直接调用__pm_stay_awake(dev->power.wakeup)来达到相同的目的。

4.__pm_stay_awake

该函数的功能是上锁wakeup source来阻止系统睡眠。

函数原型如下:

其中入参*ws表示需要上锁的wakeup source。该函数无返回值。

__pm_stay_awake的具体实现代码如下:

在wakeup_source_report_event接口中,对组合变量combined_event_count的低16位做加1动作,从而达到阻止睡眠的目的,因为是否睡眠就是通过判断该变量的低16位是否为0来决策的。把对应的timer删掉,因为此wakeup source并不是延迟锁,不需要timer。

5.pm_relax

该函数与pm_stay_awake对应,在对应业务处理完成后,把持有的睡眠锁释放掉。

函数原型如下:

其中入参*dev表示需要释放锁的设备。该函数无返回值。

pm_relax的具体实现代码如下:

通过调用__pm_relax来达到释放睡眠锁的目的,释放后,系统将不会再因为本wakeup source而阻止睡眠。

6.__pm_relax

该函数与__pm_stay_awake对应,在对应业务处理完成后,把持有的睡眠锁释放掉。

函数原型如下:

其中入参*ws表示需要释放锁的wakeup source。该函数无返回值。

__pm_relax的具体实现代码如下:

通过调用wakeup_source_deactivate来达到释放睡眠锁的目的,释放后,系统将不会再因为本wakeup source而阻止睡眠。主要通过对combined_event_count的低16位进行减1来达到释放锁的效果。在释放锁后,如果combined_event_count的低16位为0,则表示当前没有在处理中的wakeup source,该接口会触发wakeup_count_wait_queue等待队列运行,如果工作队列满足睡眠条件,则继续进入睡眠流程,该机制是通过pm_get_wakeup_count接口与autosleep配合使用的。

7.pm_get_wakeup_count

该函数的功能是获取wakeup event值(combined_event_count高16位)与正在处理的wakeup event是否为0(combined_event_count低16位)。

函数原型如下:

其中出参*count表示combined_event_count高16位历史上的唤醒事件的总数,入参block表示是否要等combined_event_count低16位为0才返回。该函数无返回值。

pm_get_wakeup_count的具体实现代码如下:

1)如果入参block为0,则仅仅对入参count赋值当前wakeup event历史处理的总数,并返回当前combined_event_count低16位是否为0。

2)如果入参block为1,则需要一直等到combined_event_count低16位为0或者当前挂起进程有事件需要处理时才退出。退出时的操作与block为0的操作一样,一是对入参count赋值当前处理的唤醒事件的总数,二是返回当前combined_event_count低16位是否为0。该block为1的处理分支的wait等待队列会在__pm_relax函数满足睡眠条件时触发调度运行,即finish_wait。

8.pm_wakeup_pending

该函数的功能是确定当前是否满足睡眠条件,函数原型如下:

该函数返回值为bool类型,true表示可以睡眠,false表示不可以睡眠。

pm_wakeup_pending的具体实现代码如下:

返回值有2个参考点:

❏combined_event_count低16位是否为0,即是否有正在活动状态的wakeup source。

❏pm_abort_suspend值是否大于0,如果大于0,表示睡眠流程中出现了唤醒相关的中断或事件,唤醒事件通过调用pm_system_wakeup接口来给pm_abort_suspend值做加1操作。

wakeup.c中的函数还有不少配套的接口,但是核心接口就是上文列举的这些,只要把核心接口“吃透”,那么就可以掌握这个模块的功能和运行机制,对我们后续构建自己的唤醒机制也会很有帮助。

2.1.6 函数工作时序

wakeup source是如何与其他模块进行工作交互的呢?我们通过图2-2展示说明。

前提条件:

CONFIG_PM_SLEEP特性开关打开,使能wakeup功能。

工作步骤:

1)dev或者其他需要上锁的模块调用wakeup_source_register来注册对应的wakeup source。

2)在处理业务过程中,为了防止系统在业务处理完之前进入睡眠流程,dev需要通过调用pm_stay_awake或者__pm_stay_awake来投票阻止睡眠。

3)当dev处理完自己的业务后,通过调用pm_relax或者__pm_relax来投票允许睡眠。

4)__pm_relax在释放票的过程中,会检查当前是否有正在处理的持票事件,如果没有,则触发wakeup_count_wait_queue。

5)wakeup_count_wait_queue所在的pm_get_wakeup_count接口会返回到autosleep的工作队列中继续走睡眠流程。

图2-2 wakeup source交互时序 IY6yNv85pvEbInAN2sE/+hLVzEhhBymte2j+iXHTHfQHetTfHY6TdARg7rg/C0XD

点击中间区域
呼出菜单
上一章
目录
下一章
×