Welcome

首页 / 软件开发 / VC.NET / 借助C++进行Windows开发:Windows服务增强

借助C++进行Windows开发:Windows服务增强2010-04-16 MSDN / Kenny Kerr目录

延迟自动启动服务

改进关机可预见性

失败操作和受控停止

减少权限

保护服务数据

用受限令牌保护其他项目

接收服务通知

后续内容

Windows®服务开发的状态自从在 Windows NT® 中出现服务以来一直没有较大的改变,但是 Windows Vista® 和 Windows Server® 2008 打破了这一僵局。这其中的许多功能主要是为了以更简捷的方式生成更安全的服务,但是在与安全性不相关的服务功能中,有些功能是为了提高 Windows 的整体响应能力和可靠性。

延迟自动启动服务

可通过 StartService 函数将服务配置为只在得到特定请求时启动。此服务称为按需启动服务。或者,也可将它们配置为在操作系统的启动进程中自动启动。这些就是所称的自动启动服务,对许多功能在所有时间都可用的服务或其他服务所必需的服务,此模式很有意义。但是,有些自动启动服务在用户登录到计算机时,并非绝对需要运行,因为可能不会立即使用它们。

Windows Vista 采取另一个步骤来减少 Windows 的启动时间,方法是:为只在 Windows 完成自身启动进程后要启动的自动启动服务提供工具。具体而言,就是在正常自动启动服务完成两分钟后,激活延迟的自动启动服务。服务控制管理器 (SCM) 还可创建优先级较低的主服务线程,从而确保任何登录的用户都不受延迟自动启动服务的显著影响。优先级在服务更新其正在运行的状态后重新设置为“正常”。如果发现延迟的自动启动服务在应用程序需要使用它的时候没有启动,可通过使用 StartService 函数强制其迅速启动。

以下函数显示如何控制此选项(它应由服务的安装程序调用):

bool ChangeDelayedAutoStart(
SC_HANDLE service, bool delayed)
{
ASSERT(0 != service);
SERVICE_DELAYED_AUTO_START_INFO info = { delayed };
return 0 != ::ChangeServiceConfig2(
service,
SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
&info);
}

服务句柄确定要配置的服务。此句柄可通过调用 OpenService 或 CreateService 函数来获得。ChangeServiceConfig2 函数能让您更改许多更高级的服务配置选项。在本例中,我使用 SERVICE_CONFIG_DELAYED_AUTO_START_INFO 标记告知函数将SERVICE_DELAYED_AUTO_START_INFO 指针作为其第三个参数。如果失败,ChangeServiceConfig2 将返回零值,可通过调用 GetLastError 函数获取详细的错误信息。

改进关机可预见性

Windows Vista 以前的版本,通常不可能在计算机关机时始终确保服务能正常停止。系统通知 SCM 要关机时,只有大约 20 秒的时间指示所有运行中的服务停止。如果时间太长,系统最终将粗略中止 SCM 进程。由于没有正常关闭的服务在再次重新启动时通常需要处理不一致的状态,因此这会引起许多问题,包括增加启动时间。

Windows Vista 引进了服务可以请求接收的新预关机通知。如果要开发必须正常关闭但不需要很快关闭的服务,则可以向 SCM 请求预关机通知。SCM 在执行其传统服务的关机进程前,将等待(可能无限期)所有预关机服务都停止。尽管它对服务有好处,但是对于希望计算机很快关机的用户而言作用不大。因此,应限制将此功能用于非常关键的服务,最好是只用于一个服务器方案。

服务通过将 SERVICE_ACCEPT_PRESHUTDOWN 标记包含在其服务状态中,指示其希望接收预关机通知。服务的处理程序函数随后将通过 SERVICE_CONTROL_PRESHUTDOWN 控制代码得到通知。尽管 Windows 愿意无限期地等待预关机服务停止,但是服务仍必须对来自 SCM 的查询给出响应,并通过增加检查点值来更新服务的状态。如果 SCM 确定服务没有响应,则会最终放弃,并且系统将继续关机。注意,如果服务处理 SERVICE_CONTROL_PRESHUTDOWN 控制代码,则不会接收传统的 SERVICE_CONTROL_SHUTDOWN 控制代码。在大多数情况下,对所有与停止相关的控制代码,最好使用单个关机程序。

SCM 一个最显著的行为特征是一次只与一个服务进行通信。它具有特定的含意,如表露出来意思十分明显:如果两个应用程序(或线程)调用服务控制函数(如 StartService 或 ControlService),则 SCM 将对它们进行排队,并且一次只为一个请求服务。另一个存在更多问题的情况是在 Window 关机时。通过在 SCM 调用 StartServiceCtrlDispatcher 函数时,SCM 和服务之间建立的命名管道来进行通信。SCM 向服务发送控制代码时,通过命名管道与服务中的 StartServiceCtrlDispatcher 函数进行通信。它将请求转发到特殊服务(共享的进程服务)的处理程序,并使处理程序在返回请求前对其进行处理。问题是不能阻止处理函数阻塞很长时间。如果服务不能在 30 秒之内作出响应或虽然作出了响应,但同时没有其他服务可以控制,则 SCM 发送的请求将最终超时。

作为服务开发人员,需要记住这一点,并且切勿阻塞服务的处理程序函数。一个性能良好的服务应该能接受请求,设置标记或启动某些工作程序线程以处理请求并立即返回。这样,在您的服务有时间处理请求时,SCM 就能继续与其他服务进行通信。图 1 显示了如何执行停止和关闭控制代码。

Figure1处理停止和关机

DWORD WINAPI StopThread(PVOID)
{
// perform any length shutdown operation
m_status.dwCurrentState = SERVICE_STOPPED;
VERIFY(::SetServiceStatus(m_handle,
&m_status));
return 0;
}
...
switch (control)
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_PRESHUTDOWN:
{
m_status.dwCurrentState = SERVICE_STOP_PENDING;
VERIFY(::SetServiceStatus(m_handle,
&m_status));
CHandle thread(::CreateThread(0, // default security
0, // default stack size
StopThread,
this, // context
0, // no flags
0)); // ignore thread id
break;
}
...
}

处理 SERVICE_CONTROL_STOP、SERVICE_CONTROL_SHUTDOWN 和 SERVICE_CONTROL_PRESHUTDOWN 控制代码的控制处理程序将服务的状态设置为 SERVICE_STOP_PENDING,创建一个工作程序线程进行工作,然后返回,而不做进一步操作。StopThread 函数可在需要时阻塞函数,然后将服务的状态设置为 SERVICE_STOPPED,通知 SCM,它随后可让 StartServiceCtrlDispatcher 函数返回并中止进程(假定它是共享的进程服务的最后一个服务)。只需记住,如果服务处于待定状态,则需要定期用递增的检查点值更新服务的状态,以确保 SCM 不挂起。