4. 服务控制程序
当您编写了一个服务时,同时也会建立一个应用程序以让管理员用来控制该服务。这个管理用的应用程序应该经由使用在本章中所提之各种函数来控制服务。这个应用程序也应该帮助管理者处理其他方面的服务,例如设定它(在第五章中叙述)与在Active Directory中发布它。在观念上,此管理应用程序实现了一个像在Microsoft Management Console(MMC)中的嵌入式管理单元或是一个以网路为基础的主控台。
与一个机器的SCM通讯之第一个步骤是呼叫OpenSCManager:
SC_HANDLE OpenSCManager(
PCTSTR pszMachineName,
PCTSTR pszDatabaseName,
DWORD dwDesiredAccess);
这个函数建立了一个与机器上之SCM通讯的通道,它经由pszMachineName参数指定,并传递NULL去开启在本端机器上的SCM。pszDatabaseName参数会确认应该开始哪一个资料库;您应该只是传递SERVICES_ACTIVE_DATABASE或NULL二者之一给此参数。dwDesiredAccess参数告诉函数您想要处理的SCM资料库。表4-1说明了有哪些存取权限可供使用。
| 表4-1 用来指定存取SCM之OpenSCManager的dwDesiredAccess参数存取权限值 |
| 存取权限 | 说明 |
|---|---|
| SC_MANAGER_ALL_ACCESS | 除了STANDARD_RIGHTS_REQUIRED外,还包括了所有列在本表中的存取类型。 |
| SC_MANAGER_CONNECT | 允许连接至SCM。即使没有明白指定,然而这个存取经常是被隐含在内的。 |
| SC_MANAGER_CREATE_ SERVICE | 呼叫CreateService,以将一个服务加入SCM资料库中。 |
| SC_MANAGER_ENUMERATE_SERVICE | 呼叫EnumServicesStatus,以取得在SCM资料库中的服务清单与每一个服务的状态。 |
| SC_MANAGER_LOCK | 呼叫LockServiceDatabase,以停止从SCM启动的任何更多的服务。 |
| SC_MANAGER_QUERY_ LOCK_STATUS | 呼叫QueryServiceLockStatus,以找出哪一些使用者被锁定在SCM资料库中。 |
Windows提供了下列所示之预设存取,以使SCM变得较安全:
- 管理者对SCM拥有完全的存取权。
- 本机与Everyone拥有对SCM之SC_MANAGER_CONNECT、SC_MANAGER_ENUMERATE_SERVICE与SC_MANAGER_QUERY_ LOCK_STATUS的存取权。
OpenSCManager回传了一个SC_HANDLE让您将它传递至另一个函数中,以便您可以操作SCM的资料库。当您完成了对SCM资料库的存取时,您必须将它传递给CloseServiceHandle,以关闭此handle:
BOOL CloseServiceHandle(SC_HANDLE hSCManager);
增加一个服务至SCM资料库
大部份要操作SCM资料库的原因之一即是增加一个服务。为了要增加一个服务,您必须呼叫OpenSCManager,并指定SC_MANAGER_CREATE_SERVICE之存取,然后再呼叫CreateService:
SC_HANDLE CreateService(
SC_HANDLE hSCManager,
PCTSTR pszServiceName, //内部的、计划性的字串名称
PCTSTR pszDisplayName,
DWORD dwDesiredAccess,
DWORD dwServiceType,
DWORD dwStartType,
DWORD dwErrorControl,
PCTSTR pszPathName,
PCTSTR pszLoadOrderGroup,
PDWORD pdwTagId, // 若为服务的话,通常为0
PCTSTR pszDependencies, // 使0终止的字串增加一倍
PCTSTR pszUserName,
PCTSTR pszUserPswd);
如您所见,CreateService需要相当多的参数(正确数目为13个)。hSCManager参数为由OpenSCManager回传的handle。接下来的二个参数pszServiceName和pszDisplayName指示了服务的名称。服务拥有一个内部的名称供程序设计者使用,以及一个给使用者看的显示名称。经由pszServiceName可以确定内部名称,它被SCM用来储存在登录内部的服务资讯。举例来说,Logical Disk Manager服务拥有一个内部名称「dmserver」,而且它的服务资讯可以在以下所示的登录机码中找到:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\dmserver
CreateService的dwDesiredAccess参数是有用的,因为它会告诉SCM在CreateService返回一个handle给它后,您打算如何处理刚被安装的服务(所以您可以正确的操作该服务)。如果您只安装了一个服务并且在它被安装后不打算操作它,那么只需简单地传递0给dwDesiredAccess,然后立即关闭经由呼叫CloseServiceHandle而被CreateService回传的handle即可。表4-2中显示了当您使用CreateService时可指定给dwDesiredAccess的存取权限。
| 表4-2 CreateService的dwDesiredAccess参数的存取权限值,它可用来指定被增加至SCM资料库的服务之存取动作 |
| 存取权限 | 说明 |
|---|---|
| SERVICE_START | 能呼叫StartService以启动服务。 |
| SERVICE_STOP | 能呼叫ControlService以停止服务。 |
| SERVICE_PAUSE_CONTINUE | 能呼叫ControlService以暂停与继续执行服务。此存取 也允许改变一个服务的参数。 |
| SERVICE_INTERROGATE | 能呼叫ControlService以询问服务并立即回报它的状 态。 |
| SERVICE_USER_DEFINED_ | 能呼叫ControlService以指定一个经使用者定义的控制 CONTROL 码。 |
| SERVICE_QUERY_STATUS | 能呼叫 QueryServiceStatus(Ex) 函数以询问服务控制管 理员有关服务的状态。 |
| SERVICE_ENUMERATE_DEPENDENTS | 能呼叫EnumDependentServices以依据服务而将所有的 服务列举出来。 |
| SERVICE_CHANGE_CONFIG | 能呼叫ChangeServiceConfig(2) 以改变服务的设定。 |
| SERVICE_QUERY_CONFIG | 能呼叫QueryServiceConfig(2) 以询问服务的设定。 |
| DELETE | 能呼叫DeleteService以删除服务。 |
CreateService 函数一个参数可接受一个SECURITY_ATTRIBUTES结构的指标。所以当一个新的服务被安装至SCM资料库时,SCM会对该服务设定预设之安全性。您可以使用QueryServiceObjectSecurity与SetServiceObjectSecurity函数来改变这些安全性设定。它们为SCM为服务设定的预设安全性:
- Administrators与System Operators拥有SERVICE_CHANGE_CONFIG、SERVICE_ENUMERATE_DEPENDENTS、SERVICE_INTERROGATE、SERVICE_ PAUSE_CONTINUE、SERVICE_QUERY_CONFIG、SERVICE_QUERY_STATUS、SERVICE_START、SERVICE_STOP、SERVICE_USER_DEFINED_CONTROL、READ_CONTROL、WRITE_OWNER、WRITE_DAC与DELETE以存取服务。
- 本机帐户拥有 SERVICE_ENUMERATE_DEPENDENTS、SERVICE_INTERROGATE 、SERVICE_PAUSE_CONTINUE、SERVICE_QUERY_CONFIG、SERVICE_QUERY_STATUS、SERVICE_START、SERVICE_STOP、SERVICE_ USER_DEFINED_CONTROL 与 READ_CONTROL 以存取服务。
- 经过验证的使用者拥有 SERVICE_ENUMERATE_DEPENDENTS、SERVICE_ INTERROGATE、SERVICE_QUERY_CONFIG、SERVICE_QUERY_STATUS、SERVICE_USER_DEFINED_CONTROL 与 READ_CONTROL 以存取服务。
- 在Windows 2000 Professional与Windows 2000 Server中,Power Users拥有SERVICE_QUERY_CONFIG、SERVICE_QUERY_STATUS、SERVICE_ ENUMERATE_DEPENDENTS、SERVICE_INTERROGATE、SERVICE_START、SERVICE_STOP、SERVICE_PAUSE_CONTINUE、SERVICE_USER_ DEFINED_CONTROL与READ_CONTROL以存取服务。
dwServiceType参数告诉系统哪一些可执行档中包含了一个或多个服务。当可执行档中实行单一的服务时,传递SERVICE_WIN32_OWN_PROCESS;而当可执行档中实行了二或多个服务时,则传递SERVICE_WIN32_ SHARE_PROCESS。如果您想要让服务在一个处理程序中与使用者的桌面互动时,您也可以结合SERVICE_INTERACTIVE_PROCESS标记与SERVICE_WIN32_ OWN_PROCESS或SERVICE_WIN32_SHARE_PROCESS二者之一。
说明
在SERVICE_WIN32_SHARE_PROCESS服务之可执行档中,如果该服务要求使用SERVICE_INTERACTIVE_PROCESS标记时,所有的服务即必须使用这个标记。当系统第一次启动该服务时,服务的设定会决定哪一个处理程序被允许与桌面互动。
dwStartType参数告诉系统服务应该在何时被启动。当机器开机时,会有一个SERVICE_AUTO_START值指示SCM启动服务以及SERVICE_DEMAND_START值会指示系统不启动服务。管理员则可以手动地启动服务。此外,SERVICE_ DEMAND_START可指定一个服务为要求启动的服务,它告诉SCM如果管理员试图去启动一个服务时,便自动地启动服务。接下来会讨论更多有关服务的依存关系。一个SERVICE_DISABLED值可防止由系统完全地启动服务。
服务在系统中是一个非常重要的部份,所以系统需要知道如果服务启动失败时,它应该做什么。这个指令是dwErrorControl参数的工作。传递一个SERVICE_ERROR_IGNORE或SERVICE_ERROR_NORMAL以告诉系统在系统之事件记录中记录服务的错误以及继续启动系统。这二个控制码的不同之处是设定为SERVICE_ERROR_NORMAL时系统会显示一个讯息方块以通知使用者服务启动失败。设定为要求启动的服务应该指定为SERVICE_ERROR_IGNORE。
当服务启动失败时,SERVICE_ERROR_SEVERE与SERVICE_ERROR_CRITICAL会告诉系统中止启动它。当一个服务启动失败并且被指定为这些控制码之一时,系统会在系统之事件记录中记录此错误情形,并且自动地使用已知上次为良好的设定重新开机。如果系统以已知上次为良好的设定开机后,有一个服务在启动失败时的错误控制码为SERVICE_ERROR_SEVERE,则系统会继续开机。若有一个服务之启动失败的错误控制码为SERVICE_ERROR_CRITICAL,则系统也会中止已知上次为良好设定的开机动作。
CreateService的pszPathName参数指示了包含一个或多个服务之可执行档的完整路径。许多服务文件被安装在 \WINNT\System32目录中,但是您可以将服务之可执行档放置在文件系统的任何一个地方。
现在我们到达服务之依存关系的议题。简单地说,一个服务就像作业系统的一部份,而且除非他们知道另一个系统的一部份已经先执行,否则有很多服务不会正确地工作。当系统开机时,它会依照一个规定服务之启动顺序的演算法来执行。Microsoft将系统服务划分为一个事先已定义的群组中,列示如下:
- 系统保留的
- 启动汇流排延长器
- 系统汇流排延长器(支援PCMCIA)
- SCSI miniport(SCSI设备驱动程序)
- 通讯埠
- 主要的磁盘(软碟机/硬盘机驱动程序)
- SCSI类别(SCSI驱动程序)
- SCSI CDROM类别(CD-ROM驱动程序)
- Filter(CD设备)
- 启动文件系统(快速的FAT驱动程序存取)
- Base(系统哔哔声)
- 指标通讯埠(支援滑鼠)
- 键盘通讯埠(支援键盘)
- 指标类别(支援多个滑鼠)
- 键盘类别(支援多个键盘)
- Video Init(支援影像)
- 影像(支援影像晶片)
- 影像储存(支援多个影像)
- 文件系统(支援CD-ROM与NTFS文件系统)
- 事件记录(支援事件记录)
- 资料流驱动程序
- NDIS包装函数
- PNP_TDI(支援NetBT与TCP/IP)
- NDIS(支援网路)
- TDI(支援AFD网路与DHCP)
- NetBIOSGroup(支援NetBIOS)
- PlugPlay
- SpoolerGroup(支援列印集区)
- NetDDEGroup(支援网路DDE)
- 并列仲裁器(支援并列埠)
- Extended base(支援数据机、序列与并列)
- 远端验证RemoteValidation(支援net logon)
- PCI设定
- MS处理
您也可以在以下所示之登录子机码中找到这个清单:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ServiceGroupOrder
就像系统开机一样,它会反覆地执行这个清单、载入任何的设备驱动程序以及属于每一个部份的服务。例如,在载入属于SCSI miniport群组之设备驱动程序与服务前即载入包含在系统保留群组中的所有设备驱动程序与服务。
当您加入一个服务至SCM资料库中时,可以经由在CreateService的pszLoadOrderGroup参数中传递群组的名称,指定它为上述之已定义群组清单中的一项。通常,您的服务不需要在系统开机周期即载入,而是应该在所有群组设备与服务开始启动并执行后才载入执行。为了确定您的服务在所有的系统之关键设备驱动程序与服务之后才载入执行,您只需简单地在pszLoadOrderGroup参数中传递NULL即可。
如果您增加了一个设备驱动程序至SCM中(相对于一个服务),则可以在经由指定一个标签ID,在建立驱动程序的启动时间取得更多的资料点(Granularity)。服务无法利用这个额外的资料点,并且必须总是传递NULL给CreateService的pdwTagId参数。若您对设备驱动程序有与趣,可参阅在Platform SDK文件与DDK文件中讨论pdwTagId参数以及二个附加的选项(SERVICE_BOOT_ START与SERVICE_SYSTEM_START)。
除了告诉SCM您的服务属于一个特定载入顺序的群组外,您还可以告诉SCM您的服务要求某些其他的服务与群组需要在您的服务可以执行前即已执行。例如,Computer Browser服务要求Workstation与Server服务必须在它可以正确地执行前即已执行,而ClipBook服务则要求Network DDE服务被执行。
指定您的服务依存于哪些服务,比表明您服务为某个群组的一部份更为有用。您会使用CreateService的pszDependencies参数来告知SCM资料库您的服务依存于哪一些服务。如果您的服务没有存在依存关系,则只需传递NULL给此参数即可。
因为您必须传递一个透过零分离的名称之双倍终止位址阵列,所以pszDependencies是一个非常奇特的参数。以另一种方式来说,pszDependencies参数必须指向一个包含终止字串与一个缓冲器尾端之额外空字元的内存区块。
所以为了建立一个依存于Workstation服务的服务(像Alerter服务即是),在传递它给CreateService前,您应先设定pszDependencies(程序代码如下所示):
// 在下面的缓冲器中以二个空字元结束
PCTSTR pszDependencies = TEXT("LanmanWorkstation\0");
CreateService(..., pszDependencies, ...);
而建立一个依存于Workstation服务与Remote Procedure Call(RPC)服务(如Messenger服务)的服务时,您将以如下的方式设定pszDependencies:
// 在下面的缓冲器中分开一个空字元的字串并以二个空字元结束
PCTSTR pszDependencies = TEXT("LanmanWorkstation\0RpcSs\0");
CreateService(..., pszDependencies, ...);
一个服务也能被一个群组而非一个单一的服务依存,但是这是非常不寻常的情形。载入次序群组上的从属意味着在一个尝试启动群组中所有成员的动作被完成后,该群组中至少有一个成员为执行状态。为了在一个pszDependencies缓冲器中指定一个群组,您必须在群组名称前加上一个特定的SC_GROUP_IDENTIFIER字元,它被定义在WinSvc.h中,如下所示:
#define SC_GROUP_IDENTIFIERW L’+’
#define SC_GROUP_IDENTIFIERA ’+’
#ifdef UNICODE
#define SC_GROUP_IDENTIFIER SC_GROUP_IDENTIFIERW
#else
#define SC_GROUP_IDENTIFIER SC_GROUP_IDENTIFIERA
#endif
因此,为了要建立一个依存于Workstation服务与TDI群组的服务,您应如以下的方式来设定pszDependencies:
// 在下面的缓冲器中指定二个依存关系:Workstation服务与TDI群组(一个「+」在TDI之前的群组)
PCTSTR pszDependencies = TEXT("LanmanWorkstation\0+TDI\0");
CreateService(..., pszDependencies, ...);
当设定pszDependencies值时,您可以依自己的方式指定许多服务与群组。只要记得在每一个服务或群组间放置一个空字元与在所有群组名称前放入一个加号,以及在结尾引用前加入一个终止之空字元。
我们现在可以开始说明CreateService之最终二个参数:pszUserName与pszUserPswd。这二个参数允许您在使用者帐户下指定哪一些服务可被执行。为了使服务在本机帐户下执行(大部份的情形),只需传递NULL给此二个参数即可。如果您想让服务在一个特定的使用者帐户下执行,则以DomainName\UserName形式来传送一个帐户名称给pszUserName参数,并传递使用者帐户的密码至pszUserPswd参数中。