本文主要介绍 Windows service 的编程模式和 SCM 的相关功能。
关于 Windows Service 的定义,可以参考 Wiki: Windows Service ,总结如下:
参考 MSDN - About Services 可知,Windows Services 包括以下几个主要知识点:
SCM - Service Control Manager,是一个特殊的系统进程,负责管理 Windows Services 。
The SCM executable, Services.exe, runs as a Windows console program and is launched by the Wininit process early during the system startup. Its main function, SvcCtrlMain(), launches all the services configured for automatic startup.
SCM 通过读下面的注册表项来初始化它的“internal database”
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services, which contains the actual database of services and device drivers and is read into SCM’s internal database.
换句话说,HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services 中包含了 Windows Services 的各种信息。
《Windows驱动开发技术详解》 CH3 中的 LoadNTDriver.exe 示例程序演示了如何通过 SCM 加载和卸载一个 Windows Service 。它的核心函数是 CreateService() 。
通过 services.msc 命令可以打开 SCM 的 console 。
关于 SCM 更详细的资料,可以参考 MSDN - SCM,其中也提供了一个完整的示例程序 - The Complete Service Sample。
参考 MSDN - About Services 可知“SCM 主要是通过数据库来管理 services”:
The service control manager (SCM) maintains a database of installed services and driver services, and provides a unified and secure means of controlling them. The database includes information on how each service or driver service should be started. It also enables system administrators to customize security requirements for each service and thereby control access to the service.
参考 MSDN - Service Programs。
为了符合 SCM 的接口要求,Windows Services程序必须包含这三个部分:
通过 CMD 命令 eventvwr.msc 可以查看 Windows Services 程序的加载、卸载、启动、停止和崩溃等等记录。
从进程创建的角度来看,Services 的启动需要经过以下几个步骤:
Services are generally written as console applications. The entry point of a console application is its main function. The main function receives arguments from the ImagePath value from the registry key for the service.
需要注意的是,services 程序都是 console 程序,它运行在后台,不带窗口。显然,它的入口函数只能是 main() 函数。
Services 程序的执行流在 SCM 与 main() 函数之间有一个切换的动作,具体流程是:
When the SCM starts a service program, it waits for it to call the StartServiceCtrlDispatcher function.
StartServiceCtrlDispatcher() 函数能够告诉 SCM 关于该 service 的一些信息:
The StartServiceCtrlDispatcher function takes a SERVICE_TABLE_ENTRY structure for each service contained in the process. Each structure specifies the service name and the entry point for the service.
SERVICE_TABLE_ENTRY 是一个字符串与函数指针的映射表,它标明一个进程可以注册多个 ServiceMain 函数。
联系前面的内容,事实上,main() 与传统意义的 win32 console 程序的 main() 完全不同,它仅仅是进行一些“公共初始化”,然后告诉 SCM 真正的入口点函数(ServiceMain)在哪。
main() 函数的初始化操作与 service type 相关,共有两种 type:
前者立即调用 StartServiceCtrlDispatcher ,并在 service 启动后再执行初始化;后者需要在调 StartServiceCtrlDispatcher 进行一些“公共初始化”,也可以开新线程执行这个“公共初始化”。
总结来说,service 的 main 函数很简单,主要调用 StartServiceCtrlDispatcher 函数,传入“Service Name”和“ServiceMain”。
void __cdecl _tmain(int argc, TCHAR *argv[])
{
// If command-line parameter is "install", install the service.
// Otherwise, the service is probably being started by the SCM.
if( lstrcmpi( argv[1], TEXT("install")) == 0 )
{
SvcInstall();
return;
}
// TO_DO: Add any additional services for the process to this table.
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{ SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },
{ NULL, NULL }
};
// This call returns when the service has stopped.
// The process should simply terminate when the call returns.
if (!StartServiceCtrlDispatcher( DispatchTable ))
{
SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));
}
}
ServiceMain() 才是 service 程序真正的功能开始执行的地方。它的名字可以任意指定,仅需要在 SERVICE_TABLE_ENTRY 保存即可。
When a service control program requests that a new service run, the Service Control Manager (SCM) starts the service and sends a start request to the control dispatcher. The control dispatcher creates a new thread to execute the ServiceMain function for the service.
ServiceMain 与 main 不在同一个线程中。但是,它们应该在同一个进程上下文环境中(我猜)。
ServiceMain 的主要任务如下:
Call the RegisterServiceCtrlHandler function immediately to register a Handler function to handle control requests for the service. The return value of RegisterServiceCtrlHandler is a service status handle that will be used in calls to notify the SCM of the service status.
参考 MSDN - Service Control Handler :
Each service has a control handler, the Handler function, that is invoked by the control dispatcher when the service process receives a control request from a service control program. Therefore, this function executes in the context of the control dispatcher.
Service Control Handler 是一个回调函数,其中会维护一个 switch - cases,执行 Controller dispatcher 的一些转发过来的一些关于 service 的 STOP 或 PENDING 相关的操作。
Simple Windows Service in C++ 是一个非常小巧但是功能集备的 Windows Service 程序。
对照上面的知识点讲解,可以很轻松地掌握 Windows Service 程序的架构。
除此之外,MSDN - CPP Windows Service 示例程序演示了 service 程序的加载、启动、停止和卸载,也可以参考。
对于 Windows service 程序来说,它除了可以设置开机自动启动外,它还有一项非常有用的特点——自动重启(Auto restart)。当 Windows service 程序意外奔溃之后,系统会自动重启该 service,可以设置重启的次数。参考:Auto restart a windows service if it crashes
ServiceMain 函数用于与 SCM 交互,一般不在其所在的线程执行 tasks,以防阻塞 service 。因此,明智的设计是在 ServiceMain 中创建新的线程执行 service tasks 任务。
Service 进程作为一种常驻内存的后台进程,一般都将它当作 watch dog 。在程序设计的时候,可以让 Service 进程在创建其他进程去执行任务,这种设计可以保持Service 进程地址空间的独立性,进而减少 service 程序的崩溃次数。