디스크, 디렉토리, 파일열거
#include <iostream>
#include <windows.h>
#include <stdio.h>
void LoadDiskInfo(const TCHAR* pszPath)
{
TCHAR szVolName[MAX_PATH] = { 0 };
TCHAR szFileSys[MAX_PATH] = { 0 };
TCHAR szRoot[MAX_PATH] = { 0 };
DWORD dwSerialNum = 0, dwMaxCompLen = 0, dwSysFlag = 0;
memcpy(szRoot, pszPath, sizeof(TCHAR) * 3);
::GetVolumeInformation(szRoot, szVolName, MAX_PATH, &dwSerialNum,
&dwMaxCompLen, &dwSysFlag, szFileSys, MAX_PATH);
wprintf(TEXT("Volume name : %s, File system : %s\n"),
szVolName, szFileSys);
ULARGE_INTEGER llAvailableSpace = { 0 };
ULARGE_INTEGER llTotalSpace = { 0 };
ULARGE_INTEGER llFreeSpace = { 0 };
if (::GetDiskFreeSpaceEx(szRoot,
&llAvailableSpace, &llTotalSpace, &llFreeSpace))
{
wprintf(TEXT(" (Disk free space: %I64u/%I64u GB)\n"),
llFreeSpace.QuadPart / (1024 * 1024 * 1024),
llTotalSpace.QuadPart / (1024 * 1024 * 1024) );
}
}
void LoadFileList(const TCHAR* pszPath)
{
//*.exe: 실행파일만 보기
//a*.txt: a로 시작하는 텍스트 파일만 보기
//*.* : 모든 파일 보기
TCHAR szPath[MAX_PATH] = {0};
wsprintf(szPath, TEXT("%s\\*.*"), pszPath);
WIN32_FIND_DATA FindData;
BOOL bResult = TRUE;
::ZeroMemory(&FindData, sizeof(WIN32_FIND_DATA));
HANDLE hFirstFile = ::FindFirstFile(
(LPTSTR)(const TCHAR*)szPath, &FindData);
while (bResult)
{
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
wprintf(TEXT("[DIR] %s\n"), FindData.cFileName);
else
wprintf(TEXT("%s\n"), FindData.cFileName);
bResult = ::FindNextFile(hFirstFile, &FindData);
}
}
int main()
{
::_wsetlocale(LC_ALL, TEXT("korean"));
LoadDiskInfo(TEXT("C:\\"));
LoadFileList(TEXT("C:\\"));
TCHAR szCurDir[MAX_PATH] = { 0 };
::GetCurrentDirectory(MAX_PATH, szCurDir);
wprintf(TEXT("Current Directory: %s\n"), szCurDir);
}
MAX_PATH , MAX_FNAME = 디렉토리 길이, 파일이름 길이의 최대치이다.
동기 파일 입/출력 API와 파일 복사
I/O는 os가 처리하는 영역이다. 비동기로 하면 명시적으로 os가 처리 후 보통의 경우 callback으로 다되면 호출한다.
fopen -> CreateFile()
fread -> ReadFile()
fwrite -> WriteFile()
#include <iostream>
#include <stdio.h>
#include <windows.h>
int main()
{
::_wsetlocale(LC_ALL, L"korean");
HANDLE hFileSource = NULL, hFileTarget = NULL;
//1. 원본 파일을 읽기모드로 연다.
hFileSource = ::CreateFile(TEXT("C:\\TEST\\Sleep Away.zip"),
GENERIC_READ, //읽기모드
FILE_SHARE_READ, //읽기모드 공유허용
NULL, //보안속성 없음.
OPEN_EXISTING, //존재하는 파일 열기
FILE_ATTRIBUTE_NORMAL,
NULL); //동기 I/O
if (hFileSource == INVALID_HANDLE_VALUE)
{
wprintf(L"Failed to open source file [ERROR CODE: %d]\n",
::GetLastError());
return 0;
}
//2. 대상 파일을 쓰기모드로 연다.
hFileTarget = ::CreateFile(TEXT("C:\\TEST\\Sleep Away - copy.zip"),
GENERIC_WRITE, //쓰기모드
0, //공유안함.
NULL, //보안속성 없음.
CREATE_ALWAYS, //새파일 생성
FILE_ATTRIBUTE_NORMAL,
NULL); //동기 I/O
if (hFileTarget == INVALID_HANDLE_VALUE)
{
wprintf(L"Failed to open target file [ERROR CODE: %d]\n",
::GetLastError());
::CloseHandle(hFileSource);
return 0;
}
//3. 루프를 돌면서 파일 내용을 복사한다.
LARGE_INTEGER llFileSize = { 0 };
LONGLONG llTotalReadSize = 0;
DWORD dwReadSize = 0, dwWriteSize = 0;
BOOL bResult = FALSE;
BYTE byBuffer[65536];
if (!::GetFileSizeEx(hFileSource, &llFileSize))
{
wprintf(L"원본 파일의 크기를 알 수 없습니다.\n");
::CloseHandle(hFileSource);
::CloseHandle(hFileTarget);
return 0;
}
for (LONGLONG i = 0; i < llFileSize.QuadPart; i += dwReadSize)
{
//64KB씩 읽어서 복사
::ZeroMemory(byBuffer, 65536);
bResult = ::ReadFile(hFileSource,
byBuffer, 65536, &dwReadSize, NULL);
if (!bResult)
{
wprintf(L"Failed to read source file [ERROR CODE: %d]\n",
::GetLastError());
break;
}
//파일의 끝이면 종료
else if (bResult && dwReadSize == 0)
break;
llTotalReadSize += dwReadSize;
wprintf(L"%I64d%%\n",
llTotalReadSize * 100 / llFileSize.QuadPart);
//64KB씩 저장
if (!::WriteFile(hFileTarget,
byBuffer, dwReadSize, &dwWriteSize, NULL) ||
dwReadSize != dwWriteSize)
{
wprintf(L"Failed to write target file [ERROR CODE: %d]\n",
::GetLastError());
break;
}
}
_putws(L"Complete!\n");
::CloseHandle(hFileSource);
::CloseHandle(hFileTarget);
return 0;
}
CreateFile 도 rwa+ 같은 권한을 부여하면서 생성할 수 있다. = 생성하면서 file open도 하는것이다.
또한 보안객체임으로 관리자 권한 같은 어떤 User의 권한도 설정을 하게 할 수 있다.
보안속성 없음 = 현 프로세스 것을 상속을 하겠다는 의미
Page가 보통 64Kb라 한번에 읽어들일 수 있는 I/O 단위이다.
제일 좋은 방법은 한번에 읽어들이는게 좋지만 메모리가 많이 들기 때문에 trade off 관계이다.
비동기 파일 입/출력 (Event 방식)
중첩된 비동기 파일의 입출력
file은 크기가 0 에서 시작해서 write가 일어나는 시점에 메모리크기가 증가하게 된다.
논리적으로는 stream의 양상을 띄고있어서 쓰는 만큼 늘어나게 되는 것이다.
비동기 입출력은 명세서 (offset ,실제 쓸 것들) 들을 os에게 한번에 요청을 하게 된다.
OS는 묶음단위로 요청을 처리하게 된다. (이를 중첩(Overlapped)된 것이라 표현함)
순서를 OS가 알아서 제일 빠르게 처리를 하게된다.

os에게 write요청을 여러개 보낸다음에 특정 Event를 생성하여 각각 기다리게 한다.
OVERLAPPED 공용체에 hEvent에 넣고 , Offset을 채워넣는다.
이후 위 공용체를 설정으로 삼아서 WriteFile을 시도한다.
GetLastError 에서 ERROR_IO_PENDING 은 os에서 실제로 I/O작업을 해야되기때문에 pending되는것이 정상이다.
os가 잘 접수 받았고 처리중이라는 의미이다.

event들을 기다린다. 비동기로 처리하는것의 의미가 좀 떨어지게 된다. 보통 다른스레드에서 wait하게 작성한다.
비동기 파일 입/출력 (Callback 방식)
Callback = 내가 만든함수이지만 운영체제가 호출하는 함수
하지만 요청한 스레드가 Alterable wait가 되어야 커널에서 넘겨받은 함수를 호출 할 수 있다.

main thread가 worker thread를 그냥 기다리고 있다.

파일에 쓸 overlapped 구조체를 생성하고 new 로 동적할당을 하게 된다. complete callback 에서 메모리 해제를 해줄 것이다.

worker thread에서는 쓰기를 시도하면서 FileIoComplete 라는 콜백 함수를 넘기게 된다.
SleepEx를 True로 호출하게 되면 현 스레드가 Alertable wait 상태로 들어가게 된다.

동적할당한 Overlapped 구조체의 메모리를 delete 로 해제한다.
IOCP = IO completion 함수
1. 파일에서 read
2. memory 에다 copy
3. overlap구조체 만들고 memory를 가르키게 만듬
4. os에게 요청함
File을 pointer 로 추상화 시키면 된다. 메모리 카피를 덜하게 된다한다..?
메모리 맵 파일
파일을 메모리로 추상화하면 memcpy 함수로 파일을 복사할 수 있게 된다.
CreateFileMapping으로 새로 생성하던지 OpenFIleMapping으로 열어서 handle을 통해서 접근을 하게 된다.
int main()
{
//1. 텍스트 파일을 생성한다.
HANDLE hFile = ::CreateFile(
TEXT("C:\\Test\\MYFILE.TXT"),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
//생성한 파일의 크기를 1024로 강제 설정한다.
::SetFilePointer(hFile, 1024, NULL, FILE_BEGIN);
//2.파일에 대한 매핑 객체를 생성한다.
HANDLE hMap = ::CreateFileMapping(
hFile, NULL, PAGE_READWRITE, 0, 1024, NULL);
if (hMap == NULL)
{
wprintf(L"Failed to create file mapping obj [ERROR CODE: %d]\n",
::GetLastError());
::CloseHandle(hFile);
return 0;
}
//3. 매핑 객체에 대한 접근 포인터를 얻음. (메모리로 추상화)
char *pszMemory = (char*)::MapViewOfFile(
hMap, FILE_MAP_ALL_ACCESS, 0, 0, 1024);
if (pszMemory == NULL)
{
::CloseHandle(hMap);
::CloseHandle(hFile);
return 0;
}
//4. 메모리에 데이터를 쓴다.
strcpy_s(pszMemory, 1024, "Hello, Memory Mapped File!");
//::FlushViewOfFile(pszMemory, 1024);
//5. 메모리 매핑을 해제하고 종료한다.
::UnmapViewOfFile(pszMemory);
::CloseHandle(hMap);
//::FlushFileBuffers(hFile);
::CloseHandle(hFile);
return 0;
}
MapViewOfFile을 통해서 hMap 맵핑 객체와 파일을 연결시켜주는 역할을 한다. return값은 포인터로 준다.
pointer -> Map -> File 이런식으로 구조가 연결 된다.
strcpy_s 를 통해 memory to file 입출력이 일어나게 된것이다. memory to memory 가 아니라서 속도가 느리다.
메모리 맵과 비동기 입/출력 조합
void CloseAll(char* pszMem, HANDLE hMap, HANDLE hSrc, HANDLE hDst)
{
if(pszMem != NULL)
::UnmapViewOfFile(pszMem);
if(hMap != NULL)
::CloseHandle(hMap);
if (hSrc != NULL)
::CloseHandle(hSrc);
if (hDst != NULL)
::CloseHandle(hDst);
}
int main()
{
::_wsetlocale(LC_ALL, L"korean");
HANDLE hFileSource = NULL, hFileTarget = NULL;
LARGE_INTEGER llFileSize = { 0 };
HANDLE hMap = NULL;
char* pszMemory = NULL;
//1. 원본 파일을 읽기모드로 연다.
hFileSource = ::CreateFile(TEXT("C:\\TEST\\Sleep Away.zip"),
GENERIC_READ, //읽기모드
FILE_SHARE_READ, //읽기모드 공유허용
NULL, //보안속성 없음.
OPEN_EXISTING, //존재하는 파일 열기
FILE_ATTRIBUTE_NORMAL,
NULL); //동기 I/O
if (hFileSource == INVALID_HANDLE_VALUE)
{
wprintf(L"Failed to open source file [ERROR CODE: %d]\n",
::GetLastError());
return 0;
}
if (!::GetFileSizeEx(hFileSource, &llFileSize))
{
wprintf(L"Failed to get source file size [ERROR CODE: %d]\n",
::GetLastError());
CloseAll(NULL, NULL, hFileSource, NULL);
return 0;
}
//2. 원본 파일에 대한 파일매핑 객체 생성
hMap = ::CreateFileMapping(hFileSource, NULL, PAGE_READONLY, 0,
(DWORD)llFileSize.QuadPart, NULL);
if (hMap == NULL)
{
wprintf(L"Failed to create file mapping obj [ERROR CODE: %d]\n",
::GetLastError());
CloseAll(NULL, NULL, hFileSource, NULL);
return 0;
}
//매핑 객체에 대한 접근 포인터를 얻음.
pszMemory = (char*)::MapViewOfFile(hMap, FILE_MAP_READ, 0, 0,
(DWORD)llFileSize.QuadPart);
if (pszMemory == NULL)
{
CloseAll(NULL, hMap, hFileSource, NULL);
return 0;
}
//3. 대상 파일을 비동기 쓰기모드 모드로 개방
::DeleteFile(TEXT("C:\\TEST\\Sleep Away - copy.zip"));
hFileTarget = ::CreateFile(TEXT("C:\\TEST\\Sleep Away - copy.zip"),
GENERIC_ALL, //쓰기모드
0, //공유안함.
NULL, //보안속성 없음.
CREATE_ALWAYS, //새파일 생성
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (hFileTarget == INVALID_HANDLE_VALUE)
{
wprintf(L"Failed to open target file [ERROR CODE: %d]\n",
::GetLastError());
CloseAll(pszMemory, hMap, hFileSource, NULL);
return 0;
}
//4. WriteFile() 함수를 이용해서 비동기 모드로 저장한다.
LPOVERLAPPED pOverLapped = (LPOVERLAPPED)malloc(sizeof(OVERLAPPED));
pOverLapped->OffsetHigh = 0;
pOverLapped->Offset = 0;
pOverLapped->hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
DWORD dwWritten = 0;
::WriteFile(hFileTarget, pszMemory, (DWORD)llFileSize.QuadPart,
&dwWritten, pOverLapped);
if (::GetLastError() != ERROR_IO_PENDING)
{
wprintf(L"WriteFile [ERROR CODE: %d]", ::GetLastError());
CloseAll(pszMemory, hMap, hFileSource, NULL);
::CloseHandle(pOverLapped->hEvent);
free(pOverLapped);
return 0;
}
//5. 비동기 처리가 완료 될때까지 대기
if(::WaitForSingleObject(pOverLapped->hEvent, INFINITE) ==
WAIT_OBJECT_0)
_putws(L"Complete!\n");
CloseAll(pszMemory, hMap, hFileSource, hFileTarget);
::CloseHandle(pOverLapped->hEvent);
free(pOverLapped);
return 0;
}
파일을 열면서 생성함 -> 해당 파일의 사이즈를 알아냄 -> 원본 파일에 대한 파일매핑 객체 생성 -> 매핑 객체에 대한 pointer를 얻어 pszMemory
Overlapped 구조체를 통해서 파일에 비동기로 쓸때의 configuration을 진행한다.
WriteFile을 할때 pszMemory pointer 에 대고 하게 된다.
이후 configuration에서 생성했던 Overlapped Event에 대해서 WriteFile이 끝날때까지 대기하게 된다.
typedef struct _COPY_DATA
{
LPVOID pMapView;
HANDLE hMap;
HANDLE hFileSource;
HANDLE hFileTarget;
} COPY_DATA;
VOID CALLBACK WriteFileIOCompletionRoutine(DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped)
{
_putws(L"WriteFileIOCompletionRoutine() - Begin");
if (lpOverlapped->hEvent != NULL)
{
//각종 핸들 닫기 및 메모리 해제
COPY_DATA* pCopyData = (COPY_DATA*)lpOverlapped->hEvent;
::UnmapViewOfFile(pCopyData->pMapView);
::CloseHandle(pCopyData->hMap);
::CloseHandle(pCopyData->hFileSource);
::CloseHandle(pCopyData->hFileTarget);
free(pCopyData);
_putws(L"WriteFileIOCompletionRoutine() - Release memory & handles");
}
//main() 함수에서 할당한 OVERLAPPED 구조체 메모리 해제
free(lpOverlapped);
_putws(L"WriteFileIOCompletionRoutine() - End");
}
void CloseAll(char* pszMem, HANDLE hMap, HANDLE hSrc, HANDLE hDst)
{
if (pszMem != NULL)
::UnmapViewOfFile(pszMem);
if (hMap != NULL)
::CloseHandle(hMap);
if (hSrc != NULL)
::CloseHandle(hSrc);
if (hDst != NULL)
::CloseHandle(hDst);
}
int main()
{
::_wsetlocale(LC_ALL, L"korean");
HANDLE hFileSource = NULL, hFileTarget = NULL;
LARGE_INTEGER llFileSize = { 0 };
HANDLE hMap = NULL;
char* pszMemory = NULL;
//1. 원본 파일을 읽기모드로 연다.
hFileSource = ::CreateFile(TEXT("C:\\TEST\\Sleep Away.zip"),
GENERIC_READ, //읽기모드
FILE_SHARE_READ, //읽기모드 공유허용
NULL, //보안속성 없음.
OPEN_EXISTING, //존재하는 파일 열기
FILE_ATTRIBUTE_NORMAL,
NULL); //동기 I/O
if (hFileSource == INVALID_HANDLE_VALUE)
{
wprintf(L"Failed to open source file [ERROR CODE: %d]\n",
::GetLastError());
return 0;
}
if (!::GetFileSizeEx(hFileSource, &llFileSize))
{
wprintf(L"Failed to get source file size [ERROR CODE: %d]\n",
::GetLastError());
CloseAll(NULL, NULL, hFileSource, NULL);
return 0;
}
//2. 원본 파일에 대한 파일매핑 객체 생성
hMap = ::CreateFileMapping(hFileSource, NULL, PAGE_READONLY, 0,
(DWORD)llFileSize.QuadPart, NULL);
if (hMap == NULL)
{
wprintf(L"Failed to create file mapping obj [ERROR CODE: %d]\n",
::GetLastError());
CloseAll(NULL, NULL, hFileSource, NULL);
return 0;
}
//매핑 객체에 대한 접근 포인터를 얻음.
pszMemory = (char*)::MapViewOfFile(hMap, FILE_MAP_READ, 0, 0,
(DWORD)llFileSize.QuadPart);
if (pszMemory == NULL)
{
CloseAll(NULL, hMap, hFileSource, NULL);
return 0;
}
//3. 대상 파일을 비동기 쓰기모드 모드로 개방
::DeleteFile(TEXT("C:\\TEST\\Sleep Away - copy.zip"));
hFileTarget = ::CreateFile(TEXT("C:\\TEST\\Sleep Away - copy.zip"),
GENERIC_ALL, //쓰기모드
0, //공유안함.
NULL, //보안속성 없음.
CREATE_ALWAYS, //새파일 생성
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (hFileTarget == INVALID_HANDLE_VALUE)
{
wprintf(L"Failed to open target file [ERROR CODE: %d]\n",
::GetLastError());
CloseAll(pszMemory, hMap, hFileSource, NULL);
return 0;
}
//4. WriteFileEx() 함수를 이용해서 비동기 모드로 저장한다.
LPOVERLAPPED pOverLapped = (LPOVERLAPPED)malloc(sizeof(OVERLAPPED));
pOverLapped->OffsetHigh = 0;
pOverLapped->Offset = 0;
//※ 지금 할당된 메모리는 나중에 완료 루틴에서 해제
COPY_DATA* pCopyData = (COPY_DATA*)malloc(sizeof(COPY_DATA));
pCopyData->pMapView = pszMemory;
pCopyData->hMap = hMap;
pCopyData->hFileSource = hFileSource;
pCopyData->hFileTarget = hFileTarget;
//※이벤트 핸들을 포인터로 활용
pOverLapped->hEvent = pCopyData;
if (!::WriteFileEx(hFileTarget, pszMemory, (DWORD)llFileSize.QuadPart,
pOverLapped, WriteFileIOCompletionRoutine))
{
wprintf(L"WriteFileEx [ERROR CODE: %d]", ::GetLastError());
CloseAll(pszMemory, hMap, hFileSource, hFileTarget);
return 0;
}
//5. 비동기 처리가 완료 될때까지 대기
for (; ::SleepEx(1, TRUE) != WAIT_IO_COMPLETION;);
_putws(L"main() - End\n");
return 0;
}
WriteFileEx 를 호출하여 비동기 callback을 처리할 함수인 WriteFileIOCompletionRoutine을 넣어준다.
callback에서 lpOverlapped가 포인터로 어떤 구조체를 가르키게 해서 좀 더 완료단계에 다양한 용도로 활용되게 할 수 있다.
IOCP 모델 개요
- Proactor 방식 고속 입/출력 모델
- 비동기 I/O 통지 (callback) 구조
- 사용자 요청에 대한 처리 스레드 풀을 OS가 직접관리
- 커널 영역에서 사용자 메모리 영역을 공유해 불필요한 메모리 복사 방지
- 커널에서 사용자 메모리 영역을 복사하는것이 아니라 락을 걸어버려서 자기만 사용함
- 입/출력 처리시 관련 메모리에 대해 페이지 단위 Lock/Unlock
- Callback 함수는 사용자 모드 함수이나 커널에서 호출하며 이 때마다 스위칭

WriteFileEx를 호출하면 IOCP Queue에 쌓이게 되어 차례대로 처리하게 된다.
'Operating System > Windows 시스템 프로그래밍 - 기본' 카테고리의 다른 글
프로세스 관리 (0) | 2023.07.18 |
---|---|
메모리 시스템 (0) | 2023.07.15 |
스레드 동기화 (0) | 2023.07.07 |
스레드 생성 및 제어 (0) | 2023.07.05 |
기본 이론 (0) | 2023.07.03 |