System2008. 11. 25. 15:40
인터락 함수는 크리티컬 섹션보다 더 사용하기 쉽습니다 --;


InterlockedIncrement 함수는 전달된 인자의 값을 1 더해주는 함수입니다.
1 줄이는 함수는 InterlockedDecrement 구요.
  1. LONG InterlockedIncrement(  
  2.   LONG volatile* Addend  
  3. );  
  4.  
  5. LONG InterlockedDecrement(  
  6.   LONG volatile* Addend  
  7. );  

volatile 이라는 키워드가 있죠.
이것은 "코드 최적화를 수행하지 말 것" "메모리에 직접 연산하라(캐쉬하지 말것)" 효과를 가지고 있습니다.

이 함수들 외에도 64비트 기반의 인터락함수, InterlockedIncrement64, InterlockedDecrement64 같은 함수들도 있구요.
해당 포인터의 값을 변경하는 InterlockedExchangePointer 함수도 있습니다.

Posted by skensita
System2008. 11. 25. 15:38

크리티컬 섹션을 사용하는 방법은 의외로 간단합니다.

1. CRITICAL_SECTION   hCriticalSection;
크리티컬 섹션을 선언합니다.
여기서는 이름을 hCriticalSection 이라고 했습니다.

2. InitializeCriticalSection(&hCriticalSection);
크리티컬 섹션을 초기화합니다.

3. EnterCriticalSection (&hCriticalSection);
임계영역에 진입직전에 호출합니다.

4. LeaveCriticalSection (&hCriticalSection);
임계영역에서 나오자마자 호출합니다.

5. DeleteCriticalSection(&hCriticalSection);
더이상 크리티컬 섹션이 필요없거나, 프로그램 종료시에 크리티컬 섹션을 해제합니다.

  1. /*  
  2. CriticalSectionSync.cpp  
  3. 프로그램 설명: 생성 가능한 쓰레드의 개수 측정.  
  4. */ 
  5.  
  6. #include <stdio.h>  
  7. #include <windows.h>  
  8. #include <process.h>  
  9. #include <tchar.h>  
  10.  
  11. #define NUM_OF_GATE     6  
  12.  
  13. LONG gTotalCount = 0;  
  14.  
  15. CRITICAL_SECTION   hCriticalSection;  
  16.  
  17.  
  18. void IncreaseCount()  
  19. {  
  20.     EnterCriticalSection (&hCriticalSection);  
  21.     gTotalCount++;  
  22.     LeaveCriticalSection (&hCriticalSection);  
  23. }  
  24.  
  25.  
  26. unsigned int WINAPI ThreadProc( LPVOID lpParam )   
  27. {   
  28.     for(DWORD i=0; i<1000; i++)  
  29.     {  
  30.         IncreaseCount();  
  31.     }  
  32.  
  33.     return 0;  
  34. }   
  35.  
  36.  
  37. int _tmain(int argc, TCHAR* argv[])  
  38. {  
  39.     DWORD dwThreadId[NUM_OF_GATE];  
  40.     HANDLE hThread[NUM_OF_GATE];  
  41.  
  42.     InitializeCriticalSection(&hCriticalSection);  
  43.  
  44.     for(DWORD i=0; i<NUM_OF_GATE; i++)  
  45.     {  
  46.         hThread[i] = (HANDLE)  
  47.             _beginthreadex (   
  48.             NULL,  
  49.             0,                        
  50.             ThreadProc,                 
  51.             NULL,                      
  52.             CREATE_SUSPENDED,            
  53.             (unsigned *)&dwThreadId[i]     
  54.         );  
  55.  
  56.         if(hThread[i] == NULL)  
  57.         {  
  58.             _tprintf(_T("Thread creation fault! \n"));  
  59.             return -1;  
  60.         }  
  61.     }  
  62.  
  63.     for(DWORD i=0; i<NUM_OF_GATE; i++)  
  64.     {  
  65.         ResumeThread(hThread[i]);  
  66.     }  
  67.  
  68.  
  69.     WaitForMultipleObjects(NUM_OF_GATE, hThread, TRUE, INFINITE);  
  70.  
  71.     _tprintf(_T("total count: %d \n"), gTotalCount);  
  72.  
  73.     for(DWORD i=0; i<NUM_OF_GATE; i++)  
  74.     {  
  75.         CloseHandle(hThread[i]);  
  76.     }  
  77.  
  78.     DeleteCriticalSection(&hCriticalSection);  
  79.  
  80.     return 0;  
  81. }  
쓰레드 6개를 생성해서, 각각의 쓰레드가 gTotalCount 을 1000씩 더하는 예제입니다.

 
Posted by skensita
System2008. 11. 25. 15:37

기본적인 원리는 크리티컬 섹션과 같습니다만, 크리티컬 섹션이 유저 모드 동기화 방법인 반면...
뮤텍스는 커널 모드 동기화 방법입니다.
또, 뮤텍스는 서로 다른 프로세스의 쓰레드 사이에도 동기화를 수행할 수 있습니다.



WaitForSingleObject 로 뮤텍스를 획득하고, ReleaseMutex 로 뮤텍스를 반납합니다.
뮤텍스 사용시의 흐름도는 다음과 같습니다.

1. HANDLE hMutex;
핸들을 하나 선언합니다.

2. hMutex = CreateMutex (
  NULL,     // 디폴트 보안관리자.
  FALSE,    // 누구나 소유 할 수 있는 상태로 생성.
  NULL      // numaned mutex
  );
뮤텍스를 생성합니다.

3. WaitForSingleObject(hMutex, INFINITE);
임계영역 진입직전에 뮤텍스를 획득합니다.

4. ReleaseMutex(hMutex);
임계영역에서 나오자마자 뮤텍스를 반환합니다.

5. CloseHandle(hMutex);
더이상 뮤텍스를 사용할 경우가 없거나, 프로그램 종료시에 뮤텍스를 해제합니다.

아래는 뮤텍스를 사용한 예제입니다.

  1. /*  
  2.     CriticalSectionSyncMutex.cpp  
  3.     프로그램 설명: 크리티컬 섹션과 뮤텍스 비교  
  4. */ 
  5.  
  6. #include <stdio.h>  
  7. #include <windows.h>  
  8. #include <process.h>  
  9. #include <tchar.h>  
  10.  
  11. #define NUM_OF_GATE     6  
  12.  
  13. LONG gTotalCount = 0;  
  14.  
  15. // CRITICAL_SECTION   gCriticalSection;  
  16. HANDLE hMutex;  
  17.  
  18. void IncreaseCount()  
  19. {  
  20. //  EnterCriticalSection (&gCriticalSection);  
  21.     WaitForSingleObject(hMutex, INFINITE);  
  22.  
  23.     gTotalCount++;  
  24.  
  25. //  LeaveCriticalSection (&gCriticalSection);  
  26.     ReleaseMutex(hMutex);  
  27. }  
  28.  
  29.  
  30. unsigned int WINAPI ThreadProc( LPVOID lpParam )   
  31. {   
  32.     for(DWORD i=0; i<1000; i++)  
  33.     {  
  34.         IncreaseCount();  
  35.     }  
  36.  
  37.     return 0;  
  38. }   
  39.  
  40.  
  41. int _tmain(int argc, TCHAR* argv[])  
  42. {  
  43.     DWORD dwThreadIDs[NUM_OF_GATE];  
  44.     HANDLE hThreads[NUM_OF_GATE];  
  45.  
  46. //  InitializeCriticalSection(&gCriticalSection);  
  47.     hMutex = CreateMutex (  
  48.                    NULL,     // 디폴트 보안관리자.  
  49.                    FALSE,    // 누구나 소유 할 수 있는 상태로 생성.  
  50.                    NULL      // numaned mutex  
  51.              );  
  52.  
  53.     if (hMutex == NULL)   
  54.     {  
  55.         _tprintf(_T("CreateMutex error: %d\n"), GetLastError());  
  56.     }  
  57.  
  58.     for(DWORD i=0; i<NUM_OF_GATE; i++)  
  59.     {  
  60.         hThreads[i] = (HANDLE)  
  61.             _beginthreadex (   
  62.                 NULL,  
  63.                 0,                        
  64.                 ThreadProc,                 
  65.                 NULL,                      
  66.                 CREATE_SUSPENDED,            
  67.                 (unsigned *)&dwThreadIDs[i]     
  68.             );  
  69.  
  70.         if(hThreads[i] == NULL)  
  71.         {  
  72.             _tprintf(_T("Thread creation fault! \n"));  
  73.             return -1;  
  74.         }  
  75.     }  
  76.  
  77.     for(DWORD i=0; i<NUM_OF_GATE; i++)  
  78.     {  
  79.         ResumeThread(hThreads[i]);  
  80.     }  
  81.  
  82.  
  83.     WaitForMultipleObjects(NUM_OF_GATE, hThreads, TRUE, INFINITE);  
  84.  
  85.     _tprintf(_T("total count: %d \n"), gTotalCount);  
  86.  
  87.     for(DWORD i=0; i<NUM_OF_GATE; i++)  
  88.     {  
  89.         CloseHandle(hThreads[i]);  
  90.     }  
  91.     
  92. //  DeleteCriticalSection(&gCriticalSection);  
  93.     CloseHandle(hMutex);  
  94.  
  95.     return 0;  
  96. }  
 
Posted by skensita
System2008. 11. 25. 15:35


세마포어는 뮤텍스에서 확장된 개념인데, 쉽게 설명하면...
뮤텍스에 접근가능한 쓰레드의 최대개수를 설정할 수 있습니다.

그 밖에 또 다른 차이점 한가지는 뮤텍스는 자신을 생성한 프로세스에 종속적이나,
세마포어는 프로세스에 자유롭게 호출할 수 있습니다.
-> 이 부분은 다음 포스팅에 쉽게 설명하겠습니다.

1. lpSemaphoreAttributes
지겹게 등장하는 커널 오브젝트 핸들에 대한 보안 속성이져.

2. lInitialCount
세마포어의 초기 개수를 설정합니다

3. lMaximumCount
세마포어가 가질 수 있는 최대 개수를 설정합니다.
보통 IInitialCount 와 같거나 큰 값을 넣습니다.

4. lpName
세마포어에 이름을 지정합니다.
다음 포스팅에 설명할 부분인데, 이렇게 이름을 지정하면
서로다른 프로세스 사이에도 이 세마포어에 접근할 수 있습니다.


세파포어 카운트가 10이라면 최대 10개의 쓰레드가 임계영역에 접근할 수 있게됩니다.
세마포어 획득은 WaitForSingleObject 를 사용하고, 반환은 ReleaseSemaphore 를 사용합니다.

아래는 세마포어를 아용한 명동교자라는 예제 ;;;
 
  1. /*  
  2.     MyongDongKyoJa.cpp  
  3.     프로그램 설명: 카운트 세마포어에 대한 이해  
  4.     시뮬레이션 제한 요소:  
  5.         1. 테이블이 총 10개이고, 동시에 총 10분의 손님만 받을 수 있다고 가정한다.  
  6.         2. 오늘 점심시간에 식사하러 오실 예상되는 손님의 수는 총 50분이다.  
  7.         3. 각 손님들께서 식사 하시는 시간은 대략 10분에서 30분 사이이다.  
  8. */ 
  9.  
  10.  
  11. #include <stdio.h>  
  12. #include <stdlib.h>  
  13. #include <time.h>  
  14. #include <windows.h>  
  15. #include <process.h>  
  16. #include <tchar.h>  
  17.  
  18. #define NUM_OF_CUSTOMER 50  
  19. #define RANGE_MIN 10  
  20. #define RANGE_MAX (30 - RANGE_MIN)  
  21. #define TABLE_CNT 10  
  22.  
  23.  
  24. HANDLE hSemaphore;  
  25. DWORD randTimeArr[50];  
  26.  
  27. void TakeMeal(DWORD time)  
  28. {  
  29.     WaitForSingleObject(hSemaphore, INFINITE);  
  30.     _tprintf( _T("Enter Customer %d~ \n"), GetCurrentThreadId());  
  31.  
  32.     _tprintf(_T("Customer %d having launch~ \n"), GetCurrentThreadId());  
  33.     Sleep(1000 * time); // 식사중인 상태를 시뮬레이션 하는 함수.  
  34.  
  35.     ReleaseSemaphore(hSemaphore, 1, NULL);  
  36.     _tprintf( _T("Out Customer %d~ \n\n"), GetCurrentThreadId());  
  37. }  
  38.  
  39.  
  40. unsigned int WINAPI ThreadProc( LPVOID lpParam )   
  41. {   
  42.     TakeMeal((DWORD)lpParam);  
  43.     return 0;  
  44. }  
  45.  
  46.  
  47. int _tmain(int argc, TCHAR* argv[])  
  48. {  
  49.     DWORD dwThreadIDs[NUM_OF_CUSTOMER];  
  50.     HANDLE hThreads[NUM_OF_CUSTOMER];  
  51.      
  52.     srand( (unsigned)time( NULL ) );    // random function seed 설정  
  53.  
  54.  
  55.     // 쓰레드에게 전달할 random 값 총 50개 생성.  
  56.     for(int i=0; i<NUM_OF_CUSTOMER ;i++)  
  57.     {  
  58.         randTimeArr[i] = (DWORD) (  
  59.                 ((double)rand() / (double)RAND_MAX) * RANGE_MAX + RANGE_MIN  
  60.             );  
  61.     }  
  62.  
  63.     // 세마포어 생성.  
  64.     hSemaphore = CreateSemaphore (  
  65.                    NULL,    // 디폴트 보안관리자.  
  66.                    TABLE_CNT,      // 세마포어 초기 값.  
  67.                    TABLE_CNT,      // 세마포어 최대 값.  
  68.                    NULL     // unnamed 세마포어 구성.  
  69.                  );  
  70.     if (hSemaphore == NULL)   
  71.     {  
  72.         _tprintf(_T("CreateSemaphore error: %d\n"), GetLastError());  
  73.     }  
  74.  
  75.  
  76.     // Customer를 의미하는 쓰레드 생성.  
  77.     for(int i=0; i<NUM_OF_CUSTOMER; i++)  
  78.     {  
  79.         hThreads[i] = (HANDLE)  
  80.             _beginthreadex (   
  81.                 NULL,  
  82.                 0,                        
  83.                 ThreadProc,                 
  84.                 (void*)randTimeArr[i],                      
  85.                 CREATE_SUSPENDED,            
  86.                 (unsigned *)&dwThreadIDs[i]     
  87.             );  
  88.  
  89.         if(hThreads[i] == NULL)  
  90.         {  
  91.             _tprintf(_T("Thread creation fault! \n"));  
  92.             return -1;  
  93.         }  
  94.     }  
  95.  
  96.     for(int i=0; i<NUM_OF_CUSTOMER; i++)  
  97.     {  
  98.         ResumeThread(hThreads[i]);  
  99.     }  
  100.  
  101.     WaitForMultipleObjects(NUM_OF_CUSTOMER, hThreads, TRUE, INFINITE);  
  102.  
  103.     _tprintf(_T("----END-----------\n"));  
  104.  
  105.     for(int i=0; i<NUM_OF_CUSTOMER; i++)  
  106.     {  
  107.         CloseHandle(hThreads[i]);  
  108.     }  
  109.       
  110.     CloseHandle(hSemaphore);  
  111.  
  112.     return 0;  
  113. }  
Posted by skensita
System2008. 11. 25. 14:40

Linux와 Windows에서의 고성능 프로그래밍 기술




난이도 : 초급

Edward G. Bradford 박사, 수석 Programmer, IBM 

2001 년 10 월 01 일

Ed는 OS 프로그래밍 인터페이스에 대한 연구를 시작한다. 그 첫 번째 대상은 pipe 이다. 연구 대상 OS에 최근에 배포된 Windows XP가 추가되었다. 이 글에서 Ed는 Windows 2000 Advanced Server (Service Pack 2 설치), Linux (Red Hat 7.1), Windows XP professional에서 pipe를 실행한다.

두 개의 Windows 버전을 실험해야 하기 때문에 앞으로 사용하게 될 용어에 대해 설명을 하겠다. Windows 2000과 Windows XP를 구별 할 필요가 없을 때에는 "Windows"라고 하겠다. 구별이 필요할 때에는, "Windows 2000" 또는 "Windows XP"라고 각각 명기하도록 하겠다.

Pipe

pipe는 Windows와 Linux (UNIX)에서 실행하는 프로세스간 통신 메커니즘이다. pipe는 원래 UNIX의 Bell Laboratories version에 나타났고 후에 모든 UNIX와 Linux에 쓰이게 되었다. pipe는 정상적인 IO 인터페이스를 통해 액세스 된 바이트 스트림이다. UNIX와 Linux 같은 경우, IO 호출은 read()write()이다. Windows의 경우, API는 ReadFile()WriteFile()이다. 양방향 (bi-directional) IO를 지원한다는 점에서 Windows pipe는 Linux pipe와는 다르다. Linux pipe는 양방향 IO를 수행하기 위해 두 개의 파일 식별자(file descriptor)를 리턴한다.






Windows pipe

Windows pipe는 Linux pipe 보다 더욱 복잡하다. Windows는 named와 unnamed pipe를 지원한다. unnamed는 인터페이스가 이름을 밝히지 않는 곳의 단순한 named pipe 이다. Windows는 pipe 에 비동기식 IO를 지원한다. 싱글 쓰레드가 pipe로의 IO 호출을 막지않는다. 다른 IO 인터페이스틑 비동기식 IO 기능을 사용할 때 필요하다. Windows pipe는 byte type과 message type이 있다. byte-type pipe는 UNIX pipe와 유사하고 byte 스트림을 지원한다.이 글에서는 message-type pipe는 연구 대상에서 제외할 것이다. Windows pipe는 CreateNamedPipe() API로 만들어진다. 일단 한번 만들어지면, OpenFile() API는 새로 만들어진 named pipe의 다른 끝에 액세스 하는데 사용된다. pipe 이름은 "flat name space"에 있다. 예를 들어, \\.\pipe\anyname는 Windows named pipe에 대한 공식 이름이 된다. C 또는 C++ 에서 이름은 다음과 같이 표현된다:

char *pipeAdult = "\\\\.\\pipe\\anyname";

anyname 부분만 지정될 수 있다. Windows pipe를 사용하기 위해서, 이것은 하나의 API에 의해 만들어져야 한다. 그리고 또 다른 API에 의해 열려야 한다. 다음 예제 코드는 이것이 어떻게 수행되는지 보여준다.


Windows named pipe
	//
	// Create named pipe in Windows
	// nbytes -- block size from command line arguments.
	//
	int mult = 1;
	int x;
	x = mult*nbytes + 24;
	handleA = CreateNamedPipe(pipeAdult,
				PIPE_ACCESS_DUPLEX,
				PIPE_TYPE_BYTE,
				2,              // two connections
				x,              // input buffer size
				x,              // output buffer size
				INFINITE,       // timeout
				NULL);          // security
	if(handleA == INVALID_HANDLE_VALUE) {
		printf("CreateNamedPipe() FAILED: err=%d
", GetLastError());
		return 1;
	}
	handleB = CreateFile(pipeAdult,
				GENERIC_READ|GENERIC_WRITE,
				FILE_SHARE_READ|FILE_SHARE_WRITE,
				NULL,
				OPEN_EXISTING,
				FILE_ATTRIBUTE_NORMAL,
				NULL);
	if(handleB == INVALID_HANDLE_VALUE) {
		printf("CreateFile() FAILED: err=%d
", GetLastError());
		return 1;
	}


첫 번째 실행 라인에서 숫자 24는 시험용으로 결정되었다. Platform SDK에서 이것에 관한 어떤 언급도 찾을 수 없었다. 이것이 존재하지 않으면 프로그램은 작동하지 않는다. 분명한 것은 pipe는 쓰기에 24 바이트 헤더를 필요로 한다.






Linux pipe

Linux pipe는 작성하고 사용하기가 훨씬 간단하다. 파라미터도 더 적다. Windows와 같은 pipe 생성 태스크를 수행하기 위해서는, Linux와 UNIX는 다음과 같이 한다:


Linux named pipe 만들기
	int fd1[2];
	if(pipe(fd1)) {
		printf("pipe() FAILED: errno=%d
",errno);
		return 1;
	}

Linux pipe는 블록킹(blocking)하기 전에 쓰기 사이즈에 대한 제한이 있다. 각 파이프에 할당된 커널 레벨 버퍼는 정확히 4096 바이트이다. reader가 pipe를 비우면 4K 이상 쓰기는 막힌다. 실제로 이것은 많은 제한이 아니다. 왜냐하면 읽기와 쓰기는 다른 쓰레드에서 수행되기 때문이다.






단일 쓰레디드 프로세스의 pipe 속도

나는 OS pipe 코드가 얼마나 빠르게 실행되는지 시험하기 위해서 프로그램을 작성했다. (pipespeed2.cpp). 이것은 pipe를 만들고 싱글 쓰레드 안에 있는 모든 데이터를 쓰고 읽는다. Linux는 단지 4K 쓰기를 지원하기 때문에 writer가 막기 전에 테스트는 4K 블록 사이즈에서 멈춘다. 숫자를 만들어냈던 bash 쉘 스크립트는 간단했다:


테스트 결과를 나타내는 bash 쉘 파일
    list="1 2 3 4 6 8 10 12 14 16 20 24 28 32 36 40 44 48 52 56 60 64 72 80 88"
    list="$list 96 104 112 120 128 144 160 176 192 208 224 240 256 288 320 352"
    list="$list 384 416 448 480 512 576 640 704 768 832 896 960 1024 1280 1536"
    list="$list 1792 2048 2560 3k 3584 4k"
    uname -s -r
    for bytes in $list
    do
        case $bytes in
        ????|?k ) count=100k;;
        ?????|??k) count=100k;;
        ?|??|??? ) count=500k;;
        esac
        pipespeed2 $count $bytes
    done

각 실행의 아웃풋은 파일에 저장되고 Microsoft Excel로 쉽게 임포트된 텍스트 파일을 만들기 위해 텍스트 에디터로 편집된다.

프로그램 컴파일

이 글의 프로그램들은 다음의 도구를 이용하여 컴파일 된다:

  • Linux: gcc -O2 pipespeed2.cpp -o pipespeed2
  • Windows: cl -O2 pipespeed2.cpp

그리고

  • Linux: gcc -O2 pipespeed2t.cpp -lpthread -o pipespeed2t
  • Windows: cl -O2 pipespeed2t.cpp

그림 1은 Linux 2.4.2 커널 (Red Hat 7.1) 결과이다.


 
사용자 삽입 이미지

그림 2는 Windows 2000 Advanced Server에서 실행하기 위해 컴파일 된 같은 프로그램이다. 중요하지 않은 서비스는 작동하지 않는다.


 
사용자 삽입 이미지

그림 3은 Windows XP의 결과이다.


 
사용자 삽입 이미지

각각의 테스트 실행은 세개의 시리즈로 구성되어있다. 그래픽에서, Linux pipe는 Windows 2000 named pipe보다 훨씬 빠르다. Windows XP named pipe는 Windows 2000 named pipe보다 훨씬 느리다. Windows XP Professional는 평가판(evaluation" version) 이다.






쓰레디드 프로세스의 pipe 속도

Windows 2000 그래프는 블록 사이즈가 4K 이상으로 증가한다면 더욱 향상될수 있다는 것을 보여준다. 4K보다 큰 블록 사이즈를 테스트 하기 위해서 강화된 버전의 pipespeed2.cpp 프로그램이 작성되었다. 새로운 프로그램은 두 번째 쓰레드를 만든다. 첫 번째 쓰레드는 데이터를 작성하고 두 번째 쓰레드는 데이터를 읽는다. 첫 번째 프로그램의 목적은 콘텍스트 변환 오버헤드 없이 pipe 지원의 코드 경로와 관련된 오버헤드를 이해하는 것이다.

pipespeed2.cpp의 강화 버전은 pipespeed2t.cpp라고 한다. 두 개의 쓰레드는 모든 데이터를 전송하기 위해 앞 뒤로 스위치를 context 할 것이다. 따라서 두 번째 프로그램은 첫 번째에서 빠진 콘텍스트 변환에 대한 추가 오버헤드를 가지고 있다. 모든 데이터가 전송되고 두 번째 쓰레드가 적절히 종료된 후에 타이밍은 멈춘다.

그림 4, 5, 6은 Linux, Windows 2000, Windows XP를 각각 보여준다. Windows XP는 named pipe 기능에서 심각한 퍼포먼스 저하라는 결과를 나타냈다. 반면 Linux는 Windows 2000 보다 훨씬 나은 성능을 보여주었고, Windows 2000은 Windows XP보다 나았다.

그림 4 는 Linux 2.4.2 커널에서 실행한 pipespeed2t.cpp의 쓰레디드 버전의 결과이다. 피크 (peak) IO 속도는 약 700 MB/sec 이다.


 
사용자 삽입 이미지

그림 5는 Windows 2000의 쓰레디드 결과이다. 피크(peak) IO 속도는 500 MB/sec에 가깝다.


 
사용자 삽입 이미지

두 개의 그래프 모두 같은 모습이지만 Linux는 매우 큰 블록 사이즈에 대해 100 MB/sec 속도로 안정된 상태에 도달했다. Windows 2000도 큰 블록 사이즈에 안정된 상태에 도달했지만, 겨우 80 MB/sec 이다.

그림 6은 Windows XP Professional (평가판)의 쓰레디드 결과이다. 피크 IO 속도는 120 MB/sec 이다.


 
사용자 삽입 이미지

Windows XP는 블록 사이즈에서 실망스러운 결과를 나타낸다. 아마도 Microsoft 전문가라면 discussion forum 에 프로그램과 Windows XP named pipe를 향상시킬 수 있는 유용한 정보를 제공하리라 믿는다.

간단한 프로그램을 통해 얻어진 정보는 프로그램 디자이너, 소프트웨어 아키텍트, 시스템 관리자에게 OS의 기능에 대해 정보를 제공할 수 있다. 간단함만이 "현실적인" 시나리오에 대한 끝없는 논쟁을 없애는 방법이다. "간단함"은 사람들이 프로그램을 작성하는 방식을 말하는 것이다. 이번 경우 어떤 플랫폼의 퍼포먼스를 향상시키기 위해 특별한 어떤 것도 수행하지 않았다.

Windows XP의 비참한 퍼포먼스는 당혹스럽다. 데이터 전송에 대한 보다 나은 솔루션으로 소켓을 설명할 수 있다. 다음 칼럼을 통해 다루겠다.

Linux 또한 named pipe를 지원한다.나는 Linux에 named pipe를 사용한 다른 프로그램을 작성했다. Linux의 named/unnamed pipe 결과는 구별이 되지 않는다.

첫 번째 테스트는 Linux 와는 다르게 4K 버퍼 사이즈에서 멈췄다. Windows 옹호자들은 Windows named pipe와 관련하여 임의의 버퍼 사이즈가 유리하다고 제안한다. Windows named pipe 버퍼의 임의 사이즈를 나타내기 위해서 임의의 큰 블록 사이즈로 단일 쓰레디드 프로그램을 실행할 수 있었다. Windows에서 pipespeed2.cpp으로 수행하고 256 MB 버퍼 사이즈를 지정했다. ReadFile()이 만들어지기 전에 버퍼 사이즈를 부풀림으로서 Windows는 256 MB의 데이터를 보유하게 되었다. 시스템은 느려지고 실행이 완료될 때까지 기다리지 않았다. 내부 버퍼를 늘이고 줄이는 데에는 페이지 할당(allocation)과 해제(deallocation)이 필요하다.






결론

Windows 와 Linux에서 pipe를 사용할 때 좋은 프로그래밍 예제로서 두 개의 프로그램을 작성했다. 첫 번째 프로그램은 pipespeed2.cpp 로, 데이터를 OS로 전달하기 위해 단일 쓰레드를 사용하여 pipe 코드 경로의 퍼포먼스를 보여주었다. 두 번째 프로그램인 pipespeed2t.cpp 는 임의의 대량 데이터를 전송하기 위해 두 개의 쓰레드를 사용했다. 두 번째 프로그램이 좀 더 현실적이다.

Linux pipe가 Windows 2000 named pipe보다 훨씬 빠르고, Windows 2000 named pipe는 Windows XP named pipe보다 훨씬 빠르다는 것을 실험을 통해 알 수 있었다.



참고자료



필자소개

Edward Bradford 박사는 현재 IBM Software Group을 위한 Microsoft Premier Support를 관리하고 있으며, Linux 및 Windows 2000 소프트웨어 개발자를 위한 주간 뉴스레터를 담당하고 있다.


출처 : IBM
Posted by skensita
System2008. 11. 19. 11:33
1. Text (텍스트영역)

program코드 (cpu에 의해 수행되는 기계어 명령어들이 모여있는 곳)

 

2. Data(데이터영역)

전역변수와 정적(static)변수가 할당된 곳. 프로그램 시작과 동시에 할당되고 프로그램이 종료되어야 메모리에서 소멸된다.

(1) initialized data segment(초기화된 데이터영역) : initialized variables (초기화된 데이터들)

(2) uninitialized data segment(비초기화된 데이터영역) : uninitialized variables (비초기화된 데이터들) – BBS(block started by symbol)이라고도 함

 

3. Stack(스택영역)

automatically allocated variables(local variables) and other stack frame entries

지역변수와 매개변수가 저장되는 곳. 이 영역에 할당된 변수는 함수 호출이 끝나면 사라진다.

-컴파일 타임 크기 결정

 

4. Heap(힙영역)

dynamically allocated variables

동적 메모리 할당하는 곳. 프로그래머가 할당 및 해제를 해주어야 한다.

-런타임 크기 결정


구조도

Posted by skensita
System2008. 11. 19. 11:28

페이징을 위해 먼저 A20 게이트의 설명

- A20게이트를 0으로 클리어 하면 홀수 MB 메모리가 짝수 MB와 2진수 값이 같아진다 -> A20을 켜지 않으면 홀수 MB를 사용할 수 없다.

- A20게이트가 0으로 되어있다는것은 메모리 값의 2진수 표현이 1FFFFF라면 1 1111 1111 1111 1111 1111   이 0 1111 1111 1111 1111 1111되는 것이다. 이것은 과거 8086이 1MB의 주소를 사용한, 20개의 어드레스 라인을 가지고 있었기 때문이다.(즉 0부터 19번 비트까지) CPU가 발달하면서 08286이 24개의 어드레스 라인을 사용했지만 과거 프로그램과의 호환을 위해(1MB가 넘으면 다시 0부터 시작하는 메 모리 방식) 20번 비트를 키보드 컨트롤러의 특정 비트와 AND로 물려놓아 이 키보드 컨트롤러가 1로 셋트되어야만 20번 비트가 제대로 의미를 가질 수 있게 해놓았다.

- 따라서 주소지정을 올바르게 (홀수MB와 짝수 MB를 모두 사용하려면) 사용하려면 A20게이트를 열어놓아야 한다.(아마 요즘은 전혀 필요 없을듯)

예) 1MB는  16진수 -> 0x100000    2진수 -> 1 0000 0000 0000 0000 0000  A20 게이트가 켜져있지 않다면 이 값은 0
2MB는  16진수 -> 0x200000    2진수 -> 10 0000 0000 0000 0000 0000  A20게이트가 켜져있지 않아도 값은 같다.
3MB는 16진수 -> 0X300000    2진수 -> 11 0000 0000 0000 0000 0000   A20게이트가 켜져있지 않다면 이 값은 2MB와 같다.

○ 페이징은 논리주소 -> 선형주소 -> 물리주소의 과정을 거친다.

○ 32비트 시스템에서는 주소를 32비트로 표현한다. 이 32비트 주소는 페이징의 선형주소에 의해 10 비트 10 비트 12비트로 분할되어 각각 의미를 가진다.    

  페이지디렉토리 엔트리 번호(10비트)

  페이지 테이블 엔트리 번호(10비트)

      물리페이지의 offset(12비트)

○ 보호모드를 지원하는 CPU는 CR3 레지스터를 가지는데 이것이 페이지 '디렉토리'의 시작주소를 가진다.

○ 페이지 '디렉토리' 엔트리는...각 페이지 '테이블'의 시작주소의 포인터(주소)를 가진다
    '디렉토리'는 1024개(2의 10승->10비트)의 엔트리를 가지고 있다.
    마찬가지로 페이지 '테이블'은 각 물리페이지 시작주소를 포인터로 가지고 역시 1024개의 엔트리를 가진다.

○ 페이지'테이블'의 엔트리와(물리페이지의 시작주소) 12비트 offset을 합치면 실제 물리주소가 나오게 된다.
    물리페이지의 offset만 12비트인 이유는 각 페이지는 4KB로 모두 잘라져있는데(페이징이선 크기를 모두 4KB로 자른다. 페이지 디렉토리도 4KB 이고 페이지 테이블도 4KB이며 각 물리페이지도 4KB로 램상에 차지한다.) 이 4KB가 4098byte 이고 이것은 2의 12승이다. 따라서 offset이 12비트면 4KB의 영역을 바이트 단위로 접근할 수 있는것이다.

    (역으로 생각해보면 모든 페이지가 4KB단위이므로 4098byte -> 2^12승(byte) -> 2^12승은 16진수로 3칸(?)을 차지한다. 즉 모든 물리페이지는 항상 하위 3자리가 000으로 끝난다. 0x00002000, 0x00008000 등등. 이것이 테이블 엔트리나 디렉토리 엔트리에서 주소지정을 할때 20비트만 사용하는 이유이다.뒷 주소가 0xffffff000 이므로 뒤의 세자리(즉 2의 12승)는 항상 0이라고 생각하고 나머지 5자리 즉 20비트만 필요한것이다. )

○ 결과적으로 디렉토리와 테이블 엔트리의 곱 1024*1024 = 1MB 이고 각각 4KB의 페이지를 가지고 있으므로 1MB*4KB = 4GB 즉 메모리 지정을 4GB까지 할 수 있다.

○ 프로그램에서 요구되는 32bit주소를 찾아가는 순서(페이지 디렉토리를 가리키는 첫번째 10비트를 A, 페이지 테이블을 가리키는 두번째 10비트를 B, 물리페이지 offset의 12비트를 C라 하면)
    - CR3의 주소를 참조해 페이지 디렉토리의 시작주소를 찾아간다.
    - 요구된 주소의 'A'와 디렉토리의 시작주소를 더하면 이 값이 1024개의 테이블중 하나를 가리키는 주소가 된다.(따라서 이 최상위 10비트는 디렉토리의 'offset'이다)
    - 테이블의 시작주소를 얻었으므로 이 주소와 'B'를더한다. 이 더한값은 물리페이지의 시작주소가 된다.(물리페이지는 4KB)
    - 이 물리페이지의 시작주소와 'C'를 결합하면 4KB 페이지 안의 실제 메모리 물리 주소가 나오게 된다.

○ 페이지 디렉토리는 각 프로세스마다 존재하고 페이지 디렉토리의 시작주소는 mm_struct 구조체의 pgd필드가 가 가진다...
여기에 적힌 주소는 가상 주소가 아니라 물리주소이다. 페이징을 해야하면(주소가 요청되면) 이 주소가 cr3에 들어간다..

Posted by skensita
System2008. 11. 19. 11:19



순서

 메모리 관리
    - 가상 메모리(Virual Memory) 개념 및 관련 함수
    - GlobalAlloc와 LocalAlloc 함수
    - Heap에 메모리 사용 함수
    - 배열에 메모리 할당 new
    - 메모리 공유 기법

 스레드
    - 스레드 구동 방법
    - 뮤텍스(Mutex), 세머포어(Semaphore), 이벤트(Event)
    - 멀티 스레드 예제


출처 : 충북대학교

Posted by skensita
System2008. 11. 18. 17:12

0.시작하면서...

 우리는 앞서 내용에서 OS를 제작할 때 알아야할 몇가지에 대해서 가볍게(??) 알아봤다. 너무 가벼운 관계로 쪼금 찔리긴 하지만, 일단 그 정도에서 접고 슬슬 프레임워크에 대해서 설명을 하겠다.


1.부팅(Booting)이란?

 프레임워크의 동작 과정을 알려면 첫째로 부팅과정에 대해서 알아야 한다. 그럼 도대체 부팅이란 것이 무엇일까?

 우리가 매일 쓰는 네이버의 백과사전을 검색해 보았다.

Booting(부팅)은 컴퓨터의 시동을 뜻하며, 보조기억장치(bootable device:주로 플로피디스크 또는 하드디스크)를 사용하여 컴퓨터가 동작할 수 있도록 시스템에 운영 체제를 불러 들여 작동을 준비하는 작업이다. 즉, 컴퓨터 시스템을 시동하거나 초기 설정하는 것을 뜻한다.
일반적으로 컴퓨터의 전원을 켜면 먼저 부팅 프로그램을 불러 들이고, 부팅 프로그램은 운영체제(Operating System)를 기억장치로 불러 들여 컴퓨터가 작동을 할 수 있도록 준비한다.
처음 사용자가 컴퓨터의 전원을 넣는 것으로 시작하는 부팅을 콜드(cold)부팅이라 하고 원래 전원이 들어와 있는 상태에서 하는 부팅을 웜(warm)부팅이라 한다.

 그렇다. 바로 컴퓨터를 시동하는 작업을 이야기 하는 것이다. 그럼 우리가 PC를 시동하면 어떤일이 발생하는가?

 다들 알겠지만 시커먼 바탕화면에 마치 무슨 콘솔(Console)처럼 흰 글자가 화면에 찍히고 램용량 표시/CPU/HDD 정보 표시 등등이 나타난다. 이 과정을 POST(Power On Self Test)라고 하는데 메인 보드의 BIOS(Basic Input Output System)에서 자체적인 하드웨어 점검을 해주는 것이다. 여기서 이상이 생기면 비프(Beep) 음이 울리게 되고 더이상 진행되지 않는다. 이 테스트에서 별 이상이 없으면 하드디스크를 읽어서 메모리에 적재한 뒤에 OS가 실행되게  된다.


2.부팅(Booting) 과정

 그럼 대충 어떤 것인지 알아봤으니 조금더 상세하게 알아보자. 아래는 실제로 부팅이 될때 그 과정을 나타낸 것이다.

 

사용자 삽입 이미지

<Boot Process>



 위에서 보면 알겠지만, BIOS는 Boot Sector를 로딩해주는 것으로 일을 마친다. 다시말하면 Boot Sector부터 커널을 로딩해서 OS를 동작시키는 것은 우리의 몫이다.

 그렇다면 부트 코드(Boot Code)부터 전부 작성해야 한다는 이야기인가? 그렇다. 전부 다 작성해야 한다.

 부트코드를 작성하는 데, 설마 C로 하겠지라고 생각하는 사람은 없을 것이라 생각한다(예전에 아무것도 모를 때, 친구가 베이직으로 OS를 작성할 것이라고 이야기 했었는데... 갑자기 그 생각이 난다).

 생짜로 어셈블리어로 짠다 @0@)/~~~ ㅜ_ㅜ


3.부트 코드(Boot Code) 및 커널 로더(Kernel Loader)

 부트코드를 어셈블리어로 작성해야한다는 사실이 초보 레벨의 프로그래머에게 굉장한 부담이 되긴 하지만, 좋은 소식이 있다. 사실 부트 코드가 굉장히 간단하고, 기능이 다 비슷하기 때문에 어느정도 정형화된 코드가 있다.

 또한 한번 만들고 나면 거의 손볼일도 없기 때문에 여기저기 참고해서 만들어도 큰 문제가 없다(배우는 입장에서는 한번 짜보는 것이 당연히!!! 좋다). 부트 코드는 BIOS에 의해서 0x7C00에 로딩되게 되는데, 그렇다면 이 부트코드는 어디에 있는 것일까?

 전통적인 이유로 인해 16bit XT시절부터 지금까지 하드디스크 또는 플로피 디스크의 첫번째 섹터에 부트 코드가 있다. 이런 물리적인 장치의 한 섹터의 크기가 512Byte이므로 부트 코드의 크기도 512Byte로 한정되어있다. 512Byte라니... 이 크기로 무엇을 하란 말인가?

 실제로 부트코드에서 뭐 좀 제대로 일 할려고 하면 512Byte의 크기를 넘는다. 따라서 부트 코드에서는 간단히 이미지를 디스크에서 메모리로 로딩하는 역할만 한다. 그후 커널을 재배치하고 기초적인 환경 설정을 해주는 프로그램을 실행하는데 그것이 커널 로더(Kernel Loader)이다. 커널 로더와 커널은 분리되어있는 경우도 있고 같이 있는 경우도 있는데, 프레임워크에서는 분리하여 구현하였다.

 프레임워크와 같이 분리하여 구현하는 경우 커널 로더가 커널 로딩에 책임을 지게되고 커널 실행을 위한 환경 설정도 책임지게 되므로 부트 코드는 아주 간단해지는 장점이 있다.



4.커널(Kernel) 실행

 마지막으로 커널 로딩이 끝나고 나면 커널로더에서 커널의 시작 주소로 Jump하여 커널을 실행하고 이제 본격적인 OS의 동작이 시작된다.

출처 : (http://kkamagui.tistory.com, http://kkamagui.springnote.com)

Posted by skensita
System2008. 11. 14. 11:13
같은 일을 하는데 방법이 두가지가 있다면, 아마도 두개의 서로다른 회사는 서로다른 방법을 채용할 가능성이 다분할 것입니다. 머피의 법칙이기도 한  이런 가능성은 서로다른 칩디자이너가 메모리에서 데이타를 서열화하는 방법에도 또한 마찬가지로 통용됩니다.

동화(Fable)

걸리버 여행기에 등장하는 소인국(lilliputian)은 매우 작은 나라인데, 그에 걸맞게 사소한 정치적인 문제를 가지고 있었습니다. Big-Endian당과 Little-Endian당이 격론을 벌이는데, 그 격론의 내용이라는 것이 반숙된 달걀을 깨고자 할때, 뭉툭한 끝(Big-End)을 먼저 깰것인가 아니면 뾰족한 끝(Litttle-End)부터 먼저 깰것인가라는 것이 었습니다. (-_-;;)

1980년 4월 1일, 대니 코헨(Danny Cohen)이 지금에서는 유명하게 된 "On Holy Wars and a Plea for Peace"라는 책에서 워드에서의 바이트 오더링(Byte Ordering in Words)에 대해 논하면서 '엔디안'이라는 용어를 이 문제를 지칭하는데 처음 사용했습니다. 그 후 곧바로 이 용어는 고착되었고,엔디안이라는 용어는 단지 바이트 오더링만을 의미하게 되었습니다.(1)

(1) "Byte Sex"라는 용어가 사용되기도 합니다. 유닉스 프로그래머는 "NUXI 문제"라고 부르기도 하는데, 바이트 오더링에 착오가 생기면 'UNXI"라는 단어의 입력에 대해 앞뒤가 뒤바뀌어 'NUXI'라는 출력이 나오니까 말입니다.



[이진 엔디안(Binary Endian)]


컴퓨터 메모리는 기나긴 비트(0과 1의 상태를 스위칭하는)의 배열입니다. 이러한 비트들의 조각이 모여 바이트(여덟개의 비트), 그리고 워드(16bits),롱워드(32bits),쿼드워드(64bits)로 단위 그룹화됩니다.

MSB,LSB

12라는 십진수를 이진숫자로 변환한다고 생각해보십시오. 너무나 당연하게도 '1100'이라는 이진수를 떠올린다면,아마도 당신은 걸리버 여행기에서 달걀은 뭉툭한 끝을 깨어서 먹어야한다고 주장하는 빅엔디안족에 분류가 될것입니다. 글자를 쓸경우 왼쪽에서 오른쪽으로 쓰는 나라도 있고, 오른쪽에서 왼쪽으로 쓰는 나라, 그리고 위에서 밑으로 내려쓰는 나라도 있습니다. 아쉽게도 밑에서 위로 쓰는 경우는 발견하지 못했지만, 못하는 것이 아니라 안하는 것일 뿐이겠죠. ^^ 

1100 (왼쪽에서 오른쪽으로)
0011 (오른쪽에서 왼쪽으로)

'왼편','오른편'이라는 표현을 사용했지만, 사실 이런 상대적인 표현은 정확한 것이 아닙니다. 앞에서 지적했다시피 글자의 방향은 문자를 쓰는 나라마다 자의적으로 변할 수 있으니까 말입니다. 그래서 이런 부정확성을 제거하기위해 컴퓨터 기술자들은 MSB,LSB라는 용어를 이용해서 표현하는데, 우선 MSB,LSB가 의미하는 바를 알아야겠죠?

MSB - Most Significanct Bit (가장 큰 비트 자릿수)
LSB - Least Significant Bit (가장 작은 비트 자릿수)

십진수 12는 이진수 '1100'이라는 이진숫자로 표현되는데, 가장 왼쪽에 나오는 1이라는 비트가 MSB가 됩니다. 반대로 가장 오른쪽에 나오는 비트 0은 LSB가 됩니다.


                        1      1      0       0
                      MSB                 LSB

리틀 엔디안식으로 표현하자면 아래와 같이 위치가 변합니다.

                        0      0      1       1
                      LSB                  MSB


컴퓨터 메모리는 각자 주소를 가지고 있습니다. 우리가 쓰는 IBM PC는 바이트단위로 주소를 할당하는데, 컴퓨터에 따라서는 바이트 단위가 아닌 워드단위(16비트)이상으로 주소를 할당하기도 합니다. 편의적으로 위의 그림에서 왼쪽이 주소가 낮은 쪽, 오른쪽이 주소가 높은 쪽이라고 생각해 봅시다. 그럼 다음과 같은 엔디안의 정의가 가능해집니다.

리틀엔디안 - LSB가 낮은 쪽의 주소에 먼저 등장하는 경우의 비트열

빅엔디안 - MSB가 낮은 쪽의 주소에 먼저 등장하는 경우의 비트열

이제좀 명확해졌습니까??

자.. 지금까지는 바이너리 오더링 다시말해 bit ordering에 관해서 살펴본 것입니다. 엔디안을 살펴볼때, 크게 두가지 체계에서 살펴볼 수 있는데, 첫째가 bit ordering이고 두번째가 byte ordering입니다. 그런데 실제로 문제가 되는 경우는 바이트 오더링이고 비트오더링은 별로 문제가 안됩니다. 왜냐하면, 비트오더링은 대부분의 컴퓨터가 빅엔디안을 채택하고 있기때문입니다. 모토롤라 계열이건, 인텔계열이건 8비트 내부의 비트오더링은 빅엔디안이 거의 표준으로 받아들여져서 사용하기 때문입니다.


다음편에서는 가장 논란의 가져오는 바이트 오더링에 관해서 살펴보겠습니다.


[바이트오더링(Byte Ordering)]

우습잖게도 바이트값들을 더 큰 단위(워드,롱워드,쿼드워드..)로 표현하는 것은 생각지도 않은 큰 차이를 가져옵니다.

사람들이 '엔디안'이라는 이슈로 이야기할때는 바로 '바이트순서'(Byte Ordering)를 문제삼는 것이라고 할 수 있습니다.

서로 다르게 바이트순서를 결정하는 32비트 프로세서를 생각해봅시다.
바이트는 아스키문자를 저장할 수 있는 8비트 한단위라는 것을 잊지맙시다. 그럼 서로 다른 32비트 프로세서가 똑같은 자료를 어떻게 표현하려 하는지를 보기로 하겠습니다. 

   Big Endian  -->              0        1          2          3

                                            U        N         I           X

                                            3         2         1           0             <-- Little Endian         


빅엔디안 프로세서가 데이타를 일정한방향으로 저장합니다. 문제는 그와 다른 프로세서가 그 데이타를 읽어들일 경우에 다른 순서로 정렬하려한다는 것입니다. 둘다 32비트 컴퓨터로 가정했을 경우(4바이트를 한단위로 처리하는),한 프로세서가 U-N-I-X라는 순서로 데이타를 쓸경우, 다른 프로세서는  X-I-N-U라는 순서로 읽어들입니다. 큰 문제가 아닐 수 없습니다.

       이런 성가신 문제가 처음 등장한 것은 16비트 프로세서였는데,이 경우 한 프로세서가 16비트
       단위를 한 단위로 해서 'UN' 'IX'를 쓸 경우에, 다른 프로세서는 'NU', 'XI'라는 짝으로 다시말해
       'NUXI'라는 조합을 읽어들입니다. 이것이 바로 유닉스 프로그래머들이 엔디안을 언급할경우에
       'NUXI 문제'라고 지칭하는 이유가 되는 것이죠.
  
이 문제는 단지 문자들에만 영향을 미치는 것은 아닙니다. 바이너리값에도 영향을 미치는데, 많은 값들은 바이트의 조합으로 표현됩니다. 한바이트는 0-255사이의 값을 가지고 있고, 두바이트가(16비트)가 하나의 값을 표현할 경우에는 0-65535(256x256)사이의 값을 가질 수 있습니다. 두바이트중에 한바이트는 'least significant'(하위바이트)라고해서 그대로의 값을 표현하고, 또 한바이트는 'most significant'(상위바이트)라고 해서 자신이 가진 값의 256배를 표현합니다. 가령 한 컴퓨터에서 상위바이트가 50이고 하위바이트가 10이라는 값을 가질경우, 그것이 의미하는 값은 50 x 256 + 10 = 12,810 이 됩니다. 그런데 다른 컴퓨터에서는 상위바이트가 10,하위바이트가 50이 되어 10x256 +50=2,610을 의미하게 되는데 역시 문제가 아닐 수 없습니다.

MSB,LSB라는 것을 바이너리 오더링에서는 Most Significant Bit,Least Significant Bit이라고 했지만, 때로는 Most Significant Byte,Least Significant Byte를 말하기도 합니다. 표현의 문맥을 살펴서 어떤 의미로 쓰여졌는지를 잘 파악해야합니다.

바이트 오더링이 가장 극명하게 문제시되는 분야는 아마 네트웍통신과 파일호환의 문제에서 나타날 것입니다. 가령 맥킨토시에서 파일에 12/34(슬래쉬는 바이트의 구분을 나타냄)라는 값을 쓸경우에 파일에는 1234라고 순서대로 저장되는데, 이 파일을 ibm pc에서 읽어들일 경우에는 34/12라는 값으로 읽어들여서 엉뚱한 값으로 변환되어버립니다. (한바이트 내부의 표현은 대부분 동일하다고 이미 바이너리 오더링 편에서 밝혔습니다.) 이 문제는 응용프로그램의 호환성에도 많은 영향을 미치는데, 이 때문에 파일의 저장형식을 표준화하는 문제가 제기되는 것입니다.

네트웍의 자료교환을 예로 들자면,C의 함수중에는 ntohs,htons,ntohl,htonl등의 바이트 스왑명령어가 존재합니다. 네트웍은 네트웍 바이트 오더링이라고 해서 자료교환의 순서를 표준화했는데, 빅엔디안의 방식에 따릅니다. ibm pc는 리틀엔디안이므로 자료를 보낼때도 바이트 순서를 바꿔야하고, 받을때에도 순서를 바꿔서 해석해야합니다. 단 바이트단위의 전송에는 해당하지 않습니다.

바이트 오더링(엔디안)의 문제에 있어서 현실적이고 단일한 해결방안은 존재하지 않습니다. 서로의 방식을 인정하고 그에 따라 변환하는 수밖에는 딱히 뾰족한 방책이 없지요.

         PowerPC같은 프로세서는 'Bi Endian'이라 불리는데,설정에 따라서 빅엔디안도 될 수있고,
        리틀엔디안도 될수 있는 프로세서입니다. 하지만 OS는 통상 하나의 엔디안에 의존하게 되므로
        동시에 두가지의 엔디안을 쓴다는 것은 큰 의미가 없습니다.

        인텔(x86)은 리틀엔디안, 모토롤라(68000's)는 빅엔디안입니다. 맥OS는 빅엔디안, Windows는
        리틀엔디안입니다. 
Posted by skensita