📄 linux 关机重启流程分析.doc
字号:
<tr style='mso-yfti-irow:0;mso-yfti-lastrow:yes;height:285.0pt'>
<td valign=top style='padding:0cm 0cm 0cm 0cm;height:285.0pt'>
<p class=MsoNormal style='margin-bottom:12.0pt'><span style='color:#594802'>作者<span
lang=EN-US> : 范晓炬 (xiaoju_f@263.net ) <br>
联想(北京)有限公司软件设计中心嵌入式研发处开发工程师 <br>
<br>
Linux 下的关机和重启流程对于一般的桌面应用和网络服务器来说并不重要,但是在用户自己定义的嵌入式系统内核中就有一定的研究意义,通过了解 Linux
关机重启的流程,我们对它可以修改和自定义,甚至以此为基础开发出全新的功能来。 <br>
<br>
1. 概述 <br>
<br>
在 Linux 下的关机和重<span class=GramE>启可能</span>由两种行为引发,一是通过用户编程,一是系统自己产生的消息。用户和系统进行交互的方式也有两个,一个是系统调用:<span
class=SpellE>sys_reboot</span>,另一个就是 <span class=SpellE>apm</span> 或则 <span
class=SpellE>acpi</span> 的设备文件,通过对其操作也可以使系统关机或者重启。 <br>
<br>
2. 通过系统调用 <span class=SpellE>sys_reboot</span> 的重启 <br>
<br>
这个系统调用定义了一系列的 MAGIC_NUMBER,在调用的开始部分首先检查 MAGIC_NUMBER 是否正确,只有正确才继续向下运行。在重启的时候转向分支
<br>
<br>
case Linux _REBOOT_CMD_RESTART: <br>
<br>
首先使用 <span class=SpellE>notifier_call_chain</span> 向其它部分发出重启的消息,然后调用 <span
class=SpellE>machine_restart</span> 函数完成重启。 <br>
<br>
<span class=SpellE>machine_restart</span> 函数的开始部分有一段SMP相关的代码,主要完成多 CPU 时由一个
CPU 完成重启操作,其它 CPU 处于等待状态。之后系统根据一个变量 <span class=SpellE>reboot_thru_bios</span>
的内容判断重<span class=GramE>启方式</span>,通过阅读 <span class=SpellE>reboot_setup</span>
我们可以得知,这个参数的内容是在系统启动时指定的,决定了是否利用 bios,事实上是系统复位后的入口 (FFFF:0000) 地址的程序进行重启。在不通过
bios 进行重启的情况下,系统首先设定了重<span class=GramE>启标志</span>,然后向端口 0xfe 写入数字 0x64,这种重启的具体原理我还不大清楚,似乎是模拟了一次
reset 键的按下,希望大家和我讨论。在通过 bios 重启的情况下,系统同样先设定了重<span class=GramE>启模式</span>,然后切换到了实模式,通过一条
<span class=SpellE>ljmp</span> $0xffff,$0x0 完成了重启。 <br>
<br>
3. 通过系统调用 <span class=SpellE>sys_reboot</span> 进行关机 <br>
<br>
在系统调用的处理分支上,我们可以看到,首先同样是检查 MAGIC_NUMBER,然后在 <br>
<br>
case Linux _REBOOT_CMD_POWER_OFF: <br>
<br>
的执行流程里面,又是使用 <span class=SpellE>notifier_call_chain</span> 发出了关闭计算机电源的消息,紧接着执行了
<span class=SpellE>machine_power_off</span> 函数。我们在 <span class=SpellE>machine_power_off</span>
函数中可以看到,如果 <span class=SpellE>pm_power_off</span> 这个函数指针不为空,那么系统就会通过调用这个函数进行关机。在
<span class=SpellE>apm</span> 已经加载的情况下 (SMP 除外),实际上 <span class=SpellE>pm_power_off</span>
函数实际上指向了 <span class=SpellE>apm.c</span> 中的 <span class=SpellE>apm_power_off</span>,在这个函数里系统通过
<span class=SpellE>apm_info</span> 结构里的值,使用切换到实模式关机,或者使用 <span
class=SpellE>apm_bios_call_simple</span> 函数调用保护模式下的 <span class=SpellE>apm</span>
接口关机两种方法。 <br>
<br>
4. <span class=SpellE>apm</span> 驱动本身的关机过程 <br>
<br>
<span class=SpellE>apm</span> 使用其注册的设备的 <span class=SpellE>ioctl</span> 接口完成
<span class=SpellE>apm</span> 的操作,在 <span class=SpellE>apm.c</span>的<span
class=SpellE>do_ioctl</span> 函数中可以看见处理的分支。这里只有 suspend 和 standby 的代码,所以我们不能通过
<span class=SpellE>ioctl</span> 这种方法使用 <span class=SpellE>apm</span> 关机。 <br>
<br>
当用户按下 POWER 开关的时候,如果有 <span class=SpellE>apm</span> 模块,那么关机流程是由 <span
class=SpellE>apm</span> 来处理的。<span class=SpellE>apm</span> 驱动在初始化的时候启动了一个 <span
class=SpellE>apm</span> 内核线程:<span class=SpellE>apm_mainloop</span>,系统会在这里检测到
POWEROFF 按键消息并且将其命名为 APM_SYS_SUSPEND,以区别 <span class=SpellE>apm</span> -s 设置的
APM_USER_SUSPEND 模式。紧接着进入了 <span class=SpellE>apm_event_handler</span> 函数,又从
<span class=SpellE>apm_event_handler</span> 函数进入了 <span class=SpellE>check_events</span>
函数,处理函数对应的 case 分支上。系统同样使用了 suspend 函数进行关机,不过由于其它参数的原因,suspend 最后调用的是关机的流程。
<br>
<br>
5. 解决问题实例 <br>
<br>
1) 按 POWER <span class=GramE>键时某些</span>主板死机 <br>
<br>
经<span class=GramE>查只有</span>某些特定的驱动装载之后才会出现这样的情况,并且当使用关机系统调用 <span
class=SpellE>sys_reboot</span> 的时候没有这样的问题。分析 <span class=SpellE>apm</span> 的处理流程,怀疑是在关机前驱动程序没有正确处理
<span class=SpellE>apm</span> 发出的询问消息造成的。由于部分驱动程序没有源代码,决定 hack 掉 <span
class=SpellE>apm.c</span> 的关机部分,让两种方式的关机<span class=GramE>走同样</span>的流程。于是把
<span class=SpellE>apm.c</span> 的 <span class=SpellE>check_events</span> 函数中对
APM_SYS_SUSPEND 部分改写为如下代码: <br>
<br>
ret = <span class=SpellE>exec_usermodehelper</span>(<span class=SpellE>poweroff_helper_path</span>,
<span class=SpellE>argv</span>, <span class=SpellE>envp</span>); if (ret) {
<span class=SpellE>printk</span>(KERN_ERR "<span class=SpellE>apm.c</span>:
failed to exec %s , <span class=SpellE>errno</span> = %d\\n", <span
class=SpellE>poweroff_helper_path</span>, <span class=SpellE>errno</span>);
} break;<br>
<br>
<br>
<br>
定义了一个用户态应用程序 <span class=SpellE>poweroff_helper_path</span>,当 POWEROFF 键按下的时候系统运行这个
<span class=SpellE>kernel_helper</span> 程序。我们再写一个通过 <span class=SpellE>sys_reboot</span>
系统调用关机的程序,放在指定的位置下。死机的问题就解决了。 <br>
<br>
2) 快速返回实模式重启 <br>
<br>
主要可以参考了 <span class=SpellE>process.c</span> 中的返回实模式的代码,比如我把 <span
class=SpellE>real_mode_switch</span> 换成如下代码: <br>
<br>
// For fast reboot support static unsigned char <span class=SpellE>fast_reboot_switch</span>
[] = { 0x66, 0x0f, 0x20, 0xc0, /* <span class=SpellE>movl</span> %cr0,%eax
*/ 0x66, 0x25, 0x10, 0x11, 0x11, 0x11, /* <span class=SpellE>andl</span>
$0x11111110,%eax */ 0x66, 0x0f, 0x22, 0xc0, /* <span class=SpellE>movl</span>
%eax,%cr0 */ 0xea, 0x00, 0x00, 0x00, 0x70 /* <span class=SpellE>ljmp</span>
$0x7000,$0x0000 */ };<br>
<br>
<br>
<br>
系统就可以切换到实模式中,然后跳转到 7000H:0 位置开始执行。 <br>
<br>
6. ACPI 概述 <br>
<br>
在 2.4.20 内核中 ACPI 模块被注明为试验和未完成,里面有一部分功能也许没有实现。如果 APM 和 APCI 两个模块同时编译进内核,APM
在 ACPI 前被加载,APM 起作用使 ACPI 退出。对于系统电量、电源实践一类的支持(主要是在笔记本上有用),靠的是 <span
class=SpellE>acpid</span> 这个 daemon 程序。 <br>
<br>
没有一个功能类似 <span class=SpellE>apm</span> 的应用程序切换状态,<span class=SpellE>acpi</span>
的程序仅仅完成了对 <span class=SpellE>acpi</span> 状态的查询。用户实现 S0-S4 的功能可以直接向 /proc/<span
class=SpellE>acpi</span>/sleep 文件中写入数字来实现。通过读出 (cat) 其中的内容可以知道系统到底支持那些模式。 <br>
<br>
<span class=SpellE>acpi</span> 模块的源代码主程序在 <span class=SpellE>linux/drivers/acpi/driver.c</span>
中,如果向 sleep 文件写东西,就转到了 <span class=SpellE>linux/drivers/acpi/ospm/system/sm_osl.c</span>
文件的 <span class=SpellE>sm_osl_proc_write_sleep</span> 函数中,这个函数后来调用了 <span
class=SpellE>sm_osl_suspend</span> 函数。在这个函数里完成了各种功能,包括保护各种状态。最后真正的 sleep 是通过对
<span class=SpellE>acpi_enter_sleep_state</span> 的调用完成的,这个函数在 Linux
/drivers/<span class=SpellE>acpi/hardware/hwsleep.c</span> 文件中,这里写了 <span
class=SpellE>acpi</span> 的寄存器使系统进入 sleep 状态。写寄存器的指令在这个目录下面的 <span
class=SpellE>hwregs.c</span> 中。 <br>
<br>
7. 总结 <br>
<br>
本文对 <span class=SpellE>acpi</span> 的介绍非常简略,实际上 ACPI 必定会成为将来 Linux 内核中首选的电源管理方式。由于目前官方代码中
ACPI 版本较低,所以没有太详细的论述,希望将来的内核能有所改变。 <br>
<br>
参考资料 <br>
<br>
Linux -2.4.20 源代码 <br>
<br>
关于作者 <br>
<br>
范晓炬,联想(北京)有限公司软件设计中心嵌入式研发处开发工程师,研究兴趣为 Linux 内核,网络安全,<span class=SpellE>XWindow</span>
系统,Linux 桌面应用,人工智能系统。你可以通过 <a href="mailto:xiaoju_f@263.net">xiaoju_f@263.net</a>
联系他。<br>
<br style='mso-special-character:line-break'>
<![if !supportLineBreakNewLine]><br style='mso-special-character:line-break'>
<![endif]></span></span></p>
</td>
</tr>
</table>
</div>
<p class=MsoNormal><span lang=EN-US style='display:none;mso-hide:all'><o:p> </o:p></span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
</td>
</tr>
</table>
</div>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
</div>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -