在使用sleepgraph分析并优化suspend/resume flow一节https://blog.youkuaiyun.com/dachai/article/details/103785380,我们看到,把scsi_scan_type从”sync”改成”async”之后,resume latency大大降低,能够有效提升suspend/resume效率。
其实,除了这个,还有一个global的pm_async参数,决定suspend/resume过程中,device的suspend/resume是同步还是异步操作。
话不多说,我们先用sleepgraph看一下pm_async分别为1/0时,suspend/resume的情况如何。
pm_async=1时,sleepgraph获取STR suspend/resume流程如下:
pm_async=0时,sleepgraph获取STR suspend/resume流程如下:
通过对以上两张图片对比分析,可以得出以下结论:
1)pm_async=1时,支持async_suspend的device执行异步操作,每个阶段的时长由耗时最长的那个device来决定;
在suspend/suspend_late/suspend_noirq以及resume_noirq/resume_early/resume阶段,不同device的操作是可以同时进行的。
2)pm_async=0时,所有device的操作都需要串行执行,每个阶段的时长都是所有device操作时长的总和;
3)使用async PM的话,对kernel resume的改善较为明显,kernel resume时长从680ms降低到319ms。
4)在kernel suspend过程中,设置pm_async与否,影响不太大。这是因为scsi disk suspend过程耗时过长,其他device的suspend时长跟它都不在一个数量级上,即使设置了async suspend,suspend 阶段的耗时最终还是由最大的sd suspend时长来决定。
那么pm_async到底是如何影响suspend/resume流程的呢?而且,众所周知,Linux device model中,很多device可能存在parent-child或provider-consumer的关系,在suspend时,需要保证所有的children/consumer device都完成suspend操作之后,才去执行parent/provider device的suspend操作;而在resume的时候,又需要先resume parent/provider device的function,之后才能resume children/consumer device,而如果是async异步操作的话,是如何保证这一点的呢?我们通过source code去了解一下。
通过/sys/power/pm_async来切换sync和async
1、异步执行device的suspend/resume
echo 1 > /sys/power/pm_async 使用async异步的方式执行suspend/resume流程
我们以dpm_suspend和dpm_resume为例进行说明
echo 1 > /sys/power/pm_async
pm_async_store
{
pm_async_enabled = 1;
}
dpm_suspend(state)
{
……
while(!list_empty(&dpm_prepared_list))
{
//1.遍历dpm_prepare_list中的每个device,执行device_suspend()函数
struct device *dev = to_device(dpm_prepared_list.prev);
error = device_suspend(dev);
{
/*
* 1.1 如果使能了async,把async_suspend加入到workqueue中异步执行之后,
* 就可以返回了
*/
ret = dpm_async_fn(dev, async_suspend);
{
……
//1.1.1 把async_suspend function加入到async schedule异步执行
async_schedule(async_suspend, dev);
{
async_schedule_node(func, data, NUMA_NO_NODE);
{
async_schedule_node_domain(func, data, node, &async_dfl_domain);
{
//1) 创建一个async_entry
struct async_entry *entry;
entry = kzalloc(sizeof(struct async_entry), GFP_ATOMIC);
//2) 初始化async_entry并加入到domain中;
INIT_LIST_HEAD(&entry->domain_list);
INIT_LIST_HEAD(&entry->global_list);
INIT_WORK(&entry->work, async_run_entry_fn);
entry->func = func;
entry->data = data;
entry->domain = domain;
list_add_tail(&entry->domain_list, &domain->pending);
if(domain->registered)
list_add_tail(&entry->global_list,&async_global_pending);
//3) 将entry->work加入到workqueue中等待调度执
atomic_inc(&entry_count);
current->flags |= PF_USED_ASYNC;
queue_work_node(node, system_unbound_wq, &entry->work);
}
}
}
}
if(ret)
return 0;
/*
* 1.2 如果不支持async操作,或async操作失败,
* 就需要同步执行该device的suspend操作之后,才返回
*/
return __device_suspend(dev, pm_transition, false);
}
}
//2. 等待所有device异步的suspend操作完成
//这也是我们在sleepgraph图形上看到的suspend phase的时长由耗时最长的device决定
async_synchronize_full();
{
async_synchronize_full_domain(NULL);
{
async_synchronize_cookie_domain(ASYNC_COOKIE_MAX, domain);
{
……
//等待async_done信号,并满足lowest_in_progress(domain)超出cookie
wait_event(async_done, lowest_in_progress(domain) >= cookie);
}
}
}
}
async work被调度时,执行async_run_entry_fn函数
async_run_entry_fn (struct work *work)
{
struct async_entry *entry = container_of(work, struct async_entry, work);
//1.执行async_entry对应的function;
entry->func(entry->data, entry->cookie);
即async_suspend()
{
__device_suspend(dev, pm_transition, true)
{
/ *
* 等待children或consumer device的suspend操作完成;
* 这部分async操作特有的,虽然所有device 的suspend操作都是依次启动的,
* 但是其完成时间无法保证,这里需要先check一下
*/
dpm_wait_for_subordinate(dev, async);
{
//等待该device的所有children device的suspend操作完
dpm_wait_for_children(dev,async);
{
device_for_each_child(dev, &async, dpm_wait_fn);
{
……
//对该device的每个child device,执行dpm_wait_fn
klist_iter_init(&parent->p->klist_children, &i);
while(!error && (child==next_device(&i)))
{
error = dpm_wait_fn(child, data);
{
dpm_wait(child, data);
{
wait_for_completion(&dev->power.completion);
}
}
}
klist_iter_exit(&i);
}
}
//等待该device的所有从summer device的suspend操作完成
dpm_wait_for_consumers(dev, async);
{
list_for_each_entry_rcu(link, &dev->links.consumers, s_node)
if (READ_ONCE(link->status) != DL_STATE_DORMANT)
dpm_wait(link->consumer, async);
}
}
//2.执行该device的suspend callback;
//依次从pm_domain/type/class/bus/driver->pm field中获取suspend callback
dpm_run_callback(callback, dev, state, info);
//3.通知该device的suspend已完成
complete_all(&dev->power.completion);
}
}
//2.suspend操作完成后,将该entry从pending list中删除
list_del_init(&entry->domain_list);
list_del_init(&entry->global_list);
//3.销毁entry对象
free(entry);
atomic_dec(&entry_count);
//4.唤醒等待该device suspend完成的waiter
wakeup(&async_done);
}
2、同步执行device的suspend/resume
echo 0 > /sys/power/pm_async 使用sync同步的方式执行suspend/resume流程
跟async异步方式相比,同步是需要等到上一个device的suspend/resume流程结束返回后,才能去执行下一个device的suspend/resume操作。因此,如上图sync的sleepgraph结果所示,suspend phase时长是所有device suspend时长之和。
dpm_suspend(state)
{
……
while(!list_empty(&dpm_prepared_list))
{
//1.遍历dpm_prepare_list中的每个device,执行device_suspend()函数
struct device *dev = to_device(dpm_prepared_list.prev);
error = device_suspend(dev);
{
/*
* 如果不支持async操作,就需要同步执行该device的suspend操作之后,才返回
*/
return __device_suspend(dev, pm_transition, false);
/*
* __device_suspend()跟异步async_entry worker handler相同,
* 都是先完成device的suspend操作,再complete_all(&dev->power.completion);
*/
}
}
//2. 等待所有device的suspend操作完成,
//这里只是为了跟async保持一致,实际上device_suspend()会等待每个device的suspend操作完成
async_synchronize_full();
{
async_synchronize_full_domain(NULL);
{
async_synchronize_cookie_domain(ASYNC_COOKIE_MAX, domain);
{
……
//等待async_done信号,并满足lowest_in_progress(domain)超出cookie
wait_event(async_done, lowest_in_progress(domain) >= cookie);
}
}
}
}
简单总结一下,支持pm_async的话,
1)在suspend/suspend_noirq/suspend_late、resume_early/resume_noirq/resume阶段,可以异步进行device的suspend/resume的操作;
2)一个阶段结束之前,需要等待所有async的device操作完成(每个phase需要同步一下)。