10.19(금) 이론 - ( IPC, IOCP )

from Study/System 2007/10/21 22:07 view 51466
1. IPC( Inter Process Communication )

  1) 사용자 정의 메시지
    - SendMessage 로 메시지를 보낸다. 64비트 만 전달 할 수 있다.
    - 포인터는 가상주소간의 접근이 금지 되어 있으므로 불가능하다.

  2) WM_COPYDATA
    - 내부적으로 MMF 를 쓰기 때문에 결국 포인터가 아닌 MMF 공간의 주소를 사용한다.
 
  3) 공유 메모리
  4) MMF
  5) PIPE
    - 이름 없는 파이프는 상대방이 능동적으로 알 수 없다.
      - 만든놈이 알려줘야 한다. DuplicateHandle
      - 상속을 해주자. SetHandleInformation
   
    - 이름 있는 파이프 : 로컬 내에서만 지원한다.
      - '\\pc이름\PipeMailSlot\이름' 의 형식으로 파이프를 생성 해줘야 한다.
  6) 클립보드
  7) 메일 슬롯
  8) DDE, SOKET, RPC

2. IOCP ( 비동기 I/O : 중첩된 입출력, Overlapped I/O )
 
  - 큰파일을 쓰는 동안 다른 작업을 할 수 있도록 할 수 있다.
  - 작업의 취소가 가능해진다.
  - CreateIoCompletionPort 로 IOCP 를 생성한다.
사용자 삽입 이미지


2007/10/21 - [Study/System] - 10.19(금) 실습 - ( IPC 기법들, IOCP 기초 )
Tag | ,
1. API Hooking

  - 직접 호출
    1) DLL내의 함수 위치는 항상 똑같을 수 없다. 주소가 달라진다면 호출이 제대로 되지 않는다.
    2) OS가 호출하는 코드를 바꿔줄 수는 있으나 100군데라면 전부 바꿔야 한다.

  - 간접호출
    1) .idta 에 자기가 사용하는 API의 모든 주소를 가지고 있다.
    2) 0x42,0000 에 메시지 박스 함수의 위치를 가지고 있다가 디레퍼런스 해주면 된다.
    3) 이 주소에 사용자가 만든 임의의 함수 f00() 의 주소를 복사해 넣는다면 재배치(Relocation)을 한다.
    4) 사용자가 만든 임의의 함수 foo() 의 주소를 복사해 넣는다면 API Hooking 을 할 수 있다.
    5) 유저레벨에서 Low 하게 함수를 가로 채는 방법이다.

2. 에러 처리

  - 엑셀의 셀 만들어 보기
    1) 한 셀당 3K를 보관 할 수 있다고 가정해보자.
    2) 먼저 넓은 영역을 통째로 잡고 예약만 한 다음에 쓸려고 할 때 확정을 하는 기법을 하고자 한다.
    3) 셀의 사용유무를 기억하는데만도 n의 수치 즉 셀의 수만큼 의 비트수가 필요하다.
    4) 셀에 무조건 문자열을 쓰고 예외가 발생(즉 셀이 비어있거나,더 쓰고자 할때) 의도적으로 exception을 발생시켜서 셀을 확정 짓는다. 그렇다면 효과적인 메모리 관리(?) 가 가능하다.

2007/10/17 - [Study/System] - 10.17(수) 실습 - ( API Hooking, 예외 )
Tag | ,

10.16(화) 이론-2 ( Hook )

from Study/System 2007/10/16 19:28 view 25526
1. Hook
사용자 삽입 이미지

  - CPU는 INT이 1개 이므로 8259를 이용하여 외부신호를 받아온다. 15개의 신호를 받아 올수 있다.
  - 마스터 8259중 하나는 IRQ# : Slave신호를 받아온다.
  - IDT는 OS가 구축 해 놓아서 디바이스 드라이버에 신호를 보내준다.
  - 키보드 핸들러는 PORT상에서 키보드 상태를 얻어서 SHIQ에 넣어준다.

  - USER 영역에서는 SHIQ=>RIT로 보낼때 Low Level Hook 를 하거나
  - RIT => MSGQ에 보낼때 가로 챌수 있다. ( 제일 쉬움 )
  - NProtected 와 같은 보안 프로그램은 커널쪽인 IDT에서 암호화를 해서 내보낸다.
 g_hHook = SetWindowsHookEx( WH_GETMESSAGE,    // 훅의 종료(15가지)
                                               GetMsgProc,            // 훅함수
                                               g_hDll,                     // 훅함수를 가진 DLL핸들(주소)
                                                tid );                       // 훅을 설치할 스레드( 0:Global hook )


  - 훅은 '전역'(모든 윈도우) 의 성격을 지닌다. 또한 GetMessage 단계에서 메시지를 가로 챌 수 있다.
  - 서브클래싱은 특정윈도우에 메시지를 보내는 DispatchMessage 에서 가로 챈다.

GetMessage()
{
   1. SendQ, PostQ, 가상입력 Q순으로 메세지를 조사한다.
   2. 먼저 WH_GETMESSAGE 훅이 설치 되어 있다면 DLL을 Load하고 hook 함수를 호출한다.
   3. 메세지를 가져간다.
}

///////////////////////////////////////////////////////////////////////
// 모든 스레드는 Kernel32.dll에 있는 아래 함수 부터 실행된다.
////////////////////////////////////////////////////////////////////////

BaseThreadStart()
{
 __try
 {
   스레드도 dll을 가지고 있으므로 ThreadEntryPoint를 이함수로 해준다.
   가상주소에 있는 모든 Dll에 대해서 DllMain을 호출해준다.
   그리고 사용자 함수(foo) 호출을 한다.
   해지를 위해선 foo가 종료 되었을때 DllMain을 또 불러준다.
 }
 __except( 1 ) // 예외처리
 {
 }
}

///////////////////////////////////////////////////////////////////////
// FreeLibrary 도 MMF를 열고 해지를 한다.
// LoadLibrary의 원리
//////////////////////////////////////////////////////////////////////

HMODULE WINAPI LoadLibrary( LPCTSTR name )
{
  1. CreateFile 로 파일 생성
  2. MMF를 사용해서 가상주소와 연결( MapViewOfFileEx - PE헤더의 주소로 연결)
  3. if ( DllMain 호출( 주소, DLL_PROCESS_ATTACH, 0(명시적) ) == FALSE )
        MMF 해지, 화일 닫고 return 0;  // 로드를 다시 해지한다. TRUE리턴이 아니라면..
  4. return 주소!!!
}

2007/10/16 - [Study/System] - 10.16(화) 실습 ( 스택,힙, 훅, DLL )
Tag | ,

10.16(화) 이론-1 ( 스택, 힙, DLL)

from Study/System 2007/10/16 16:49 view 26575
1. 스택의 원리
사용자 삽입 이미지


- Page-Guard : 접근시 예외를 발생시키는 속성을 가진다.

- 2번째 페이지 에 접근하면 Page-Guard는 Commit 이 되고 3번째 페이지가 Page-Guard 상태가 된다.

- 마지막 페이지에 도달하면 Reserve 상태가 된다. 이를 벗어 나면 오버플로우 가 발생한다.

- 메모리가 필요할 때 Commit 상태가 되는것이다.

- 메모리의 상태를 알고 싶다면 VirtualQuery, VirtualQueryEx를 쓴다.



char* addr = (char*)0x0;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery( addr, &mbi, sizeof(mbi) );

2. Heap
사용자 삽입 이미지

- 힙은 프로세스당 1개씩 있지만 새롭게 힙공간을 만들어도 된다.
- HeapAlloc, HeapFree 는 기본힙을 생성하고 소멸 해준다.
  1) 확보된 영역을 조금씩 쓰기 위해선 OS가 관리해주는 Heap메모리는
 Heap매니저가 담당해준다.( 소형 메모리 할당 )

  2) 좌측 그림처럼 메모리가 조각화 되어서 메모리 낭비가 발생한다.


- HeapCreate, HeapDestroy 는 새롭게 힙공간을 만들어 준다.
  1) 메모리 조각화 현상을 막을 수 있다. 새로운 힙공간에는 똑같은 크기의 메모리만 써서 메모리 낭비를 막는다.

2) 기본힙은 내부적으로 동기화를 수행하므로 멀티스레드에는 안전하지만 수행속도가 그만큼 느려진다. 단일 스레드 프로그램에선 힙을 하나 생성해서 동기화를 수행치 않게 되면 빨라진다.

사용자 삽입 이미지

- 윈도우즈 환경에서의 메모리 관리 함수들의 계층은 위 그림과 같다.
- 이외에도 GlobalAlloc ( 전역 힙 ), LocalAlloc ( 지역 힙 ) 이 있지만 이는 16bit시절에만 의미가 있고
- 지금은 HeapAlloc 이나 다름이 없다. 하지만 GlobalAlloc 은 유일하게 클립보드에 써줘야 한다.
The global functions are slower than other memory management functions and do not provide as many features. Therefore, new applications should use the heap functions. However, the global functions are still used with DDE, the clipboard functions, and OLE data objects.


3. DLL

  - 프로세스에 로드될때 ( DllMain() 생성,초기를 해준다. )    DLL_PROCESS_ATTACH
  - 프로세스에서 해지될때 ( DllMain() 소멸자 역할을 한다. ) DLL_PROCESS_DETACH
  - 스레드가 생성시 ( DllMain() TLS공간을 만들어 준다. )     DLL_THREAD_ATTACH
  - 스레드가 파괴시 ( DllMain() TLS공간을 없애준다. )         DLL_THREAD_DETACH

   - DllMain은 Serialize 가 된다. 스레드 두개가 동시에 접근 못하도록 구현되어 있다.( 한스레드만 접근 )
BOOL WINAPI DllMain( HANDLE hDll,    // DLL 핸들, 결국 주소
                     DWORD  r,                    // DllMain 이 호출된 이유
                     LPVOID how                 // DLL이 Load된 방식( 0이면 LoadLibrary 사용 )
                    )

  - 동적TLS( 스레드에서 dll을 로드 했을 때 전역변수 문제를 해결한다. strtok 의 static변수 )
사용자 삽입 이미지
  - 멀티스레드 환경을 고려할 때 dll의 전역변수는 동적 TLS를 사용해야 한다.
  - 동적TLS는 사용하지 않는 Index에 메모리를 할당하는 기법으로 모든 스레드는 인덱스를 가지고 메모리에 접근한다. 이때 Index는 프로세스에 할당되어 있다.~
DWORD index = 0; // 동적 TLS index로 사용.
//////////////////////////////////////////////////////////////////////////////////

index = TlsAlloc();    // 동적 TLS에 빈슬롯 할당
buf = (char*)HeapAlloc( GetProcessHeap(), 0, 1000 );
// 주소를 TLS에 보관
TlsSetValue( index, (void*)buf );

TlsFree( index );
////////////////////////////////////////////////////////////////////////////////
// 새로운 스레드가 생성 될 때마다 메모리를 할당해서 Thread-Safe를 보장!!
buf = (char*)HeapAlloc( GetProcessHeap(), 0, 1000 );
TlsSetValue( index, (void*)buf );

buf = (char*) TlsGetValue( index );
HeapFree( GetProcessHeap(), 0, buf );
/////////////////////////////////////////////////////////////////////////////////


2007/10/16 - [Study/System] - 10.16(화) 실습 ( 스택,힙, 훅, DLL )
동적TLS MS문서
Tag | ,
1. 가상메모리

Kernel영역 2~4G 사이
PageTable 0xC000,0000
ntoskrnl.exe 0x8000,000
User영역 0~2G 사이
OS를 보호(완충지대) 0x7FFF,0000
기본적인 DLL 영역
kernel32.dll, user32.dll
GDI32.dll, ntdll.dll
0x7xxx,xxxx
사용자 추가 DLL 0x1000,0000
MS-DOS 시절
전용프로그램 공간(호환성)
0x40,0000
Heap ~ 0x13,0000
Stack 0x13,0000 ~ 0x3,0000
User영역의 시작 0x1,0000
NO_ACCESS(접근금지) 0~64k

  - 좀더 많은 주소를 얻고 싶을 때 boot.ini 에서 3G를 주면 된다.
  - malloc이 실패하면 0 번지가 리턴된다. 이영역은 사용하지못한다.
  - 2G-128K 가 사용자가 쓸수 있는 공간이라 할수 있다.
  - 하나의 프로세스에서 스레드 1000개 정도가 MAX라 할수 있다.( Thread는 1M의 스택영역을 갖고있다. )
  - 지금은 0x40,0000을 쓰지 않지만 과거와의 호환을 위해서 0x40,000 번지 부터 프로그램이 로드된다.

  - VirtualAlloc () => 비어있는 가상주소공간을 할당한다는 개념..
    1) 예약 : 주소만 확보해둔다.
    2) 확정 : 물리공간과의 연결을 해준다.
    3) 결국 예약을 함으로써 주소만 미리 확보 해둔다음 확정을 사용하여 쓴다는 개념!!
char* p1 = (char*)VirtualAlloc( (void*)0, // 원하는 주소(64k배수), 자동으로 할당 0
                                         size*15,    // 원하는 크기(4k 단위)
                                         MEM_RESERVE,          // 예약만
                                         PAGE_NOACCESS );    // 보호 속성(어짜피 접근못하므로 NOACCESS)

void* p2 = VirtualAlloc( p1, size, MEM_COMMIT, PAGE_READWRITE );

  - VirtualAllocEx => 다른 프로세스의 가상메모리를 확보.
  - 주소 할당단위는 64k배수 뒷4자리가 0이어야 하고, 0일때는 자동으로 할당된다.
  - malloc은 결국 윈도우에선 VirtualAlloc 을 호출 한다는 것을 알 수 있다.

2. MMF( Memory Mapped File )

  - OS는 오래동안 쓰지 않는 프로그램의 물리메모리를 가상메모리에 백업해 놓는다.
  - 백업되 있던 가상메모리를 물리 메모리로 올리기 위해선 오버헤드가 발생된다.
  - VirtualLock 을 쓰면 항상 물리공간에 있게 할 수 있다. ( 물리메모리에 부담이 간다. )
  - Working Set : 물리 공간에 있는 Page 집합 SetProcessWorkingSet ( 1~2M를 쓸 수 있다. )

사용자 삽입 이미지

  - 메모리에 연결된 파일을 MMF 라 한다.!! 파일로 직접 접근하여 쓸 수 있다.
  - 보안을 설정해주면 접근을 제한 해줄 수 있다. 아무나 접근 할 수 있는 공유메모리와 다른 개념!!!
  - 파일을 Open해서 쓰는 것보다 효율적이다.
    1) 일반파일보다 빠르게 작업을 할 수 있다.
    2) 연결만 해놓으면 그자체가 buf가 되므로 버퍼 없는 작업이 가능하다.
    3) 동일한 파일을 가상주소로 연결 하며 프로세스간 통신(IPC)가 구현된다.

    4) PEVIEW 처럼 exe의 Header를 분석하기 위해선 MMF가 이상적이다. 메모리로 exe를 로드해서 읽기 보다는 가상주소를 exe로 바로 맵핑하면 해당 주소에 맞는 구조체만 만들어 주면 된다.
    5) exe, dll은 모두 MMF로 구성되어 있다. ( Demand Page )모두 Page단위로 필요할 때만 물리메모리에 올려진다.

  - WM_SETTEXT 의 MMF 사용
    1) 다른 프로세스의 윈도우 제목을 변경하고자 할때 문자열만 넘기면 왜 되는 것인가?
int main()
{
    HWND h = FindWindow( 0, "계산기" );

    // 계산기의 캡션바를 "Hello"로 변경한다. - 될까?
    SendMessage( h, WM_SETTEXT, 0, (LPARAM)"Hello" );
}
    2) WM_SETTEXT 메시지를 수행할 때 내부적으로 MMF 에 "Hello" 를 보관했다가 계산기에서 이 영역에서 문자열을 읽어 와서 제목을 변경 해 주는 것이다.

3. DLL Inject ( 참고 문서 :

  - 다른 프로세스에 내가 만든 DLL을 집어넣어 보자.!!! 핵심은 CreateRemoteThread
  - FindWindow 로 계산기 핸들을 얻은 다음에 pid => 프로세스 핸들 을 얻게 되면
  - SetWindowLong으로 WndProc 를 변경 할 수 있을까? (서브클래싱을 할 수 있을까?)
  - 되지 않는다.. 왜냐하면 프로세스간의 독립성을 보장하는 가상메모리를 생각하면 알 수 있다.
  - 계산기의 가상 메모리에는 내가 변경하고자 하는 함수가 올라와 있지 않기 때문이다.
  - 이를 해결하기 위해선 계산기의 가상메모리에 함수를 올려놓아야 한다.( DLL을 사용해서 올려보자!! )

사용자 삽입 이미지

  - 문자열을 써주기 위해선 VirtualAllocEx 로 공간을 할당한 후에 WriteProcessMemory로 써준다.

2007/10/15 - [Study/System] - 10.15(월) 실습-1( 가상메모리, MMF, DLL Inject )
참고문서 :
Tag | ,
1. 메모리

  - 16비트
    1) jmp A : 3 은 결국 3번지로 이동하라는 명령이다. 이는 CS : 3 ( CS레지스터리부터 3번지로 개선됐다. )
    2) 16bit 시절부터 offset을 이용하여 주소를 결정하였다.
code Segment : CS
data  Segment : DS
stack Segment : SS
TEB Segment : FS

mov [10], 100 : DS로부터 10떨어진곳에 100을 넣어라
mov [500], 100 : DS로부터 500떨어진곳에 100을 넣다가 다른 메모리에 접근할 수도 있다. 보안문제 발생

  - 32비트
    1) 재배치를 해준다. GDT( Global Descripter Table ) 을 거쳐야 가상주소가 논리주소,선형주소가 된다.
Index Address Size Access
1 1000 5000 R
2 7000 3000 R/W

    2) mov [100], 100  : DS가 Index 2라면 7000에서 100떨어진곳에 100을 추가한다.
    3) mov[6000], 100 : 하지만 잘못된접근이나 권한밖의 일은 리셋시켜서 보호해준다. Table의 역할!!!
    4) Windows 나 Linux는 GDT 를 쓰지 않고 Intel CPU와의 호환을 위해 그냥 만들어 놓기만 한다.
    5) 그러므로 결국 가상주소가 논리주소,선형주소가 된다.

  - 가상주소 + 세그먼트 => GDT => 선형주소(linear Address ) // Segmentation 이라 한다.

2. 페이징

  - GDT 테이블을 거친 선형주소는 4G가의 메모리를 참조 할 수 있다.(가상주소랑 일치)

  - Page Table
    1) PFN (Page Frame Number) : 메모리 관리를 효율적으로 하기 위하여 물리 메모리를 4k로 잘라서 보관!
    2) 물리메모리가 1MB 라면 이를 잘게 나눠서 0~255 번의  PFN 를 매기는 것이다!!
    3) 하지만 물리메모리가 4G 라면 너무 많은 PFN이 생성되므로 2단 주소로 바꿔서 관리를 해준다.

사용자 삽입 이미지

    4) 선형주소 => PageDirectory => PageTable => PageFrame => Offset  의 순서 물리메모리의 주소를 찾아간다. 이 모든것이 선형주소에 담겨 있는것을 볼 수 있다.
    5) 선형주소는 CR3 레지스터를 참고하여 페이지디렉토리를 찾아간다.

  - 이 모든 작업은 Intel CPU가 알아서 해준다. 그저 테이블과 CR3에 주소만 담아 놓으면 된다.
  - PFN 에는 32bit의 주소만 갖고 있는것이 아니라 48bit로 구성되어 16bit는 HDD에 백업되어 있는 물리메모리 값을 파악해서 다시 물리메모리에 올려 놓는 작업도 하고 있다.

3. 공유 메모리

  - A.exe 에 전역으로 선언된 변수가 있다면 이를 2번 실행시 데이터와 코드를 공유하여 물리메모리에 올린다.
  - 하지만 전역 변수에 데이터를 쓰게 되면 Copy On Write 를 하여 새로운 메모리 공간을 만들어 준다.
  - 즉, 처음에는 메모리 영역을 공유하지만 쓸때에는 복사본을 만들어서 딴 공간을 만들게 된다.

  - Copy On Write 를 하지 않는다면 전역변수를 서로 공유할 수 있게 되는 것이다.
// 일반 전역 변수..  .data section에 놓인다. 기본적으로 COW하는 속성을 가지고 있다.
int x = 0;

// exe에 새로운 data 섹션을 만든다.
#pragma data_seg("AAA")
int y = 0;      // 공유 섹션으로 만드려면 반드시 초기값이 필요하다.
#pragma data_seg()

// 특정 섹션의 속성을 지정한다. - Read, Write, Share( COW 속성이 제거된다. )
#pragma comment( linker, "/section:AAA,RWS")    // Copy On Write
- .exe 에 Share 속성을 가진 AAA 섹션이 생긴것을 알 수 있다.
- 전역변수는 .data 에 있지만 AAA라는 Section을 만들어서 물리메모리 상에서 같이 사용하는 것이다.
- 같은 프로세스 끼리는 공유가 필요 없으므로 DLL에 써서 다른 프로그램에도 읽게 해준다면 더 효율적..
- A.exe => int x;(전역), x.dll에 선언 <= B.exe
// SHARE.dll - COW를 하지않는 DLL상에서의 전역 변수 만들기
// 공유 메모리는 반드시 초기화 해야 한다.,!!!!!!!!!!!!!!

#pragma data_seg("SHARED")
char buf[1024] = { 0 };
#pragma data_seg()
#pragma comment( linker, "/section:SHARED,RWS" )
/////////////////////////////////////////////////////
extern "C" __declspec(dllexport) void SetData(char* s)
{
    strcpy( buf, s );
}
extern "C" __declspec(dllexport) void GetData(char* s)
{
    strcpy( s, buf );
}
/////////////////////////////////////////////////////
// A.cpp - DLL에서 가져와서 사용하기!!!

    HMODULE hDll = LoadLibrary( "SHARED.dll" );
    F SetData = (F)GetProcAddress( hDll, "SetData" );
    F GetData = (F)GetProcAddress( hDll, "GetData" );

Tag | ,

10.12(금) 이론-1( TLS, 우선순위 )

from Study/System 2007/10/14 20:46 view 22873
1. TLS

  - Thread Local Storage ( 스레드별로 따로 할당되는 공간 )
  - 스레드별로 독립적인 공간을 만들어 준다.
  - 정적 TLS 를 만들기 위해선 __declspec(thread) 를 static 변수 또는 전역변수 앞에 붙여준다.
  - 주스레드에서 새로운 스레드를 생성하면 새로운 TLS 공간이 생기고 주스레드의 TLS의 내용과 dll의 TLS의 내용을 가져온다. ( .tls 라는 섹션이 생긴다!!! )

  - 하지만 LoadLibrary 를 사용하여 새로운 dll을 가져올 때 TLS의 초기화는 이미 끝나 있다.(명시적 로드시)
  -  정적 TLS의 문제점 : DLL에 만들고.. 해당 DLL을 LoadLibrary로 사용하면 TLS에 있는 변수가 제대로 동작하지 않는다. => 동적 TLS를 사용하자.

  - 동적 TLS : TlsAlloc, TlsFree, TlsSetValue, TlsGetValue
  - 항상 똑같은 Slot을 같은 용도로 사용한다.
  - 동적 TLS는 프로세스에서 관리 하게 되는데 사용하면 1 비사용은 0으로 관리를 하게 된다.
  - 전역변수( 모든 스레드 공유 )로 TLS Index를 보관해야 한다.

2. _beginthreadex
// 임의의 함수가 내부적으로 지역변수만 사용하고 있다. - 멀티스레드에 안전
// 임의의 함수가 내부적으로 static 지역변수를 사용하고 있다. - 멀티스레드에 불안전

unsigned int __stdcall _beginthreadex( ... )
{
    // C함수들 중에서 내부적으로 static 지역변수를 사용하는 함수를 위해서
    // 힙에 메모리를 할당한다.
    _tiddata* p = malloc( sizeof(_tiddata) );

    p->func = 사용자 함수;

    // TLS에 방금 할당한 P를 보관한다.
    HANDLE h = CreateThread( 0, 0, threadstartex, p, 0, 0 );
}

void threadstartex( _tiddata* p )
{
    p->func();
    free( p );  // 메모리 제거
}

  - strtok는 내부적으로 static 지역변수를 사용하므로 멀티스레드에 불안전 하다. VC6.0( LIBC.lib )
  - 그래서 VC2005 부터는 LIBCMT.lib 를 사용하여 멀티스레드에 Thread Safe 를 제공한다.(TLS사용)
 
  - 멀티스레드를 사용하고자 할때는 _beginthreadex를 사용하여 TLS에 구조체 하나를 생성해줘야 한다.. 그래야 strtok와 같은 함수를 안전하게 사용할 수 있다.
  - static이 스레드 하나에 종속되어 다른 스레드에 영향을 끼지치 않는다는 것을 보장!!


1. 우선순위

  - 스레드는 프로세스 우선순위에(8)에 상대적으로 매길 수 있다.
  - 8 을 기준으로 -1, +1 을 한다. SetPriorityClass, SetThreadPriority ( 우선순위 변경 )

  - RTOS 는 반드시 우선순위를 지켜야 한다.( 우선순위 역전 현상을 방지 )
사용자 삽입 이미지

  - 위 그림은 2 ~ 3 ~ 1 순으로 끝나게 된다. 3이 뮤텍스를 획득하고 파서 대기하는라고 2가 계속 실행되는 현상이 발생하는 것이다.
  - 만약에 인공위성이 지구와 통신 하는 것이 3이라면 영원히 실행되지 않을 수 있다.

  - 이를 방지하기 위해서 3이 뮤텍스 대기 상태에 빠지면 뮤텍스를 획득한 스레드를 자신과 같은 우선순위로 만들어 뮤텍스를 반납하게 만들어 줘야 한다. 위 그림에서 1을 우선순위 3으로 만들고 반납하게 해주면 된다.

1. 스레드 친화력 : 어떤 스레드를 어떤 CPU에서 실행할 것인가를 정할 수 있을까??(듀얼)
 
  - SetThreadAffinityMask http://www.microsoft.com/korea/msdn/library/ko-kr/dev/vc/optimization.aspx

2. 스레드, 프로세스가 지니고 있는것

  - 스레드 : Stack, MessageQ, TLS, HOOK
  - MessageQ는 스레드당 1나씩 존재하는데 +0x040 Win32ThreadInfo  : Ptr32 Void 유저모드에서 관리!!.
Tag | ,
1. 스레드풀

  - 최초에 Thread를 10개를 만든 후에 일이 필요하면 자고 있는 스레드를 깨워서 일을 시킨 후에 다시 재운다.
  - 15개의 일이 들어오면 나머지 5개는 Q에 대기 시켜 놓고 일을 다하면 실행시켜 준다.
  - 미리 만들어 놓고 내가 쓰레드를 관리한다는 개념이 쓰레드 풀링이다~~!

  - 핵심은 세마포어를 활용하는 것!! 대기상태를 세마포어로 구현하여 활용하면 된다.!!!
  - 세마포어는 리소스 카운팅에 적합하다.!!

2007/10/14 - [Study/System] - 10.11(목) 실습-1
Tag | ,
1. 뮤텍스

  - 화장실 열쇠가 한개만 존재하여 한사람씩 사용가능한 경우..
  - Enter ~ Leave Critical 은 전역변수로 단일 프로세스에서만 사용하나 뮤텍스는 다중프로세스에서 사용.
  - KMUTANT로 구조체를 확인 가능하다.
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListEntry  : _LIST_ENTRY
   +0x018 OwnerThread      : Ptr32 _KTHREAD
   +0x01c Abandoned        : UChar
   +0x01d ApcDisable       : UChar

  - CreateMutex( 0, FASLE, "m" ); // 또한번 CreateMutex("m")를 하면 Open으로 여는 것과 같다.
  - 처음 소유자 스레드 ID는 0이지만 10번 스레드를 얻게 된다면 Id엔 10을 채우고 재귀횟수가 1이 된다.
  - ReleaseMutex를 하지 않고 스레드가 끝나면 WAIT_ABANDONED 포기된 mutex가 발생한다.

2. 세마포어

  - 정해진 자원을 카운트 한다.
  - CreateSemaphore( 0, 3, 3, "s" ); // 보안:0, signal:+3, 이름:"s", 참조:0, limit:3
  - WaitForSingleObject 를 하게 되면 signal은 1씩 줄어들게 되고 0 이 되게 되면 나중에 실행되는 스레드는 signal 이 1이 될때까지 대기 해야한다.

  - Limit가 1인 세마포어는 뮤텍스와 비슷하나 차이점이 있다.
    1) 뮤텍스는 소유권이 있지만 세마포어는 소유권이 없다.
    2) 즉, 열쇠를 공동으로 관리 한다는 말이다. 뮤텍스는 자기가 꼭 관리 해주지만 세마포어는 자원의 갯수를 감소시키고 증가시키는 작업을 다른 스레드(사람)가 해줘도 된다. 열쇠를 모두 공유한다는 개념으로 이해..
    3) 모두 공유한다는 개념이 있으므로 꼭 접근을 동기화 해줘야 한다.!!!!

3. 이벤트

  - Event : 스레드간 통신에 활용된다.
HANDLE h = CreateEvent(
        0,      //보안
        FALSE// Reset의 종류( T:manual, F:auto )
        FALSE// 초기 signal 상태 ( T:signal, F:non-signal )
        "e" );  // 이름
 
  - 2번째 인자를 FALSE로 하여 AutoReset 으로 하면 WaitForSingleObject 통과시 자동으로 리셋된다.
    1) 여러개의 스레드에 이벤트"e"가 존재하면 누가 먼저 깨어날지는 알수가 없게 된다. "경쟁 상태"

  - 2번째 인자를 TRUE로 한다면 manual 상태가 되어  WaitForSingleObject 통과시 signal 상태이다.
    1) 여러개의 스레드에 이벤트"e"가 존재한다면 모두에게 신호를 보내기도 한다.
    2) SetEvent 일때는 WaitForSingleObject 통과시 signal 상태이지만
    3) PulseEvent 일떄는 WaitForSingleObject 통과시 non-signal이 되어 한번씩만 꺠운다.!!
    4) CreateEvent를 동일한 이름으로 두번 수행하게 되면 두번째로 생성되는것은 Open의 의미를 갖는다.

2007/10/14 - [Study/System] - 10.11(목) 실습-1

Tag | ,

10.9(화) 이론-2( 스레드 )

from Study/System 2007/10/14 15:58 view 19806
1. 스레드!!!

  - 개념 ( 윈도우는 스레드기반으로 돌아간다. )
    1) 가상주소 => 페이지테이블 => 물리주소
    2) CR3 : 페이지 테이블의 주소
    3) CPU는 10ms씩 인터럽트가 발생하여 Context Switching 이 발생
    4) 인터럽트 Handler 는 스케줄러 담당한다. 두개이상의 프로세스가 동시에 실행되도록 한다.
    5) 퀀텀Time : 20ms 최소의 실행시간을 보장해준다.
    6) 메시지Q는 GUI 요소를 갖는 윈도우만 가지고 있다.
    7) GetMessage() 에서 message가 없다면 Context Switching 이 발생..
    8) Sleep(0); 을 해서 강제로 C.W 발생..
    9) 메모리 관리 : 프로세스가 CR3를 가지고 있다.(ReadProcessMemory는 CR3의 주소값을 참조.)
   10) 실행흐름 : 스레드가 관리한다.

  - 함수
    1) CreateThread, CreateRemoteThread ( 다른프로세스에 스레드를 생성 ), _beginThreadex

  - 주스레드에서(main()) 스레드A를 생성하게 되면 ETHREAD에 1MB의 스택영역이 생성된다.
  - 주스레에서 main리턴하게 되면 프로세스가 종료되고, ExitThread를 해주면 주스레드만 종료된다.
  - 다른 스레드의 종료를 대기해줘야 할때 ( WaitForxxx )
    1) WaitForSingleObject - 스레드구조체의 staterk signal 상태가 되기를 기다리게 된다.

  - WaitForSingleObject 의 원리
    1) dt nt!_KEVENT, dt nt!_KTHREAD 에는 공통적으로 _DISPATCHER_HEADER 가 있다.
+0x000 Header           : _DISPATCHER_HEADER
///////////////////////////////////////////////////////////////////////////////////////
   +0x000 Type             : UChar
   +0x001 Absolute         : UChar
   +0x002 Size             : UChar
   +0x003 Inserted         : UChar
   +0x004 SignalState      : Int4B  <= 모든 KO는 이를 가지고 있다. 실행과 대기를 위해서~~
   +0x008 WaitListHead     : _LIST_ENTRY

2. MulitiThread
DWORD CALLBACK foo( void* p )
{
    int x;                // TLS 내에 생긴다.(Thread Local Storage) 지역변수는 Thread Safe 
    static int y;       // 프로세스의 static 공간에 생긴다. 스레드에 안전하지 않다. CriticalSection

    return 0;
}

- CriticalSection 영역을 ThreadSafe 하기 위해선 CRITICAL_SECTION을 사용한다.
- Serializetion( 직렬화 ) : 2차선 -> 1차선으로 바꿔서 스레드 한개만 활동하게 한다.

- 듀얼 코어에서는 자러가는시간 1000ms 과 깨어나는 시간 100ms 라면 깨어나는 Thread가 계속 실행될 수 있다. 그래서 SpinCounter 500ms 정도로 설정하여 CS에 진입을 재시도 하게 한다.

3. 원자 연산 ( Atomic Operation )
DWORD CALLBACK foo( void* p )
{
    for ( int i = 0; i < 10000; ++i )
    {
        // COM 기반 기술에서 자주 사용한다.!!!!!!!
 
      InterlockedExchange( &x, 0 ); // x = 0
        InterlockedExchangeAdd( &x, 5 );    // x = x+5;
        InterlockedDecrement( &x ); // x -= 1;
        InterlockedIncrement( &x );    // lock inc x 로 구현된 함수. x += 1

        x = x+1;    // 보장받지 못한다.

        __asm
        {
            lock inc x // inc 는 원자 연산이다. 즉, inc 실행중 절대 Context Switch가 발생안함.


           // 아래 명령어는 원자 연산을 보장 하지 못한다.

            mov eax,    x
            add eax,    1
            mov x,        eax
          ///////////////////////////////////////////////
        }
    }
    return 0;
}

  - asm으로 원잔연산을 보장 해주기 위해선 inc 라는 명령어를 사용한다.
  - 듀얼코어에서는 lock 이라는 접두어를 붙여줘야 한다.
  - InterlockIncrement : lock inc x 를 구현해 놓은 함수이다...


TIP : 함수의 어셈 코드 보기

HMODULE hdll = GetModuleHandle( " xxx.dll " );  // 함수가 포함된 dll
void* p = (void*) GetProcAddress( hdll, "함수명" );
printf( "%p\n", p );

=> 함수의 dll에서의 주소(p)를 디스어셈블리창에서 찾아보면 나온다..
Tag | ,