왜 함수가 실패했는지 이해하는 것이 유용할 경우가 많다. 마이크로소프트튼 발생 가능한 모든 에러 코드들을 모아서 리스트를 만들고 각에러코드들을 32비트의 크기의 숫자 중 하나에 할당하였다. 어떤 에러인지 정확히 알고 싶을 때는 GetLastError?()를 호출하면 된다.
에러를 감지할 때, 호출한 스레드에 적합한 에러 코드 번호를 연결시키기 위하여 스레드 로컬 스토리지라는 메커니즘을 사용한다. (21장)
사용자 애플리케이션을 수행 중 에러를 감지하고, 사용자에게 관련된 텍스트 설명을 보여줄 경우가 발생할때 에러 코드에 해당하는 텍스트 설명으로 바꿀수 있다.
자세한 내용은 24장에서 다룬다.
윈도우 CE의 경우 윈도우 2000과 마찬가지로 유니코드를 기반으로 개발되었다.
중립코드로는 PTSTR, PCTSTR이 정의 되어 있다. ( LP-, P- 는 똑같이 쓰인다. )
이 함수들도 마찬가지로 UNICODE 정의 여부에 따라 -A, -W 함수들로 확장된다.
C 런타임 함수들은 유니코드 문자열을 다루기 위한 좋은 기능이 없다.
엑센트 있는 문자 등은 대소문자 변경이 불가하다. 이런 결점을 보완한 함수이다. 물론 ANSI와 UNICODE 둘다 지원한다.
마이크로소프트의 C 런타임 라이브러리의 printf() 계열의 함수들에 특별히 필트 타입 몇개가 추가되었다.
sprintf에서 %s는 ANSI, %S는 Unicode
swprintf에서 %s는 Unicode, %S는 ANSI
앞의 상속과 마찬가지로 타겟 프로세스에 사용 가능해진 핸들에 대한 통보가 이루어지지 않는다. 이것은 프로세스간의 통신을 통해 해결해야 하는 문제이다.
링커는 실행파일로 링크할 때 적절한 C/C++ 런타임 Startup 함수를 선택한다.
만약 잘못된 Startup 함수가 선택되어 있으면(콘솔 어플리케이션에 윈도우 엔트리 포인트를 요구한다던가) 링커는 "unresolved external symbol" 에러를 리턴한다.
모든 C/C++ 런타임 Startup 함수는 기본적으로 같은 일을 한다.
차이점은
1. ANSI 혹은 유니코드 중 어떤 문자열을 처리하는지 여부
2. C 런타임 라이브러리를 초기화 한 후 어떠한 엔트리 포인트 함수를 호출하는가?
비주얼 C++ 소스 코드는 C 런타임 라이브러리를 활용해서 실행된다. CRT0.C 파일에서 4가지 Startup 함수들에 대한 코드를 찾아볼 수 있다.
Startup 함수가 하는 일을 요약하면 다음과 같다.
1. 새로운 프로세스의 전체 커맨드 라인을 가리키는 포인터를 가진다.
2. 새로운 프로세스의 환경 변수를 가진다.
3. C/C++ 런타임의 전역 변수를 초기화 한다. Stdlib.h 헤더 파일을 인클루드하면 이러한 전역변수에 접근할 수 있다.
4. C 런타임 메모리 할당 함수와 다른 하위 레벨 입출력 루틴이 사용하는 힙을 초기화 한다.
5. 전역 그리고 정적 C++ 클래스 오브젝트의 생성자 함수를 호출한다.
HMODULE 과 HINSTANCE는 완전히 같은 것이다. 16비트 윈도우즈에서 달랐을 뿐이다.
실행 파일 이미지가 로드되는 시작 주소는 링커가 결정한다. 서로 다른 링커는 서로 다른 디폴트 주소를 사용할 수 있다.
비주얼 C++ 링커는 디폴트 시작 주소로 0x00400000을 사용한다. 왜냐하면 윈도우즈98에서 0x00400000는 실행 파일 이미지가 로드될 수 있는 가장 최하위 주소이기 때문이다.
마이크로소프트 링커의 /BASE : address 링커 스위치를 사용해서 애플리케이션이 로드되는 시작주소를 변경할 수 있다.
윈도우즈 98에서 실행 파일을 0x00400000 보다 더 아래쪽의 시작 주소로 로드하고자 하면 윈도우즈98 로더는 실행 파일을 다른 주소로 재배치해야만 한다. 실행은 되지만 로딩 시간이 늘어난다. 그냥 0x00400000 이상으로 잡자.
각 에러 플래그는 OR 연산을 통해 조합하여 사용할 수 있다.
자식 프로세스는 부모로 부터 에러 모드 플래그를 상속 받는다.
| 멤버 |
설명 |
| dwOSVersionInfoSize? |
sizeof(OSVERSIONINFO) 또는 sizeof(OSVERSIONINFOEX) 를 설정하고 GetVersionEx?()를 호출해야 한다. |
| dwMajorVersion? |
호스트 시스템의 메이저 버전 넘버 |
| dwMinorVersion? |
호스트 시스템의 마이너 버전 넘버 |
| dwBuildNumber? |
현재 시스템의 빌드 넘버 |
| dwPlatformId? |
현재 시스템이 지원하는 플랫폼을 명시한다. VER_PLATFORM_WIN32s(win32s), VER_PLATFORM_WIN32_WINDOWS(windows95/98), VER_PLATFORM_WIN32_NT (windows nt/2000), VERPLATFORM_WIN32_CEHH(windows ce) |
| szCSDVersion |
인스톨된 운영체제에 관해 좀 더 많은 정보를 제공하는 문자열을 담는다. |
| wServicePackMajor? |
마지막으로 인스톨된 서비스 팩의 메이저 넘버 |
| wServicePackMinor? |
마지막으로 인스톨된 서비스 팩의 마이너 넘버 |
| wSuiteMask? |
시스템에서 이용 가능한 스위트들을 명시. VER_SUITE_SMALLBUSINESS, VER_SUITE,ENTERPRISE, VER_SUITE_BACKOFFICE, VER_SUITE_COMMUINICATIONS, VER_SUITE_TERMINAL, VER_SUITE_SMALLBUSINESS_RESTRICTED, VER_SUITE_EMBEDDEDNT, VER_SUITE_DATACENTER |
| wProductType? |
VER_NT_WORKSTATION, VER_NT_SERVER, VER_NT_DOMAIN_CONTROLLER |
| wReserved |
예약 |
호스트 시스템의 버전과 애플리케이션이 요구하는 버전의 비교를 더욱 쉽게 하기 위하여 새로운 함수를 제공한다.
호스트 시스템이 정확하게 윈도우즈 2000인지 테스트 하는 방법이다.
| 플래그 |
설명 |
| DEBUG_PROCESS |
부모 프로세스인 디버거는 현재 생성하는 자식 프로세스뿐만 아니라 자식 프로세스가 미래에 생성할 모든 손자 프로세스까지 디버그 할 것임을 시스템에게 알려주는 플래그이다. |
| DEBUG_ONLY_THIS_PROCESS |
부모 프로세스인 디버거는 현재 생성하는 자식 프로세스에서 발생하는 이벤트만 전달받는다. 나중에 자식 프로세스가 손자 프로세스를 생성한다 하더라도, 디버거는 새로운 손자 프로세스들 상에서 발생하는 이벤트들에 대해서는 전달 받지 못한다. |
| CREATE_SUSPENDED |
새로운 프로세스를 생성하되, 프라이머리 스레드를 서스펜드 시키고자 할 때 사용된다. 부모프로세스가 자식프로세스에 대해 실행코드를 실행하기 전에 추가적인 작업이 필요할 경우 사용된다. 자식 프로세스에 추가적인 작업이 완료되면 ResumeThread?()를 호출하여 프로세스가 다시 시작하도록 한다. |
| DETACHED_PROCESS |
시스템이 생성된 CUI 기반 자식 프로세스가 부모의 콘솔윈도우에 접근하는 것을 막고, 자식 프로세스의 출력을 새로운 콘솔 윈도우에게 보내도록 한다. 디폴트는 자식, 부모 둘다 CUI 기반일 때 부모의 콘솔에 자식의 내용을 출력하게 된다. (커맨드 쉘에서 실행시킬때 하나의 쉘을 공유하는 것으로 쉽게 생각할 수 있다. |
| CREATE_NEW_CONSOLE |
새로운 프로세스를 위해서 새로운 콘솔 윈도우를 생성하도록 한다. DETACHED_PROCESS를 함께 명시하면 에러가 발생한다. |
| CREATE_NO_WINDOW |
콘솔 윈도우를 생성하지 않도록 한다. |
| CREATE_NEW_PROCESS_GROUP |
사용자가 Ctrl+C나 Ctrl+Break를 누를 때, 이러한 사항을 알아야 할 프로세스 목록을 변경한다. 이러한 키 조합이 입력되면, 시스템은 프로세스 그룹 내에 있는 모든 프로세스들에게 사용자가 현재 작업을 멈추기를 원한다는 것을 알려 준다.. 새로운 CUI기반 프로세스를 생성할 때 이 플래그를 명시하면 새로운 프로세스 그룹을 생성한다. 그룹내의 어떤 프로세스가 액티브한 상황에서 사용자가 Ctrl+C 나 Ctrl+Break를 입력하면, 시스템은 사용자의 요청을 이 프로세스가 속한 그룹 내의 프로세스들에게만 알린다. |
| CREATE_DEFAULT_ERROR_MODE |
새롭게 생성되는 자식 프로세스가 부모 프로세스로 부터 에러모드를 상속 받지 않도록 한다. |
| CREATE_SEPARATE_WOW_VDM |
윈도우즈 2000에서 16비트 윈도우즈 애플리케이션을 실행할 때 유용핟. 시스템은 별도의 가상 DOS 머신(VDM)을 생성하고 16비트 애플리케이션을 VDM에서 실행한다. 디폴트로 모든 16비트 애플리케이션은 하나의 VDM을 공유한다. 분리된 VDM의 장점은 각자의 입력큐가 있어 하나의 애플리케이션이 임시적으로 멈추어도, 분리된 VDM에서 실행되는 다른 애플리케이션은 계속 수행이 가능하다. |
| CREATE_SHARED_WOW_VDM |
모든 16비트 애플리케이션은 디폴트로 하나의 VDM에서 실행된다. HKEY_LOCAL_MACHINE\System\CurrentControlSet?\Control\WOW 에 설정된 DefaultSeparate?를 VDM값을 yes/no로 ㅜ저하여 디폴트 동작을 변경할 수 있다. |
| CREATE_UNICODE_ENVIRONMENT |
자식 프로세스 환경 블록이 유니코드 문자를 가지도록 한다. 디폴트는 ANSI 문자열 구성이다. |
| CREATE_FORCEDOS |
시스템이 16비트 OS/2 애플리케이션에 임베드 되어 있는 MS-DOS 애플리케이션을 실행하도록 한다. |
| CREATE_BREAKEAWAY_FROM_JOB |
Job 내의 어떤 프로세스가 소속된 Job과 관련이 없는 새로운 프로세스를 생성할 수 있도록 한다. (5장에 자세한 내용) |
fdwCreate 파라미터에는 우선 순위도 지정할 수 있다. 마찬가지로 OR 연산으로 결합한다.
| 멤버 |
윈도우, 콘솔 또는 둘다 |
설명 |
| cb |
둘다 |
STARTUPINFO 구조체 자체의 바이트 단위의 크기를 명시한다. |
| lpReserved |
둘다 |
예약됨 NULL로 초기화 되야함 |
| lpDesktop |
둘다 |
애플리케이션이 시작하는 데스크탑의 이름을 명시한다. 데스크탑이 존재하면, 새로운 프로세스는 명시된 데스크탑과 연결된다. 데스크탑이 존재하지 않거나 NULL인경우 프로세스는 현재의 데스크 탑과 연결된다. |
| lpTitle |
콘솔 |
콘솔 윈도우의 타이틀을 명시한다. NULL이면 실행파일의 이름을 윈도우 타이틀로 사용한다. |
| dwX, dwY |
둘다 |
스크린에서 애플리케이션의 윈도우가 위치할 픽셀단위의 x,y 좌표를 명시한다. CreateWindow?의 x,y 파라미터로 CW_USEDEFAULT를 사용하여 처음으로 오버랩드윈도우를 생성할 경우에만 사용된다. 콘솔윈도우에서는 콘솔 윈도우를 생성하는 애플리케이션에서는 콘솔 윈도우의 좌측 상단을 가리킨다. |
| dwXSize, dwYSize |
둘다 |
애플리케이션 윈도우의 픽셀단위 너비와 높이를 명시한다. CreateWindow?의 nWidth, nHeight 파라미터로 CW_USEDEFAULT를 사용하여 처음으로 오버랩드윈도우를 생성할 경우에만 사용된다. 콘솔윈도우 경우 너비와 높이를 가리킨다. |
| dwXCountChars?, dwYCountChars? |
콘솔 |
자식 콘솔 윈도우의 너비와 높이(문자단위)를 명시한다. |
| dwFillAttribute? |
콘솔 |
자식 콘솔 윈도우가 사용할 텍스트와 백그라운드 색깔을 명시한다. |
| dwFlags |
둘다 |
다음 절과 아래의 테이블에서 기술할 것이다. |
| wShowWindow? |
윈도우 |
nCmdShow? 파라미터로 SW_SHOWDEFAULT를 전달하면서 처음으로 ShowWindow?를 호출하는 경우 적용된다. |
| cbReserved2 |
둘다 |
예약됨. 0으로 초기화 되어야 한다. |
| lpReserved2 |
둘다 |
예약됨. NULL로 초기화 되어야 한다. |
| hStdInput?, hStdOutput?, hStdError? |
콘솔 |
콘솔 입출력 핸들들을 명시한다. 디폴트로 input은 키보드 버퍼, output, error는 콘솔 윈도우 버퍼를 가리킨다. |
| 플래그 |
설명 |
| STARTF_USESIZE |
dwXSize, dwYSize 멤버를 사용한다. |
| STARTF_USESHOWWINDOW |
wShowWindow? 멤버를 사용한다. |
| STARTF_USEPOSITION |
dwX, dwY 멤버를 사용한다. |
| STARTF_USECOUNTCHARS |
dwXCountChars?, dwYCountChars? 멤버를 사용한다. |
| STARTF_USEFILLATTRIBUTE |
dwFillAttribute? 멤버를 사용한다. |
| STARTF_USESTDHANDLES |
hStdInput?, hStdOutput?, hStdError? 멤버를 사용한다. |
| STARTF_RUN_FULLSCREEN |
x86 컴퓨터에서 실행되는 애플리케이션이 전체화면모드로 시작하게 한다. |
| STARTF_FORCEOFFFEEDBACK |
CreateProcess?()는 커서를 스타트 글래스로 바꾸지 않는다. |
| STARTF_FORCEONFEEDBACK |
CreateProcess?()가 새로운 프로세스 초기화를 모니터하고 결과에 따라 커서를 변경하도록 한다. CreateProcess?()가 이 플래그와 함께 호출되면 스타트 글래스로 바뀐다. 만약 2초후에 새로운 프로세스가 GUI를 호출하지 않으면, 다시 화살표로 변경한다. 2초 내에 GUI를 호출하면 이번에는 윈도우를 생성할 때까지 기다린다. 이것은 GUI 호출후 5초이내에 이루어져야 한다. 윈도우가 생성되지 않으면 다시 화살표로 변경한다. 윈도우가 생성되면 다시 5초동안 GetMessage?()호출을 기다린다. 이를 완료하면 즉시 커서를 화살표로 재설정하고, 새로운 프로세스에 대한 모니터링을 중단한다. |
부모 프로세스에 의해서 초기화된 STARTUPINFO 구조체의 복사본을 얻을 수 있다.
프로젝트 커널 오브젝트는 프로세스가 살아 있는 한 항상 살아 있다. 나아가 프로세스보다 프로세스 커널 오브젝트는 더 오래 살아남을 수 도 있다.
프로세스가 종료될때, 시스템은 자동으로 커널 오브젝트의 사용 카운트를 감소시킨다. 이때 0이되면 파괴되지만, 다른 프로세스에서 프로세스 커널 오브젝트의 핸들이 열려진 상태라면 파괴되지 않는다.
이것은 버그가 아니라 특성이다. 프로세스 커널 오브젝트는 프로세스와 관련된 통계적인 정보를 유지하고 있다. 다른 프로세스에서 종료되었건 아니건 간에 프로세스 커널 오브젝트를 통해 종료코드와 같은 정보를 얻을 수 있다.
| 멤버 |
설명 |
주의해야할 점 |
| PerProcessUserTimeLimit? |
100ns 간격 안에서 각 프로세스에 할당된 최대 사용자모드 타임을 명기한다. |
시스템은 할당된 시간보다 많이 사용하는 프로세스를 자동적으로 중단한다. 이 제한을 설정하려면 LimitFlags? 멤버의 JOB_OBJECT_LIMIT_PROCESS_TIME 플래그를 명기한다. |
| PerJobUserTimeLimit? |
100ns 간격 안에서 얼마나 많은 사용자 모드 타임을 Job의 프로세스들이 사용할 수 있는지 명기한다. |
기본으로 시스템은 이 타임 제한에 도달하면 자동적으로 모든 프로세스들을 중단한다. 독자는 job이 실행중일 때 정기적으로 이 값을 변화시킬 수 있다. |
| LimitFlags? |
Job에 어떤 제한을 적용할지 가리킨다. |
보다 많은 정보를 위해서는 이 테이블 다음에 나오는 절을 참조하라. |
| MinimumWorkingSetSize?/MaximumWorkingSetSize? |
각 프로세스를 위한 최대 / 최소작업 셋 크기를 명시한다. Job에 있는 모든 프로세스들에게 적용되는 것은 아니다. |
보통 하나의 프로세스의 작업 셋은 최대보다 높아질 수 있다. MaximumWorkingSetSize?를 셋하면 하드 제한을 한다. 한번 프로세스의 작업 셋이 이 제한에 도달하면 프로세스 페이지는 그에 대항한다. 개인 프로세스가 호출한 SetProcessWorkingSetSize?는 프로세스가 작업 셋을 비우려 하지 않으면 무시된다. 이 제한을 설정하기 위해서는 LimitFlags? 멤버의 JOB_OBJECT_ LIMIT_WORKINGSET플래그를 명기하면 된다. |
| ActiveProcessLimit? |
Job에서 동시에 실행될 수 있는 프로세스의 최대수를 명기한다. |
이 제한을 넘으려고 하면 새로운 프로세스는 "not enough quota" 에러를 내며 중단된다. 이 제한을 설정하려면 LimitFlags? 멤버의 JOB_OBJECT_ LIMIT_ACTIVE_PROCESS플래그를 명기하면 된다. |
| Affinity |
프로세스들을 실행할 수 있는 CPU들의 서브셋을 명기한다. |
개인 프로세스들은 보다 쉽게 제한할 수 있다. 이 제한을 설정 하려면 LinitFlags? 멤버의 JOB_OBJECT_ LIMIT_AFFINITY플래그를 명기하면 된다. |
| PriorityClass? |
모든 프로세스가 사용하는 우선순위 클래스를 명기한다. |
프로세스가 SetPriorityClass?를 호출하면 그것이 실제적으로 실패일지라도 성공인 것처럼 리턴한다. 만약 프로세스가 GetPriorityClass?를 호출하면 함수는 프로세스의 실제 우선순위 클래스가 아닐지라도 프로세스가 우선순위 클래스를 설정한 것을 리턴한다. SetThreadPriority?는 일반 우선권을 넘는 스레드를 일으키는데 실패하지만 낮은 스레드의 우선권을 사용하는데 쓰일 수 있다. |
| SchedulingClass? |
Job에서 스레드에게 배정된 상대적 시간양을 명기한다. |
값은 0부터 9까지 될 수 있다. 5가 기본값이다. 큰 값이 보다 높은 우선순위 이다. 이 제한을 설정하려면 LimitFlags?멤버의 JOB_OBJECT_LIMIT_SCHEDULING_CLASS 플래그를 명기하면 된다. |
이럴 경우, JOB_OBJECT_LIMIT_RESERVE_JOB_TIME 플래그를 사용하면 된다.
이 플래그와 JOB_OBJECT_LIMIT_JOB_TIME 플래그는 상호 배타적이다.
JOB_OBJECT_LIMIT_RESERVE_JOB_TIME 플래그는 시간에 대한 제한을 변경하지 않고 나머지 제한을 변경하겠다는 의미이다.
| UIRestrictionsClass? 플래그 |
설명 |
| JOB_OBJECT_UILIMIT_EXITWINDOWS |
프로세스가 ExitWingowsEx? 함수를 통해 로그오프, 셧다운, 리부팅 또는 시스템이 꺼지는 것으로부터 방지한다. |
| JOB_OBJECT_UILIMIT_READCLIPBOARD |
프로세스가 클립보드를 읽는 것을 방지한다. |
| JOB_OBJECT_UILIMIT_WRITECLIPBOARD |
프로세스가 클립보드를 지우는 것을 방지한다. |
| JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS |
프로세스가 SystemParametersInfo? 함수를 통해 시스템 파라미터를 변경시키는 것을 방지한다. |
| JOB_OBJECT_UILIMIT_DISPLAYSETTINGS |
프로세스가 ChangeDisplaySetting? 함수를 통해 디스플레이 세팅을 변경시키는 것을 방지한다. |
| JOB_OBJECT_UILIMIT_GLOBALATOMS |
Job에게 그 자신의 전역 원자 테이블을 주고 job의 프로세스가 오직 job의 테이블만 접근하도록 제한 한다. |
| JOB_OBJECT_UILIMIT_DESKTOP |
프로세스가 CreateDesktop? 또는 SwitchDesktop? 함수를 사용함으로써 데스크톱을 생성하거나 스위칭하는 것을 방지한다. |
| JOB_OBJECT_UILIMIT_HANDLES |
Job의 프로세스가 같은 job 외부 프로세스에 의해 생성된 HWND와 같은 USER 오브젝트를 사용하는 것을 방지 한다. |
마지막 플래그인 JOB_OBJECT_UILIMIT_HANDLES는 흥미롭다.
이 제한은 Job 내의 어떠한 프로세스도 Job 오부의 프로세스가 만든 USER 오브젝트에 접근할 수 없도록 만든다.
(반대의 경우인 외부의 프로세스가 Job 내부 프로세스가 만든 USER오브젝트에 접근하는것은 언제든지 (플래그가 설정되었든 아니든) 상관이 없다.)
예는 Spy++을 Job 내부에 속하게 한다음 JOB_OBJECT_UILIMT_HANDLES로 설정하면 Spy++은 다른 윈도우를 찾지 못한다.
반대로 Spy++을 외부에 Job 내부에 메모장을 띄운후 JOB_OBJECT_UILIMIT_HANDLES을 설정하면 Spy++이 메모장을 찾는건 가능하다.
완벽한 제한을 가한 상태로 만드는 것은 쉽지만 보통의 경우 외부의 프로세스와 통신이 필요하다.
가장 쉬운 방법은 윈도우 메시지를 이용하는 것인데 Job 프로세스가 UI 핸들에 접근할 수 없다면, Job 내부의 프로세스는 Job 외부에 윈도우 메시지를 Send하거나 Post할 수 없다. 이 문제는 다음 API로 해결한다.
| 알아 내기 위한 제한 종류(두번째 파라미터) |
받아 오기 위한 구조체(세번째 파라미터) |
| JobObjectBasicAccountingInformation? |
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION |
| JobObjectBasicAndIoAccountingInformation? |
JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION |
| JobObjectBasicLimitInformation? |
JOBOBJECT_BASIC_LIMIT_INFORMATION |
| JobObjectBasicProcessIdList? |
JOBOBJECT_BASIC_PROCESS_ID_LIST |
| JobObjectBasic?UIRestrictions |
JOBOBJECT_BASIC_UI_RESTRICTIONS |
| JobObjectExtendedLimitInformation? |
JOBOBJECT_EXTENDED_LIMIT_INFORMATION |
| JobObjectSecurityLimitInformation? |
JOBOBJECT_SECURITY_LIMIT_INFORMATION |
| 멤버 |
설명 |
| TotalUserTime? |
얼마나 많은 사용자모드 CPU타임 프로세스가 job에서 사용되었는지 명기한다. |
| TotalKernelTime? |
얼마나 많은 커널모드 CPU타임 프로세스가 job에서 사용되었는지 명기한다. |
| ThisPeriodTotalUserTime? |
기본 제한 정보를 변경하기 위해 SetInformationJobObject?를 호출할 때와 JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME 제한 플래그가 사용되지 않았을 때 이 값이 0으로 리셋된다는 것을 제외하고는 TotalUserTime?과 같다. |
| ThisPeriodTotalKernelTime? |
이 값이 커널모드 타임을 보여준다는 것을 제외하고는 TimePeriodTotalUserTime?과 같다. |
| TotalPageFaultCount? |
Job 안의 프로세스가 발생시킨 페이지 실패의 총 수를 명기한다. |
| TotalProcesses? |
Job을 나눈 프로세스의 총 수를 명기한다. |
| ActiveProcesses? |
현재적으로 job의 부분인 프로세스의 총 수를 명기한다. |
| TotalTerminatedProcesses? |
할당된 CPU타임 제한을 넘어서 중단된 프로세스의 총 수를 명기한다. |
Job에 대한 정보를 보기 위한 쉬운 방법으로 Microsoft Management Console (MMC) Performance Monitor Snap-In을 사용할 수도 있다.
(XP기준 제어판-관리도구-성능)
| 이벤트 |
설명 |
| JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO |
Job에서 아무런 프로세스도 실행되지 않고 있을 때 발생 |
| JOB_OBJECT_MSG_END_OF_PROCESS_TIME |
프로세스에 할당된 CPU 타임을 초과했을 때 발생. 프로세스는 종료되고 프로세스의 ID가 주어진다. |
| JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT |
Job에서 활동적 프로세스의 수를 넘으려고 했을 때 발생 |
| JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT |
프로세스의 제한을 넘어 메모리를 지정하려는 시도를 했을 때 발생. 프로세스의 ID가 주어진다. |
| JOB_OBJECT_MSG_JOB_MEMORY_LIMIT |
프로세스가 Jobdml 제한을 넘어 메모리를 지정하려는 시도를 했을 때 발생. 프로세스의 ID가 주어진다. |
| JOB_OBJECT_MSG_NEW_PROCESS |
Job에 프로세스가 추가되었을 때 발생. 프로세스의 ID가 주어진다. |
| JOB_OBJECT_MSG_EXIT_PROCESS |
프로세스가 종료되었을 때 발생한다. 프로세스의 ID가 주어진다. |
| JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS |
프로세스가 처리되지 않은 예외상황 때문에 종료되었을 때 발생. 프로세스 ID가 주어진다. |
| JOB_OBJECT_MSG_END_OF_JOB_TIME |
Job에게 할당된 CPU타임을 초과했을 때 발생. 프로세스는 종료되지 않는다. 이것이 계속 실행되게 할 수 있고 새로운 타임 설정을 하거나 TerminateJobObject? 함수를 호출할 수도 있다. |
디폴트 동작은 Job이 할당된 CPU 시간을 소진하면 Job내의 모든 프로세스들은 자동적으로 종료되고 JOB_OBJECT_MSG_END_OF_JOB_TIME통지는 전달되지 않도록 구성된다.
반대로 Job 오브젝트가 프로세스를 종료하지 못하게 하고 또 시간을 소진했음을 알리게끔 하려면..
싱글스레드 애플리케이션용 라이브러리가 필요한 이유는 처음 라이브러리가 만들어진 70년대에는 멀티스레드에 대한 개념이 없었기 때문이다.
표준 C/C++ 런타임 라이브러리가 멀티스레드 애플리케이션을 고려해서 애초에 설계되지 않아서 문제가 생기는 변수와 함수는
errno, _doserrno, strtok, _wcstok, strerror, _strerror, tmpnam, tmpfile, asctime, _wasctime, gmtime, _ecvt, fcvt 등이 있다.
결국 싱글스레드용 C/C++ 런타임은 자신만의 스레드의 데이터 블록안에서 작업을 수행하도록 해야 한다.
그래서 이런 것들이 고려된 스레드 생성 함수가 만들어졌다.
CONTEXT 다루기
CONTEXT 구조체는 프로세서마다 고유한 레지스터 데이터를 가지고 있다. 시스템은 다양한 내부 연산을 수행하기 위해 이러한 CONTEXT 구조체를 사용한다. 현재로는, Intel, MIPS, Alpha, PowerPC 프로세서들에 대한 CONTEXT 구조체들이 정으되어 있다. 이 구조체는 WinNT.h 헤더 파일에서 참조할 수 잇다.
윈도우에서 유일하게 프로세서마다 다르게 정의된 구조체이다.
x86 프로세서는 Eax, Ebx, Ecx, Edx 등이 있으며 Alpha 프로세서의 경우 IntV0?, IntT0?, IntT1?, IntS0?, IntRa?, IntZero? 등이 있다.
typedef struct _CONTEXT {
//
// The flags values within this flag control the contents of
// a CONTEXT record.
//
// If the context record is used as an input parameter, then
// for each portion of the context record controlled by a flag
// whose value is set, it is assumed that that portion of the
// context record contains valid context. If the context record
// is being used to modify a threads context, then only that
// portion of the threads context will be modified.
//
// If the context record is used as an IN OUT parameter to capture
// the context of a thread, then only those portions of the thread's
// context corresponding to set flags will be returned.
//
// The context record is never used as an OUT only parameter.
//
DWORD ContextFlags;
//
// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
// set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT
// included in CONTEXT_FULL.
//
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
//
FLOATING_SAVE_AREA FloatSave;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_SEGMENTS.
//
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_INTEGER.
//
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_CONTROL.
//
DWORD Ebp;
DWORD Eip;
DWORD SegCs; // MUST BE SANITIZED
DWORD EFlags; // MUST BE SANITIZED
DWORD Esp;
DWORD SegSs;
//
// This section is specified/returned if the ContextFlags word
// contains the flag CONTEXT_EXTENDED_REGISTERS.
// The format and contexts are processor specific
//
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
CONTEXT_CONTROL 은 명령 포인터(Instruction Pointer), 스택 포인터(Stack Pointer), 플래그, 함수 리턴 주소들과 같은, CPU 제어 레지스터를 포함한다.
(함수 호출시 x86 CPU의 경우 함수 리턴주소를 스택에 푸시하지만, Alpha CPU의 경우 리턴주소를 특정 레지스터에 넣는다.)
CONTEXT_INTERGER의 경우 CPU의 정수 레지스터를 담고 있다.
CONTEXT_FLOATING_POINT는 CPU의 부동 소수점 레지스터를 담고 있다.
CONTEXT_SEGMENTS는 CPU의 세그먼트 레지스터를 담고 있다. (오직 x86에서만)
CONTEXT_DEBUG_REGISTERS는 CPU의 디버그 레지스터를 담고 있다.
CONTEXT_EXTENDED_REGISTERS는 CPU의 확장 레지스터를 담고 있다.(오직 x86에서만)
윈도우즈에서는 스레드 커널 오브젝트의 내부에 있는, 현재의 CPU 레지스터 집합을 GetThreadContext?()를 통해 얻을 수 있다.
BOOL GetThreadContext(
HANDLE hThread,
PCONTEXT pContext
);
이 함수를 호출하기 위해서는 CONTEXT 구조체에 ContextFlags
CPU 타입에 따른 명령어포인터(IP)와 스택 포인터(SP)의 멤버들이다.
만약, 원격 스레드에 CONTEXT를 바꾸고자 한다면. 다룰수 없는 예외 메시지 박스가 나타나면 프로세스가 종료할 것이다.
자세한 내용은 24장에서 다룬다.
우선순위 레벨0은 제로 페이지 스레드에 예약되어 있다.
우선순위 레벨17, 18, 19, 20, 21, 27, 28, 29, 30은 디바이스 드라이브 프로그램을 위해 예약되어 있다. 즉 유저 모드 애플리케이션에서는 사용할 수 없다.
또, Real-time은 우선순위 레벨16이하로 내려갈 수 없고 그외 우선순위 클래스는 레벨15이상으로 올라갈수 없다.
커맨드 쉘에서 애플리케이션을 실행할때, 시작 우선순위를 변경할 수 있다.
윈도우 2000 이상은 작업관리자를 이용해서도 우선순위를 변경할 수 있다.