linux内核(1)- S3功能

2021/5/18 7:27:42

本文主要是介绍linux内核(1)- S3功能,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

(1)s3实现原理

STR(suspend to ram),待机到内存,cpu和外设电源关闭。只有内存处于自刷新的状态。

(2)s3实现流程

1)睡眠流程

  1. 应用程序通知系统进入待机状态
  2. 系统保留当前状态,保存当前进程状态、保存外设寄存器的值到内存。
  3. cpu 进程睡眠,留一个核将外设断电。
  4. cpu断电,进入s3状态
  1. 唤醒流程
  1. 不执行正常dxe阶段,仅执行sec和pei阶段,这两个阶段在cache中执行(避免将内存中的内核数据破坏)
  2. 执行boot script table中外设的配置脚本来初始化必要的外设,这部分在NVS内存(???)块中执行。这部分内存是bios传参的时候预留。执行之后跳转到内核。

(3)跳转流程

loongson_suspend_lowlevel这个叶子函数就是内核在执行s3状态的最后一个函数。

主要作用:1、将cache中的数据和当前通用寄存器的值保存到到内存中,保存当前sp指针到a1寄存器中,唤醒执行函数地址(wakeup_start)保存在a0寄存器。2、然后将loongson_suspend_addr(0xffffffffbfc00500)这个地址加载到v0寄存器中,然后跳转到这个地址。loongson_suspend_addr通过在UEFI给内核传参的时候,传递给内核。3、跳转到UEFI之后,UEFI首先要做的就是将a0和a1寄存器的值保存到指定的内存地址上,这个内存地址必须保证是UEFI不使用的,在UEFI跳转到内核的时候还需要使用这个地址。

唤醒后UEFI代码执行完,跳回内核的代码的地址就是函数wakeup_start对应的函数地址。wakeup_star叶子函数跳转回来之后第一个执行的内核代码,主要就是恢复函数栈和必要的CPU寄存器的值。这样控制权又返回给了内核。

.align指令实现对二进制的指定位置编译。

(4)内核和uefi异常向量覆盖问题

内核和UEFI的异常向量不同,但是存储的内存是同一块区域(物理地址0x00000180)。当系统睡眠时,从内核上下文跳转到uefi中,需要将内核的异常向量代码保存。当系统唤醒时,跳转到内核之前,需要恢复内核异常向量处理代码(使用cache的地址拷贝这部分代码,这样cache中就有了内核异常向量的拷贝,这样可以使内核的现象和睡眠之前的一致)。在执行恢复内核异常向量代码时,需将UEFI的中断关闭,防止拷贝过程中出现异常处理跳转,跳转到异常处理的地址执行,而代码拷贝还不完整,造成未知现象。

(5)多核启动

龙芯cpu在启动过程中。主核执行uefi代码,从核没有执行代码,它们在cache的地址上轮询mailbos寄存器的状态,等待系统进入内核状态来唤醒它们。唤醒过程需要注意设置从核状态,是它们取轮询mailbox寄存器。当内核启动后唤醒从核来执行内核代码。

(6)内核中待机实现流程

1)state_store函数的注册流程

当执行echo mem > /sys/power/state命令后。内核首先执行state_store函数(位于kernel/power/main.c)。

state_store函数的注册流程:pm_init -> kobject_create_and_add -> kobject_create -> kobject_init(kobj. &dyncamic_kobj_type)。这里dyncamic_kobj_type是个结构,state_store就包含在这个结构中。obj_attr_store函数中调用的kattr->store(kobj, kattr, buf, count)就是执行的state_store函数。

arch_initcall(loongson_pm_init)在编译的时候就已经将这段代码编译到初始化的函数中,在start_kernel执行的时候就会调用loongson_pm_init这个函数,这个函数就是我们平台对应的函数。会将loongson_pm_ops这个结构注册到内核,然后内核调用平台的代码函数的时候,就会调用这里面的函数。

2)待机流程

state_store做一些必要的操作:上锁,参数解析。调用pm_suspend。pm_suspend函数进行一些必要的参数解析,然后调用enter_state函数。如果没有错误,更新一下suspend_stats.success的值,如果出错,会进行错误的处理和更新suspend_stats.fail的值。

enter_state函数调用了suspend_prepare和suspend_devices_and_enter函数,suspend_prepare主要是待机之前的准备工作,冻结用户进程,内核线程,workqueue等操作,然后调用suspend_devices_and_enter去执行设备的待机操作,然后进入真正的待机状态。

suspend_devices_and_enter函数首先会调用suspend_console()这个函数将控制台冻结,从此之后用printk函数打印的log将不会再有输出。然后调用dpm_suspend_start这个函数,这是去执行设备的suspend的函数,设备的待机操作相关的函数都是这个函数实现的。

dpm_suspend_start函数主要调用了dpm_prepare和dpm_suspend函数,dpm_prepare函数就是执行的设备驱动中注册的suspend_prepare,函数调用的是device_prepare,主要的功能就是获取设备的待机前prepare函数的指针,也就是代码中的callback函数指针的值。dev对应每个设备驱动,在设备驱动初始化的时候,已经将这个结构的成员注册,这里面只需要找到结构中的prepare函数,来执行即可。

dpm_suspend函数最重要的就是获取到设备驱动注册的suspend函数,找到后callback函数的指针赋值,然后调用dpm_run_callback函数去执行真正的suspend函数,这样一次遍历链表,将每个设备都suspend之后,然后到函数dpm_suspend_start中。最后执行 suspend_enter去执行,函数从这里就调用到了平台注册的函数,

suspend_enter函数调用loongson_suspend_lowlevel函数跳转到uefi过程中执行后续设备和cpu的断电工作。

 

(7)唤醒工作中关于跳转到内核的内嵌汇编解析:

__asm__ __volatile__(

            ".set   noat            \n" /*  set noat防止汇编器将汇编代码翻译成at/$1寄存器的指令序列

.set nomacro防止汇编器将单个汇编语句翻译成多个指令

.set noreorder 防止汇编器为了在分支延迟槽中填充有用代码而打    乱代码执行顺序 */

            ".set   mips64          \n"

            "move   $t0, %0         \n"

            "move   $t1, %1         \n"

            "dli    $t2, 0x00000000ffffffff \n"

            "and    $t1,$t2         \n"

            "dsll   $t0,32          \n"

            "or $sp, $t0,$t1        \n"

            "jr %2                \n"

            "nop                \n"

            ".set   at          \n"

/*if there is*/

/* : "=r" (result) */

            : /* No outputs */

            :"r"(sp_h), "r"(sp_l),"r"(str_ra)

            );

__asm__ __volatile__("……"); "__asm__"表示后面的代码为内嵌汇编,"__volatile__"表示编译器不要优化代码,后面的指令保留原样,括号里面是汇编指令。

 

内嵌汇编语法如下:
__asm__(汇编语句模板: 输出部分: 输入部分: 破坏描述部分)

".set noat\n……"是指令模板;"%0"、"%1和%2"代表指令的操作数,称为占位符,内嵌汇编靠它们将C 语言表达式与指令操作数相对应。指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:"sp_h、sp_l和str_ra",他们按照出现的顺序分别与指令操作数"%0"、"%1和%2"对应;注意对应顺序:第一个C 表达式对应"%0";第二个表达式对应"%1",依次类推,操作数至多有10 个,分别用"%0","%1"...."%9"表示。

在每个操作数前面有一个用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。 "result"前面的限制字符串是"=r",其中"="表示"result"是输出操作数,"r" 表示需要将"result"与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是"result"本身,当然指令执行 完后需要将寄存器中的值存入变量"result",从表面上看好像是指令直接对"result"进行操作,实际上GCC做了隐式处理,这样我们可以少写一些指令。 "sp_h"、sp_l和str_ra前面的"r"表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。
   C表达式或者变量与寄存器的关系由GCC自动处理,我们只需使用限制字符串指导GCC如何处理即可。限制字符(指示编译器如何处理其后的C语言变量与指令操作数之间的关系)必须与指令对操作数的要求相匹配,否则产生的汇编代码将会有错。



这篇关于linux内核(1)- S3功能的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程