如何搭建程序复现笔记本芯片在电源状态切换中的死锁问题
最近碰到一个问题,是关于芯片测试过程的。这颗芯片被用在笔记本的端口上。笔记本的客户那边会进行一个压力测试,测试内容是频繁地进行电脑电源状态的切换,包括 S0(正常使用的开机状态)、S3(睡眠模式)、S4(休眠模式)以及 S5(关机模式)。主要是在压力测试过程中,客户发现了芯片会出现不正常的死锁。客户把机台寄了回来,接下来要如何复现呢?客户那边有自己的一套压力测试系统,但是会测试很多东西,不太便于给我们,并且每一次循环耗时比较长。那么,是否可以自己搭建一套能够控制电脑睡眠、休眠、关机以及唤醒的程序呢?
上面讲述的是一个应用背景,告知大家实际上是存在需求的,只是在平时不太会使用,所以将其记录了下来。
首先,将电脑从开机状态 S0 切换至 S3 是比较容易实现的。其次,把电脑从开机状态 S0 切换到 S4 也是比较容易实现的。再者,把电脑从开机状态 S0 切换到 S5 同样是比较容易实现的,见下面代码:
<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'> <pre class="syl-page-code"><code>Application 设置挂起状态为电源状态挂起(PowerState.Suspend),设置为假(false),设置为假(false);这是从 S0 进入 S3 的操作。
Application 设置挂起状态,状态为电源状态休眠,第二个参数为 false,第三个参数为 false;此操作是从 S0 进入 S4。
Process.Start 函数用于启动一个新进程。其中,第一个参数 "shutdown" 表示要执行的操作是关机。第二个参数 "/s /t 0" 中,"/s" 的意思是要关闭计算机,"/t 0" 表示设置关机的延迟时间为 0 秒,即立即关机。
参数 /t 0 的含义是向计算机传达在 0 秒过后执行命令这一信息。
Process.Start("shutdown", "/r /t 0");此操作中 /r 参数的含义为要重新启动计算机。</code></pre></p>
如果调用上述语句就能达成从 S0 到其他电源状态的转变,那么反过来要如何唤醒呢?
唤醒存在难点。处于 S3、S4 以及 S5 的状态时,我的上位机程序不会运行。所以,上位机软件的定时唤醒无法工作。那么笔记本客户那边是如何操作的呢?他们通过底层的 EC 控制来显示上述功能。然而,我们不知道底层 EC 的接口,并且我们需要一个通用的程式,那该怎么实现呢?
在笔记本的设计里,S3、S4、S5 通常不是所有东西都会关闭。通常会有一个硬件定时器处于开启状态。如果我们能够操作这个定时器,那么是否就可以实现我们所想要的功能呢?
可以调用以下两个函数,即某个函数和另一个函数,这两个函数能够控制电脑中所开启的硬件定时器,至于这个硬件定时器究竟是位于 CPU 内部还是 EC 内部,我并不知晓,也未曾进行过研究,如果有精通此领域的大神进行过研究,可以留言,我也可以借此学习学习。
<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'> <pre class="syl-page-code"><code>
公共静态外部方法 SafeWaitHandle 创建可等待定时器(IntPtr lpTimerAttributes,布尔值 bManualReset,字符串 lpTimerName);
在“kernel32.dll”中进行 DllImport 操作,并且设置了 SetLastError 为 true 。
公共静态外部方法 SetWaitableTimer 接收一个 SafeWaitHandle 类型的参数 hTimer,一个引用类型的 long 类型参数 pDueTime,一个 int 类型的参数 lPeriod,一个 IntPtr 类型的参数 pfnCompletionRoutine,一个 IntPtr 类型的参数 lpArgToCompletionRoutine,以及一个 bool 类型的参数 fResume,并返回一个 bool 值。该方法用于设置可等待定时器。</code></pre></p>
另外,有一点需要说明,使用这个定时器是有条件的。首先,你得先设置笔记本,即“Panel>Power>Plan>Power>Sleep>Allow Wake”,以实现定时器唤醒功能。其次,“Panel>Power>Plan>Power>Brad/>a on”,这一步是关闭唤醒需要密码。
完成上述设置后,电脑能够从 S3、S4、S5 唤醒。然而,在我使用的过程中,遇到了一个问题,即唤醒之后屏幕不亮,会让人误以为没有唤醒。所以,我增加了控制鼠标移动的命令,这样一来,唤醒之后屏幕就会亮起。
<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'> <pre class="syl-page-code"><code>
公共静态外部方法 mouse_event,该方法接收一个 32 位整数 dwFlags,一个 32 位整数 dx,一个 32 位整数 dy,一个 32 位整数 dwData,以及一个无符号指针 dwExtraInfo 作为参数。
调用 mouse_event 函数,传入参数 0x0001、0、1、0 和 UIntPtr.Zero,用于模拟鼠标操作。
调用 mouse_event 函数,其参数为 0x0001、0、-1、0 和 UIntPtr.Zero 。 该函数用于模拟鼠标操作,这里的第一个参数 0x0001 可能代表某种特定的鼠标动作。 第二个参数 0 表示鼠标的水平移动量。 第三个参数 -1 表示鼠标的垂直移动量。 第四个参数 0 可能与鼠标的其他相关设置有关。 最后一个参数 UIntPtr.Zero 也可能在鼠标事件的处理中起到特定的作用。</code></pre></p>
另外需注意,在笔记本从 S0 到 S3/S4/S5 再到 S0 的循环中,S0 以及 S3/S4/S5 这几种状态的停留时间需足够长。因为每台笔记本完全进入各状态的时间不同,例如我自己的笔记本,这些状态的停留时间至少要 20 秒。否则,笔记本尚未完全进入就退出,会致使电脑关机,而此时笔记本还未被唤醒,从而导致程式死锁。而新的刚买的笔记本,只需要设置10s即可完全进入。
废话不多说,直接上代码:
<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'> <pre class="syl-page-code"><code>using System;
系统使用了一系列的集合类型,这些集合类型包含了多个元素,它们共同构成了特定的数据结构,用于存储和管理相关的数据。
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
使用 Microsoft.Win32.SafeHandles 。
System.Runtime.InteropServices 被使用。
namespace AutoSwitchGUI
{
通过这个类可以创建出相应的图形用户界面。
{
公共静态外部方法 SafeWaitHandle 创建可等待定时器(IntPtr lpTimerAttributes,布尔值 bManualReset,字符串 lpTimerName);
在“kernel32.dll”中进行 DllImport 操作,并且设置了 SetLastError 为 true 。
使用 MarshalAs 特性并将其参数设置为 UnmanagedType.Bool 进行返回
公共静态外部方法 SetWaitableTimer 接受一个 SafeWaitHandle 类型的参数 hTimer,以及一个引用类型的 long 类型参数 pDueTime,一个 int 类型的参数 lPeriod,一个 IntPtr 类型的参数 pfnCompletionRoutine,一个 IntPtr 类型的参数 lpArgToCompletionRoutine 和一个 bool 类型的参数 fResume。该方法用于设置等待定时器。
公共静态外部函数 SetThreadExecutionState 接收一个 uint 类型的参数 esFlags 并返回一个 uint 值。
公共静态外部方法 mouse_event,它接受一个 32 位整数 dwFlags,一个 32 位整数 dx,一个 32 位整数 dy,一个 32 位整数 dwData,以及一个无符号指针 dwExtraInfo 作为参数。
公共事件 EventHandler 被引发唤醒;
创建了一个私有类型的 BackgroundWorker 对象,名为 bgWorker 。
https://img2.baidu.com/it/u=3820540396,1166282762&fm=253&fmt=JPEG&app=120&f=JPEG?w=818&h=500
公共结构体 auto_switch_gui_status_t
{
public bool test_status;
public UInt64 test_times_cnt;
public UInt64 test_times;
public byte cur_state;
public int s0_duration;
public int s3_duration;
}
公共的自动切换图形用户界面状态类型 auto_switch_gui_status_t 为 auto_switch_status ;
public AutoSwitchGUI()
{
InitializeComponent();
bgWorker 的 DoWork 事件添加了一个事件处理程序,该处理程序为 bgWorker_Dowork 方法。
bgWorker 的 RunWorkerCompleted 事件被添加了一个新的事件处理程序,该处理程序为 bgWorker_RunWorkerCompleted 。
}
在 bgWorker 的 DoWork 事件中,当接收到 DoWorkEventArgs e 时,会执行以下操作:在私有方法内部进行相关处理。
{
long waketime 等于 (long)e.Argument ;
使用 (SafeWaitHandlehandle = CreateWaitableTimer(IntPtr.Zero, true, 这个GetType().Assembly.GetName().Name.ToString() + "Timer"))
{
如果设置可等待定时器(handle),将唤醒时间(ref waketime)设置为 0,并且设置为非零值(IntPtr.Zero),设置为非零值(IntPtr.Zero),设置为真(true),那么就会成功设置可等待定时器。
{
使用一个 EventWaitHandle 对象 wh,将其初始化为 false 状态,并且设置为自动重置模式,即 EventResetMode.AutoReset
{
wh.SafeWaitHandle = handle;
wh.WaitOne();
}
}
else
{
使用 Marshal.GetLastWin32Error() 获取的最后一个 Windows 32 错误码,并抛出一个新的 Win32Exception 异常。
}
}
}
当 private 方法被触发时,它会接收到两个参数,分别是 sender 和 RunWorkerCompletedEventArgs e,这个方法用于处理后台工作线程完成后的相关操作。
{
调用 mouse_event 函数,其参数为 0x0001,0,1,0,UIntPtr.Zero 。
调用 mouse_event 函数,参数为 0x0001、0、-1、0 和 UIntPtr.Zero 。
auto_switch_status 的 test_times_cnt 加 1 ;
TestTimes.Text 等于 auto_switch_status.test_times_cnt 转换为字符串后的结果。
SystemTimer 的 Interval 等于 auto_switch_status 的 s0_duration 乘以 1000 。
SystemTimer.Start();
}
公共方法用于设置唤醒时间,该时间以 UInt64 类型的数值表示。
{
bgWorker 运行工作异步操作,操作的参数是当前时间(System.DateTime.Now)加上指定的秒数(time)后转换为文件时间(ToFileTime())。
}
在点击开始按钮时,会触发这个私有方法。这个方法接收两个参数,一个是发送者对象 sender,另一个是事件参数 EventArgs e。
{
try
{
auto_switch_status 的 test_times 等于将 SetTestTimes.Text 转换为 UInt64 类型
auto_switch_status 的 s0_duration 等于将 S0Duration.Text 转换为整数类型
auto_switch_status 的 s3_duration 等于将 S3Duration.Text 转换为整数类型。
如果 auto_switch_status 的 test_times 大于 0
{
https://img2.baidu.com/it/u=4065599885,147254820&fm=253&fmt=JPEG&app=138&f=JPEG?w=500&h=667
SetThreadExecutionState 会设置一些特定的状态,包括 0x00000001 这个状态,也包括 0x00000002 这个状态,还包括 0x80000000 这个状态,同时也包括 0x00000040 这个状态。
TestStatus 的背景颜色被设置为绿色。
auto_switch_status 的 test_status 等于 true。
TestTimes.Text = "0";
auto_switch_status 的 test_times_cnt 等于 0 。
SystemTimer 的 Interval 等于 auto_switch_status 的 s0_duration 乘以 1000 。
auto_switch_status 的当前状态为 0。
SystemTimer.Start();
return;
}
}
catch
{
}
显示一个消息框,内容为“Configuration Failed!”。
}
在 StopButton 被点击时(对象为 sender,事件为 e),会执行这个私有方法。
{
SystemTimer.Stop();
auto_switch_status 的 test_status 等于 true。
TestStatus 的背景颜色被设置为红色。
}
在 SystemTimer 的 Tick 事件中,当触发该事件时,会调用这个私有方法,参数分别是 sender 和 e 。
{
如果 auto_switch_status 的当前状态为 0
{
auto_switch_status 的当前状态为 0。
SystemTimer.Stop();
如果 auto_switch_status 的 test_times_cnt 大于或等于 auto_switch_status 的 test_times
{
}
else
{
设置唤醒时间为 (UInt64)auto_switch_status.s3_duration ;
Application 使系统进入挂起状态,挂起状态为 PowerState.Suspend,并且不强制关闭程序,也不保存程序状态。
Application 使系统进入休眠状态(PowerState.Hibernate),并且设置为不强制关闭应用程序,也不关闭系统电源。
}
}
如果 auto_switch_status 的当前状态等于 1
{
auto_switch_status 的 test_times_cnt 加 1 ;
TestTimes.Text 等于 auto_switch_status.test_times_cnt 转换后的字符串形式。
auto_switch_status.cur_state = 0;
SendKeys.Send(" ");
将 "TEST1\r\n" 添加到了 MessageInfo 的 Text 中
}
}
}
}
</code></pre></p>
另外声明,关于和我是参考如下链接的:
希望能帮到大家,此代码在我自己的笔记本上可以适用,在客户的笔记本上也可以适用。
页:
[1]