首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Windows 服務程式設計深度解析(二):服務啟動與控制機制

     編輯:關於C++

服務程式進入點與服務表結構

在上一篇文章中,我們概略介紹了 Windows 服務的基本架構。本文將深入探討服務程式內部的運作細節。服務程式的進入點函數(通常是 main 或 WinMain)的核心任務是初始化一個 SERVICE_TABLE_ENTRY 結構陣列,這個陣列記錄了該程式所包含的所有服務名稱及其對應的 ServiceMain 回呼函數位址。

SERVICE_TABLE_ENTRY 範例

SERVICE_TABLE_ENTRY service_table_entry[] =
{
    { "MyFTPd", FtpdMain },
    { "MyHttpd", Httpserv },
    { NULL, NULL },
};

陣列中的每個元素包含兩個成員:第一個是服務名稱,第二個是該服務的 ServiceMain 函數指標。最後一個 {NULL, NULL} 標記陣列結束。上述範例定義了兩個服務:MyFTPd 和 MyHttpd。

啟動服務控制調度器

初始化完成後,將陣列位址傳遞給 StartServiceCtrlDispatcher 函數:

BOOL StartServiceCtrlDispatcher(
    LPSERVICE_TABLE_ENTRY lpServiceStartTable
);

此函數會通知服務控制管理器(SCM)目前進程包含的服務。SCM 啟動服務進程後,會等待主執行緒呼叫此函數。若在兩分鐘內未呼叫,SCM 會以 TerminateProcess 強制終止進程,因此主執行緒必須儘快呼叫它。

StartServiceCtrlDispatcher 不會立即返回,而是進入一個循環,為陣列中每個非空元素建立一個新執行緒,分別執行對應的 ServiceMain。在循環中,它會等待兩類事件:

  1. 控制通知:SCM 發送控制碼(如停止、暫停)給某服務時,該執行緒喚醒並呼叫該服務的 CtrlHandler 處理,處理完畢後回到等待狀態。
  2. 服務執行緒結束:若某服務執行緒終止,進程內運行的服務計數遞減。計數歸零時,函數返回進入點以便進行進程級清理並退出;否則繼續循環等待。

ServiceMain 函數的關鍵職責

ServiceMain 原型雖接受參數,但通常忽略。服務設定最好存放於登錄檔:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ServiceName\Parameters

外部應用程式修改設定時,服務可用 RegNotifyChangeKeyValue 即時感知並重新配置。

根據 MSDN,ServiceMain 必須立即執行兩項關鍵操作:

  1. 呼叫 RegisterServiceCtrlHandler 登錄控制處理函數。
  2. 呼叫 SetServiceStatus 回報初始化狀態。

原因在於 SCM 設有啟動逾時機制(Windows NT 4.0 為 80 秒),若超時未完成初始化,SCM 會判定啟動失敗。

註冊控制處理函數

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
    LPCTSTR lpServiceName,      // 服務名稱
    LPHANDLER_FUNCTION lpHandlerProc // CtrlHandler 函數位址
);

參數 lpServiceName 必須與 SERVICE_TABLE_ENTRY 中的名稱一致。函數返回 SERVICE_STATUS_HANDLE(32 位元控制碼),SCM 用它唯一識別服務,後續狀態回報必須攜帶此控制碼。SCM 要求在 1 秒內完成註冊,否則視為啟動失敗(NT 4.0 不會終止進程但回報錯誤,Windows 2000 已修正此行為)。

回報服務狀態:SERVICE_STATUS 結構

註冊成功後,必須立即呼叫 SetServiceStatus 告知 SCM 服務正在初始化:

BOOL SetServiceStatus(
    SERVICE_STATUS_HANDLE hService,
    LPSERVICE_STATUS lpServiceStatus
);

SERVICE_STATUS 結構包含七個成員,必須正確初始化:

typedef struct _SERVICE_STATUS {
    DWORD dwServiceType;            // 服務類型
    DWORD dwCurrentState;           // 當前狀態
    DWORD dwControlsAccepted;       // 接受的控制碼
    DWORD dwWin32ExitCode;          // Win32 錯誤碼
    DWORD dwServiceSpecificExitCode;// 服務專用錯誤碼
    DWORD dwCheckPoint;             // 進度檢查點
    DWORD dwWaitHint;               // 預估等待時間(毫秒)
} SERVICE_STATUS, *LPSERVICE_STATUS;
  • dwServiceType:單一服務進程用 SERVICE_WIN32_OWN_PROCESS,多服務共用進程用 SERVICE_WIN32_SHARE_PROCESS;需與桌面互動時加上 SERVICE_INTERACTIVE_PROCESS。此值生命週期內不可變。
  • dwCurrentState:最重要成員。初始化階段設為 SERVICE_START_PENDING。
  • dwControlsAccepted:指定接受的控制通知,可組合 SERVICE_ACCEPT_STOP、SERVICE_ACCEPT_PAUSE_CONTINUE、SERVICE_ACCEPT_SHUTDOWN 等。
  • dwWin32ExitCode / dwServiceSpecificExitCode:錯誤回報機制。正常時設 NO_ERROR;需回報 Win32 錯誤時填寫前者;服務專用錯誤時將前者設為 ERROR_SERVICE_SPECIFIC_ERROR 並在後者填入自訂碼。
  • dwCheckPoint / dwWaitHint:用於回報漫長初始化的進度。初始化時 dwCheckPoint=0,dwWaitHint 設為合理預估毫秒數。每次呼叫 SetServiceStatus 可遞增 dwCheckPoint 指示步驟。初始化完成後,將 dwCurrentState 改為 SERVICE_RUNNING,並將兩者歸零。

服務主迴圈與結束流程

初始化完成並回報 SERVICE_RUNNING 後,服務通常進入一個循環,掛起等待網路請求或控制通知。收到請求時喚醒處理,處理完畢繼續等待。

若收到停止或關機通知,服務執行緒跳出迴圈,執行清理工作並返回。ServiceMain 返回導致執行緒終止,StartServiceCtrlDispatcher 內的等待執行緒喚醒,遞減服務計數,進入上述的結束邏輯。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved