일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- binary search
- Stored Procedure
- 스토어드 프로시저
- Dijkstra
- 이진탐색
- MYSQL
- Trie
- Two Points
- union find
- DP
- Brute Force
- SQL
- String
- Hash
- 다익스트라
- 그래프
- two pointer
- Today
- Total
codingfarm
7. 프로세스간 통신(IPC) 1 본문
학습 목표
- IPC 기법에 대해 이해한다.
- 커널 오브젝트의 두가지 상태(signaled & Non-Signaled)와 핸들 테이블에 대해 이해한다.
1. 프로세스간 통신(IPC; Inter Process Communication)
- 프로세스는 각각 독립된 메모리 공간을 소유하므로 IPC가 어렵다.
- 프로세스는 자신에게 할당된 메모리 공간 이외에는 접근이 불가능하다.
- 그러므로 IPC를 위한 별도의 통신수단이 필요하다
2. 메일슬롯 방식의 IPC
메일 슬롯(Mail Slot)의 원리
- 대표적인 IPC 기법중 하나
- 프로세스 간에 데이터를 주고 받기 위해 접선 장소를 마련한다.
- 단방향 통신만 가능
mailslot의 생성과 연결은 CreateMailSlot과 CreateFile 함수를 통해 이루어지며
데이터 수신 및 송신은 ReadFile과 WriteFile을 통해 이루어진다.
mailslot 정의
- 프로세스간 데이터를 받기 위한 역할
- 커널에 의해 관리되는 resource이므로 kernel object가 있으며, handle로 접근함
- 단방향 통신
- broadcasting 불가
- 주소를 부여하여 외부 proces가 접근 가능
- 각기 다른 프로세스에 동일한 주소의 mail slot 제작이 가능
- broad cast 방식의 송신 가능
- 생성과 동시에 usage count가 1이다
- mailslot을 생성한 프로세스 하나만 참조한다
- process와 thread를 제외한 다른 모든 resource의 kernel object는 생성과 동시에 usage count가 하나임
mailslot 구성을 위해 필요한 요소
1. Receiver가 준비할것
CreateMailSlot 함수로 mailslot을 생성
1
2
3
4
5
6
|
HANDLE CreateMailslot(
LPCTSTR lpName,
DWORD nMaxMessageSize,
DWORD lReadTimeout,
LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
|
cs |
인수 | 기능 |
LPCTSTR lpName | ▶ 생성하는 메일슬롯의 이름을 결정 ▶ 주소의 기본 형식 \\computername\mailslot\[path]name $\rightarrow$ "\\\\.\\mailslot\\mailbox" |
DWORD nMaxMessageSize | ▶ mailslot의 버퍼 크기를 지정 - 0 : 시스템이 허용하는 최대 크기로 지정 |
DWORD lReadTimeout | ▶ 최대 blocking 시간을 millisecond 단위로 지정하는데 사용 - 0 : blocking 상태 없이 빠져나옴 - MAILSLOT_WAIT_FOREVER : 읽어들일 데이터가 존재하게 될 때까지 브로킹 상태에 놓임 |
LPSECURITY_ATTRIBUTES lpSecurityAttributes |
▶ 핸들의 상속 여부를 결정 - NULL : 상속X |
return value | ▶ 생성된 mailslot kernel object의 handle |
생성된 메일슬롯에 들어온 데이터는 ReadFile 함수를 통해 읽을 수 있다.
CreateMailSlot과 ReadFile을 이용하는 뼈대코드
1
2
3
4
5
6
7
8
9
10
|
// Recevier의 skeleton code
HANDLE hMailSlot;
hMailSlot = CreateFile("\\\\.\\mailslot\\mailbox", ...);
...
...
CHAR message[50];
WriteFile(hMailSlot, message, ...);
|
cs |
2. sender가 준비할 것
- CreateFile 함수로 mail slot에 접근하고 WriteFile을 통해 데이터를 전송
- Receiver측에서 만든 mailslot의 이름(주소)를 알아야 한다.
주소체계
"\\Computername\mailslot\[path]name"
ex) "\\.\mailslot\abc\def\mailbox"
. : local computer(현재 자신이 쓰는 컴퓨터)
computer name : 동일한 네트워크 도메인에 존재하는 호스트
* : 네트원크로 연결된 모든 같은 조소로 생성된 mailslot
데이터 스트림
- mailslot에 data 전송을 위해 필요
- 개념적인 표현임
- sender에서 receiver의 mailslot과 연결하게끔 해주는 다리 역할
- resource이므로 kernel object와 handle의 생성을 동반함
BroadCasting 방식의 통신
하나의 sender가 한번에 여러개의 receiver에게 동일한 메시지를 전송
예제 코드
receiver
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
43
44
45
|
// MailReceiver.cpp
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox")
int _tmain(int argc, TCHAR* argv[]) {
HANDLE hMailSlot;
TCHAR messageBox[50];
DWORD bytesRead;
hMailSlot = CreateMailslot(
SLOT_NAME, // 주소
0, // 버퍼 크기를 허용 최대치로
MAILSLOT_WAIT_FOREVER, // ReadFile 함수 특성
NULL // 보안 설정(handle 미상속)
);
if (hMailSlot == INVALID_HANDLE_VALUE) {
_fputts(_T("Unable to create mailslot!\n"), stdout);
return 1;
}
// Message 수신
while (1) {
if (!ReadFile(hMailSlot, messageBox, sizeof(TCHAR) * 50, &bytesRead, NULL)) {
_fputts(_T("Unable to read!"), stdout);
CloseHandle(hMailSlot);
return 1;
}
if (!_tcsncmp(messageBox, _T("exit"), 4)) {
_fputts(_T("Good Bye!"), stdout);
break;
}
messageBox[bytesRead / sizeof(TCHAR)] = 0; // NULL 문자 삽입
_fputts(messageBox, stdout);
}
CloseHandle(hMailSlot);
return 0;
}
|
cs |
sender
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
43
44
45
46
47
48
49
50
|
// MailSender.cpp
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox")
int _tmain(int argc, TCHAR* argv[]) {
HANDLE hMailSlot;
TCHAR message[50];
DWORD bytesWritten;
hMailSlot = CreateFile(
SLOT_NAME,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hMailSlot == INVALID_HANDLE_VALUE) {
_fputts(_T("Unable to create mailslot!\n"), stdout);
return 1;
}
// Message 송신
while (1) {
_fputts(_T("MY CMD>"), stdout);
_fgetts(message, sizeof(message) / sizeof(TCHAR), stdin);
if (!WriteFile(hMailSlot, message,
_tcslen(message) * sizeof(TCHAR), &bytesWritten, NULL)) {
_fputts(_T("Unable to write!"), stdout);
CloseHandle(hMailSlot);
return 1;
}
// exit 입력시 종료
if (!_tcscmp(message, _T("exit\n"))) {
_fputts(_T("Good Bye!"), stdout);
break;
}
}
CloseHandle(hMailSlot);
return 0;
}
|
cs |
실행 결과
receiver를 먼저 실행하여 mailslot을 생성하고, sender를 실행하여 연결을 시도해야한다.
sender에서 입력한대로 receiver측에서 출력이 발생하는 프로그램이며, exit를 입력하면 연결이 종료된다.
특이한점은 sender측의 _tcslen 함수에서 "exit\n"과 비교하는 부분에서 \n이 빠지면 제대로 종료가 안되지먄
receiver측에서는 "exit\n" 에 \n이 빠저도 제대로 종료된다. 아직 이유는 모르겠다.
3. Signaled vs Non-Signaled
kernel object state
- signaled(TRUE), non-signaled(FALSE) 2가지로 나뉨
- resource에 특정 상황이 발생했음을 알려주기 위해 존재
- 특정상황은 resource에 따라 다름
- 그러므로 kernel object의 상태가 변하는 시점은 그 종류에 따라 달라짐
process kernel object의 상태 변화
- process가 생성되면서 process kernel object가 만들어지며, 최초의 상태는 Non-signaled가 된다.
- Non-Signaled : 프로세스 실행 중
- Signaeld : 프로세스 종료
프로세스의 커널 오브젝트는 Signaled 에서 Non-Signaled로 전환 불가능 하다
(이는 리소스의 종류마다 서로 상이한 특성임)
커널 오브젝트의 상태 확인하기
WaitForSingleObject 함수를 통해 알 수 있다.
1
2
3
4
|
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
|
cs |
- hHandle : 상태를 확인하기 원하는 커널 오브젝트의 핸들
- dwMilliseconds : 상태를 확인중인 커널 오브젝트가 Signaled가 될 때까지 기다리는 시간
- INFINITE : 무한정 대기
- 정수 값 : milliseconds 시간만큼 대기
- return value
- WAIT_OBJECT_0 : 커널 오브젝트가 Signaled 상태가 되었을 때 반환하는 값
- WAIT_TIMEOUT : 설정된 시간이 다 될 경우 반환하는 값
- WAIT_ABANDONED : 소유 관계와 관련하여 함수가 정상적이지 못한 오류 발생에 의해 반환되는 경우(13장에서 배움)
둘 이상의 kernel object를 확인하고 싶을 경우 WaitForMultipleObjects 함수로 판별 가능
1
2
3
4
5
6
|
DWORD WaitForMultipleObjects(
DWORD nCount,
const HANDLE *lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);
|
cs |
- nCount : 배열의 크기
- lpHandles : 배열의 시작 주소
- bWaitAll
- TRUE : 관찰 대상이 모두 Signaled 상태가 되기를 기다린다.
- FALSE : 하나라도 signaled 상태가 되면 반환
- dwMilliseconds : 타임아웃 설정
프로세스의 종료 코드(Exit Code)
프로세스는 main 함수의 return 값을 통해 자신이 어떻게 종료되었는지에 대한 정보를 담은 exit code를 남길 수 있다.
이 exit code는 해당 프로세스의 kernel object 내에 남게 되며, GetExitCodeProcess를 호출하여 값을 얻어올 수 있다.
예제 코드
커널 오브젝트의 상태 확인이 필요한 상황을 연출해보자
부모 프로세스에서 A와 B 2개의 자식프로세스를 생성한다.
각 자식 프로세스는 1~5, 6~10 의 합연산을 처리하며
연산이 끝나면 결과값을 부모 프로세스에서 받아 두 수의 합을 출력하는 프로그램이다.
위 프로그램 작성시 주의할 부분은
프로세스 A와 B의 연산이 끝난후에 부모프로세스에서 합 연산이 진행되어야 한다는 점이다.
이를 위해 프로세스를 동기화할 필요가 있으며, 여기에서 앞서 배운 WaitForSingleObject나 WaitForMultipleObject를 사용하겠다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// PardAdder.cpp
#include<tchar.h>
#include<windows.h>
int _tmain(int argc, TCHAR* argv[]) {
if (argc != 3)
return -1;
DWORD start = _ttoi(argv[1]);
DWORD end = _ttoi(argv[2]);
DWORD total = 0;
for (DWORD i = start; i <= end; i++)
total += i;
return total;
}
|
cs |
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
// WaitForObject.cpp
#include<stdio.h>
#include<tchar.h>
#include<windows.h>
using namespace std;
int _tmain(int argc, TCHAR* argv[]) {
STARTUPINFO si1 = { 0, };
STARTUPINFO si2 = { 0, };
PROCESS_INFORMATION pi1, pi2;
DWORD return_val1;
DWORD return_val2;
TCHAR command1[] = _T("PartAdder.exe 1 5");
TCHAR command2[] = _T("PartAdder.exe 6 10");
DWORD sum = 0;
si1.cb = sizeof(si1);
si2.cb = sizeof(si2);
CreateProcess(NULL, command1,
NULL, NULL, TRUE,
0, NULL, NULL,
&si1, &pi1);
CreateProcess(NULL, command2,
NULL, NULL, TRUE,
0, NULL, NULL,
&si2, &pi2);
CloseHandle(pi1.hThread);
CloseHandle(pi2.hThread);
/*
HANDLE handles[2];
handles[0] = pi1.hProcess;
handles[1] = pi2.hProcess;
WaitForMultipleObjects(2, handles, TRUE, INFINITE);
*/
WaitForSingleObject(pi1.hProcess, INFINITE);
WaitForSingleObject(pi2.hProcess, INFINITE);
GetExitCodeProcess(pi1.hProcess, &return_val1);
GetExitCodeProcess(pi2.hProcess, &return_val2);
if (return_val1 == -1 || return_val2 == -1)
return -1; // 비정상 종료
sum += return_val1;
sum += return_val2;
_tprintf(_T("total : %d \n"), sum);
CloseHandle(pi1.hProcess);
CloseHandle(pi2.hProcess);
system("pause");
return 0;
}
|
cs |
'Windows > 윈도우즈 시스템 프로그래밍' 카테고리의 다른 글
18. 파일 I/O와 디렉터리 컨트롤 - 작성중 (0) | 2021.05.15 |
---|---|
6. 커널 오브젝트와 오브젝트 핸들 (0) | 2020.06.15 |
5. 프로세스의 생성과 소멸 (0) | 2020.06.15 |
4. 컴퓨터 구조에 대한 두 번째 이야기 (0) | 2020.05.29 |
2. 아스키코드와 유니코드 (0) | 2020.05.29 |