|
유형 |
설명 |
SERVICE_BOOT_START |
시스템 시작시에 같이 시작되며 드라이버 서비스에만 적용 |
SERVECE_SYSTEM_START |
loinitSystem 함수에 의해 시작되며 드라이버 서비스에만 적용 |
SERVICE_AUTO_START |
시스템이 시작 될 때 SCM에 의해 자동으로 시작 |
SERVICE_DEMAND_START |
사용자의 요구가 있을 때 StartService 함수로 시작 |
SERVICE_DISABLED |
사용 중지 된 서비스. 이 상태의 서비스는 수동으로 시작 불가 |
◎ 에러 제어 수준
레지스트리에 ErrorControl값으로 기억되어 있는 에러 제어 수준(Error Control Level)은 서비스 시동 시에 에러가 발생한 경우의 처리를 지정한다. 서비스의 중요도에 따라 에러 제어 수준이 달라지는데 다음 4가지 수준 중 하나가 지정된다.
< 에러 제어 수준의 종류>
값 |
설명 |
SERVICE_ERROR_IGNORE |
에러 로그만 남기고 부팅을 계속 진행 |
SERVICE_ERROR_NORMAL |
에러 로그를 남기고 메시지 박스를 띄어 사용자에게 에러가 발생했음을 알린다. 부팅은 계속 진행 |
SERVICE_ERROR_SEVERE |
에러가 발생하기 전의 서비스 구성(LKG)을 읽어 재부팅한다. LKG로 재부팅중에 에러가 발생하면 에러 로그를 남긴 후 부팅을 계속 진행 |
SERVICE_ERROR_CRITICAL |
에러가 발행하기 전의 LKG를 읽어 재부팅한다. LKG로 재부팅중에 에러가 발생하면 부팅은 실패 |
LKG(Last Known Good)는 쉽게 말해 서비스 DB의 백업본으로서 최후로 부팅에 성공한 서비스 구성표라고 할 수 있다. 시스템은 부팅할 때마다 이 백업본을 다음 레지스트리 위치에 작성해 놓는다.
HKEY_LOCAL_MACHINE\SYSTEM\ControlSetXXX\Services
여기서 XXX는 백업본의 일련번호라고 할 수 있으며 또 다른 레지스트리인 System\Select\LastKnownGood에 저장되어 있다. Critical 에러 제어 수준을 가지는 서비스가 실패하면 대부분의 경우 응급복구 디스켓으로 서버를 복구하거나 아니면 최악의 경우 시스템을 다시 설치해야 한다. 별로 중요하지도 않은 서비스에 Critical 에러 제어 수준을 주었다가는 서비스 이용자의 원성을 사게 될 것이다.
◎ 실행 파일 경로
레지스트리에 ImagePath로 기억되며 서비스 코드를 가지는 실행 파일 경로이다. 직관적으로 가장 이해하기 쉬운 값이다.
◎ 종속성
서비스끼리는 종속성을 가지고 있어 하나의 서비스가 실행되기 위해서는 다른 서비스가 실행 중 이어야 하는 경우가 있는데 서비스가 서비스에게 서비스를 베푸는 경우라고 할 수 있는데 이 종속성 정보도 레지스트리에 저장되어 있다. SCM은 서비스 시작 시에 해당 서비스가 종속되어 있는 서비스가 아직 실행되지 않았다면 그 서비스를 먼저 실행 한 후 해당 서비스를 실행시킨다. 종속성 지정은 시작 유형보다 우선하므로 만약 자동시작 서비스가 수종시작 서비스에 종속된다면 수동시작 서비스가 자동으로 시작 될 수도 있다. 그러나 실상 서비스간의 종속 관계 설정은 자주 사용되지 않는다.
◎ 계정
계정을 지정하지 않으면 Local System이라는 특수한 계정으로 실행되는데 이 계정은 서비스와 같은 시스템 프로세스를 위해 미리 정의되어 있는 계정이며 패스워드는 가지지 않는다. 보통 Local System 계정으로 실행하면 무난하다.
u 서비스 프로그램
서비스 프로그램은 main 함수와 서비스 메인, Handler 함수 세 가지로 구성되어 있는데 서비스 프로그램은 이 세가지 요소를 반드시 구성해야 한다. 이 요소들은 서비스가 SCM과 상호작용하기 위해 반드시 필요하며 서비스 프로그램을 만들기 위한 일종의 의무사항이다.
◎ main 함수
서비스 프로그램은 사용자와 상호 작용이 필요 없기 때문에 보통 콘솔 프로세스로 작성되며 그래서 프로그램의 시작점이 main이다. GUI 형태의 서비스를 작성한다면 WinMain도 가능하기는 하지만 일반적이지 않다. main 함수가 특별히 어떤 의미를 가진다기 보다는 일반적인 C프로그램의 시작점일 뿐이며 서비스 프로세스도 당연히 main 으로부터 시작된다.
main함수가 해야 할 가장 중요한 일은 디스패처 thread를 실행하는 것이다. 디스패처는 서비스를 시작하고 핸들러에게 제어 코드를 전달하는 일을 하는 독립된 thread다. main 함수는 디스패처를 실행시켜 이 프로세스가 포함하고 있는 서비스를 시작할 준비를 한다. main 함수는 이 일 외에는 하는 일이 거의 없으며 대개 모양이 정해져 있다.
◎ 서비스 메인
서비스 메인은 실제 서비스 작업을 하는 본체라고 할 수 있다. 서비스 메인은 main 함수에서 등록되어 서비스를 실행시킬 때 디스패처에 의해 호출되며 서비스 운영에 관한 여러가지 일들을 한다. 핸들러를 등록하고 서비스를 기동하며 자신의 상태 변화를 SCM에 알린다.
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv); |
콘솔 main 함수와 비슷한 원형을 가지며 잘 쓰이지는 않지만 명령행 인수를 받아 들일 수 있다. 이 인수들은 서비스의 동작에 영향을 주지만 사용하는 경우가 드물어 원형만 지킨다면 함수명은 마음대로 바꿔서 쓸 수 있으며 main에서 디스패처를 실행할 때 함수명을 지정한다.
◎ 핸들러
핸들러 함수는 서비스의 제어 신호를 처리하는 함수로서 서비스의 메시지 루프라고 보아도 좋다. 핸들러는 서비스 메인에서 디스패처에 등록된다. 서비스는 사용자로부터 직접 명령을 받지는 않지만 서비스 제어 프로그램이나 서비스 애플릿 등으로부터 명령을 받아 처리한다. SCM이 이 명령을 받아 디스패처로 전달하며 디스패처는 등록된 핸들러에게 제어 신호를 보낸다.
VOID WINAPI Handler(DWORD fdwControl); |
인수로 서비스가 해야 할 작업 내용을 담고 있는 제어 신호값을 받아들인다. 함수 이름은 당연히 마음대로 정할 수 있다.
u Dispatcher
서비스 프로그램이 실행되면 서비스 프로세스의 main 함수가 제일 먼저 실행된다. 이때 main 함수에서 해야 할 가장 중요한 일은 이 프로세스와 프로세스에 속한 서비스들이 SCM과 통신하기 위한 장치를 마련하는 것이다. 실행중인 서비스들은 SCM으로부터 끊임없이 제어 신호를 받아들여야 하고 자신의 상태를 SCM에게 보고해야 하기 때문이다. 디스패처(Service Control Dispatcher)는 SCM과 서비스 프로세스와의 통신을 담당하는 스레드이다. 디스패처는 서비스가 실행중인 동안 무한 루프를 돌며 서비스 시작과 제어 신호 전달을 수행한다. 그러기 위해서 디스패처는 이 프로세스가 어떠한 서비스들을 가지고 있으며 각 서비스들의 서비스 메인 함수의 시작번지를 알고 있어야 한다. 이 정보들은 main 함수가 디스패처를 시작할 때 전달해야 하는데 이때 다음 함수가 사용된다.
BOOL StartServiceCtrlDispatcher(CONST LPSERVICE_TABLE_ENTRY lpServiceTable); |
이 함수는 다음과 같이 정의되어 있는 구조체 배열 포인터를 인수로 요구한다.
typedef struct_SERVICE_TABLE_ENTRY { LPTSTR lpServiceName; LPSERVICE_MAIN_FUNCTION lpServiceProc; } SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY; |
lpServiceName은 서비스의 명칭이며 lpServiceProc은 서비스 메인 함수의 시작 번지이다. 구조체 배열이므로 복수 개의 서비스에 대한 정보를 전달 할 수 있다. 단 배열의 끝을 표시하기 위해 마지막 요소는 NULL,NULL 값으로 되어 있어야 한다
main 함수의 예를 보겠다.
int main() { SERVICE_TABLE_ENTRY ste[] ={ {“Memstat”,(LPSERVICE_MAIN_FUNCTION)MyServiceMain}, {NULL,NULL} }; StartServiceCtrlDispatcher(ste);
return 0; } |
u Service Main
◎ 상태보고
서비스는 실행 중에 자신의 상태 변화가 있을 때마다 SCM에게 보고해야 한다.
BOOL SetServiceStatus(SERVICE_STATUS_HANDLE hServiceStatus, LPSERVICE_STATUS lpServiceStatus); |
첫 번째 인수 hServiceStatus는 보고 주체인 서비스의 상태 핸들인데 이 핸들은 핸들러를 등록 할 때 리턴되는 값이다. 이 핸들은 수시로 사용되므로 별도의 전역 변수에 보관해 두어야 한다. 두 번째 인수 lpServiceStatus는 서비스의 현재 상태를 나타내는 구조체의 포인터이다. 이 구조체는 다음과 같이 선언되어 있다.
typedef struct_SERVICE_STATUS { DWORD dwServiceType; DWORD dwCurrentState; DWORD dwControlsAccepted; DWORD dwWin32ExitCode; DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint; DWORD dwWaitHint; }SERVICE_STATUS, *LPSERVICE_STATUS; |
멤버가 많으며 멤버의 의미도 복잡하다. dwServiceType은 서비스의 유형을 알려주는데 프로세스에 하나의 서비스만 있으므로 SERVICE_WIN32_OWN_PROCESS일 것이다. dwCurrentState는 서비스의 현재 상태를 알려주는 가장 중요한 멤버인데 다음 일곱 가지의 값 중에 하나가 된다.
상태 |
설명 |
SERVICE_STOPPED |
서비스가 중지 |
SERVICE_START_PENDING |
시작 중 |
SERVICE_STOP_PENDING |
중지 중 |
SERVICE_RUNNING |
실행 상태 |
SERVICE_CONTINUE_PENDING |
재개 중 |
SERVICE_PAUSE_PENDING |
일시 중지 중 |
SERVICE_PAUSED |
일시 중지 |
위 에서 보면 알 수 있듯이 PENDING(변환 중)도 하나의 상태로 취급된다.
dwControlAccepted는 서비스의 핸들러가 받아들일 수 있고 처리할 수 있는 제어 신호를 설정한다.
모든 서비스는 현재 서비스 상태를 질문하는 SERVICE_CONTROL_INTERROGATE 신호는 의무적으로 받아들여야 하며 나머지 신호는 선택적으로 받아 들일 수 있다.
신호 |
설명 |
SERVICE_ACCEPT_STOP |
SERVICE_CONTROL_STOP 신호를 받는다. 서비스를 중지 시킬 수 있다. |
SERVICE_ACCEPT_PAUSE_CONTINUE |
일시 중지 하거나 재개할 수 있다. PAUSE나 CONTINUE 신호를 받는다. |
SERVICE_ACCEPT_SHUTDOWN |
시스템 셧다운 통지를 받아 들인다. |
SERVICE_ACCEPT_PARAMCHAGNE |
서비스를 중지 하지 않고 파라미터를 다시 읽어 들일 수 있다. |
SERVICE_ACCEPT_NETBINDCHANGE |
서비스를 중지 하지 않고 네트워크 바인딩 변경 통지를 받을 수 있다. |
SERVICE_ACCEPT_HARDWAREPROFILECHANGE |
서비스를 중지 하지 않고 네트워크 바인딩 변경 통지를 받을 수 있다. |
SERVICE_ACCEPT_POWEREVENT |
컴퓨터의 전원 상태 변경을 통지 받을 수 있다. |
아래 쪽 4개의 신호는 호환성 문제로 사용하기 어려운 신호들이다. 주로 사용되는 신호는 위쪽 두 개 인데 서비스가 일단 시작된 후 중지 될 수 있는지, 일시 중지 될 수 있는지를 알려준다.
여기까지 서비스의 정의와 백그라운드에서 활동 중인 서비스를 확인하는 법 및 간단한 서비스 프로그램에서 사용되는 함수와 인수들에 대해서 알아 보았다. 서비스 제어 프로그램과 설정 프로그램의 함수와 인수들에 대해서도 알아봐야겠지만, 한 번에 너무 많은 기술 문서를 작성하면 오히려 정보 전달의 불편함을 줄 수 있다 생각하여 여기 까지 줄이고(지금도 너무 많다고 생각한다.) 2편으로 찾아 오도록 해야겠다.
|