Внешне и служба, и обычная программа выглядят для операционной системы одинаково: это исполняемый файл, который превращается в процесс с собственной памятью и потоками выполнения. Но стоит заглянуть чуть глубже, и различия оказываются фундаментальными, а не косметическими. Служба живёт по совершенно другим правилам запуска, взаимодействия с системой и доступа к рабочему столу, и эти правила прописаны на уровне кода задолго до того, как разработчик вообще напишет первую строчку бизнес-логики своего приложения.
Что физически представляет собой обычная программа с точки зрения системы
Когда пользователь дважды кликает по ярлыку, Windows создаёт новый процесс от имени именно этого пользователя, наследуя его права доступа, его переменные окружения и, что важнее всего, привязку к его текущему рабочему столу. Программа получает возможность создавать окна, показывать диалоги, обращаться к буферу обмена и вообще делать всё то, что подразумевает обычное интерактивное взаимодействие с человеком. Жизненный цикл такой программы напрямую связан с сеансом пользователя: если человек выходит из системы, все его обычные процессы принудительно завершаются вместе с сеансом, поскольку продолжать работу без хозяина сессии для них попросту не предусмотрено архитектурой. Более того, обычная программа наследует от пользователя не только права доступа к файлам, но и переменные среды вроде пути к рабочему столу или временной папке, поэтому если запустить одну и ту же программу от имени двух разных учётных записей, она физически будет обращаться к совершенно разным файлам на диске, даже используя внутри себя одинаковый относительный путь.
Через какую функцию служба регистрируется в диспетчере управления
Служба устроена иначе с самого первого момента своего запуска. Вместо привычной точки входа WinMain или обычной main разработчик службы пишет функцию ServiceMain, а сам исполняемый файл при старте обязан немедленно вызвать StartServiceCtrlDispatcher, которая устанавливает связь между процессом и специальным системным компонентом под названием диспетчер управления службами, или Service Control Manager, коротко SCM. Этот диспетчер реализован отдельным процессом services.exe, который запускается автоматически при загрузке компьютера ещё до входа какого-либо пользователя в систему, и именно он решает, когда стартовать конкретную службу, когда её остановить и как реагировать на команды вроде паузы или перезапуска. Минимальный скелет службы на языке C выглядит примерно так:
void WINAPI ServiceMain(DWORD argc, LPTSTR *argv)
{
statusHandle = RegisterServiceCtrlHandler(TEXT("MyService"), ServiceCtrlHandler);
serviceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(statusHandle, &serviceStatus);
while (running) {
DoServiceWork();
}
}
int main()
{
SERVICE_TABLE_ENTRY table[] = {
{ TEXT("MyService"), ServiceMain },
{ NULL, NULL }
};
StartServiceCtrlDispatcher(table);
return 0;
}
Обычная программа не обязана ничего подобного регистрировать, поэтому попытка запустить исполняемый файл службы напрямую двойным щелчком чаще всего просто мгновенно завершается с ошибкой: без диспетчера SCM функция StartServiceCtrlDispatcher не находит, с кем устанавливать связь, и прекращает работу. Разработчики нередко предусматривают в коде проверку именно этого сценария и выводят в таком случае понятное текстовое сообщение о том, что программу нужно устанавливать и запускать как службу через оснастку управления, а не напрямую, чтобы избавить пользователя от загадочного мгновенного закрытия окна консоли без единой подсказки о причине.
Почему службы работают в изолированной сессии с номера ноль
До выхода Windows Vista службы и обычные приложения первого вошедшего в систему пользователя делили одну и ту же сессию, что создавало серьёзную угрозу безопасности: служба с высокими системными привилегиями оказывалась в одном пространстве взаимодействия с куда менее доверенным пользовательским кодом, и злоумышленник теоретически мог подсунуть службе поддельное окно или перехваченное сообщение, эксплуатируя разницу в уровнях доверия. Начиная с Windows Vista, Microsoft полностью отделила службы в собственную, специально выделенную сессию номер ноль, которая физически лишена рабочего стола, панели задач и вообще какого-либо визуального окружения. Пользовательские сеансы при этом получают номера от единицы и выше, и между сессией служб и сессиями пользователей выстроен барьер, через который обычное оконное сообщение просто не пройдёт. Именно поэтому старая практика служб с собственным видимым интерфейсом окончательно ушла в прошлое: современная служба, попытавшаяся показать диалоговое окно, рискует остаться неувиденной, ведь показывать его физически не на каком рабочем столе, доступном человеку. Единственным исключением остаётся крайне редкий и намеренно ограниченный тип интерактивной службы, который до сих пор технически поддерживается системой ради обратной совместимости со старым специализированным программным обеспечением, но подобное окно в лучшем случае появится в самой сессии номер ноль, куда обычный пользователь никогда не попадает при штатной работе за компьютером.
Под какими учётными записями запускаются службы
Обычная программа всегда работает от имени того пользователя, который её запустил, и её права полностью совпадают с правами этого человека в системе. Служба же чаще всего запускается от одной из специальных встроенных учётных записей, ни одна из которых не соответствует конкретному живому пользователю компьютера. Самая привилегированная из них называется LocalSystem и предоставляет практически неограниченный доступ к локальному компьютеру, что делает её мощным, но и потенциально опасным выбором для служб с посредственным качеством кода. Учётная запись NetworkService обладает урезанными локальными правами, но при этом может обращаться к сетевым ресурсам от имени компьютера, а LocalService и вовсе ограничена локальной машиной без каких-либо сетевых привилегий вовсе. Такое разделение позволяет администратору выдавать каждой конкретной службе ровно столько прав, сколько ей реально нужно для работы, не привязывая её жизненный цикл к учётной записи какого-то отдельного человека, который завтра может уволиться или сменить пароль. Служебные учётные записи к тому же не подчиняются обычной политике истечения пароля, применяемой к живым пользователям, что решает ещё одну практическую проблему администрирования: критичная служба не остановится посреди рабочего дня только из-за того, что кто-то забыл вовремя продлить пароль связанной с ней учётной записи, как это иногда случается с программами, ошибочно настроенными на запуск от имени конкретного человека.
Как несколько служб делят один процесс svchost.exe между собой
Ещё одно заметное архитектурное отличие связано с тем, как службы группируются внутри процессов. Многие системные службы реализованы не как отдельные исполняемые файлы, а как динамические библиотеки, которые загружаются внутрь общего процесса-хоста svchost.exe, и одновременно в памяти компьютера можно наблюдать сразу десяток и более копий svchost.exe, каждая из которых обслуживает собственную группу связанных служб. Такая группировка снижает накладные расходы на создание отдельного процесса под каждую мелкую службу, но одновременно усложняет диагностику: если один экземпляр svchost вдруг начинает потреблять избыточно много памяти или процессорного времени, администратору приходится дополнительно выяснять, какая именно из нескольких служб внутри этого процесса на самом деле виновата в проблеме. Обычные пользовательские программы такой группировкой никогда не пользуются: каждый запущенный человеком exe-файл получает собственный, полностью изолированный процесс. Именно из-за этой архитектурной разницы диспетчер задач показывает рядовому пользователю привычные названия программ вроде браузера или текстового редактора отдельными строками, тогда как десятки системных служб нередко прячутся под одинаковыми на первый взгляд записями svchost.exe, и лишь развернув дополнительные сведения о процессе, можно увидеть полный список конкретных служб, работающих в его памяти в данный момент.
Какие типы автозапуска существуют у служб
Обычная программа запускается либо вручную пользователем, либо через ярлык в папке автозагрузки, который система обрабатывает уже после входа человека в систему. У служб же существует куда более гибкая и формализованная система типов запуска, которую стоит перечислить для наглядности:
- Автоматический запуск означает, что служба стартует ещё на этапе загрузки системы, до появления экрана входа в учётную запись;
- Автоматический запуск с задержкой откладывает старт службы до момента, когда основная нагрузка загрузки системы уже спадёт, снижая конкуренцию за ресурсы диска и процессора;
- Ручной запуск подразумевает, что служба остаётся неактивной, пока её явно не запросит другая служба, программа или сам администратор;
- Отключённый тип полностью блокирует любую попытку запуска службы, даже если какой-то другой компонент системы попытается её вызвать программно.
Такая формализация типов запуска прямо зашита в реестр и обрабатывается диспетчером SCM ещё до появления графической оболочки, тогда как для обычной программы подобной тонкой настройки на уровне самой операционной системы попросту не существует. Разработчику обычной программы приходится реализовывать сравнимую логику вручную, например читая ключ реестра автозагрузки при старте системы или подписываясь на планировщик заданий, тогда как для службы вся эта инфраструктура запуска уже встроена в саму операционную систему и требует от программиста лишь корректно заполнить несколько полей при регистрации.
Как на практике управлять службами через оснастку и утилиту командной строки
Взаимодействие со службами тоже устроено иначе, чем с обычными программами. Для служб существует специализированная оснастка services.msc с графическим списком всех зарегистрированных служб, их текущим статусом и типом запуска, а также утилита командной строки sc.exe, через которую можно создавать, удалять, останавливать и опрашивать состояние службы программно, что особенно удобно в скриптах развёртывания и автоматизации. Обычное приложение подобного централизованного реестра не имеет: операционная система знает о нём ровно до тех пор, пока процесс жив, а после завершения работы всякий след программы в системе исчезает, если только сама программа заранее не позаботилась о собственной автозагрузке через отдельный, куда менее формализованный механизм ярлыков и записей автозапуска в реестре пользователя. Ещё одна практическая разница проявляется в правах на управление: остановить или перезапустить обычную программу способен любой пользователь, у которого она работает, тогда как большинство системных служб защищены списком контроля доступа на уровне самого объекта службы в реестре SCM, и рядовой пользователь без прав администратора попросту не сможет ни остановить, ни перенастроить критичный компонент, даже случайно. Такая модель разграничения прав напрямую отражает разницу в назначении: обычная программа принадлежит конкретному человеку и полностью в его власти, тогда как служба принадлежит всей системе в целом и её жизненный цикл сознательно выведен из-под контроля рядового пользователя ради стабильности компьютера в целом. Понимание этой границы помогает и в повседневной диагностике неполадок: если зависла обычная программа, её можно смело снять через диспетчер задач без последствий для остальной системы, а вот принудительное завершение процесса службы вместо её штатной остановки через SCM способно оставить систему в неконсистентном состоянии, поскольку служба не успеет корректно освободить занятые ресурсы и уведомить зависимые от неё компоненты.