레이블이 시스템 프로그래밍인 게시물을 표시합니다. 모든 게시물 표시
레이블이 시스템 프로그래밍인 게시물을 표시합니다. 모든 게시물 표시

2012년 9월 3일 월요일

윈도우즈 시스템 프로그래밍 [8] - 프로세스간 통신 (IPC) 2.




윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.



7장.  프로세스간 통신 (IPC) 1



I. 프로세스간 통신(IPC)의 의미



1. 프로세스의 커널 오브젝트 핸들 테이블

- 마이크로소프트에서 Windows 운영체제 소스코드를 공개하지 않아 핸들 테이블이 어떻게 관리되는지
   알 방법이 없음
- 리소스 생성 과정에서 커널 오브젝트가 생성되면, 해당 오브젝트의 핸들이 반환

① 핸들과 커널 오브젝트의 리뷰 (Review)


=> 핸들 256을 통해 0x2400번지에 할당 되어 있는 메일 슬롯에 접근 가능
      BUT 핸들 256이 0x2400번지의 커널 오브젝트를 의미한다는 정보가 없음
      => 핸들 테이블 개념을 도입


② 핸들 테이블의 도입


- 핸들 테이블은 핸들 정보를 저장하고 있는 테이블로서 프로세스 별로 독립적


2. 핸들의 상속

- CreateProcess 함수 호출 시 다섯번째 인자가 무엇이냐에 따라 부모 프로세스의 핸들 테이블이
   자식 프로세스에게 상속될 수 있음

① 핸들의 상속에 대한 이해

- 자식 프로세스는 부모 프로세스의 핸들 테이블 정보를 상속 받을 수 있음
   but 모든 핸들 정보를 상속 받진 않음
- 핸들 테이블에는 해당 핸들의 상속 여부 컬럼(column)이 존재
- 상속시, 상속 여부에 대한 정보도 변경 없이 그대로 상속됨
   => 자식 프로세스에 상속된 핸들은 또 다른 자식 프로세스 만들때도 상속됨
- 상속되는 핸들을 결정 하는건 프로그래머 몫


② 핸들의 상속을 위한 전달 인자

BOOL CreateProcess (
   LPCTSTR lpApplicationName,
   LPTSTR lpCommandLine,
   ...
   BOOL bInheritHandles // 핸들 상속 인자
   ...
);

bInheritHandles  -> TRUE 전달할 경우 부모 프로세스의 핸들 테이블은 상속됨
  -> BUT 상속 여부가 Y 로 설정된 핸들에 한해서만 상속이 이루어짐


③ 핸들의 상속과 커널 오브젝트의 Usage Count (UC)

- 프로세스가 핸들을 얻게 되었다는 의미 : 핸들 테이블에 대한 정보가 갱신됨

ex) CreateMailslot 호출
      ⑴ 메일 슬롯 리소스 생성
      ⑵ 커널 오브젝트 생성
      ⑶ 핸들 정보가 핸들 테이블에 갱신  // 이때부터 프로세스가 핸들 정보를 얻음
      ⑷ CreateMailslot 함수를 빠져 나오면서 핸들값 반환

- 부모 프로세스 핸들 테이블에 있는 커널 오브젝트들 UC가 1
- 자식 프로세스 생성 후 상속여부가 Y 였던 커널 오브젝트는 UC가 1 증가 => UC=2가 됨


④ 상속이 되기 위한 핸들의 조건

- 핸들의 상속 여부는 리소스 생성되는 순간 결정 (핸들 테이블의 Y or N)

- LPSECURITY_ATTRIBUTES 구조체 = SECURITY_ATTRIBUTES의 포인터로 정의
- NULL 이 전달되면 상속이 되지 않음
   BUT 구조체 변수를 적절히 초기화해 주소값 전달하면 상속 가능

typedef struct _SECURITY_ATTRIBUTES {
   DWORD nLength;   // 구조체 변수 크기
   LPVOID lpSecurityDescriptor;   // 상속과 관계 없음
   BOOL bInheritHandle;   // 상속 여부 결정   TRUE : Y,  FALSE : N
}

ex)

SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;

HANDLE hMailSlot = CreateMailslot(   ...   , &sa);
CreateProcess(   ...   , &sa,   ...   );




II. Pseudo (가짜) 핸들과 핸들의 중복 (Duplicate)


- 프로세스의 핸들은 Pseudo (가짜) 핸들

1. 프로세스의 커널 오브젝트 핸들 테이블







2012년 8월 29일 수요일

윈도우즈 시스템 프로그래밍 [7] - 프로세스간 통신 (IPC)




윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.



7장.  프로세스간 통신 (IPC) 1



I. 프로세스간 통신(IPC)의 의미



- IPC = Inter-Process Communication ( 프로세스 사이의 통신 )
- 통신 : 기본적으로 데이터를 주고 받는 행위
- 대부분 하나의 프로그램 = 하나의 프로세스 but  여러개의 프로세스를 가진 프로그램도 종종 있음



1. 프로세스 사이에서 통신이 이뤄지기 위한 조건


- 서로 통신하고자 하는 프로세스가 만날 수 있는 여건 (공유하는 메모리 영역이 존재)이 되어야 함
- 여건이 허락되지 않으면 전화 or 메신저 같은 보조 수단이 필요


2. 프로세스들이 서로 만날 수 없는 이유


- 프로세스는 서로 데이터를 주고 받을 만한 접선 장소가 없기 때문에 데이터 주고받는 것은 불가능
- 프로세스는 자신에게 할당된 메모리 공간 이외의 접근은 불가능


3. 프로세스들이 만나지 못하게 디자인한 이유 


- 안정성을 높이기 위해





II. 메일 슬롯 방식의 IPC




1. 메일 슬롯 (Mail Slot)의 원리


- 파이프와 더불어 대표적인 IPC 기법
- 메일 슬롯 : 편지를 넣을 가느다란 우체통의 입구
- 기본 원리 : 데이터를 주고받기 위해 프로세스가 우체통을 마련


2. 메일 슬롯 (Mail Slot) 구성을 위해 필요한 요소


 ① Receiver가 준비해야할 것

HANDLE CreateMailslot (
   LPCTSTR lpName,
   DWORD nMAXMessageSize,
   DWORD lReadTimeout,
   LPSECUritY_ATTRIBUTES lpSecurityAttributes
);

1) lpName : 생성하는 메일 슬롯의 이름
   - 주소의 기본 형식 : \\computer name\mailslot\[path] name

2) nMaxMessageSize : 메일 슬롯의 버퍼 크기를 지정
   - 0을 전달할 경우 시스템이 허용하는 최대 크기로 지정

3) lReadTimeout : 최대 블로킹 시간을 ms 단위로 전달
   - 메일 슬롯은 전소오딘 데이터를 읽기위해 파일 입/출력 함수 ReadFile 함수를 이용
   - 메일 슬롯이 비어 있다면 데이터가 채워질 때 까지 Blocking 상태에 놓임 
   - 0을 전달하면 데이터가 있든 없든 블로킹 상태를 빠져나와 다음 단계를 실행
   - MAILSLOT_WAIT_FOREVER을 인자로 전달하면 데이터가 존재할 때 까지 블로킹 상태에 놓임

4) lpSecurrityAttributes : 핸들의 상속 여부를 지정

5) 함수의 변환 타입 : HANDLE
   => 메일 슬롯은 커널에 의해 관리되는 리소스 이므로 커널 오브젝트가 생성되며, 그 핸들이 반환됨


② Sender가 준비해야 할 것

- 메일 슬롯 이름을 알아야 함 (&주소 ( 디렉토리))

ex)

   HANDLE hMailSlot;
   hMailSlot=CreateFile("\\\\.\\mailslot\\ ", ...);   // 파일을 생성하거나 개방할때 사용
   CHAR message[50];
   WriteFile(hMailSlot, message, ...);

- CreateFile, WriteFile : Windows에서 제공하는 파일 관련 시스템 함수 ( <-> ANSI 표준함수)
- CreateFile을 통해 메일 슬롯으로 보내기 위한 통로(리소스)를 형성
   => 데이터 전송을위한 Data Stream을 형성
- 메일 슬롯은 파일이 아니지만 파일 입/출력 함수를 사용
   -> Windows 파일 시스템을 기반으로 구현되어 있기 때문

- 주소 골격 : \\computername\mailslot\[path] name
- 주소에서 'mailslot' 부분은 바뀌지 않음
- computername에 '.'의 의미 : 로컬 컴퓨터






- Sender 프로세스는 우체통(메일 슬롯)의 주소를 알고 있어야 데이터 전송이 가능
- 메일 슬롯 = 메모리 공유 역할
- 데이터 전송이 양방향이 아닌 단방향(우체통)
- 여러개의 리시버가 같은 주소를 가질 수 있음
   => 센더가 그 주소로 전송하면 모든 메일 슬롯에 데이터 전송이 가능 (브로드 케스팅)





- CreateFile : 파일 만들거나 오픈
- WriteFile : 파일에 데이터 작성

- IPC 메일 슬롯을 이용하지 않고 파일만으로 데이터를 주고받을 수 있음
   ex)
       프로세스 A가 파일 a.txt를 만들고 프로세스 B가 a.txt를 읽음
        => BUT 안정성에 문제가 있음 ->  메일 슬롯을 이용

ex) Receiver.cpp

#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox");

int _tmain(int argc, LPTSTR argv[]) {
   HANDLE hMailSlot;
   TCHAR messageBox[50];
   DWORD bytesRead;

   hMailSlot = CreateMailSlot (
      SLOT_NAME,
      0,
      MAILSLOT_WAIT_FOREVER,
      NULL
   );

   ReadFile(hMailSlot, messageBox, sizeof(TCHAR)*50, &bytesRead, NULL);

   closeHandle(hMailSlot);   // UC 1줄여 커널 오브젝트의 소멸을 도움

ex) Sender.cpp

#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox");

int _tmain(int argc, LPTSRT argv[]) {
   HANDLE hMailSlot;
   TCHAR message[50];
   DWORD bytesWritten;

   hMailSlot=CreateFile (  //  Receiver과 연결을 만들기 위해
      SLOT_NAME,
      GENERIC_WRITE,  // 여기서부터
      FILE_SHARE_READ,
      NULL,
      OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      NULL   // 여기까지 거의 고정
   );

   _fgetts(message, sizeof(messsage)/sizeof(TCHAR), stdin);
   WriteFile(hMail, message, _tcslen(message)*sizeof(TCHAR), &bytesWritten, NULL);

   CloseHandle(hMailSlot);

- 메일 슬롯 주소에서 '.'는 컴퓨터 자신, '*'는 모든 컴퓨터
  => 브로드 케스팅 이용 가능




III. Signaled vs Non-Signaled => 커널 오브젝트의 상태




1. 상태에 대한 이해


- 상태(state)라는 용어를 사용하는 이유는 변하기 때문
- signaled (신호를 받는 상태)와 Non-Signaled (신호를 받지 않는 상태)
- 커널 오브젝트를 구성하는 멤버 변수 중 하나가 상태 정보를 저장함
   => TRUE : Signaled   FALSE : Non-Signaled


2. 프로세스 커널 오브젝트 상태 (state)에 대한 이해


- 프로세스가 생성될 때 프로세스 커널 오브젝트가 생성됨
   => 처음 상태 : Non-signaled
- 프로세스가 종료되면 Signaled 상태로 변경됨
- Signaled 된 커널 오브젝트는 Non-Signaled 상태로 변경되지 않음
   => 종료된 프로세스를 다시 재개하지 못하기 때문 ( 새로운 프로세스를 다시 실행해야 함)


3. 커널 오브젝트의 두가지 상태를 확인하는 용도의 함수


- 커널 오브젝트의 상태를 확인하는 대표적인 함수

ex) DWORD WaitForSingleObject (
         HANDLE hHandle,   // 확인하려는 커널 오브젝트의 핸들
         DWORD dwMilliseconds   // 커널 오브젝트가 Signaled 될 때 까지 기다릴 수 있는 최대 시간
       );

- dwMilliseconds에 IFINITE 인자 전달시 Signaled 될 때까지 기다림

- WaitForSingleObject 반환값
   1) WAIT_OBJECT_0 : 커널 오브젝트가 Signaled 되었을 때 반환값
   2) WAIT_TIMEOUT : Signaled 되지 않고 dwMillisecons가 다 되었을 때
   3) WAIT_ABANDONED : 오류 발생시



4.  커널 오브젝트의 상태 확인이 필요한 상황

      => 하나의 프로그램이 여러개의 프로세스로 구성되는 경우 (CPU 코어 수의 증가에 따라)


- 이런 상황에서 WaitForSingleObject 사용







참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저 





2012년 8월 27일 월요일

윈도우즈 시스템 프로그래밍 [6] - 커널 오브젝트와 오브젝트 핸들




윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.



6장. 커널 오브젝트와 오브젝트 핸들



I. 커널 오브젝트에 대한 이해


- 커널 : 컴퓨터를 운영하는데 있어서 중심이 되는 운영체제의 핵심 부분
   -> 일반적으로 커널과 운영체제를 같은 의미로 사용

- 커널 오브젝트 : 커널에서 관리하는 중요한 정보들을 담아둔 데이터 블록


1. 커널 오브젝트의 이해


- 프로세스의 생성과 소멸, 상태 벼화, 관리는 모두 운영체제 몫
- 여러개의 프로세스르 관리하면 고정적으로 저장하고 갱신할 정보가 존재
ex) 프로세스 상태 정보 ( Running, Blocked, Ready 상태 ), 우선순위 정보
   => 운영체제 내부에 저장되어야 할 정보, 변경될 때 마다 갱신되어야 함
   -> 프로세스 스케쥴러가 이를 바탕으로 프로세스를 관리

- 프로세스에 관련된 정보를 저장, 참조 및 변경할 수 있는 구조체 존재 
  (편의상 '프로세스 관리 구조체'라 함)

- 프로세스가 생성된 후 정보를 초기화 한 것이 커널 오브젝트


- 프로세스 관리 구조체는 어떻게 구성되어 있는지 알 수 없음 (마이크로소프트가 공개하지 않음)
   => 프로세스의 커널 오브젝트는 프로그래머가 직접 생성하거나 조작하는 것을 막음



2. 그 이외의 커널 오브젝트


- 프로세스 내에 쓰레드를 생성할 때, IPC를 위해 사용하는 파이프나 메일 슬롯을 생성할 때도
   커널 오브젝트를 생성해야 함

- 커널 오브젝트 종류에 따라 다른 구조체를 생성
ex) 파일 커널 오브젝트 -> 파일 속성 정보 (Read모드, Read / Write모드 ... )
       프로세스 커널 오브젝트 -> 프로세스 우선 순위 정보 (스케쥴링을 위해)

=> ∴ Windows 운영체제는 프로세스, 쓰레드와 파일 같은 Resource 들을 원활히 관리하기 위해
          데이터를 저장하는 메모리 블록이 존재 => 커널 오브젝트

- 데이트 블록 : 커널 오브젝트   |   프로세스나 파일 : Resource

- Windows 커널에 의해 관리되는 리소스 수 만큼 커널 오브젝트도 생성됨




II. 오브젝트 핸들(Handle)을 이용한 커널 오브젝트의 조작


- 프로그래머는 직접 커널 오브젝트를 조작할 수 없음
- 리소스의 특성을 변경하기 위해선 (프로세스 우선 순위 변경 같은)
   해당 리소스 커널 오브젝트를 조작해야함
   -> 마이크로소프트에서 제공하는 시스템 함수를 이용 (안정적인 방식)

1. 프로세스 우선순위 (Priority) 변경


ex)

BOOL SetPriorityClass (
   HANDLE hProcess;   // 우선 순위를 변경할 프로세스의 핸들
   DWORD dwPriorityClass;   // 새롭게 적용할 우선 순위
);

- 핸들 (Handle) : 커널 오브젝트에 할당되는 숫자 (정수형)


2. 커널 오브젝트에 할당되는 숫자 = 핸들 (Handle)


- Windows는 커널 오브젝트를 생성할 때 마다 핸들을 하나씩 부여


3. 핸들 정보


- 핸들 정보를 얻는 방법은 커널 오브젝트 종류에 따라서 다양함 ( 프로세스, 파일, 쓰레드 ...)
- GetCurrentProces 함수 -> 현재 실행되고 있는 프로세스의 핸들을 얻을 떄 사용
  => 항상 같은 값을 리턴(-1)
- '-1'은 자기 자신을 의미
- 함수가 호출되어 실행되는 중에 (함수는 여러개의 명령어로 구성) CPU 할당시간을
  다른 프로세스에 넘겨줄 수 있음
  => 우선순위를 넘기는 기준은 CPU 관점에서 명령어 단위가 기준




III. 커널 오브젝트와 핸들의 종속관계



1. 커널 오브젝트의 종속 관계


- 커널 오브젝트는 Windows 운영체제에 종속적

① 커널 오브젝트에 종속적인 것이 아니라, 운영체제에 종속적인 관계로
     커널 오브젝트의 소멸 시점은 운영체제에 의해 결정됨
② 커널 오브젝트는 여러 프로세스에 의해 접근이 가능 (물론 함수 호출을 통한 간접 호출)

2. 핸들의 종속 관계


- 핸들 (핸들 테이블) 은 운영체제에 종속적이지 않고 프로세스에 종속적 
   ex) 프로세스의 우선 순위를 높이기 위해서는 핸들을 이용해 커널 오브젝트에 접근.

- PROCESS_INFORMATION 구조체

typedef struct _PROCESS_INFORMATION {
   HANDLE hProcess;   // 프로세스 핸들
   HANDLE hThread;   // 쓰레드 핸들
   DWORD dwProcessID;   // 프로세스의 ID
   DWORD dwThreadID;   // 쓰레드의 ID
} PROCESS_INFORMATION;


3. 프로세스 핸들과 ID


- 운영체제는 프로세스 생성시 프로세스들을 구분짓기 위해 ID(식별자)를 할당
- 프로세스 핸들 : 프로세스의 커널 오브젝트를 가리키기(구분짓기) 위함
- 프로세스 ID : 커널 오브젝트가 아니라 프로세스 자체를 구분짓기 위함
- CreateProcess 함수를 통해 프로세스를 생성하면 쓰레드라는 시스템 리소스도 생성됨




IV. 커널 오브젝트와 Usage Unit



1. CloseHandle 함수에대한 정확한 이해



CreateProcess 함수 호출이 커널 오브젝트의 생성 원인이나 단지 프로세스의 생성을 요청할 뿐
프로세스 생성의 주체는 운영체제이다. (함수가 아님)
- 프로세스 생성시, 그 프로세스르 위한 커널 오브젝트가 생성됨
- but 프로세스가 소멸한다고 해서 커널 오브젝트가 소멸하진 않음
   => 소멸할 수도 있으나 하지 않을 수도 있음

- BOOL CloseHandle (
      Handle hObject   // 핸들을 닫음(반환)  ->  핸들의 소멸, 리소스 해제를 의미하지 않음
   );



2. 커널 오브젝트와 Usage Count


- 자식 프로세스의 종료코드는 자식 프로세스 커널 오브젝트에 저장됨
- if 자식 프로세스가 종료될 때 커널 오브젝트도 소멸되면 종료코드는 사라짐
   =>  ∴ 프로세스가 종료되어도 커널 오브젝트는 동시에 소멸하지 않음

- 해당 커널 오브젝트를 참조하는 대상이 없을 때 소멸하는 것이 이상적
   => windows가 커널 오브젝트를 소멸 시키는 방법

- 프로세스의 생성과 동시에 커널 오브젝트의 Usage Count=1
- 프로세스에 접근 가능한 핸들 수가 늘어날때 마다 Usage Count 증가
- 자식프로세스의 Usage Count=2 (부모 프로세스 참조 + 자기자신 참조)
   => 부모프로세스가 CreateProcess 함수 호출과정에서  PROCESS_INFORMATION
      구조체를 통해 자식 프로세스의 핸들을 얻기 때문


- 핸들값은 커널 오브젝트에 매핑되지 않음 -> 각각의 핸들 테이블에서 핸들 값을 사용
   => ∴ 핸들 값, 핸들 테이블은 프로세스에 종속적

- 자식프로세스가 사라지면 프로세스에 종속적인 핸들 테이블이 사라지나
  부모 프로세스에서 자식 프로세스 커널 오브젝트를 보고 있으므로 (핸들 테이블에서 사용중) 
  커널 오브젝트는 사라지지 않고 단지 Usage Count를 2에서 1로 감소시킴
  => UC(Usage Count)가 존재하는 이유

- 운영체제 영역에서 커널 오브젝트가 있음 = 커널 오브젝트의 소멸은 운영체제게 UC를 통해 정함
- UC가 0이 되면 커널 오브젝트는 소멸 ( but 파일은 물리적으로 남아있음)


Q1. 프로세스 생성시 자기 자신의 커널 오브젝트를 핸들 테이블에서 참조하는가?

- 프로세스 생성시 자기 자신의 커널 오브젝트에 대한 핸들정보는 핸들 테이블에 저장하지 않음
   but GetCurrentProcess는 상수(-1)을 리턴
- 함수들은 -1이 리턴 되면 현재 프로세스를 지칭 한다고 인식함 (windows의 약속)

Q2. 자식프로세스가 종료된 후 부모 프로세스가 자식 프로세스의 커널 오브젝트를 참조한다고 해서
       커널 오브젝트를 남겨둘 이유가 있는가?

- 부모 프로세스는 자식 프로세스가 정상 종료 햇는가가 궁금함
- 자식 프로세스가 소멸되자 마자 자식 프로세스의 커널 오브젝트가 소멸되면
   부모 프로세스는 자식 프로세스의 정상 종료 여부를 알지 못함
   =>  ∴ UC를 통해 운영체제가 커널 오브젝트를 관리!


- STARTUPINFO si = {0, };   // 프로세스 생성시 초기 정보를 전달하는 목적
   si.cb= sizeof(si);

- PROCESS_INFORMATION pi; /// 생성된 프로세스의 정보를 얻기 위한 구조체 (핸들값 얻을 수있음)

- 자식 프로세스가 종료되더라도 부모 프로세스의 참조에 의해 자식 프로세스의 커널 오브젝트는 남음
   -> but 종료코드를 얻고 더이상 커널 오브젝트를 참조할 필요가 없다면 UC를 0으로 만들어 운영체제가 소멸시키도록 해야함
      ( 자원 낭비를 막기위해 )
   => CloseHandle (pi.hProcess);   // 함수를 이용해 UC를 하나 줄여 메모리 낭비 방지





참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저 



2012년 8월 25일 토요일

윈도우즈 시스템 프로그래밍 [5] - 프로세스의 생성과 소멸



윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.



5장. 프로세스의 생성과 소멸



I. 프로세스 ( Process ) 의 이해



1. 프로세스란 무엇인가?


- 프로세스 = 실행 중에 있는 프로그램


2. 프로세스를 구성하는 요소


① execution of "C" program

- Data 영역 : 전역 변수, static 변수의 할당을 위한 공간
- Stack 영역 : 지역 변수, 함수 호출시 전달되는 인자 값의 저장을 위한 공간
- Heap 영역 : 동적 할당 (malloc, calloc) 함수를 위한 공간
- Code 영역 : 시행 파일을 구성하는 명령어들이 올라가는 메모리 영역

② register Set

- 프로그램 실행을 위해선 레지스터가 절대적으로 필요
- 레지스터의 상태도 프로세스의 일부로 포함시킬 수 있음
   => 컨텍스트 스위칭(context switching)의 이해에 중요!

 

II. 프로세스의 스케쥴링과 상태 변화


1. 프로세스의 스케쥴링


- 하나의 CPU가 여러개의 프로세스를 번갈아가며 실행
  => 고속으로 실행하므로 사용자는 동시라고 느낌
∴ 여러개의 프로세스가 CPU 할당 시간을 나누기 때문에 동시 실행처럼 보임


2. 스케쥴링의 기본 원리


- 스케쥴링 : 프로세스의 CPU 할당 순서 및 방법을 결정짓는 일
- 스케쥴러 : 스케쥴링 알고리즘을 적용해 프로세스를 관리하는 운영체제 요소 ( 모듈)


3. 멀티 프로세스는 CPU를 바쁘게 함


- A프로세스가 I/O에 관련된 일( ex. 네트워크 송수신 )을 할 경우, 운영체제는 스케쥴러를 통해
   다른 프로세스가 실행되도록 스케쥴링 함

- I/O가 프로그램 실행에서 많은 부분을 차지


4. 프로세스의 상태변화



① S(Start)에서 Ready 상태로의 전이
- S는 프로세스가 생성되었음을 의미 => 생성과 동시에 Ready 상태가 됨
- Ready 상태 = CPU에 할당 되기를 기다리는 상태
   -> 멀티 프로세스 운영체제 => 실행 중인 프로세스가 있을것

② Ready 상태에서 Running 상태로의 전이
- 스케쥴러는 Ready 상태의 프로세스 중 하나를 실행할 수 있음

③ Running 상태에서 Ready 상태로 전이
- 프로세스는 생성시 중요도에 따라 우선순위 (Priority) 가 매겨짐
   => 다시 Ready상태가 된 프로세스는 다른 프로세스(우선순위가 높은)가 종료되기를 기다려야 함

④ Running 상태에서 Blocked 상태로 전이
- 실행 중에 있는 프로세스가 멈추는 (Blocked) 상태로 들어가는 것
- 데이터 입/출력에 관련된 일을 하는 경우 발생
   => 입/출력에 시간이 많이 걸림 -> 입/출력 진행 중인 프로세스는 잠시 Blocked 상태로 돌리고
   Ready 상태의 프로세스를 대신 실행시킴

 ⑴ 프로세스 A가 파일을 입/출력 해야하는 상황을 만남
 ⑵ Blocked 상태가 되어 파일 입/출력을 진행
 ⑶ 프로세스 A가 Blocked 되는 동시에 Ready 상태의 프로세스가 Running 됨 

⑤ Blocked 상태에서 Ready 상태로의 전이
- Blocked 상태는 스케쥴러에 의해 선택될 수 없는 상태
   => Ready 상태와의 차이점. Ready 상태에선 스케쥴러에 의해 실행 가능
- 입/출력이 완료된 Blocked 프로세스는 다시 Ready 상태가 되어 스케쥴러의 선택을 기다려야 함.




5. 프로세스의 상태 변화, 시나리오 다시 이해


- Running -> Ready : 해야할 일이 있으나, 어쩔 수 없이 양보해야하는 상황
  ex) 우선순위가 높은 프로세스의 등장

  but 항상 스케쥴러의 관심 대상이 됨

- Running -> Blocked : 해야할 일을 하고, 다른 프로세스에 Running 상태를 양보
  ex) I/O 관련 작업 -> CPU 연산을 필요로 하지 않음 => 다른 프로세스의 CPU 연산을 양보

but 스케쥴러의 관심 대상이 되지 않음 -> Blocked 작업이 끝난 후 Ready로 가서 선택을 기다림





III. 컨텍스트 스위칭 (Context Switching)


- 실행중인 프로세스의 벼녕은 시스템에 많은 부하를 줌
- CPU 내에 레지스터는 현재 실행 중인 프로세스 데이터로 채워짐
   =>  프로세스가 바뀌면 레지스터 값도 백업 후 변경되어야 함.
- Running -> Ready 될 때 레지스터 값은 메모리에 저장해 두어야 함 (Back Up)

- Running 프로세스와 Ready 프로세스가 바뀔 떄 => 컨텍스트 스위칭 발생
  -> 실행되는 프로세스의 변경과정에서의 컨텍스트 스위칭은 시스템에 부담을 줌
  => 레지스터가 많을수록, 프로세스별 관리 데이터가 많을 수록 더 함 -> 멀티 프로세스의 단점.



IV. 프로세스의 생성


1. 프로세스의 생성


방법 1 : 실행 파일을 더블 클릭해 독립적인 프로세스를 생성
방법 2 : 프로그램 실행 중에 또 하나의 프로세스를 생성


2. CreateProcess 함수의 이해


- Windows에서 프로세스 생성을 위해 제공되는 함수
- CreateProcess 함수를 호출하는 프로세스 -> 부모 프로세스 (Parent Process)
- 호출 당하는 프로세스 -> 자식 프로세스 (Child Process)

BOOL CreateProcess (
   LPCTSTR lpApplicationName,   // 
   LPTCSTR lpCommandLine,   // 
   LPSECURITY_ATTRIBUTES lpProcessAttributes,
   LPSECURITY_ATTRIBUTES lpThreadAttributes,
   BOOL bInheritHandles,
   DWORD dwCreationFlags,
   LPVOID lpEnvironment,
   LPCTSTR lpCurrentDirectory,
   LPSTARTUPINFO lpStartupInfo,   // 
   LPPROCESS_INFORMATION lpProcessInformation   // 
)

① lpApplicationName : 생성할 프로세스의 실행파일 이름
- 경로명을 지정할 수 있음
- 경로를 지정하지 않으면 프로그램의 현재 디렉토리에서 찾게 됨

② lpCommandLine : 
- argc, argv와 같이 새로 생성하는 프로세스에 인자를 전달할 때
- lpApplicationName이 NULL일 경우 표준 검색경로에 따라 실행할 파일을 찾음

③ lpStartupInfo :
- STARTUPINFO 구조체 변수들을 초기화 한 후, 이 변수의 포인터를 전달함

④ lpProcessInformation
- 생성하는 프로세스의 정보를 얻기 위해 사용되는 인자
- PROCESS_INFORMATION 구조체 변수의 주소값을 인자로 전달






참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저 


2012년 8월 24일 금요일

윈도우즈 시스템 프로그래밍 [4] - 컴퓨터 구조 2



윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.



4장. 컴퓨터 구조 2



I. 컴퓨터 구조의 접근 방법



1. 컴퓨터를 디자인 하자


- CPU의 기본 요소 : ALU, Control Unit, Registers
- 이중 register 만을 디자인 대상으로 삼음
- 시스템 프로그래머(어셈블리 프로그래머)가 CPU를 볼 때 register에 초점을 둠


2. 레지스터를 디자인하자


① 몇 비트로 구성?    16비트
② 몇 개의 레지스터로 구성?    8개 (r0 ~ r7)
③ 레지스터 각각의 용도?









- ir : instruction register

- sp : stack pointer

- lr : link register 

- pc : program counter




3. 명령어 구조를 디자인 하자


- CPU 구성 형태 ( 레지스터 구성 형태 )에 따라 명령어 구조가 달라짐

∴ 레지스터를 디자인 한 후 명령어를 디자인 함.

- 어셈블리어로 구현된 프로그램은 구조가 다른 CPU로 인식이 불가능
- 16비트를 최대한 효율적으로 활용할 수 있도록 구성
   2^16 = 65536개
   C / C++ 연산자 = 100개를 넘지 않음

ex) 레지스터 r1에 있는 값과 7을 더해 r2에 저장하라

=> 총 4개의 정보 필요 : "덧셈", "레지스터 r1", "레지스터 r2", "숫자 7"


- 연산자 :

덧셈  :  ADD,  001
뺄셈  :  SUB,  010
곱셈  :  MUL,  011
나눗셈  :   DIV,  100

- 저장소 : 레지스터가 8개 이므로 3비트 할당

- 피연산자 1, 2 : 숫자가 들어갈 수 있도록 디자인

- 피연산자 부분 -> 저장된 데이터가 숫자인지 레지스터인지 구분이 어려움
  => ∴ 네개의 비트 중 첫번째 비트가 1이면 레지스터 정보를 나타냄

ex) 1001 : r1,  0001 : 숫자 1

but 숫자를 8개 밖에 표현하지 못하는 제약 사항이 있음


- 명령어는 ir (instruction resiter )에 저장됨
   => 다음번 실행될 명령어를 미리 가져다 놓는 용도

- 구성하는 명령어의 형태에 따라 Control Unit ( 명령어를 해석하는 역할 ) 의 구조가 결정됨

- 여러가지 제약 사항들은 CPU의 종합적 성능이 고려되는 가운데 등장하게 됨
   ex) 첫번째 피연산자 위치에는 레지스터 이름만 와야함


① CISC 구조 (Complex Istruction Set Computer)

-  복잡한 명령어 체계를 가지는 컴퓨터
- 다양한 명령어를 제공 -> 수십줄에 걸쳐 구현해야할 명령어를 단 한줄로 구성할 수 있음
- 메모리를 효율적으로 사용할 수 있음
   but CPU구조가 복잡해져야 함 => 성능 향상에 제한


② RISC 구조 (Reduced Instruction Set Computer)

- CISC 구조에서 10%의 명령어만 주로 사용됨에 착안
- 명령어 수를 줄이고 길이를 일정하게 디자인
- 근래 CPU, 임베디드 환경에서 사용되는 대부분의 CPU
   => 클럭당 처리하는 명령어 수를 늘여줌 ( Pipelining ) => 성능 향상 ( 물론 초당 클럭수도 중요 )




II. LOAD & STORE 명령어 디자인



1. LOAD & STORE 명령어의 필요성


- 지금까진 " 사칙 연산 결과를 레지스터에만 저장할 수 있다 "는 제약을 둠
   => ∴ 피연산자에 메모리 주소를 사용할 수 없음


2. LOAD & STORE 명령어의 디자인




- 이전에 디자인 한 사칙 연산에 비해 피연산자가 두개(메인 메모리, 레지스터) 만 필요
- LOAD -> 110

ex) Load r3, 0x07
|  00  |  110  |  011  |  00000111  |

- STORE -> 111   // destination 과 source가 반대


ex) "c = a + b"

LOAD r1, 0x10
LOAD r2, 0x20
ADD r3, r1, r2
STORE r3, 0x30






III. Direct 모드와 Indirect 모드



1. Direct 모드의 문제점과 Indirect 모드의 제안


- 하나의 명령어에 여러 정보를 담음 -> 데이터 크기의 제한

ex) destination에는 레지스터가 8개나 되므로 문제가 없으나
source 부분에는 0x0000 ~ 0x00ff까지만 표현할 수 있음

- Direct 모드 : 할당된 비트 수 만큼의 메모리 영역만 접근 가능  =>  주소값을 명령어에 '직접' 표현




2. Indirect 모드의 이해


- 명령어에서 지정하는 번지에 저장된 값을 주소값으로 참조하게 됨




3. Indirect 모드의 활용 예제


ex)

   int a = 10;   // 0x0010번지에 할당
   int b = 20;   // 0x0100번지에 할당 => Direct 모드로는 불가능
   int c = 0;      // 0x0020번지에 할당
   c = a + b;


=====================

LOAD r1, 0x0010
MUL r0, 4, 4
MUL r2, 4, 4
MUL r3, r0, r2

STORE r3, 0x0030
(LOAD r2, r3는 사용 불가능 => source 부분에는 주소 정보만 사용가능 하기 때문)

LOAD r2, [0x0030]    // 0x0030에는 0x0100이 저장되어 있음

ADD r3, r1, r2




참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저



2012년 8월 23일 목요일

윈도우즈 시스템 프로그래밍 [3] - 64기반 프로그래밍



윈도우즈 시스템 프로그래밍을 공부하면서 정리한 글입니다.



3장. 64비트 기반 프로그래밍



I. WIN 32 VS WIN 64



1. 64비트와 32비트


- I/O 버스를 통해 전송 및 수실할 수 있는 데이터 크기에 따라 분류
- 한번에 송, 수신할 수 있는 데이터 크기와 한번에 처리할 수 있는 데이터 크기를 기준으로
   32비트 컴퓨터와 64비트 컴퓨터를 구분 짓는다.

2. 프로그래머 입장에서의 64비트 컴퓨터


- 표현할 수 있는 주소값의 범위가 넓으면 넓을수록 좋음
- 32비트 컴퓨터 -> 주소 범위와 연산 속도를 고려했을때 32비트 주소 표현이 최선
   ( 주소값 이동 & 연산이 한번에 처리됨, 32비트 컴퓨터는 표현할 수 있는 범위가 4GB나 됨 )

ex)

int arr[5] = {1, 2, 3, 4, 5,};
int *pArr = arr;
pArr += 1;

=> 주소가 32비트라면 ALU에 전송 후 연산이 한번에 가능
=> 주소가 64비트라면 ALU에 두번 나눠 전송 후 최소 두번의 연산이 필요 -> 속도 저하

- 메인 메모리인 RAM에는 주소가 붙어 있지 않음
   -> 운영체제에서 프로그램 실해응ㄹ 위해 편의상 붙여놓은 가상 메모리 주소를 사용


II. 프로그래머 관점에서의 WIN32 VS WIN64



1. LP64 VS LL64


- 32비트 -> int, long, 포인터 모두 4바이트로 표현
- 64비트

Windows
   LL64모델, char : 1바이트, short : 2바이트, int : 4바이트, long : 4바이트, 포인터 : 8바이트

UNIX
   LP64모델, char : 1바이트, short : 2바이트, int : 4바이트, long : 8바이트, 포인터 : 8바이트

2. 64비트와 32비트 공존의 문제


ex)

int main {

int arr[10] = { 0, };
int arrVal = (int) arr;   // 64비트에서 포인터를 int, long 등 4바이트 자료형으로 변환해선 안됨
printf ( "%d \n", arrVal );

return 0;
}

=> 64비트 시스템에서는 포인터가 가지고 있는 주소값을 4바이트 정수형으로 형 변환 해서는 안됨





참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저



Create Process 함수의 이해


AdderProcess.cpp   자식 프로세스 코드

1:  #include <stdio.h>  
2:  #include <tchar.h>  
3:  #include <Windows.h>  
4:    
5:  int _tmain(int argc, TCHAR* argv[]) {  
6:       DWORD val1, val2;  
7:       val1=_ttoi(argv[1]);   
8:       // 문자열 데이터를 정수형 데이터로 변경하는 atoi 함수와 유니코드 기반의 _wtoi를 동시 지원하는 매크로 함수  
9:       val2=_ttoi(argv[2]);  
10:    
11:       _tprintf(_T("%d + %d = %d \n"), val1, val2, val1+val2);  
12:    
13:       _gettchar();  // 프로그램의 실행을 잠시 멈추기 위해, getchar과 getwchar 함수를 동시 지원하는 매크로 함수  
14:       return 0;  
15:  }  



CreateProcess.cpp   부모 프로세스 코드

1:  #include <stdint.h>  
2:  #include <tchar.h>  
3:  #include <windows.h>  
4:    
5:  #define DIR_LEN MAX_PATH+1  // MAX_PATH, windef.h에 선언되어 있음. 윈도우에서 허용하는 완전경로의 최대 길이  
6:    
7:  int _tmain(int argc, TCHAR* argv[]) {  
8:       STARTUPINFO si={0, };  // 선언과 동시에 초기화. CreateProcess 함수의 9번째 인자요소. 프로세스의 속성을 지정  
9:       // 0으로 초기화 해야함.  
10:       PROCESS_INFORMATION pi;  // PROCESS_INFORMATION 구조체 변수는 새로 생성되는 프로세스 관련 정보를 얻기 위해 사용  
11:    
12:       si.cb=sizeof(si);  // 구조체 변수 si의 바이트 단위 크기, 형식적인 측면이 강함  
13:       si.dwFlags=STARTF_USEPOSITION | STARTF_USESIZE;  
14:       // si 구조체 변수에 설정된 값중에 반영하고자 하는 멤버들에 대한 정보 설정  
15:       // STARTF_USEPOSITION은 dwX와 dwY를 참조해 창의 위치를 결정  
16:       // sTARTF_USESIZE는 dwXSize와 dwYSize를 참조해 윈도우의 크기를 결정  
17:       si.dwX=100;  
18:       si.dwY=200;  
19:       si.dwXSize=300;  
20:       si.dwYSize=200;  
21:       si.lpTitle=_T("I am a boy!"); // 윈도우의 타이틀 바 제목을 지정  
22:       // 콘솔 프로세스가 아닌, 노트패드와 같은 윈도우 프로세스일 경우 아무런 의미가 없음  
23:    
24:       TCHAR command[]=_T("AdderProcess.exe 10 20");  // 실행 파일의 이름과 함께 전달할 인자  
25:       TCHAR cDir[DIR_LEN];  // 디렉토리 정보가 저장될 버퍼 포인터  
26:       BOOL state;  
27:    
28:       GetCurrentDirectory(DIR_LEN, cDir);  
29:       // cDir 디렉토리 정보가 저장될 버퍼 포인터, DIR_LEN 디렉토리 정보가 저장될 메모리 버퍼의 크기  
30:       _fputts(cDir, stdout);  // 현재 디렉토리 정보를 추출해서 출력
31:       _fputts(_T("\n"), stdout);  
32:    
33:       SetCurrentDirectory(_T("C:\\WinSystem"));  
34:       // 현재 디렉토리 경로명을 지정할 수 있음  
35:    
36:       GetCurrentDirectory(DIR_LEN, cDir);  // 현재 디렉토리를 다시 확인  
37:       _fputts(cDir, stdout);  // 현재 디렉토리 경로명을 다시 출력. C:\\WinSystem이 있어야 변경됨.
38:       _fputts(_T("\n"), stdout);  
39:    
40:       state = CreateProcess (  
41:            NULL,  // 첫번째 인자로 NULL을 전달하고  
42:            command,  // 두번째 인자를 통해 생성하려는 프로세스 이름 정보와 함께 전달 인자까지 함께 전달할 수 있음.  
43:            //  전달 인자는 반드시 변수 형태여야 함. _T("AdderProcess.exe 10 20"),을 직접 파라미터에 쓰면 안됨.  
44:            NULL, NULL, TRUE,  
45:            CREATE_NEW_CONSOLE,  // 프로세스의 특성을 결정지을 때 사용.  
46:            // CREATE_NEW_CONSOLE은 프로세스를 위한 콘솔 윈도우가 독립적으로 생성됨.  
47:            // CREATE_NEW_CONSOLE을 전달하지 않으면 부모 프로세스의 콘솔 윈도우를 자식 프로세스가 공유하게 됨  
48:            // CREATE_NEW_CONSOLE 대신 0을 전달해 확인할 수 있음  
49:            NULL, NULL, &si, &pi  
50:            );  
51:    
52:       if(state!=0)  
53:            _fputts(_T("Creation OK! \n"), stdout);  
54:       else  
55:            _fputts(_T("Creation ERROR! \n"), stdout);  
56:    
57:       return 0;  
58:  }  


< 응용 >

Calculator.cpp

1:  #include <stdio.h>  
2:  #include <tchar.h>  
3:  #include <Windows.h>  
4:    
5:  enum{DIV=1, MUL, ADD, MIN, ELSE, EXIT};  
6:    
7:  DWORD ShowMenu();  
8:  void Divide(double, double);  
9:  void Multiple(double, double);  
10:  void Add(double, double);  
11:  void Min(double, double);  
12:    
13:  int _tmain(int argc, TCHAR* argv[]) {  
14:       STARTUPINFO si={0, };  
15:       PROCESS_INFORMATION pi;  
16:       si.cb=sizeof(si);  
17:    
18:       TCHAR command[]=_T("calc.exe");  
19:       SetCurrentDirectory(_T("C:\\WINDOWS\\system32"));  
20:    
21:       DWORD sel;  
22:       double num1, num2;  
23:       while(true) {  
24:            sel=ShowMenu();  
25:            if(sel==EXIT)  
26:                 return 0;  
27:    
28:            if(sel != ELSE) {  
29:                 _fputts(_T("Input Num1 Num2 : "), stdout);  
30:                 _tscanf(_T("%lf %lf"), &num1, &num2);  
31:            }  
32:    
33:            switch(sel) {  
34:            case DIV:  
35:                 Divide(num1, num2);  
36:                 break;  
37:            case MUL:  
38:                 Multiple(num1, num2);  
39:                 break;  
40:            case ADD:  
41:                 Add(num1, num2);  
42:                 break;  
43:            case MIN:  
44:                 Min(num1, num2);  
45:                 break;  
46:            case ELSE:  
47:                 ZeroMemory(&pi, sizeof(pi));  
48:                 CreateProcess(  
49:                      NULL, command, NULL, NULL,  
50:                      TRUE, 0, NULL, NULL, &si, &pi);  
51:                 break;  
52:            }  
53:       }  
54:         
55:       return 0;  
56:  }  
57:    
58:  DWORD ShowMenu() {  
59:       DWORD sel;  
60:    
61:       _fputts(_T("------Menu------\n"), stdout);  
62:       _fputts(_T("num1: Divide\n"), stdout);  
63:       _fputts(_T("num2: Multiple\n"), stdout);  
64:       _fputts(_T("num3: Add\n"), stdout);  
65:       _fputts(_T("num4: Minus\n"), stdout);  
66:       _fputts(_T("num5: Any other operations\n"), stdout);  
67:       _fputts(_T("num6: EXIT"), stdout);  
68:       _fputts(_T("SELECION >> "), stdout);  
69:       _tscanf(_T("%d"), &sel);  
70:    
71:       return sel;  
72:  }  
73:    
74:  void Divide(double a, double b) {  
75:       _tprintf(_T("%f/%f=%f \n\n"), a, b, a/b);  
76:  }  
77:    
78:  void Multiple(double a, double b) {  
79:       _tprintf(_T("%f*%f=%f \n\n"), a, b, a*b);  
80:  }  
81:    
82:  void Add(double a, double b) {  
83:       _tprintf(_T("%f+%f=%f \n\n"), a, b, a+b);  
84:  }  
85:    
86:  void Min(double a, double b) {  
87:       _tprintf(_T("%f-%f=%f \n\n"), a, b, a-b);  
88:  }  




참고 : 뇌를 자극하는 윈도우즈 시스템 프로그래밍. 윤성우 저