Windows 服务(2) (from MSDN)

来源:互联网 发布:全球经济现状 知乎 编辑:程序博客网 时间:2024/05/21 18:30

◆服务程序
     一个服务程序包含了一个或多个服务的可执行代码.用SERVICE_WIN32_OWN_PROCESS类型创建的服务只包含了一个服务的可执行代码,而用SERVICE_WIN32_SHARE_PROCESS类型创建的服务包含了多个服务的可执行代码.服务不仅可以被配置在本地网域,主网域或者信任网域中执行,也可以运行在服务用户账户环境下.
     接下来我们将分几个部分来介绍一些服务程序必须包含的SCM接口要求,当然了这些描述部分并不适用于驱动服务.
     ● 服务入口点
     ● 服务程序的主函数
     ● 服务控制句柄函数
     我们也会顺便提到下面的知识点:
     ● 获得服务中事件
     ● 多线程服务
     ● 服务和注册
     ● 服务和重定位驱动
 
     下面我们来逐个进行介绍:

     1.服务入口点
     服务程序通常是控制台程序,控制台程序入口点是它的主函数.該函数将該服务的注册键的ImagePath值作为其输入参数,详情见CreateService函数介绍的Remark部分.
     当SCM启动一个服务程序时,它等待該程序调用StartServiceCtrlDispatcher函数,它遵循下面原则:
     ● 具有SERVICE_WIN32_OWN_PROCESS类型的服务应该在其主线程中立即调用StartServiceCtrlDispatcher.在服务开始之后你可以进行任何的初始化动作,下一段落中我们具体介绍.
     ● 如果服务具有SERVICE_WIN32_SHARE_PROCESS创建类型,并且程序中包含所有服务的公共初始化部分,那你就可以在主线程调用StartServiceCtrlDispatcher之前进行初始化,但你必须保证你的初始化过程少于30秒,否则的話,你必须在主线程调用StartServiceCtrlDispatcher时创建一个新线程进行公共的初始化过程.这还没完,当服务开始以后你还有进行服务的各自初始化过程.
StartServiceCtrlDispatcher为在该进程中的每一个服务传入SERVICE_TABLE_ENTRY结构,每一个结构中标识了服务名称和服务程序的入口点.在"如何编写服务程序主函数"段落中我们会有一个例子.
     如果StartServiceDispatcher被成功调用以后,调用线程不会立即返回直到进程中所有运行服务都进入SERVICE_STOPPED状态,SCM通过命名管道发送控制请求给这条线程.这条线程充当一个控制分发器的角色,主要执行以下任务:
     ● 当一个新服务启动后,负责创建一个新线程去调用该服务的相应的入口函数
     ● 调用合适的"handler function"去处理服务控制请求.
 
     2.服务程序的主函数
     当一个服务控制程序请求一个新服务运行时,SCM启动该服务然后发送一个开始请求给控制分发器.分发器然后创建一个新线程去执行此服务的ServiceMain函数.具体示例看"写一个ServiceMain函数".
     ServiceMain函数应该执行以下任务:
     ● 初始化所有的全局变量.
     ● 立即调用RegisterServiceCtrlHandler函数去注册一个Handler函数来处理此服务的控制请求.
     ● 开始初始化.(这里请注意初始化时间长短的不同做法,上面我们已经提到,故不重复).
     ● 当初始化完成以后,调用SetServiceStatus设置服务状态为SERVICE_RUNNING.
     ● 开始执行服务任务,或如果所有任务都完成,返回控制权给调用者.
     ● 当服务处初始化或者运行期间时,如果有如何错误发生,如果清理动作的时间比较长,服务应该调用SetServiceStatus设
     当前服务的状态为SERVICE_STOP_PENDING.当清理动作完成以后,调用SetServiceStatus设置服务状态为SERVICE_STOPPED.   

     3.服务控制句柄函数
     每个服务都有一个控制句柄,Handler函数,当服务接收到来自服务控制程序的控制请求消息时候,该函数就会被激活,但这个函数在分发控制器上下文中执行.情况"写一个控制句柄函数"段落.
     每个服务调用RegisterServiceCtrlHandler或者RegisterServiceCtrlHandlerEx函数去注册其服务控制句柄函数.
     无论何时服务控制句柄一旦被激活,服务必须调用SetServiceStatus函数将当前状态报告给SCM,不管其状态是否改变,该步骤必须被完成.
服务控制程序可以使用ControlService函数发送控制请求,所有服务必须要接受然后处理SERVICE_CONTROL_INTERROGATE控制码.通过调用SetServiceStatus函数,你可以启用或者禁用其它的控制码的可接受性,要想接收SERVICE_CONTROL_DEVICEEVENT控制码,你必须调用RegisterDeviceNotification函数.服务也可以处理其它的用户自定义控制码.
     如果一个服务接收到SERVICE_CONTROL_STOP控制码,它就必须停止等待接收其它控制消息,进入SERVICE_STOP_PENDING或者
SERVICE_STOPPED状态.当SCM送出此控制码以后,它就不会发送其它控制码了.
     控制处理程序必须在30秒之内返回,否则的话SCM就会返回一个错误.假如当服务正在执行控制程序的时候需要执行长时间的处理过程,那它应当为此处理过程启动一个新的线程来执行,让后返回控制句柄.
     当用户关闭操作系统时,所有的控制句柄那些调用了SetServiceStatus函数并以SERVICE_ACCEPT_PRESHUTDOWN控制码作参数传递就会接收到SERVICE_CONTROL_PRESHUTDOWN控制码.SCM一直等到该服务停止或者预关闭时间超时值到达.该控制码应该应该仅被使用在一些特殊的情况下,因为处理这个通知消息的服务会阻断系统关机直到此服务停止或者预关机时间超时值已经达到.
     当预关机通知消息完成了以后,所有的调用SetServiceStatus函数并且传入SERVICE_ACCEPT_SHUTDOWN参数值的控制处理句柄就会收到SERVICE_CONTROL_SHUTDOWN控制码.它们被通知到的顺序依赖于它们在已安装服务数据库的出现顺序,在默认情况下,在系统关闭之前,一个服务大概有20秒的时间去执行清理任务.这个时间过去以后,系统就会继续执行关机动作而不管服务的关闭操作是否已经完成.这里注意一下:如果系统处于关闭状态(没用重启或关掉电源),服务会继续运行的.
     如果服务要求更多的时间执行清理,除了等待提示之外,它还会发送STOP_PENDING状态消息,故服务控制器能够知道要等多久才能将服务关闭消息通知给操作系统.但不管怎么样,为了防止服务阻止了系统关机,对于服务控制器应该等多久有一个时间限制.如果服务是通过被嵌入式关闭的话,那限制的时间值就是125秒,如果关闭原因是由于操作系统重启那么有限显得时间值被以下注册键值:
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control的键值WaitToKillServiceTimeout所标识.
     这里有一点需要说明一下,在服务关闭期间,SCM并不考虑依赖关系,SCM遍历所有运行服务列表然后发送SERVICE_CONTROL_SHUTDOWN命令.但是服务可能会由于器依赖服务已经关闭而失败,要设置依赖服务的关闭顺序,请使用SetProcessShutdownParameters函数.SCM使用这个函数给了它的句柄0x1E0的优先权值.在它的控制句柄被调用时候,SCM会发送SERVICE_CONTROL_SHUTDOWN的通知消息,然后就在返回它的句柄之前等待服务去退出.

 

     4.获得服务中事件
     控制台程序服务可以注册一个控制台句柄,这样的话,当一个用户登出系统时,该服务就能够接收到通知消息,但一个交互式用户登录进来后,不会有任何控制台事件被发送出去的.关于当用户登录时接收到的通知消息的详细情况,请看"创建一个系统登录信息通知包".
     当系统设备发生改变时,系统会将这些设备变更事件以广播的形式发送给所有服务.这些事件可以被窗口程序服务或者它的服务控制句柄接收到.如果想指定那些事件可以被你的服务接收,请调用RegisterDeviceNotification函数.
     一定要保证处理拔出和播放设备事件尽可能快.否则的话,就可能引起系统不稳定.如果你的事件处理句柄将要去执行一个可能会阻断诸如I/O的操作,你最好启动一个新线程去异步处理.
     当一个服务调用了RegisterDeviceNotification,服务也会标识出一个窗口句柄或者服务状态句柄.如果一个服务标明了一个窗口句柄,窗口处理程序就会接收到通知事件消息.要是标明了服务状态句柄,它的服务控制句柄就会接收到处理事件.更详细的信息,请参考HandlerEx函数.
     你可以通过调用RegisterDeviceNotification来获得设备通知句柄,当你不再使用该句柄时,记得调用UnregisterDeviceNotification函
数来关闭该句柄.

 

     5.多线程服务
     SCM通过发送服务控制事件给服务控制句柄来控制一个服务的运行状态,服务必须要周期性地回应控制事件,只有这样SCM才能可以保持对
此服务状态的实时追踪.服务的当前状态必须与SCM接收到的状态描述相匹配.
     由于服务与SCM之间交流机制的原因,当你在服务中使用多线程时请一定要小心使用:当SCM要终止一个服务运行时,该服务必须等待一直到
服务中所有线程都退出以后才应该向SCM报告服务已经停止消息.否则的话,SCM就会对了解服务的当前实际的工作状态很困惑,这就很容易导致在关闭服务时的失败.
     当服务正在回应停止控制事件或者正在停止服务时候,你也需要去通知SCM.如果服务在前一个调用SetServiceStatus的等待提示参数时间内作出了回应,SCM就会认为你正在进行某个状态,当前的检测点将会在前一个调用SetServiceStatus的检测点值基础上增加.
     如果在所有线程退出之前服务已经停止,那当服务会将这一消息通知给SCM,SCM可能就会认为运行中出现了冲突,最终可能会导致服务进入
了一个不能被停止或者重启的状态.

 

     6.服务和注册
      一个服务不应当去访问HKEY_CURRENT_USER或者HEKY_CLASSES_ROOT,尤其在模拟一个用户进行操作的情况下.在这种情况下,你应该使用RegOpenCurrentUser或者RegOpenUserClassesRoot函数.
     如果在一个服务中,你试图去访问HKEY_CURRENT_USER或者HKEY_CLASSES_ROOT,那你就可能会失败或者会出现另外一种情况:似乎操作的结果运行很良好但是去隐藏了一个潜在的问题,由于该问题导致服务在加载或者卸载用户配置时会经常出现问题.

 

    7.服务和重定位驱动
     一个服务不能够通过映射的驱动盘符来访问本地或者网络资源.此外,服务不应该去使用操作系统管理函数来管理被映射的驱动盘符.如果有
些服务(或者任何一个运行在不同安全上下文下的进程)必须要访问远程资源的话,那就必须使用通用命名约定(Universal Naming Convention)的名称来访问这些资源.
     尽管那些WNet函数可能会成功的返回,但其导致的结果也不是你预期的.当系统建立一个重定位驱动器,那它一定是基于用户账户模式的.仅有那系统用户可以管理那些重定位驱动器.系统一直保持着对基于用户的登录"安全标识符"(SID)的跟踪.登录SID是一个唯一标识符,它标识了用户的登录会话.在系统内,一个用户同时可以有多个登录会话.
     如果一个服务被配置在一个用户账户下运行,那系统总是为该用户创建一个新的登录会话,然后在新的登录会话中启动该服务,但一个服务对那些在其它用户会话中建立的映射驱动器进行管理.

原创粉丝点击