안녕하세요 삽잡이입니다.
이전 시간까지 어플리케이션을 사용할 수 있도록 API를 배웠습니다.
API는 OS의 라이브러리를 쓰는 것이라고도 할 수 있는데...
이 API에는 시스템 프로그래밍에 관해서도 포함되어 있습니다.
이번 시간부터는 시스템 프로그래밍에 대해서 배워보도록 하겠습니다.
일반적으로 OS의 동작 원리를 이해하고 알아가는 것이 시스템 프로그래밍이라고 할 수 있습니다.
시스템 프로그래밍에는
쓰레드, 프로세스, DLL, 메모리, 동기화 오브젝트, 커널 오브젝트등이 해당됩니다.
아무튼...
OS의 구조를 이해하고, OS와 밀접하게 연관된 프로그램과 관련된 요소들을 공부해보자는 것이
바로, 시스템 프로그래밍입니다.
물론, 현 시절 MS에서는 .NET을 밀고 있는 추세로서,
.NET에는 OS API가 굳이 필요가 없습니다. .NET Framework를 사용하기 떄문인데요...
참고로, .NET Framework는 개발에 관련된 .NET의 프로그램, 개발과 관련된 모든 것들을 의미합니다.
하지만, 아직까지는 우리가 공부하고자 하는 시스템 프로그램의 요소들이
다른 프로그램에서 사용하는 부분들이 많이 있기 때문에 이해할 수 있어야합니다.
우선, 프로세스에 대해서 생각해보도록 하겠습니다.
프로세스는 프로그램의 인스턴스라고들 부르는데요,
Windows 에서 프로세스란, 독립적인 주소 공간을 가지고 있으며,
명령 실행을 위한 쓰레드를 가지고 있는 형태를 말합니다.
또한 이 프로그램을 동작 시키기 위한
그 외 요소들을 가지고 있는 논리적인 묶음, 프로그램의 틀, 경계라고 말합니다.
이 프로세스의 핵심 두가지를 뽑는다면,
독립적인 프로세스만의 주소 공간과 쓰레드를 뽑을 수 있습니다.
어렵게 생각하면 어렵겠고 쉽게 생각하면 쉬워지겠지만,
프로세스는 위의 것들을 담고 있는 컨테이너에 지나지 않습니다.
프로세스를 구분하기 위해서 커널에서는 Uniq 한 ID를 제공합니다.
이를, Process ID 줄여서 PID라고 부릅니다.
PID의 중요성에 대해서는 잠시후 설명하도록 하겠습니다.
윈도우에는 객체들을 크게 세가지로 분류할 수 있습니다.
1. 커널 오브젝트 (커널 객체)
2. 유저 객체
3. GDI 객체
GDI 객체는 Pen, Brush, BitMap, Font 등을 말합니다.
이전에 WIndow API 프로그래밍 책에서 보시던 기억들이 있으시련지요...
이 객체들은 모두 ID를 가지고 있습니다. ID는 일반적으로 이름이나 정수 번호로 할당이 됩니다.
프로세스 아이디는 PID 라고 정수 번호를 가지고 있습니다.
아무튼, 커널 객체는 커널과 밀접한 것들을 말하는데 설명에 앞서 잠시 커널이 뭔지 살펴보도록 하겠습니다.
운영체제는 크게 내부적인 핵심을 가지고 있는 '커널' 부분과,
외각, 껍데기를 의미하는 '쉘'로 이루어져 있습니다.
이 두 부분들에 대해 조금은 더 자세하게 설명을 하자면,
쉘은 간단하게 사용자와 이야기하는 프로그램을 말합니다.
즉, 사용자와 대면을 하는 친구입니다.
예를 들어 사용자가 더블 클릭을 하거나,
키보드 엔터를 치면 쉘이 명령어를 입력 받는 것입니다.
커널은 장치, 내부적 메모리를 관리하는 프로그램을 말합니다.
디바이스 관리, 메모리 관리, 프로세스 관리 같은 것들은 모두 커널을 이야기합니다.
이처럼 운영체제에서 크게 두 가지로 나뉠 수 있습니다.
자... 다시 커널 객체로 이야기를 진행해보도록 하겠습니다.
커널 객체는 OS와 밀접한 관련을 가지고 있는 객체들을 말합니다.
이 커널 객체에는 프로세스, 쓰레드, 메모리(커널), 동기화 등이 있습니다.
여담으로 말하자면, 시스템 프로그래밍을 공부하는 것중 99%가 커널 객체에 해당합니다.
윈도우 객체 3가지 종류 중 다른 곳에서는 ID보다 핸들의 중요성이 높다고 할 지라도,
커널 객체에서 만큼은 ID가 가장 중요합니다.
왜냐하면, Uniqe하게 구분하는 것이기 때문입니다.
무슨 말인지 잘 모르시겠지요... 우선 커널 객체는 핸들보다 ID가 더 중요하다는 것을 기억하시길 바랍니다.
다른 객체들... 그러니까 GDI와 같은 객체에서는 ID도 없습니다.
그 객체에서 중요한 것은 바로 핸들이지요.
또한, 유저 객체를 컨트롤하기 위해서는 핸들이 있어야 되기 때문에 핸들이 중요합니다.
이와같이 설명을 하게 된다면,
커널 객체에서 핸들은 중요하지 않는 것인지 하는 오해를 하실 수 있을듯 한데,
커널 객체에도 핸들은 있는데 중요하지 않다는 것이 아니라 ID가 더 중요하다는 것입니다.
그렇다면 다 같은 핸들과 ID를 가지고 있으면서 차이점은 무엇일까요?
우선, GDI에서 핸들은 프로그램 내에서만 사용할 수 있습니다. 이를 지역적이라고 말합니다.
유저 객체의 핸들은 전역적입니다.
커널 객체의 핸들은 좀 애매모호하지만 한정적 즉, 일부에서만 사용가능한 특징을 가지고 있습니다.
GDI에서는 핸들을 외부에 쓸 일이 없습니다. 블럭 내부에서만 쓰고 끝내기 때문이죠...
하지만, 윈도우, 커서 핸들같은 경우 전역적으로 사용합니다.
윈도우 핸들은 다른 프로세스에 넘길 수 있습니다.
따라서, 다른 프로세스에서는 그 핸들로 제어를 할 수 있게 됩니다.
즉, 핸들을 사용할 수 있다는 것은 전역적이라는 것을 말하게 됩니다.
하지만 ID는 Uniqe 하지 않습니다. 더군다나 ID가 같다고 해서 큰 문제를 일으키지 않습니다.
정리하자면, GDI 핸들은 프로그램 내에서! 유저 핸들은 전역적으로! 라고 설명할 수 있습니다.
커널 객체의 핸들은 Uniqe합니다. 해당 프로그램 내에서만 사용할 수 있습니다.
그렇다면, 다른 프로그램에서 커널 핸들을 얻고싶다면 어떻게 할 수 있을까요?
바로 ID만 있으면 됩니다.
예를들어 A프로세스와 B프로세스가 있다고 해보겠습니다.
A프로세스가 커널 객체를 만들고 핸들이 만들어졌는데 그 핸들값이 200이라고 해보겠습니다.
하지만, 커널 객체에서는 해당 프로그램 내에서만 핸들을 사용할 수 있기 때문에
B프로세스에 200이라는 핸들 값을 줘도 사용할 수 없습니다.
(이와 반대로 윈도우에서 핸들은 전역적이기 때문에 가능합니다.)
하지만, ID를 B 프로세스에게 알려주면, 언제든지 핸들을 찾아올 수 있습니다.
이 말은 즉, B 프로세스가 사용할 수 있는 핸들을 만들어준다는 것입니다.
이렇게 된다면 A, B프로세스에서 핸들 값이 각각 달리 할당될 수 있지만 ID는 같아서 사용할 수 있게 됩니다.
그렇다면 이제 시스템 프로그래밍에 사용되는 함수들을 하나씩 살펴보도록 하겠습니다.
위의 코드에서 사용된 함수들은 프로세스 ID와 핸들을 출력하기 위한 함수들입니다.
우선 GetCurrentProcessId()는 프로세스 ID를 얻어오는 함수입니다.
하지만, 이와 같이 코드를 구현하게 된다면, 우리는 이상한 점을 느낄 수 있습니다.
GetCurrentProcess() 함수를 호출한 값이 '-1'입니다.
핸들 값이 -1일수도 있다는 것일까요...
사실 이 핸들은 pseudo handle 이라고 부르며 속히 가짜 핸들이라고 부릅니다.
자기 프로세스 내에서 핸들을 구할 필요가 있을까요...
우선, 프로세스 핸들을 구하는 이유가 뭘까요? 내 프로세스를 뭔가 핸들링하기 위해서 구하지 않습니까?
그런데, 내 프로그램에서 진짜 핸들을 사용할 이유가 없으니
이와 같이 가짜핸들인 pseudo handle을 사용하는 것입니다.
GetCurrentProcess() 함수를 호출했더니
-1을 반환하는 것은 가짜핸들을 주는 것이라는 것을 알게 됬습니다.
그렇다면 pseudo handle을 사용하는 이유는 뭘까요? 이에 대해서는 잠시후에 생각해보도록 하겠습니다.
ID만 알고 있다면 우리는 핸들을 구할 수 있다고 했습니다.
따라서, ID로 프로세스 핸들을 얻기 위해서는
이와 같은 방법을 사용할 수 있습니다. 함수로는 진짜 프로세스 핸들을 찾아올 수 있게 됩니다.
즉, ID만 있으면 프로세스의 진짜 핸들을 찾아주는 함수라는 것입니다.
따라서 OpenProcess 함수의 세번째 매개변수로는 프로세스 ID를 주게됩니다.
프로세스의 ID를 줘야 진짜 자신의 핸들을 반환해줄것 아닐까요.
또한, 내 프로세스 안에서 진짜 핸들은 필요 없기에 가짜 핸들을 사용하지만,
프로세스 핸들을 얻어올 때 다른 프로세스가 핸들을 얻을 위험성을 대비하여
첫번째 인자로 접근 권한을 줍니다.
위의 PROCESS_QUERY_LIMITED_INFORMATION 이라는 권한은,
정보에 대하여 질의가 가능한 핸들만 얻겠다는 뜻을 가지고 있겠군요...
프로세스는 언젠가 종료되게 됩니다.
이때에 핸들을 주고 종료코드를 받아오라는 명령을 담당하는 함수가 있습니다.
바로, GetExitCodeProcess 함수입니다.
종료코드란, 프로세스가 종료됬을때 반환하는 값인데,
어떻게 종료되었는지, 혹은 프로세스가 아직 살아있다면 살아있다는 코드를 반환함으로써
상태에 대해서 확인할 수 있게 해줍니다.
정상 종료가 되면 0을 반환한다고 치고,
비정상 적으로 종료를 할 때에도 그에 맞는 종료코드를 정의했다고 해봅시다.
그런 경우를 위해 확인하고자 종료코드를 만드는 것입니다.
즉, 종료코드는 외부 프로세스를 위해 존재한다고 할 수 있다는 것입니다.
위의 코드에서는 STILL_ACTIVE 코드를 반환하는 것으로 보아,
프로세스가 종료되지 않았다는 것을 알 수 있습니다.
우리는 또한 윈도우를 찾아야될 필요성이 있을 때가 있습니다.
이때에는 두가지의 방법이 있습니다.
1. 클래스의 이름으로 찾을 수 있다.
2. 윈도우의 이름으로 찾을 수 있다.
해당 코드는 notepad라는 클래스 이름으로 메모장을 찾는 코드입니다.
메모장을 찾게 된다면, 해당 핸들을 반환해주겠지요...
다음으로 확인해볼 함수는, GetWindowThreadProcessId 함수입니다.
이 함수는 많이들 오해를 하시고 계신 함수인데,
첫번째 매개변수인 핸들을 통해 두번째 매개변수 주소에 PID 값을 저장합니다.
또한, 리턴값으로 쓰레드 ID를 반환해줍니다.
프로세스를 죽이는 방법에는 크게 3가지의 방법이 있습니다.
1. Main 함수로 부터 리턴하는 방법
2. ExitProcess 함수를 호출하는 방법
3. TerminateProcess 함수를 호출하는 방법
첫번째 방법이 가장 정상적이고 올바른 방법으로써,
프로세스가 가지고 있는 리소스들이 정상적으로 종료되는 바람직한 방법입니다.
두번째, 세번째 방법의 차이는
'자기 자신이 종료를 시킨다.', '외부에서 종료 시킨다.' 입니다.
특히, 세번째 방법인 TerminateProcess 함수를 사용하여 프로세스를 죽이는 방법은
결코 권장하는 방법은 아닙니다.
프로세스는 자기 스스로 여러 리소스를 가지고 있습니다.
하지만, 외부에서 해당 프로세스를 죽이게 되면 대책을 마련할 수가 없습니다.
예를 들어, 어떤 파일에 글을 쓰고 있는데 쓰는 도중에 그 프로세스를 죽인다고 해봅시다.
대책 없이 죽이게되니 문제가 발생할 위험이 있습니다.
또한, 프로세스는 죽어있는데 리소스는 남아있어 메모리 낭비를 초래할 수 있게 됩니다.
이처럼 TerminateProcess 함수를 통해 외부 프로세스를 종료시키고
GetExitCodeProcess 함수를 통해 종료 코드값을 얻을 수 있게됩니다.
그런데, TerminateProcess 함수를 통해 외부 프로세스를 죽였다고 가정해봅시다.
각 프로세스들은 독립적인 존재로써,
죽이고자 하는 명령을 전달하고 리턴을 기다리는데 서로 맞물리지 않는 경우가 있을 수 있습니다.
외부 프로세스의 종료코드를 얻을 수 없는 경우가 발생할 수도 있다는 것입니다.
따라서, 죽으라는 명령을 날린 프로세스가 종료코드를 반환할 때 까지
기다려주는 함수가 있습니다.
다음시간에 뵙겠습니다!
이상 삽잡이였습니다!
'삽질의 현장 > - 윈도우 시스템' 카테고리의 다른 글
#018_WIndow_System_쓰레드(thread) 란? (0) | 2015.09.22 |
---|---|
#017_WIndow_System_리소스 (0) | 2015.09.21 |
#016_WIndow_System_커널 오브젝트 & 핸들 테이블 (0) | 2015.09.21 |
#015_WIndow_System_명령행 인자 (0) | 2015.09.19 |
#014_WIndow_System_프로세스 생성 (0) | 2015.09.19 |