玩轉Windows服務系列——服務運行、停止流程淺析


通過研究Windows服務注冊卸載的原理,感覺它並沒有什么特別復雜的東西,Windows服務正在一步步退去它那神秘的面紗,至於是不是美女,大家可要睜大眼睛看清楚了。

接下來研究一下Windows服務的啟動和停止的流程。

啟動流程

啟動時自然是從程序的入口點開始

extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, 
                                LPTSTR /*lpCmdLine*/, int nShowCmd)
{
    //這里是程序的入口點,直接調用了ATL框架中的CServiceModuleT類的WinMain方法
    return _AtlModule.WinMain(nShowCmd);
}

接下來進入_AtlModule.WinMain查看細節。

//處理命令行參數后,開始啟動
if (pT->ParseCommandLine(lpCmdLine, &hr) == true)
    hr = pT->Start(nShowCmd);

WinMain方法中,主要是對命令行參數進行處理后,調用Start方法進行啟動。

HRESULT Start(_In_ int nShowCmd) throw()
{
    T* pT = static_cast<T*>(this);
    // Are we Service or Local Server
    CRegKey keyAppID;
    LONG lRes = keyAppID.Open(HKEY_CLASSES_ROOT, _T("AppID"), KEY_READ);
    if (lRes != ERROR_SUCCESS)
    {
        m_status.dwWin32ExitCode = lRes;
        return m_status.dwWin32ExitCode;
    }

    CRegKey key;
    lRes = key.Open(keyAppID, pT->GetAppIdT(), KEY_READ);
    if (lRes != ERROR_SUCCESS)
    {
        m_status.dwWin32ExitCode = lRes;
        return m_status.dwWin32ExitCode;
    }

    TCHAR szValue[MAX_PATH];
    DWORD dwLen = MAX_PATH;
    //讀取注冊表信息
    //通過regserver方式注冊服務,則lRes為ERROR_SUCCESS
    //通過service方式注冊服務,   則lRes不等於ERROR_SUCCESS
    lRes = key.QueryStringValue(_T("LocalService"), szValue, &dwLen);

    m_bService = FALSE;
    if (lRes == ERROR_SUCCESS)
        m_bService = TRUE;

    if (m_bService)
    {
        //以Windows服務的方式運行
        SERVICE_TABLE_ENTRY st[] =
        {
            { m_szServiceName, _ServiceMain },
            { NULL, NULL }
        };
        if (::StartServiceCtrlDispatcher(st) == 0)
            m_status.dwWin32ExitCode = GetLastError();
        return m_status.dwWin32ExitCode;
    }
    // local server - call Run() directly, rather than
    // from ServiceMain()        
    //以普通應用程序的方式運行
    m_status.dwWin32ExitCode = pT->Run(nShowCmd);
    return m_status.dwWin32ExitCode;
}

Start方法中會根據讀取到的注冊表信息,來決定是否以服務的方式運行。如果是通過RegServer方式注冊服務,則以普通程序運行;如果是通過Service方式注冊服務,則以Windows服務的方式運行。

普通程序方式運行,不在本文的討論范圍之內,下面來看一下以Windows服務方式運行的過程。

以Windows服務方式運行的話,程序會調用StartServiceCtrlDispatcher方法,看一下關於此方法的MSDN的解釋

Connects the main thread of a service process to the service control manager, which causes the thread to be the service control dispatcher thread for the calling process.


Remarks
When the service control manager starts a service process, it waits for the process to call the StartServiceCtrlDispatcher function. The main thread of a service process should make this call as soon as possible after it starts up (within 30 seconds). If StartServiceCtrlDispatcher succeeds, it connects the calling thread to the service control manager and does not return until all running services in the process have entered the SERVICE_STOPPED state. The service control manager uses this connection to send control and service start requests to the main thread of the service process. The main thread acts as a dispatcher by invoking the appropriate HandlerEx function to handle control requests, or by creating a new thread to execute the appropriate ServiceMain function when a new service is started.

這段話的大意是 “調用此方法可以與服務管理器建立連接,這樣服務管理器就可以管理服務的狀態”,實際是服務管理器發出命令后,服務可以對接收到的命令進行響應。

“當服務管理器啟動一個服務進程,服務管理器會等待服務進程調用StartServiceCtrlDispatcher方法。服務進程的主線程必須確保此方法在30秒內被盡快的執行。如果StartServiceCtrlDispatcher方法成功與服務管理器建立連接,那么它會等到服務的狀態變為SERVICE_STOPPED后才返回”。

由此可知,當Start方法調用StartServiceCtrlDispatcher后,會進入到_ServiceMain方法。

void ServiceMain(
        _In_ DWORD dwArgc,
        _In_reads_(dwArgc) _Deref_pre_z_ LPTSTR* lpszArgv) throw()
{
    lpszArgv;
    dwArgc;
    // Register the control request handler
    m_status.dwCurrentState = SERVICE_START_PENDING;
    m_dwThreadID = GetCurrentThreadId();
    //注冊命令處理程序,用於響應服務管理器的控制命令
    RegisterServiceCtrlHandler(m_szServiceName, _Handler);

    //設置服務的狀態為已啟動
    SetServiceStatus(SERVICE_START_PENDING);

    m_status.dwWin32ExitCode = S_OK;
    m_status.dwCheckPoint = 0;
    m_status.dwWaitHint = 0;

    T* pT = static_cast<T*>(this);

    // When the Run function returns, the service has stopped.
    m_status.dwWin32ExitCode = pT->Run(SW_HIDE);
    //當Run方法結束后,會設置方法的狀態為已停止
    SetServiceStatus(SERVICE_STOPPED);
}

_ServiceMain方法中主要是注冊了一個服務控制命令的處理程序,然后設置服務的狀態為已啟動,然后調用Run方法。

HRESULT Run(_In_ int nShowCmd = SW_HIDE) throw()
{
    HRESULT hr = S_OK;
    T* pT = static_cast<T*>(this);
    //初始化Com相關的東西
    hr = pT->PreMessageLoop(nShowCmd);

    if (hr == S_OK)
    {
        //處理Msg消息
        pT->RunMessageLoop();
    }

    if (SUCCEEDED(hr))
    {
        //釋放Com相關資源
        hr = pT->PostMessageLoop();
    }

    return hr;
}

Run方法中主要是循環處理Msg消息,防止主線程退出。

至此,服務算是完全啟動了。

前面看到_ServiceMain方法中注冊了一個服務控制命令處理程序,接下來看一下這個方法做了什么。

void Handler(_In_ DWORD dwOpcode) throw()
{
    T* pT = static_cast<T*>(this);

    switch (dwOpcode)
    {
        //停止命令
    case SERVICE_CONTROL_STOP:
        pT->OnStop();
        break;
        //暫停命令
    case SERVICE_CONTROL_PAUSE:
        pT->OnPause();
        break;
        //恢復命令
    case SERVICE_CONTROL_CONTINUE:
        pT->OnContinue();
        break;
    case SERVICE_CONTROL_INTERROGATE:
        pT->OnInterrogate();
        break;
    case SERVICE_CONTROL_SHUTDOWN:
        pT->OnShutdown();
        break;
    default:
        pT->OnUnknownRequest(dwOpcode);
    }
}

可以看到,這個方法根據不同的控制命令,做了不同的處理。

 

Windows服務的啟動流程總結

Windows服務啟動流程圖

 

停止流程

上面我們看到當服務接收到停止服務的命令后,Hanlder方法會通過調用pT->OnStop方法來進行處理。

void OnStop() throw()
{
    SetServiceStatus(SERVICE_STOP_PENDING);
    ::PostThreadMessage(m_dwThreadID, WM_QUIT, 0, 0);
}

OnStop方法中,通過SetServiceStatus方法來設置服務狀態為正在停止。並通過PostThreadMessage產生一個WM_QUIT的消息。

void RunMessageLoop() throw()
{
    MSG msg;
    while (GetMessage(&msg, 0, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

此時RunMessageLoop中的While循環中斷。

While循環中斷后會通過SetServiceStatus設置服務狀態為已停止。

然后WinMain方法執行結束,服務進行退出。

這就是整個的服務停止流程。

 

Windows服務的停止流程總結

Windows服務停止流程圖

 

給服務添加自己的啟動和停止時的處理

既然已經對Windows服務的啟動和停止的流程有了一個大概的了解,那么給服務添加自己的啟動和停止時的處理也就相對簡單了一些。

下面是我的實現代碼

class CServicesModule : public ATL::CAtlServiceModuleT< CServicesModule, IDS_SERVICENAME >
{
public :
    DECLARE_LIBID(LIBID_ServicesLib)
    DECLARE_REGISTRY_APPID_RESOURCEID(IDR_SERVICES, "{0794CF96-5CC5-432E-8C1D-52B980ACBE0F}")
        HRESULT InitializeSecurity() throw()
    {
        // TODO : 調用 CoInitializeSecurity 並為服務提供適當的安全設置
        // 建議 - PKT 級別的身份驗證、
        // RPC_C_IMP_LEVEL_IDENTIFY 的模擬級別
        // 以及適當的非 NULL 安全描述符。

        return S_OK;
    }
    //服務啟動
    HRESULT Load();
    //服務停止
    HRESULT UnLoad();

    HRESULT Run(_In_ int nShowCmd = SW_HIDE) throw()
    {
        HRESULT hr = S_OK;
        OutputDebugString(_T("准備啟動服務"));
        hr = Load();
        if(hr)
        {
            OutputDebugString(_T("啟動服務失敗"));
            return hr;
        }
        OutputDebugString(_T("Services服務已啟動"));

        hr = ATL::CAtlServiceModuleT< CServicesModule, IDS_SERVICENAME >::Run(nShowCmd);

        hr = UnLoad();
        OutputDebugString(_T("Services服務正常退出"));
        return hr;
    }
};

CServicesModule _AtlModule;

//
extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, 
                                LPTSTR /*lpCmdLine*/, int nShowCmd)
{
    return _AtlModule.WinMain(nShowCmd);
}


HRESULT CServicesModule::Load()
{
    OutputDebugString(_T("服務正在啟動"));
    return 0;
}

HRESULT CServicesModule::UnLoad()
{
    OutputDebugString(_T("服務正在停止"));
    return 0;
}

 

系列鏈接

玩轉Windows服務系列——創建Windows服務

玩轉Windows服務系列——Debug、Release版本的注冊和卸載,及其原理

玩轉Windows服務系列——無COM接口Windows服務啟動失敗原因及解決方案

玩轉Windows服務系列——服務運行、停止流程淺析

玩轉Windows服務系列——Windows服務小技巧

玩轉Windows服務系列——命令行管理Windows服務

玩轉Windows服務系列——Windows服務啟動超時時間

玩轉Windows服務系列——使用Boost.Application快速構建Windows服務

玩轉Windows服務系列——給Windows服務添加COM接口


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM