Service Control Manager,服務控制管理器,人稱SCM就是它!在Windows內核中,都可以看到她忙碌的身影,可以說是系統服務和驅動的管家婆了!
SCM管家婆起早貪黑,每次系統啟動,她也隨着而起。她憑借着自己的努力,終於在Windows的內核占據了一席之地,調配着手下許多服務和驅動。SCM她到底具有什么能力呢?她是一種遠程過程調用服務,為普通程序操控計算機的服務提供了一扇方便的門,這里還包括遠程計算機喔。
服務配置和控制程序通過調用一系列服務函數接口使用SCM的功能!
說到底,SCM在內核中默默地發揮她的作用,才能受到眾多服務的尊敬,那么她到底為Windows服務做了哪些好事呢?細細想來,SCM在內核中主要擔任了五大任務:
- 維護已安裝服務的數據庫
- 在需要或系統啟動的時候,打開服務或驅動服務
- 枚舉已安裝的服務或驅動服務
- 為已運行的服務或驅動服務維護狀態信息
- 將控制碼傳遞給運行中的服務
- 鎖定/解鎖服務數據庫
我們現在來說說SCM的看家本領,即上面的五大任務!
1. 維護已安裝服務的數據庫
1.1 什么是數據庫?
什么是服務的數據庫?數據庫?聽起來那么耳熟,但其實又不是那么回事,初學者一定會和MySQL等主流的數據庫混為一談,其實不太一樣。這個數據庫只不過是系統注冊表的一些和服務相關的數據集合而已。它的具體位置是HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services。


它很重要,因為它存儲着已安裝服務的一些信息。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_MACHINE\SYSTEM\CurrentControlSet\Control\ServiceGroupOrder的List值存放的是服務組的啟動順序。最前面的服務組最先啟動。
一個服務可以通過上面的第一點的Group值指定它是屬於哪個服務組的,這樣不同服務組的不同服務便有不同的啟動順序。
如果服務沒有這一項,那么它不屬於任何一個服務組,系統則會默認的將其在所有的服務啟動后加載。
但是屬於同一個服務組的啟動順序該由誰決定呢?
注冊表:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GroupOrderList存放的是所有服務組的信息。

每個服務組信息都被保存為了一個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_MACHINE\SYSTEM\CurrentControlSet\Control
中的BootVerificationProgram鍵指定,默認情況下,這個值是沒有的。)。當第一個用戶登錄后,系統會簡單地報告啟
動成功。可以單獨提供一個啟動確認程序來檢查系統問題和報告啟動狀態給SCM,使用 NotifyBootConfigStatus 函數。
2.5 傳說中的LKG——last-known-good

當系統成功啟動后,系統就克隆保存一份數據庫備份,作為last-known-good(LKG)配置。如果當前使用的數據庫
導致系統啟動失敗,那么可以用備份來恢復。備份的數據庫就保存在:
HKEY_LOCAL_MACHINE\SYSTEM\ControlSetXXX\Services 中。
其中XXX值也被保存在:
HKEY_LOCAL_MACHINE\System\Select\LastKnownGood 中。
如果自動啟動的服務自動的時候得出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
來獲取服務或驅動的依賴關系。當然,如果只是想獲得服務的名字,只要簡單地調用
GetServiceKeyName
或
GetServiceDisplayName
即可。