<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Oasis Feng &#187; Linux</title>
	<atom:link href="http://blog.oasisfeng.com/category/linux/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.oasisfeng.com</link>
	<description>Challenge your imagination!</description>
	<lastBuildDate>Tue, 13 Jul 2010 16:56:15 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
<atom:link rel="hub" href="http://pubsubhubbub.appspot.com"/><atom:link rel="hub" href="http://superfeedr.com/hubbub"/>		<item>
		<title>借助GRUB4DOS在U盘上引导ISO镜像</title>
		<link>http://blog.oasisfeng.com/2008/10/13/boot-iso-by-grub4dos/</link>
		<comments>http://blog.oasisfeng.com/2008/10/13/boot-iso-by-grub4dos/#comments</comments>
		<pubDate>Mon, 13 Oct 2008 15:33:09 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Computer]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Boot]]></category>
		<category><![CDATA[CD-ROM]]></category>
		<category><![CDATA[Vista]]></category>
		<category><![CDATA[Windows2008]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/?p=496</guid>
		<description><![CDATA[根据cch在上一篇《新电脑的规格配置》一文中的留言提示，我下载了Hitachi Feature Tool工具，打算将我的WD640AAKS也调节为高性能模式。不过面临的直接问题是这个工具是以ISO格式的光盘镜像提供，其中封装的是IBM DOS和FTOOL工具。而我没有刻录光驱，也无法安装DOS系统（全部分区都被我格式化为NTFS了），想要引导这个镜像还真有点麻烦。 正好一直以来都想解决U盘引导多重ISO镜像的难题，于是便去网上找答案，最后让我发现了GRUB4DOS这个非常强大的系统引导辅助工具。它的文档写的很详细，覆盖了各种场景下的安装方法。即使像我这样从未玩过Vista/Win2008中BCD引导系统的新手，也能按部就班的顺利装上GRUB4DOS。其实步骤说起来也很简单：（仅适用于Vista/Win2008） 首先应将你的U盘分区转换为可引导分区：（注意将“U:”换成实际U盘的盘符） bootsect /nt60 U: /force /mbr 接下来需要借用Vista/Win2008安装介质中的引导部分，将其中的bootmgr、bootmgr.efi以及整个boot和efi文件夹复制到U盘上（根文件夹下）。 （因为我手中这个优盘之前就做成了Win2008的安装盘，因此省去了上面两步） 然后需要修改BCD记录，为GRUB4DOS增加引导项： bcdedit /store U:\boot\bcd /create /d "More..." /application bootsector 正常情况下，你会得到一个形如“{05d33150-3fde-11dc-a457-00021cf82fb0}成功创建”的提示。这个“{&#8230;}”就是新引导项的ID，将要用在下面的几条命令中（注意将{id}替换为上述提示中的“{&#8230;}”)： bcdedit /store U:\boot\bcd /set {id} device boot bcdedit /store U:\boot\bcd /set {id} path \grldr.mbr bcdedit /store U:\boot\bcd /displayorder {id} /addlast 然后将grldr.mbr、gdldr和menu.lst复制到U盘的根文件夹下即可。 接下来要修改menu.lst，增加对光盘的引导选项。编辑menu.lst文件，在第一个“title”所在行之前插入如下内容： title Hitachi Feature Tool map --mem (hd0,0)/ftool.iso (hd32) map --hook [...]]]></description>
			<content:encoded><![CDATA[<p>根据cch在上一篇<a href="http://blog.oasisfeng.com/2008/10/13/my-new-pc/">《新电脑的规格配置》</a>一文中的留言提示，我下载了Hitachi Feature Tool工具，打算将我的WD640AAKS也调节为高性能模式。不过面临的直接问题是这个工具是以ISO格式的光盘镜像提供，其中封装的是IBM DOS和FTOOL工具。而我没有刻录光驱，也无法安装DOS系统（全部分区都被我格式化为NTFS了），想要引导这个镜像还真有点麻烦。</p>
<p>正好一直以来都想解决U盘引导多重ISO镜像的难题，于是便去网上找答案，最后让我发现了GRUB4DOS这个非常强大的系统引导辅助工具。它的文档写的很详细，覆盖了各种场景下的安装方法。即使像我这样从未玩过Vista/Win2008中BCD引导系统的新手，也能按部就班的顺利装上GRUB4DOS。其实步骤说起来也很简单：（仅适用于Vista/Win2008）</p>
<p><span id="more-496"></span>首先应将你的U盘分区转换为可引导分区：（注意将“U:”换成实际U盘的盘符）</p>
<pre>bootsect /nt60 U: /force /mbr</pre>
<p>接下来需要借用Vista/Win2008安装介质中的引导部分，将其中的bootmgr、bootmgr.efi以及整个boot和efi文件夹复制到U盘上（根文件夹下）。</p>
<p>（因为我手中这个优盘之前就做成了Win2008的安装盘，因此省去了上面两步）</p>
<p>然后需要修改BCD记录，为GRUB4DOS增加引导项：</p>
<pre>bcdedit /store U:\boot\bcd /create /d "More..." /application bootsector</pre>
<p>正常情况下，你会得到一个形如“{05d33150-3fde-11dc-a457-00021cf82fb0}成功创建”的提示。这个“{&#8230;}”就是新引导项的ID，将要用在下面的几条命令中（注意将{id}替换为上述提示中的“{&#8230;}”)：</p>
<pre>bcdedit /store U:\boot\bcd /set {id} device boot
bcdedit /store U:\boot\bcd /set {id} path \grldr.mbr
bcdedit /store U:\boot\bcd /displayorder {id} /addlast</pre>
<p>然后将grldr.mbr、gdldr和menu.lst复制到U盘的根文件夹下即可。</p>
<p>接下来要修改menu.lst，增加对光盘的引导选项。编辑menu.lst文件，在第一个“title”所在行之前插入如下内容：</p>
<pre>title Hitachi Feature Tool
map --mem (hd0,0)/ftool.iso (hd32)
map --hook
chainloader (hd32)
savedefault --wait=2</pre>
<p>因为这个光盘镜像其实很小，所以我使用了&#8211;mem参数将其缓存到内存中。对于较大的ISO文件，最好去掉&#8211;mem参数。（除非你的内存足够容纳下）</p>
<p>最后包含“savedefault”那行不是必须的，它只是让你可以在引导时将这个项保存为默认值。</p>
<p>完成上述步骤后，再把光盘镜像文件ftool.iso丢到和grub同样的根文件夹下就完工了。</p>
<p>重新启动后，在Vista的启动选单中选择“More&#8230;”，然后就会出现GRUB的引导菜单，其中第一项就是我们希望引导的光盘镜像了。</p>
<p>GRUB4DOS强大的功能远不止引导光盘镜像、WinXP、Win9x、DOS、Linux、Mac OS全都不在话下，而GRUB4DOS本身也支持多种加载方式：MBR、boot.ini、BCD、PXE…… 以后有时间再来细细研究了，反正目前主要是用它来在U盘上多重引导ISO镜像。</p>
<hr />
<p>倘若引导不成功，通常可能的原因有：</p>
<p>（1）光盘镜像的CD-ROM文件系统是Joliet CD格式，GRUB4DOS不支持这种格式。你需要通过工具转换一下镜像文件的文件系统格式。<br />
（2）BIOS中激活了SATA的AHCI模式。GRUB4DOS可能无法兼容AHCI模式，应在BIOS中将SATA模式设置为IDE。</p>
<hr />
<p><b>UPDATE: </b>倘若不需要兼做Vista/Win2008的安装U盘，那么完全可以删除掉boot\fonts下多余的繁体中文、日文、韩文等字体文件，可以节省不少空间。bootmgr.efi及整个efi文件夹按理也可以删掉，只要用起来没有兼容性问题。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2008/10/13/boot-iso-by-grub4dos/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>深入探究MODVERSIONS的实现原理</title>
		<link>http://blog.oasisfeng.com/2008/08/12/all-about-modversions/</link>
		<comments>http://blog.oasisfeng.com/2008/08/12/all-about-modversions/#comments</comments>
		<pubDate>Mon, 11 Aug 2008 18:17:44 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[modprobe]]></category>
		<category><![CDATA[Module]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/?p=430</guid>
		<description><![CDATA[最近在分析Linux内核模块与内核之间的版本耦合关系时，在实验中发现了一些有趣的结果：在同一Linux版本基础上经过不同裁剪的内核，甚至是在跨度不大的两个内核版本间，内核模块可以自由互用，而且内核本身似乎具备检查这种兼容性的能力。这与之前所知的情况是不同的。在我久远的记忆中，Linux的是依靠内核模块构建时自动产生的“vermagic”标识检查是否与当前内核版本一致的，如果不一致则拒绝加载。除非在insmod/modprobe时指定参数强制忽略vermagic，但这样做的代价是如果使用错误版本的内核模块就可能导致内核崩溃。 看了一下内核这部分的源代码（所参考源码的内核版本为2.6.17.11），发现了两种不同截然的处理方式，由一个内核构建宏“CONFIG_MODVERSIONS”所控制。在它关闭的情况下，加载内核模块时，将对vermagic作全字符串的完整匹配，任何不一致均会阻止该内核模块的加载；而倘若这个宏被开启，则只有vermagic第一个空格之后的部分会参与匹配，也就是说形如“2.6.5-7.191-bigsmp”这一段内核版本标识其实并不要求一致。事实上，当上述宏开启后，除了对vermagic后半段的匹配性检查外，内核还会进行额外的“内核接口指纹”检验，才最终决定是否允许加载该内核模块。 何谓“内核接口指纹”？其实这是Linux内核在试图解决跨版本内核模块兼容性问题上的一个积极尝试。通过在内核构建流程中插入一个特殊的“内核接口指纹”提取阶段，由一个专用的GNU工具“genksyms”为内核的源代码生成“指纹”信息。这里的“指纹”其实是一组特殊的CRC，它们分别对应着内核符号表中的每一个符号。生成CRC的输入包括这个符号的完整定义，以及其中所有参数涉及的数据类型定义，包括typedef、enum、struct、union。其中typedef将展开至最原始的标准数据类型；后三种类型将包含其定义的完整组成部分（例如结构体的成员、枚举的清单），对于组成成员也将递归应用上述原则。也就是说，“指纹”信息代表了对内核接口有兼容性影响的绝大部分要素，一旦其发生变化，则意味着接口的兼容性可能已经被破坏。 除了内核本身的构建在CONFIG_MODVERSIONS打开后会包含上述指纹信息外，内核模块的构建是否包含指纹信息还依赖于构建该内核模块的内核构建环境中此宏的状态。只有当两者均包含上述指纹信息时，上述检验机制才能真正生效。想要知道当前内核是否包含了内核接口指纹，只需提取/proc/config.gz并查看其中CONFIG_MODVERSIONS的取值；而若想知晓一个内核模块是否包含指纹信息，则需要用到modprobe的下面这个参数： modprobe --dump-modversions &#60;module&#62; 其实，就如Linux在内核编译菜单中对CONFIG_MODVERSIONS解释，也用了一个“hopefully”。这个机制本身的出发点是很好的，而且也尽了最大努力去校验。但事实上，接口的兼容性很难仅仅用定义来保证，因为接口所体现的功能部分是无法在定义中反应出来的，这也是程序尚无法完全取代人类的判断能力。因此，MODVERSIONS可以作为版本不配套时的后备选择（尤其对非开源的驱动程序），但不能依赖它作为兼容性的判定依据。 &#8212; 参考文献：Kernel Symbols and CONFIG_MODVERSIONS]]></description>
			<content:encoded><![CDATA[<p>最近在分析Linux内核模块与内核之间的版本耦合关系时，在实验中发现了一些有趣的结果：在同一Linux版本基础上经过不同裁剪的内核，甚至是在跨度不大的两个内核版本间，内核模块可以自由互用，而且内核本身似乎具备检查这种兼容性的能力。这与之前所知的情况是不同的。在我久远的记忆中，Linux的是依靠内核模块构建时自动产生的“vermagic”标识检查是否与当前内核版本一致的，如果不一致则拒绝加载。除非在insmod/modprobe时指定参数强制忽略vermagic，但这样做的代价是如果使用错误版本的内核模块就可能导致内核崩溃。</p>
<p><span id="more-430"></span>看了一下内核这部分的源代码（所参考源码的内核版本为2.6.17.11），发现了两种不同截然的处理方式，由一个内核构建宏“CONFIG_MODVERSIONS”所控制。在它关闭的情况下，加载内核模块时，将对vermagic作全字符串的完整匹配，任何不一致均会阻止该内核模块的加载；而倘若这个宏被开启，则只有vermagic第一个空格之后的部分会参与匹配，也就是说形如“2.6.5-7.191-bigsmp”这一段内核版本标识其实并不要求一致。事实上，当上述宏开启后，除了对vermagic后半段的匹配性检查外，内核还会进行额外的“内核接口指纹”检验，才最终决定是否允许加载该内核模块。</p>
<p>何谓“内核接口指纹”？其实这是Linux内核在试图解决跨版本内核模块兼容性问题上的一个积极尝试。通过在内核构建流程中插入一个特殊的“内核接口指纹”提取阶段，由一个专用的GNU工具“genksyms”为内核的源代码生成“指纹”信息。这里的“指纹”其实是一组特殊的CRC，它们分别对应着内核符号表中的每一个符号。生成CRC的输入包括这个符号的完整定义，以及其中所有参数涉及的数据类型定义，包括typedef、enum、struct、union。其中typedef将展开至最原始的标准数据类型；后三种类型将包含其定义的完整组成部分（例如结构体的成员、枚举的清单），对于组成成员也将递归应用上述原则。也就是说，“指纹”信息代表了对内核接口有兼容性影响的绝大部分要素，一旦其发生变化，则意味着接口的兼容性可能已经被破坏。</p>
<p>除了内核本身的构建在CONFIG_MODVERSIONS打开后会包含上述指纹信息外，内核模块的构建是否包含指纹信息还依赖于构建该内核模块的内核构建环境中此宏的状态。只有当两者均包含上述指纹信息时，上述检验机制才能真正生效。想要知道当前内核是否包含了内核接口指纹，只需提取/proc/config.gz并查看其中CONFIG_MODVERSIONS的取值；而若想知晓一个内核模块是否包含指纹信息，则需要用到modprobe的下面这个参数：</p>
<pre>modprobe --dump-modversions &lt;module&gt;</pre>
<p>其实，就如Linux在内核编译菜单中对CONFIG_MODVERSIONS解释，也用了一个“hopefully”。这个机制本身的出发点是很好的，而且也尽了最大努力去校验。但事实上，接口的兼容性很难仅仅用定义来保证，因为接口所体现的功能部分是无法在定义中反应出来的，这也是程序尚无法完全取代人类的判断能力。因此，MODVERSIONS可以作为版本不配套时的后备选择（尤其对非开源的驱动程序），但不能依赖它作为兼容性的判定依据。</p>
<p>&#8212;<br />
参考文献：<a href="http://www.skynet.ie/~mark/home/kernel/symbols.html">Kernel Symbols and CONFIG_MODVERSIONS</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2008/08/12/all-about-modversions/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>【Linux小技巧】秘密追踪——谁用了我的大宝？&gt;v</title>
		<link>http://blog.oasisfeng.com/2008/07/02/who-used-my-dabao/</link>
		<comments>http://blog.oasisfeng.com/2008/07/02/who-used-my-dabao/#comments</comments>
		<pubDate>Wed, 02 Jul 2008 14:01:52 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[跟踪]]></category>
		<category><![CDATA[initrd]]></category>
		<category><![CDATA[PPID]]></category>
		<category><![CDATA[track]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/?p=320</guid>
		<description><![CDATA[如果宿舍里就你买了一瓶大宝，而且用了之后效果还挺不错。隔不久，你就发现最近这瓶大宝似乎用的有点快了，敢情是哪个哥们儿想买之前先试用一下？不巧的是，你恰好非常好奇这哥们儿是谁，想要搞一次秘密追踪。啥？指纹鉴别？噢，请原谅我没有那么专业的器材…… 不过土办法、馊主意我们还是有的：只要往大宝里掺和一点稀释过的辣椒水，隔天就等着找猴子屁股吧。^_+ 扯太远了，其实本篇是想和大家分享一个在Linux下利用辣椒水的思路实现追踪程序被调用情况的小技巧。因为前段时间在分析Linux中各种内核模块被加载的方式、时间和源头。为此，我写了一个小小的Shell脚本： trackme.sh #!/usr/bash echo [`date`] [`cat /proc/$PPID/cmdline &#124; tr "\000" " "`] $0 $* > /var/log/$0.log $0.org $* 然后将需要跟踪调用情况的程序更名（在我的例子中是/sbin下的modprobe和insmod），添加一个.org的后缀。再在原位置分别以原名（modprobe和insmod）创建符号链接指向上面这个脚本。 这样，一旦modprobe或insmod被执行，则案发时间、嫌疑人（包含完整的命令行）、作案手段（全部传入参数）均被完整记录在案（/var/log下同名的日志文件），一个都逃不掉！ 最后补充一点，如果还需要记录在Linux启动早期initrd阶段中的调用，则需要将上述脚本打包到initrd镜像中，同时一并修改initrd中需要跟踪的程序（方法同上）。由于不同版本的Linux可能对/var/log处理方式有差别，最好将日志记录位置调整为/dev/shm（前提是内核中编译进了tmpfs模块）。这是一个相对安全的暂存空间，不会因为rootfs的更替而丢失，在启动完成后也可以顺利查阅到。 小小技巧，还望大虾们不要见笑，若能点拨一二，不甚感激！ 嘿，小样，别以为抹了辣椒水我就认不出你了！]]></description>
			<content:encoded><![CDATA[<blockquote><p>如果宿舍里就你买了一瓶大宝，而且用了之后效果还挺不错。隔不久，你就发现最近这瓶大宝似乎用的有点快了，敢情是哪个哥们儿想买之前先试用一下？不巧的是，你恰好非常好奇这哥们儿是谁，想要搞一次秘密追踪。啥？指纹鉴别？噢，请原谅我没有那么专业的器材…… 不过土办法、馊主意我们还是有的：只要往大宝里掺和一点稀释过的辣椒水，隔天就等着找猴子屁股吧。^_+</p></blockquote>
<p>扯太远了，其实本篇是想和大家分享一个在Linux下利用辣椒水的思路实现追踪程序被调用情况的小技巧。因为前段时间在分析Linux中各种内核模块被加载的方式、时间和源头。为此，我写了一个小小的Shell脚本：</p>
<p><span id="more-320"></span>trackme.sh</p>
<pre>#!/usr/bash
echo [`date`] [`cat /proc/$PPID/cmdline | tr "\000" " "`] $0 $* > /var/log/$0.log
$0.org $*</pre>
<p>然后将需要跟踪调用情况的程序更名（在我的例子中是/sbin下的modprobe和insmod），添加一个.org的后缀。再在原位置分别以原名（modprobe和insmod）创建符号链接指向上面这个脚本。</p>
<p>这样，一旦modprobe或insmod被执行，则案发时间、嫌疑人（包含完整的命令行）、作案手段（全部传入参数）均被完整记录在案（/var/log下同名的日志文件），一个都逃不掉！</p>
<p>最后补充一点，如果还需要记录在Linux启动早期initrd阶段中的调用，则需要将上述脚本打包到initrd镜像中，同时一并修改initrd中需要跟踪的程序（方法同上）。由于不同版本的Linux可能对/var/log处理方式有差别，最好将日志记录位置调整为/dev/shm（前提是内核中编译进了tmpfs模块）。这是一个相对安全的暂存空间，不会因为rootfs的更替而丢失，在启动完成后也可以顺利查阅到。</p>
<p>小小技巧，还望大虾们不要见笑，若能点拨一二，不甚感激！</p>
<blockquote><p>嘿，小样，别以为抹了辣椒水我就认不出你了！</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2008/07/02/who-used-my-dabao/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>在Linux中实现虚桩式内核模块</title>
		<link>http://blog.oasisfeng.com/2008/06/28/linux-stub-kernel-module/</link>
		<comments>http://blog.oasisfeng.com/2008/06/28/linux-stub-kernel-module/#comments</comments>
		<pubDate>Sat, 28 Jun 2008 12:38:46 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[ko]]></category>
		<category><![CDATA[modprobe]]></category>
		<category><![CDATA[Module]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/?p=316</guid>
		<description><![CDATA[最近在把以前做的一个驱动程序在线加载技术移植到SuSE的AutoYaST安装系统中时遇到了一点小麻烦。AutoYaST采用SuSE自己编写的Linuxrc作为其初期引导部分，有别于大部分常规的initrd引导部分，Linuxrc是直接用C语言编写的，而且几乎没有提供什么扩充的灵活性。如此一来，就无法像以往移植到其它环境中那样单纯修改引导脚本就可以搞定。虽说直接修改Linuxrc的源码也可以达到这个目的，但这样就增加了后期维护的复杂度。唉，还真是个头疼的问题…… 仔细分析了一下AutoYaST的设计，发现它至少还是允许增补驱动程序的，但仅仅支持最简单的用配置文件写死的insmod方式…… 好吧，只要还给了这个勉强算得上可扩充的途径，我一样能把驱动在线加载模块集成进来。由于只能固定调用某个驱动，所以必须从内核模块下手。把现有程序改造成内核模块显然是不现实的，好在Linux内核中还提供了这样一个调用用户态程序的接口：call_usermodehelper()，你可以在kmod.h中找到它。功能很简单，调用一个用户态程序，并且可以选择是否阻塞直到它执行完成。 顺着这个思路，只要写一个简单的虚桩式内核模块，在其初始化函数中调用我们真正想要执行的用户态程序即可达到目的了。但是，当我提出这个方案后，立即有同事质疑：在内核模块初始化阶段加载别的内核模块，搞不好会锁死内核吧？“搞不好”至少说明他也不肯定，为了找寻答案，我分析了一下内核中系统调用sys_init_module()的代码（以2.6.17为蓝本）。 asmlinkage long sys_init_module(void __user *umod, unsigned long len, const char __user *uargs) { ...... /* Only one module load at a time, please */ if (mutex_lock_interruptible(&#038;module_mutex) != 0) return -EINTR; 刚进入就加锁了，似乎有点不妙。紧接着开始加载内核模块： /* Do all the hard work */ mod = load_module(umod, len, uargs); if (IS_ERR(mod)) { mutex_unlock(&#038;module_mutex); return PTR_ERR(mod); } /* [...]]]></description>
			<content:encoded><![CDATA[<p>最近在把以前做的一个驱动程序在线加载技术移植到SuSE的AutoYaST安装系统中时遇到了一点小麻烦。AutoYaST采用SuSE自己编写的Linuxrc作为其初期引导部分，有别于大部分常规的initrd引导部分，Linuxrc是直接用C语言编写的，而且几乎没有提供什么扩充的灵活性。如此一来，就无法像以往移植到其它环境中那样单纯修改引导脚本就可以搞定。虽说直接修改Linuxrc的源码也可以达到这个目的，但这样就增加了后期维护的复杂度。唉，还真是个头疼的问题……</p>
<p>仔细分析了一下AutoYaST的设计，发现它至少还是允许增补驱动程序的，但仅仅支持最简单的用配置文件写死的insmod方式…… 好吧，只要还给了这个勉强算得上可扩充的途径，我一样能把驱动在线加载模块集成进来。由于只能固定调用某个驱动，所以必须从内核模块下手。把现有程序改造成内核模块显然是不现实的，好在Linux内核中还提供了这样一个调用用户态程序的接口：call_usermodehelper()，你可以在kmod.h中找到它。功能很简单，调用一个用户态程序，并且可以选择是否阻塞直到它执行完成。</p>
<p><span id="more-316"></span>顺着这个思路，只要写一个简单的虚桩式内核模块，在其初始化函数中调用我们真正想要执行的用户态程序即可达到目的了。但是，当我提出这个方案后，立即有同事质疑：在内核模块初始化阶段加载别的内核模块，搞不好会锁死内核吧？“搞不好”至少说明他也不肯定，为了找寻答案，我分析了一下内核中系统调用sys_init_module()的代码（以2.6.17为蓝本）。</p>
<pre>asmlinkage long
sys_init_module(void __user *umod,
		unsigned long len,
		const char __user *uargs)
{
	......

	/* Only one module load at a time, please */
	if (mutex_lock_interruptible(&#038;module_mutex) != 0)
		return -EINTR;</pre>
<p>刚进入就加锁了，似乎有点不妙。紧接着开始加载内核模块：</p>
<pre>	/* Do all the hard work */
	mod = load_module(umod, len, uargs);
	if (IS_ERR(mod)) {
		mutex_unlock(&#038;module_mutex);
		return PTR_ERR(mod);
	}

	/* Now sew it into the lists.  They won't access us, since
           strong_try_module_get() will fail. */
	stop_machine_run(__link_module, mod, NR_CPUS);

	/* Drop lock so they can recurse */
	mutex_unlock(&#038;module_mutex);

	blocking_notifier_call_chain(&#038;module_notify_list,
			MODULE_STATE_COMING, mod);

	/* Start the module */
	if (mod->init != NULL)
		ret = mod->init();</pre>
<p>看来这段代码的作者（或维护者）似乎早就预见到了我所面临的场景。内核模块的加载和初始化被清晰的分为两步，由于加载过程需要链接内核模块，因此必须加锁，而后续的初始化过程已经不会影响内核的完整性了，所以自然可以在解锁后进行。</p>
<p>排除这个疑虑后，我写了一个简单的用于检验上述思路的内核模块：</p>
<pre>#include &lt;linux/kernel.h&gt;
#include &lt;linux/module.h&gt;
#include &lt;linux/kmod.h&gt;

static int __init kmod_init(void)
{
        int ret;
        char *envp[] = {
                "PATH=/sbin:/bin",
                NULL
        };
        char *argv[] = {
                "/sbin/modprobe",
                "--first-time",
                "e1000",
                NULL
        };
        if ((ret = call_usermodehelper(argv[0], argv, envp, 1)) != 0)
                printk(KERN_ERR "Kmod test failed! (%d)\n", ret);

        return ret > 0 ? - ret : ret;
}

static void __exit kmod_exit(void)
{
}

module_init(kmod_init);
module_exit(kmod_exit);

MODULE_LICENSE("GPL");</pre>
<p>用insmod插入它时实际上调用了用户态的modprobe插入另一个内核模块e1000。如果不希望将这个虚桩留在内核中，也可以通过在kmod_init中返回-1将自身从内核中移除。</p>
<p>验证结果表明这个虚桩式内核模块的方案完全是可行的，而且与被加载的实际内核模块之间没有任何耦合及影响，它们可以各自独立的被卸载。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2008/06/28/linux-stub-kernel-module/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Linux下可以替换运行中的程序么？</title>
		<link>http://blog.oasisfeng.com/2008/04/14/replace-running-program-in-linux/</link>
		<comments>http://blog.oasisfeng.com/2008/04/14/replace-running-program-in-linux/#comments</comments>
		<pubDate>Mon, 14 Apr 2008 12:08:33 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[cp]]></category>
		<category><![CDATA[Demand-Paging]]></category>
		<category><![CDATA[Shared Object]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/?p=307</guid>
		<description><![CDATA[今天被朋友问及“Linux下可以替换运行中的程序么？”，以前依稀记得Linux下是可以的（而Windows就不让），于是随口答道“OK”。结果朋友发来一个执行结果：（test正在运行中） # cp test2 test cp: cannot create regular file `test': Text file busy 看起来是程序被占用，无法覆盖。于是自己又再做了几个实验： （1）先rm删除正在运行的test，然后cp test2 test就没有错误了。 （2）先mv改名正在运行的test，然后cp test2 test也没有问题。 查了查资料并动手分析了一下，找到了比较满意的解释。cp并不改变目标文件的inode，事实上它的实现是这样的： # strace cp test2 test  2&#62;&#38;1 &#124; grep open.*test open("test2", O_RDONLY&#124;O_LARGEFILE)     = 3 open("test", O_WRONLY&#124;O_TRUNC&#124;O_LARGEFILE) = 4 我原以为cp的实现是“rm + open(O_CREAT)”，不过现在想想上面的实现方式才是最可靠的（保证了时序安全和目标文件的属性）。这也可以解释为什么cp的目标文件会继承被覆盖文件的属性而非源文件。 Linux由于Demand Paging机制的关系，必须确保正在运行中的程序镜像（注意，并非文件本身）不被意外修改，因此内核在启动程序后会锁定这个程序镜像的inode。这就是为什么cp在用“O_WRONLY&#124;O_TRUNC”模式open目标文件时会失败。而先rm再cp的话，新文件的inode其实已经改变了，原inode并没有被真正删除，直到内核释放对它的引用。同理，mv只是改变了文件名，其inode不变，新文件使用了新的inode。 问题到这里已经水落石出，不过刨根究底的个性驱使我再做了以下一组实验，没想到结果完全出乎我意料之外！ 写了一个简单的测试程序： #include &#60;stdio.h&#62; int main(int argc, char * argv[]) { [...]]]></description>
			<content:encoded><![CDATA[<p>今天被朋友问及“Linux下可以替换运行中的程序么？”，以前依稀记得Linux下是可以的（而Windows就不让），于是随口答道“OK”。结果朋友发来一个执行结果：（test正在运行中）</p>
<pre># cp test2 test
cp: cannot create regular file `test': Text file busy</pre>
<p>看起来是程序被占用，无法覆盖。于是自己又再做了几个实验：</p>
<p>（1）先rm删除正在运行的test，然后cp test2 test就没有错误了。<br />
（2）先mv改名正在运行的test，然后cp test2 test也没有问题。</p>
<p><span id="more-307"></span>查了查资料并动手分析了一下，找到了比较满意的解释。cp并不改变目标文件的inode，事实上它的实现是这样的：</p>
<pre># strace cp test2 test  2&gt;&amp;1 | grep open.*test
open("test2", O_RDONLY|O_LARGEFILE)     = 3
open("test", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4</pre>
<p>我原以为cp的实现是“rm + open(O_CREAT)”，不过现在想想上面的实现方式才是最可靠的（保证了时序安全和目标文件的属性）。这也可以解释为什么cp的目标文件会继承被覆盖文件的属性而非源文件。</p>
<p>Linux由于Demand Paging机制的关系，必须确保正在运行中的程序镜像（注意，并非文件本身）不被意外修改，因此内核在启动程序后会锁定这个程序镜像的inode。这就是为什么cp在用“O_WRONLY|O_TRUNC”模式open目标文件时会失败。而先rm再cp的话，新文件的inode其实已经改变了，原inode并没有被真正删除，直到内核释放对它的引用。同理，mv只是改变了文件名，其inode不变，新文件使用了新的inode。</p>
<p>问题到这里已经水落石出，不过刨根究底的个性驱使我再做了以下一组实验，没想到结果完全出乎我意料之外！</p>
<p>写了一个简单的测试程序：</p>
<pre>#include &lt;stdio.h&gt;

int main(int argc, char * argv[])
{
    foo();  // An export function by libtest.so.
    sleep(1000);
    return 0;
}</pre>
<p>foo()是另一个测试动态库libtest.so的导出接口，只打印一行提示就返回。接下来我把上面对执行文件的测试用例对动态库又做了一遍：</p>
<p>（1）cp libtest2.so libtest.so可以直接覆盖已加载的动态库。<br />
（2）先rm删除已加载的libtest.so，然后cp libtest2.so libtest.so成功。<br />
（3）先mv改名已加载的libtest.so，然后cp libtest2.so libtest.so成功。</p>
<p>除了第一个用例外，结果相同。这样看来，动态库被加载时难道ld并没有锁定inode？不过想想也可以宽恕，毕竟ld也是用户态程序，没有权利去锁定inode，也不应与内核的文件系统底层实现耦合。</p>
<p>到这里都还算在情理之中，看起来Linux也都处理的很好。不过还剩下一个问题：动态库被以cp的方式覆盖后难道不会和Demand Paging机制产生冲突？</p>
<p>在思考这个问题的过程中，我意识到前面这个测试程序的一个致命漏洞，稍作修改如下：</p>
<pre>#include &lt;stdio.h&gt;

int main(int argc, char * argv[])
{
loop:
    foo();  // An export function by libtest.so.
    sleep(1);
    goto loop;
    return 0;
}</pre>
<p>这次，再执行上面的三个用例后发现，“cp libtest2.so libtest.so”虽然仍可直接覆盖已加载的动态库，但是测试程序马上出现了“Segmentation fault”。而后两个用例结果不变。由此可见，想要安全的替换已加载的动态库，还是用“笨拙”的“rm + cp”吧，看似捷径的“cp覆盖”会直接葬送掉你的程序……</p>
<p>看来，我再一次低估了Linux的健壮性，看似符合逻辑的流程也可能会带来灾难性的后果；“rm &amp; cp”与“cp覆盖”背后所隐藏的底层差异却可以成为你的救星。Linux用得越久越是让人觉得这是一块充满了荆棘和陷阱的原始丛林，只有步步为营实踏前行才能走的更远。</p>
<p>注：以上实验基于SuSE Linux Enterprise Server 9 SP1（Linux 2.6.5 &amp; glibc 2.3.3）。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2008/04/14/replace-running-program-in-linux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>运用GDB进行UT/ST的小经验</title>
		<link>http://blog.oasisfeng.com/2007/05/29/using-gdb-for-ut-and-st/</link>
		<comments>http://blog.oasisfeng.com/2007/05/29/using-gdb-for-ut-and-st/#comments</comments>
		<pubDate>Tue, 29 May 2007 14:29:36 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[GDB]]></category>
		<category><![CDATA[ST]]></category>
		<category><![CDATA[Test]]></category>
		<category><![CDATA[UT]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/2007/05/29/using-gdb-for-ut-and-st/</guid>
		<description><![CDATA[用惯了VC/Eclipse图形化的程序调试界面后，要适应GDB这种“回归淳朴”的命令行方式，确实需要一些时间。不过当你熟悉了GDB的高级用法后，才能真正体会到程序调试那种随心所欲，尽在掌握的酣畅感。 最近一段时间用GDB作代码测试，积累了一些小小经验，希望对尚未熟悉GDB的朋友有所帮助。（本文以C语言为例说明相应的用法，其它语言可参考GDB帮助文档中对其的支持说明） 业界比较专业的UT（Unit Test，单元测试）工具很多，所以通常不必直接用GDB进行UT，但使用这些专业工具前往往需要花大力气配置环境，编译驱动等，若对于一个小项目，则比直接使用GDB作UT麻烦多了。所以，小项目往往可以借助GDB进行UT/ST合一的测试，效率提升是非常可观的。 ST（System Test，系统测试）一般需要尽可能的逼近实际运行环境，所以应尽量避免或减少对代码有直接介入的工具。这时以GDB作为测试/调试手段就非常实用了。 下面就几个UT/ST中使用频率最多的场景介绍一些GDB的使用经验： 函数测试 这在UT中使用最多，同时也是部分ST用例的入口。GDB中可以在Ctrl+C暂停程序执行后，使用call命令调用程序中的任何函数，使用方法与被测程序语言的语法一致，如： call foo(5, &#8220;Hello!&#8221;) 函数执行的上下文比较特殊，栈空间是GDB临时分配的特殊内存区域，进程空间与被测函数相同，但与任何具体线程无关。如果使用call进行UT，则你会发现GDB的断点设置对call所调用的函数无效，这是GDB的一个默认保护：任何命令涉及的表达式（函数调用同样被视作表达式）都不受断点影响。为了顺利进行UT，我们必须手动修改这一默认设置： set debug expression 1 这样就可以break在被call所调用的函数中了。 Tip：如果函数中使用了类printf的输出函数，那么可能在call之后看不到任何输出，那是由于标准输出的缓冲造成的，只需简单的c (continue)一次就可以观察到输出了。 简单的函数桩 UT中对函数进行打桩是一件非常麻烦的事情，对于小项目，往往会浪费大量的精力。其实，通过GDB，我们可以变相的实现简单的函数桩，且较为便捷。 基本思路就是在需要打桩的函数上设置断点，待进入函数后，根据对函数的打桩需求，可以分别采取如下措施： （1）直接返回：用GDB的return命令强制返回即可，如果有返回值，直接跟在return后面即可，如： return 5 （2）执行函数的部分逻辑：这需要借助GDB的执行路径篡改功能。 “jump &#60;行号&#62;”直接跳至对应的代码行继续执行，也可以用“jump *&#60;地址&#62;”跳转到机器指令地址。（题外话：在x86开发环境下，即使像VC这种不支持“执行路径篡改”功能的调试器，也可以通过直接修改寄存器PC的值达到跳转的目的） 临时数据的构造 在测试一些包含复杂数据类型的代码时，常常需要构造一些复杂结构的临时数据，通常简单易行的办法是直接分配一块临时内存，如：（假设SOME_TYPE是某种复杂数据类型，如typedef的struct、union、enum等） set $temp = ({SOME_TYPE *} malloc(sizeof(SOME_TYPE))) 然后为临时变量$temp填充相应的数据： set $temp-&#62;some_field = 7 最后将其用作普通的数据参与测试：（假设foo()函数接收SOME_TYPE *类型的参数） call foo($temp) GDB强大的调试功能尚远不止如此，充分运用这些高级特性可以大大节省程序调试的时间，并且达到一些常常在你意料之外的效果。欢迎各位朋友就这方面的话题进行探讨！]]></description>
			<content:encoded><![CDATA[<p>用惯了VC/Eclipse图形化的程序调试界面后，要适应GDB这种“回归淳朴”的命令行方式，确实需要一些时间。不过当你熟悉了GDB的高级用法后，才能真正体会到程序调试那种随心所欲，尽在掌握的酣畅感。</p>
<p>最近一段时间用GDB作代码测试，积累了一些小小经验，希望对尚未熟悉GDB的朋友有所帮助。（本文以C语言为例说明相应的用法，其它语言可参考<a href="http://www.gnu.org/software/gdb/documentation/" title="GDB Online Document" target="_blank">GDB帮助文档</a>中对其的支持说明）</p>
<p>业界比较专业的UT（Unit Test，单元测试）工具很多，所以通常不必直接用GDB进行UT，但使用这些专业工具前往往需要花大力气配置环境，编译驱动等，若对于一个小项目，则比直接使用GDB作UT麻烦多了。所以，小项目往往可以借助GDB进行UT/ST合一的测试，效率提升是非常可观的。</p>
<p>ST（System Test，系统测试）一般需要尽可能的逼近实际运行环境，所以应尽量避免或减少对代码有直接介入的工具。这时以GDB作为测试/调试手段就非常实用了。</p>
<p><span id="more-245"></span>下面就几个UT/ST中使用频率最多的场景介绍一些GDB的使用经验：</p>
<ol>
<li><strong>函数测试</strong></li>
<p>这在UT中使用最多，同时也是部分ST用例的入口。GDB中可以在Ctrl+C暂停程序执行后，使用call命令调用程序中的任何函数，使用方法与被测程序语言的语法一致，如：</p>
<p>call foo(5, &#8220;Hello!&#8221;)</p>
<p>函数执行的上下文比较特殊，栈空间是GDB临时分配的特殊内存区域，进程空间与被测函数相同，但与任何具体线程无关。如果使用call进行UT，则你会发现GDB的断点设置对call所调用的函数无效，这是GDB的一个默认保护：任何命令涉及的表达式（函数调用同样被视作表达式）都不受断点影响。为了顺利进行UT，我们必须手动修改这一默认设置：</p>
<p>set debug expression 1</p>
<p>这样就可以break在被call所调用的函数中了。</p>
<p>Tip：如果函数中使用了类printf的输出函数，那么可能在call之后看不到任何输出，那是由于标准输出的缓冲造成的，只需简单的c (continue)一次就可以观察到输出了。</p>
<li><strong>简单的函数桩</strong></li>
<p>UT中对函数进行打桩是一件非常麻烦的事情，对于小项目，往往会浪费大量的精力。其实，通过GDB，我们可以变相的实现简单的函数桩，且较为便捷。</p>
<p>基本思路就是在需要打桩的函数上设置断点，待进入函数后，根据对函数的打桩需求，可以分别采取如下措施：</p>
<p>（1）直接返回：用GDB的return命令强制返回即可，如果有返回值，直接跟在return后面即可，如：</p>
<p>return 5</p>
<p>（2）执行函数的部分逻辑：这需要借助GDB的执行路径篡改功能。</p>
<p>“jump &lt;行号&gt;”直接跳至对应的代码行继续执行，也可以用“jump *&lt;地址&gt;”跳转到机器指令地址。（题外话：在x86开发环境下，即使像VC这种不支持“执行路径篡改”功能的调试器，也可以通过直接修改寄存器PC的值达到跳转的目的）</p>
<li><strong>临时数据的构造</strong></li>
<p>在测试一些包含复杂数据类型的代码时，常常需要构造一些复杂结构的临时数据，通常简单易行的办法是直接分配一块临时内存，如：（假设SOME_TYPE是某种复杂数据类型，如typedef的struct、union、enum等）</p>
<p>set $temp = ({SOME_TYPE *} malloc(sizeof(SOME_TYPE)))</p>
<p>然后为临时变量$temp填充相应的数据：</p>
<p>set $temp-&gt;some_field = 7</p>
<p>最后将其用作普通的数据参与测试：（假设foo()函数接收SOME_TYPE *类型的参数）</p>
<p>call foo($temp)</ol>
<p>GDB强大的调试功能尚远不止如此，充分运用这些高级特性可以大大节省程序调试的时间，并且达到一些常常在你意料之外的效果。欢迎各位朋友就这方面的话题进行探讨！</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2007/05/29/using-gdb-for-ut-and-st/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>借尸招魂——WinXP 12小时抢救实录</title>
		<link>http://blog.oasisfeng.com/2007/04/08/rescue-my-winxp-in-12-hours/</link>
		<comments>http://blog.oasisfeng.com/2007/04/08/rescue-my-winxp-in-12-hours/#comments</comments>
		<pubDate>Sun, 08 Apr 2007 11:55:49 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Computer]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/2007/04/08/rescue-my-winxp-in-12-hours/</guid>
		<description><![CDATA[前两天实在被nForce3的驱动程序折腾疯了，甚至于走火入魔到自己mod起驱动程序来。话说这nVidia发布的早期驱动确实bug不少，手术过程中一不留神就会遭致“机瘫Win亡”的惨剧。昨晚，我的新WinXP第一次面临了安装以来最严酷的生死考验。 4月7日·晚9时许 不记得在哪一次修改完驱动组合后，重启WinXP，硬盘哗啦啦的一路高歌猛进，可输入输出设备却似乎跟不上节奏了。显示器首先鸣警：无信号输入，紧接着发现键盘也不听使唤了，Caps Lock/Num Lock尽皆失灵。初看起来不过是又一次寻常的mod失败，Reset并启动安全模式，收拾残局准备下一次尝试。不过，事情似乎没那么简单，这一次罢工的影响远超出我的预料，Safe Mode倒是能进入，也见到了“和蔼”的欢迎提示，不过该死的键盘和鼠标依旧失灵…… 晚10时 安全模式的失效意味着WinXP第一道应急防线的彻底沦陷，看来不得不放弃前沿阵地而启用灾难救援预案了。首先想到的是“故障恢复控制台”，Oh, my god! 上次重装WinXP后竟然把这项重要的安全工程给遗漏了，而现在甚至连一张WinXP的安装光盘都找不到（估计是搬家时给漏掉了……）。无奈，只得放弃ERC，出动老将VFloppy for NTFS——这位曾数次助我化险为夷的显赫功臣。正所谓祸不单行，VFloppy竟然向我回禀“NTFS分区挂载失败……”，Shit! 没想到向来无往不利的VFloppy偏偏这个时候失灵了。 就在快要陷入无助之境时，突然忆起上次重装WinXP的时候顺带在第二硬盘上兴建了一个“Area 51”：一套Ghost版的WinXP，平日一直封存起来，只待灾难来临时成为酝酿最后反击的基地。于是，我用颤抖的双手启动了“Area 51”的硬盘引导，片刻之后，我终于不得不承认，现实就是现实，远没有科幻电影中的情节那般激动人心和荡气回肠，因为我看到的只有一个孤冷的提示：“NTLDR missing&#8230;”（弦外音：后备营救系统就好比灭火设施，万万不可放松日常检查！X&#60; ） 晚10时30分 很久没有如此背水一战了，在诸般手段尽皆失灵后，我从光盘包里找到了最后的一条救命稻草 —— 一张友人相赠尚未开封的“Ubuntu Linux 64-bit”光盘。用这张“Linux Live CD”引导系统后，终于见到了久违的Linux Desktop。有了这个完整的Linux平台，我总算可以放手实施救援行动了！ 晚10时50分 检查了一下WinXP的系统文件夹和启动日志，初步判断可能是PCI总线驱动程序的问题，需要替换回官方的版本。但开始着手替换文件时才发现原来Ubuntu自带的NTFS支持是“只读”的，不得以又去网上找了一个支持完全读写功能的ntfs-3g。因为所用Linux是64bit的缘故，没有现成的安装包可用，只好自己手动编译。折腾完这一堆麻烦事儿后，已经累的不行了。 晚12时30分 替换了驱动程序文件，依旧没有任何起色。看来今天是解决不了问题了，索性直接关掉电脑睡觉去，说不定早上一觉醒来，发现XP还是那个可爱的XP，眼下的一切不过是一场噩梦罢了~（非典型Matrix臆想症……） 4月8日·上午8时15分 被闹铃唤醒后，打开电脑，更衣，洗漱。回头面对屏幕时才想起我的WinXP尚处于生死未卜的境地，原来真是不只是一场噩梦…… 既然排除了驱动文件损坏的可能性，那么问题的焦点自然而然的锁定在注册表，看来多半是在安装驱动程序过程中的异常重启对注册表造成了严重创伤。好在我常备有ERUNT……等等，还是先虔诚的祈祷一番再启用它吧，免得又遭遇如VFloppy和“Area 51”同样的厄运。斋戒祝祷完毕后，打开ERUNT，哗，好家伙，竟然悄无声息的吞掉了我1个多G的C盘空间，难怪前些日子发觉C盘剩余空间日渐苍白呢。暂且抛开怨念，眼下毕竟还指望着它救命那。 试着用前一天上午的备份恢复了注册表的System分支（注：硬件驱动相关的问题可以不必恢复Software分支，以减少对系统的负面影响），重启，切换回WinXP引导。数分钟的沉闷之后，终于再一次见到了我那纷乱不堪的桌面。这种感觉，正如古人所云：一日不见，如隔三秋…… 上午9时左右 重新安装了Remix 11.10的nForce驱动程序后，WinXP终于又回复了往昔的宁静与安详。全然看不出刚刚所经历的这一番生死劫难。 God forgive me~ 再也不玩危险的mod了……]]></description>
			<content:encoded><![CDATA[<p>前两天实在被nForce3的驱动程序折腾疯了，甚至于走火入魔到自己mod起驱动程序来。话说这nVidia发布的早期驱动确实bug不少，手术过程中一不留神就会遭致“机瘫Win亡”的惨剧。昨晚，我的<a href="http://blog.oasisfeng.com/2006/12/09/mourn-for-my-winxp/">新WinXP</a>第一次面临了安装以来最严酷的生死考验。</p>
<p><strong>4月7日·晚9时许</strong></p>
<p>不记得在哪一次修改完驱动组合后，重启WinXP，硬盘哗啦啦的一路高歌猛进，可输入输出设备却似乎跟不上节奏了。显示器首先鸣警：无信号输入，紧接着发现键盘也不听使唤了，Caps Lock/Num Lock尽皆失灵。初看起来不过是又一次寻常的mod失败，Reset并启动安全模式，收拾残局准备下一次尝试。不过，事情似乎没那么简单，这一次罢工的影响远超出我的预料，Safe Mode倒是能进入，也见到了“和蔼”的欢迎提示，不过该死的键盘和鼠标依旧失灵……</p>
<p><strong>晚10时</strong></p>
<p>安全模式的失效意味着WinXP第一道应急防线的彻底沦陷，看来不得不放弃前沿阵地而启用灾难救援预案了。首先想到的是“故障恢复控制台”，Oh, my god! 上次重装WinXP后竟然把这项重要的安全工程给遗漏了，而现在甚至连一张WinXP的安装光盘都找不到（估计是搬家时给漏掉了……）。无奈，只得放弃ERC，出动老将VFloppy for NTFS——这位曾数次助我化险为夷的显赫功臣。正所谓祸不单行，VFloppy竟然向我回禀“NTFS分区挂载失败……”，Shit! 没想到向来无往不利的VFloppy偏偏这个时候失灵了。</p>
<p>就在快要陷入无助之境时，突然忆起上次重装WinXP的时候顺带在第二硬盘上兴建了一个“Area 51”：一套Ghost版的WinXP，平日一直封存起来，只待灾难来临时成为酝酿最后反击的基地。于是，我用颤抖的双手启动了“Area 51”的硬盘引导，片刻之后，我终于不得不承认，现实就是现实，远没有科幻电影中的情节那般激动人心和荡气回肠，因为我看到的只有一个孤冷的提示：“NTLDR missing&#8230;”（弦外音：后备营救系统就好比灭火设施，万万不可放松日常检查！X&lt; ）</p>
<p><strong>晚10时30分</strong></p>
<p>很久没有如此背水一战了，在诸般手段尽皆失灵后，我从光盘包里找到了最后的一条救命稻草 —— 一张友人相赠尚未开封的“Ubuntu Linux 64-bit”光盘。用这张“Linux Live CD”引导系统后，终于见到了久违的Linux Desktop。有了这个完整的Linux平台，我总算可以放手实施救援行动了！</p>
<p><strong>晚10时50分</strong></p>
<p>检查了一下WinXP的系统文件夹和启动日志，初步判断可能是PCI总线驱动程序的问题，需要替换回官方的版本。但开始着手替换文件时才发现原来Ubuntu自带的NTFS支持是“只读”的，不得以又去网上找了一个支持完全读写功能的ntfs-3g。因为所用Linux是64bit的缘故，没有现成的安装包可用，只好自己手动编译。折腾完这一堆麻烦事儿后，已经累的不行了。</p>
<p><strong>晚12时30分</strong></p>
<p>替换了驱动程序文件，依旧没有任何起色。看来今天是解决不了问题了，索性直接关掉电脑睡觉去，说不定早上一觉醒来，发现XP还是那个可爱的XP，眼下的一切不过是一场噩梦罢了~（非典型Matrix臆想症……）</p>
<p><strong>4月8日·上午8时15分</strong></p>
<p>被闹铃唤醒后，打开电脑，更衣，洗漱。回头面对屏幕时才想起我的WinXP尚处于生死未卜的境地，原来真是不只是一场噩梦……</p>
<p>既然排除了驱动文件损坏的可能性，那么问题的焦点自然而然的锁定在注册表，看来多半是在安装驱动程序过程中的异常重启对注册表造成了严重创伤。好在我常备有ERUNT……等等，还是先虔诚的祈祷一番再启用它吧，免得又遭遇如VFloppy和“Area 51”同样的厄运。斋戒祝祷完毕后，打开ERUNT，哗，好家伙，竟然悄无声息的吞掉了我1个多G的C盘空间，难怪前些日子发觉C盘剩余空间日渐苍白呢。暂且抛开怨念，眼下毕竟还指望着它救命那。</p>
<p>试着用前一天上午的备份恢复了注册表的System分支（注：硬件驱动相关的问题可以不必恢复Software分支，以减少对系统的负面影响），重启，切换回WinXP引导。数分钟的沉闷之后，终于再一次见到了我那纷乱不堪的桌面。这种感觉，正如古人所云：一日不见，如隔三秋……</p>
<p><strong>上午9时左右</strong></p>
<p>重新安装了Remix 11.10的nForce驱动程序后，WinXP终于又回复了往昔的宁静与安详。全然看不出刚刚所经历的这一番生死劫难。</p>
<p>God forgive me~ 再也不玩危险的mod了……</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2007/04/08/rescue-my-winxp-in-12-hours/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>WinXP crashed again, now live with Linux&#8230;</title>
		<link>http://blog.oasisfeng.com/2007/04/07/winxp-crashed-again-now-live-with-linux/</link>
		<comments>http://blog.oasisfeng.com/2007/04/07/winxp-crashed-again-now-live-with-linux/#comments</comments>
		<pubDate>Sat, 07 Apr 2007 15:09:37 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Computer]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/2007/04/07/winxp-crashed-again-now-live-with-linux/</guid>
		<description><![CDATA[After some crazy modding of nforce drivers, my WinXP crashed fatally. Even the safe mode is not available&#8230; I&#8217;ve been seeking for a rescue CD of WinXP, but with no luck. Instead, I found a Ubuntu Linux CD for 64-bit system which is received from my friend. So, I&#8217;m now getting attached to Linux~ It&#8217;s [...]]]></description>
			<content:encoded><![CDATA[<p>After some crazy modding of nforce drivers, my WinXP crashed fatally. Even the safe mode is not available&#8230;</p>
<p>I&#8217;ve been seeking for a rescue CD of WinXP, but with no luck. Instead, I found a Ubuntu Linux CD for 64-bit system which is received from my friend. So, I&#8217;m now getting attached to Linux~</p>
<p>It&#8217;s really not that comfortable for me to live with Linux, even Ubuntu gives the perfect experience on installation and user interface. First, I had to find a Chinese input method in order to end up with this messy English writing&#8230; )-X</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2007/04/07/winxp-crashed-again-now-live-with-linux/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Linux启动时间的极限优化</title>
		<link>http://blog.oasisfeng.com/2006/09/21/linux-bootup-time-optimization/</link>
		<comments>http://blog.oasisfeng.com/2006/09/21/linux-bootup-time-optimization/#comments</comments>
		<pubDate>Wed, 20 Sep 2006 17:50:49 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/2006/09/21/linux-bootup-time-optimization/</guid>
		<description><![CDATA[　　在上次完成嵌入式应用的Linux裁减后，Linux的启动时间仍需要 7s 左右，虽然勉强可以接受，但仍然没有达到我个人所追求的目标——2s 以内。况且，在实际的商用环境中，设备可靠性的要求可是“5个9”（99.999%，即OOS时间低于5分钟/年），这就意味着每减少一秒钟Linux启动（设备复位）时间，对可靠性都是一个明显的提升。 　　言归正传，如何着手对Linux的启动时间进行优化呢？ 　　CELF(The Consumer Electronics Linux Forum)论坛为我们指引了一个方向。 （1）首先是对Linux启动过程的跟踪和分析，生成详细的启动时间报告。 　　较为简单可行的方式是通过PrintkTime功能为启动过程的所有内核信息增加时间戳，便于汇总分析。PrintkTime最早为CELF所提供的一个内核补丁，在后来的Kernel 2.6.11版本中正式纳入标准内核。所以大家可能在新版本的内核中直接启用该功能。如果你的Linux内核因为某些原因不能更新为2.6.11之后的版本，那么可以参考CELF提供的方法修改或直接下载它们提供的补丁：http://tree.celinuxforum.org/CelfPubWiki/PrintkTimes 　　开启PrintkTime功能的方法很简单，只需在内核启动参数中增加“time”即可。当然，你也可以选择在编译内核时直接指定“Kernel hacking”中的“Show timing information on printks”来强制每次启动均为内核信息增加时间戳。这一种方式还有另一个好处：你可以得到内核在解析启动参数前所有信息的时间。因此，我选择后一种方式。 　　当完成上述配置后，重新启动Linux，然后通过以下命令将内核启动信息输出到文件： dmesg -s 131072 > ktime 　　然后利用一个脚本“show_delta”（位于Linux源码的scripts文件夹下）将上述输出的文件转换为时间增量显示格式： /usr/src/linux-x.xx.xx/scripts/show_delta ktime > dtime 　　这样，你就得到了一份关于Linux启动时间消耗的详细报告。 （2）然后，我们就来通过这份报告，找出启动中相对耗时的过程。 　　必须明确一点：报告中的时间增量和内核信息之间没有必然的对应关系，真正的时间消耗必须从内核源码入手分析。 　　这一点对于稍微熟悉编程的朋友来说都不难理解，因为时间增量只是两次调用printk之间的时间差值。通常来说，内核启动过程中在完成一些耗时的任务，如创建hash索引、probe硬件设备等操作后会通过printk将结果打印出来，这种情况下，时间增量往往反映的是信息对应过程的耗时；但有些时候，内核是在调用printk输出信息后才开始相应的过程，那么报告中内核信息相应过程的时间消耗对应的是其下一行的时间增量；还有一些时候，时间消耗在了两次内核信息输出之间的某个不确定的时段，这样时间增量可能就完全无法通过内核信息反应出来了。 　　所以，为了准确判断真正的时间消耗，我们需要结合内核源码进行分析。必要的时候，例如上述第三种情形下，还得自己在源码中插入printk打印，以进一步确定实际的时间消耗过程。 　　以下是我上次裁减后Linux内核的启动分析： 　　内核启动总时间： 6.188s 　　关键的耗时部分： 　　1) 0.652s &#8211; Timer,IRQ,Cache,Mem Pages等核心部分的初始化 　　2) 0.611s &#8211; 内核与RTC时钟同步 　　3) 0.328s &#8211; 计算Calibrating Delay（4个CPU核心的总消耗） 　　4) 0.144s [...]]]></description>
			<content:encoded><![CDATA[<p>　　在上次完成<a href="http://blog.oasisfeng.com/2006/09/15/embedded-linux-cutdown/">嵌入式应用的Linux裁减</a>后，Linux的启动时间仍需要 7s 左右，虽然勉强可以接受，但仍然没有达到我个人所追求的目标——2s 以内。况且，在实际的商用环境中，设备可靠性的要求可是“5个9”（99.999%，即OOS时间低于5分钟/年），这就意味着每减少一秒钟Linux启动（设备复位）时间，对可靠性都是一个明显的提升。</p>
<p>　　言归正传，如何着手对Linux的启动时间进行优化呢？</p>
<p><span id="more-118"></span>　　<a href="http://www.celinuxforum.org/">CELF(The Consumer Electronics Linux Forum)</a>论坛为我们指引了一个方向。</p>
<p><strong>（1）首先是对Linux启动过程的跟踪和分析，生成详细的启动时间报告。</strong></p>
<p>　　较为简单可行的方式是通过PrintkTime功能为启动过程的所有内核信息增加时间戳，便于汇总分析。PrintkTime最早为CELF所提供的一个内核补丁，在后来的Kernel 2.6.11版本中正式纳入标准内核。所以大家可能在新版本的内核中直接启用该功能。如果你的Linux内核因为某些原因不能更新为2.6.11之后的版本，那么可以参考CELF提供的方法修改或直接下载它们提供的补丁：<a href="http://tree.celinuxforum.org/CelfPubWiki/PrintkTimes">http://tree.celinuxforum.org/CelfPubWiki/PrintkTimes</a></p>
<p>　　开启PrintkTime功能的方法很简单，只需在内核启动参数中增加“time”即可。当然，你也可以选择在编译内核时直接指定“Kernel hacking”中的“Show timing information on printks”来强制每次启动均为内核信息增加时间戳。这一种方式还有另一个好处：你可以得到内核在解析启动参数前所有信息的时间。因此，我选择后一种方式。</p>
<p>　　当完成上述配置后，重新启动Linux，然后通过以下命令将内核启动信息输出到文件：</p>
<p><code>dmesg -s 131072 > ktime</code></p>
<p>　　然后利用一个脚本“show_delta”（位于Linux源码的scripts文件夹下）将上述输出的文件转换为时间增量显示格式：</p>
<p><code>/usr/src/linux-x.xx.xx/scripts/show_delta ktime > dtime</code></p>
<p>　　这样，你就得到了一份关于Linux启动时间消耗的详细报告。</p>
<p><strong>（2）然后，我们就来通过这份报告，找出启动中相对耗时的过程。</strong></p>
<p>　　必须明确一点：报告中的时间增量和内核信息之间没有必然的对应关系，真正的时间消耗必须从内核源码入手分析。</p>
<p>　　这一点对于稍微熟悉编程的朋友来说都不难理解，因为时间增量只是两次调用printk之间的时间差值。通常来说，内核启动过程中在完成一些耗时的任务，如创建hash索引、probe硬件设备等操作后会通过printk将结果打印出来，这种情况下，时间增量往往反映的是信息对应过程的耗时；但有些时候，内核是在调用printk输出信息后才开始相应的过程，那么报告中内核信息相应过程的时间消耗对应的是其下一行的时间增量；还有一些时候，时间消耗在了两次内核信息输出之间的某个不确定的时段，这样时间增量可能就完全无法通过内核信息反应出来了。</p>
<p>　　所以，为了准确判断真正的时间消耗，我们需要结合内核源码进行分析。必要的时候，例如上述第三种情形下，还得自己在源码中插入printk打印，以进一步确定实际的时间消耗过程。</p>
<p>　　以下是我上次裁减后Linux内核的启动分析：</p>
<p>　　内核启动总时间： 6.188s</p>
<p>　　关键的耗时部分：<br />
　　1) 0.652s &#8211; Timer,IRQ,Cache,Mem Pages等核心部分的初始化<br />
　　2) 0.611s &#8211; 内核与RTC时钟同步<br />
　　3) 0.328s &#8211; 计算Calibrating Delay（4个CPU核心的总消耗）<br />
　　4) 0.144s &#8211; 校准APIC时钟<br />
　　5) 0.312s &#8211; 校准Migration Cost<br />
　　6) 3.520s &#8211; Intel E1000网卡初始化</p>
<p>　　下面，将针对上述各部分进行逐一分析和化解。</p>
<p><strong>（3）接下来，进行具体的分项优化。</strong></p>
<p>　　CELF已经提出了一整套针对消费类电子产品所使用的嵌入式Linux的启动优化方案，但是由于面向不同应用，所以我们只能部分借鉴他们的经验，针对自己面对的问题作出具体的分析和尝试。</p>
<p>　　内核关键部分（Timer、IRQ、Cache、Mem Pages……）的初始化目前暂时没有比较可靠和可行的优化方案，所以暂不考虑。</p>
<p>　　对于上面分析结果中的 2、3 两项，CELF已有专项的优化方案：“RTCNoSync”和“PresetLPJ”。</p>
<p>　　前者通过屏蔽启动过程中所进行的RTC时钟同步或者将这一过程放到启动后进行（视具体应用对时钟精度的需求而定），实现起来比较容易，但需要为内核打补丁。似乎CELF目前的工作仅仅是去掉了该过程，而没有实现所提到的“延后”处理RTC时钟的同步。考虑到这个原因，我的方案中暂时没有引入这一优化（毕竟它所带来的时间漂移已经达到了“秒”级），继续关注中。</p>
<p>　　后者是通过在启动参数中强制指定LPJ值而跳过实际的计算过程，这是基于LPJ值在硬件条件不变的情况下不会变化的考虑。所以在正常启动后记录下内核信息中的“Calibrating Delay”数值后就可以在启动参数中以下面的形式强制指定LPJ值了：</p>
<p><code>lpj=9600700</code></p>
<p>　　上面分析结果中的 4、5 两项都是SMP初始化的一部分，因此不在CELF研究的范畴（或许将来会有采用多核的MP4出现？……），只能自力更生了。研究了一下SMP的初始化代码，发现“Migration Cost”其实也可以像“Calibrating Delay”采用预置的方式跳过校准时间。方法类似，最后在内核启动参数中增加：</p>
<p><code>migration_cost=4000,4000</code></p>
<p>　　而Intel的网卡驱动初始化优化起来就比较麻烦了，虽然也是开源，但读硬件驱动完全不比读一般的C代码，况且建立在如此肤浅理解基础上的“优化”修改也实在难保万全。基于可靠性的考虑，我最终在两次尝试均告失败后放弃了这一条路。那么，换一个思维角度，可以借鉴CELF在“ParallelRCScripts”方案中的“并行初始化”思想，将网卡驱动独立编译为模块，放在初始化脚本中与其它模块和应用同步加载，从而消除Probe阻塞对启动时间的影响。考虑到应用初始化也可能使用到网络，而在我们的实际硬件环境中，只有eth0是供应用使用的，因此需要将第一个网口初始化的0.3s时间计算在内。</p>
<p>　　除了在我的方案中所遇到的上述各优化点，CELF还提出了一些你可能会感兴趣的有特定针对性的专项优化，如：</p>
<p>　　ShortIDEDelays &#8211; 缩短IDE探测时长（我的应用场景中不包含硬盘，所以用不上）<br />
　　KernelXIP &#8211; 直接在ROM或Flash中运行内核（考虑到兼容性因素，未采用）<br />
　　IDENoProbe &#8211; 跳过未连接设备的IDE口<br />
　　OptimizeRCScripts &#8211; 优化initrd中的linuxrc脚本（我采用了BusyBox更简洁的linuxrc）</p>
<p>　　以及其它一些尚处于设想阶段的优化方案，感兴趣的朋友可以访问<a href="http://tree.celinuxforum.org/CelfPubWiki/BootupTimeResources">CELF Developer Wiki</a>了解详情。</p>
<p><strong>（4）优化结果</strong></p>
<p>　　经过上述专项优化，以及对inittab、rcS脚本的冗余裁减，<strong>整个Linux内核的启动时间从优化前的 6.188s 下降到了最终的 2.016s，如果不包含eth0的初始化，则仅需 1.708s</strong>（eth0初始化可以和系统中间件及部分应用加载并行），基本达到了既定目标。与Kexec配合，可以大大降低软件故障导致的复位时间，有效的提升了产品的可靠性。</p>
<p>　　大家如果对内核启动时间的优化还有什么建议或疑惑，都欢迎与我探讨。:)</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2006/09/21/linux-bootup-time-optimization/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>从POST到Linux的一体化远程控制</title>
		<link>http://blog.oasisfeng.com/2006/09/18/remote-control-from-post-to-linux/</link>
		<comments>http://blog.oasisfeng.com/2006/09/18/remote-control-from-post-to-linux/#comments</comments>
		<pubDate>Mon, 18 Sep 2006 14:40:29 +0000</pubDate>
		<dc:creator>oasisfeng</dc:creator>
				<category><![CDATA[Computer]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[AMI]]></category>
		<category><![CDATA[KVM]]></category>
		<category><![CDATA[Remote-Access]]></category>
		<category><![CDATA[Serial-Redirection]]></category>

		<guid isPermaLink="false">http://blog.oasisfeng.com/2006/09/18/remote-control-from-post-to-linux/</guid>
		<description><![CDATA[　　在上次初尝Remote KVM甜头的基础上，本着充分挖掘其实用价值的考虑，最近对这一技术又进行了深入的研究和尝试。 　　在网上查阅了各种相关资料后，才知道上次所实现的串口终端功能的正式学名叫做“SREDIR (Serial Redirection，串口重定向)”，它是AMI在其最新的AMI BIOS 8系列中引入的一项新技术，可以看作是一个简化版的“Remote KVM”。主要实现了对字符界面显示输出及键盘输入的重定向到串口，它对于服务器群或者Blade Server来说是非常实用的功能。 　　下面就让我来介绍一下如何利用这项技术实现从POST到Linux的全程串口控制。 　　（1）首先，需要接上普通的终端，并在AMI BIOS的CMOS设置中“Advanced Settings”页下打开“Remote Access”的选项（如果你的BIOS是默认开启该选项的，那么你甚至不必准备一套普通的终端，直接从下一步开始）。 　　根据实际情况配置好端口、参数及终端的模式： 　　“Redirection after BIOS POST”应选择“Always”，以保证从POST、Boot Loader到Linux的平滑过渡； 　　“Terminal Type”建议选择“ANSI”，这样显示效果最接近实际终端，也能获得最大的兼容性； 　　“VT-UTF8 Combo Key Support”建议打开，这样可以使用扩展的F5-F12映射键，这或许在进入某些第三方的Addon ROM时需要用到。 　　（2）完成上述设置后就可以在另一台电脑上通过串口线连接至我们需要控制的服务器，然后打开你的终端程序（Windows自带的“超级终端”即可），匹配好端口参数。一些就绪后，复位服务器，顺利的话，你就可以从终端程序里看到BIOS自检过程的显示信息了。如果没有任何显示或者出现杂乱的信息，那么请检查端口及参数是否匹配。 　　（3）先让我们来试试从远程终端进入CMOS设置，“Del”是通常进入的CMOS设置界面的热键。但问题就在这里，终端程序的键位映射中一般是没有Del键的，你在终端按下Del键实际传到串口的可能就是Ctrl+H或者其它某个（组合）键了。那么如何才能进入CMOS设置界面呢？按照AMI官方文档的说明是可以用另一个替代热键F4来达到相同的作用，但我实际实验发现在我们这块Intel的服务器板上按F4却没有任何效果…… 后来折腾了半天，总算让我找到了另一个替代的办法：在“超级终端”的“连接属性”设置中有一项“Backspace键发送”，把它改为“Del”。这样在POST阶段按下终端的Backspace键就可以成功进入CMOS设置界面了。:) 　　（4）接下来，我们要进一步实现Boot Loader的串口控制。 　　如果你的Boot Loader是字符界面的，而且前面CMOS设置中“Redirection after BIOS POST”选择了“Always”，那么通常你通常可以直接在POST结束后从终端上看到Boot Loader界面了。倘若你不幸和我一样用了图形界面的GRUB，那么此时从终端上是看不到任何显示的（但是你仍然可以“盲打”）。 　　为了解决这个问题，我们需要修改GRUB的配置文件（通常是/boot/grub/menu.lst），注释掉“gfxmenu”所在的一行。这样就可以让GRUB回归到传统的字符界面了。 　　如果到这里你还不能从终端看到Boot Loader的话，那么可能是你的Boot Loader与BIOS的Remote Access存在冲突。你需要手动在GRUB的配置文件中添加下面几行： serial --unit=0 --speed=9600 --word=8 --parity=no --stop=1 terminal serial 　　注：unit参数是串口的序号，0代表COM1，1代表COM2，…… 其它参数需要与CMOS中的配置及终端配置保持一致。 　　（5）而后，我们还必须解决Linux启动过程的串口控制，否则Boot Loader开始加载Linux之后我们就彻底从串口失去控制权了。 [...]]]></description>
			<content:encoded><![CDATA[<p>　　在上次<a href="http://blog.oasisfeng.com/2006/09/05/diy-remote-kvm-server/">初尝Remote KVM甜头</a>的基础上，本着充分挖掘其实用价值的考虑，最近对这一技术又进行了深入的研究和尝试。</p>
<p>　　在网上查阅了各种相关资料后，才知道上次所实现的串口终端功能的正式学名叫做“SREDIR (Serial Redirection，串口重定向)”，它是AMI在其最新的AMI BIOS 8系列中引入的一项新技术，可以看作是一个简化版的“Remote KVM”。主要实现了对字符界面显示输出及键盘输入的重定向到串口，它对于服务器群或者Blade Server来说是非常实用的功能。</p>
<p>　　下面就让我来介绍一下如何利用这项技术实现从POST到Linux的全程串口控制。</p>
<p><span id="more-110"></span>　　（1）首先，需要接上普通的终端，并在AMI BIOS的CMOS设置中“Advanced Settings”页下打开“Remote Access”的选项（如果你的BIOS是默认开启该选项的，那么你甚至不必准备一套普通的终端，直接从下一步开始）。</p>
<p><a class="imagelink" title="CMOS Setup - Serial Redirection" href="http://blog.oasisfeng.com/wp-content/uploads/2006/09/sr_setup.jpg"><img id="image116" alt="CMOS Setup - Serial Redirection" src="http://blog.oasisfeng.com/wp-content/uploads/2006/09/sr_setup.jpg" /></a></p>
<p>　　根据实际情况配置好端口、参数及终端的模式：</p>
<p>　　“Redirection after BIOS POST”应选择“Always”，以保证从POST、Boot Loader到Linux的平滑过渡；<br />
　　“Terminal Type”建议选择“ANSI”，这样显示效果最接近实际终端，也能获得最大的兼容性；<br />
　　“VT-UTF8 Combo Key Support”建议打开，这样可以使用扩展的F5-F12映射键，这或许在进入某些第三方的Addon ROM时需要用到。</p>
<p>　　（2）完成上述设置后就可以在另一台电脑上通过串口线连接至我们需要控制的服务器，然后打开你的终端程序（Windows自带的“超级终端”即可），匹配好端口参数。一些就绪后，复位服务器，顺利的话，你就可以从终端程序里看到BIOS自检过程的显示信息了。如果没有任何显示或者出现杂乱的信息，那么请检查端口及参数是否匹配。</p>
<p>　　（3）先让我们来试试从远程终端进入CMOS设置，“Del”是通常进入的CMOS设置界面的热键。但问题就在这里，终端程序的键位映射中一般是没有Del键的，你在终端按下Del键实际传到串口的可能就是Ctrl+H或者其它某个（组合）键了。那么如何才能进入CMOS设置界面呢？按照AMI官方文档的说明是可以用另一个替代热键F4来达到相同的作用，但我实际实验发现在我们这块Intel的服务器板上按F4却没有任何效果…… 后来折腾了半天，总算让我找到了另一个替代的办法：在“超级终端”的“连接属性”设置中有一项“Backspace键发送”，把它改为“Del”。这样在POST阶段按下终端的Backspace键就可以成功进入CMOS设置界面了。:)</p>
<p>　　（4）接下来，我们要进一步实现Boot Loader的串口控制。</p>
<p>　　如果你的Boot Loader是字符界面的，而且前面CMOS设置中“Redirection after BIOS POST”选择了“Always”，那么通常你通常可以直接在POST结束后从终端上看到Boot Loader界面了。倘若你不幸和我一样用了图形界面的GRUB，那么此时从终端上是看不到任何显示的（但是你仍然可以“盲打”）。</p>
<p>　　为了解决这个问题，我们需要修改GRUB的配置文件（通常是/boot/grub/menu.lst），注释掉“gfxmenu”所在的一行。这样就可以让GRUB回归到传统的字符界面了。</p>
<p>　　如果到这里你还不能从终端看到Boot Loader的话，那么可能是你的Boot Loader与BIOS的Remote Access存在冲突。你需要手动在GRUB的配置文件中添加下面几行：</p>
<pre>serial --unit=0 --speed=9600 --word=8 --parity=no --stop=1
terminal serial</pre>
<p>　　注：unit参数是串口的序号，0代表COM1，1代表COM2，…… 其它参数需要与CMOS中的配置及终端配置保持一致。</p>
<p>　　（5）而后，我们还必须解决Linux启动过程的串口控制，否则Boot Loader开始加载Linux之后我们就彻底从串口失去控制权了。</p>
<p>　　首先，需要从Boot Loader中去掉任何Splash画面的设置，比如GRUB的配置文件中包含“splashimage”的那一行。然后，在内核参数中增加以下内容：</p>
<pre>console=tty0 console=ttyS0,9600n8</pre>
<p>　　其中，ttyS0代表COM1口，如果你使用的是COM2，那么需要修改为ttyS1。后面的9600n8即串口的参数，应与前述设置保存一致。</p>
<p>　　注：多个console参数存在时，只有最后一个console指定的终端具有交互权，其它终端上只是同步输出启动阶段的信息。所以这里一定要将串口作为最后一个console参数，我们才能从这里登录。</p>
<p>　　OK，万事俱备，让我们从串口登录到Linux吧。怎么？你的root帐号无法登录？对了，还有最后一点忘了说。默认情况下，login程序没有把串口列为安全终端，也就是说，你无法从串口登录root帐号。那么，如何让login建立对串口的信任呢？只需要简单的修改一下/etc/securetty文件，增加一行你的串口设备名，如“ttyS0”即可。</p>
<p>　　以上，我们就完成了全部串口控制所需的步骤，结合上次例子中提到的终端服务，即可轻松实现从POST到Linux的一体化远程控制了。</p>
<p>　　参考资料：“AMI BIOS 8 &#8211; Serial Redirection”: <a href="http://www.ami.com/support/doc/AMIBIOS8-SerialRedirection.pdf">http://www.ami.com/support/doc/AMIBIOS8-SerialRedirection.pdf</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.oasisfeng.com/2006/09/18/remote-control-from-post-to-linux/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
	</channel>
</rss>

<!-- Dynamic Page Served (once) in 0.424 seconds -->
