Windows服务编写综述.docx
- 文档编号:8245707
- 上传时间:2023-01-30
- 格式:DOCX
- 页数:18
- 大小:25.65KB
Windows服务编写综述.docx
《Windows服务编写综述.docx》由会员分享,可在线阅读,更多相关《Windows服务编写综述.docx(18页珍藏版)》请在冰豆网上搜索。
Windows服务编写综述
Windows服务编写综述
分类:
编码常识2013-03-1110:
36 260人阅读 评论(0) 收藏 举报
摘要:
几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。
它由服务程序、服务控制程序(SCP,servicecontrolprogram)和服务控制管理器(SCM,servicecontrolmanager)三个组件构成。
本文针对服务程序与服务控制程序的编写进行综合讲述。
关键词:
Windows,服务,VC++
1 服务介绍
几乎所有的操作系统在启动的时候都会启动一些不需要与用户交互的进程,这些进程在Windows中就被称作服务。
它通常用于实现客户/服务器模式中的服务器方,如我们常见的Web服务IIS,当操作系统在启动后它就自动被运行,不管是否有人登陆到系统只要系统开启它就能得到运行。
服务程序、服务控制程序(SCP,servicecontrolprogram)和服务控制管理器(SCM,servicecontrolmanager)组成了Windows服务。
我们可以通过服务控制程序操纵服务控制管理器来配置、启动、暂停、停止服务程序。
其中服务程序和服务控制程序可以由我们自己来编写扩展,而服务控制管理器(\windows\system32\servics.exe)则是操作系统内置的一个部件。
首先我们来了解一下SCM的工作情况,然后我们介绍服务程序的编写和服务控制时所涉及API的使用。
2 服务控制管理器
SCM本身也是一个服务程序(\windows\system32\servics.exe),作为windows的后台服务运行的。
Winlogon在系统引导的早期会将SCM启动起来。
SCM的服务入口函数首先创建一个初始化为无信号的同步事件对象(SvcCtrlEvent_A3752DX);接下来,它开始建立一个内部服务数据库,这个数据库要按事先规定好的一个顺序列出所有服务组,并记录与服务相关的详细信息;当这个数据库建立完成时SCM就开始按顺序启动那些启动方式为自动的服务,如果有服务要动行于指定用户账户中时还要调用LSASS,如果服务启动失败则会被放入一个名为ScFailedDrivers的列表中。
当这些工作都完成后,SCM将同步事件对象SvcCtrlEvent_A3752DX置为有信号状态;并做好系统停机的准备。
当系统要关机时会向Windows子系统进程Csrss发送一个消息,以便调用Csrss的停机例程。
Csrss会对所有活动的进程循环通知系统正在停机。
对于除SCM以外的每一个系统进程如果没有返回退出的响应Csrss都会等待由HKEY_USER\.DEFAULT\ControlPanel\Desktop\WaitToKillAppTimeout指定的毫秒数(我的系统中是20000,也就是20秒),然后知通下一个进程结束。
当遇到SCM时也会通知SCM进程系统正在停机,但不同的是会使用一个专用的超时间隔值(SCM在系统初始化时要向Csrss登记,于是Csrss就将SCM的进程ID保存了下来Csrss也就是通过个ID来识别SCM的)。
这个专用的超时间隔位于HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout中,有趣的是默认值也是20秒。
这在段时间里SCM要通知所有在初始化时(服务程序自身初始化)请求SCM通知自己系统停机的服务,SCM有一个停机处理器负责这项工作。
通知下达后SCM就等待服务退出,服务在接收到停机消息后会返回给SCM一个等待时间,SCM跟踪所有服务返回等待时间找出其中的最大值,当这个最大值达到后,如果有一个服务或多个服务又告诉SCM它们正在处理停机工作,那么SCM还会循环等待下去,但Csrss也再等待SCM当Csrss等待超时后,会继续后面的停机工作最终完成停机。
这就要求服务程序要在Csrss等待SCM的这段时内完成自己的停机处理工作,否则服务就没机会在系统停机前完成自己的关闭工作了。
3 服务程序
Windows服务程序其实并不神秘,它只是遵循特定规则编写的一个程序。
只要遵循这个特定的规则与服务控制管理器正确的交互,就可实现我们的服务程序。
而我们只要能实现一个简单的服务程序,设计一个能处理复杂业务的服务也并非难事,因为从结构上看两者并没有太大的区别。
只要遵循与SCM交互的规则,设计服务程序与设计普通的应用程序几乎没什么区别。
3.1 程序结构概要
服务程序的与普通应用程序一样也需要一个主函数(main())作为程序的入口,与之不同的是作为一个服务程序它需要在主函数(main())中立即调用StartServiceCtrlDispatcher来注册一个服务的入口函数(ServerMain(DWORDargc,LPTSTR*argv),当然这个名字可自由命名)。
StartServiceCtrlDispatcher函数的原型是:
BOOLStartServiceCtrlDispatcher(LPSERVICE_TABLE_ENTRYlpServiceStartTable);
它的参数是一个指向SERVICE_TABLE_ENTRY的指针;SERVICE_TABLE_ENTRY结构有两个域;第一个域存储服务的内部名称,第二个域是服务入口函数的指针。
这个函数完成后,SCM就要可以服务启动的时候调用服务的入口函数。
例如:
管理员在服务管理器启动一个服务,SCM就会在一个单独的线程中调用服务注册的入口函数。
这时我们在服务的这个入口函数中必须调用RegisterServiceCtrlHandler完成Handler函数的注册,这个函数用来接收和处理SCM的控制消息。
下面列出Hander要处理的控制消息和RegisterServiceCtrlHandler的函数原型:
VOIDWINAPIHandler(
DWORDfdwControl//请求控制消息代码
);
控制消息宏定义
说明
SERVICE_CONTROL_STOP
要服务停止
SERVICE_CONTROL_PAUSE
要服务暂停
SERVICE_CONTROL_CONTINUE
要服务继续
SERVICE_CONTROL_INTERROGATE
要服务马上报告它的状态
SERVICE_CONTROL_SHUTDOWN
告诉服务即将关机
SERVICE_STATUS_HANDLERegisterServiceCtrlHandler(
LPCTSTRlpServiceName,//服务的内部名称
LPHANDLER_FUNCTIONlpHandlerProc//Handler函数的地址
);
RegisterServiceCtrlHandler调用完成后我们就可以开始我们的业务处理的初始化工作。
初始化完成后向SCM报告服务开始运行(SERVICE_RUNNING)的消息。
如果ServerMain(DWORDargc,LPTSTR*argv)函数退出服务也就停止了。
下面让我总结一下实现服务程序的步骤:
(1)在main()调用StartServiceCtrlDispatcher来注册一个服务的入口函数;
(2)在ServerMain(DWORDargc,LPTSTR*argv)中调用RegisterServiceCtrlHandler注册Handler函数。
(3)完成业务处理程序的初始化工作,如果初始化时间较长要实时向SCM报告当前正在启动
(4)初始化完毕,报告服务正在运行;开始业务处理工作。
3.2 程序实例分析
(1)main()函数
intmain(intargc,char*argv[])
{
SERVICE_TABLE_ENTRYserviceTable[]=
{
{
SERVICE_NAME,
(LPSERVICE_MAIN_FUNCTION)ServiceMain
}
{
NULL,NULL
}
};
BOOLsuccess;
success=StartServiceCtrlDispatcher(serviceTable);
if(!
success)
{
ErrorHandler("InStartServiceCtrlDispatcher",GetLastError());
}
return0;
}
StartServiceCtrlDispatcher的参数必须是一个以NULL结尾的数组指针,我们可以在一个程序文件中注册多个服务实例,只在把所要注册的服务名和服务入口函数地址写到数组中即可,在我们调用CreateService创建服务时要把dwServiceType 参数设为共享进程(SERVICE_WIN32_SHARE_PROCESS);不过当要创建独立进程的服务时(dwServiceType 参数为SERVICE_WIN32_OWN_PROCESS时)在这里就只能注册一个服务实例。
(2)服务入口函数ServerMain()
VOIDServiceMain(DWORDargc,LPTSTR*argv)
{
BOOLsuccess;
StatusHandler=
RegisterServiceCtrlHandler(SERVICE_NAME,(LPHANDLER_FUNCTION)Handler);
if(!
serviceStatusHandler)
{
return;
}
success=ReportStatus(SERVICE_START_PENDING,
NO_ERROR,0,1,5000);
if(!
success)
{
return;
}
endEvent=CreateEvent(0,TRUE,FALSE,0);
if(!
endEvent)
{
return;
}
success=ReportStatus(SERVICE_START_PENDING,
NO_ERROR,0,2,5000);
if(!
success)
{
return;
}
//////initparameterstart
RecvParam(argc,argv);
//////initparameterend
success=ReportStatus(SERVICE_START_PENDING,
NO_ERROR,0,3,5000);
if(!
success)
{
return;
}
success=InitService();
if(!
success)
{
return;
}
success=ReportStatus(SERVICE_RUNNING,NO_ERROR,0,0,0);
if(!
success)
{
return;
}
WaitForSingleObject(endEvent,INFINITE);
}
RegisterServiceCtrlHandler完成Handler函数的注册(Handler函数的具体实现我们在第三小节中介绍),它的第一个参数是调用CreateService创建服务时lpServiceName指向的名服务名称,每二个参数是Handler函数的地址;函数名可以自由命名。
ReportStatus是向SCM报告服务当前状态的一个自定义函数。
它内部调用SetServiceStatus向SCM报告服务的当状态,此函数有两个参数第一个就是RegisterServiceCtrlHandler完成时返回的SERVICE_STATUS_HANDLE,第二个参数是一个SERVICE_STATUS变量的指针,它指示了服务当前的状态信息;当注册完Handler函数后向SCM报告一下自己当前的状态(正在启动)。
接着创建endEvent事件对像,它是当我们收到SCM的退出控制代码时通知服务主函数退出的,大家可看ServiceMain的最后一句。
下面又是向SCM报告自己正在启动,当初始化所花费的时间非常短时这样做并不是必须的,但如果很长就必须这样做。
RecvParam(argc,argv)使用了ServiceMain函数的两个参数,大家可以看出ServiceMain和main有着一样的形参;说明ServiceMain和main一样可以接收配置参数,稍后我们会在服务控制程序的编写中给大家介绍如何给服务配置参数。
InitService()完成我们的业务初始化工作并开始业务处理。
最后报告服务启动完成,等待endEvent事件退出服务。
下面我们再来看一下SCM控制消息的处理。
(3)SCM控制消息处理(Handler函数)
VOIDHandler(DWORDcontrolCode)
{
DWORDcurrentState=0;
BOOLsuccess;
switch(controlCode)
{
caseSERVICE_CONTROL_STOP:
success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);
CloseTask();
success=ReportStatus(SERVICE_STOPPED,NO_ERROR,0,0,0);
return;
caseSERVICE_CONTROL_PAUSE:
if(runningService&&!
pauseService)
{
success=ReportStatus(SERVICE_PAUSE_PENDING,NO_ERROR,0,1,1000);
pauseService=TRUE;
ServicePause();
currentState=SERVICE_PAUSED;
}
break;
caseSERVICE_CONTROL_CONTINUE:
if(runningService&&pauseService)
{
success=ReportStatus(SERVICE_CONTINUE_PENDING,
NO_ERROR,0,1,1000);
pauseService=FALSE;
ServiceContinue();
currentState=SERVICE_RUNNING;
}
break;
caseSERVICE_CONTROL_INTERROGATE:
//检索更新状态的时
break;
caseSERVICE_CONTROL_SHUTDOWN:
//告诉服务即将关机
success=ReportStatus(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000);
CloseTask();
return;
default:
break;
}
ReportStatus(currentState,NO_ERROR,0,0,0);
}
Handler只有一个参数就是SCM传来的控制消息代码;这里处理的了停止,暂停,继续,更新,关机五个控制消息。
但并不是这五个消息SCM都会向服务发送,要在向服务报告状时向SCM报告自己可以响应的控制消息,只要设置SERVICE_STATUS结构中的dwControlsAccepted域即可,它对应的值有:
SERVICE_ACCEPT_STOP,SERVICE_ACCEPT_PAUSE_CONTINUE,SERVICE_ACCEPT_SHUTDOWN,当要设置多个时只要把宏相或(|)传给dwControlsAccepted域即可。
在响应SCM控制消息时也要注意及时报告服务当前的状态信息,否则SCM会认为服务响应超时出错了。
(4)服务的安装与卸载
服务程序编写完成并编译通过后,还要安装注册到操作系统中,这样它才会出现在管理工具->服务,那个管理器里面。
API给我们提供了一个函数来实现我们注册服务的功能;SC_HANDLECreateService(
SC_HANDLEhSCManager,//服务控制管理器的句柄
LPCTSTRlpServiceName,//指向服务的内部名称
LPCTSTRlpServiceName,,//指向服务的显示名称
DWORDdwDesiredAccess,//服务的访问类型
DWORDdwServiceType,//服务的类型
DWORDdwStartType,//服务的启动方式(自动,手动,禁用)
DWORDdwErrorControl,//错误控制方式
LPCTSTRlpBinaryPathName,//服务程序的路径
LPCTSTRlpLoadOrderGroup,//服务组的名称
LPDWORDlpdwTagId,//服务的标签号
LPCTSTRlpDependencies,//服务依赖的服务或组名
LPCTSTRlpServiceStartName,//服务的启动帐户
LPCTSTRlpPassword//服务启动帐户的密码
);
hSCManager:
这是函数的第一个参数-SCM的句柄。
它要调用OpenSCManager来获得,稍后我们会讲它怎么调用方法。
lpServiceName和lpServiceName:
分别的服务的名称和服务的显示名称,服务是显示名称就服务管理器中看到的那个服务名。
则是服务在SCM中注册的名称,比如调用OpenService打开服务时就会用到它。
dwDesiredAccess:
标出服务同意请求的访问,可以是下面任意任值:
SERVICE_ALL_ACCESS
SERVICE_CHANGE_CONFIG
SERVICE_ENUMERATE_DEPENDENTS
SERVICE_INTERROGATE
SERVICE_PAUSE_CONTINUE
SERVICE_QUERY_CONFIG
SERVICE_QUERY_STATUS
SERVICE_START
SERVICE_STOP
SERVICE_USER_DEFINED_CONTROL
我们可以指定一个或多个,如果有多个的话要用或符号(|)联结起来。
dwServiceType:
注册服务的类型,它必须是下面的值:
SERVICE_WIN32_OWN_PROCESS
SERVICE_WIN32_SHARE_PROCESS
SERVICE_KERNEL_DRIVER
SERVICE_FILE_SYSTEM_DRIVER如果指定的是SERVICE_WIN32_OWN_PROCESS类型的服务还可以加上SERVICE_WIN32_OWN_PROCESS(允许用户桌面交互),我们这里介绍的服务只能注册为SERVICE_WIN32_OWN_PROCESS或SERVICE_WIN32_SHARE_PROCESS;另两种类型是驱动级的服务用的,有兴趣大家可查看相关资料。
dwStartType:
服务的启动类型SERVICE_BOOT_START、SERVICE_SYSTEM_START、SERVICE_AUTO_START、SERVICE_DEMAND_START、SERVICE_DISABLED。
分别为前两种类型仅对驱动程序用效,所在我们这里所说的这类服务能后三种(自动,手动,禁用)。
dwErrorControl:
服务的错误控制标记
SERVICE_ERROR_IGNORE:
忽略所有错误
SERVICE_ERROR_NORMAL:
正常报告服务返回的错误
SERVICE_ERROR_SEVERE:
当服务返回错误出现时,如果最后已知好控制集(最后已知好控制集:
是系统最后一次成功引导时使用的服务注册表配置)尚未使用,则重新引导进入最后已知好控制集,否则重新引导。
SERVICE_ERROR_CRITICAL:
当服务返回错误出现时,如果最后已知好控制集尚未使用,则重新引导进入最后已知好控制集,否则蓝屏崩溃。
lpBinaryPathName:
服务程序的文件路径
lpLoadOrderGroup:
服务所属的组
lpdwTagId:
在组中的唯一标识
lpDependencies:
服务所依赖的其它组和服务
lpServiceStartName和lpPassword:
服务由哪个用户启动,也即服务运行在哪个用户权限下,分别指定用户名和密码.
下再说两重要的函数:
OpenSCManager和CloseServiceHandle给出它们的原型
SC_HANDLEOpenSCManager(
LPCTSTRlpMachineName,//机器名,打开本机的SCM时可为NULL
LPCTSTRlpDatabaseName,//指向SCM数据库的名字可为NULL
DWORDdwDesiredAccess//访问权限类型如:
SC_MANAGER_ALL_ACCESS等
);
BOOLCloseServiceHandle(
SC_HANDLEhSCObject//服务控制句柄
);
这果列出一段注册服务的代码供大家参考:
SC_HANDLEnewService,scm;
BOOLsuccess=FALSE;
SERVICE_STATUSstatus;
scm=OpenSCManag
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Windows 服务 编写 综述