Программирование служб Windows 7 с триггерами (ч.1)

Источник: realcoding

Несколько недель назад мы рассмотрели изоляцию Сессии 0 в свете программной совместимости. Поэтому вполне естественно, что мы возвращаемся к обсуждению служб в контексте Windows 7. Но на этот раз мы поговорим о некоторых выгодах оптимизации служб, доступных в Windows 7. Эта статья посвящена новой возможности Windows 7 - Trigger Start Services. Но прежде, чем мы обратимся к API, давайте обрисуем общий фон служб.

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

Мы предпочитаем считать службы запущенными задачами, работающими в фоновом режиме и не затрагивающими операции пользователя. Службы в Windows отвечают за все виды фоновой активности, начиная со службы Remote Procedure Call (RPC), Printer Spooler и вплоть до службы Network Location Awareness.

На протяжении многих лет Windows росла и вместе с ней росло число фоновых служб. Будем честны, фоновые службы в Windows ощущаются довольно болезненно - операционная система изначально поставляется с множеством служб. Помимо этого, независимые разработчики ПО (ISV) и их приложения добавляют еще больше служб. Назовем в пример хотя бы службы обновления программного обеспечения. Вместе с тем, некоторые службы критически важны и требуются в процессе загрузки, в то время как необходимость в других возникает позже, когда определенный пользователь выполняет вход в систему, а иные вовсе не нуждаются в запуске, пока не будут вызваны. Несмотря на это, когда вы смотрите на список запущенных в данный момент служб, вы видите множество служб, которым нет необходимости работать по схеме 24х7.
Что плохого в службах, работающих 24 часа в сутки 7 дней в неделю?
Есть несколько проблем, связанных с наличием служб, работающих по схеме 24х7. Во-первых, зачем что-то должно работать (пускай даже и в фоновом режиме), если в его работе нет нужды? Любой запущенный процесс (включая службы) использует драгоценную память и ресурсы ЦП, которые могли бы использоваться для других приложений и служб. Если вы подсчитаете все службы, запущенные в определенный момент, то они служатся в значительный объем памяти, дескрипторов, потоков и использование ЦП. Все эти "растрачиваемые" ресурсы понижают общую производительность компьютера, его отзывчивость и заставляют пользователя думать, что его компьютеры вялые и медлительные. К тому же, поскольку множество работающих служб настроены на автоматический запуск (начинают работать со входом в систему), эти службы влияют на время загрузки компьютера.

Во-вторых, эти растрачиваемые ресурсы непосредственным образом сказываются на потреблении электроэнергии. Чем больше нагрузки мы даем на ЦП, тем больше электроэнергии компьютер потребляет. Это может быть критически важно для ноутбуков и может сократить время работы батареи на несколько часов.

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

Наконец, если служба работает по схеме 24х7, и если это хорошо известная служба (которая может оказаться у каждого популярного приложения - например, у PDF Reader), то это создает большую поверхность для атаки. Злоумышленник может воспользоваться сведениями о том, что определенное популярное приложение устанавливает службу, работающую в режиме 24х7, и попытаться взломать ее для получения доступа к компьютеру.

Учитывая все вышесказанное, вы можете удивиться, почему так много разработчиков настраивают свои службы на постоянную работу, если у них имеется другая возможность. Даже до Windows 7 было доступно несколько вариантов запуска служб:

  • Disabled (Отключена) полностью отключает службу и предотвращает ее запуск и запуск зависимых служб - это означает, что пользователь должен включить службу вручную из Панели управления или командной строки
  • Manual (Вручную) запускает службу по надобности (в связи с зависимостями других служб) или при вызове службы из приложения при помощи соответствующих API, как будет показано ниже
  • Automatic (Автоматически) запускает службу при входе в систему
  • Automatic Delayed (Автоматически (отложенный запуск) - более новый тип запуска, появившийся в Windows Vista, при помощи которого запуск службы происходит после завершения загрузки и выполнения первоначальных операций, что ускоряет запуск системы

К сожалению, многие ISV (включая саму Microsoft) продолжают настраивать свои службы на автоматический (Automated) или автоматический отложенный запуск (Automatic Delayed), поскольку для всех это представляется простейшим решением. Служба просто работает 24х7 и всегда доступна, устраняя любую необходимость проверки зависимостей или того, запущена ли служба.

Можно привести множество примеров существующих служб, которые могут расходовать куда меньше ресурсов и стать безопаснее, не работая 24х7. Например, подумайте о службе обновлений, которая проверяет наличие новых обновлений для приложения. Если компьютер не подключен к сети и не имеет IP-адреса, зачем работать этой службе? Она ничего не может сделать, так зачем оставлять работающей программу, которая ничего не делает? Подумайте о службе управления политиками, которая используется при изменении групповых политик или при подключении компьютера к домену или отключении от него, но сейчас, когда компьютер подключен к моей домашней сети, служба, опять же, работает впустую.

Появление служб с запуском по триггеру

Решение вышеуказанных проблем заключается в выведении службы из "состояния постоянной работы" в другие виды фоновой активности, такие как запланированные задачи или службы, запускаемые триггером. Эта статья посвящена Windows 7 Trigger Start Services. О Windows 7 Scheduled Tasks можно сказать очень много полезного, что и будет сделано в последующих статьях.

Службы, запускаемые по триггеру (англ. trigger-start service), впервые появились в Windows 7. По сути, это обычная служба, которую вы можете настроить на запуск (или остановку) в случае срабатывания триггера, то есть в определенном случае или состоянии, которые вы сами задаете (например, когда становится доступным IP-адрес или когда он исчезает). Ниже приведен список доступных триггеров, с помощью которых вы можете настроить режим запуска вашей службы:

  • Подключение или отключение устройства
  • Вход в домен или выход из него
  • Открытие или закрытия порта брандмауэра
  • Изменение в групповых политиках
  • Доступность первого IP-адреса / исчезновение последнего IP-адреса
  • Настраиваемое событие - Трассировка событий для Windows (ETW)

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

Так что же такое триггер?

Триггер состоит из:

[lst]

  • Типа события триггера
  • Подтипа события триггера
  • Действия, которое должно быть предпринято при совершении события триггера
  • Одного или более элементов данных, связанных с триггером (для определенных типов событий триггера)
    Подтип и связанные с триггером элементы данных вместе устанавливают состояние для уведомления службы о событии. Формат элемента данных зависит от типа события триггера; элемент данных может состоять из бинарных, строковых или многостроковых данных.

    Работа с Trigger Start Services
    К сожалению, в пользовательском интерфейсе консоли Windows 7 Services MMC нет графического представления Trigger Start Services. Однако у вас есть две возможности. Вы можете по-прежнему использовать старый добрый sc.exe (программа командной строки Service Configuration) или воспользоваться методом WIN32 ChangeServiceConfig2 для программной настройки опций запуска службы, как будет показано в этой статье.

    Использование SC.exe для запроса данных триггера службы (Query Service Trigger Information)
    Пора повеселиться. Начнем с получения сведений о конфигурации некоторых служб. Общая форма для использования конфигурации службы выглядит следующим образом:

    Код:
    sc <server> [command] [service name] <option1> <option2>...

    Где server опционален, а по умолчанию вы работаете с локальным компьютером:

    • command - это операция, которую вы хотите выполнить, например, запрос данных триггера
    • service name - это имя службы, с которой вы хотите работать
    • options - это различные значения (опции), которые вы можете выполнить для настройки службы

    Начнем с запроса определенной службы о ее конфигурации триггера. Для этого нам понадобится запустить окно Windows Shell:

    [ist]1. Откройте меню "Пуск".
    2. Введите CMD в поле поиска.
    3. Выберите cmd.exe.
    4. Введите sc qtriggerinfo w32time и нажмите клавишу ввода.
    Вот, как это должно выглядеть:

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

    Microsoft в Windows 7 обновила приложение командной строки sc.exe для поддержки конфигурации и получения сведений о поддерживаемых триггерах. Введите sc triggerinfo в окне Windows Shell и нажмите клавишу ввода. Результат будет похож на тот, что приведен ниже, и будет содержать все триггеры и сведения о том, как настроить службы на их использование.

    Код:
    ОПИСАНИЕ:
    Изменяет параметры активации службы.
    USAGE:
    sc <сервер> triggerinfo [имя службы] <параметр1> <параметр2>...

    ПАРАМЕТРЫ:
    start/device/UUID/HwId1/... <Запуск службы после получения строки UUID указанного класса интерфейса устройства с одной или несколькими строками кода оборудования или совместимыми строками кода>
    start/custom/UUID/data0/.. <Запуск службы после получения события от строки UUID указанного настраиваемого поставщика трассировки событий Windows с одним или несколькими двоичными элементами данных в формате шестнадцатеричной строки, например,r ABCDABCD, для задания 4 байтов данных>
    stop/custom/UUID/data0/... <Остановка службы после получения события от строки UUID указанного настраиваемого поставщика трассировки событий Windows с одним или несколькими двоичными элементами данных в формате шестнадцатеричной строки, например, ABCDABCD, для задания 4 байтов данных>
    start/strcustom/UUID/data0/.. <Запуск службы после получения события от строки UUID указанного настраиваемого поставщика трассировки событий Windows с одним или несколькими необязательными элементами данных>
    stop/strcustom/UUID/data0/.. <Остановка службы после получения события от строки UUID указанного настраиваемого поставщика трассировки событий Windows с одним или несколькими необязательными элементами данных>
    start/networkon <Запуск службы при первом IP-адресе>
    stop/networkoff <Остановить службу при отсутствии IP-адресов>
    start/domainjoin <Запуск службы при подключении к домену>
    stop/domainleave <Остановка службы при отсоединении от домена>
    start/portopen/параметр <Запуск службы при открытии сетевого порта. Параметр имеет следующую форму: номер_порта;имя_протокола; путь_к_образу;имя_службы>
    stop/portclose/параметр <Остановка службы при закрытии сетевого порта. Параметр имеет следующую форму: номер_порта;имя_протокола; путь_к_образу;имя_службы>
    start/machinepolicy <Запуск службы при изменении групповой политики компьютера или при ее наличии на момент загрузки>
    start/userpolicy <Запуск службы при изменении групповой политики пользователя или при ее наличии на момент загрузки>
    delete <Удаление текущих параметров активации>


    Так всё, что вам нужно для настройки службы на запуск при появлении IP-адреса, - это ввести sc triggerinfo [имя вашей службы] start/networkon , где "имя вашей службы" заменено на имя той службы, которую вы хотите настроить.

    Программная настройка Trigger Start Services при помощи ChanceServiceConfig2
    Более интересным с точки зрения разработчиков аспектом является создание служб, зависящих от триггера, и использование кода для конфигурации службы. В Windows 7 вы можете использовать функцию ChangeServiceConfig2 для настройки данных триггера службы и функцию QueryServiceConfig2 для их вызова.

    Регистрация триггера службы производится вызовом ChangeServiceConfig2 использованием SERVICE_CONFIG_TRIGGER_INFO для параметра dwInfoLevel и представлением данных регистрации триггера в структуре SERVICE_TRIGGER_INFO посредством параметра lpInfo . К тому же, могут быть указаны дополнительные связанные с триггером данные. Ниже приведен пример функции установщика службы, который создает триггер USB-устройства для службы под названием MyService :

    Код:
    define SERVICE_NAME L"MyService"
        //set the device guid
        static const GUID GUID_USBDevice = {
           0x53f56307, 0xb6bf, 0x11d0,
           {0x94, 0xf2, 0x00, 0xa0, 0xc9,
           0x1e, 0xfb, 0x8b }};

    BOOL _SetServiceToStartOnDeviceTrigger()
    {
        BOOL fResult = FALSE;

        SC_HANDLE hScm = OpenSCManager(
            NULL, //local machine
            NULL, //active database
            SC_MANAGER_CONNECT);

        if(hScm != NULL)
        {
            SC_HANDLE hService = OpenService(
                hScm,
                SERVICE_NAME,
                SERVICE_ALL_ACCESS);

            If( hService != NULL)
            {

               LPCWSTR lpszDeviceString = L"USBSTOR\\GenDisk";
               SERVICE_TRIGGER_SPECIFIC_DATA_ITEM deviceData = {0};
               deviceData.dwDataType = SERVICE_TRIGGER_DATA_TYPE_STRING;
               deviceData.cbData =
                           (wcslen(lpszDeviceString)+1) * sizeof(WCHAR);   
               deviceData.pData = (PBYTE)lpszDeviceString;

               
               SERVICE_TRIGGER st;
               st.dwTriggerType =
                           SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL;
               st.dwAction = SERVICE_TRIGGER_ACTION_SERVICE_START;
               st.pTriggerSubtype = (GUID *) &GUID_USBDevice;
               st.cDataItems = 1;
               st.pDataItems = &deviceData;

               
               SERVICE_TRIGGER_INFO sti;
               sti.cTriggers = 1;
               sti.pTriggers = &st;
               sti.pReserved = 0;

               fResult = ChangeServiceConfig2(
                            hService,
                            SERVICE_CONFIG_TRIGGER_INFO,
                            &sti);
            }
            CloseServiceHandle (hService);
        }
        CloseServiceHandle (hScm);

        if(!fResult)
        {
            printf("Service trigger registration failed (%d)\n",
                     GetLastError());
        }
        return fResult;
    }


    Примечание: все службы контролируются Service Control Manager (SCM), который мы рассмотрим в другой статье.

    Вы можете видеть, как в приведенном выше фрагменте кода мы сначала берем дескриптор (hScm) к SCM вызовом openSCManager . Далее мы вызываем openService и проходим дескриптор к SCM - hscm, и имя службы - SERVICE_NAME, к которой мы хотим получить доступ. Последний параметр, SERVICE_ALL_ACCESS, указывает, что у нас имеется полный доступ к службам. Полагая, что теперь у нас в службе имеется верный дескриптор, мы начинаем создавать отдельную структуру, которой воспользуемся вскоре для настройки службы.

    SERVICE_TRIGGER_SPECIFIC_DATA_ITEM задает тип события триггера. Он содержит данные о событии триггера службы. В нашем случае, мы задаем строку, описывающую подключение USB-диска.

    Затем мы задаем структуру SERVICE_TRIGGER, которая представляет события триггеру службы. Заметьте, что именно здесь мы задаем тип триггера (подключение устройства), действие (запуск службы), и подтип триггера (определенный род USB-дисков). Следом мы определяем конкретное устройство, которое будет вызывать службу. Заметьте, что вы можете задать список устройств и их GUID. Также следует отметить, что мы не хотим срабатывания триггера запуска службы при подключении любого USB-устройства, как то мышь или камера. Мы хотим, чтобы служба запускалась только при появлении USB-диска.

    Наконец, мы задаем структуру SERVICE_TRIGGER_INFO, которая содержит данные события триггера службы. Эта структура просто указывает на структуру SERVICE_TRIGGER, которую мы задали ранее, и количество триггеров, число которых в данном случае равно одному.

    Теперь мы можем вызвать функцию ChanceServiceConfig2 и пройти дескриптор к службе, которую мы хотим настроить, параметр SERVICE_CONFIG_TRIGGER_INFO, который указывает, что мы хотим настроить триггер службы, и Null.

    Вот и все. Если вы все сделали правильно, то служба запуститься при подключении USB жесткого диска.

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

    Вы можете узнать больше о Windows 7 при помощи Windows 7 Training Kit for Developers или просмотрев видео, посвященные Windows 7, на Channel 9.

    Вы также можете потренироваться в работе с Windows 7 Trigger Start Services при помощи тренинга Windows 7 Online, являющегося частью Channel 9 Learning Center.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=22876