(4) Discussion on some problems
The content of the previous chapter is some of the general writing principles of service, but there are some problems. If you write simple services, you can't see it, but there will be some problems with complex applications, so this chapter is used to analyze , Solve these problems, applicable to senior applications. The content of my chapter is experimentally obtained, and it is very practical.
I said in the first chapter, execute the Ctrlhandler function by the main thread of a service, which will receive a variety of control commands, but the command is really handled, and the operation is the thread of ServiceMain. Now, when a service_control_stop arrives, how do you stop this service as a developer? In some source code I have seen, most of them just simply call the TerminateThread function to force the service process. However, it should be a bit a little bit of thread programming. You should know that the TerminateThread function is the most bad one in the available calls, the service thread will not have any opportunity to do clear work, such as clearing memory, release core objects, DLLS is not Notifications have been destroyed to any thread.
So the appropriate way to stop the service is to activate the service thread in some way, let it stop continue providing service functions, and then returns the current operation and clearing. This means that you must perform appropriate thread communication between CtrlHandler threads and serviceMain threads. The best internal thread communication mechanism known now is I / O Completion Port. If you have written a large service, you need to process a number of requests, and run in multiprocessor system Above, this model can provide the best system performance. But it is also because of its high complexity, it is not worth spending much time and effort in small-scale applications. At this time, as a developer can select other communication methods, such as asynchronous process call queues, sockets, and Window messages to adapt to the actual situation.
Another important issue when developing services is to call all status report issues when the SetServiceStatus function is called. Many service developers often argue in order to invoke setServiceStatus, generally recommended methods are: first call the setServiceStatus function, report service_stop_pending status, then pass control code to the service thread or build a new thread, let It continues to perform the operation, and then set the status of the service to service_stopped before the thread is about to perform the operation, and then the service is just stopped.
The above idea is still very nice from two aspects. First, the service can immediately confirm that the control code is received and will be processed when it thinks is appropriate; then because of the previously said, the executive CtrlHandler function is the main thread, if this work method, the CtrlHandler function can return quickly. It does not affect the control requests that other services may receive. For programs with multiple services, the speed of the control code in response to each service will be greatly improved. However, the problem is that the problem - RACE CONDition is the result of "competitive conditions".
The following is an example of a competitive condition. I spent a little time to modify the code of my basic service, intended to deliberately trigger the occurrence of "competitive conditions". I added a thread that the thread of the CtrlHandler function will respond immediately after receiving the request, set the current service status to "request is being processed" ie ..._ pending, then the thread I added is 5 seconds after sleeping Then set the service status to "Request Completed" Status - A simulation service is being processed some distortion events, and only the status of the service will only be changed after processing is completed. After everything, I tried to send two "pause" requests in a short period of time. If "Competitive Condition" does not exist, the request to be sent can only reach the SCM, and the other should return information, information, and the request. Send failed. The world is peaceful. In fact, very unfortunate, I succeeded. When I set the following commands in two different "command prompt" windows:
NET PAUSE KSERVICE
After that, I found my service record in "Application Log" in "Application Log", the result is that I got this list of events:
Service_pause_pending service_pause_pending service_paused service_paused
Is it very strange? Because the service is being paused, it should not be paused again. But in the fact that many services have been explicitly reported. I have thought that SCM should say something or do, to prevent the appearance of "competition state", but the experimental results tell me that SCM seems to be powerful because it cannot control the status code when it is sent. When the user uses the "Service" tool inside "Administrative Tool" to manage the status of the service, it cannot be "pause" requests to it after a "pause" request has been issued, and if the service is being suspended, there will be A dialog box appears to prevent you from pressing any button on the toolbar of the "Services" tool behind it, if the "Pause" button will become gray. However, at this time, use the command line tool Net.exe to send the suspension request again to the service. The evidence is that the other event records I added will record the calls of the setServiceStatus, which further illustrates the two pause requests I submitted through the SCM and then reached my service.
Next, I have made other tests, such as first send "pause" request, and send "stop" requests, and send "stop" requests, then send "Pause" or "Stop" request. The previous situation is even worse, the "suspension" request and the "stop" request sent first, the "stop" request did not get a good end, although the SCM is old and the service is suspended, and the service is stopped, but Net.exe The call of the two instances failed. However, when the test is first sent to the "request", all the phenomena indicate that the two requests have only been sent by the "stop" to the SCM, this is also a good news ...
In order to solve this problem, when the service gets a "stop" "pause" or "Continue" request, you should first check if the service is already processed, if so, it is dependent: Yes, it is not called the setServiceStatus direct Return or temporarily endure until the previous request action completes the call setServiceStatus, this is what you have to decide by a developer.
If the question in front is troublesome, the following problems will make you feel more weird. It is actually a way to solve the above problem: When the thread of the CtrlHandler function receives the service_pause_pending request, it calls the SetServiceStatus report service is being suspended, then calls SuspendThread to suspend the thread of the service, then call it itself. The SetServiceStatus report service has been paused. Doing this avoids the emergence of "competitive conditions" because all work is made by a function. It is not a "competitive condition" that needs to be paid now, but the thread hangs will not be suspended. The answer is it. But what is the suspension of service means? If my service is used to handle the request for network customers, then paused for my service should be stopped to accept new requests. What should I do if I have now dealing with the request? Maybe I should end it, so that the customer does not hang indefinitely. But if I just call SuspendThread, the service thread is not eliminated in an isolated intermediate state, or is calling the malloc function to try allocated memory, if another service running in the same process also adjusts the memory allocation function, Then it will be hanged, which is certainly not the result I expect.
There is also a question: Does users think that they can be allowed to stop a service that has been suspended? I think it is like this, and it is obvious that Microsoft thinks. Because when we select a paused service in the Services management tool, the "Stop" button can be pressed. But how do I stop a service that is suspended because the thread is suspended? No, don't TerminateThread, please don't mention it with me.
The best way to solve all the confusion is that there is a thread that can make all things, and it should be a service thread, not a ctrlhandler thread. When the CtrlHandler function gets a control code, it will quickly send the control code to the service thread through the thread internal communication means, and then the CtrlHandler function should return, it will never adjust the setServiceStatus. In this way, the service can control everything as you want, because there is nothing more says than it, there is no "competitive condition". The service decision is suspended, and the service can allow himself to stop in the case of suspended, and the service decides what internal communication mechanism is the best - and the CtrlHandler function must be simpler than this mechanism.
There is not perfect, the above method is no exception, it has only one small defect: it is assumed that the response can be made in a shorter time when the service receives the control code. If the service thread is busy with the request of a customer, the control code may enter the waiting queue, and the setServiceStatus may also be called quickly. If this is true, the SCP responsible for sending notifications may think that your service has failed and report a message box to the user. In fact, the service has not failed, and it will not be terminated.
This situation is worse, no user will blame SCP - although the SCP will guide them to the wrong state, they will only blame the author of the service - it is me or you ... therefore, how to prevent it in the service What happened to this problem? Very simple, make the service quickly and effectively, and always keep an active thread waiting to process the control code.
It seems that it is easy to say, but actually is so simple, this is not what I can explain to you, only to debug your own service, you can find the most suitable method. So my article is really at the end, thank you for your browsing. If I have something wrong, please let me know, thank you. Here is the source code I wrote, there is no function, you can only start, stop and install.
#include
#define szappname "BasicService" #define szservice "kservice" #define szservicedisplayName "kservice" #define szdependencies ""
Void WinAPI KServicemain (DWORD Argc, LPTSTR * Argv); Void InstallService (const char * szservice); void Logevent (LPCTSTR PFORMAT, ...); void start (); void stoc ();
Service_status ssstatus; service_status_handle sshstatushandle;
Int main (int Argc, char * argv []) {if ((argc == 2) && (:: strcmp (argv [1] 1, "install") == 0)) {InstallService ("kservice") Return 0;}
Service_table_entry service_table_entry [] = {{"kservice", kservicemain}, {null, null}}; :: StartServiceCtrldispatcher (service_table_entry); return 0;}
void InstallService (const char * szServiceName) {SC_HANDLE handle = :: OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS); char szFilename [256]; :: GetModuleFileName (NULL, szFilename, 255); SC_HANDLE hService = :: (handle CreateService, szServiceName , szServiceName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, szFilename, NULL, NULL, NULL, NULL, NULL); :: (hService) CloseServiceHandle; :: (handle) CloseServiceHandle;}
Service_status serviceArstatus; service_status_handle servicetatushandle;
Void WinAPI ServiceCtrlHandler (DWORD DWCONTROL) {switch (dwect) {
// Although the following is a pause, continue, etc., but there is no practical role // this is why? The following KServiceMain to function inside will understand ... case SERVICE_CONTROL_PAUSE: servicestatus.dwCurrentState = SERVICE_PAUSE_PENDING; // TODO: add code to set dwCheckPoint & dwWaitHint // This value need to try a lot to confirm // ... :: SetServiceStatus (servicestatushandle, & servicestatus); // TODO: add code to pause the service // not called in this service // ... servicestatus.dwCurrentState = SERVICE_PAUSED; // TODO: add code to set dwCheckPoint & dwWaitHint to 0 break;
case SERVICE_CONTROL_CONTINUE: servicestatus.dwCurrentState = SERVICE_CONTINUE_PENDING; // TODO: add code to set dwCheckPoint & dwWaitHint :: SetServiceStatus (servicestatushandle, & servicestatus); // TODO: add code to unpause the service // not called in this service // .. ServicesTatus.dwcurrentState = service_running; // Todo: Add code to set dwcheckpoint & dwwaithint to 0 break;
case SERVICE_CONTROL_STOP: servicestatus.dwCurrentState = SERVICE_STOP_PENDING; // TODO: add code to set dwCheckPoint & dwWaitHint :: SetServiceStatus (servicestatushandle, & servicestatus); // TODO: add code to stop the service Stop (); servicestatus.dwCurrentState = SERVICE_STOPPED; / / TODO: Add code to set dwcheckpoint & dwwaithint to 0 break;
Case service_control_shutdown: // Todo: add code for system shutdown // as quick as possible break;
case SERVICE_CONTROL_INTERROGATE: // TODO: add code to set the service status // ... servicestatus.dwCurrentState = SERVICE_RUNNING; break;} :: SetServiceStatus (servicestatushandle, & servicestatus);} void WINAPI KServiceMain (DWORD argc, LPTSTR * argv) { servicestatus.dwServiceType = SERVICE_WIN32; servicestatus.dwCurrentState = SERVICE_START_PENDING; servicestatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; // answers to the above questions is here servicestatus.dwWin32ExitCode = 0; servicestatus.dwServiceSpecificExitCode = 0; servicestatus.dwCheckPoint = 0; servicestatus.dwWaitHint = 0;
ServicesTATUSHANDLE = :: RegisterServiceCtrlHandler ("KSERVICE", ServiceCtrlHandler; if (ServiceStatusHandle == (Service_Status_Handle) 0) {Return;}
Bool Binitialized = false; // initialize the service // ... start ();
Binitialized = True;
servicestatus.dwCheckPoint = 0; servicestatus.dwWaitHint = 0; if (bInitialized!) {servicestatus.dwCurrentState = SERVICE_STOPPED; servicestatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; servicestatus.dwServiceSpecificExitCode = 1;} else {servicestatus.dwCurrentState = SERVICE_RUNNING;} :: SetServiceStatus ( ServiceStatushandle, & service;
Void Start () {LOGEVENT ("Service Starting ...");
Void Logevent (LPCTSTR PFORMAT, ...) {Tchar chmsg [256]; handle heventsource; lptstr lpszstrings [1]; va_list Parg;
VA_START (PARG, PFORMAT); _VSTPRINTF (CHMSG, PFORMAT, PARG); VA_END (PARG);
LPSZSTRINGS [0] = CHMSG;
if (1) {// Get a handle to use with ReportEvent () hEventSource = RegisterEventSource (NULL, "KService");. if (! hEventSource = NULL) {// Write to event log ReportEvent (hEventSource, EVENTLOG_INFORMATION_TYPE, 0. , NULL, 1, 0, (LPCTSTSTR *) & lpszstrings [0], null); derecistereventsource;}} else {// as we are not running as a service, just write the error to the console. _Putts (CHMSG);}