当有虚拟机在运行的时候阻止 Windows 关机

January 6, 2008 – 11:03 am

我经常在后台开一个虚拟机运行 Linux ,而几乎每次关机的时候都忘记了还有一个虚拟机在运行,导致 Linux 非正常关机了。所以我写了一个小程序来防止这种悲剧继续发生:在关机的时候检测是否有虚拟机在运行,有则阻止 Windows 关机。

广告时间

在介绍我的小程序之前,我实在是忍不住要对我现在用的虚拟机和虚拟桌面广告一番了! :p VirtualBox 是一个很不错的虚拟机。和老牌的 VMware 对比,也有许多优点:

  • VirtualBox 可以免费使用,而且还有一个开源版本。而 VMware 是商业软件,虽然 VMware Player 可以免费使用,但是有一些不便之处(比如无法直接创建虚拟机,需要用一个第三方的在线工具)。
  • VirtualBox 性能卓越,许多人都说用它跑虚拟机跟在本地跑一样,比 VMware 的性能还好,更不用说开源的 qemu 了,但是这是牺牲了 host OS 的性能为代价的。我自己的感觉是确实速度很快,但是并没有发现 host OS 会因此而变得很卡,只有当 guest OS 里面在做什么密集计算或 IO 之类的(比如安装操作系统),才会出现 CPU 占用 50% 的情况(我是双核 ;) )。
  • VirtualBox 附带的工具在 Linux 下轻松编译。包括虚拟显卡驱动、共享文件夹、虚拟网卡驱动之类的内核模块在最新内核下很容易就编译过了。相反,VMware 提供的源代码在最新内核下根本编译不过,网卡驱动要打过补丁,但是共享文件夹又出问题了,总之是很麻烦的。
  • VirtualBox 很 “lightweight”。VirtualBox 1.5.4 的安装文件 17MB ,而 VMware Workstation ACE 6.0.1 的安装文件 300+MB 。VMware 安装好之后默认会建立两个虚拟网卡,而 VirtualBox 不需要建立虚拟网卡就可以用 NAT 方式上网。通过端口映射的方式访问 guest OS 的 ssh 端口速度飞快。
  • VirtualBox 的窗口能自动捕捉和释放鼠标和键盘,而 VMware 需要手动点击进入虚拟机中,还需要按 Ctrl+Alt 来释放键盘和鼠标。

当然 VirtualBox 也有它不足的地方:

  • Suspend 功能好像不如 VMware 快,而且从 suspend 中恢复过来之后立即从 host OS 通过端口映射连接 guest OS 的话,大半天都没有反应,一般需要先从 guest 中往外面连接一下才行,难道他的网卡是 lazy init 的?
  • 目前还不是很稳定,偶尔会让 guest OS 突然 kernel panic 掉吧,但是幸运的是这种事情并不常见,而且相信会不断改进。

总之我现在的工作环境是 Windows 作为 Host OS ,VirtualBox 里面有一个 Debian 。需要终端的时候我一般会通过一个 ssh client 连接过去,不过我现在更喜欢另外一种工作方式:虚拟桌面——左边是 Windows 桌面,右边是一个全屏了的 Debian 桌面,可惜我的本本不支持 galilette 提到的那种对手拍打的感应,否则左边拍一拍:切换到 Linux ;右边拍一拍:切换到 Windows 。别提有多 Cool 啦! :D

Windows 这个可恶的破烂桌面系统竟然连虚拟桌面都不支持,只有用第三方软件来模拟,也算是 workaround 了,试用了几款软件之后发现 VirtuaWin 还不错,效率也还可以,我喜欢设置两个虚拟桌面,并设置为按住 Shift 的时候鼠标碰到屏幕边缘就切换到对应的虚拟桌面。幸好 VirtualBox 可以在合适的时候自动释放鼠标,在 Linux 桌面中我也可以用同样的方法切换回 Windows 桌面,当然我没有试过 VMware ,不知道这全是 Virtuawin 的功劳还是需要 VirtualBox 合作的。总之现在真是又 Cool 又爽又方便!哈哈! :D

唯一的一个问题就是:关机的时候经常忘记在另外一个虚拟桌面上还跑着一个虚拟机,然后就眼睁睁地看着它非正常关机了。VMware 在这方面做得很人性化,这种时候它会跳出来告诉你还有虚拟机在运行,并把 Windows 的关机过程中断掉。可惜 VirtualBox 目前还没有这个功能,所以:

阻止 Windows 关机的小程序

其实我觉得 VirtualBox 要加上这个功能可能不超过 10 行代码就可以了(不考虑界面之类的话),可是我发现要在 Windows 下编译 VirtualBox 要安装许多开发工具,而且看上去很麻烦,我又没看过它的源代码,所以想来想去,还是先给邮件列表发了一封信,希望增加这个功能。不过,即使他们答应增加这个功能,也无外乎是两种反馈方式:公布一个 patch ,这样我还是得自己编译;集成到下一个版本里面去,下一个版本也不知道何时会发布呢。

所以,我决定先自己写一个小工具凑活着。思想很简单:关机的时候检测是否有 VirtualBox 窗口存在,存在则阻止关机。Windows 在关机或注销的时候会向窗口发送 WM_QUERYENDSESSION 消息,返回 0 就可以告诉 Windows ,如果关机会造成很严重的问题,就可以阻止关机了(当然阻止不了某些特殊的强制关机的方法,但是我想我自己是不会用那些变态的方法来关机的 -.-bb)。

所以只要在窗户过程中加上处理 WM_QUERYENDSESSION 的代码,并用 FindWindow 来查看是否有 VirtualBox 的窗口存在,再返回适当的值就 OK 了:

case WM_QUERYENDSESSION:
	if (FindWindow (TEXT ("QWidget"), TEXT("innotek VirtualBox")))
	{
		SetTimer (hwnd, TIMER_SHOW_MESSAGE, 200, WontShutdown);
		return FALSE;
	}
	else
	{
		return TRUE;
	}

阻止关机的时候显示一个对话框告诉用户还有虚拟机在运行,为了避免阻塞窗户过程,用了一个 Timer 来延迟:

#define TIMER_SHOW_MESSAGE 0xDFF1
VOID CALLBACK WontShutdown(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
	KillTimer (hwnd, TIMER_SHOW_MESSAGE);
	MessageBox (NULL, TEXT ("Do close all your VirtualBox VMs before logging off!"),
				TEXT ("WARNING"), MB_ICONWARNING);
}

这样差不多就 OK 了。可惜管不管用还得看人品 :p ,因为 VirtualBox 有可能先收到消息,自己就被结束掉了。解决办法就是:让我的这个程序尽早地收到关机的通知:

/* Be sure our process is shutdown before VirtualBox (and other normal apps)
    So that we can interpret the shutting down process when needed */
SetProcessShutdownParameters (0x3FE, 0);

传递的参数 0x3FE 就可以让这个程序的“关机优先级”比普通的程序(一般是 0x280 )更高了。

到此为止,我的小工具就做好了,把它扔到启动里就万事大吉了。主要代码只有寥寥几行,可是完整的代码却有将近 100 行,而且程序根本没有用到窗口,可是我不知道在窗口过程之外如何处理消息,所以只好建立了一个隐藏的窗口。似乎是有些习惯 Ruby 的方式了,觉得这样写代码的方式真是 verbose 啊! :(

最后,如果你需要的话,完整的源代码可以在这里下载到。

  1. 4 Responses to “当有虚拟机在运行的时候阻止 Windows 关机”

  2. VirtualBox真不错~

    By jiqihuman on Jan 7, 2008

  3. 有2点需要指出:
    1、windows有微软出品的Virtual Desktop Manager,免费,550k(DeskmanPowertoySetup.exe),可以虚拟4个桌面;
    2、在windows上运行debian,colinux比virtualbox资源占用少,快速

    By jamesqiu on Jan 26, 2008

  4. @jamesqiu:
    你说的那个是 powertoy 里面的吧?忘记试用过没有了。

    至于 coLinux 嘛,好像比较麻烦啊。 :)

    By pluskid on Jan 27, 2008

  1. 1 Trackback(s)

  2. Jun 20, 2009: Free Mind » Gracefully Shutdown in openbox

Post a Comment