메모리에 대한 이야기

from Study/System 2007/10/15 20:05 view 21821
윈도우의 모든 프로세스는 가상메모리 기반으로 되어있습니다.
프로세스가 실행될때, 먼저 가상 공간을 확보후 (페이징파일과는 분명히 다릅니다.) 실제 메모리에 매핑을 하게되는 과정을 거칩니다.

작업관리자에 보면 메모리 사용량과 가상메모리 사용량이 나오는데, (디폴트는 아닙니다. 보기->칼럼선택 에서 선택해 줘야 나옵니다.) 페이징파일을 날렸는데 VM Size는 표시가 되지요. 어찌된 일일까요?

이는 그냥 프로세스에 할당된 가상 메모리 양에 불과합니다. 페이징파일과 아무런 관련이 없지요.
하지만 대부분의 사람들은 이를 페이징파일 사용량이라 착각하곤 합니다. 페이징파일과 가상메모리는 분명히 다르며, 가상메모리의 종류도 페이징파일만이 아닙니다.

Q2. 램이 많으므로 페이징파일 삭제?

 결론부터 말씀드리자면 삭제하지 말기를 추천드립니다. 페이징파일을 날리면 메모리를 쓸데없는곳에 쓰게됩니다. 윈32에서 실제 메모리의 할당단위는 64K 입니다. 하지만 페이지의 단위는 플랫폼마다 다르지만 x86 환경에서는 4K입니다.

 예를들어 4KB짜리 데이터 100개를 램상주시킨다 할때, 모두 실제메모리에 올린다면 최소단위가 64K므로 6.4M의 메모리가 나갑니다만, 페이징파일로 처리시는 400K면 끝납니다.

 그리고 대부분 모르시는 이야기이지만, 가상 메모리에는 단순 페이징 파일 외에, 메모리 맵 파일(Memory-mapped file) 과 힙(Heap)이 존재합니다.

 메모리 맵 파일은 윈32 메모리 관리 매커니즘의 꽃이지만, 가상이 아닌 실제 메모리 부분이므로 넘어가도록 하겠습니다. 파일을 메모리처럼 사용하는 기술이라 생각하시면 됩니다.

힙은 프로세스 내부적으로 이미 확보된 공간을 말합니다. 프로세스마다 디폴트로 1M가 확보되는데 이건 100% 페이징파일로 들어갑니다. 페이지가 4KB라는 장점을 극대화시키기 위한 메모리로, 작은 데이터를 처리할때 최고의 경쟁력과 경세성을 보장해줍니다. 다만 페이징파일이다보니 속도가 느린 단점은 존재하겠지만요...어쨌든 당연히 페이징파일의 삭제는 힙이라는 훌륭한 매커니즘을 포기한다는 이야기가 되는겁니다.

 그 밖의 경우는 실제메모리로 올릴것인지, 페이징파일에 넣을지는 OS가 결정합니다. 사용빈도가 높다면 메모리에 올리고, 적다면 페이징파일에 심습니다. 알아서 최대의 효율을 낼수있도록 지속적으로 감시해주니,
이런 좋은 기능을 페이지파일 삭제로 없애버리는 오류는 만들지 마시길...

 특별히 손을 안대도 큰파일은 메모리로 작은파일은 페이징파일로, 알아서 관리해 준답니다. (Q3에서 관련내용 이어집니다.)

그리고 대표적으로 토토샵같이 페이징파일을 반드시 이용하게끔 제작된 소프트웨어도 상당수 있다는걸 염두해두시길...(아마도 힙사용을 위해 프로그램상에서 미리 체크하는거라 생각됩니다.)


Q3. 페이징파일은 자신의 메모리기준 얼마? 그리고 가변시킬까 고정시킬까?

'지금 자신의 시스템이 얼마만큼의 페이징파일을 사용하는지 알고는 계십니까?'

 사실 요즘같이 램이 빵빵환 환경에서는 페이징파일에 큰 데이터가 들어가지도 않습니다. 512M 시스템 기준으로 제가 측정해본 결과로는 간단한 업무등의 상황에서는 100M 미만, 풀작업환경시 (Eclipse, Toad..등 메모리사용량 536M 상황입니다.) 120~130M 사이를 왔다갔다했습니다.

 512M 기준 윈도우에서 디폴트로 잡아주는값이 768M 이긴 합니다만. 게임등을 돌려서 오버헤드시라 해도 200~300M 이상 안나옵니다. 크게잡아 하드 400M정도를 낭비하는 셈이 되는거죠.

 1.5배라는 속설은 메모리가 128, 256 하던 옛날 이야기입니다. 메모리가 1기가 이상라면 말할것도 없이 낭비율은 더 커질수밖에 없고요. 할당량 가변, 고정의 논쟁도 의미가 없습니다.

 할당량 가변시 트레이에 가상메모리 부족이란 말풍선이 뜨면서 증가작업에 들어가는데, 최근에 이거 보신분이 솔직히 몇명이나 될런지 의문이 듭니다. 잘 일어나지도 않는 증가작업 걱정에 괜한 하드 낭비하시는거라 생각드네요.

 그리고 솔직히 말풍선이 떠서 증가작업에 들어갔다 하더라도, 대부분 사용상의 부족이 아닌 특정 프로세스가 Hang이 걸려 무한루프에 빠지거나,메모리 누수가 발생한 경우가 대부분이지요.

제가 제안하는 최적의 세팅은, 자신의 가상메모리 사용량보다 살짝 위로해서 최저로 잡고, 최고치를 넉넉하게 잡아주는겁니다.

이렇게 사용시 부족메세지가 자주 보이면 최저를 올려줘야 한단 이야기가 되겠죠.



Q4. 메모리 1G->2G의 효용성은?

자신의 메모리 사용량을 안다면 답은 나옵니다. 2G만큼을 쓴다면 2G로 증설하면 됩니다. 당연히 2G만큼을 쓰지않으면서 증설하면 효과가 없겠죠. 참고로 일반적인 개인 컴퓨팅 환경에서 1G채울일도 거의 없습니다.
최근 고사양 게임에서나 1G 안팎으로 놀긴 하지만요.

Q5. 32비트 환경에서 4G의 메모리가 잡히지 않는 이유와 그 효용성은?

이부분은 좀 어려운 내용입니다. 윈32(9x 포함)에서의 메모리 관리는 다음과같은 영역으로 되어있습니다.

0x00000000~0x0000FFFF : 널포인터 할당영역 (에러보고를 위한 구간)
0x00001000~0x7FFEFFFF : 사용자 영역 (사용자가 사용할수 있는 공간)
0x7FFF0000~0x7FFFFFFF : 64K 오프리밋 (구간간 경계선)
0x80000000~0xFFFFFFFF : 커널 영역 (시스템이 사용할수 있는 구간)

사실 위는 NT고 윈9x에서는 16비트영역, 공유영역 다른구간이 있습니다만, 베이스는 동일하니 NT기반으로 설명합니다.

어쨌든 위와 같이 윈32의 사용자가 사용할수 있는 메모리 공간은 2G - 128K 입니다. 우리같은 사용자는 사용자 영역서만 작업이 가능하기 때문이죠. 그렇기 때문에 4G의 램을 붙여봐야 2G밖에 사용을 할수 없습니다.

과거에 커널영역도 사용자가 사용할 수 있는 OS가 있었습니다. 하지만, 커널영역에 접근한 사용자의 응용프로그램이 문제를 일으키면서, 물귀신처럼 커널영역에 올라간 시스템데이터를 망가트리는 경우가 많았죠.
결과는 바로 블루스크린이 뜨는거였고, 이 OS는 너무나도 유명한 윈9x 계열이랍니다.

그렇기때문에 현재의 NT기반 OS는 커널영역의 접근이 철저히 금지되어 있습니다. 하지만 요새 괴기스러운 팁이 나도는데, /PAE를 비롯한 각종 옵션으로, 메모리를 2G 넘어서까지 사용하게끔 해주는 팁이죠. 별 대단한것은 아닙니다. 메모리 영역이 다음과같이 변화됩니다.

0x00001000~0xBFFEFFFF : 사용자 영역 (사용자가 사용할수 있는 공간)
0xBFFF0000~0xBFFFFFFF : 64K 오프리밋 (구간간 경계선)
0xC0000000~0xFFFFFFFF : 커널 영역 (시스템이 사용할수 있는 구간)

즉 커널영역을 뺏어쓰는거밖에 안됩니다. 그리고 MS기술문서에는 커널영역의 감소로 인한 문제점 발생 경고를 하고 있습니다. 또한 대형서버급 몇몇 OS는 제외하고는 이 설정은, '모양만 똑같이 갖춰줄뿐 사용자 영역 메모리는 여전히 2G로 제한된다.' 라고 명시되어 있습니다. 개발자의 64비트 드라이버 개발 등을 비롯한 테스트 목적으로만 사용하라고 나와있죠.

즉, 64비트환경이 아니라면 4G램은 아무런 의미가 없습니다. 여전히 2G이상 사용 불가능입니다.
(참고 :
http://support.microsoft.com/kb/291988)


 보너스. NT(2000,XP..) 계열이 윈9x계열보다 메모리를 많이먹는 이유.

위에서 잠깐 언급만 했습니다만, 9x에서는 공유영역이란게 있습니다. kernel32.dll, user32.dll, gdi32.dll 등의 시스템 모듈들은 공유영역에 올라가게 되고, 응용프로그램들이 공유영역에 올라간 모듈들을 참조해서 사용하는데, 이건 결과론적으로 최악의 구성이 되어버렸습니다.

응용프로그램이 오류로 인해 죽을시 공유된 모듈을 함께 물고 죽인다는 치명적인 문제가 있었죠.
kernel32가 내려가버린 OS는 도대체 뭘까요? ㅎㅎ 바로 블루스크린 등장하시게 되는겁니다.

이때문에 NT계열에서는 공유영역이 사라지고, 공유영역에 올라가던 핵심 모듈들이, 프로세스의 수많큼 매번 로드되어 메모리에 적재됩니다. 때문에 프로세스 수만큼 메모리가 * 되어 사용되는 단점이 있지요.

반면, 오류 발생시 자신이 로드한 녀석만 죽기때문에, 전체 시스템은 원활하게 굴러가게 되는 구조가 되었습니다. 메모리의 낭비가 있긴 하지만, 아무래도 블루스크린없는 후자가 좋겠죠.

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. 가상메모리 기본( VirtualAlloc )

more..


2. MMF 기본

more..


3. MMF 를 이용한 그리기 통신( B가 A에게 오버헤드를 발생시킨다.ㅜ_ㅜ )

more..


4. DLL Inject

more..


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 23004
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. 정적 TLS

more..


2. 동적 TLS

more..


3. 알고리즘 시간을 측정

more..


4. 스레드에 메시지 루프 집어넣기

more..


5. 스레드에서 주 스레드로 메시지 보내기

more..


6. 공유메모리

more..


7. 공유메모리의 DLL화

more..



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 | ,
1. 뮤텍스

more..


2. 세마포어

more..


3. Event

more..


4. 생산자와 소비자

more..


5. STL list의 Thread Safe

more..


6. CriticalSection 단위전략 으로 사용해보기.

more..


7. 스레드 풀 ( 세마포어를 이용 )

more..


8. 프로세스 감시( 종료를 기달린다. )

more..


Tag | ,

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

from Study/System 2007/10/14 15:58 view 19934
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 | ,