• 【原创】Windows服务管家婆之Service Control Manager


        Service Control Manager,服务控制管理器,人称SCM就是它!在Windows内核中,都可以看到她忙碌的身影,可以说是系统服务和驱动的管家婆了!


        SCM管家婆起早贪黑,每次系统启动,她也随着而起。她凭借着自己的努力,终于在Windows的内核占据了一席之地,调配着手下许多服务和驱动。SCM她到底具有什么能力呢?她是一种远程过程调用服务,为普通程序操控计算机的服务提供了一扇方便的门,这里还包括远程计算机喔。服务配置和控制程序通过调用一系列服务函数接口使用SCM的功能!
     
        说到底,SCM在内核中默默地发挥她的作用,才能受到众多服务的尊敬,那么她到底为Windows服务做了哪些好事呢?细细想来,SCM在内核中主要担任了五大任务:
    • 维护已安装服务的数据库
    • 在需要或系统启动的时候,打开服务或驱动服务
    • 枚举已安装的服务或驱动服务
    • 为已运行的服务或驱动服务维护状态信息
    • 将控制码传递给运行中的服务
    • 锁定/解锁服务数据库
        我们现在来说说SCM的看家本领,即上面的五大任务!
     
    1. 维护已安装服务的数据库
     
    1.1 什么是数据库?
     
        什么是服务的数据库?数据库?听起来那么耳熟,但其实又不是那么回事,初学者一定会和MySQL等主流的数据库混为一谈,其实不太一样。这个数据库只不过是系统注册表的一些和服务相关的数据集合而已。它的具体位置是HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServices。
     
     
        它很重要,因为它存储着已安装服务的一些信息。SCM可以在需要或系统启动的时候,负责启动已安装的服务。在启动的时候,SCM需要取得驱动的基本信息数据库为设备驱动提供了需要的入口。。同时数据库可以用于SCM或者程序来添加,删除,修改或配置服务。
        那数据库到底包含了哪些重要的内容呢?
        
    1.2 数据库内容
     
        我们从最外窥探到里面吧。
     
    1.2.1 子键名字——服务的名字
     
        先说services下的众多子键吧,什么.NET CLR Data等等不知所云的名字其实就是服务的名字,每个服务安装后都会在这个数据库里挂个名,以防SCM在启动的时候找不到它。那这个名字是谁指定,总得说清楚吧!那还要追溯到服务出生的那个夜晚...还记得CreateService这个函数吧,里面有个lpServiceName形参便是服务的名字!
     
    1.2.2 子键的内容
     
        上面是一个服务的最基本的内容,当然还有其他的。
    • Type服务类型)。对普通服务来说,它表明服务运行环境:自己进程内或与其他服务共享的进程;对于驱动服务来说,它是一个内核服务还是文件系统服务。下面是它的数字含义:0x1:驱动服务,0x2:文件系统服务;0x10:运行在自己进程的服务;0x20:与其他服务共享进程的服务
    • Start(启动类型)。它表明这是一个自启动服务呢还是一个需求启动服务,自启动又有系统引导启动或SCM启动;需求启动服务有服务配置程序手动启动。同时它也能够指示此服务是否有用,在这种情况下,服务是无法启动的。0x00:系统引导程序自动运行服务;0x01:一个由 IoInitSystem函数启动的服务;0x02:在系统启动的时候,由SCM自动运行服务;0x03:需求启动,当一个程序调用StartService函数时,SCM启动服务;0x04:一个不能启动的服务
    • ErrorControl(错误处理方式)。当该启动服务失败时产生错误的严重程度以及采取的保护措施。0x00:忽略错误;0x01-0x03:其他错误处理,参考CreateService函数。
    • ImagePath(可执行文件的完整路径)。对服务来说,后缀名是.exe;对驱动来说,后缀名是.sys。可以是相对路径,可以带有启动参数。
    • 可选的账户名字和密码(对服务),服务程序运行在此账户的上下文环境,当没指定任何账户时,默认是LocalSystem账户
    • 可选的驱动对象名字(对驱动),名字在IO系统中会被用来加载驱动设备。如果没有指定名字,IO系统会根据驱动服务名字生成一个默认的名字。
    • DependOnService(可选的依赖信息)。对于服务,信息可以是一个服务列表,在SCM启动此服务前,必须先启动列表中指定的服务。对于驱动,信息可以是一个驱动列表,在SCM启动此服务前,必须先启动列表中指定的驱动。
    • Group(服务组)。如果服务没有这一项,那么它不属于任何一个服务组,系统则会默认的将其在所有的服务启动后加载。
    • Tag(标签)。它用来描述服务的标识。每一个在服务组中的服务都会被分配一个唯一的标识。注册表通过对服务组的服务标识的排列来安排,同一服务组中的个服务的加载先后顺序
     
    2. 打开服务或驱动服务
     
        SCM管理着服务的启动,当然启动对于我们使用者来说分为自动启动和手动启动,这两种启动都是由SCM亲自启动的。还记得上面所讲的启动类型吗?在系统启动的时候,SCM随着而起,她要干活了。她首先要读取数据库中服务的启动信息,根据启动类型,打开服务!
     
    2.1 自启动服务
     
        在系统启动的时候,SCM会启动所有自启动类型的服务和驱动,包括这些服务和驱动所依赖的其他服务和驱动。
     
    2.2 延迟的自启动服务
     
        一个自启动服务可以通过ChangeServiceConfig2 函数的SERVICE_CONFIG_DELAYED_AUTO_START_INFO形参被配置为延迟启动,当系统重启后,改变才会生效。
     
    2.3 修改服务启动顺序
     
        对于自启动的服务,如果没有人告诉或提醒该先启动哪一项,SCM她会稀里糊涂的,最终导致服务启动顺序紊乱,例如A需要先在B前面启动,但是偶然的机会,SCM先启动了B那么会导致系统错误!
     
    2.3.1 Group服务组
        操作系统提供一种服务组的机制,这个机制由两部分组成;一个是服务组列表,另一个是Tag标志。这个机制是怎么运作的呢?
        注册表:HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlServiceGroupOrder的List值存放的是服务组的启动顺序。最前面的服务组最先启动。
        一个服务可以通过上面的第一点的Group值指定它是属于哪个服务组的,这样不同服务组的不同服务便有不同的启动顺序。如果服务没有这一项,那么它不属于任何一个服务组,系统则会默认的将其在所有的服务启动后加载。
        但是属于同一个服务组的启动顺序该由谁决定呢?
        注册表:HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlGroupOrderList存放的是所有服务组的信息。
     
        
     
        每个服务组信息都被保存为了一个REG_BINARY类型的值,为了方便查看,我们把它分为几部分来看:
     
        02 00 00 00 | 01 00 00 00 | 02 00 00 00
     
        第一个字段表示该服务组存在两个服务,一个是Tag值为01的服务,一个是Tag值为02的服务,从左到右顺序加载不同Tag值的服务。
        关于Tag标志,可以查看第一点的Tag标签,它是一个服务的基本内容之一。
        这样便解决服务的启动顺序问题!
     
    2.4 确认程序  
     
        当启动完成的时候,系统执行启动确认程序(由注册表的HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControl中的BootVerificationProgram键指定,默认情况下,这个值是没有的。)。当第一个用户登录后,系统会简单地报告启动成功。可以单独提供一个启动确认程序来检查系统问题和报告启动状态给SCM,使用 NotifyBootConfigStatus 函数。
     
    2.5 传说中的LKG——last-known-good

        当系统成功启动后,系统就克隆保存一份数据库备份,作为last-known-good(LKG)配置。如果当前使用的数据库导致系统启动失败,那么可以用备份来恢复。备份的数据库就保存在:HKEY_LOCAL_MACHINESYSTEMControlSetXXXServices 中。
     
        其中XXX值也被保存在:HKEY_LOCAL_MACHINESystemSelectLastKnownGood 中。
     
        如果自动启动的服务自动的时候得出SERVICE_ERROR_CRITICAL错误,SCM就会重新启动机器,并使用LKG的配置,如果LKG的配置已经被使用了,启动就会失败。
     
        注册表中服务的ErrorControl值表示SCM如何处理服务错误。如果值为SERVICE_ERROR_IGNORE(0)或者没有指定,SCM只忽略错误并继续服务的启动,如果为SERIVCE_ERROR_NORMAL(1),就在事件日志中记录下错误原因。如果错误控制为SERIVCE_ERROR_SEVERE(2)或者SERIVCE_ERROR_CRITICAL(3),服务就报告启动错误。SCM记录事件日志,并调用函数ScreverToLastKnownGood,将系统注册配置切换到LKG的版本,然后调用NtShutDownSystem重新启动系统。如果系统已经使用LKG版本,就直接重新启动。
     
    2.5.1  LKG版本的产生
     
        LKG版本的产生:SCM在系统启动阶段启动了所有自起服务之后,需要来决定这个LKG配置。缺省情况下,一次成功的启动包括所有服务的成功启动和一个用户的登录。如果在启动服务阶段存在服务的SERIVCE_ERROR_SEVERE(2)或者SERIVCE_ERROR_CRITICAL(3)错误,那么这就是失败的启动。如果SCM成功完成服务的启动,当有用户登录的时候,Winlogon调用NotifyBootConfigStatus函数发送消息给SCM。在成功启动所有服务,并且收到NotifyBootConfigStatus的登录信息,SCM就调用NtInitializeRegistry保存当前的启动配置信息。
     
    2.6 手动启动服务
     
     
     
        通过服务控制面板启动服务,或者通过调用StartService函数启动服务;两者都可以为服务指定参数。
        当服务启动时,SCM将会执行下列步骤:
    • 检索取回存储在数据库的账户信息
    • 登录服务的账户
    • 加载用于预置文件
    • 创建处于挂起状态的服务
    • 将登录令牌分配给程序
    • 允许程序运行
    3. SCM句柄
     
        SCM支持句柄类型来操作以下对象:
    • 已安装服务的数据库
    • 一个服务
    • 数据库锁
        一个SCManager 对象代表已安装服务的数据库。它是一个存储服务对象的对象容器。 OpenSCManager 能够取得一个SCManager 对象的句柄。这个句柄用于安装,删除,打开,枚举服务,以及锁定服务数据库。
        一个服务对象代表一个已安装的服务。 CreateService 和OpenService 函数返回一个已安装服务的句柄。
        OpenSCManager ,CreateService 和OpenService函数能够获取不同的权限,允许或拒绝请求权限取决于运行程序的权限令牌和与SCManager或服务对象相关的安全描述符。
        当你不需要这些句柄时,确保使用CloseServiceHandle 函数关闭此句柄
     
    4. 将控制码传递给运行中的服务——ControlService
     
        SCM这个人做事比较专一,在还未完成一件事前,她绝不会去接收另一件事。她是以串行的方式处理服务控制通知。
        当有客户端通过ControlService函数向指定的服务发送一个控制码,如果SCM她正好有空,她会先接收这个控制码,然后她会判断指定的服务是否已经处于控制码可以发送给它的状态或者指定服务已经指定要接收控制码了,好这时服务已经接收了控制码。但是SCM这个人做事比较专一,有始有终。在得到服务的反馈之前,任务还未完成。她会等待服务完成一个服务控制通知的处理,才发送下一个通知,这个时候SCM很忙,她不会接收处理了。
        所以如果任何服务正忙于处理一个控制码,ControlService的一个调用将会暂停30s的时间(这是一个超时时间),如果超时时间一过但处理还没完成,那么函数将返回一个ERROR_SERVICE_REQUEST_TIMEOUT。
        和ControlService一样,如果其他服务正忙于处理控制码,StartService也有一个超时时间30s。如果忙于处理控制码的服务没有在30s内从它的处理函数返回,那么StartService将调用失败,返回ERROR_SERVICE_REQUEST_TIMEOUT,这是因为SCM一次只能处理一个服务控制通知。
     
    5. 枚举已安装或已运行的服务或驱动服务
     
        SCM管理着系统上上下下许多的服务,包括驱动。对于来自应用层的访问,她提供有固定的接口。对于枚举或获取服务和驱动的相关信息,SCM提供了EnumServicesStatusEx函数,甚至还可以通过EnumDependentServices来获取服务或驱动的依赖关系。当然,如果只是想获得服务的名字,只要简单地调用GetServiceKeyNameGetServiceDisplayName即可。
     
  • 相关阅读:
    drf 02 APIView的请求生命周期及各个模块源码分析和配置
    drf 03 序列化模块
    Django-restframework框架01
    javascript深入理解闭包(转)
    从经典问题来看 Copy 方法(转)
    JavaScript要点(七) 函数调用
    关于iOS中SQLITE句柄的使用的细节
    Xcode8 创建NSManageObject subclass方法
    JavaScript要点 (六) 函数参数
    JavaScript要点 (五) 函数定义
  • 原文地址:https://www.cnblogs.com/cposture/p/4721139.html
Copyright © 2020-2023  润新知