Программирование служб 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 и нажмите клавишу ввода.
Вот, как это должно выглядеть:
Как можно видеть, мы запросили данные триггера службы 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.