» Навигация
Биография
Диссертация
Библиотека
Ссылки
Результаты поиска
Индивидуальное задание


» Университет
Мой Университет
Портал магистров
ФВТИ


» Оглавление

Резиденты


Сервисы


Демоны


Литература



  » Диссертация

Написание резидентных программ (сервисов, демонов) под различные операционные системы (DOS, Windows, UNIX).

Руководитель: доцент кафедры ЭВМ Теплинский С.В.


1.Резиденты

    

Что такое резидентная программа?

    Обычно после завершения очередной выполняющейся программы DOS освобождает место в памяти, которое занимала программа, чтобы загрузить на это место новую. Однако есть способ оставить программу в памяти и после ее завершения. Следующая запускаемая программа при этом разместится в памяти после оставленной.
    Остающиеся в памяти после завершения своей работы программы называются резидентными или TSR (Terminate and Stay Resident).
    Для чего может понадобиться составление TSR-программ?
    Существует несколько приложений, для которых подходят TSR-программы. Чаще всего TSR-программы используются для русификации импортных персональных компьютеров. Написано множество программ для загрузки русских шрифтов в память видеоадаптеров, для печати русских букв на принтере в графическом режиме, для русификации клавиатуры и т.п. Для всех этих программ характерно то, что они запускаются один раз при загрузке компьютера - их имена обычно включают в AUTOEXEC.BAT. Эти программы могут переключать на себя обработку прерываний, связанных с выводом на печать или с обращением к клавиатуре и/или выполнять разовые инициализирующие действия, такие как загрузка русских шрифтов в память видеоадаптера.
    Другим примером использования TSR-программ могут служить программы резидентных калькуляторов, справочных баз данных и т.д. Такие программы тоже обычно запускаются через AUTOEXEC.BAT или при необходимости. Они перехватывают клавиатурные прерывания и отслеживают нажатие клавиш. Как только обнаруживается нажатие определенной заранее клавиши, поверх имеющегося на экране изображения выводится окно диалога резидентной программы.
    Иногда TSR-программы используют для обслуживания нестандартной аппаратуры, особенно когда с этой аппаратурой должны работать по очереди несколько разных программ. В этом случае TSR-программа встраивает обработчик какого-либо прерывания, не занятого системой, и через этот обработчик все прикладные программы обращаются к нестандартной аппаратуре.
    Для обслуживания нестандартной аппаратуры больше подходят загружаемые драйверы устройств. Драйверы предоставляют намного более гибкие и богатые средства общения с устройствами, чем TSR-программы. Однако, если все, что вы собираетесь сделать, - это обработать несколько прерываний или обслужить какое-нибудь несложное устройство, то TSR-программы - это то, что вам нужно.
    TSR-программы не могут свободно пользоваться прерываниями DOS из-за того, что программы DOS (и BIOS) не обладают свойством реентерабельности.

    Как программе стать резидентной?

    У вас есть две возможности оставить свою программу резидентной в памяти - использовать прерывание INT 27h или функцию 31h прерывания INT 21h.
    Не стоит увлекаться длинными TSR-программами, так как обычно освободить память, занимаемую ставшей уже ненужной резидентной программой, можно только с помощью перезагрузки операционной системы.
    Библиотека функций C содержит специальную функцию для оставления программы резидентной в памяти. Эта функция использует прерывание INT 21h (функция 31h) и имеет имя _dos_keep(). Первый параметр функции - код завершения (то, что записывается в регистр AL), а второй - длина резидентной части программы в параграфах.

    Вызов резидентной программы

    Возможны два способа вызова резидентной программы: либо прикладная программа выдает прерывание, обрабатываемое TSR-программой, либо сама TSR-программа отслеживает нажатие клавиш оператором компьютера и, в случае нажатия определенной клавиши (или комбинации клавиш), запускает диалоговую часть резидентной программы.
    Первый способ используется прикладными программами, работающими с нестандартной аппаратурой, второй - диалоговыми резидентными программами.
    Самый простой способ организовать связь оператора с TSR-программой - это использовать прерывание по нажатию клавиши.
    Если вы используете прерывание 9, вырабатываемое при каждом нажатии на клавишу для определения момента запуска диалога, не следует забывать об организации цепочки прерываний, с тем чтобы после анализа (или перед анализом) передать управление стандартному обработчику 9-го прерывания.

    Особенности резидентных программ

    TSR-программы имеют некоторые особенности, отличающие их от "нормальных" программ.
    Им не разрешается использовать DOS-прерывания, когда вздумается. Это связано с тем, что DOS проектировалась как однозадачная операционная система, поэтому модули DOS не обладают свойством реентерабельности (повторной входимости).
    Допустим, программа записывает длинный файл на диск. Во время записи вы (возможно, случайно) нажали клавишу, активизирующую TSR-программу записи содержимого экрана в файл.
    Теперь доступа к диску требуют две программы - прикладная, записывающая длинный файл, и Ваша TSR-программа. Запись файла из прикладной программы приостановится, далее произойдет запись копии экрана в файл, после чего возобновится запись файла из прикладной программы. Все было бы хорошо, если бы прикладная программа и TSR-программа не использовали одни и те же внутренние области данных DOS для работы с диском. При запуске TSR-программа безвозвратно испортит текущее состояние служебных областей данных, которые прикладная программа использовала при записи на диск.
    И таких примеров можно привести много. BIOS также далеко не весь реентерабельный. TSR-программа может смело использовать разве лишь прерывание 16h для работы с клавиатурой, которое реентерабельно. Для вывода на экран лучше всего использовать непосредственную запись символов в видеопамять дисплейного адаптера.
    Не стоит пользоваться многими функциями библиотеки C, так как они могут вызывать прерывания DOS. Например, функция malloc() вызывает прерывание DOS для определения размера свободной памяти в системе.
    Могут возникнуть трудности с использованием арифметических действий с числами в формате плавающей запятой, так как функция _dos_keep() при завершении программы восстанавливает прерывания, использовавшиеся для эмуляции арифметики с плавающей запятой и для работы с арифметическим сопроцессором.
    Некоторые из перечисленных проблем (те, что связаны с использованием прерываний DOS) можно решить с помощью недокументированного прерывания INT 28h.
    Это прерывание вызывается DOS при ожидании ввода с клавиатуры. В этот момент вы можете использовать любое прерывание DOS, кроме функций от 00h до 0Сh прерывания INT 21h.
    Можно использовать следующий способ использования прерывания 28h. Обработчик прерывания 9 отслеживает нажатие клавиши, которая должна активизировать TSR-программу. Обнаружив эту клавишу (или комбинацию клавиш), обработчик прерывания 9 устанавливает флаг запроса на активизацию TSR-программы и завершает свою работу обычным способом.
    TSR-программа должна создать свой обработчик прерывания 28h и сцепить его со стандартным. Каждый раз, когда DOS ожидает ввода с клавиатуры (в этот момент DOS не использует сама свои прерывания), вызывается прерывание 28h. Ваш обработчик проверяет флаг активизации, устанавливаемый обработчиком прерывания 9, и если флаг установлен, TSR-программа активизируется и может пользоваться услугами DOS, в частности, файловой системой.
    Разумеется, после выполнения всех необходимых действий TSR-программа должна сбросить флаг активизации.
    Можно также вместе с прерыванием 28 использовать аппаратное прерывание таймера с номером 8. В этом случае надо проверять не только флаг активизации, но и так называемый флаг критической секции DOS. Это байт, адрес которого возвращает недокументированная функция 34h прерывания DOS 21h в регистрах ES:BX. Если этот байт равен 0, то DOS не использует свои прерывания и наступил подходящий момент для активизации TSR-программы.
    TSR-программа может вступить в конфликт с другими TSR-программами или прикладным обеспечением, если будет использовать занятые ими номера прерываний.
    Очень важно не допустить случайного повторного запуска TSR-программы, так как повторное переназначение векторов прерываний почти наверняка приведет систему к краху.
    Для проверки наличия TSR-программы в памяти обычно используют прерывание мультиплексора 2Fh, специально предназначенное для организации элементов мультизадачности в DOS.
    TSR-программа может переназначить это прерывание на себя и сделать так, чтобы оно "откликалось" на какой-либо код функции, зарезервированный для прикладных программ. Можно использовать коды C0h...FFh. При запуске TSR-программа вызывает прерывание 2Fh с выбранным кодом и проверяет ответ (передаваемый, например, в регистре AX). Если прерывание отвечает тем кодом, который задан в TSR-программе, это означает, что копия программы уже есть в памяти и повторная установка недопустима.


2.Сервисы

    

Установка/удаление

    Работа с любой программой начинается с установки и заканчивается удалением. Службы - не исключение. Отличие состоит в том, что при установке службу необходимо зарегистрировать. Правильней и проще писать службы, умеющие устанавливаться/удаляться в полуавтоматическом режиме.
    Функции, выполняющие собственно установку/удаление, выглядят примерно так:
    void CmdLine::Install()
    {
    открываем SCM (OpenSCManager)
    создаём службу (CreateService)
    закрываем всё, что открыли
    }
    
    void CmdLine::Uninstall()
    
    открываем SCM (OpenSCManager)
    открываем службу (OpenService)
    удаляем службу (DeleteService)
    закрываем всё, что открыли
    

Отсчёт пошёл…
    На некоторых этапах выполнения служба должна выполнить определённые действия за определённый срок. В MSDN с разной степенью конкретности перечислены пять требований. В книге Джеффри Рихтера и Джейсона Кларка "Программирование серверных приложений в Windows 2000" приведено шестое. Ниже перечислены сами требования и комментарии к ним.
    1. Служба должна вызвать StartServiceCtrlDispatcher не позже, чем через 30 секунд после начала работы, иначе выполнение службы завершится. Кроме того, в раздел Event Log'а System будет добавлена запись об ошибке (источник - "Service Control Manager"). Если служба запускается вручную из программы Services, пользователь получит сообщение (MessageBox).
    2. Функция ServiceMain должна вызвать RegisterServiceCtrlHandler[Ex] немедленно. Что будет в противном случае - не указано. Несоблюдение этого правила - один из случаев "нарушений во время инициализации", описанных ниже.
    3. Функция ServiceMain должна вызвать SetServiceStatus первый раз "почти сразу" после RegisterServiceCtrlHandler[Ex], после чего служба должна продолжать вызывать её до тех пор, пока инициализация не закончится. Неправильное использование SetServiceStatus - второй случай "нарушений во время инициализации".
    4. При обработке сообщения служба должна вернуть управление из функции Handler[Ex] в течение 30 секунд, иначе SCM сгенерирует ошибку.
    5. При получении сообщения SERVICE_CONTROL_SHUTDOWN служба должна закончить работу за время, не превышающее число миллисекунд, указанное в параметре WaitToKillServiceTimeout ключа HKLM\System\CurrentControlSet\Control, иначе будет завершена принудительно.
    6. После завершения работы в качестве службы (то есть после посылки службой уведомления об этом) процессу даётся 20 секунд на очистку/сохранение, после этого процесс завершается.
    Если служба быстро инициализируется и мгновенно обрабатывает сообщения, в результате чего автоматически удовлетворяет всем пунктам, то все нормально. Если нет, можно использовать несколько потоков. Например, так:
    1. Дополнительный поток выполняет необходимую инициализацию, а основной поток вызывает StartServiceCtrlDispatcher.
    2. Один из потоков посылает уведомления о продвижении процесса, второй выполняет инициализацию.
    DWORD WINAPI SendPending(LPVOID dwState)
    {
    sStatus.dwCheckPoint = 0;
    sStatus.dwCurrentState = (DWORD) dwState;
    sStatus.dwWaitHint = 2000;
    
    for (;;)
    {
    if (WaitForSingleObject(eSendPending, 1000)!=WAIT_TIMEOUT) break;
    sStatus.dwCheckPoint++;
    SetServiceStatus(ssHandle, &sStatus);
    }
    
    sStatus.dwCheckPoint = 0;
    sStatus.dwWaitHint = 0;
    return 0;
    }
    Уведомления посылаются с помощью функции SetServiceStatus. sStatus - глобальная переменная типа SERVICE_STATUS, описывающая состояние службы, в dwState передаётся состояние, о котором необходимо сообщать, eSendPending - событие, установка которого означает окончание работы этого потока.
    Идея в том, что, если обработка сообщения может затянуться, функция Handler[Ex] инициирует её и завершается, не дожидаясь окончания. Если рабочий поток службы в цикле ожидает каких-то событий, обработку может выполнить он, Handler[Ex] должна только проинформировать его о приходе сообщения, если рабочий поток постоянно занят, можно породить ещё один поток. При подобной реализации необходимо учесть, что следующее сообщение может прийти в течение обработки предыдущего, то есть до того, как служба пошлёт уведомление об окончании обработки. С помощью Services этого не сделать, но пользователь может использовать утилиту Net.
    Ограничения, накладываемые требованиями (5) и (6) обойти не удаётся. Но, в отличие от (5), в (6) момент посылки уведомления о завершении регулируете вы. Поэтому можно выполнять всю необходимую очистку/сохранение заранее.
    Теперь о "нарушениях в процессе инициализации". Варианты нарушений:
    - Задержка перед вызовом RegisterServiceCtrlHandler[Ex].
    - Задержка перед первым вызовом SetServiceStatus.
    - Слишком большие паузы между вызовами SetServiceStatus.
    - Не меняется поле dwCheckPoint структуры, передаваемой SetServiceStatus.
    Во всех перечисленных случаях реакция системы будет одинаковой. А именно:
    A) Служба запускается автоматически.
    - Минуты через две (если за это время "нарушение" не прекратится и служба не начнёт работать нормально) в Event Log-е появится запись "The ... service hung on starting."
    - Если хоть одна служба "повисла", пользователь получит сообщение "At least one service or driver failed during system startup. Use Event Viewer to examine the event log for details." Такое ощущение, что это сообщение появляется в тот момент, когда запускается первая "зависшая" служба.
    B) Служба запускается вручную из Services.
    - Минуты три система подождёт.
    - Появится сообщение об ошибке.
    - В программе Services в столбце Status служба будет помечена словом "Starting".
    В любом случае служба, в конце концов, запустится.
    
    На первый взгляд задействовано три потока: один исполняет main/WinMain, второй - ServiceMain, третий - Handler[Ex]. Очевидно, что первый и третий потоки не подходят. Про второй поток ничего не известно и, вполне возможно, функция ServiceMain должна возвращать управление. Можно поступить просто: создать в ServiceMain дополнительный поток, который выполнял бы работу. Окончание функции выглядить так:
    ...
    // Создаёт рабочий поток и возвращает управление
    Begin();
    }
    Возвращать управление из ServiceMain сразу рекомендуется только службам, не нуждающимся в потоке для выполнения работы (например, вся работа может заключаться в реакции на сообщения).
    Если служба успешно выполнила свою миссию или, наоборот, окончательно провалилась (неважно, во время выполнения или инициализации), её нужно завершить. Несколько вариантов того, "как делать не надо":
    - Завершить все рабочие потоки, поток, выполняющий Handler[Ex] не трогать. В этом случае SCM "ничего не заметит" и служба продолжит выполняться. Это не смертельно, но и не очень хорошо, так как ресурсы используются.
    - Завершить все рабочие потоки, поток, выполняющий Handler[Ex] завершить вызовом ExitThread при обработке первого следующего сообщения. SCM генерирует ошибку и добавляет запись о ней в Event Log.
    - Завершить процесс вызовом ExitProcess. Результат аналогичен предыдущему, даже ошибка такая же. Код завершения процесса не сохраняется.
    Как надо делать. Об окончании работы служба должна сообщить. Как обычно, для сообщения об изменении состояния используется функция SetServiceStatus. В данном случае из всех полей передаваемой в неё структуры SERVICE_STATUS интерес представляют dwCurrentState, dwWin32ExitCode и dwServiceSpecificExitCode. dwCurrentState в любом случае должно быть установлено в SERVICE_STOPPED, значения остальных зависят от ситуации.
    - Служба завершилась успешно. dwWin32ExitCode = NO_ERROR, в Event Log ничего записано не будет.
    - Произошла неисправимая ошибка, и это одна из стандартных ошибок Windows. dwWin32ExitCode = ERROR_..., в Event Log будет добавлена запись, описывающая ошибку, числовое значение ошибки указано не будет.
    - Произошла неисправимая ошибка, специфичная для вашей службы. dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR, в dwServiceSpecificExitCode код ошибки. Так как систему кодирования ошибок программист придумывает сам, расшифровать значение кода может тоже только он. В Event Log будет добавлена запись следующего содержания: "The ... service terminated with service-specific error ..." (в местах многоточий - имя службы и код ошибки).
    Если для завершения службы необходимо выполнить продолжительные действия, в процессе их выполнения имеет смысл посылать уведомления SERVICE_STOP_PENDING. Но это не обязательно.
    Что будет со службой после вызова SetServiceStatus? Это верно для любых вариантов завершения службы, при которых вызывается SetServiceStatus с соответствующими параметрами, кроме случая с SERVICE_CONTROL_SHUTDOWN:
    - Если exe-файл содержит несколько служб и хоть одна из них (не считая завершающейся) запущена, ни процесс, ни один из потоков не будут завершены насильно.
    - После завершения последней службы функция ServiceControlDispatcher возвращает управление в main/WinMain. Если main/WinMain самостоятельно заканчивается в течение 20-ти секунд, то, как и у нормального приложения, выполняется функция ExitProcess() и завершаются все потоки.
    Так как ServiceControlDispatcher возвращает управление в main/WinMain сразу после вызова SetServiceStatus, main/WinMain может вызвать ExitProcess раньше, чем ваш рабочий поток (или потоки, если у вас их несколько) закончат выполнение. В результате, например, могут оказаться невызванными деструкторы стековых объектов. Чтобы избежать этого можно поступить так:
    - получить описатель рабочего потока (например, с помошью DuplicateHandle) и сохранить его в глобальной переменной
    - в main/WinMain дождаться завершения рабочего потока
    Другие (но тоже печальные) возможные последствия преждевременного вызова ExitProcess описаны в MSDN в Q201349: "PRB: ServiceMain thread May Get Terminated in DllMain() when a Service Process Exits Normally".
    - Через 20 секунд после завершения последней службы процесс уничтожается.
    
    В один exe-файл можно поместить несколько служб. Это затрудняет кодирование и отладку, а единственный выигрыш - экономия ресурсов на компьютере пользователя.
    

Интерактивность
    Интерактивности в службах следует избегать. Службы предназначены для непрерывной работы в отсутствии пользователей, поэтому дожидаться, пока оператор нажмёт "OK", можно очень долго. Но, тем не менее, возможности есть.
    Самое простое - отобразить сообщение (MessageBox). Это может любая служба, какие бы флаги не стояли. Для этого нужно в функцию MessageBox[Ex] помимо прочих флагов передать MB_SERVICE_NOTIFICATION или MB_DEFAULT_DESKTOP_ONLY. Первый флаг заставит функцию вывести сообщение на экран, даже если пользователь ещё не вошёл в систему. Представьте: на экране приглашение ввести пароль и десяток сообщений, поздравляющих пользователя. Но для этого придётся написать десять служб, так как процесс не может отображать на экране несколько таких сообщений одновременно, судя по всему, они ставятся в очередь (к MB_DEFAULT_DESKTOP_ONLY это тоже относится). Если установлен второй флаг, сообщение появится только на "нормальном" рабочем столе. Более строго, MB_SERVICE_NOTIFICATION заставляет сообщение появиться на текущем активном desktop-е, а MB_DEFAULT_DESKTOP_ONLY только на "нормальном". Этими флагами можно пользоваться, если определен макрос _WIN32_WINNT и его значение больше или равно 0x0400.
    Если пользовательский интерфейс службы должен быть богаче, существует два выхода. В первом случае должны выполняться следующие условия:
    - Служба запущена в контексте безопасности LocalSystem.
    - Служба должна быть помечена как интерактивная.
    - Значение параметра NoInteractiveServices ключа HKLM\SYSTEM\CurrentControlSet\ Control\Windows\ должно быть равно 0.
    Если всё это так, служба может выводить на экран что угодно. Иначе, служба может попробовать самостоятельно открыть и использовать нужный ей desktop.
    

Отладка
    Есть несколько причин, по которым отлаживать службы сложнее, чем обычные приложения.
    - В службе будет как минимум два потока.
    - Службу запускает SCM.
    - Если в exe-файле несколько служб, отладка будет ещё неприятнее.
    Однако чаще всего удаётся написать приложение, полностью воспроизводящее рабочую часть службы, отладить его и поместить в обёртку из некоторого стандартного кода, превращающего его в службу. Если ядро приложения отлажено, и код, реализующий стандартные функции службы, проверен, при стыковке больших проблем быть не должно. Но (как это ни странно) появляются средние и мелкие. Помимо "отладки без отладчика" остаётся следующее.
    - Присоединить отладчик к запущенной службе.
    - Использовать DebugBreak.
    - В HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options добавить ключ "имя_исполяемого_файла.exe" (без пути), в нём создать строковый параметр Debugger и записать в него полное имя отладчика. При запуске указанного файла, он будет запущен под отладчиком.
    - Использовать SoftIce.
    При отладке кода запуска следует помнить, что ограничение на время (30 секунд) никуда не исчезает, если вы не уложитесь, служба завершится.
    

Администрирование
    Обычно у службы есть какие-то параметры, которые можно настраивать. Иногда нужно иметь возможность определять и корректировать текущее состояние службы.
    Для администрирования пишется отдельное приложение (далее - конфигуратор), которое каким-то образом взаимодействует со службой. Могу использовать следующие варианты:
    - Параметры хранятся в реестре (обычно в HKLM\System\CurrentControlSet\Services\имя_службы\Parameters\). Конфигуратор их изменяет, служба, используя функцию RegNotifyChangeKeyValue, отслеживает эти изменения.
    - Параметры хранятся где угодно. После их изменения конфигуратор посылает службе сообщение. Для этого нужно открыть службу функцией OpenService с правом доступа SERVICE_USER_DEFINED_CONTROL и послать сообщение функцией ControlService. Конечно, в службе необходимо предусмотреть соответствующую обработку.
    - Используются именованные каналы, сокеты, DCOM или любое другое средство коммуникации между процессами. В этом случае помимо передачи параметров можно получать и изменять состояние службы.
    - Самый простой вариант - служба реагирует на изменение параметров только после перезапуска.
    

Event Log
    Интерактивность - это нехорошо. Но служба должна каким-то образом сообщать администратору об ошибках и других важных событиях. Службам, генерирующим несколько десятков (или больше) сообщений в день, целесообразно использовать для этого файлы в формате какой-нибудь СУБД. Для остальных лучшее решение - Event Log. Он достаточно гибок, стандартен, администратор может просмотреть его утилитой Event Viewer.
    Если необходимо записать сообщение в Event Log, нужно:
    - создать файл сообщений (при разработке службы);
    - зарегистрировать в реестре новый источник сообщений (при инсталляции);
    - собственно записать сообщение.
    

Файл сообщений
    Файл сообщений - это exe- или dll-файл, содержащий ресурс "таблица сообщений". Для получения нужно скомпилировать message compiler'ом (mc.exe) правильно написанный текстовый файл , получившийся файл ресурсов включить в проект и скомпоновать. Mc.exe не интегрирован в Visual Studio и запускается из командной строки.
    "Правильно написанный текстовый файл" имеет следующую структуру:
    [Заголовок]
    сообщение_1
    ...
    сообщение_N
    Заголовок необязателен, он может отсутствовать или присутствовать частично. Обычно используется только поле LanguageNames. Синтаксис несложен:
    LanguageNames = (обозначение_для_языка_1 = LangID_1 : имя_bin_файла_1)
    ...
    LanguageNames = (обозначение_для_языка_n = LangID_n : имя_bin_файла_n)
    "обозначение_для_языка" и "имя_bin_файла" могут быть произвольными, таблица LangID есть в MSDN.
    Смысл этого поля - перечисление поддерживаемых языков. Если файл сообщений поддерживает несколько языков, в разных локализациях Windows (русской, английской, ...) Event Log будет отображать разные версии сообщений.
    На каждый определённый в заголовке язык должно быть по одному "телу" (если вы не определили ни одного языка, используйте "English").
    

Регистрация источника сообщений
    В ключе реестра HKLM\System\CurrentControlSet\Services\Eventlog\Application\ нужно создать ключ с любым именем (это имя будет именем источника сообщений), в этом ключе создать строковый параметр EventMessageFile и записать в него полный путь к файлу сообщений.
    Имя источника сообщений отображается Event Viewer-ом в колонке "Source", оно же используется для получения описателя (handle) при записи в Event Log.
    

Запись
    Для начала нужно получить описатель.
    Эту операцию выполняет функция RegisterEventSource.
    Параметры:
    - имя сервера, на котором находится лог. Для записи в лог текущей машины передавайте NULL.
    - имя зарегистрированного источника сообщений.
    Для закрытия описателя используется функция DeregisterEventSource.
    Запись сообщения производит функция ReportEvent.
    

Unicode
    Unicode - кодировка, в которой одному символу соответствуют два байта. В результате получается 65536 различных символов. Использовать Unicode в программах для Windows NT/2000/XP полезно и просто. Полезно потому, что, во-первых, это повышает производительность, во-вторых, позволяет вывести на экран или в файл именно то, что вы хотите, независимо от локализации ОС пользователя и, в-третьих, иногда это гораздо удобнее. А просто потому, что вся необходимая поддержка обеспечена.
    Большинство API-функций, принимающих в качестве параметров строки, существуют в двух вариантах - ANSI и Unicode. ANSI-вариант имеет суффикс "A", Unicode-вариант - суффикс "W" (от wide - широкий). В Windows NT/2000/XP ANSI-функции просто преобразуют переданные строки в Unicode и вызывают соответствующую Unicode-функцию. Unicode - "родная" кодировка для этих ОС. Для Win 9x "родная" кодировка - ANSI, в ОС этой группы полностью реализовано всего несколько Unicode-функций, остальные сразу возвращают ошибку. Поэтому программа, использующая Unicode, в Windows NT/2000/XP будет работать быстрее, а в Win 9x не будет работать вообще. Поскольку в Win 9x служба всё равно не сможет работать, это не должно вас волновать.
    

Мелочи
    Здесь собраны факты, знать которые полезно, но не необходимо.
    - Служба не обязательно является консольным приложением.
    - В параметре ImagePath ключа HKLM\System\CurrentControlSet\Services\имя_службы можно задать командную строку (можно даже "/uninstall").
    - Начиная с Windows 2000 в параметре Description ключа HKLM\System\CurrentControl Set\Services\имя_службы можно задать описание службы. Оно отображается Services в столбце "Description". Для установки этого параметра можно воспользоваться RegSetValueEx или ChangeServiceConfig2. Предпочтительнее пользоваться ChangeServiceConfig2, но проще RegSetValueE.
    - Судя по всему, пока служба не вызовет StartServiceCtrlDispatcher, SCM не может запустить следующую. Это ещё одна причина не помещать инициализацию в main/WinMain.
    - После вызова StartServiceCtrlDispatcher основной поток приложения не простаивает. Как минимум, он исполняет обработчики сообщений всех служб exe-файла. Поэтому "задействовано" не три потока, а два.
    - Когда функция MessageBox вызывается с флагом MB_SERVICE_NOTIFICATION или MD_SERVICE_DEFAULT_DESKTOP_ONLY, в раздел Event Log'а System добавляется запись. Источник - "Application Popup", внутри - содержимое сообщения. Время создания записи соответствует времени вызова функции MessageBox, а не времени отображения сообщения.
    - Сообщения могут приходить абсолютно бессистемно. То есть, например, несмотря на то, что ваша служба не стоит на паузе, пользователь может (утилитой net.exe или какой-нибудь своей) отправить ей сообщение SERVICE_CONTROL_CONTINUE. Если в результате ваша служба упадёт, он будет очень рад, но уважения к вам у него не прибавится.
    - Функция CreateProcessW имеет одну особенность - её второй параметр имеет тип LPWSTR, а не LPCWSTR, причём, если этот параметр будет указателем на константу, произойдет исключение. Несмотря на это, в функцию CreateProcessA можно спокойно передавать указатель на константу, так как при преобразовании из ANSI в Unicode она выделяет буфер и передаёт CreateProcessW указатель на него.


3.ДЕМОНЫ

    Демон (от английского demon или daemon-встречаются обе транскрипции!)представляет собой программу выполняющуюся в фоновом режиме, незаметно для пользователя и дополняющую операционную систему каким либо специальным сервисом. Происхождение названия не имеет ничего общего с ужасами потустороннего мира или игрой DOOM, и представляет собой акроним от "Disk And Execution MONitor". Основная идея, положенная в основу демонов, состоит в том, что эта программа не вызывается пользователем в явной форме, а спокойно ожидает в памяти какого-либо определенного события. Инициатор генерации события может и не подозревать, что в дебрях оперативной памяти его подстерегает голодный демон (а иногда программа может выдать квитанцию о выполнении той или иной операции только в том случае, если она передала информацию соответствующему демону).
    Впрочем, демоны как обработчики событий довольно часто используются и в прикладных программах - например, в приложениях искусственного интеллекта.
    Так, программа обслуживания базы знаний может использовать демонов для реализации машины вывода. При добавлении в базу знаний новой информации активизируются различные демоны (какие именно, определяется содержимым информации), которые переваривают входной поток данных и создают новое правило, которое в свою очередь может пробудить к жизни других демонов и так далее. В результате обновление базы знаний выполняется в фоновом режиме, а основная программа в это время может продолжать выполнение своей главной задачи.
    Демоны, как правило, стартуют сразу же после окончания загрузки системы и не нуждаются в присутствии активных пользователей. Поэтому вы можете организовать работу UNIX-системы без участия человека, например, создав FTP-сервер или BBS, которые запускаются в автоматическом режиме.     

Создание и разрушение процессов
   Единственным способом создания процесса в системе UNIX является использование системного вызова fork (). Функционально, новый или порожденный процесс создается посредством копирования выполняемого или порождающего процесса. Порожденный процесс наследует большую часть атрибутов своего родителя, и в частности, открытые файлы (порожденный процесс обладает копией дескрипторов файлов, открытых его родителем). Особый процесс init является общим предком по отношению ко всем остальным. Он инициализирует совокупность администраторов системы (демонов) и связывает процесс с каждым терминалом. Системный вызов exec () позволяет запустить выполнение новой программы в рамках текущего процесса. Порождающий процесс может быть синхронизирован с порожденными благодаря функции wait (). Последние сигнализируют о своем окончании посредством обращения exit ().
   Если порожденный процесс заканчивается раньше своего родителя, обработка данных производится следующим образом:
   - если процесс-родитель находился в состоянии ожидания, обеспеченного функцией wait (), он выходит из этого состояния;
   - если родителем не была задействована функция типа wait (), порожденный процесс остается в состоянии "зомби", т.е. он возвращает все ресурсы, приданные ему системой, но сохраняет свое местонахождение в таблице процессов до подачи сигнала о его окончании.
   Если порождающий процесс закончился раньше своих преемников, приемным родителем последних становится процесс init ().     

Существует несколько способов создать демон :
   - запустить его при запуске системы, включив его в файл /etc /rc. В этом случае, в программе необходимо создать процесс, окончание которого на ожидается, для того, чтобы не блокировать командный файл запуска;
   - выполнить его с помощью файла crontab, что позволяет периодически контролировать его демоном cron;
   - выполнить его в качестве фоновой задачи из shell, программно создав процесс, окончания которого не ожидается.
   Для правильного кодирования демона необходимо соблюдать не- которые правила:
   - закрыть все дескрипторы открытых файлов;
   - выйти в корневой каталог дерева файловой системы;
   - установить маску создания файлов; - отсоединиться от контрольного терминала, создав собственную группу процессов;
   - игнорировать сигналы ввода-вывода;
   - правильно управлять сигналом SIGCLD, для того, чтобы возможные порожденные процессы не оставались в состоянии "зомби".     

Шаги, чтобы программа стала демоном:
   1. 'fork()' таким образом, родитель может выйти, это возвращает управление к командной линии или оболочке вызывающей программу. Этот шаг требуется для того, чтобы новый процесс гарантируемо не был лидером группы процесса. Следующий шаг, `setsid () ', будет неудачным, если процесс - лидер группы процесса.
   2. 'setsid ()', чтобы стать процессом группы и лидером группы сеанса. С того момента как управление терминалом связано с сеансом, и этот новый сеанс еще не приобрел терминал управления наш процесс не имеет никакого терминала управления.
   3. 'fork()' снова таким образом родитель, (лидер группы сеанса), может выйти.
   Это означает, что мы, как не сеансовый лидер группы, никогда не сможем восстановить управление терминалом.
    4. 'chdir (" / ")', чтобы гарантировать, что наш процесс не удерживает никакой директории в пользовании. Неудача приведет к тому, что администратор не сможет установить файловую систему, потому что это был наш текущий каталог.
    [Эквивалентно, мы могли попасть в любой каталог, содержащий файлы важный для операции демона.]
   5. 'umask (0)' таким образом получаем полный контроль над разрешению на запись. Мы не знаем, какой umask можем, унаследовать.
    [Этот шаг дополнительный]
    6. 'close()' fds 0, 1, и 2. Это стандарт in, out, и error мы наследуем из нашего родительского процесса. Мы не можем знать где эти fds были переадресованы. Много демонов используют 'sysconf ()', чтобы определить предел '_SC_OPEN_MAX'. '_SC_OPEN_MAX' показывает максимум открытых файлов/процессов. Тогда в цикле, демон может закрыть все возможные описатели файла. Программист должен решить сам делать это или нет.
    7. Установить новые открытые описатели для stdin, stdout и stderr. Даже если программист не планирует использовать их, все равно открыть их жорошая идея.
   Точная обработка их - вопрос вкуса; если имеется logfile, например, можно открыть это как stdout или stderr, и открыть '/dev/null' как stdin; альтернативно, можно открыть '/dev/console' как stderr и/или stdout, и '/dev/null' как stdin, или любая другая комбинация, которая имеет смысл для специфического демона.
   Почти ни один пункт из выше перечисленных не необходим (или желателен), если демон начатый посредством 'inetd'. В этом случае, stdin, stdout и stderr все установлены автоматически, чтобы нормально запуститься, и `fork()'s и сеанс манипуляции не должны выполняться (чтобы избежать путаницы 'inetd'). Только `chdir () ' и `umask () ' шаги остаются как полезными.
   Рассмотрим некоторый абстрактный пример алгоритма написания демона.
   Итак, основной процесс запускает дочерний и тот становиться демоном:
   ...
   pid=fork();
   if (pid==0) {
   setsid();
   start_daemon();
   exit(1);
   }
   ...
   Основной процесс сохраняет в отдельном файле pid потомка.
   В функции start_daemon() перехватываются все сигналы и запускается на выполнение основной цикл. Его задача - непрерывное накопление и обработка данных .
   Для двух (SIGINT и SIGUSR1) определены новые обработчики.
   При поступлении SIGINT демон завершает выполнение, по сигналу SIGUSR1 осуществляется считывание данных. Сама функция имеет приблизительно такой вид:
   void start_daemon ()
   {
   /*Перехватываем сигналы*/
   /*Запускаем бесконечный цикл*/
   loop();
   /*Сюда мы не попадем. Но нам сюда и не надо !*/
   exit(0);
   }
   Теперь демон после запуска заходит в цикл и спокойно в нем работает до прихода сигнала SIGINT. Переменные, с которыми имеет дело цикл, определены как глобальные.
   При запуске основного модуля в командной строке задается режим работы - старт демона, его остановка или считывание данных. Для режима "Старт" все понятно, только что его рассмотрели. Если задан режим остановки, считывается pid демона и ему посылается сигнал SIGINT:
   kill(pid, SIGINT);
   Сам обработчик этого сигнала прост :
   void stop_daemon() {
   exit(1);
   }
   После этого выполнение демона завершается.
   Если задан режим считывания данных, демону посылается сигнал SIGUSR1. Функция-обработчик этого сигнала считывает значение глобальной переменной (это может быть даже структура), с которой работал цикл loop(), и сбрасывает результат в отдельный файл.


Литература

1.Андрей Богатырев. Хрестоматия по программированию на Си в Unix.
2.MSDN.
3.Ядро ОС Linux.Руководство системного программиста
4.Зубков С.В. "Assembler для DOS, Windows и UNIX"
5.Джеффри Рихтер и Джейсон Кларк "Программирование серверных приложений в Windows 2000".