• Написание программ, реализующихслужбы Windows Services: обзор
  • Функция main()
  • Функции ServiceMain()
  • Регистрация управляющей программы службы
  • Настройка состояния службы
  • Структура SERVICE_STATUS
  • Специфический для службы код
  • Обработчик управляющих команд службы
  • Пример: "интерфейсная оболочка" службы
  • Управление службами Windows
  • Открытие SCM
  • Создание и удаление службы
  • Запуск службы
  • Управление службой
  • Опрос состояния службы
  • Резюме: функционирование и управление службой
  • Пример:команднаяоболочкауправленияслужбами
  • Совместное использование объектов ядра приложениями и службами
  • Регистрация событий
  • Замечания по отладке службы
  • Резюме
  • В следующих главах
  • Дополнительная литература
  • Упражнения
  • ГЛАВА 13

    Windows Services

    Серверные программы, рассмотренные в главах 11 и 12, являются консольными приложениями, выполняющимися как фоновые задачи. Вообще говоря, эти серверы могут выполняться в течение неопределенно длительного времени, обслуживая многочисленных клиентов по мере того, как те будут подключаться к серверу, посылать запросы, принимать ответы и разрывать соединения. Таким образом, указанные серверы могут работать как службы непрерывного действия, однако, чтобы быть в полной мере эффективными, эти службы должны быть управляемыми.

    Службы Windows Services,[33] известные ранее под названием NT Services, предоставляют все средства управления, необходимые для превращения наших серверов в службы, которые могут активизироваться по команде или во время запуска системы еще до входа в нее пользователей, приостанавливаться, а также возобновлять или прекращать свое выполнение. Службы могут даже осуществлять мониторинг работоспособности самих служб. Информация о службах хранится в системном реестре.

    В конечном счете, любая серверная система наподобие тех, которые были разработаны в главах 11 и 12, должна быть преобразована в службу, особенно в тех случаях, когда она предназначена для использования широким кругом клиентов или внутри организации.

    Windows предоставляет целый ряд служб; в качестве примера можно привести службы telnet, отправки и приема факсимильных сообщений, а также службы управления безопасностью учетных записей и драйверы устройств. Доступ ко всем службам можно получить через пиктограмму Administrative Tools (Администрирование), который находится в окне панели управления.

    Примитивную форму управления сервером можно было наблюдать в приведенной в главе 6 программе JobShell (программа 6.3), которая обеспечивает возможность перевода сервера под управление задачи и его остановку путем посылки сигнала завершения работы. В то же время, службы Windows Services предоставляют гораздо более широкие возможности и отличаются высокой надежностью, как это будет продемонстрировано в данной главе на примере преобразования программы к форме, обеспечивающей управление службами Windows Services.

    В данной главе также показано, как преобразовать существующее консольное приложение в службу Windows, осуществить ее установку, а также организовать мониторинг и управление этой службой. Кроме того, здесь рассматривается ведение журнала учета событий, что обеспечивает регистрацию действий службы.

    Написание программ, реализующихслужбы Windows Services: обзор

    Службы Windows выполняются под управлением диспетчера управления службами (Service Control Manager, SCM). Преобразование консольного приложения, такого как serverNP или serverSK, в службу Windows осуществляется в три этапа, после выполнения которых программа переходит под управление SCM.

    1. Создание новой точки входа main(), которая регистрирует службу в SCM, предоставляя точки входа и имена логических служб.

    2. Преобразование прежней функции точки входа main() в функцию ServiceMain(), которая регистрирует обработчик управляющих команд службы и информирует SCM о своем состоянии. Остальная часть кода, по существу, сохраняет прежний вид, хотя и может быть дополнена командами регистрации событий. Имя ServiceMain() является заменителем имени логической службы, причем логических служб может быть несколько.

    3. Написание функции обработчика управляющих команд службы, которая должна предпринимать определенные действия в ответ на команды, поступающие от SCM.

    По мере описания каждого из этих трех этапов будут даваться отдельные разъяснения, касающиеся создания служб, их запуска и управления ими. Более подробные сведения приводятся в последующих разделах, а взаимодействие между отдельными компонентами службы иллюстрируется на рис. 13.1 далее в этой главе.

    Функция main()

    Задачей новой функции main(), которая вызывается SCM, является регистрация службы в SCM и запуск диспетчера службы (service control dispatcher). Для этого необходимо вызвать функцию StartServiceControlDispatcher, передав ей имя (имена) и точку (точки) входа одной или нескольких логических служб.

    BOOL StartServiceCtrlDispatcher(LPSERVICE_TABLE_ENTRY lpServiceStartTable)

    Эта функция принимает единственный аргумент lpServiceStartTable, являющийся адресом массива элементов SERVICE_TABLE_ENTRY, каждый из которых представляет имя и точку входа логической службы. Конец массива обозначается двумя последовательными значениями NULL.

    Функция возвращает значение TRUE, если регистрация службы прошла успешно. Если служба уже выполняется или возникают проблемы с обновлением записей реестра (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services), функция завершается с ошибками, обработка которых может осуществляться обычным путем.

    Основной поток процесса службы, которая вызывает функцию StartService-ControlDispatcher, связывает поток с SCM. SCM регистрирует службу с вызывающим потоком в качестве потока диспетчера службы. SCM не осуществляет возврата в вызывающий поток до тех пор, пока не завершат выполнение все службы. Заметьте, однако, что фактического запуска логических служб в этот момент не происходит; запуск службы требует вызова функции StartService, которая описывается далее в этой главе.

    Типичная основная программа службы, соответствующая случаю единственной логической службы, представлена в программе 13.1.

    Программа 13.1. main: точка входа main службы 

    #include "EvryThng.h"


    void WINAPI ServiceMain(DWORD argc, LPTSTR argv[]);


    static LPTSTR ServiceName = _T("SocketCommandLineService");


    /* Главная программа запуска диспетчера службы. */

    VOID _tmain(int argc, LPTSTR argv[]) {

     SERVICE_TABLE_ENTRY DispatchTable[] = {

      { ServiceName, ServiceMain },

      { NULL, NULL }

     };


     if (!StartServiceCtrlDispatcher(DispatchTable)) ReportError(_T("Ошибка при запуске диспетчера службы."), 1, TRUE);

     /* ServiceMain() начнет выполняться только после того, как ее */

     /* запустит SCM. Возврат сюда осуществляется только после того, */

     /* как завершится выполнение всех служб. */

     return;

    }
     

    Функции ServiceMain()

    Эти функции, которые указываются в таблице диспетчеризации, фигурирующей в программе 13.1, представляют логические службы. По сути, эти функции являются усовершенствованными версиями основной программы, преобразуемой в службу, и каждая логическая служба будет активизироваться в ее собственном потоке SCM. В свою очередь, логическая служба может запускать дополнительные потоки, например, рабочие потоки сервера, которые использовались в программах serverSK и serverNP. Часто внутри службы существует только одна логическая служба. Логическая служба в программе 13.2 получена путем соответствующей адаптации основного сервера из программы 12.2. В то же время, логические службы на основе сокетов и именованных каналов могут выполняться в рамках одной и той же службы Windows, что потребует предоставления основных функций обеих служб.

    Несмотря на то что функция ServiceMain() является адаптированным вариантом функции main() с ее параметрами, представляющими количество аргументов и содержащую их строку, между ними имеется одно незначительное отличие: функция службы должна быть объявлена с типом void, а не иметь возвращаемое значение типа int, как в случае обычной функции main().

    Для регистрации обработчика управляющих команд службы, который представляет собой функцию, вызываемую SCM для осуществления управления службой, требуется дополнительный код.

    Регистрация управляющей программы службы

    Обработчик управляющих команд службы, вызываемый SCM, должен обеспечивать управление соответствующей логической службой. Возможности обработчиков такого рода в ограниченном виде иллюстрирует обработчик управляющих сигналов консоли в сервере serverSK, устанавливающий глобальный флаг завершения выполнения. Однако, прежде всего, каждая логическая служба должна немедленно зарегистрировать свой обработчик с помощью функции RegisterServiceCtrlHandlerEx. Вызов этой функции должен помещаться в начало функции ServiceMain() и впоследствии нигде не повторяться. Обработчик вызывается SCM после получения запроса службы. 

    RegisterServiceCtrlHandlerEx(LPCTSTR lpServiceName, LPHANDLER_FUNCTION_EX lpHandlerProc, LPVOID lpContext)
     

    Параметры

    lpServiceName — определяемое пользователем имя службы, которое предоставляется в соответствующем поле таблицы диспетчеризации, отведенном для данной логической функции.

    lpHandlerProc — адрес функции расширенного обработчика, которая описывается в следующем разделе. Расширенный обработчик был добавлен в NT5, причем функция RegisterServiceCtrlHandlerEx заменяет функцию Register-ServiceCtrlHandler. Следующий параметр также был введен в NT5.

    lpContext — определяемые пользователем данные, передаваемые обработчику. Благодаря этому обработчик может различать ассоциированные с ним службы, которых может быть несколько.

    В случае ошибки возвращаемое функцией значение, которым является объект SEPARARE_STATUS_HANDLE, равно 0, а для анализа ошибок могут быть использованы обычные методы.

    Настройка состояния службы

    Теперь, когда управляющая программа зарегистрирована, необходимо сразу же перевести службу в состояние SERVICE_START_PENDING, воспользовавшись для этого функцией SetServiceStatus. Функция SetServiceStatus будет применяться еще в других местах для установки различных значений параметра состояния, информируя SCM о текущем состоянии службы. Описания других возможных состояний службы, характеризуемых значениями параметра состояния, отличными от SERVICE_STATUS_PENDING, приведены в табл. 13.3.

    Обработчик службы должен устанавливать состояние службы при каждом вызове, даже если ее состояние не менялось.

    Далее, любой из потоков службы может в любой момент вызвать функцию SetServiceStatus, чтобы сообщить данные, характеризующие степень выполнения задачи, а также предоставить информацию об ошибках или иную информацию, причем для периодического обновления состояния многие службы часто выделяют отдельный поток. Длительность временного промежутка между вызовами обновления состояния указывается в одном из полей структуры данных, выступающей в качестве параметра. Если в пределах указанного промежутка времени состояние не обновлялось, то SCM может предположить, что произошла ошибка. 

    BOOL SetServiceStatus(SERVICE_STATUS_HANDLE hServiceStatus, LPSERVICE STATUS lpServiceStatus)
     

    Параметры

    hServiceStatus — дескриптор типа SERVICE_STATUS_HANDLE, возвращенный функцией RegisterCtrlHandlerEx. Поэтому вызову функции SetServiceStatus должен предшествовать вызов функции RegisterCtrlHandlerEx.

    lpServiceStatus — указатель на структуру SERVICE_STATUS, содержащую описание свойств, состояния и возможностей службы.

    Структура SERVICE_STATUS

    Ниже приведено определение структуры SERVICE_STATUS. 

    typedef struct _SERVICE_STATUS {

     DWORD dwServiceType;

     DWORD dwCurrentState;

     DWORD dwControlsAccepted;

     DWORD dwWin32ExitCode;

     DWORD dwServiceSpecificExitCode;

     DWORD dwCheckPoint;

     DWORD dwWaitHint;

    } SERVICE_STATUS, *LPSERVICE_STATUS;
     

    Параметры

    dwWin32ExitCode — обычный код завершения потока, используемый логической службой. Служба должна установить этот код равным NO_ERROR в процессе выполнения и при нормальном завершении.

    dwServiceSpecificExitCode — может использоваться в качестве кода завершения, когда ошибка возникает при запуске или остановке службы, но это значение игнорируется, если значение параметра dwWin32ExitCode не было установлено равным ERROR_SERVICE_SPECIFIC_ERROR.

    dwCheckPoint — служба должна периодически увеличивать значение этого параметра для индикации выполнения на всех стадиях, включая стадии инициализации и остановки. Этот параметр не действует и должен устанавливаться равным 0, если служба не находится в состоянии запуска, остановки, паузы и не выполняет никаких длительных операций.

    dwWaitHint — ожидаемая длительность интервалов времени (в миллисекундах) между последовательными вызовами функции SetServiceStatus, осуществляемыми с увеличенным значением параметра dwCheckPoint или измененным значением параметра dwCurrentState. Как уже отмечалось ранее, если на протяжении этого промежутка времени вызова функции SetServiceStatus не происходит, то SCM предполагает, что это вызвано возникновением ошибки.

    Остальные элементы структуры SERVICE_STATUS обсуждаются ниже по отдельности. 

    Тип службы

    Параметр dwServiceType должен иметь одно из значений, описанных в таблице 13.1.

    В наших примерах в качестве типа службы почти всегда будет предполагаться тип SERVICE_WIN32_OWN_PROCESS, но из приведенных в таблице значений видно, что службы могут играть множество различных ролей.


    Таблица 13.1. Типы служб 

    Значение Описание
    SERVICE WIN32 OWN PROCESS Указывает на службу Windows, выполняющуюся в собственном процессе с собственными ресурсами. Используется в программе 13.2.
    SERVICE_WIN32_SHARE_PROCESS Указывает на службу Windows, разделяющую процесс с другими службами, в результате чего несколько служб могут совместно использовать одни и те же ресурсы, переменные окружения и так далее.
    SERVICE_KERNEL_DRIVER Указывает на драйвер устройства Windows.
    SERVICE_FILE_SYSTEM_DRIVER Определяет драйвер файловой системы Windows.
    SERVICE_INTERACTIVE_PROCESS Указывает на процесс службы Windows, который может взаимодействовать с пользователем через рабочий стол.
    Состояние службы

    Значение параметра dwCurrentState указывает на текущее состояние службы. Возможные значения этого параметра перечислены в табл. 13.2.


    Таблица 13.2. Значения параметра состояния службы

    Значение Описание
    SERVICE_STOPPED Служба не выполняется.
    SERVICE_START_PENDING Служба находится на стадии запуска, но пока не готова отвечать на запросы. Например, могут еще не быть запущены рабочие потоки.
    SERVICE_STOP_PENDING Служба находится на стадии остановки, но еще не завершила своего выполнения. Например, мог быть установлен глобальный флаг завершения, но рабочие потоки еще не успели на это отреагировать.
    SERVICE_RUNNING Служба выполняется.
    SERVICE CONTINUE_PENDING Служба переходит в состояние выполнения после нахождения в состоянии паузы.
    SERVICE_PAUSE_PENDING Служба переходит в состояние паузы, но ее безопасное нахождение в этом состоянии пока не обеспечено.
    SERVICE PAUSED Служба находится в состоянии паузы.
    Воспринимаемые управляющие коды

    Параметр dwControlsAccepted определяет управляющие коды, которые служба будет воспринимать и обрабатывать с помощью своего обработчика (см. следующий раздел). В табл. 13.3 указаны четыре возможных значения, которые могут объединяться посредством операции поразрядного "или" (|). Версия программы serverSK, которую мы впоследствии разработаем, будет воспринимать лишь три первых значения. Дополнительные значения приведены в разделе MSDN, содержащем описание структуры SERVICE_STATUS.


    Таблица 13.3. Коды, воспринимаемые службой (неполный перечень) 

    Значение Описание
    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 Требуется NT5. Обеспечивает изменение параметров запуска без выполнения самого перезапуска. Соответствующей командой является SERVICE_CONTROL_PARAMCHANGE 

    Специфический для службы код

    После того как обработчик зарегистрирован и для службы установлено состояние SERVICE_START_PENDING, служба может инициализировать себя и вновь установить свое состояние. Если говорить о преобразованной версии serverSK, то сразу же после того, как сокеты будут инициализированы, а сервер готов к работе с клиентами, должно быть установлено состояние SERVICE_RUNNING.

    Обработчик управляющих команд службы

    Обработчик управляющих команд службы, то есть функция косвенного вызова, определяемая с помощью функции RegisterServiceCtrlHandlerEx, имеет следующий прототип: 

    DWORD WINAPI HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
     

    dwControl — обозначает фактическую управляющую команду, поступившую в обработчик от SCM. До появления NT5 и введения функции RegisterServiceCtrlHandlerEx этот параметр был единственным параметром обработчика.

    Всего существует 14 возможных значений параметра dwControl, включая те, которые перечислены в табл. 13.3, хотя некоторые команды поддерживаются только в NT5 или XP. Нас будут интересовать следующие значения, которые используются в примере:

    SERVICE_CONTROL_STOP

    SERVICE_CONTROL_PAUSE

    SERVICE_CONTROL_CONTINUE

    SERVICE_CONTROL_INTERROGATE

    SERVICE_CONTROL_SHUTDOWN

    Разрешены также пользовательские значения, определяемые в интервале 128-255, однако нам они не понадобятся.

    dwEventType — обычно принимает значение 0, в то время как ненулевые значения используются для управления устройствами, но рассмотрение этого вопроса выходит за рамки данной книги. Параметр dwEventType определяет дополнительную информацию, которая требуется соответствующим событиям.

    Наконец, lpContext — пользовательские данные, передаваемые в функцию RegisterServiceCtrlHandlerEx во время регистрации обработчика.

    Обработчик активизируется SCM в том же потоке, что и основная программа, и обычно содержит ряд операторов switch, как будет показано в приведенных ниже примерах.

    Пример: "интерфейсная оболочка" службы

    Программа 13.2 реализует преобразованный вариант программы serverSK, который мы перед этим обсуждали. Преобразование сервера в службу сопряжено с решением всех ранее описанных задач. После внесения незначительных изменений существующий код сервера помещается в функцию ServiceSpecific. Поэтому представленный ниже код, по сути, является оболочкой (wrapper) существующей программы сервера, точка входа которой main заменена на ServiceSpecifiс.

    Другим дополнением, которое здесь не представлено, но включено в вариант программы, находящийся на Web-сайте книги, является использование журнала службы, поскольку службы часто запускаются без интерактивной консоли, никак себя видимо не проявляя. 

    Программа 13.2. SimpleService: оболочка службы 

    /* Глава 13. serviceSK.c

       Преобразование сервера serverSK в службу Windows.

       Несмотря на рассмотрение частного случая, оболочка имеет универсальный характер. */


    #include "EvryThng.h"

    #include "ClntSrvr.h"

    #define UPDATE_TIME 1000 /* Интервал обновления – 1 секунда. */


    VOID LogEvent(LPCTSTR, DWORD, BOOL);

    void WINAPI ServiceMain(DWORD argc, LPTSTR argv[]);

    VOID WINAPI ServerCtrlHandlerEx(DWORD; DWORD, LPVOID, LPVOID);

    void UpdateStatus (int, int); /* Вызывает, функцию SetServiceStatus. */

    int ServiceSpecific (int, LPTSTR *); /* Ранее программа main. */

    volatile static BOOL ShutDown = FALSE, PauseFlag = FALSE;

    static SERVICE_STATUS hServStatus;

    static SERVICE_STATUS_HANDLE hSStat; /* Дескриптор, используемый при установке состояния. */


    static LPTSTR ServiceName = _T("SocketCommandLineService");

    static LPTSTR LogFileName = _T("CommandLineServiceLog.txt");


    /* Основная процедура, запускающая диспетчер управления службой. */

    VOID _tmain(int argc, LPTSTR argv[]) {

     SERVICE_TABLE_ENTRY DispatchTable[] = {

      { ServiceName, ServiceMain }, { NULL, NULL }

     };

     StartServiceCtrlDispatcher(DispatchTable);

     return 0;

    }


    /* Точка входа ServiceMain, вызываемая при создании службы. */

    void WINAPI ServiceMain(DWORD argc, LPTSTR argv[]) {

     DWORD i, Context = 1;

     /* Установить текущий каталог и открыть файл журнала, присоединяемый к существующему файлу. */

     /* Определить все элементы структуры состояния сервера. */

     hServStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;

     hServStatus.dwCurrentState = SERVICE_START_PENDING;

     hServStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE;

     hServStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIF0C_ERROR;

     hServStatus.dwServiceSpecificExitCode = 0;

     hServStatus.dwCheckPoint = 0;

     hServStatus.dwWaitHint = 2 * CS_TIMEOUT;

     hSStat = RegisterServiceCtrlHandlerEx(ServiceName, ServerCtrlHandler, &Context); 

     SetServiceStatus(hSStat, &hServStatus);

     /* Запустить специфическую для службы обработку; выполнение типового участка кода завершено. */

     if (ServiceSpecific(argc, argv) != 0) {

      hServStatus.dwCurrentState = SERVICE_STOPPED;

      hServStatus.dwServiceSpecificExitCode = 1;

      /* Ошибка при инициализации сервера. */

      SetServiceStatus(hSStat, &hServStatus);

      return;

     }

     /* Возврат сюда будет осуществлен лишь после завершения функции ServiceSpecific, указывающего на прекращение работы системы. */

     UpdateStatus(SERVICE_STOPPED, 0);

     return;

    }


    void UpdateStatus(int NewStatus, int Check)

    /* Определить новое состояние и контрольную точку — задается либо истинное значение, либо приращение. */

    {

     if (Check < 0) hServStatus.dwCheckPoint++;

     else hServStatus.dwCheckPoint = Check;

     if (NewStatus >= 0) hServStatus.dwCurrentState = NewStatus;

     SetServiceStatus(hSStat, &hServStatus);

     return;

    }


    /* Функция обработчика, активизируемая SCM для выполнения в том же */

    /* потоке, что и основная программа. */

    /* Последние три параметра не используются, так что обработчики, написанные*/

    /* для версий Windows младше NT5, в этом примере также будут работать. */

    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);

      break;

     case SERVICE_CONTROL_PAUSE:

      PauseFlag = TRUE; /* Периодический опрос. */

      break;

     case SERVICE_CONTROL_CONTINUE:

      PauseFlag = FALSE;

      break;

     case SERVICE_CONTROL_INTERROGATE:

      break;

     default:

      if (Control > 127 && Control < 256) /*Пользовательские сигналы.*/ 

       break;

     }

     UpdateStatus(-1, –1); /* Инкрементировать контрольную точку. */

     return;

    }


    /* Эта специфическая для службы функция играет роль функции "main" и вызывается из более общей функции ServiceMain. Вообще говоря, вы можете взять любой сервер, например ServerNP.c, и поместить его код прямо сюда, переименовав функцию "main" в "ServiceSpecific". Однако для кода обновления состояния потребуются некоторые изменения. */

    int ServiceSpecific(int argc, LPTSTR argv[]) {

     UpdateStatus(-1, –1); /* Инкрементировать контрольную точку. */

     /* … Инициализация системы … */

     /* Обеспечьте периодическое обновление контрольной точки. */

     return 0;

    }
     

    Управление службами Windows

    Следующее, что потребуется сделать после написания кода службы — поместить ее под управление SCM, что позволит запускать и останавливать службу, а также осуществлять любые иные формы управления, какие только могут понадобиться.

    Для этого необходимо выполнить несколько шагов, включая открытие SCM, создание службы под управлением SCM и последующий ее запуск. При этом вы воздействуете не непосредственно на службу, а на SCM, который, в свою очередь, и осуществляет управление заданной службой.

    Открытие SCM

    Для создания службы требуется отдельный процесс, выступающий в качестве "администратора" и играющий во многом ту же роль, что и программа JobShell, которая использовалась в главе 6 для запуска задач. Первый шаг состоит в открытии SCM и получении дескриптора, который впоследствии будет использован для создания службы.

    SC_HANDLE OpenSCManager(LPCTSTR lpMachineName, LPCTSTR lpDatabaseName, DWORD dwDesiredAccess)
     

    Параметры

    lpMachineName — указатель на строку с именем сетевого компьютера, на котором установлен SCM, или NULL, если SCM установлен на локальном компьютере.

    lpDatabaseName — обычно принимает значение NULL.

    dwDesiredAccess — обычно указывается значение SC_MANAGER_ALL_ACCESS, соответствующее полным правам доступа, но можно задать и ограничить эти права, о чем более подробно говорится в оперативной справочной системе.

    Создание и удаление службы

    Для регистрации службы следует вызвать функцию CreateService: 

    SC_HANDLE CreateService(SC_HANDLE hSCManager, LPCTSTR lpServiceName, LPCTSTR lpDisplayName, DWORD dwDesiredAccess, DWORD dwServiceType, DWORD dwStartType, DWORD dwErrorControl, LPCTSTR lpBinaryPathName, LPCTSTR lpLoadOrderGroup, LPDWORD lpdwTagId, LPCTSTR lpDependencies, LPCTSTR lpServiceStartName, LPCTSTR lpPassword);
     

    Информация о новых службах записывается в следующий раздел реестра:

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services

    Параметры

    hSCManager — дескриптор типа SC_HANDLE, полученный через функцию OpenSCManager.

    lpServiceName — имя, используемое при последующих ссылках на службу и являющееся одним из имен логических служб, определенных в диспетчерской таблице при вызове функции StartServiceCtrlDispatcher. Заметьте, что для каждой логической службы используется отдельный вызов CreateService.

    lpDisplayName — имя, которое будет отображаться в реестре в качестве его раздела, а также в административной утилите Services (доступ к которой открывается через пиктограмму Administrative Tools в панели управления). Это имя появится в указанных местах сразу же после успешного завершения функции CreateService. 

    dwDesiredAccess — может принимать значение SERVICE_ALL_ACCESS или комбинацию значений GENERIC_READ, GENERIC_WRITE и GENERIC_EXECUTE. Дополнительную информацию вы можете получить, ознакомившись с оперативной справочной документацией.

    dwServiceType — возможные значения перечислены в табл. 13.1.

    dwStartType — указывает способ запуска службы. В наших примерах используется значение SERVICE_DEMAND_START, соответствующее запуску по требованию, тогда как другие значения (SERVICE_BOOT_START и SERVICE_SYSTEM_START) обеспечивают запуск служб драйверов устройств на стадии начальной загрузки или во время загрузки системы, а значение SERVICE_AUTO_START указывает на то, что служба должна быть запущена во время запуска системы.

    lpBinaryPathName — имя исполняемого файла службы; указывать расширение .exe не требуется.

    Другие параметры используются для указания имени учетной записи и пароля, групп, объединяющих несколько служб, а также зависимостей, если существует несколько отдельных служб.

    Конфигурационные параметры существующей службы можно изменить с помощью функции ChangeServiceConfig или, в случае NT5, ChangeService-Config2. Служба идентифицируется по своему дескриптору, и для большинства параметров вы можете указать новые значения. Например, можно предоставить новые значения параметров dwServiceType или dwStartType, но в случае параметра dwAccess это сделать невозможно.

    Доступна также функция OpenService, которая позволяет получить дескриптор именованной службы. Для удаления службы из реестра используется функция DeleteService, а для закрытия дескрипторов SC_HANDLE — функция CloseServiceHandle.

    Запуск службы

    Созданная служба сразу не выполняется. Для этого необходимо вызвать функцию ServiceMain(), указав дескриптор, полученный при помощи функции CreateService, а также параметры командной строки argc и argv, ожидаемые основной функцией службы (то есть функцией, указанной в таблице диспетчеризации). 

    BOOL StartService(SC_HANDLE hService, DWORD argc, LPTSTR argv[])
     

    Управление службой

    Чтобы начать управление службой, вы должны сообщить SCM о необходимости активизации обработчика управляющих команд службы с указанным управляющим кодом. 

    BOOL ControlService(SC_HANDLE hService, DWORD dwControlCode, LPSERVICE_STATUS lpServStat)
     

    Параметр dwControlCode, если доступ разрешен, может принимать одно из следующих значений:

    SERVICE_CONTROL_STOP

    SERVICE_CONTROL_PAUSE

    SERVICE_CONTROL_CONTINUE

    SERVICE_CONTROL_INTERROGATE

    SERVICE_CONTROL_SHUTDOWN

    или значение, определенное пользователем, лежащее в пределах диапазона 128–255. Эти значения совпадают с теми, которые использовались вместе с флагом dwControl в функции ServerCtrlHandler.

    lpServStat — указатель на структуру SERVICE_STATUS, которая получает текущее состояние. Это та же структура, которая использовалась функцией SetServiceStatus.

    Опрос состояния службы

    Для получения структурой SERVICE_STATUS текущего состояния службы используется следующая функция: 

    BOOL QueryServiceStatus(SC_HANDLE hService, LPSERVICE_STATUS lpServiceStatus)
     

    Резюме: функционирование и управление службой

    На рис. 13.1 показано, каким образом SCM связан со службами и программой управления службами, подобной программе 13.3, которая рассматривается в следующем разделе. В частности, SCM должен зарегистрировать службу, и все команды, предназначенные для службы, должны пропускаться через SCM. 

    Рис. 13.1. Управление службами Windows через SCM

    Пример:команднаяоболочкауправленияслужбами

    Управление службами часто осуществляется посредством утилит, входящих в группу Administrative Tools, доступ к которым открывается через пиктограмму Services (Службы). Для управления пользовательскими службами можно также использовать оболочку ServiceShell (программа 13.3), представляющую собой видоизмененный вариант программы JobShell из главы 6 (программа 6.3).

    Программа 13.3. ServiceShell: программа управления службами

    /* Глава 13. */

    /* ServiceShell.с. Программа командной оболочки управления службами Windows.

       Эта программа является видоизмененным вариантом программы управления задачами из главы 6, но только управляет службами, а не задачами. */

    /* Поддерживаемые команды:

       create — создание службы

       delete – удаление службы

       start – запуск службы

       control – управление службой */

    #include "EvryThng.h"


    static SC_HANDLE hScm;

    static BOOL Debug;


    int _tmain(int argc, LPTSTR argv[]) {

     BOOL Exit = FALSE;

     TCHAR Command[MAX_COMMAND_LINE + 10], *pc;

     DWORD i, LocArgc; /* Локальный параметр argc. */

     TCHAR argstr[MAX_ARG][MAX_COMMAND_LINE];

     LPTSTR pArgs[MAX_ARG];

     /* Подготовить локальный массив "argv" в виде указателей на строки. */

     for (i = 0; i < MAX_ARG; i++) pArgs[i] = argstr[i];

     /* Открыть диспетчер управления службами на локальной машине. */

     hScm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

     /* Главный цикл обработки команд. */

     _tprintf(_T("\nУправление службами Windows Services"));

     while (!Exit) {

      _tprintf(_T ("\nSM$"));

      _fgetts(Command, MAX_COMMAND_LINE, stdin);

      … Как для JobShell …

      if (_tcscmp(argstr [0], _T("create")) == 0) {

       Create(LocArgc, pArgs, Command);

      }

      … Аналогичным образом для всех команд …

     }

     CloseServiceHandle(hScm);

     return 0;

    }


    int Create(int argc, LPTSTR argv[], LPTSTR Command) {

     /* Создание новой службы в виде службы, запускаемой "по требованию":

        argv[1]: имя службы

        argv[2]: отображаемое имя службы

        argv[3]: название исполняемого файла */

     SC_HANDLE hSc;

     TCHAR CurrentDir[MAX_PATH +1], Executable[MAX_PATH + 1];

     hSc = CreateService(hScm, argv[1], argv[2], SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, Executable, NULL, NULL, NULL, NULL, NULL);

     return 0;

    }


    /* Удаление службы – argv [1]: имя удаляемой службы. */

    int Delete(int argc, LPTSTR argv[], LPTSTR Command) {

     SC_HANDLE hSc;

     hSc = OpenService(hScm, argv[1], DELETE);

     DeleteService(hSc); 

     CloseServiceHandle(hSc);

     return 0;

    }


    /* Запуск именованной службы - argv [1] : имя запускаемой службы. */

    int Start(int argc, LPTSTR argv[], LPTSTR Command) {

     SC_HANDLE hSc;

     TCHAR WorkingDir[MAX_PATH + 1];

     LPTSTR pWorkingDir = WorkingDir;

     LPTSTR argvStart[] = {argv[1], WorkingDir};

     GetCurrentDirectory(MAX_PATH + 1, WorkingDir);

     hSc = OpenService(hScm, argv[1], SERVICE_ALL_ACCESS);

     /* Запустить службу с одним аргументом — именем рабочего каталога. */

     /* Примечание: по умолчанию имя службы совпадает с именем, */

     /* связанным с дескриптором hSc посредством функции OpenService. */

     /* Вместе с тем, функция ServiceMain это не проверяет. */

     StartService(hSc, 2, argvStart);

     CloseServiceHandle(hSc);

     return 0;

    }


    /* Управление именованной службой.

       argv[1]: имя управляемой службы.

       argv[2]: управляющая команда: stop (остановка), pause (пауза), resume (возобновление), interrogate (опрос). */

    static LPCTSTR Commands[] = {"stop," "pause," "resume," "interrogate," "user"};

    static DWORD Controls[] = {

     SERVICE_CONTROL_STOP, SERVICE_CONTROL_PAUSE,

     SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_INTERROGATE, 128

    };


    int Control(int argc, LPTSTR argv[], LPTSTR Command) {

     SC_HANDLE hSc;

     SERVICE_STATUS ServiceStatus;

     DWORD dwControl, i;

     BOOL Found = FALSE;

     for (i= 0; i < sizeof(Controls)/sizeof(DWORD) && !Found; i++) Found = (_tcscmp(Commands [i], argv[2]) == 0);

     if (!Found) {

      _tprintf(_T("\nНесуществующая команда управления %s"), argv[1]);

      return 1;

     }

     dwControl = Controls[i – 1];

     hSc = OpenService(hScm, argv[1], SERVICE_INTERROGATE | SERVICE_PAUSE_CONTINUE | SERVICE_STOP | SERVICE_USER_DEFINED_CONTROL | SERVICE_QUERY_STATUS);

     ControlService(hSc, dwControl, &ServiceStatus); 

     if (dwControl == SERVICE_CONTROL_INTERROGATE) {

      QueryServiceStatus (hSc, &ServiceStatus);

      printf(_T("Состояние, полученное при помощи QueryServiceStatus\n"));

      printf(_T("Состояние службы\n"));

      … Вывести всю остальную информацию о состоянии …

     }

     if (hSc != NULL) CloseServiceHandle(hSc);

     return 0;

    }
     

    Совместное использование объектов ядра приложениями и службами

    Возможны ситуации, в которых служба и приложения разделяют объект ядра. Например, служба может использовать именованный мьютекс для защиты разделяемой области памяти, используемой для обмена данными с приложениями. Более того, в нашем примере также будет применяться разделяемый объект ядра, которым в данном случае является отображение файла.

    Существует одна трудность, связанная с тем, что контекст безопасности приложений отличается от контекста безопасности служб, выполняющихся от имени системной учетной записи. Даже если защита не требуется, было бы нелогично создавать и (или) открывать разделяемые объекты ядра с указателем атрибутов безопасности, установленным в NULL (см. глава 15). Вместо этого необходим, по крайней мере, нулевой список разграничительного контроля доступа (см. главу 15), то есть приложения и служба должны использовать ненулевую структуру атрибутов защиты. В общем случае вы захотите защитить объекты, и этот вопрос также будет рассмотрен в главе 15.

    Следует обратить ваше внимание также на то, что если служба выполняется от имени системной учетной записи, то могут возникать трудности с доступом службы к таким ресурсам, как разделяемые файлы, находящиеся на других машинах.

    Регистрация событий

    Службы часто выполняются, внешне ничем себя не проявляя, без диалогового взаимодействия с пользователем. Некоторые службы создают консоль, окно сообщений[34] или окно для взаимодействия с пользователем, но лучше всего записывать информацию о событиях в файл регистрации событий или использовать соответствующие функциональные возможности, предоставляемые Windows. Такая информация сохраняется в реестре, и ее можно просматривать с помощью специальной программы просмотра событий, предоставляемой группой инструментов Administrative Tools, пиктограмма которой находится в панели управления.

    В доступных на Web-сайте книги программах ServiceSK.c и SimpleService.c показано, как организовать регистрацию заслуживающих внимания событий и ошибок в регистрационном файле, а в комментариях к текстам упомянутых программ рассказано о том, как используется регистрация событий. Для этого имеются три функции, описанные в оперативной справочной документации.

    1. RegisterEventSource — позволяет получить дескриптор регистрационного файла.

    2. ReportEvent — используется для внесения записи в регистрационный файл.

    3. DeregisterEventSource — закрывает дескриптор регистрационного файла.

    Замечания по отладке службы

    Предполагается, что служба будет выполняться непрерывно, поэтому она должна быть надежной и по возможности лишенной каких бы то ни было дефектов. Несмотря на возможность подключения службы к отладчику и использования журнала регистрации событий для отслеживания операций, выполняемых службой, эти методы являются наиболее подходящими в условиях, когда служба уже развернута.

    Однако на стадии первоначальной разработки и отладки службы часто гораздо легче воспользоваться преимуществами оболочки службы, представленной в программе 13.2.

    • Разработайте сначала "предварительную" версию службы в виде отдельной программы. В таком ключе, например, была разработана программа serverSK.

    • Используйте в программе средства регистрации событий или предусмотрите регистрационный файл.

    • Когда вы придете к заключению, что программа готова к развертыванию в виде службы, переименуйте основную точку входа и свяжите ее с кодом оболочки службы, представленным программой 13.2 (он находится на Web-сайте книги вместе с двумя программами: SimpleService.c и serviceSK.c).

    • Весьма важную роль играет дальнейшее тестирование службы для обнаружения дополнительных логических ошибок и проблем с обеспечением безопасности. Службы могут выполняться от имени системной учетной записи, но не иметь доступа к пользовательским объектам, и обнаружения проблем подобного рода "предварительная" версия службы не гарантирует.

    • Если служба нуждается в интенсивной поддержке, извлеките ее код из оболочки и превратите его вновь в отдельную программу или консольное приложение, используя для этого GUI или текстовый интерфейс. Можно поступить и по-другому, предусмотрев для функции ServiceMain дополнительный аргумент командной строки, используемый в качестве флага отладки или трассировки.

    Резюме

    Службы Windows предоставляют стандартные возможности подключения пользовательских служб к системе Windows. Используя методы, обсуждавшиеся в этой главе, любую независимую программу можно превратить в службу.

    Для создания служб, а также управления ими и контроля их функционирования можно воспользоваться средствами Administrative Tools (Администрирование) или представленной в этой главе программой ServiceShell. Управление развернутыми службами и их мониторинг осуществляются через SCM, и информация обо всех служб заносится в реестр.

    В следующих главах

    В главе 14 описывается асинхронный ввод/вывод, который предоставляет два метода, позволяющих выполнять множественные операции ввода/вывода параллельно с другими видами обработки. При этом использование нескольких потоков не является обязательным и требуется всего лишь один пользовательский поток.

    В большинстве случаев программировать многопоточное выполнение проще, чем асинхронный ввод/вывод, и производительность в первом случае, как правило, выше. В то же время, без асинхронного ввода/вывода не обойтись, работая с портами завершения ввода/вывода, которые оказываются чрезвычайно полезными при построении масштабируемых серверов, способных взаимодействовать с большим количеством клиентов.

    В главе 14 также рассматриваются таймеры ожидания.

    Дополнительная литература

    Эта тема подробно обсуждается в [21]. Драйверы устройств и их взаимодействие со службами в настоящей главе не рассматривались; соответствующая информация содержится, например, в [24].

    Упражнения

    13.1. Расширьте возможности службы serviceSK таким образом, чтобы она могла воспринимать команды приостановки.

    13.2. При опросе состояния службы оболочка ServiceShell просто выводит соответствующие числовые данные. Расширьте возможности службы, обеспечив вывод информации о состоянии службы в текстовой форме.

    13.3. Преобразуйте сервер serverNP (программа 11.3) в службу.

    13.4. Видоизмените службу serviceSK, введя в нее средства регистрации событий. 


    Примечания:



    3

    Замечания, сделанные в адрес UNIX, в равной степени относятся также к Linux и некоторым другим системам, поддерживающим POSIX API.



    33

    Эта терминология может несколько сбивать с толку, поскольку системы Windows предоставляют многочисленные услуги, которые не относятся к услугам, оказываемым службами Windows Services. Однако использование на протяжении всей этой книги термина "Windows" в тех местах, где имеется в виду API, кое-кем также может восприниматься неоднозначно.



    34

    Если служба вызывает функцию MessageBox, то в качестве типа окна сообщения следует указать MB_SERVICE_NOTIFICATION. Тогда сообщения будут отображаться на активном рабочем столе, даже если ни один из пользователей еще не успел войти в систему на данном компьютере.







     


    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх