• Все пользователи имеют права SERVICE_QUERY_CONFIG, SERVICE_QUERY_STATUS, SERVICE_ENUMERATE_DEPENDENTS, SERVICE_INTERROGATE и SERVICE_USER_DEFINED_CONTROL;
• Пользователи, входящие в группу Power Users и учетная запись LocalSystem дополнительно имеют права SERVICE_START, SERVICE_PAUSE_CONTINUE и SERVICE_STOP;
• Пользователи, входящие в группы Administrators и System Operators имеют право SERVICE_ALL_ACCESS.
Службы и интерактивность
По умолчанию интерактивные службы могут выполняться только в контексте безопасности LocalSystem. Это связано с особенностями вывода на экран монитора в Windows NT, где существует, например, такой объект как "Desktop", для работы с которым нужно иметь соответствующие права доступа, которых может не оказаться у произвольной учетной записи, отличной от LocalSystem. Несмотря на то, что в подавляющем большинстве случаев это ограничение несущественно однако иногда существует необходимость создать службу, которая выводила бы информацию на экран монитора и при этом выполнялась бы в контексте безопасности отличном от LocalSystem, например, серверная компонента приложения для запуска приложений на удаленном компьютере.
Следующий фрагмент кода иллюстрирует такую возможность.
// Функция, аналог MessageBox Win32 API
int ServerMessageBox(RPC_BINDING_HANDLE h, LPSTR lpszText, LPSTR lpszTitle, UINT fuStyle) {
DWORD dwThreadId;
HWINSTA hwinstaSave;
HDESK hdeskSave;
HWINSTA hwinstaUser;
HDESK hdeskUser;
int result;
// Запоминаем текущие объекты "Window station" и "Desktop".
GetDesktopWindow();
hwinstaSave = GetProcessWindowStation();
dwThreadId = GetCurrentThreadId();
hdeskSave = GetThreadDesktop(dwThreadId);
// Меняем контекст безопасности на тот,
// который есть у вызавшего клиента RPC
// и получаем доступ к пользовательским
// объектам "Window station" и "Desktop".
RpcImpersonateClient(h);
hwinstaUser = OpenWindowStation("WinSta0", FALSE, MAXIMUM_ALLOWED);
if (hwinstaUser == NULL) {
RpcRevertToSelf();
return 0;
}
SetProcessWindowStation(hwinstaUser);
hdeskUser = OpenDesktop("Default", 0, FALSE, MAXIMUM_ALLOWED);
RpcRevertToSelf();
if (hdeskUser == NULL) {
SetProcessWindowStation(hwinstaSave);
CloseWindowStation(hwinstaUser);
return 0;
}
SetThreadDesktop(hdeskUser);
// Выводим обычное текстовое окно.
result = MessageBox(NULL, lpszText, lpszTitle, fuStyle);
// Восстанавливаем сохраненные объекты
// "Window station" и "Desktop".
SetThreadDesktop(hdeskSave);
SetProcessWindowStation(hwinstaSave);
CloseDesktop(hdeskUser);
CloseWindowStation(hwinstaUser);
return result;
}
В этом фрагменте в ответ на запрос, посланный клиентской частью приложения последством RPC, служба выводит текстовое сообщение на экран монитора.
Пример службы (ключевые фрагменты)
Рассмотрим на примере ключевые фрагменты приложения на языке C++, реализующего службу Windows NT. Для наглядности несущественные части кода опущены.
Функция mainВот как выглядит код функции main:
void main() {
SERVICE_TABLE_ENTRY steTable[] = {
{SERVICENAME, ServiceMain}, {NULL, NULL}
};
// Устанавливаем соединение с SCM. Внутри этой функции
// происходит прием и диспетчеризация запросов.
StartServiceCtrlDispatcher(steTable);
}
Функция ServiceMainОсобенностью кода, содержащегося в ServiceMain, является то, что часто невозможно заранее предсказать время выполнения той или иной операции, особенно, если учесть, что ее выполнение происходит в операционной системе с вытесняющей многозадачностью. Если операция продлится дольше указанного в параметре вызова SetServiceStatus интервала времени, служба не сможет во-время отправить следующее уведомление, в результате чего SCM остановит ее работу. Примерами потенциально операций могут служить вызовы функций работы с сетью при больших таймаутах или единовременное чтение большого количества информации с медленного носителя. Кроме того, такой подход совершенно не применим при отладке службы, поскольку выполнение программы в отладчике сопровождается большими паузами, необходимыми разработчику.
Для преодоления этой проблемы все операции по взаимодействию с SCM следует выполнять в отдельном потоке, не зависящем от действий, происходящих на этапе инициализации.
Алгоритм корректного запуска службы, использующий вспомогательный поток:
void WINAPI ServiceMain(DWORD dwArgc, LPSTR *psArgv) {
// Сразу регистрируем обработчик запросов.
hSS = RegisterServiceCtrlHandler(SERVICENAME, ServiceHandler);
sStatus.dwCheckPoint = 0;
sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
sStatus.dwServiceSpecificExitCode = 0;
sStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
sStatus.dwWaitHint = 0;
sStatus.dwWin32ExitCode = NOERROR;
// Для инициализации службы вызывается функция InitService();
// Для того, чтобы в процессе инициализации система не
// выгрузила службу, запускается поток, который раз в
// секунду сообщает, что служба в процессе инициализации.
// Для синхронизации потока создаётся событие.
// После этого запускается рабочий поток, для
// синхронизации которого также
// создаётся событие.
hSendStartPending = CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE hSendStartThread;
DWORD dwThreadId;
hSendStartThread = CreateThread(NULL, 0, SendStartPending, NULL, 0, &dwThreadId);
//Здесь производится вся инициализация службы.
InitService();
SetEvent(hSendStartPending);
if (WaitForSingleObject(hSendStartThread, 2000) != WAIT_OBJECT_0) {
TerminateThread(hSendStartThread, 0);
}
CloseHandle(hSendStartPending);
CloseHandle(hSendStartThread);
hWork = CreateEvent(NULL, TRUE, FALSE, NULL);
hServiceThread = CreateThread(NULL, 0, ServiceFunc, 0, 0, &dwThreadId);
sStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(hSS, &sStatus);
}
// Функция потока, каждую секунду посылающая уведомления SCM
// о том, что процесс инициализации идёт. Работа функции
// завершается, когда устанавливается
// событие hSendStartPending.
DWORD WINAPI SendStartPending(LPVOID) {
sStatus.dwCheckPoint = 0;
sStatus.dwCurrentState = SERVICE_START_PENDING;
sStatus.dwWaitHint = 2000;
// "Засыпаем" на 1 секунду. Если через 1 секунду
// событие hSendStartPending не перешло
// в сигнальное состояние (инициализация службы не
// закончилась), посылаем очередное уведомление,
// установив максимальный интервал времени
// в 2 секунды, для того, чтобы был запас времени до
// следующего уведомления.
while (true) {
SetServiceStatus(hSS, &sStatus);
sStatus.dwCheckPoint++;
if (WaitForSingleObject(hSendStartPending, 1000) != WAIT_TIMEOUT) break;
}
sStatus.dwCheckPoint = 0;
return 0;
}
// Функция, инициализирующая службу. Чтение данных,
// распределение памяти и т.п.
void InitService() {
...
}
// Функция, содержащая «полезный» код службы.
DWORD WINAPI ServiceFunc(LPVOID) {
while (true) {
if (!bPause) {
// Здесь содержится код, который как правило
// выполняет какие-либо циклические операции...
}
if (WaitForSingleObject(hWork, 1000) != WAIT_TIMEOUT) break;
sStatus.dwCheckPoint = 0;
return 0;
}
}
Функция HandlerА вот код функции Handler и вспомогательных потоков:
// Обработчик запросов от SCM
void WINAPI ServiceHandler(DWORD dwCode) {
switch (dwCode) {
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0, 1000);
hSendStopPending = CreateEvent(NULL, TRUE, FALSE, NULL);
hSendStopThread = CreateThread(NULL, 0, SendStopPending, NULL, 0, & dwThreadId);
SetEvent(hWork);
if (WaitForSingleObject(hServiceThread, 1000) != WAIT_OBJECT_0) {
TerminateThread(hServiceThread, 0);
}
SetEvent(hSendStopPending);
CloseHandle(hServiceThread);
CloseHandle(hWork);
if(WaitForSingleObject(hSendStopThread, 2000) != WAIT_OBJECT_0) {
TerminateThread(hSendStopThread, 0);
}
CloseHandle(hSendStopPending);
sStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(hSS, &sStatus);
break;
case SERVICE_CONTROL_PAUSE:
bPause = true;
sStatus.dwCurrentState = SERVICE_PAUSED;
SetServiceStatus(hSS, &sStatus);
break;
case SERVICE_CONTROL_CONTINUE:
bPause = true;
sStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(hSS, &sStatus);
break;
case SERVICE_CONTROL_INTERROGATE:
SetServiceStatus(hSS, &sStatus);
break;
default:
SetServiceStatus(hSS, &sStatus);