
Сервисы Windows (120
..pdfОкончание табл. 1
Параметр |
Значение |
|
Описание |
|
|
|
|
||
FailureCommand |
Командная строка про- |
SCM считывает этот параметр, толь- |
||
|
граммы |
ко если в параметре FailureActions |
||
|
|
указано, |
что программа |
должна |
|
|
быть запущена при сбое сервиса. |
||
|
|
Этот параметр не применим к серви- |
||
|
|
ñàì Win32 |
|
|
|
|
|
|
|
Security |
Дескриптор защиты |
Содержит |
дескриптор |
защиты, |
|
|
определяющий, кто и с какими пра- |
||
|
|
вами может получать доступ к объ- |
||
|
|
екту сервиса, взаимодействующему |
||
|
|
ñ SCM |
|
|
|
|
|
|
|
К драйверам устройств применимы три значения параметра Type (Тип). Они используются драйверами устройств, которые также хранят свои параметры в разделе реестра Services. SCM отвечает за запуск драйверов со значением Start, равным значению SERVICE_AUTO_START или SERVICE_DEMAND_START, поэтому база данных SCM включает в себя и драйверы. В сервисах применяются другие типы: SERVICE_WIN32_OWN_PROCESS и SERVICE_WIN32_SHARE_PROCESS, которые являются взаимоисключающими. В программах, где используется более одного сервиса, указывается тип SERVICE_WIN32_SHARE_PROCESS. При этом экономятся ресурсы ОС, связанные с диспетчеризацией процессов. Но потенциальный недостаток этой схемы в том, что работа всех сервисов данного процесса прекращается, если один из них вызовет ошибку, из-за которой завершается процесс. Кроме того, все сервисы одного процесса должны выполняться под одной учетной записью.
Зарегистрировав сервис, программа установки или управляющее приложение может запустить его, вызвав функцию StartService. Поскольку некоторые сервисы необходимо инициализировать при запуске ОС, программа установки должна зарегистрировать сервис как запускаемый автоматически.
1.7. Выполнение сервиса
Когда SCM запускает сервисный процесс, тот немедленно вызывает функцию StartServiceCtrlDispatcher. Эта функция принимает
21

список точек входа* в сервисы – по одной на каждый сервис процесса. Каждая точка входа идентифицируется именем соответствующего сервиса. Установив соединение с SCM по именованному каналу, функция StartServiceCtrlDispatcher входит в цикл ожидания команды запуска сервиса от SCM. После этого SCM посылает команду запуска, и функция StartServiceCtrlDispatcher, выйдя из цикла ожидания, создает поток сервиса (Service Thread). Поток сервиса вызывает точку входа в сервис и входит в цикл ожидания команд для сервиса. Функция StartServiceCtrlDispatcher находится в бесконечном ожидании команд от SCM и возвращает управление основной функции процесса только после остановки всех сервисов в данном процессе. Это необходимо для обеспечения возможности освобождения занимаемых ресурсов.
При передаче управления на точку входа сервиса происходит вызов функции RegisterServiceCtrlHandler. Эта функция принимает
èхранит указатель на функцию – обработчик управления (Сontrol Service), которую реализует сервис для обработки различных команд SCM. Она не взаимодействует с SCM, а лишь хранит упомянутую функцию в локальной памяти процесса для функции StartServiceCtrlDispatcher. Точка входа продолжает инициализацию, выделяя необходимую память, создавая коммуникационный канал
èсчитывая из реестра данные о собственной конфигурации сервиса. По соглашению эти параметры хранятся в разделе Параметры (Parameteres) раздела реестра для данного сервиса. Точка входа, инициализируя сервис, может периодически посылать SCM сообщения в ходе процесса запуска сервиса, используя функцию SetServiceStatus. Когда точка входа заканчивает инициализацию, поток сервиса обычно входит в цикл ожидания запросов от клиентских приложений. Например, Web-сервер должен инициализировать TCP-сокет и ждать запросы на входящие HTTP-соединения.
На рис. 6 показана схема внутренней организации сервисного процесса:
1)функция StartServiceCtrlDispatcher запускает поток сервиса;
2)поток сервиса регистрируется обработчиком управления;
3)функция StartServiceCtrlDispatcher вызывает обработчик в ответ на команды SCM;
* Точка входа – это место, с которого начинается выполнение процесса или потока.
22

Ðèñ. 6. Структура поцесса сервиса
4) поток сервиса обрабатывает клиентские запросы.
На рис. 6 показаны два потока процесса, созданных для одного сервиса: основной поток и поток сервиса.
SCM посылает сервису следующие команды: Stop (Стоп), Pause (Пауза), Resume (Возобновление), Interrogate (Опрос), Shutdown (Выключение) и команды, определяемые приложением.
2. РАЗРАБОТКА СЕРВИСА WIN32
2.1. Структура программы сервиса
Любой сервис должен содержать три основные функции, которые принято называть точками входа.
1. Функцию WinMain (main), которая является точкой входа процесса. При поступлении в SCM запроса на запуск сервиса SCM производит запуск программы сервиса на выполнение. В этот момент вызывается точка входа main. Функция main принимает аргументы из командной строки обычным образом, например: void __cdecl _tmain(int argc, TCHAR *argv[]), и выполняет соответствую-
23
щие действия; если в параметре argv[1] содержится строка install, то сервис, возможно, будет запущен SCM.
2. Функция ServiceMain, которая вызывается ОС, является функцией обратного вызова (Сallback-функцией) и содержит код, инициирующий работу сервиса. Функция может иметь любое имя, но должна соответствовать прототипу: VOID WINAPI ServiceMain (__in DWORD dwArgc, __in LPTSTR *lpszArgv);
Значения параметров:
•dwArgc – количество аргументов в параметре lpszArgv. Если аргументов нет, то этот параметр может быть нулевым;
•lpszArgv [] – массив строк. Если аргументы есть, то первый аргумент (lpszArgv [0]) – это имя службы, следующие за ним строки передаются в службу функцией StartService, которая запускает службы.
Прототип находится в библиотеке windows.h.
Для запуска функции ServiceMain создается отдельный поток. Таким образом, даже если исполняемый файл содержит только один сервис, создаются два потока: главный и второй для сервиса. Если в исполняемом файле содержится несколько сервисов, то они все должны иметь собственную функцию ServiceMain. Каждый такой сервис будет запущен как отдельный поток. В качестве параметра функция ServiceMain получает командную строку, указанную в базе данных сервисов.
Функция Handler, которая является функцией обратного вызова и работает совместно с функцией RegisterServiceCtrlHandler (), должна иметь следующий прототип: VOID WINAPI Handler( __in DWORD fdwControl); эта функция была заменена функцией HandlerEx, которая используется совместно с функцией RegisterServiceCtrlHandlerEx () и имеет следующий прототип:
DWORD WINAPI HandlerEx( __in DWORD dwControl, __in DWORD dwEventType, __in LPVOID lpEventData, __in LPVOID lpContext);
Прототип находится в библиотеке windows.h.
Для каждого сервиса необходимо определить отдельню функцию Handler. SCM вызывает функцию Handler для изменения статуса сервиса. Например, если пользователь, используя SCP-апплет, останавливает сервис, функция Handler сервиса получает уведомление SERVICE_CONTROL_STOP. Функция Handler ответственна за
24
действия, необходимые для безопасной остановки сервиса. Основной поток процесса запускает на выполнение все функции Handler, имеющиеся в исполняемом файле. Необходимо так реализовать код функции Handler, чтобы она выполнялась как можно быстрее и чтобы другие функции Handler, имеющиеся в исполняемом файле, получили возможность на выполнение в течение заданного отрезка времени. Поскольку функция Handler запускается основным потоком процесса, а сервис выполняется другим потоком, то данная функция должна содержать механизм, обеспечивающий передачу информации об изменении статуса сервиса от одного потока к другому. Стандартного решения для создания такого рода механизма не существует. Все зависит от реализации сервиса, от работы, которую он выполняет. Можно создать очередь асинхронных вызовов (Remoute Procedure Call – RPC), послать статус завершения или использовать функцию посылки сообщений в систему и т. п.
Точка входа main
Основная задача точки входа main – вызов функции StartServiceCtrlDispatcher. Эта функция осуществляет подключение сервиса к SCM и запускает поток обработки команд. Возврат из этой функции производится по завершении всех сервисов процесса. В ка- честве аргумента эта функция принимает массив структур, содержащих имя сервиса и точку входа в данный сервис – для каждого сервиса данного процесса.
Внутри функции WinMain (main) инициализируется структура SERVICE_TABLE_ENTRY, которая выглядит так:
typedef struct _SERVICE_TABLE_ENTRY { LPTSTR lpServiceName; LPSERVICE_MAIN_FUNCTION lpServiceProc;
} SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY; Первый элемент структуры – имя сервиса, а второй – адрес
Сallback-функции ServiceMain. Поскольку шаблон процесса содержит только один сервис, то в структуре SERVICE_TABLE_ENTRY должно быть два элемента в массиве – один для сервиса и второй, нулевой (NULL), для указания конца массива.
Затем адрес этого массива передается функции StartServiceCtrlDispatcher: BOOL StartServiceCtrlDispatcher (LPSERVICE_TABLE_ENTRY lpServiceStartTable);
25
Эта API-функция, как и исполняемый процесс, посылает SCM уведомления от сервисов, находящихся в процессе. Функция StartServiceCtrlDispatcher создает новый поток для каждого ненулевого элемента массива SERVICE_TABLE_ENTRY, переданного ей в качестве параметра. Созданный поток начинает свою работу с функции ServiceMain, которая определена в элементе lpServiceProc структуры.
SCM отслеживает процесс запуска сервиса. Например, после того, как SCM запускает сервис, он входит в режим ожидания, пока основной поток исполняемого файла оболочки не вызовет функцию StartServiceCtrlDispatcher. Если функция StartServiceCtrlDispatcher основным потоком не вызвана, SCM считает, что в реализации сервиса присутствует ошибка, и вызывает функцию TerminateProcess для завершения процесса. По этой причине, если процесс требует более 2 мин для инициализации, необходимо создать отдельный поток, в котором будет происходить инициализация, тем самым позволив основному потоку как можно быстрее выполнить функцию StartServiceCtrlDispatcher. После завершения работы функция StartServiceCtrlDispatcher не сразу возвращает управление в функцию WinMain (main). Вместо этого она создает цикл (наподобие цикла сообщений стандартных исполняемых файлов:
// нулевые значения массива не читаются
int nNumRunningServices = NumElementsInServiceStartTable – 1; while (nNumRunningServices > 0) {
WaitForAServiceControlCodeOrAServiceThreadToTerminate(); if (AServiceControlCode) {
RemoveServiceControlCodeFromQueue()
CallServiceHandler(fdwControlCode);
}else { nNumRunningServices--;
}
}
return(TRUE);
// StartServiceCtrlDispatcher возвращает управление в WinMain/main Находясь в этом цикле, функция StartServiceCtrlDispatcher «за-
сыпает» в ожидании одного из двух событий:
1) SCM желает послать уведомление одному из сервисов процесса. Когда уведомление получено, поток «просыпается» и вызывает на выполнение функцию Handler данного сервиса. Функция
26
Handler, в свою очередь, обрабатывает полученное уведомление (обычно взаимодействуя с потоком сервиса) и возвращает управление функции StartServiceCtrlDispatcher, которая снова входит в цикл
è«засыпает» в ожидании нового уведомления;
2)один из потоков сервиса прекращает работу. В этом случае функция StartServiceCtrlDispatcher «просыпается» и уменьшает счет- чик работающих сервисов на единицу. Если счетчик равен нулю (т. е. все сервисы остановлены), функция StartServiceCtrlDispatcher возвращает управление функции WinMain(main), для того чтобы можно было произвести очистку ресурсов системы и завершить процесс. Если, по крайней мере, один из сервисов еще работает, функция StartServiceCtrlDispatcher будет находиться в цикле ожидания уведомлений или завершения работы очередного сервисного потока.
Обычно функция ServiceMain игнорирует оба передаваемых ей параметра, поскольку сервисы чаще всего не используют параметров вовсе. Лучшим способом настройки сервиса является получение параметров настройки из реестра. Сервис должен использовать функции доступа к реестру для поиска своих параметров в ключе HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ ServiceName\Parameters, где ServiceName – название сервиса. Конечно, можно написать приложение, имеющее пользовательский интерфейс, с помощью которого пользователь сможет изменять настройки сервиса. В таком случае приложение должно сохранять параметры настройки сервиса в реестре, откуда сервис впоследствии сможет их получить. Работающий сервис может использовать APIфункцию RegNotifyChangeKeyValue для получения уведомлений о внесении изменений в реестр. Это позволит сервису изменять свои параметры, как говорится, «на лету».
Точка входа в сервис (ServiceMain)
Основной задачей точки входа ServiceMain является инициализация сервиса и передача адреса Сallback-функции сервиса посредством вызова функции RegisterServiceCtrlHandler: SERVICE_ STATUS_HANDLE RegisterServiceCtrlHandler (LPCTSTR lpServiceName, LPHANDLER_FUNCTION lpHandlerProc), в которой первый параметр указывает, за каким сервисом закреплена Handler-функция, а второй – адрес Handlerфункции. Параметр lpServiceName должен быть таким же, как имя сервиса в массиве структур SERVICE_TABLE_ENTRY, который передается функ-
27
ции StartServiceCtrlDispatcher. После этого функция Register ServiceCtrlHandler возвращает значение SERVICE_STATUS_ HANDLE, которое является просто 32-битным значением и используется SCM для уникальной идентификации сервиса. Если у сервиса появляется необходимость сообщить SCM свой текущий статус, то необходимо передать его в качестве идентификатора сервиса (Service Handle) желаемой API-функции. В отличие от других идентификаторов объектов, присутствующих в ОС Windows, закрывать явным образом идентификатор сервиса, возвращаемый функцией RegisterServiceCtrlHandler, не следует.
По возвращении функцией RegisterServiceCtrlHandler управления функции ServiceMain поток последней должен незамедлительно сообщить SCM о том, что инициализация сервиса продолжается. Для этого используется функция SetServiceStatus, имеющая следующий прототип: BOOL SetServiceStatus (SERVICE_STATUS_HANDLE hService, LPSERVICE_STATUS lpServiceStatus);
В качестве первого параметра эта функция требует идентификатор сервиса, который был получен ранее при вызове функции RegisterServiceCtrlHandler, а в качестве второго параметра – адрес подготовленной структуры SERVICE_STATUS, имеющей следующее описание:
typedef struct _SERVICE_STATUS { DWORD dwServiceType; DWORD dwCurrentState; DWORD dwControlsAccepted; DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;
Эта структура содержит семь элементов, отражающих текущий статус сервиса. Элемент структуры dwServiceType показывает тип сервиса и может принимать значения, перечисленные в табл. 2.
Флаг SERVICE_WIN32_OWN_PROCESS устанавливается в случае, если исполняемый файл содержит только один сервис, если же сервисов более одного, то устанавливается флаг SERVICE_ WIN32_SHARE_PROCESS. В дополнение к этим двум флагам с помощью оператора OR можно установить флаг SERVICE_
28
INTERACTIVE_PROCESS. Это позволит сервису отправлять диалоговые сообщения пользователю (но использовать флаг SERVICE_ INTERACTIVE_PROCESS следует как можно реже). После установки флага (набора флагов) его значение не следует менять до окончания работы сервиса.
|
|
|
Таблица 2 |
|
Значения флага типа сервиса |
|
|
|
|
|
|
|
|
|
Ôëàã |
Значение |
Описание |
|
|
|
|
|
||
SERVICE_WIN32_ |
0x00000010 |
Сервис запускается в |
||
OWN_PROCESS |
|
своем |
собственном |
|
|
|
процессе |
|
|
|
|
|
|
|
SERVICE_WIN32_SHARE_P |
0x00000020 |
Сервис |
делит |
ïðî- |
ROCESS |
|
цесс с другими серви- |
||
|
|
ñàìè |
|
|
SERVICE_WIN32 |
SERVICE_WIN32_ |
Сервис является ком- |
||
|
OWN_PROCESS| |
бинацией указанных |
||
|
SERVICE_WIN32_ |
выше типов сервисов |
||
|
SHARE_PROCESS |
|
|
|
|
|
|
||
SERVICE_KERNEL_ |
0x00000001 |
Сервис является драй- |
||
DRIVER |
|
вером устройства |
||
|
|
|
||
SERVICE_FILE_ |
0x00000002 |
Сервис является драй- |
||
SYSTEM_DRIVER |
|
вером файловой сис- |
||
|
|
òåìû |
|
|
|
|
|
||
SERVICE_ADAPTER |
0x00000004 |
Сервис является драй- |
||
|
|
вером адаптера |
|
|
|
|
|
||
SERVICE_RECOGNIZER_D |
0x00000008 |
Сервис является драй- |
||
RIVER |
|
вером |
распознающе- |
|
|
|
го устройства |
|
|
|
|
|
||
SERVICE_DRIVER |
SERVICE_KERNEL_ |
Комбинация драйвер- |
||
|
DRIVER| |
ных сервисов |
|
|
|
SERVICE_FILE_ |
|
|
|
|
SYSTEM_DRIVER| |
|
|
|
|
SERVICE_ |
|
|
|
|
RECOGNIZER_DRIVER |
|
|
|
|
|
|
||
SERVICE_INTERACTIVE_P |
0x00000100 |
Сервис может взаимо- |
||
ROCESS |
|
действовать с |
ðàáî- |
|
|
|
чим столом пользова- |
||
|
|
òåëÿ |
|
|
|
|
|
||
SERVICE_TYPE_ALL |
SERVICE_WIN32| |
Сервис является ком- |
||
|
SERVICE_ADAPTER| |
бинацией всех переч- |
||
|
SERVICE_DRIVER| |
исленных выше типов |
||
|
SERVICE_INTERACTIVE_ |
сервисов |
|
|
|
PROCESS |
|
|
|
|
|
|
|
|
|
|
|
|
29 |
Элемент dwCurrentState – наиболее важный в структуре SERVICE_STATUS. Он «подсказывает» SCM текущий статус сервиса. Для указания того, что сервис находится в процессе инициализации, необходимо установить значение этого элемента SERVICE_START_PENDING. Остальные возможные значения приведены в табл. 3.
|
|
Таблица 3 |
Флаги текущего статуса сервиса |
||
|
|
|
Ôëàã |
Значение |
Описание |
|
|
|
SERVICE_STOPPED |
0x00000001 |
Сервис не запущен |
|
|
|
SERVICE_START_PENDING |
0x00000002 |
Сервис находится в процессе |
|
|
запуска |
|
|
|
SERVICE_STOP_PENDING |
0x00000003 |
Сервис находится в процессе |
|
|
остановки |
|
|
|
SERVICE_RUNNING |
0x00000004 |
Сервис запущен и работает |
|
|
|
SERVICE_CONTINUE_ |
0x00000005 |
Сервис находится в процессе |
PENDING |
|
повторного запуска |
|
|
|
SERVICE_PAUSE_PENDING |
0x00000006 |
Сервис находится в процессе |
|
|
установки паузы |
|
|
|
SERVICE_PAUSED |
0x00000007 |
Сервис приостановлен |
|
|
|
Элемент структуры dwControlsAccepted показывает, какого рода уведомления готов получать сервис. Если в дальнейшем предполагается использование опции SCP-приложения Приостановить/Продолжить (Pause/Continue), то используется флаг SERVICE_ ACCEPT_PAUSE_CONTINUE. Многие сервисы не поддерживают возможность приостановки сервиса, поэтому необходимо решить, нужна ли сервису такая возможность. При желании предоставить SCP-приложению возможность останавливать (Stop) сервис устанавливается флаг SERVICE_ACCEPT_STOP. Если же сервис должен принимать уведомления о прекращении работы ОС, устанавливается флаг SERVICE_ACCEPT_SHUTDOWN. Можно также использовать оператор OR для комбинирования флагов (табл. 4).
Элементы dwWin32ExitCode и dwServiceSpecificExitCode структуры позволяют сервису сообщать об ошибках. Если возникает ошибка, код которой описан в файле WinError.h, код ошибки присваивается элементу структуры dwWin32ExitCode. В случае возникновения специфичной (не описанной в структуре
30