4 분 소요

프로세스의 메모리 구조

Image

  • Code
    프로그래머가 작성한 ‘소스 코드’가 ‘기계어’로 변환되어 저장되는 영역
    • CPU가 읽는 명령어들
    • 읽기 전용(Read-Only)!
      프로그램 실행 중, 변경되면 안되므로
    • 컴파일 타임에 크기 결정
      • 일반적인 C++ 빌드 파이프라인
        전처리기 -> 컴파일러 -> 어셈블러 -> 링커
  • Data
    전역 변수와 정적 변수가 저장되는 영역
    • Data : 값이 초기화(할당)된 변수들 저장
    • BSS(Block Started by Symbol)
      초기화되지 않은 변수들 저장
      (실행 시, 0으로 자동 초기화)
  • Heap
    프로그래머가 필요로 인해 동적으로 할당하는 메모리가 저장되는 영역
    (ex : New / Malloc 등)
    • ‘런타임’ 중에 ‘실제 필요한’ 메모리를 알 수 있음
    • ‘낮은 주소’에서 ‘높은 주소’로 자라남
  • Stack
    함수 지역 변수, 매개 변수 등이 저장되는 영역
    • ‘스택’ 자료구조처럼 LIFO 구조
    • ‘높은 주소’와 ‘낮은 주소’로 자람
      (힙과 반대!)

관련 개념들!

스택 오버플로우(Stack Overflow)

  • 스택 영역은 크기가 ‘고정’되어 있으며
    이 크기를 넘어설 때 발생하는 현상
    • 체크 방식?
      가드 페이지
      스택 크기 바로 다음 페이지에
      ‘접근 권한’이 없는 페이지를 넣고
      이 페이지에 접근하려 하면
      Page Fault 를 발생시켜 OS가 Kill할 수 있게 함
  • 원인과 해결 방법
    • 깊은 재귀 호출
      해결법
      • 재귀 종료 조건 체크
      • 반복문을 사용
      • ‘꼬리 재귀’ 사용
        (마지막 줄에 return 하며 함수 호출하여 return 주소를 남기지 않아
        스택 영역 해제 시도)
        (컴파일러 설정 필요 + 지원하는 컴파일러가 없을수도 있음)
    • 너무 큰 지역 변수 사용
      • Heap 영역을 통한 동적할당을 이용하도록

Stack Vs Heap?

  • 스택은 ‘컴파일 시간’에 크기가 결정되는 변수들이 보관되는 영역
    • ‘함수 호출’ 자체는 매개변수, 지역변수, 함수 리턴 주소 등이 보관되며
      이러한 ‘함수의 크기’는 ‘컴파일 시간’에 결정될 수 있음
    • 이러한 스택의 할당은 ‘스택 포인터’라는 레지스터를 이용하기에
      무척 빠름
  • 힙은 사용자가 할당이 끝날 시 직접 해제해야 함!
    • 프로그래머의 관리 필요
    • 해제하지 않을 시, ‘메모리 누수’가 발생하여
      계속 힙 영역을 잡아먹음
    • Heap 영역을 해제하였다면 관련 포인터도 정리하는 것을 권장
      (Heap 영역 해제 후에도 여전히 포인터는 그 영역을 가리킴)
    • 이러한 포인터를 ‘댕글링 포인터’라 하며
      가리키는 공간에 접근 시, UB(정의되지 않은 동작) 발생 가능

TMI : 메모리 누수를 잡을 수 있는 방법?

  • CRT 라이브러리 사용
#define _CRTDBG_MAP_ALLOC // 파일명과 라인 넘버를 표시하기 위해 필수
#include <cstdlib>
#include <crtdbg.h>

#ifdef _DEBUG
    #define new new( _NORMAL_BLOCK, __FILE__, __LINE__ )
#endif

int main() {
    // 프로그램 종료 시 자동으로 누수를 체크하도록 설정
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    int* leak = new int[10]; // 해제 안 함 (누수 발생!)

    // _CrtDumpMemoryLeaks(); // 플래그를 안 썼다면 이걸 직접 호출해야 함
    return 0;
}
  • VS 등에서 메모리/성능 프로파일러 등을 사용하기
    • 기능 실행 전과 후를 비교
  • AddressSantizer?(ASan)
    구글이 만들고 MSVC에 내장된 최신 도구
    (프로젝트 속성 -> C/C++ -> 일반 -> 주소 삭제 사용)
    • 메모리 누수나 오류 발생 시, ‘에러 리포트’를 띄움

가상 메모리 공간

  • 프로세스가 크고 연속적인 메모리에서
    CPU와 독대한다고 생각하는 ‘공간’

  • 프로세스는 ‘실제 메모리 주소’를 모르며
    자신에게 주어진 ‘가상 메모리 주소’를 기반으로 이야기

  • CPU는 Page Table과 MMU를 통해
    가상 메모리와 매핑된 ‘물리 메모리 주소’를 찾아 작업 수행

메모리를 이렇게 나누는 이유?

  • 메모리 보호와 효율성
    • Code 영역은 ‘읽기 전용’으로 선언하여 변경 불가
    • Stack과 Heap 영역은 서로 반대 방향으로 성장하여
      메모리 공간을 최대로 이용하도록!

컴파일 시간에 ‘크기’가 정해지는 영역?

  • 기본적인 요점은 ‘컴파일 시간’에
    ‘크기’를 알 수 있는지

  • Code/Data
    • Code는 소스코드가 기계어 명령에 의해 번역된 것
      런타임 중에 변할 수 없도록 ‘읽기 전용’
    • Data에 보관되는 정적/전역 변수들은 ‘자료형’이 저장되어 있기에
      컴파일러가 크기를 알 수 있음
  • Stack?
    • 호출할 함수의 크기와 Stack 총량은 컴파일 시간에 정해짐
    • 그러나 실제 stack의 크기는 함수 호출에 따라 늘어나고 줄어듦
      (Stack Counter 레지스터로 인해)

가상 메모리, 물리 메모리 매핑

  • 가상 메모리는 ‘페이지’, 물리 메모리는 ‘프레임’ 이라는 고정 크기 블록으로 나눔
    이를 페이지 테이블을 통해 매핑
    mmu라는 장치를 통해 호출된 가상 주소의 실제 물리 메모리 위치를 찾아
    데이터를 가져옴
    (TLB라는 추가적인 캐시를 통해, 최근 사용한 주소를 저장)
    • 페이지 폴트?
      페이지 테이블 엔트리에서 ‘유효 비트’를 확인하고, 이 부분이 손상되면
      발생 (그 외에도 유효하지 않은 접근시 발생)
      • 유효한 접근이라면, 디스크에서 다시 데이터를 로드함
      • 그렇지 않다면, 프로세스 킬

지연 할당 (Lazy Allocation)

  • OS는 New,Malloc 등을 호출하여도 실제 Ram에서 바로 물리 메모리를 매핑하진 않음
    • 실제 그 메모리에 ‘접근’ 시도 했을 때
      페이지 폴트를 발생하면
      그제서야 할당을 준비함
    • 물리 메모리 할당 시도(비어있거나 페이지 교체 정책에 의한 선정된 페이지)
      -> 디스크에서 데이터를 읽거나, 초기화하여 물리 메모리를 할당
      -> 이후 페이지 테이블을 갱신
  • Stack의 Reserve, Commited 명령어와도 연관
    • Reserve : 예약 (초기에 1MB 할당하지만, 실제 RAM 사용은 아님)
    • Commited : 할당 (실제 Ram에 매핑)

Window 메모리 구성

  • image : 실행 파일이나 라이브러리가 로드된 영역 (code,data)
  • mapped : 파일과 메모리 가 연결된 영역 (대용량 파일이나 공유 메모리에 사용)
  • private : 프로세스 혼자만 사용 (stack,heap)

커널 (Kernel)

  • OS가 관리하며 ‘자원’을 직접 제어하는 부분
  • 일반적인 프로그램이 하드웨어를 직접 건드릴 ‘권한’이 없음
    그렇기에 이러한 부분은 ‘커널’에게 요청
    (‘시스템 콜’)

  • 모든 프로세스의 ‘상위’ 주소엔 ‘커널 영역’이 매핑되어 있음
    (접근시 페이지 폴트 발생하며, 유효하지 않은 접근으로 Kill)

  • 시스템 콜 발생시, User Mode -> Kernel Mode로 전환되어
    커널 영역에서 관련 코드를 실행

TMI : Null은 커널 영역?

  • null은 커널 영역 접근으로 Kill 되는 것이 아니다!
    ‘특별한 사용자 영역 취급’

  • 주소상 사용자 영역이지만 ‘접근 권한’이 ‘없음’
    (물리 메모리 매핑 시도도 X)

  • 유효 비트가 0인 특수한 주소

  • 접근시, Page Fault 발생하고
    커널이 Null 주소임을 확인하고 kill

  • Null은 ‘합의된’ 공간인 특수성이 있음

관련 포스팅

OSS 관련 1
OSS 관련 2

댓글남기기