Служби Windows NT. Призначення та розробка, Windows, Операційні системи, статті

Михайло плакун, СофтТерра

Служби Windows NT, загальні поняття

Служба Windows NT (Windows NT service) – спеціальний процес, що володіє уніфікованим інтерфейсом для взаємодії з операційною системою Windows NT. Служби діляться на два типи – служби Win32, взаємодіючі з операційною системою за допомогою диспетчера управління службами (Service Control Manager – SCM), і драйвера, що працюють по протоколу драйвера пристрою Windows NT. Далі в цій статті ми будемо обговорювати тільки служби Win32.

Застосування служб

Одним з найважливіших властивостей служби є неінтерактивність. Типове «поведінка» служби – це непомітна для звичайного користувача робота у фоновому режимі. В силу цього служби найбільш підходять для реалізації наступних типів додатків:


Основні властивості служб

Від звичайного застосування Win32 службу відрізняють 3 основних властивості. Розглянемо кожне з них.

По-перше, це можливість коректного останову (призупинити) роботи служби. Користувач або інший додаток, що використовують стандартні механізми, мають можливість змінити стан служби – перевести її зі стану виконання в стан паузи або навіть зупинити її роботу. При цьому служба перед зміною свого стану отримує спеціальне повідомлення, завдяки якому може зробити необхідні для переходу в новий стан дії, наприклад, звільнити зайняті ресурси.

По-друге, можливість запуску служби до реєстрації користувача і, як наслідок, можливість роботи взагалі без зареєстрованого користувача. Будь служба може бути запущена автоматично при старті операційної системи і почати роботу ще до того як користувач зробить вхід в систему.

І, нарешті, можливість роботи в довільному контексті безпеки. Контекст безпеки Windows NT визначає сукупність прав доступу процесу до різних об’єктів системи та даних. На відміну від звичайного додатки Win32, яке завжди запускається в контексті безпеки користувача, зареєстрованого в даний момент в системі, для служби контекст безпеки її виконання можна визначити заздалегідь. Це означає, що для служби можна визначити набір її прав доступу до об’єктів системи заздалегідь і тим самим обмежити сферу її діяльності. Стосовно до служб існує спеціальний вид контексту безпеки, що використовується за умовчанням і називається Local System. Служба, запущена в цьому контексті, володіє правами тільки на ресурси локального комп’ютера. Ніякі мережеві операції не можуть бути здійснені з правами Local System, оскільки цей контекст має сенс тільки на локальному комп’ютері і не розпізнається іншими комп’ютерами мережі.

Взаємодія служби з іншими додатками

Будь-який додаток, що має відповідні права, може взаємодіяти зі службою. Взаємодія, в першу чергу, має на увазі зміну стану служби, тобто переведення її в одне з трьох станів – Працює (Запуск), пріостанов (Пауза), останов і здійснюється за допомогою подачі запитів SCM. Запити бувають трьох типів – повідомлення від служб (фіксація їх станів), запити, пов’язані зі зміною конфігурації служби або отриманням інформації про неї і запити додатків на зміну стану служби.

Для управління службою необхідно в першу чергу отримують її дескриптор за допомогою функції Win32 API OpenService. Функція StartService запускає службу. При необхідності зміна стану служби проводиться викликом функції ControlService.

База даних служби

Інформація про кожну службі зберігається в реєстрі – в ключі HKLM \ SYSTEM \ CurrentControlSet \ Services \ ServiceName. Там містяться такі відомості:


Програми, яким потрібно отримати інформацію про будь-службі або змінити той чи інший параметр служби, по суті повинні змінити інформацію в базі даних служби в реєстрі. Це можна зробити за допомогою відповідних функцій Win32 API:


Внутрішній устрій служби.

Для того, щоб «бути службою», додаток має бути влаштовано відповідним чином, а саме – включати в себе певний набір функцій (в термінах C + +) з певною функціональністю. Розглянемо коротко кожну з них.

Функція main

Як відомо функція main – точка входу будь-якого консольного Win32 програми. При запуску служби насамперед починає виконуватися код цієї функції. Втечение 30 секунд з моменту старту функція main повинна обов’язково викликати StartServiceCtrlDispatcher для встановлення з’єднання між додатком і SCM. Всі комунікації між будь службою даного застосування і SCM здійснюються всередині функції StartServiceCtrlDispatcher, яка завершує роботу тільки після зупинки всіх служб в додатку.

Функція ServiceMain

Крім общепроцессной точки входу існує ще окрема точка входу для кожної із служб, реалізованих в додатку. Імена функцій, які є точками входу служб (для простоти назвемо їх всіх однаково – ServiceMain), передаються SCM в одному з параметрів при виклику StartServiceCtrlDispatcher. При запуску кожної служби для виконання ServiceMain створюється окремий потік.

Отримавши управління, ServiceMain насамперед повинна зареєструвати обробник запитів до служби, функцію Handler, свою для кожної із служб в додатку. Після цього в ServiceMain зазвичай йдуть будь-які дії для ініціалізації служби – виділення пам’яті, читання даних і т.п. Ці дії повинні обов’язково супроводжуватися повідомленнями SCM про те, що служба все ще знаходиться в процесі старту і ніяких збоїв не відбулося. Повідомлення надсилаються за допомогою викликів функції SetServiceStatus. Всі дзвінки, крім самого останнього повинні бути з параметром SERVICE_START_PENDING, а останній – з параметром SERVICE_RUNNING. Періодичність викликів визначається розробником служби, виходячи із наступного умови: тривалість тимчасового інтервалу між двома сусідніми викликами SetServiceStatus не повинна перевищувати значення параметра dwWaitHint, переданого SCM при першому з двох викликів. В іншому випадку SCM, не отримавши вчасно чергового повідомлення, примусово зупинить службу. Такий спосіб дозволяє уникнути ситуації «зависання» служби на старті в результаті виникнення тих чи інших збоїв (згадаємо, що служби зазвичай неінтерактивних і можуть запускатися під час відсутності користувача). Звичайна практика полягає в тому, що після завершення чергового кроку ініціалізації відбувається повідомлення SCM.

Функція Handler

Як уже згадувалося вище, Handler – це прототип callback-функції, обробника запитів до служби, своїй для кожної служби в додатку. Handler викликається, коли службі приходить запит (запуск, пріостанов, відновлення, останов, повідомлення поточного стану) і виконує необхідні відповідно до запиту дії, після чого повідомляє новий стан SCM.

Один запит слід відзначити особливо – запит, що надходить при завершенні роботи системи (Shutdown). Цей запит сигналізує про необхідність виконати деініціалізацію і завершитися. Microsoft стверджує, що для завершення роботи кожній службі виділяється 20 секунд, після чого вона зупиняється примусово. Проте тести показали, що ця умова виконується не завжди і служба примусово зупиняється до закінчення цього проміжку часу.

Система безпеки служб

Будь-яка дія над службами вимагає наявності відповідних прав у додатку. Всі додатки володіють правами на з’єднання з SCM, перерахування служб і перевірку заблокованості БД служби. Реєструвати в сиситема нову службу або блокувати БД служби можуть тільки додатки, що володіють адміністративними правами.

Кожна служба має дескриптор безпеки, що описує які користувачі мають права на ту чи іншу операцію. За замовчуванням:


Служби і інтерактивність

За замовчуванням інтерактивні служби можуть виконуватися лише в контексті безпеки LocalSystem. Це пов’язано з особливостями виведення на екран монітора в Windows NT, де існує, наприклад, такий об’єкт як “Desktop”, для роботи з яким потрібно мати відповідні права доступу, яких може не виявитися у довільній облікового запису, відмінній від LocalSystem. Незважаючи на те, що в переважній більшості випадків це обмеження несуттєво проте іноді існує необхідність створити службу, яка виводила б інформацію на екран монітора і при цьому виконувалася б в контексті безпеки відмінному від LocalSystem, наприклад, серверна компоненту програми для запуску додатків на віддаленому комп’ютері.

Фрагмент коду з Прикладу 1. ілюструє таку можливість.

У цьому фрагменті у відповідь на запит, посланий клієнтською частиною програми наслідків RPC, служба виводить текстове повідомлення на екран монітора.

Приклад служби (ключові фрагменти)

Розглянемо на прикладі ключові фрагменти програми на мові С + +, що реалізовує службу Windows NT. Для наочності несуттєві частини коду опущені.

Функція main

В Прикладі 2. показаний код функції main.

Функція ServiceMain

Особливістю коду, що міститься в ServiceMain, є те, що часто неможливо заздалегідь передбачити час виконання тієї чи іншої операції, особливо, якщо врахувати, що її виконання відбувається в операційній системі з витісняючої багатозадачністю. Якщо операція триватиме довше зазначеного в параметрі виклику SetServiceStatus інтервалу часу, служба не зможе вчасно відправити наступне повідомлення, в результаті чого SCM зупинить її роботу. Прикладами потенційно «небезпечних» операцій можуть служити виклики функцій роботи з мережею при великих таймауту або одноразова читання великої кількості інформації з повільного носія. Крім того, такий підхід зовсім не застосуємо при налагодженні служби, оскільки виконання програми в відладчик супроводжується великими паузами, необхідними розробнику.

Для подолання цієї проблеми всі операції по взаємодії з SCM слід виконувати в окремому потоці, не залежить від дій, що відбуваються на етапі ініціалізації.

В Прикладі 3. показаний алгоритм коректного запуску служби, який використовує допоміжний потік.

Функція Handler

В Прикладі 4. показаний код функції Handler і допоміжних потоків. Для запитів “Stop” і “Shutdown” використовується алгоритм коректного останову служби, аналогічний тому, який використовується при старті служби, з тією лише різницею, що замість параметра SERVICE_START_PENDING в SetserviceStatus передається параметр SERVICE_STOP_PENDING, а замість SERVICE_RUNNING – SERVICE_STOPPED.

В ідеалі для запитів “Pause” і “Continue” теж слід використовувати цей підхід. Допитливий читач без зусиль зможе реалізувати його, спираючись на дані приклади.

Висновок

На закінчення хотілося б відзначити, що з переходом на Windows NT 2000 розробка служб не зазнала змін. Служби і раніше залишаються важливою частиною програмного забезпечення на платформі Windows, що надає розробникам широке поле діяльності.



Приклад 1
/ / Функція, аналог 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;
}


Приклад 2
void main()
{
SERVICE_TABLE_ENTRY steTable[] =
{
{SERVICENAME, ServiceMain},
{NULL, NULL}
};
/ / Встановлюємо з’єднання з SCM. Усередині цієї функції / / Відбувається прийом і диспетчеризація запитів.
StartServiceCtrlDispatcher(steTable);
}

Приклад 3

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;
}
return 0;
}


Приклад 4
/ / Оброблювач запитів від 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);
break;
}
}
/ / Функція потоку, аналогічна SendStartPending / / Для зупинки служби.
DWORD WINAPI SendStopPending(LPVOID)
{
sStatus.dwCheckPoint = 0;
sStatus.dwCurrentState = SERVICE_STOP_PENDING;
sStatus.dwWaitHint = 2000;
while (true)
{
SetServiceStatus(hSS, &sStatus);
sStatus.dwCheckPoint++;
if(WaitForSingleObject(hSendStopPending,
1000)!=WAIT_TIMEOUT)
break;
}
sStatus.dwCheckPoint = 0;
return 0;
}

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*