目录

一、引言

二、电源管理

------> 2.1、电源管理的两种模型

三、系统Suspend

------> 3.1、系统睡眠模型Suspend
------> 3.2、开启suspend
------> 3.3、驱动支持Suspend

四、runtime

------> 4.1、runtime_PM 框架
------> 4.2、使用Runtime功能
------> 4.3、驱动支持Suspend
------> 4.4、实例
------> 4.5、异步(ASYNC)和同步(SYNC)

一、引言

这篇文件简单的来介绍一下linux下的电源管理框架-PM

二、电源管理

1、电源管理的两种模型

以往接触的Linux驱动,没遇到使用电池供电的情况,因此几乎没关注电源的管理。
然而实际中,不少使用电池供电的硬件平台,例如手机、POS机等,就需要对电源进行管理,比如在不使用设备的时候,休眠屏幕省电。

Linux电源管理模型有两种:系统睡眠模型suspend和Runtime电源管理模型。

三、系统Suspend

1、系统睡眠模型Suspend

名称 含义
On (on) S0 - Working
Standby (standby) S1 - CPU and RAM are powered but not executed
Suspend to RAM (mem) S3 - RAM is powered and the running content is saved to RAM
Suspend to Disk, Hibernation (disk) S4 - All content is saved to Disk and power down

其中
S3 aka STR(suspend to ram),挂起到内存,简称待机。计算机将目前的运行状态等数据存放在内存,关闭硬盘、外设等设备,进入等待状态。此时内存仍然需要电力维持其数据,但整机耗电很少。恢复时计算机从内存读出数据,回到挂起前的状态,恢复速度较快。对DDR的耗电情况进行优化是S3性能的关键,大多数手持设备都是用S3待机。

S4 aka STD(suspend to disk),挂起到硬盘,简称休眠。把运行状态等数据存放在硬盘上某个文件或者某个特定的区域,关闭硬盘、外设等设备,进入关机状态。此时计算机完全关闭,不耗电。恢复时计算机从休眠文件/分区中读出数据,回到休眠前的状态,恢复速度较慢。

系统休眠模型给我的感觉是以整机角度进行省电。
S3类似电脑的睡眠,在教长时间不使用电脑后,电脑黑屏,再次敲击键盘迅速显示桌面,原来的工作内容仍不变。
S4类似电脑的休眠,在长时间不使用电脑后,电脑黑屏,再次敲击键盘无反应,按下电源键,开机,原来的工作内容仍不变。

对于嵌入式设备,更多的是使用S3,将数据暂时放在内存里,以实现快速恢复,就像手机的电源键按下黑屏,再次按下迅速亮屏。

在Linux中,通过cat /sys/power/state可以得知当前设备支持的节能模式,一般情况有如下选项:
standby:前面的S1状态,CPU处于浅睡眠模式,主要针对CPU功耗;
mem:前面的S3状态,Suspend to RAM;
disk:前面的S4状态,Suspend to Disk;

需要设置以上模式,只需echo mem > /sys/power/state即可。下面来用我们的虚拟机试验一下

~$ cat /sys/power/state
freeze standby mem diskecho standby > /sys/power/state       //进入待机
echo mem > /sys/power/state      //进入待机
echo disk > /sys/power/state         //进入休眠

睡眠可能有两种方式:mem和standby,这两种方式都是suspend to RAM,简称STR,只是standby耗电更多一些,返回到正常工作方式时间更短一些而已。

2、开启suspend

首先将suspend功能加入内核:

Power management options  --->[*] Suspend to RAM and standby
设置唤醒源

要进入休眠,必须要有唤醒源,没有唤醒源,休眠也没有意义,唤醒源最常见的就是按键中断,就如同手机进入锁屏状态下,按下电源键唤醒一样

3、驱动支持Suspend

电源管理是在冻结APP之后和重启APP之前对驱动的电源进行控制
需要suspend和resume来实现
a.在platform_driver里的driver里添加pm结构体:

static struct platform_driver lcd_driver =
{.driver        = {.name           = "lcd_s702",.pm             = &lcd_pm,.of_match_table = of_match_ptr(lcd_dt_ids),},.probe         = lcd_probe,.remove        = lcd_remove,
};

其中pm支持的操作接口有如下

struct dev_pm_ops {int (*prepare)(struct device *dev);void (*complete)(struct device *dev);int (*suspend)(struct device *dev);int (*resume)(struct device *dev);int (*freeze)(struct device *dev);int (*thaw)(struct device *dev);int (*poweroff)(struct device *dev);int (*restore)(struct device *dev);int (*suspend_late)(struct device *dev);int (*resume_early)(struct device *dev);int (*freeze_late)(struct device *dev);int (*thaw_early)(struct device *dev);int (*poweroff_late)(struct device *dev);int (*restore_early)(struct device *dev);int (*suspend_noirq)(struct device *dev);int (*resume_noirq)(struct device *dev);int (*freeze_noirq)(struct device *dev);int (*thaw_noirq)(struct device *dev);int (*poweroff_noirq)(struct device *dev);int (*restore_noirq)(struct device *dev);int (*runtime_suspend)(struct device *dev);int (*runtime_resume)(struct device *dev);int (*runtime_idle)(struct device *dev);
};

b.设置pm成员函数:

static struct dev_pm_ops lcd_pm = {.suspend = s702_lcd_suspend,.resume  = s702_lcd_resume,
};

c.编写成员函数:
这个根据需求来编写了

随便来看一个内核中驱动的例子

static int gmux_suspend(struct device *dev)
{struct pnp_dev *pnp = to_pnp_dev(dev);struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);gmux_data->resume_client_id = gmux_active_client(gmux_data);gmux_disable_interrupts(gmux_data);return 0;
}
static int gmux_resume(struct device *dev)
{struct pnp_dev *pnp = to_pnp_dev(dev);struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);gmux_enable_interrupts(gmux_data);gmux_switchto(gmux_data->resume_client_id);if (gmux_data->power_state == VGA_SWITCHEROO_OFF)gmux_set_discrete_state(gmux_data, gmux_data->power_state);return 0;
}static const struct dev_pm_ops gmux_dev_pm_ops = {.suspend = gmux_suspend,.resume = gmux_resume,
};static struct pnp_driver gmux_pnp_driver = {.name        = "apple-gmux",.probe        = gmux_probe,.remove       = gmux_remove,.id_table    = gmux_device_ids,.driver      = {.pm = &gmux_dev_pm_ops,},
};

可以看到,在suspend和resume中有开关中断等操作

四、runtime

1、runtime_PM 框架

前面的suspend系统睡眠模型是将整个系统进行休眠,但如果需要在系统运行时,单独对某个模块进行休眠 ,就需要Runtime电源管理模型,这两个模型互相协作,才能最大的发挥电源管理的效果。

Runtime电源管理模型的原理比较简单,就是计数,
当该设备驱动被使用时就加1,放弃使用时就减1,
计数大于1时,就打开该设备的电源,等于0时就关闭电源。调用我们注册的回调函数

Runtime PM相关的函数:
a. 使能/禁止 Runtime PM:pm_runtime_enable / pm_runtime_disable (修改disable_depth变量),这个变量的初始化值是1,默认是disable的状态
b. 增加计数/减少计数:pm_runtime_get_sync / pm_runtime_put_sync (修改usage_count变量),并判断是否进入suspend/resume
c. 回调函数 暂停/恢复/空闲:runtime_suspend / runtime_resume / runtime_idle

上面4个函数不会直接导致runtime_suspend,runtime_resume,runtime_idle被调用,只是使能和修改计数值,当引用计数减为0,调用suspend,从0变为大于0调用resume。

对于runtime PM,默认状态下设备的状态是suspend,如果硬件上它是运行状态,需要调用pm_runtime_set_active()来修改它的状态,然后调用pm_runtime_enable()来使能runtime PM。一般是在probe()的结尾处使用,以为它可能导致runtime的suspend/resume函数立即调用。一般在驱动remove中调用pm_runtime_disable()

在open()/release()接口中可以调用pm_runtime_get_sync/pm_runtime_put_sync

2、使用Runtime功能

调用方式一:
驱动程序提供接口, APP来调用。
在驱动函数的open()、close()里,增加和减少引用计数。
APP调用驱动的时候就能相应的恢复、暂停设备。

调用方式二:
直接操作应用层文件:
恢复:

echo on >  /sys/devices/.../power/control

暂停:

echo auto >  /sys/devices/.../power/control

3、使驱动支持Runtime

a.在platform_driver里的driver里添加pm结构体:(和前面的一样,这里就无需操作)

static struct platform_driver lcd_driver =
{.driver        = {.name           = "lcd_s702",.pm             = &lcd_pm,.of_match_table = of_match_ptr(lcd_dt_ids),},.probe         = lcd_probe,.remove        = lcd_remove,
};

b.设置pm成员函数:

static struct dev_pm_ops lcd_pm =
{.suspend = s702_lcd_suspend,.resume  = s702_lcd_resume,.runtime_suspend = s702_lcd_suspend,.runtime_resume  = s702_lcd_resume,
};

c.编写成员函数:也是根据需求来

d.使能Runtime:
对于Runtime PM,默认状态下设备的状态是Suspended,
如果硬件上它是运行状态,需要调用pm_runtime_set_active()来修改它的状态,
然后调用pm_runtime_enable()来使能Runtime PM。

在probe()函数的后面添加:

    pm_runtime_set_active(&pdev->dev);pm_runtime_enable(&pdev->dev);

反之,还要在remove()里禁止:

pm_runtime_disable(&pdev->dev);

e.修改计数:
一般在open()和release()里面增加和减少引用计数:

4、实例

1、定义

static const struct dev_pm_ops rtl8169_pm_ops = {.suspend       = rtl8169_suspend,.resume          = rtl8169_resume,.freeze           = rtl8169_suspend,.thaw            = rtl8169_resume,.poweroff     = rtl8169_suspend,.restore     = rtl8169_resume,.runtime_suspend  = rtl8169_runtime_suspend,.runtime_resume      = rtl8169_runtime_resume,.runtime_idle     = rtl8169_runtime_idle,
};#define RTL8169_PM_OPS    (&rtl8169_pm_ops)
static struct pci_driver rtl8169_pci_driver = {.name       = MODULENAME,.id_table = rtl8169_pci_tbl,.probe       = rtl_init_one,.remove     = rtl_remove_one,.shutdown = rtl_shutdown,.driver.pm  = RTL8169_PM_OPS,
};

可以看到,这个驱动注册了runtime机制,下面就来看一下这个计数是如何执行的

rtl_init_onertl_openpm_runtime_get_sync(&pdev->dev);pm_runtime_put_noidle(&pdev->dev);     //only put,只减少计数器
rtl8169_closepm_runtime_get_sync(&pdev->dev);        //增加计数......                                    //释放内存的去初始化工作pm_runtime_put_sync(&pdev->dev);
rtl_shutdownpm_runtime_get_sync(d); pm_runtime_put_noidle(d);
rtl_remove_onepm_runtime_get_noresume(&pdev->dev);pm_runtime_put_noidle(&pdev->dev);

5、异步(ASYNC)和同步(SYNC)

设备驱动代码可在进程和中断两种上下文执行,因此put和get等接口,要么是由用户进程调用,要么是由中断处理函数调用。由于这些接口可能会执行device的.runtime_xxx回调函数,而这些接口的执行时间是不确定的,有些可能还会睡眠等待。这对用户进程或者中断处理函数来说,是不能接受的。

因此,RPM core提供的默认接口(pm_runtime_get/pm_runtime_put等),采用异步调用的方式(由ASYNC flag表示),启动一个work queue,在单独的线程中,调用.runtime_xxx回调函数,这可以保证设备驱动之外的其它模块正常运行。

另外,如果设备驱动清楚地知道自己要做什么,也可以使用同步接口(pm_runtime_get_sync/pm_runtime_put_sync等),它们会直接调用.runtime_xxx回调函数,不过,后果自负!

Linux内核学习--电源管理相关推荐

  1. Linux内核学习--内存管理模块

    Linux内核学习--内存管理模块 首先,Linux内核主要由五个部分组成,他们分别是:进程调度模块.内存管理模块.文件系统模块.进程间通信模块和网络接口模块. 本部分所讲的内存是内存管理模块,其主要 ...

  2. Linux内核配置电源管理

    最近测试板子辐射比较高,希望能在运行时降低功耗和辐射,CPU这部分没啥用到,可以降! [*] Power Management support //如果你想让你的Linux支持高级电源管理(也就是平常 ...

  3. Linux内核学习008——进程管理(四)

    Linux内核学习007--进程管理(四) 进程家族树 Unix系统的进程之间存在一个明显的继承关系,所有的进程都是PID为1的init进程的后代.内核在系统启动的最后阶段启动init进程,然后ini ...

  4. 关闭linux服务器电源,linux关闭ACPI电源管理模块

    一.运行环境 # cat /etc/redhat-release CentOS release 6.2 (Final) # uname -a Linux web-server- 2.6.-.el6.x ...

  5. linux内核之内存管理.doc,linux内核之内存管理.doc

    Linux内核之内存管理 作者:harvey wang 邮箱:harvey.perfect@ 新浪博客地址:/harveyperfect ,有关于减肥和学习英语相关的博文,欢迎交流 把linux内存管 ...

  6. 我的Linux内核学习笔记

    在开始今天的内容之前,其实有一些题外话可以和大家分享一下.自从工作以来,我个人一直都有一个观点.那就是怎么样利用简单的代码来说明开发中的问题,或者是解释软件中的原理,这是一个很高的学问.有些道理看上去 ...

  7. Linux内核学习编译流程

    一.前言 linux内核学习 1.安装vmware虚拟机或者virtualbox,再安装发行版本linux 2.www.kernel.org,挑选一个内核版本 3.进行解压并编译 4.自己写一些模块( ...

  8. Linux内核学习路线

    [推荐阅读] 手把手教你如何编写一个Makefile文件 一文讲解,Linux内核--内存管理(建议收藏) 当Linux内存耗尽时,改如何处理! 一文看懂页面置换算法 内核学习路线 很多同学接触Lin ...

  9. Android内核驱动——电源管理

    Android内核驱动--电源管理 13.1 基本原理 Android 中定义了几种低功耗状态:earlysuspend,suspend,hibernation.  earlysuspend是一种低 ...

最新文章

  1. 保存数组_面试官:讲一讲你对据结构——数组、链表、栈、队列的理解
  2. 手把手实现火爆全网的视频特效 “蚂蚁呀嘿”,太魔性了
  3. JAV A获取项目路径
  4. 每天一个linux命令(19):find 命令概览
  5. html公共模块提取出去,webpack 填坑之路--提取独立文件(模块)
  6. 新版刷卡_有信用卡的注意了,新版征信即将上线,以后刷卡消费要当心了!
  7. redhat7.1安装mysql_redhat7.1 安装mysql 5.7.10步骤详解(图文详解)
  8. Sahi ---实现 Web 自动化测试
  9. codeforces731E Funny Game(DP)
  10. 724. 寻找数组的中心索引
  11. [CI、CD入门]maven打包可执行程序之微服务-服务提供者篇
  12. 微信小程序轮播图,图片自适应,图片循环播放,图片之间有空白空间
  13. 信息安全必备的8张思维导图
  14. CATIA二次开发—参数那点事
  15. 形如e^(ax^2+bx+c)的积分公式的证明
  16. 【计导非课系列】 第五节 二进制 进制计算 编码
  17. 值得一看的网络课程推荐(不限于计算机科学)
  18. jxls2-java生成/导出excel工具!基于jxls2写的jxls增强版jxlss的完整教程
  19. 深入理解debuginfo
  20. 软件需求工程 高校教学平台 用户手册

热门文章

  1. Android 软键盘丝滑切换(一)
  2. linux中libc如何升级,linux系统更新libc ,libstdc++标准库
  3. 你不懂我,我不怪你 余秋雨
  4. Redis概述与安装
  5. 她和世界首富离婚能分4700亿家产,但每一分都是她应得的
  6. Java 获取Enum枚举中的值,以列表方式返回
  7. Python:简单的TCP网络编程
  8. 全国职业院校技能大赛 网络建设与运维 赛题(四)
  9. Arduino控制步进电机和舵机机器臂
  10. eclipse 源码中文乱码