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

Несколько недель назад мы рассмотрели изоляцию Сессии 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 в качестве триггера для службы, что дает хороший инструмент для управления запуском и остановкой служб приложения.

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

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

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

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

Работа с Trigger Start Services

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

Использование SC.exe для запроса данных триггера службы (Query Service Trigger Information)

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

 sc <server> [command] [service name] <option1> <option2>...

Где server необязательный параметр, указывающий на компьютер (по умолчанию вы работаете с локальным компьютером):

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

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

1. Откройте меню «Пуск».

2. Введите CMD в поле поиска.

3. Выберите cmd.exe.

4. Введите sc qtriggerinfo w32time и нажмите клавишу ввода.

Вот, как это должно выглядеть:

clip_image002

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

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

 C:\>sc triggerinfo
ОПИСАНИЕ:
        Изменяет параметры активации службы.
USAGE:
        sc <сервер> triggerinfo [имя службы] <параметр1> <параметр2>...
 ПАРАМЕТРЫ:
 start/device/UUID/HwId1/... <Запуск службы после получения 
                              строки UUID указанного класса 
                              интерфейса устройства с одной 
                              или несколькими строками кода 
                              оборудования или совместимыми 
                              строками кода>
 start/custom/UUID/data0/.. <Запуск службы после получения 
                              события от строки UUID указанного 
                              настраиваемого поставщика трассировки 
                              событий Windows с одним или несколькими 
                              двоичными элементами данных в формате 
                              шестнадцатеричной строки, например,
                              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            <Остановка службы при отсоединении от домена>
 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.