玩转Windows服务系列——服务运行、停止流程浅析

原文:玩转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服务的启动流程总结

 

停止流程

上面我们看到当服务接收到停止服务的命令后,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服务的启动和停止的流程有了一个大概的了解,那么给服务添加自己的启动和停止时的处理也就相对简单了一些。

下面是我的实现代码

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服务

时间: 2024-09-20 04:15:17

玩转Windows服务系列&mdash;&mdash;服务运行、停止流程浅析的相关文章

玩转Windows服务系列&amp;mdash;&amp;mdash;Windows服务小技巧

原文:玩转Windows服务系列--Windows服务小技巧 伴随着研究Windows服务,逐渐掌握了一些小技巧,现在与大家分享一下. 将Windows服务转变为控制台程序 由于默认的Windows服务程序,编译后为Win32的窗口程序.我们在程序启动或运行过程中,如果想看到一些调试信息,那么就只能通过DebugView或者输出到日志的方式了.因为如果我们通过printf或者std::cout输出调试信息的话,Win32窗口程序是无法显示的. 此时,我们是多么怀念我们的经典的控制台程序啊,它可以

玩转Windows服务系列&amp;mdash;&amp;mdash;命令行管理Windows服务

原文:玩转Windows服务系列--命令行管理Windows服务 说到Windows服务的管理就不得不说通过命令行的方式管理Windows服务,因为无论是系统管理员,还是通过编程的方式调用cmd命令,命令行都是非常方便以及强大的工具. 接下来就看一下如何通过cmd命令管理Windows服务. 管理Windows服务的主要cmd命令 管理Windows服务的命令应该有很多,但是我所了解到的命令主要有两个:sc.net. 说是两个cmd命令,实际就是windows的system32目录下的两个执行程

玩转Windows服务系列&amp;mdash;&amp;mdash;Debug、Release版本的注册和卸载,及其原理

原文:玩转Windows服务系列--Debug.Release版本的注册和卸载,及其原理 Windows服务Debug版本 注册 Services.exe -regserver 卸载 Services.exe -unregserver Windows服务Release版本 注册 Services.exe -service 卸载 Services.exe -unregserver 原理 Windows服务的Debug.Release版本的注册和卸载方式均已明确.但是为什么要这么做呢. 最初我在第一

玩转Windows服务系列——无COM接口Windows服务启动失败原因及解决方案

原文:玩转Windows服务系列--无COM接口Windows服务启动失败原因及解决方案 将VS创建的Windows服务项目编译生成的程序,通过命令行 "服务.exe -Service"注册为Windows服务后,就可以通过服务管理器进行管理了. 问题 通过服务管理器进行启动的时候,发现服务无法启动,提示错误 由于程序代码是由VS的项目模板生成的,生成后直接编译为Debug版,然后通过命令行进行注册,期间并没有修改过任何代码. 难道是只有Release版本才能通过服务管理器启动吗.于是

开发者论坛一周精粹(第十七期) :【漏洞预警】Windows再被爆SMB服务0day漏洞,阿里云提示您关注并修复

第十七期(2017年7月31日-2017年8月6日 ) 在美国拉斯维加斯举行的2017年度DEF CON黑客大会上,安全研究人员公布了Windows系统上的一个长达20年没有发现的漏洞,该漏洞名为"SMBLoris",黑客可以轻松的使用简短的20行代码利用该漏洞即可发起DoS攻击导致系统内存资源耗尽,该漏洞影响Windows 2000及以上系统的SMBv1协议 [漏洞预警]Windows再被爆SMB服务0day漏洞,阿里云提示您关注并修复 https://bbs.aliyun.com/

三步完成Windows Azure平台部署WCF服务

之前,我们在Windows Azure平台简介中介绍过Windows Azure的三大主要功能.那就是计算,存储,以及管理.至今为止,通过之前的教学文章,大家已经熟知了Windows Azure的存储功能.当然,存储只是最基本的一个功能.要充分发挥云计算的威力,我们不得不学习如何"计算". 同样在Windows Azure平台简介中,我们也介绍了Windows Azure的三种主要计算功能: 作为一个部署服务平台 作为一个软件分发平台 作为一个一般的分布式计算平台 本文将引导大家达成第

Windows Azure新增功能备份服务正式发布

今天上午,我们发布了Windows Azure的一系列更新.这些新功能包括: •备份服务:正式发布的Windows Azure备份服务 •Hyper-V的恢复管理器:Windows Azure中的Hyper-V恢复管理器的公开预览版 •虚拟机:删除连接的磁盘,可设置警告,SQL AlwaysOn的配置 •Active Directory:安全地管理数百个SaaS应用 •企业管理:使用Active Directory来更好地管理Windows Azure •Windows Azure SDK 2.

在Windows Azure平台上部署服务

简介之前,我们在Windows Azure平台简介中介绍过Windows Azure的三大主要功能.那就是计算,存储,以及管理.至今为止,通过之前的教学文章,大家已经熟知了Windows Azure的存储功能.当然,存储只是最基本的一个功能.要充分发挥云计算的威力,我们不得不学习如何"计算". 同样在Windows Azure平台简介中,我们也介绍了Windows Azure的三种主要计算功能: 作为一个部署服务平台 作为一个软件分发平台 作为一个一般的分布式计算平台 本文将引导大家达

Windows服务器上配置SNMP服务方法

SNMP(Simple Network Management Protocol,简单网络管理协议),用来对通信线路进行管理.在Windows服务器上配置SNMP服务时,使用手动填写信息太麻烦.下面是使用命令行执行配置文件来实现. 创建snmp.inf [NetOptionalComponents] SNMP = 1 [SNMP] Contact_Name = "ITSupport" Location = "ServerRoom" Service = Physical