天天看點

《Windows核心程式設計》---Windows服務

windows服務(services),是一些運作在windowsnt、windows2000和windows xp等作業系統下使用者環境以外的程式。它不同于一般的可執行程式,不需要系統登入便可以運作,以完成某些特定的功能。服務提供了管理能力,可以将背景程式轉換成服務,然後就可以用指令或者在系統啟動使用者登入之前啟動,并且也可以暫停、恢複和終止。服務資訊在系統資料庫中維護。

為了能夠在系統中正确運作,在建立一個服務時必須接受一些特殊的規則,最重要的一點是:必須在目标系統中安裝并且注冊該service。此外,基于使用者界面的service是沒有多大意義的,當然service可以有使用者界面,不過由于每一個service都是在自己的windows station中建立的,是以使用者界面相關的api調用無法進行。

一個windows服務,至少應該包括兩個程式:一個是服務本體程式,一個是服務控制程式。服務本體程式一般是一個dos控制台程式,而控制程式則是一個普通的win32應用程式,用于對服務進行啟動和停止等操作。

windows服務在服務控制管理器(scm)的控制下運作,把一個控制台程式轉換成一個windows服務,需要三個主要的步驟來将程式置于scm控制下:

1)建立一個新的main()入口點以在scm中注冊服務,它提供邏輯服務入口點和名稱;

2)轉換舊的main()入口點函數為servicemain(),它注冊服務控制處理器并向scm通知其狀态;

3)編寫服務控制處理器函數以響應scm指令。

服務控制管理器(service control manager)

scm的可執行檔案鏡像是“/winnt/system32/services.exe”,winlogon程序早在系統引導之前就啟動它了。scm運作後,掃描以下系統資料庫鍵下的内容:

hkey_local_machine/system/currentcontrolset/services

為它遇到的每個子鍵在服務資料庫中建立一個入口。服務入口包含所有服務相關的參數,也包含了追蹤服務狀态的域。如果服務或者驅動辨別為自動啟動,scm将啟動它們,并偵測啟動過程中的錯誤。i/o管理器将在任何使用者模式程序執行前加載辨別為引導期間啟動和系統啟動期間啟動的驅動程式。

hkey_local_machine/system/currentcontrolset/services下面有很多子鍵,一個子鍵名表示一個服務的内部名稱,其下的鍵值項對應所有與此服務相關的參數。

安裝服務所需的最低數量的參數如下:

displayname---使用者接口程式使用的服務名稱。如果沒有指定名稱,服務系統資料庫鍵名将作為它的名稱。

errorcontrol---如果scm啟動服務時驅動出錯,這個值指定scm的行為。取值如下:

1)service_error_ignore(0)—i/o管理器忽略驅動傳回的錯誤,但是仍然繼續啟動操作,不做任何記錄;

2)service_error_normal(1)---如果驅動加載或者初始化失敗,系統将給使用者顯示一個警告框,并将錯誤記錄到系統日志中。

imagepath---指定驅動鏡像檔案的完整路徑。

start---指定何時啟動服務,常用取值如下:

         1)service_boot_start(0)---在系統引導期間加載;

         2)service_auto_start(1)---在系統啟動期間啟動

         3)service_demand_start(2)---scm根據使用者的要求顯式加載

type---指定服務類型。若為核心驅動,則設為service_kernel_driver(1)。

服務本體程式設計:

在設計服務程式時必須滿足特定函數調用的流程。首先調用main(),然後調用startservicectrldispatcher()把向servicemain()的指針傳遞給scm,scm可以通過該指針啟動服務,servicemain()産生服務狀态句柄并注冊handler()。是以服務主體程式一般由main(),servicemain()和handler()3部分組成。

1、服務控制函數handler()

服務控制函數handler()中包含一個switch語句,它用于分發由scm發送的5個控制通知事件即:service_control_stop, service_control_pause, service_control_continue, service_control_interrogate, service_control_shutdown,使用者分别對這些通知事件進行相應處理,然後将處理後的新服務的最新狀态消息發送給scm:

//分發從scm獲得的控制通知事件,并對控制通知事件進行處理

void winapi handler(dword opcod)

{

       switch(opcod)

       {

              case service_control_stop: //處理停止服務事件

                     //usercode();       //使用者加入自己的代碼

                     ss.dwwin32exitcode = 0;     //設定服務的出錯代碼

                     ss.dwcurrentstate = service_stopped;

                     ss.dwcheckpoint = 0;

                     ss.dwwaithint = 0;

                     setservicestatus(ssh, &ss); //必須随時更新資料庫中service的狀态

                     break;

              case service_control_interrogate:

                     setservicestatus(ssh, &ss); //更新資料庫中的service狀态

              ......

       }

}

2、servicemain()

servicemain()是service的真正入口點,必須在main()中進行正确的定義。它的作用是:

1)建立狀态句柄servicestatushandle和服務控制函數handler;

2)向scm發送服務啟動狀态資訊并建立服務所在的線程;

3)建立終止事件句柄以控制服務的停止。

servicemain()的兩個參數是由startservice()傳遞過來的,在servicemain()中應該立即調用registerservicectrlhandler()注冊一個handler去處理控制程式或者控制台對service的控制要求:

void winapi servicemain(dword dwargc, lptstr *lpszargv)

       //調用registerservicectrlhandler()獲得服務狀态句柄并注冊生成handler

       ssh = registerservicectrlhandler(service_name, handler);

       //提供單個服務還是多個服務

       ss.dwservicetype = service_win32_own_process | service_win32_share_process;

       //等使用者程式完成後在目前運作狀态設為service_running

       ss.dwcurrentstate = service_start_pending;

       //目前能接受的是停止指令

       ss.dwcontrolaccepted = service_accept_stop;

       //服務的出錯代碼

       ss.dwwin32exitcode = no_error;

       //置服務在啟動/關閉/運作操作中反映操作進度

       ss.dwcheckpoint = 0;

       //置服務在啟動/關閉/運作操作時将持續的時間

       ss.dwwaithint = 0;

       //必須随時更新資料庫中的service的狀态資訊

       setservicestatus(ssh, &ss);

       //usercode() here

       ss.dwservicetype = service_win32_own_process | service_interactive_process;

       ss.dwcurrentstate = service_running;

       ss.dwcontrolsaccepted = service_accept_stop;

3、main()

main()是服務的主線程,先填充服務線程入口表service_table_entry,當scm開始一個服務時,它總是等待這個服務去調用startservicectrldispatcher()函數。main()作為這個程序的主線程應該在程式開始後盡快調用startservicectrldispatcher(),該函數被調用後并不立即傳回,它把本service的主線程連接配接到scm,進而讓scm通過這個連接配接發送開始、停止、暫停和繼續等控制指令給主線程。startservicectrldispatcher()在整個service結束時才傳回。

下面是主線程的部分實作代碼:

#include <windows.h>

service_status_handle ssh;   //定義于scm通訊的服務狀态句柄²

char *service_name = "aceservice";      //定義service線程的名字

service_status ss; //定義服務狀态标志

int main(int argc, char *argv[])

       service_table_entry ste[2];    //填充service_table_entry結構

       ste[0].lpservicename = service_name;  //線程的名字

       ste[0].lpserviceproc = servicemain;      //線程入口位址

       ste[1].lpservicename = null;       //最後必須是null

       ste[1].lpserviceproc = null;

       startservicectrldispatcher(ste); //将新的service的servicemain的指針

                                   //傳遞給scm,實作新的service在scm中的注冊

       return 0;      

新的main()函數由scm來調用,負責用scm來注冊服務,以及啟動服務控制排程程式。這需要使用一個或者多個邏輯服務的名稱和入口點來調用函數startservicectrldispatcher:

bool startservicectrldispatcher(

       lpservice_table_entry lpservicestarttable);

參數lpservicestarttable是service_table_entry條目數組的位址,而每個條目時邏輯服務名稱和入口點,數組的結尾由一對null條目來訓示。

如果注冊成功則傳回true,如果服務已經在運作或者更新系統資料庫(hkey_local_machine/system/currentcontrolset/services)時有問題,就會出現錯誤。調用startservicectrldispatcher服務程序的主線程把線程連接配接到scm。而scm用調用線程作為服務控制排程線程來注冊服務。scm不會傳回到調用線程,直到所有的服務都終止了。

另一種類型的帶有單個邏輯服務的服務主程式如下:

#include <...>

void winapi servicemain(dword argc, lptstr argv[]);

static lptstr servicename = _t("acecommandlineservice");

void _tmain(int argc, lptstr argv[])

       service_table_entry dispatchtable[] =

              {servicename, servicemain},

              {null, null}

       };

       if(!startservicectrldispatcher(dispatchtable))

              //servicemain() will not run until started by the scm

              //return here only when all services have terminated.

              reporterror(_t("failed"), 1, true);

       return;

scm調用的服務控制處理器必須能夠控制相關的邏輯服務,每個邏輯服務必須使用registerservicectrlhandlerex函數立即注冊處理器。函數的調用應該在servicemain()的開頭處,而且不需再次調用。scm在接收到服務控制請求後調用處理器:

service_status_handle registerservicectrlhandleex(

       lpctstr lpservicename,

       lphadnle_function_ex lphandlerproc,

       lpvoid lpcontext);

參數lpservicename—是使用者在服務表條目中提供的邏輯服務名稱;

lphandlerproc—是擴充處理器函數的位址;

lpcontext—是傳遞給控制處理器的使用者自定義資料,允許單個控制處理器使用相同的處理器來區分多個服務。

現在處理器已被注冊,接着使用setservicestatus來把服務狀态設定成service_start_pending。服務控制處理器每個調用時必須設定狀态,即使狀态沒有改變:

bool setservicestatus(

       service_status_handke hservicestatus,

       lpservice_status lpservicestatus);

參數hservicestatus—是由registerservicectrlhandlerex傳回的service_status_handle。是以,registerservicectrlhandlerex調用必須在setservicestatus調用之前。

lpservicestatus—指向service_status結構,描述了服務屬性、狀态和特性。

service_status結構定義如下:

typedef struct _service_status

       dword dwservicetype;       //服務類型,可能是service_kernel_driver等

       dword dwcurrentstate;       //服務目前狀态

       dword dwcontrolaccepted;      //服務能夠接收的控制代碼

       dword dwwin32exitcode;       //出錯代碼,當服務啟動或停止時它使用該參數報告一個錯誤

       dword dwservicespecificexitcode;   //當錯誤發生時,服務傳回的服務相關的錯誤代碼

       dword dwcheckpoint; //當服務啟動時,每完成一步它就增加這個值

       dword dwwaithint;    //未決的啟動、停止等操作花費的時間

}service_status, *lpservice_status;

參數dwwin32exitcode是邏輯服務的線程退出碼。而服務在運作或者正常終止時必須将其設為no_error;

當服務啟動或者停止時,dwservicespecificexitcode可以用來訓示錯誤,但是該值會被忽略;除非dwwin32exitcode被設定成error_service_specific_error;

dwcheckpoint應該由服務周期性地增加,以報告含初始化和關閉的所有步驟的過程。如果服務沒有啟動、停止、暫停或者繼續挂起,則該值是非法的,為0;

dwwaithint是用dwcheckpoint變量的增加值或者dwcurrentstate的改變值分别調用setservicestatus之間所花費的時間毫秒值;

服務類型dwservicetype取值如下:

service_win32_own_process    表示windows服務利用自己的資源在自己的程序中運作

service_win32_share_process       表示windows服務于其他服務共享程序以便多個服務可以共享資源、環境變量等

service_kernel_driver           表示windows裝置驅動器

service_file_system_driver 指定windows檔案系統驅動器

service_interactive_process      表示某windows服務程序可以通過桌面與使用者互動

參數dwcurrentstate表示目前服務的狀态:

service_stopped        服務沒有運作

service_start_pending 服務正在啟動中,但是還沒有準備好響應請求

service_stop_pending   服務正在停止中,但是還沒有完成關閉

service_running     服務在運作

service_continue_pending        在服務處于暫停狀态後服務繼續處于挂起狀态

service_pause_pending          服務暫停處于挂起狀态,但是服務沒有完全處于暫停狀态

service_paused 服務被暫停

參數dwcontrolsaccept指定了服務接受并由服務控制處理器處理的控制代碼:

service_accept_stop      啟用service_control_stop

service_accept_pause_continue           啟用service_control_pause和service_control_continue

service_accept_shutdown                當發生系統關閉時通知服務,這使得系統可以向服務發送service_control_shutdown值

service_accept_paramchange     該啟動參數可以無需重新開機就可改變,通知是service_control_paramchange

服務控制處理器,即registerservicectrlhandlerex中指定的回調函數具有如下形式:

dword winapi handlerex(

       dword dwcontrol,

       dword dweventtype,

       lpvoid lpeventdata,

參數dwcontrol表示由scm發送的應該處理的實際控制信号;它有14中可能值,如service_control_stop、service_control_pause...。

參數lpcontext是處理器被注冊時傳遞給registerservicectrlhandlerex的使用者自定義資料。

下面是編寫服務的例子:

#define update_time 1000

void logevent(lpctstr, dword, bool);

void winapi serverctrlhandlerex(dword, dword, lpvoid, lpvoid);

void updatestatus(int ,int);     //calls setservicestatus

int servicespecific(int, lptstr *);         //former main program

volatile static bool shutdown = false;

volatile static bool pauseflag = false;

static service_status hservstatus;

static service_status_handle hsstat;         //handle to set status

static lptstr logfilename = _t("commandlineservicelog.txt");

//main routing that starts the service control dispatcher

       startservicectrldispatcher(dispatchtable);

       return 0;

//servicemain entry point, called when the service is created

void winapi servicemain(dword argc, lptstr argv[])

       dwrod i;

       dword context = 1;

       //set the current directory and open a log file

       //appending to an existing file

       //set all server status data members

       hservstatus.dwservicetype = service_win32_own_process;

       hservstatus.dwcurrentstate = service_start_pending;

       hservstatus.dwcontrolaccepted = service_accept_stop |

              service_accept_shutdown | service_accept_pause_continue;

       hservstatus.dwwin32exitcode = error_service_specific_error;

       hservstatus.dwservicespecificexitcode = 0;

       hservstatus.dwcheckpoint = 0;

       hservstatus.dwwaithint = 2*cs_timeout;

       hsstat = registerservicectrlhandlerex(servicename,

              servicectrlhandler, &context);

       setservicestatus(hsstat, &hservstatus);

       //start service-specific work;generic work is complete

       if(servicespecific(argc, argv) != 0)

              hservstatus.dwcurrentstate = service_stopped;

              hservstatus.dwservicespecificexitcode = 1;

              //server initialization failed

              setservicestatus(hsstat, &hservstatus);

              return;  

       //we will only return here when the servicespecific function

       //completes, indicating system shutdown

       updatestatus(service_stopped, 0);

//set a new service status and checkpoint

//either specific value or increment

void updatestatus(int newstatus, int check)

       if(check < 0)

              hservstatus.dwcheckpoint++;

       else

              hservstatus.dwcheckpoint = check;

       if(newstatus >= 0)

              hservstatus.dwcurrentstate = newstatus;

//control handler function,invoked by the scm to run

//in the same thread as the main program

void winapi serverctrlhandlerex(dword control, dword eventtype,

       lpvoid lpeventdata, lpvoid lpcontext)

       switch(control)

              case service_control_shutdown:

              case service_control_stop:

                     shutdown = true;

                     updatestatus(service_stop_pending, -1);

              case service_control_pause:

                     pauseflag = true;

              case service_control_continue:

                     pauseflag = false;

              default:

                     if(control > 127 && control < 256)//user defined

                            break;   

       }     

       updatestatus(-1, -1);  //increment checkpoint

//this is the service-specific function,or "main",and is

//called from the more generic servicemain.

int servicespecific(int argc, lptstr argv[])

       updatestatus(-1, -1);

       //---initialize system---

       //be sure to update the checkpoint periodically

服務控制程式設計

控制程式一般是一個普通的win32應用程式,用于對服務進行啟動和停止等操作。

首先的打開scm,擷取句柄然後允許建立服務:

sc_handle openscmanager(

       lpctstr lpmachinename, //為null表示scm在本地系統,也可以通路網絡上的scm

       lpctstr lpdatabasename,    //通常為null

       dword dwdesiredaccess);   

注冊服務使用函數createservice:

sc_handle createservice(

       sc_handle hscmanager,     //由openscmanager獲得的sc_handle

       lpctstr lpservicename, //指定要安裝的服務的名稱。

//此字元串對應服務注冊處中一個子鍵的名稱

       lpctstr lpdisplayname,       //使用者界面程式使用的辨別該服務的名稱

                                   //它對應服務注冊處lpservicename子鍵下displayname的鍵值

       dword dwdesiredaccess,     //通路權限

       dword dwservicetype, //服務類型,對應服務注冊處lpservicename子鍵下type的鍵值

       dword dwstarttype,    //指定何時啟動此服務。如果打算通過指令啟動,可以傳遞

//service_demand_start,如果打算讓此服務在系統引導

//時自動啟動,傳遞service_auto_start,它對應

    //系統資料庫中的start鍵值

       dword dwerrorcontrol,      //指定驅動啟動失敗這個錯誤的嚴重性。

//service_error_ignore表示忽略所有錯誤;

//service_error_normal表示記錄下可能出現的錯誤。

//此參數對應系統資料庫中的errorcontrol鍵值

       lpctstr lpbinarypathname, //驅動二進制檔案(.sys檔案)的路徑,

//對應系統資料庫中的imagepath鍵值

       lpctstr lploadordergroup, //指定此服務所屬的加載順序組的名字

       lpdword lpdwtagid,   //指定一個在lploadordergroup組中唯一的标簽

       lpctstr lpdependencies,       //指定一組此服務依靠的服務的名字

       lpctstr lpservicestartname,       //指定此服務應該運作在哪一個賬戶下

       lpctstr lppassword);     //指定lpservicestartname賬戶對應的密碼

注冊後新的服務位于系統資料庫項中:

服務建立後并不立即執行,我們通過指定createservice函數擷取的句柄,以及服務主函數的argc、argv指令行參數,可以啟動servicemain()函數:

bool startservice(

       sc_handle hservice,      //指定打開的服務,這是openservice或createservice傳回的句柄

       dword argc,    //對裝置驅動來說,此參數永遠是null

       lptstr argv[]);//對驅動服務來說,此參數應該設為null

通過告知scm使用指定控制選項來調用服務控制處理器,即可控制服務:

bool controlservice(

       sc_handle hservice,      //服務句柄

       dword dwcontrolcode,      //向服務發送的控制代碼

//要停止服務,應發送service_control_stop

       lpservice_status lpservstat);   //傳回服務的狀态

使用以下方式可以獲得service_status資料結構中服務的目前狀态值:

bool queryservicestatus(

       sc_handle hservice,      //要查詢服務的句柄

       lpservice_status lpservicestatus);   //用于傳回狀态資訊

執行個體代碼如下:

void acecreate()       //建立服務

       sc_handle scm;      //打開指定服務

       sc_handel svc;      

       //打開scm資料庫并傳回句柄

       scm = openscmanager(null, null, sc_manager_create_service);

       if(scm != null)

              svc = createservice(scm, "aceservice", "ace's windows service",

                            service_all_access,

                            service_win32_own_process | service_interactive_process,

                            service_auto_start, service_error_ignore,

                            "c://servicefilename.exe",      //service本體程式路徑

                            null, null, null, null, null);

              if(svc != null)

                     closeservicehandle(svc);  //關閉新服務句柄

              closeservicehandle(scm); //關閉scm資料庫句柄

void acedelete() //删除服務

       sc_handle scm;

       sc_handle svc;

       service_status servicestatus;

       //指定服務控制管理器建立連接配接并打開服務管理資料庫

       scm = openscmanager(null, null, sc_manager_connect);

              queryservicestatus(svc, &servicestatus);      //傳回服務狀态,儲存到狀态結構變量中

              if(servicestatus.dwcurrentstate == service_running)

              {

                     //删除之前先得停止服務

                     controlservice(svc, service_control_stop, &servicestatus);      

                     deleteservice(svc);     //向服務發送控制代碼

                     closeservicehandle(svc); //删除service後,最後再調用closeservicehandle()

                                          //以便立即從資料庫中移走此條目

              }                   

              closeservicehandle(scm);