There is such an application, which is capable of managing the ability to manage various users (including local users and remote users), and whether the user is physically connected to computers that are running the application. Normal execution, this is the so-called service.
(1) Basic knowledge of services
Question 1. What is a service? What is its characteristics?
In NT / 2000, the service is a program that is subject to operating system. A service is first a Win32 executable, if you want to write a full-service and powerful service, you need to be familiar with the Dynamic Library (DLLS), the structure exception handling, memory map file, virtual memory, device I / O, thread and synchronization , Unicode and other application interfaces provided by the WinAPI function. Of course, this article discusses only a service that can be installed, run, started, stopped without any other functions, so you can continue to look at it without the above knowledge, I will understand the knowledge required by this article in the process.
The second question is that a service will never require a user interface. Most of the services will run on the powerful servers that are locked in some dark, winter warm summer cool, even if there is a user interface, no one can see. If the service provides any user interface such as a message box, the possibility of user misses these messages is extremely high, so the service program is usually written in the form of a console program, and the entry point function is main () instead of WinMain ().
Some people may have questions: how to set up without a user interface, how to manage a service? How to start, stop it? How to make a warning or error message, how to report statistics about its implementation? The answer to these issues is that the service can be managed by remote management, and Windows NT / 2000 provides a large number of management tools that allow other computers on the network to manage the services above a machine. For example, the "Console" program (MMC.exe) inside Windows 2000, you can manage the service on this unit or other machine with it to add "Administrative Unit".
Question 2. Service security ...
If you want to write a service, you must be familiar with the security mechanism of WIN NT / 2000. In the above operating system, all security is based on users. In other words - process, thread, file, registry key, signal, event, etc. belong to one user. When a process is generated, it is executing the context of a user (CONTEXT), which may be in this unit, or may be on other machines in the network, or in a special account: System Account- - ie the context of the system account
If a process is executing under a user account, then this process also has all access rights that this user can have, whether it is in this unit or a network. The system account is a special account, which is used to identify the system itself, and any process running under this account has all access rights on the system, but the system account cannot be used in the domain, and it is impossible to access network resources ...
The service is also the Win32 executable, which also needs to be executed in a context, and the usual service is running under the system account, but it can also be used to run in a user account according to the situation, and will also obtain the corresponding access resource. Permission.
Question 3. Three components of the service
A service consists of three parts, the first part is Service Control Manager (SCM). Each Windows NT / 2000 system has an SCM. SCM is stored in Service.exe, which is automatically run when Windows is started, accompanied by the startup and shutdown of the operating system. This process runs in system privilege and provides a unified, secure means to control service. It is actually an RPC Server, so we can install and manage services remotely, but this is not within the scope of this article. The SCM contains a database that stores information that has been installed and drivers. The SCM can be unified, securely managed this information, so the installation process of a service program is to write their own information to this database. The second part is the service itself. A service has a special code necessary to receive signals and commands from SCM, and can pass its state back to SCM after processing.
The third part is the last part, is a Service Control Dispatcher (SCP). It is a user interface that allows users to start, stop, suspend, continue, and control one or more Win32 applications installed on a computer. The role of SCP is to communicate with SCM communications, "Services" in the Windows 2000 management tool is a typical SCP.
In these three components, the user is most likely to write the service itself, and it may also have to write a client program that is accompanied by the client program as an SCP and SCM communication. This article only discusses the design and implementation of a service, about how to go Implementing an SCP is introduced in other articles in future.
Question 4. How to start design services
Remember that the entry point function I mentioned earlier is generally main ()? One service has a very important three functions, the first is the entry point function, but use WinMain () as the entry point function, although the service should not have a user interface, but there are few exceptions. This is the reason for the options in the drawings below.
Because information interaction with the user desktop, the service program sometimes uses WinMain as an entry point function.
The entrance function is responsible for initializing the entire process, executed by the main thread in this process. This means it is applied to all services in this executable. To know, a plurality of services can be included in an executable file such that it is more effective. The primary process notifies that SCM contains several services in the executable, and gives the address of each service's ServiceMain Tune (Call Back) function. Once all services within the executable have stopped running, the main thread clears the entire process before the process terminates.
The second very important function is Servicemain, I have seen some examples of the entry point function of their services in some cases, which is fixed to servicemain, in fact, there is no provision, any function, as long as the following form is in line with the following form Can be used as a service entry point function.
Void WinAPI Servicemain (DWord dwargc, // parameter number LPTSTR * LPSZARGV / / parameter string);
This function is called by the operating system and performs code that can complete the service. A dedicated thread executes the servicemain function for each service, pay attention to the service instead of the service, because each service has a servicemain function that is the only corresponding to yourself, and the service in "Management Tool" "Go to see the services that come into Win2000, it will find that many services are provided separately by Service.exe. When the main thread calls the Win32 function StartServiceCtrLDispatcher, SCM generates a thread for each service in this process. Each of these threads is executed with its corresponding service serviceMain function, which is the reason why the service is always multi-thread - an executable file with only one service will have a main thread, other thread execution services itself. The third is the last important function is CtrlHandler, which must have the following prototype:
Void WinApi CtrlHandler (DWord FDWControl // Control Command)
Like ServiceMain, CtrlHandler is also a callback function, and the user must write a separate CtrlHandler function for each service in its service, so if there is a program contains two services, then it is at least 5 different functions: Main () or WinMain () of the entry point, for the first service serviceMain function and CtrlHandler function, and servicemain functions for the second service and CtrlHandler functions.
SCM calls a service CtrlHandler function to change the status of this service. For example, when a "service" in an administrator is trying to stop your service, your service's CtrlHandler function will receive a service_control_stop notification. The CtrlHandler function is responsible for performing all the code required to stop the service. Since all CtrlHandler functions are performed, you must try to optimize your CtrlHandler function, so that it runs enough to run so fast so that the CtrlHandler function of other services in the same process can receive them within the appropriate time. announcement of. And based on the above reasons, your CTRLHANDLER function must be able to send the state you want to convey to the service thread. This transfer process does not have a fixed method, which is completely dependent on your service.
(2) In-depth discussion of services In the entry point function to complete the initialization of ServiceMain, accurate point is to initialize a service_table_entry structure array, this structure records all the names and services of all services contained in this service program. Point function Here is an example of a service_table_entry:
Service_table_entry service_table_entry [] = {{"myftpd", ftpdmain}, {"myhttpd", httpserv}, {null, null},};
The first member represents the name of the service, the second member is the address of the servicemain callback function, because the service program has two services, so there are three service_table_entry elements, the first two for service, the last NULL indicates the end of the array .
Next, the address of this array is passed to the StartServiceCtrldispatcher function:
Bool StartServiceCtrlDispatcher (lpservice_table_entry lpservicestarttable)
This Win32 function indicates how the executable process notifies the SCM to include services in this process. As in the previous chapter, StartServiceCtrldispatcher generates a new thread for each non-empty element passed to its array, and each process begins executing the servicemain function indicated by the LPServiceStAble in array elements. After the SCM launches a service, it will wait for the main thread of the program to turn StartServiceCtrLDispatcher. If that function is not called within two minutes, SCM will think that this service has a problem, and calls TerminateProcess to kill this process. This requires your main thread to call StartServiceCtrlDispatcher as quickly as possible.
The StartServiceCtrlDispatcher function does not return immediately, and it will reside in a loop. When it is in the cycle, StartServiceCtrldispatcher hangs yourself and waits one of the following two events. First, if the SCM is going to send a control notification to a service running in this process, this thread is activated. When the control notification arrives, the thread activates and calls the CtrlHandler function of the corresponding service. The CtrlHandler function handles this service control notification and returns to StartServiceCtrldispatcher. StartServiceCtrlDispatcher loops back and hangs yourself again.
Second, if a service in the service thread is aborted, this thread will also be activated. In this case, the process will be running in its number of services. If the number of services is zero, StartServiceCtrLDispatcher returns to the entry point function to enable anything related to the process and end the process. If there is also a service run, even a service, StartServiceCtrldispatcher will continue to loop and continue to wait for other control notifications or remaining service threads.
The above content is about the entry point function, the following content is about the servicemain function. Still remember the prototype of the previous servicemain function? But in fact, a servicemain function is usually ignored two parameters passed to it, because the service is generally not very transmitted. To set up a service is to set the registry, the general service stores its own settings under the hkey_local_machine / system / currentControlSet / Service / ServiceName / Parameters subkey, where ServicesName is the name of the service. In fact, it may be necessary to write a client application to perform the background settings of the service, this client application exists in the registry so that the service is read. When an external application has changed the setting data of a service in run, this service can be used to accept a notification with the RegNotifyChangeKeyValue function, so that the service is quickly resetting yourself.
As mentioned earlier, StartServiceCtrldispatcher generates a new thread for each non-empty element passed to its array. Next, what is a servicemain? MSDN inside the original had this to say: The ServiceMain function should immediately call the RegisterServiceCtrlHandler function to specify a Handler function to handle control requests Next, it should call the SetServiceStatus function to send status information to the service control manager why.? Because after the start of the service request, if the service is not completed within a certain time, the SCM will consider the service's startup has failed. The length of this time is 80 seconds in Win NT 4.0, and the Win2000 is not met ... Reasons, ServiceMain will quickly complete their work, first of all, the second work, the first item is to call the RegisterServiceCtrlHandler function to inform the SCM's address of its CtrlHandler callback function:
Service_Status_Handle RegisterServiceCtrlHandler (LPCTSTR LPSERVICENAME, // "LPHANDLER_FUNCTION LPHANDLERPROC // CtrlHandler function address)
The first parameter indicates which service you are being created, and the second parameter is the address of the CtrlHandler function. LPServiceName must match the name of the service that is initialized in service_table_entry. RegisterServiceCtrlHandler returns a service_status_handle, which is a 32-bit handle. SCM uses it to uniquely determine this service. When this service needs to report it to SCM at the time, this handle must be passed to the Win32 function that needs it. Note: This handle is different from most of the other handles, you don't need to close it.
SCM requires a serverMain function to call the RegisterServiceCtrlHandler function in one second, otherwise the SCM will believe that the service has failed. But in this case, SCM does not terminate the service, but this service will not be launched in NT 4, and an incorrect error message will be returned, which is corrected in Windows 2000.
After the RegisterServiceCtrlHandler function returns, the ServiceMain thread tells the SCM service immediately to continue to initialize. The specific method is to pass the service_status data structure by calling the SetServiceStatus function.
Bool SetServiceStatus (Service_Status_Handle Hservice, // Service Handle Service_Status LPServiceStatus // Service_Status structure) Address)
This function requests to pass the handle of the service (just getting the registerServiceCtrlHandler), and an address of an initialized service_status structure:
typedef struct _SERVICE_STATUS {DWORD dwServiceType; DWORD dwCurrentState; DWORD dwControlsAccepted; DWORD dwWin32ExitCode; DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint; DWORD dwWaitHint;} SERVICE_STATUS, * LPSERVICE_STATUS; SERVICE_STATUS structure contains seven members, they reflect the current state of the service. All of these members must be in the correct settings before this structure is passed to SetServiceStatus.
Members DWServiceType indicate the type of service executable. If there is only one separate service in your executable, set this member to service_win32_oen_process; if you have multiple services, set it to service_win32_share_process. In addition to these two markers, if your service needs to interact with the desktop (of course, it is not recommended), use the "OR" operator to attach service_interactive_process. The value of this member is absolutely should not change within your service life.
Members dwcurrentState are the most important members in this structure, which will tell SCM's current state of your service. In order to report the service is still initializing, this member should be set to service_start_pending. The other possible values are explained when the CtrlHandler function is specifically described later.
Members dwcontrolsaccepted indicates what kind of control notification is willing to accept. If you allow an SCP to pause / continue, set it to service_accept_pause_continue. Many services don't support pause or continue, you must decide whether it is available in your service. If you allow an SCP to stop service, set it to service_accept_stop. If the service is notified when the operating system is turned off, set it to service_accept_shutdown to receive the expected result. These marks can be combined with the "OR" operator.
Members DWIN32EXITCODE and DWSERVICESPECIFICEXITCODE are the key to allowing service report errors. If you want to serve a Win32 error code (predefined in WineError.h), it sets DWIN32Exitcode as required code. A service can also report that it is unique, not mapped to a predefined error in a predefined Win32 error code. For this, you have to set DWWIN32EXITCODE to Error_Service_Specific_ERROR, and then set the member dwservicespecifiXitcode for the service unique error code. When the service runs properly, there is no error to report, set the member dwwin32exitcode as NO_ERROR.
The last two members dwcheckpoint and DWWAITHINT are a service to report its current event progression. When member dwcurrentState is set to service_start_pending, DWCHECKPOINT should be set to 0, and DWWAITHINT is set to determine a relatively appropriate number after multiple attempts, so that the service can run efficiently. Once the service is fully initialized, the members of the service_status structure should be reinitial, change dwcurrentState to Service_Running, and then change DWCHECKPOINT and DWWAITHINT to 0.
The presence of dwcheckpoint members is beneficial to the user, it allows a service to report which step it is in the process. When you call setServiceStatus, you can add a number that it can indicate which step of the service has been executed, which helps users determine how long the progress is reported. If you decide every step to report the initialization process of the service, you should set DWWAITHINT to think that the number of milliseconds you need to reach the next step, not the number of milliseconds required to complete its process. After all the initialization of the service is completed, the service calls setServiceStatus indicates service_running, which is already running at that moment. Usually a service is running yourself in a loop. The service process in the loop is suspended, waiting for indicating that the next step should be paused, continued or stopped, such as network requests or notifications. When a request arrives, the service thread activates and processes this request, and then loops back to wait for the next request / notification.
If a service is activated due to a notification, it will process this notification first unless the service is notified by the stop or closure. If you really stop or close, the service thread will exit the loop, perform the necessary clear operation, then return from this thread. When the ServiceMain thread returns and abort, the thread activation in STARTSERVICECTRLDISPATCHER is activated, as in the previous explanation, reducing the count of services it runs. Now we have a function to discuss in detail, that is, the CtrlHandler function of the service.
When calling the RegisterServiceCtrlHandler function, the SCM gets and saves the address of this callback function. A SCP tunes a Win32 function telling the SCM to control the service, and now there is 10 predefined control requests:
Control Code
Meaning
SERVICE_CONTROL_STOPRequests the service to stop. The hService handle must have SERVICE_STOP access.SERVICE_CONTROL_PAUSERequests the service to pause. The hService handle must have SERVICE_PAUSE_CONTINUE access.SERVICE_CONTROL_CONTINUERequests the paused service to resume. The hService handle must have SERVICE_PAUSE_CONTINUE access.SERVICE_CONTROL_INTERROGATERequests the service to update immediately its . current status information to the service control manager The hService handle must have SERVICE_INTERROGATE access.SERVICE_CONTROL_SHUTDOWNRequests the service to perform cleanup tasks, because the system is shutting down For more information, see Remarks.SERVICE_CONTROL_PARAMCHANGEWindows 2000:. Requests the service to reread its startup parameters . The hService handle must have SERVICE_PAUSE_CONTINUE access.SERVICE_CONTROL_NETBINDCHANGEWindows 2000: Requests the service to update its network binding The hService handle must have SERVICE_PAUSE_CONTINUE access.SERVICE_CONTROL_NETBIN. DREMOVEWindows 2000:. Notifies a network service that a component for binding has been removed The service should reread its binding information and unbind from the removed component SERVICE_CONTROL_NETBINDENABLEWindows 2000:. Notifies a network service that a disabled binding has been enabled The service should reread its. binding information and add the new binding SERVICE_CONTROL_NETBINDDISABLEWindows 2000:. Notifies a network service that one of its bindings has been disabled the service should reread its binding information and remove the binding table on the winning 2,000 words is the 2000 newly added for Windows. Control code. In addition to these codes, the service can also accept the user-defined code between 128-255.
When the CtrlHandler function receives a service_control_stop, service_control_pause, service_control_continue control code, setServiceStatus must be called to confirm this code and specify the time you think that the service is required to process this status. For example: Your service receives a stop request, first set the service_status DWCurrentState member to service_stop_pending, so that the SCM determines that you have received the control code. When a service is suspended or stopped, you must specify the time you want to do this: This is because a service may not change its status immediately, it may have to wait for a network request to be completed or data Refresh to a drive. The specified time method is like the last chapter, with members dwcheckpoint and dwwaithint to indicate the time required to complete the status change. If necessary, you can use the value of adding DWCHECKPOINT members and the value of setting DWWAITHINT to indicate the process of periodic reporting progress that you expect to reach the next time.
When the entire start-up process is completed, you have to call SetServiceStatus again. At this time, set the DWCURrentState member of the service_status structure to service_stopped. When reporting status code, be sure to set members dwcheckpoint and dwwaithint to zero, because the service has completed its status change. The method is the same when paused or continues.
When the CtrlHandler function receives a service_control_interrogate control code, the service will simply set the DWCurrentState member to the current state, at the same time, set the members dwcheckpoint and dwwaithint to 0, and then call setServiceStatus.
When the operating system is turned off, the CtrlHandler function receives a service_control_shutdown control code. The service does not need to respond to this code because the system is about to close. It will execute the minimum actions required for saving data, which is to determine that the machine can turn off in time. The system only gives a few times to close all the services, and the MSDN is about 20 seconds, but it may be the setting of Windows NT 4, in my Windows 2000 Server this time is 10 seconds, you You can manually modify this value, it is recorded in the HKEY_LOCAL_MACHINE / SYSTEM / CURRENTCAL_MACHINE / System / CurrentControlset / Control subkey WaitTokillServentEtimeout in milliseconds.
When the CtrlHandler function receives any user-defined code, it should perform the desired user-defined action. Unless the user-defined action is to force the service to pause, continue or stop, otherwise the setServiceStatus function is not adjusted. If the user-defined action forces the status of the service, setServiceStatus will be called to set DWCurrentState, dwcheckpoint, and DwWaithint, the specific control code, and the same foregoing.
If your CtrlHandler function takes a long time to perform actions, you should pay attention: If the CtrlHandler function does not return in 30 seconds, the SCM will return an error, which is not what we expect. So if the above situation occurs, the best way is to build a thread and let it continue to perform the operation so that the CtrlHandler function returns quickly. For example, when receiving a service_control_stop request, just like it is said above, the service may be waiting for a network request to be completed or the data is refreshed onto a drive, and the time required for these operations is that you can't be estimated, then If you want to create a new thread waiting for the operation, execute the stop command, the CtrlHandler function still needs to report the service_stop_pending state before returning. When the new thread performs the operation, it will be set to service_stopped. If the current operation can be estimated, do not do this, still use the method processing of the previously explained. CtrlHandler function I will talk about this first, and how to install the service. A service program can add the information information to the SCM database using the CreateService function.
SC_HANDLE CreateService (SC_HANDLE hSCManager, // handle to SCM database LPCTSTR lpServiceName, // name of service to startLPCTSTR lpDisplayName, // display nameDWORD dwDesiredAccess, // type of access to serviceDWORD dwServiceType, // type of serviceDWORD dwStartType, // when to start serviceDWORD dwErrorControl, // severity of service failureLPCTSTR lpBinaryPathName, // name of binary fileLPCTSTR lpLoadOrderGroup, // name of load ordering groupLPDWORD lpdwTagId, // tag identifierLPCTSTR lpDependencies, // array of dependency namesLPCTSTR lpServiceStartName, // account name LPCTSTR lpPassword / / Account Password;
HscManager is a handle indicating the SCM database that can be simply obtained by calling OpenScManager.
SC_HANDLE OPENSCMANAGER (LPCTSTSTR LPMACHINENAME, / / Computer Namelpctstr LPDatabaseN); // A //
LPMACHINENAME is the name of the target machine, I still remember that I said in the first chapter that can be installed on other machines? This is the implementation method. The other machine name must start with "//". If you pass null or an empty string, it is a machine.
LPDATABASENAME is the name of the SCM database above the target machine, but the MSDN says that this parameter is set to Services_Active_Database if the NULL is passed, and the service_active_database is opened by default. So I haven't really figured out the existence of this parameter, and I will pass NULL when I use it.
DwdesiredAccess is the access to the SCM database, the specific value see the following table: Object Access
Description
SC_MANAGER_ALL_ACCESSIncludes STANDARD_RIGHTS_REQUIRED, in addition to all of the access types listed in this table.SC_MANAGER_CONNECTEnables connecting to the service control manager.SC_MANAGER_CREATE_SERVICEEnables calling of the CreateService function to create a service object and add it to the database.SC_MANAGER_ENUMERATE_SERVICEEnables calling of the EnumServicesStatus function to list the services that are in the database.SC_MANAGER_LOCKEnables calling of the LockServiceDatabase function to acquire a lock on the database.SC_MANAGER_QUERY_LOCK_STATUSEnables calling of the QueryServiceLockStatus function to retrieve the lock status information for the database.
If you want to get access, it doesn't seem to be so complicated. MSDN said that all processes are allowed to get SC_MANAGER_CONNECT, SC_MANAGER_ENUMERATE_SERVICE, AND SC_MANAGER_QUMERATE_SERVICE, AND SC_MANAGER_QUMERATE_SERVICE, AND SC_MANAGER_QUMERATE_SERVICE, AND SC_MANAGER_QUMERATE_SERVICE, AND SC_MANAGER_QUMERATE_SERVICE, AND SC_MANAGER_QUERITE_SERVICE, AND SC_MANAGER_QUERY_LOCK_STATUS, allow you to connect to the SCM database, enumerate the service and query the target database has been locked. But if you want to create a service, you first need administrator privileges with the target machine, and the general delivery of SC_Manager_all_access is OK. The handle returned by this function can be turned off by the ClosESERVICEHANDLE function.
LPServiceName is the name of the service, LPDisplayName is the name of the service displayed in the Service Management tool.
DwdesiredAccess is also accessible, there is a table that is more than the top, you can check MSDN yourself. We want to install services and still pass SC_Manager_All_Access.
DWServiceType means whether your service is associated with other processes, usually service_win32_ove_process, indicating that it is not associated with any process. If you confirm that your service needs to be associated with some processes, set it to service_win32_share_process. When your service is related to the desktop, you need to set it to service_interactive_process.
DWStartType is a startup method for service. The service has three start-up mode, namely "service_auto_start" "" service_demand_start "and" disabled (service_disable) ". There are still two ways in the MSDN, but it is designed for the driver.
DwerrorControl determines how to do if the service is started when the system is started.
value
significance
Service_error_ignore starter record error occurs, but continues to start. Service_error_normal launcher logging error occurred, and popped up a message box, but continues to start the service_error_severe launcher record error occurred, if it is launched by Last-Known-Good Configuration, the startup will continue. Otherwise, the computer will be restarted in Last-KNown-Good Configuration. Service_error_critical launcher record error occurs, if possible. If it is launched by Last-KNown-Good Configuration, the startup will fail. Otherwise, the computer will be restarted in Last-KNown-Good Configuration. A good mistake. LPBINARYPATHNAME is the path to the service program. In particular mentioned in MSDN, if there is a space in the service path, it must cause the path to use the quotation marks. For example, "D: // my share // myService.exe" must be specified as "/" d: // my share // myservice.exe / "".
LPLoadOrderGroup means that if a set of services are started in order to start in order, this parameter is used to specify a group name for the launch group, but I haven't used this parameter yet. Your service If you don't belong to any startup sequence group, just pass NULL or an empty string.
LPDWTAGID is the value to be specified after the above parameters, which is dedicated to the driver, which is independent of the content of this article. Pass NULL.
LPDependencies indicates a string array to indicate the name of a string or a boot sequence group. When it is associated with a startup order, the meaning of this parameter is that at least one of the members you specified have passed through all members of the entire group have all been tried to start, you have successfully launched at least one member. The service can be started. If you don't need to establish a dependency, it is still a string that passes NULL or an empty string. But if you want to specify the start sequence group, you must add the SC_GROUP_IDENTIFIER prefix for the group name, because the group name and service name are shared a namespace.
LPServiceStartName is a service startup account. If you set your service's association type is Service_WIN32_UWN_PROCESS, you need to specify a username with DomainName / UserName, if this account is in your local, you can specify. If you pass NULL, log in with a local system account. If you are Win NT 4.0 or earlier, if you specify service_win32_share_process, you must pass ./system specifies the service using a local system account. Finally, if you specify service_interactive_process, you must make the service run in the native system account.
Look at the name, I know, LPPassword is the password of the account. If you specify the system account, pass NULL. If the account does not have a password, pass a empty string. 4) Discussion on some problems
The previous content is some universal writing principle of services, but there are some questions, can't see when writing simple services, but there will be some problems with complex applications, so this chapter is used to analyze, solve These issues are applicable to developers with advanced applications. The content of my chapter is experimentally obtained, and it is very practical.
As mentioned earlier, the CtrlHandler function is executed by a serving main thread, which will receive a variety of control commands, but the truly handle, execute the operation of the servicemain thread. 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
Afterwards in the "Event Viewer", I found my service to add event records in "Application Log", the result is that I got this list: service_pause_pendingservice_pause_pendservice_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") } 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? I will understand it in the KServicemain function below ...
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 CALED IN this service // ... 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 (); service / 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 // ... service} :: setServiceStatus (ServiceStatusHandle, & ServiceStatus);
void WINAPI KServiceMain (DWORD argc, LPTSTR * argv) {servicestatus.dwServiceType = SERVICE_WIN32; servicestatus.dwCurrentState = SERVICE_START_PENDING; servicestatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; // above the answer 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; // ionize 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);}}
Void Stop () {LOGEVENT ("Service Stoped.");