프로세스 생성 및 제어
- CreateProcess(), OpenProcess()
- ExitProcess() = 내가 작성한 내프로세스를 종료시킬때
- TerminateProcess() = 남의 프로세스를 종료시킬때, 해당 process handle이 있어야함
- Get/SetProcessAffinityMask() = process 내부의 모든 스레드가 affinity 영향을 받게됨
- EnumProcess
- GetModuleFileNameEx
- CreateToolhelp32Snapshot = enumProcess도 현재 돌고있는 process를 열거하지만 이 방법을 주로 사용해야한다.
- Process32First, Process32Next
실행 중인 프로세스 열거
int main()
{
_wsetlocale(LC_ALL, L"korean");
DWORD pidArray[512] = { 0 };
DWORD cbNeeded = 0;
DWORD nProcesses = 0;
TCHAR szBuffer[MAX_PATH + _MAX_FNAME] = { 0 };
UINT uCounter = 0;
if (::EnumProcesses(pidArray, sizeof(pidArray), &cbNeeded))
{
nProcesses = cbNeeded / sizeof(DWORD);
for (DWORD i = 0; i <= nProcesses; i++)
{
HANDLE hProcess;
DWORD pid = pidArray[i];
hProcess = ::OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (hProcess != NULL)
{
::ZeroMemory(szBuffer, MAX_PATH + _MAX_FNAME);
if (::GetModuleFileNameEx(hProcess, NULL,
szBuffer, MAX_PATH + _MAX_FNAME) != NULL)
{
_tprintf(TEXT("[PID: %d] %s\n"), pid, szBuffer);
}
::CloseHandle(hProcess);
}
else
{
_tprintf(TEXT("[PID: %d, Error code: %d]\n"),
pid, ::GetLastError());
continue;
}
}
}
return 0;
}
EnumProcess 는 pid만 얻을 수 있다. 실제 해당 process의 자세한 정보를 얻기위해서는 pid로 OpenProcess를 호출해서 해당 process의 handle을 얻어야 한다.
open하려는 process가 system권한이면 제일 높은 권한이니 handle을 주지 않을 수 있다. (권한이 없으니까)
코드를 실행시켜면 에러나오는것들이 그런것들이다. error code 5 = access denied
int main()
{
_wsetlocale(LC_ALL, L"korean");
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hSnapshot != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32 pe32 = { sizeof(pe32) };
BOOL bFlag = ::Process32First(hSnapshot, &pe32);
for (; bFlag; bFlag = ::Process32Next(hSnapshot, &pe32))
_tprintf(TEXT("[PID: %d] %s\n"),
pe32.th32ProcessID, pe32.szExeFile);
::CloseHandle(hSnapshot);
}
return 0;
}
그런 권한이 높은 process들은 CreateToolhelp32Snapshot으로 호출할 수 있다.
마치 작업관리자 처럼 모든 프로세스를 볼 수 있다. 호출자가 해당 process의 handle을 얻어서 모든 것을 다 하겠다는 의미가 아니고 함수에서 추출한 정보를 보겠다는 의미이다.
Process32First/Next 로 process들을 순회해서 조회할 수 있다.
프로세스 동기화 및 IPC (Client)
프로세스 동기화
- 커널오브젝트로 구현 된 것은 스레드 동기화를 했던것 처럼 동일하게 프로세스 동기화에 쓸 수 있음
- Event, Mutex , Semaphore가 그러함
process간의 동기화는 다른 프로세스가 락을 잡고 죽었을 경우에도 고민을 해줘야한다. 안그러면 평생 대기하는 프로세스가 생길 수 있기 때문이다.
서로 다른 process간의 VMS 에 접근은 불가능하다. 어떤 정보의 동기화를 해야할때는 IPC를 통해 하게 된다.
공유 메모리 기법 (요놈도 IPC 기법 중 하나)
파일없이 맵핑 객체를 만들어버리면 1024의 메모리 한덩이가 동적할당(kenrnel 수준에서 malloc) 한 것처럼 잡힌다.
커널이 쓰는 vms에 잡힌것이니 user단에서 포인터로 추상화해서 뽑아낸다.
user단 포인터로 맵핑된것을 읽었을 때는 사실 kernel malloc된 메모리에 쓰인것을 읽게 되는 것이다.
kernel 단은 공유 되니 같은것을 보게 되는것이다.
server process가 쓴것을 client process가 읽을 수 있다.
Event로 타이밍도 공유할 수 있다.
int main()
{
OutputDebugString(_T("Client - begin\n"));
//1. 파일 매핑 객체 생성(혹은 개방)
HANDLE hMap = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE,
0,
128,
TEXT("IPC_TEST_SHARED_MEMORY"));
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
//파일 매핑 객체가 이미 생성되어 있으므로 기존 것을 오픈
hMap = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE,
TEXT("IPC_TEST_SHARED_MEMORY"));
if (hMap == NULL)
{
_tprintf(TEXT("Failed to open file mapping obj. (%d)\n"),
::GetLastError());
return FALSE;
}
}
//2. 매핑 객체에 대한 접근 포인터를 얻음. (메모리 추상화)
TCHAR *pSharedMemory = (TCHAR*)::MapViewOfFile(
hMap, FILE_MAP_ALL_ACCESS, 0, 0, 128);
if (pSharedMemory == NULL)
{
_tprintf(TEXT("Failed to get shared memory. (%d)\n"),
::GetLastError());
::CloseHandle(hMap);
return 0;
}
//3. 공유 메모리 접근 및 읽기(수신 완료) 이벤트 생성(혹은 개방)
HANDLE hEvent = ::CreateEvent(
NULL, TRUE, FALSE, TEXT("IPC_SHAREDMEM_ACCESS"));
if (::GetLastError() == ERROR_ALREADY_EXISTS)
{
hEvent = ::OpenEvent(
EVENT_ALL_ACCESS, FALSE, TEXT("IPC_SHAREDMEM_ACCESS"));
if (hEvent == NULL)
{
_tprintf(TEXT("Failed to open event obj. (%d)\n"),
::GetLastError());
::UnmapViewOfFile(pSharedMemory);
::CloseHandle(hMap);
return 0;
}
}
HANDLE hEvtComplete = ::CreateEvent(
NULL, TRUE, FALSE, TEXT("IPC_SHAREDMEM_RECV_COMPLETE"));
if (::GetLastError() == ERROR_ALREADY_EXISTS)
{
hEvtComplete = ::OpenEvent(
EVENT_ALL_ACCESS, FALSE, TEXT("IPC_SHAREDMEM_RECV_COMPLETE"));
if (hEvent == NULL)
{
_tprintf(TEXT("Failed to open event obj. (%d)\n"),
::GetLastError());
::UnmapViewOfFile(pSharedMemory);
::CloseHandle(hMap);
::CloseHandle(hEvent);
return 0;
}
}
//4. 프로세스 동기화를 위한 뮤텍스 생성
HANDLE hMutex = ::CreateMutex(NULL, FALSE,
TEXT("IPC_SHAREDMEM_MUTEX"));
if (::GetLastError() == ERROR_ALREADY_EXISTS)
{
hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE,
TEXT("IPC_SHAREDMEM_MUTEX"));
if(hMutex == NULL)
{
_tprintf(TEXT("Failed to open mutex obj. (%d)\n"),
::GetLastError());
::UnmapViewOfFile(pSharedMemory);
::CloseHandle(hMap);
::CloseHandle(hEvent);
::CloseHandle(hEvtComplete);
return 0;
}
}
//5. 공유 메모리 쓰기 이벤트 대기
OutputDebugString(TEXT("Waiting message from server...\n"));
if (::WaitForSingleObject(hEvent, INFINITE) == WAIT_OBJECT_0)
{
//6. 뮤텍스를 이용한 프로세스 동기화 (메모리 접근)
if(::WaitForSingleObject(hMutex, INFINITE) != WAIT_OBJECT_0)
OutputDebugString(TEXT("MUTEX error.\n"));
else
OutputDebugString(TEXT("MUTEX Lock.\n"));
OutputDebugString(pSharedMemory);
::ReleaseMutex(hMutex);
OutputDebugString(TEXT("MUTEX Unlock.\n"));
//7. 메모리에 읽기 완료 이벤트 세트
::SetEvent(hEvtComplete);
OutputDebugString(_T("Completion event for server!\n"));
}
::UnmapViewOfFile(pSharedMemory);
::CloseHandle(hMap);
::CloseHandle(hEvent);
::CloseHandle(hEvtComplete);
::CloseHandle(hMutex);
_tprintf(_T("Client - end\n"));
return 0;
}
CreateFileMapping에 Invalid handle value를 넣는다 파일을 넣치 않고 하지만 page read write 권한이 있는 커널단 메모리가 malloc이 된다.
만약 이미 할당이 되어있다면 (error already exists) OpenFileMapping으로 해당 매핑객체를 열어서 가져옴
이후 해당 매핑객체에 대해 user 단에서도 쓰기 위해서 메모리 추상화를 MapViewOfFile 통해 하게 된다.
위 코드는 client이니까 언제 읽을 것인가를 정하게 되는 Event 를 생성한다. server 가 써야 읽으니까
Event 생성시 이름을 부여하여 같은 커널 객체가 생성되지 않게 한다.
2개의 Event 를 생성한다. 하나는 server로부터 읽어도 된다. 나머지 하나는 client가 나 다 읽었다. 하는 이벤트이다.
client, server 에서 각각 같은 event를 바라보게 된다. event 생성시 give name이 global namespace에 등록이 되기 때문에 만약 이미 생성된거면 GetLastError에서 ERROR_ALRAEDY_EXISTS를 뱉어낸다.
server가 data를 쓰고 Event를 set하더라도 client는 mutex를 기다려야한다(임계구간 동시진입 방지).
프로세스 동기화 및 IPC (Server)
#include <iostream>
#include <tchar.h>
#include <windows.h>
int main()
{
OutputDebugString(_T("Server - begin\n"));
//1. 파일 매핑 객체 생성(혹은 개방)
HANDLE hMap = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE,
0,
128,
TEXT("IPC_TEST_SHARED_MEMORY"));
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
//파일 매핑 객체가 이미 생성되어 있으므로 기존 것을 오픈
hMap = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE,
TEXT("IPC_TEST_SHARED_MEMORY"));
if (hMap == NULL)
{
_tprintf(TEXT("Failed to open file mapping obj. (%d)\n"),
::GetLastError());
return FALSE;
}
}
//2. 매핑 객체에 대한 접근 포인터를 얻음. (메모리 추상화)
TCHAR* pSharedMemory = (TCHAR*)::MapViewOfFile(
hMap, FILE_MAP_ALL_ACCESS, 0, 0, 128);
if (pSharedMemory == NULL)
{
_tprintf(TEXT("Failed to get shared memory. (%d)\n"),
::GetLastError());
::CloseHandle(hMap);
return 0;
}
//3. 공유 메모리 접근 및 읽기(수신 완료) 이벤트 생성(혹은 개방)
HANDLE hEvent = ::CreateEvent(
NULL, TRUE, FALSE, TEXT("IPC_SHAREDMEM_ACCESS"));
if (::GetLastError() == ERROR_ALREADY_EXISTS)
{
hEvent = ::OpenEvent(
EVENT_ALL_ACCESS, FALSE, TEXT("IPC_SHAREDMEM_ACCESS"));
if (hEvent == NULL)
{
_tprintf(TEXT("Failed to open event obj. (%d)\n"),
::GetLastError());
::UnmapViewOfFile(pSharedMemory);
::CloseHandle(hMap);
return 0;
}
}
HANDLE hEvtComplete = ::CreateEvent(
NULL, TRUE, FALSE, TEXT("IPC_SHAREDMEM_RECV_COMPLETE"));
if (::GetLastError() == ERROR_ALREADY_EXISTS)
{
hEvtComplete = ::OpenEvent(
EVENT_ALL_ACCESS, FALSE, TEXT("IPC_SHAREDMEM_RECV_COMPLETE"));
if (hEvtComplete == NULL)
{
_tprintf(TEXT("Failed to open event obj. (%d)\n"),
::GetLastError());
::UnmapViewOfFile(pSharedMemory);
::CloseHandle(hMap);
::CloseHandle(hEvent);
return 0;
}
}
//4. 프로세스 동기화를 위한 뮤텍스 생성
HANDLE hMutex = ::CreateMutex(NULL, FALSE,
TEXT("IPC_SHAREDMEM_MUTEX"));
if (::GetLastError() == ERROR_ALREADY_EXISTS)
{
hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE,
TEXT("IPC_SHAREDMEM_MUTEX"));
if (hMutex == NULL)
{
_tprintf(TEXT("Failed to open mutex obj. (%d)\n"),
::GetLastError());
::UnmapViewOfFile(pSharedMemory);
::CloseHandle(hMap);
::CloseHandle(hEvent);
::CloseHandle(hEvtComplete);
return 0;
}
}
//5. 뮤텍스를 이용한 프로세스 동기화 (메모리 접근)
if(::WaitForSingleObject(hMutex, INFINITE) != WAIT_OBJECT_0)
OutputDebugString(TEXT("MUTEX error.\n"));
else
OutputDebugString(TEXT("MUTEX Lock.\n"));
wsprintfW(pSharedMemory, _T("%s"), _T("Hello, Shared memory!"));
::ReleaseMutex(hMutex);
OutputDebugString(TEXT("MUTEX Unlock.\n"));
//6. 공유 메모리 쓰기 접근 및 쓰기 이벤트 세트
OutputDebugString(TEXT("Message event set.\n"));
::SetEvent(hEvent);
//7. 클라이언트 수신 완료 이벤트 대기
OutputDebugString(TEXT("Waiting recv event...\n"));
if (::WaitForSingleObject(hEvtComplete, INFINITE) == WAIT_OBJECT_0)
OutputDebugString(_T("Completion event!\n"));
::UnmapViewOfFile(pSharedMemory);
::CloseHandle(hMap);
::CloseHandle(hEvent);
::CloseHandle(hEvtComplete);
::CloseHandle(hMutex);
OutputDebugString(_T("Server - end\n"));
return 0;
}
write 하는 부분이 있다는거 빼고 client랑 다 동일하다.
3명이 wait하고 있을 때 server가 set하면 3중에 한명이 반응하게 된다. Event가 manual reset이 아니기 때문에 한client가 받으면 Event가 reset이 되어버린다.
하지만 server입장에서 waitfor를 모든 client 마다 n개의 event를 하고 있어야함... 하지만 client 개수를 알기 어렵다.
일정시간 대기했다가 server가 reset하는 방법도 있겟다.
'Operating System > Windows 시스템 프로그래밍 - 기본' 카테고리의 다른 글
DLL (0) | 2023.07.20 |
---|---|
메모리 시스템 (0) | 2023.07.15 |
Win32 파일 입/출력 (0) | 2023.07.10 |
스레드 동기화 (0) | 2023.07.07 |
스레드 생성 및 제어 (0) | 2023.07.05 |