윈도우즈 시스템 프로그래밍

컴퓨터 구조와 운영체제의 포괄적인 이해를 위한 책.

윤성우님의 책을 기반으로 작성하였습니다!

table of content

  • 컴퓨터 구조는 Computer Archtectrue 카테고리에서 확인 가능해요!
chapter content
chapter1 Unicode vs ASCII code
chapter2 64bit vs 32bit
chapter3 Process
chapter4 Create delete Process
chapter5 Kernel Object
chapter6 Mailslot IPC
chapter7 Pipe IPC
chapter8 Scheduling
chapter9 Thread
chapter10 Thread create delete
chapter11 Thread memory access synchronize
chapter12 Thread Execution oder synchronize
chapter13 Thread Pooling
chapter14 Exception Handling
chapter15 I/O control
chapter16 synchronous vs asynchronous
chapter17 virtual memory, heap, mmf
chapter18 Dynamic Link Library



chapter 1. Unicode vs ASCII code

이 부분은 책의 내용이 살짝 부족했던 것 같다.ㅠㅜ
유니코드, 인코딩, cp949의 차이를 설명해주고 있지 않네요.
특히 WBCS를 유니코드로 설명해주고 있는데 너무 헷갈렸습니다.
결론1 : unicode encoding 방식은 에디터가 읽고 저장하는 방식에 따라, OS마따라, 컴파일러가 읽는 방식에 따라, 다를 수 있습니다.
결론2 : wide character 를 사용하면, 문자들의 size를 일정하게 만들 수 있습니다. wchar_t => window visual studio에서는 2byte utf-16으로, linux gpp에선 4byte utf-32
결론3 : window kernel에서는 UTF-16으로 2byte 환경을 사용한다. 하지만 byte order를 정해 놓지 않아, little endian은 bom을 붙힌다. 하지만 많은 system에서 bom을 문자열로 읽어들여서 에러가 날 수 있다. 그러므로 정보 교환용으로 utf-16은 좋지 않다.
결론4 : window visual studio에서는 cpp source file을 cp949로 저장하고, wide char를 쓰면 컴파일러가 utf-16으로 읽음. 만약 안 쓰면 kernel쪽에서 utf-16으로 바꿔서 읽음. linux에서는 자동으로 utf-8로 읽는다.


정리해 보겠습니다.
ASCI : 1byte로 모든 영어를 mapping.
Unicode : 1~3byte 기반으로 모든 문자를 mapping.
EUC-KR : 옛날에 한국어 기록방법
cp949 : window에서 EUC-KR을 확장해서 사용.
ANSI : 컴퓨터 내부 system의 default값. 우리나라에서는 cp949

유니코드 인코딩 방법
UTF-8, UTF-16$($window에서 사용하는 2byte wide character$)$, UTF-32$($linux에서 wide character$)$
UCS-2도 모든 code를 2byte로 읽는다. 하지만 읽지 못하는 것이 있다.
한글의 UTF-8 encoding 값은 3byte이다.
영어의 UTF-8 encoding 값은 1byte이다.

제가 이해한 대로 써보겠습니다!
다 같이 생각해보면 좋겠네요. :)

1. WBCS에 Unicode가 포함 될 수 있는가?

책에서는 wide character set이 모든 문자를 2byte로 처리하는 문자셋 이라고 표현 되어 있습니다.
찾아보니, 2byte로 문자열의 size를 고정 하는 것은 맞습니다.
하지만 wide character set에 Unicode가 포함 되는 것은 아닌 것 같습니다.
유니코드는 숫자와 문자를 연결한 사전 같은 느낌입니다.
그래서 1byte 부터 3byte까지 다양한 숫자들과 실제 문자가 mapping 됩니다!
즉, 모든 문자가 0x 00 ~ 0x 10 FF FF 까지의 숫자 중 하나에 mapping 됩니다.
이 유니코드들은 파일을 ​저장하거나 컴파일 할 때 encoding되고,
파일을 열 때 decoding 됩니다.
이 방식에 따라서 파일의 크기가 달라집니다.

2. 유니코드가 저장 될 때에는 인코딩에 따라 다양한 크기로 저장된다.

문서를 UTF-8로 인코딩$($저장$)$ 하는 경우, MBCS와 비슷한 방식을 취합니다.
Variable-width encoding을 취합니다.
영어가 저장될 때는 1byte, 한글이 저장 될 때에는 3byte로 저장됩니다!
각 문자별로 1~4byte 로 저장됩니다.

3. 그럼 왜 windows에서 strlen$($”한”$)$은 2byte를 나타내는가?

window에서 제공하는 visual studio는 기본 저장 방식이 cp949으로 돼있습니다!
cpp 파일을 notepad로 열어보면 window system에서 기본으로 제공하는 ANSI로 돼있습니다.
한국에서는 cp949로 문자열을 저장합니다! 이 문자 set에서는 한글의 strlen은 2byte로 저장됩니다.
cpp 파일을 unicode 형태로, UTF-8로 인코딩 해서 저장 한 경우에도, 한글의 strlen은 2byte로 계산 됐습니다.
컴파일러가 읽어 드릴 때, cp949 문자열로 읽는 것 같습니다.

이렇게 정리 해봤습니다.
틀린 부분이 있을 수 있습니다.
다른 분들의 생각이 궁금합니다! :)

이분들 posting이 도움 됐습니다.
reference
https://norux.me/31
https://umbum.dev/328
ms wide character
tool custumize를 통해, advanced save option을 추가할 수 있습니다!
wide character와 ucs-2의 관계
utf-8 BOM(Byte Oder Mark)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// wide char를 쓰면 컴파일러얌, UTF-16으로 읽으(디코딩)하란 뜻
// 그냥 wide character 기반으로 사용하기 위해서 사용하는 것 같음.
wmain(int argc, wchar_t* argv[])
{
  // argc : 전달 parameter의 수
  // argv : 첫번째는 실행 경로, 나머지는 command에서 넘겨준 실행 parameter
    wchar_t some[] = "aa";
    // 한국어 출력시 setlocale 해야됨..
    _wsetlocale(LC_ALL, L"korean");
    wprintf(wide_character);
};


// window에서만 사용 가능
// wide character 활용시, 몇몇 함수들과 호환이 안 될 수 있음.
// 그러므로 _t를 매크로로 사용함.
#include tchar.h
_tmain(int argc, char* argv[])
{
  //UNICODE로 조건부 정의 돼있음
  TCHAR str2[] = _T("aa");
}

윈도우에서는 type 이름을 대문자로 씁니다.
또한 헝가리안 표기법 비슷하게 씁니다.
UI를 만들 때는 꼭 알아야 할 것 같아요!
encoding decoding 해서 저장하거나 읽는 것은 editor나 compiler에 따라 다르다.
linux에서 vim으로 coding하면 default로 utf-8로 encoding된다. 컴파일러도 똑같이 utf-8로 읽는다.
linux에서 wide character를 쓰지 않는 이유는 문자 1개를 4byte로 쓰기 때문이다. window에서는 2byte utf-16이다.

1
2
3
4
5
6
7
8
9
10
11
12
// in linux gpp compiler
int main()
{
    char english_korean[] = "EN한글";
    wchar_t wide_char[] = "EN한글"
    std::cout << strlen(english_korean);
    // 8이 출력 된다. 
    // 1 + 1+ 3 +3 

    std::cout << sizeof(english_korean) <<std::endl; // 9byte!
    std::cout << sizeof(wide_char) <<std::endl;      // 20 byte!
}


chapter 2. 64bit vs 32bit

64bit 와 32bit의 차이는 IO bus에서 주소가 한번에 이동하는 크기.
CPU에서 한 쿨럭에 처리하는 크기.
process에 가상메모리를 할당해주는 크기.
windows 기반 자료형이 존재한다.
GetLastError함수를 통해 에러코드를 얻을 수 있다.
polimorphic 자료형을 제공한다. Linux에서는 호환성을 갖지 않는다.

chapter 3, 4. 9. Process Scheduling

메인 메모리와 register chache에 올라가서 돌아가는 프로그램을 process라고한다.
process의 구조는 data, instruction, heap, stack 4개의 section으로 구성된다. 각 프로세스들은 독립된 우선순위와 메모리 공간을 갖는다.
process는 크게 3가지로 나뉜다. run$($cpu에서 연산 중$)$, Block$($IO 연산 중$)$, Ready$($OS에 의해 context switching이 언제든 가능하다.$)$
이런 process들이 context switching 하며 돌아가는 프로그램을 multi processing이라고 한다.
OS는 scheduler를 통해 context switching을 한다. 한 process가 다른 process의 cpu 점유 시간까지 먹고 있는 상태를 starvation 이라고 한다.
scheduling algorithm은 선점형$($preemption$)$ 비선점형$($non-preemption$)$ 방식이 있다.

non-preemption 방식에는 FIFO, Sortest Job First, HRN $($대기 시간과 실행 시간을 혼합하여 결정$)$
preemption 방식에는 라운드 로빈$($Round Robin$)$, MultiLevel Queue, MultiLevel Feedback Queue
두 개를 합친 priority scheduling도 있다.
$($Round Robin$)$에서는 우선순위가 같은 process들은 형성있게, priority가 낮은 process는 높은 priority가 끝날 때 까지 기다린다.
이때 scheduler가 확인하는 시간 단위를 quentum 또는 time slice라고 한다.

  1. time slice마다
  2. process의 생성 및 소멸마다
  3. 실행중인 process가 io 상태로 blocking 될 때 마다 실행된다.
    Prioriy inversion을 통해서 우선순위가 높은 프로세스가 낮은 프로세스에게 기회를 줄 수 있다.
    Soft RTOS는 time slice가 작아서 반응성이 좋다. context switching이 자주 일어난다,
    HARD RTOS는 Dead line이 존재해서 그 시간안에 무조건 끝낸다.

CreateProcess 함수로 자식 process를 만든다.
STARTUPINFO, PROCESSINFOMATION 등으로 console을 어디다가 어떻게 띄울지 결정한다.
SetProcessInfomation..
GetCurrentDirectory, GetSystemDirectory, GetWindowsDirectory, GetEnvorn..
CreateProcess의 두번째 인자로 실행할 exe와 parameter를 넘길 수 있다.
current dir -> system dir -> windows dir -> environment path variable 순으로 찾는다.
PCB$($Process Control Block$)$를 windows 에서는 kernel object를 생성한다.

chapter 5. Kernel Object

OS는 process들을 관리하기 위해, PCB라는 process control block을 만든다.
Process control block 안에는 program count, priority, state$($Signaled, None Signaled, process가 죽으면 signaled가 되고, 살아있으면 non-signaled이다.$)$,
status$($Ready, Run, Break$)$, process id, 메모리 관리 등등이 저장된다.
windows의 PCB는 kernel object이다.
물론 process뿐 아닌 다른 IPC들이나 file들도 kernel object로 관리된다. 각 resource 별로 kernel object의 형태는 다르다.
kernel은 OS의 핵심 부분이다. scheduling, processing, 보안, deive driver등의 역할을 한다.
또한 process가 kernel에 접촉할 수 있게 빼놓은 포인터가 handle이다.
각 process는 독립적인 handle table을 갖고 있어, process에서 kenelobject의 제어가 가능하다.
handle table에는 자신의 kernel object를 가르키는 handle은 없다
private이기 때문에 kenelobject 안의 값을 직접적으로 바꾸지는 못한다.
Pipe, MailSlot, File 모두 kernel object로 관리된다.$($각각의 형태는 다르다.$)$
CloseHandle을 통해서 program counter의 값을 낮출 수 있다. program count가 0이 될 때, kernel object는 사라진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <Windows.h>
int main()
{
    STARTUPINFO start_info = { 0 };
    PROCESS_INFO process_info = {0};
    process_info.hProcess;
    process_info.hThread;
    HANDLE a = GetCurrentProcess();
    TCHAR command[] = _T("AdderProcess.exe 1 2");
    STARTUPINFO start_info{ 0 };
    start_info.cb = sizeof(start_info);

    PROCESS_INFORMATION process_info{ 0 };

    bool is_run =CreateProcess(
        NULL, // 생성할 프로세스의 실행 파일 이름, 경로 추가지정 가능
        command, // 원래 command를 여기다 치고, 위에는 process 이름
        NULL, // p보안
        NULL, // t보안
        TRUE, // 상속여부
        NULL, //  CREATE_NEW_CONSOLE, process특성 우선순위나 새로운 process만들 때.
        NULL, // 환경 변수 블럭 
        NULL, // 생성하는 프로세스의 현재 디렉터리를 설정 
        &start_info,
        &process_info
    );

    //block 상태에서 child process가 signaled로 바뀔 때 까지 기다림.
    WaitForSingleObject(process_info.hProcess, INFINITE)
    //WaitForMultipleObject(DWORD num_of_handle, Handle * handle_list, bool wait_all, DWORD timeout)
    //부모 process는 자식이 끝날 때 까지 block 상태로 존재함.

    //정상적으로 종료됐는지 부모가 child kernel object의 handle을 가지고 확인가능!
    GetExitCodeProcess(process_info.hProcess, &state);


    //자식 process와 연결을 끊음. program counter를 1 낮춤
    CloseHandle(process_info.hProcess);
    CloseHandle(process_info.hThread);

    //TerminateProcess로 프로세스 종료 가능
}

chapter 6. Mailslot IPC

IPC : Inter Process Conmmunication
process는 각각 독립적인 가상주소공간을 가지고 있다.
mailslot은 bload casting이 가능하다. but 단방향
Socket과 pipe가 더 광범휘 하게 쓰이는 것 같다.
Sender와 Receiver가 있다.
주소 체계는 //computername/mailslot/$[$path$]$name
실질적으로 고치는 부분은 computername과 path name 부분이다. $($네트워크 도메인이 같으면 공유 가능 socket이 더 좋겠지만$)$
Receiver는 mailslot을 만들고, Sender는 mailslot의 이름을 알아야한다.
Receiver는 CreateMailslot$(\()과 ReadFile$(\))$을 통해 구현한다.
Sender는 CreateFile$(\()$과 WriteFile$(\))$을 통해 구현한다. $($files system을 통해 구현 됐기 때문$)$

process가 꺼진 Signaled 상태, process가 실행중인 None-Singaled 상태.
WaitFor를 통해, 부모 process가 block 상태로 기다림.

CreateFile과 CreateMailslot이 생성 될 때 kernel object가 만들어진다.
mail_sender에서 자식 process에게 CreateFile의 kernel object를 가르키는 handle table을 상속해줄 수 있다.
헨들 상속여부는 리소스가 생성되는 순간 결정된다.
handle talble 상속 여부는 bInheritHandle에 의해 설정 됨.
SECURITY_ATTRIBUTE구조체 안에 정의되어있음.

Pseudo handle, Handle duplicate.

GetCurrentProcess는 프로세스가 자신의 kernel object를 가르키는 약어을 갖는다.
그러므로 실제 핸들을 얻기 위해서는, DuplicateHandle을 사용해야 한다.

1
2
3
4
5
6
7
8
9
BOOL DuplicatedHandle(
  복제할 프로세스의 헨들, A process의
  복제할 헨들, 핸들 256을
  복제된 헨들을 소유할 프로세스 헨들, B process에 등록해 
  복제된 값들을 저장할 변수 주소, 등록된 값은 여기에 저장해
  접근권한,
  복제된 핸들의 상속여부,
  원본 핸들과 접근권한
)



chapter 7. Pipe IPC

Anonymous pipe, Named Pipe 두 종류가 존재한다.
Anonymous Pipe : 관계 있는 $($부모 자식, 형제$)$프로세스들 사이에서 통신 하는 경우. 단방향 CreatePipe
WriteFile과 ReadFile handle로 데이터를 송 수신한다. CreateNamedPipe


Named Pipe : 소켓 메일슬롯과 비슷함.
서버에서는 CreateNamedPipe를 생성하고, Client의 CreateFile과 연결함.
자식 프로세스에게 핸들 정보를 넘겨주려면 매개변수로 넘겨주거나, 환경변수로 넘겨주는 방법이 좋다.

chapter 9. 10. Thread

Thread는 scheduling되는 가장 작은 단위이다.
Process 안에서 Locality가 보장되기 때문에 multi process보다 빠르다.
각 Process에서 stack 공간을 독립적으로 사용하고, data section과 code section heap section은 공유한다.
kernel level Thread vs User level Thread
대체적으로 thread 한개가 IO중이면, process 전체가 멈출 수 있다. user가 더 빠르지만 한정된 기능을 제공한다.
beginthreadex함수를 사용하자. CreateThread 보다는, 왜냐면 ANSI기준 함수를 못쓴다고 한다.
ps. 그럴바엔 STL의 thread를 쓰는 것이 좋겠다.

chapter 11. 12. Thread Synchronize

multi thread program에서 같은 변수에 접근하면 오류가 난다. $($register에서 계산되는 과정을 생각해보자$)$

chapter 14. Thread Synchronize

실행 순서의 동기화 vs 메모리 접근의 동기화
multi thread programing에서 Critical Setion에서 문제가 발생함.

User mode Synchronize 성능상 이점. kernel mode로 이동하지 않기 때문에. 해당 구간에는 열쇠가 1개이다.

Critical Section. 해당 부분을 묶자
Inter lock funciton $($간단한 critical section만 묶으면 될 때, 함수로 처리하자$)$
Kernel mode Synchronize
Mutex 열쇠가 1개다
Semaphore 열쇠가 여러개다.
named Mutex
—실행 순서의 동기화
생산자와 소비자. 만들어져야지 쓰지!
Event

수동리셋 모드 : signaled가 됐을 때 복수개 처리
자동리셋 모드 : 자동으로 한개씩 함
이벤트 생성과 소멸
Timer 함수를 계속 x 초마다 불러야해.

chapter 13. Thread Pool

Thread를 생성하고 소멸하는 작업은 큰 overhead가 있다.
그러므로 Thread를 생성해 둔 후, block 시켜놓고, 일이 생길 때마다 Thread를 할당하여 사용한다.

chapter 15. I/O control, Exception Handling

ANSI 표준 함수는 system 함수를 call한다.
인터넷을 참조하자!

chapter 16. synchronous vs asynchronous

중첩을 통해 해결.
ANSI 함수 fread는 파일러 부터 함수를 읽을 때, block 된다. 즉 block-funciton이다.
system 함수를 쓰면 non-block상태의 함수를 호출가능하다.
synchronous 통신은 Overapped IO를 통해 구현한다.

chapter 17. virtual mememory heap mmf

chapter 18. DLL

Comments