原文:玩转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服务

玩转Windows服务系列——服务运行、停止流程浅析的更多相关文章

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

    通过研究Windows服务注册卸载的原理,感觉它并没有什么特别复杂的东西,Windows服务正在一步步退去它那神秘的面纱,至于是不是美女,大家可要睁大眼睛看清楚了. 接下来研究一下Windows服务的 ...

  2. 玩转Windows Azure存储服务——网盘

    存储服务是除了计算服务之外最重要的云服务之一.说到云存储,大家可以想到很多产品,例如:AWS S3,Google Drive,百度云盘...而在Windows Azure中,存储服务却是在默默无闻的工 ...

  3. .Net Core微服务系列--服务发现

    什么是服务发现 首先我们先思考一个问题,当我们在浏览器中输入一个域名比如baidu.com,然后发生了什么才能让我们访问到百度的网页?简单来说,浏览器会首先从主机的hosts文件中查看是否有baidu ...

  4. 玩转Windows Azure存储服务——高级存储

    在上一篇我们把Windows Azure的存储服务用作网盘,本篇我们继续挖掘Windows Azure的存储服务——高级存储.高级存储自然要比普通存储高大上的,因为高级存储是SSD存储!其吞吐量和IO ...

  5. coTurn 运行在Windows平台的方法及服务与客户端运行交互流程和原理

    coTurn是一个开源的STUN和TURN及ICE服务项目,只是不支持Windows.为了在window平台上使用coTurn源码,需要在windows平台下安装Cygwin环境,并编译coTurn源 ...

  6. 玩转Windows服务系列汇总

    玩转Windows服务系列汇总 创建Windows服务 Debug.Release版本的注册和卸载及其原理 无COM接口Windows服务启动失败原因及解决方案 服务运行.停止流程浅析 Windows ...

  7. 玩转Windows服务系列——给Windows服务添加COM接口

    当我们运行一个Windows服务的时候,一般情况下,我们会选择以非窗口或者非控制台的方式运行,这样,它就只是一个后台程序,没有界面供我们进行交互. 那么当我们想与Windows服务进行实时交互的时候, ...

  8. 玩转Windows服务系列——使用Boost.Application快速构建Windows服务

    玩转Windows服务系列——创建Windows服务一文中,介绍了如何快速使用VS构建一个Windows服务.Debug.Release版本的注册和卸载,及其原理和服务运行.停止流程浅析分别介绍了Wi ...

  9. 玩转Windows服务系列——Debug、Release版本的注册和卸载,及其原理

    Windows服务Debug版本 注册 Services.exe -regserver 卸载 Services.exe -unregserver Windows服务Release版本 注册 Servi ...

随机推荐

  1. OpenCV 例子代码的讲解、简介及库的安装 .

    转载请标明是引用于 http://blog.csdn.net/chenyujing1234 欢迎大家提出意见,一起讨论! 一.OpenCV介绍: OpenCV是由Intel性能基元(IPP)团队主持, ...

  2. Tuxedo入门学习

    中间件介绍: 介于客户机和server之间的夹层,突破了传统的c/s架构,为构建大规模,高性能,分布式c/s应用程序提供了通信,事物,安全,容错等基础服务,屏蔽了底层应用细节,应用程序不必从底层开发, ...

  3. 数学之路-python计算实战(14)-机器视觉-图像增强(直方图均衡化)

    我们来看一个灰度图像,让表示灰度出现的次数,这样图像中灰度为 的像素的出现概率是  是图像中全部的灰度数, 是图像中全部的像素数,  实际上是图像的直方图,归一化到 . 把  作为相应于  的累计概率 ...

  4. 史上最强Android 开启照相或者是从本地相册选中一张图片以后先裁剪在保存并显示的讲解附源码

    整个程序的布局很简单 只在一个垂直方向上的线性布局里面有俩个按钮(Button)和一个显示图片的控件(ImageView)这里就不给出这部分的代码了   1.是打开系统的相册   Intent alb ...

  5. 基于visual Studio2013解决C语言竞赛题之1036递归求值

          题目 解决代码及点评 /* 36.已知有如下递推公式 求该数列的前n项.不允许使用数组. */ float fp50036(int n,float x,float ...

  6. Java多线程实现生产者消费者延伸问题

    在操作系统中有一类问题被称为生产者消费者问题:意为,有数个生产者生产产品,有数个消费者消费产品,他们共享一定数量的缓存. 这里用java多线程编程,实现生产者消费者问题的一种延伸,橘子苹果问题. 题目 ...

  7. oracle 的常用语句

    第一部分 基本语法 //拼接表字段 select id   ||   'is'   ||   name from admin select * from emp where ename like '% ...

  8. (5/9)*(f-32)与5*(f-32)/9

    今天在Linux下用c语言写个小程序玩玩,主要就是根据华氏温度计算摄氏温度.公式是:摄氏度=(5/9)*(华氏度-32) 代码很简单~ #include<stdio.h> main() { ...

  9. php等号(==)与全等(===)

    <?php require_once 'Person.php'; header("content-type:text/html;charset=utf-8"); $perso ...

  10. MonkeyRunner 学习

    monkeyrunner 本文档中包含 一个简单的monkeyrunner示例程序 monkeyrunner API简介 运行monkeyrunner monkeyrunner内建帮助 使用插件扩展m ...