Windows服务之Service Program

来源:互联网 发布:安史之乱 知乎 编辑:程序博客网 时间:2024/05/21 08:49

Service Programs


Service Program 包括了服务的可执行代码,有两种类型,分别是:
  • SERVICE_WIN32_OWN_PROCESS,一个单独的进程,承载一个服务。
  • SERVICE_WIN32_SHARE_PROCESS ,一个单独的进程,承载多个服务。
svchost.exe就是典型的Service Program,但svchost是系统自用的,开发者则需要实现自己的host。
Service Program既可以在内建账户的上下文环境下运行,也可以在可信任的域内运行,还可以指定在某个特定的用户下运行。

SCM规定了一个Service Program必须包含3个接口:(仅限于普通服务,驱动另说)
  • 服务入口。
  • 服务的主函数。
  • 服务控制的回调函数。

服务入口

服务一般都是控制台程序,入口就是main函数,main函数从注册表的ImagePath键值获取参数。

当SCM启动service program后,它就等着service program调用StartServiceCtrlDispatcher函数,对于SERVICE_WIN32_OWN_PROCESS 的服务,需要立即调用StartServiceCtrlDispatcher函数。对于SERVICE_WIN32_SHARE_PROCESS 的服务,可能有一些总体的初始化之后才调用,但不能超过30秒,如果达不到这个30秒的要求,只能创建另外的线程来做初始化的工作,而主线程调用StartServiceCtrlDispatcher函数。

StartServiceCtrlDispatcher函数调用成功后,线程并不直接返回,而是等进程进入SERVICE_STOPPED 后才返回。

SCM通过命名管道给这个线程发送控制请求,而这个线程则扮演控制调度器的角色,控制调度器要做的事情是:

  • 当服务启动时创建一个新的线程来调用合适的入口函数。
  • 当有服务控制请求时去调用合适的回调函数。

服务的主函数

当服务控制程序请求运行一个新的服务时,SCM启动这个新服务并且发一个“启动”请求给控制调度器。控制调度器创建一个新的线程来执行ServiceMain函数。而ServiceMain函数应该做下面这些事:

  • 初始化全局变量。
  • 立马调用RegisterServiceCtrlHandler函数去注册一个回调函数,用于处理一些服务的请求。
  • 初始化其他变量,如果需要的时间很短,就直接在ServiceMain里搞定。如果时间很长,应该用SetServiceStatus函数设置服务状态为SERVICE_START_PENDING,以保证服务在准备好之前SCM不发送控制请求。
  • 初始化完成后,用SetServiceStatus函数设置服务状态成:SERVICE_RUNNING 。
  • 执行服务的具体任务,完成后返回调用者。
  • 如果没有初始化和运行错误,如果清理工作时间很长,应该使用SetServiceStatus函数设置服务状态成SERVICE_STOP_PENDING ,完成后再设置成SERVICE_STOPPED 。

服务控制的回调函数

每一个服务都有一个回调函数,用于接收服务控制程序发送的控制请求。
服务调用RegisterServiceCtrlHandler或RegisterServiceCtrlHandlerEx函数注册回调函数。

服务状态的切换


服务要负责向SCM汇报服务当前的状态。服务控制程序和系统只能通过SCM获取服务的状态信息,所以服务务必要正确地汇报服务的状态。汇报服务状态的函数是SetServiceStatus。

服务的初始状态是SERVICE_STOPPED,当SCM启动服务时,状态是SERVICE_START_PENDING,用于做初始化的工作。完成初始化后,状态是SERVICE_RUNNING ,这也表示服务启动成功,否则表示启动失败。

只有某些状态间的切换是有效的,如下图:


服务的状态决定了SCM怎么与服务进行交互,比如状态是SERVICE_STOP_PENDING的话,SCM就认为服务正在结束,而不转发任何控制请求。那么下一个状态只能是SERVICE_STOPPED ,因上它只能从SERVICE_STOP_PENDING转换才有效。

下图是状态转换的更多细节。只有当服务指定了accept的话,SCM才发送控制请求。所以某个服务可能不会收到下图中所有的请求。


服务中的多线程

SCM通过发送服务控制事件给服务控制器的回调来控制一个服务。服务必须及时响应控制事件,这样SCM就能准确地跟踪的状态。
基于服务和SCM间这样的通信机制,必须小心多服务中的多线程,当服务从SCM得到指示需要停止时,必须等待所有的线程退出后才能汇报服务已停止的状态。否则,SCM会不知道服务的状态而导致服务停止失败。


如果在所有线程退出之前就将服务已经停止的状态汇报给SCM的话,可能导致服务不能关闭或直接重启。

服务和注册表

服务不能访问HKEY_CURRENT_USER 和 HKEY_CLASSES_ROOT,如果强行访问会失败。