Part2) CH4. 프로세스 메모리 구조

songtofu·2022년 11월 20일
0

전문가를 위한 C

목록 보기
4/10

앞서

  • 스택과 힙은 프로세스의 동적 메모리 레이아웃에 속함. 모든 메모리 할당과 해제는 이 세그먼트에서 일어남

4.1) 프로세스 메모리 레이아웃

  • 실행 파일을 열 때마다 운영체제는 새 프로세스를 만든다. 프로세스는 실행중인 프로그램이며 메모리에 로딩. 고유의 프로세스 식별자(PID)를 가짐.

  • 운영체제는 새 프로세스의 스폰과 로디을 담당하는 유일한 개체

  • 프로세스를 생성할 때 운영체제는 프로세스를 위한 메모리를 가장 먼저 할당. 정의된 메모리 레이아웃을 적용.

  • 일반적인 프로세스의 메모리 레이아웃은 분할. 각 부분을 세그먼트라고 함.

  • 각 세그먼트는 정해진 작업을 수행하며 특정 자료형을 저장하는 메모리 영역

  • 메모리 레이아웃에 포함되는 목록

    1. 초기화되지 않은 데이터 세그먼트 or BSS 세그먼트
    2. 데이터 세그먼트
    3. 텍스트 세그먼트 또는 코드 세그먼트
    4. 스택 세그먼트
    5. 힙 세그먼트

    4.2) 메모리 구조 알아보기

  • 어떤 세그먼트는 실행 가능한 목적 파일 내에 존재. 다른 세그먼트는 프로세스가 스폰되었을 때 프로그램이 런타임 동안 동적으로 생성된다.

  • 실행 가능한 목절 파일은 프로세스와 같은 것이 아님!!!

    • 실행 가능한 목적 파일 = 기계 명령어를 포함, 컴파일러에 의해 생성. 그저 파일에 불과 이 파일은 향후 프로세스를 스폰하는 토대가 되는, 미리 만들어진 초기 레이아웃 작업을 포함.
      - 프로세스는 실행 중인 프로그램이며 실행 가능한 목적 파일에 의해 스폰된 것, 메인 메모리 영역을 소비. CPU는 계속 메모리의 명령어를 가져와서 실행. 운영체제 내에서 실행중인 개체
  • 실행 중인 프로세스의 메모리 레이아웃에서, 몇몇 세그먼트는 토대가 되는 실행 가능한 목적파일에서 직접 만든다. = 정적 메모리 레이아웃

  • 다른 세그먼트는 프로세스가 로딩될 때, 프로그램이 실행되는 동안 동적으로 생성 = 동적 메모리 레이아웃

  • 정적 메모리 레이아웃과 동적 메모리 레이아웃은 사전에 정의된 세그먼트의 집합.

  • 정적 메모리 레이아웃의 내용 소스 코드를 컴파일할 때 컴파일러가 실행 가능한 목적 파일에 미리 작성

  • 동적 메모리 레이아웃의 내용은 프로그램의 명령어에 따라 작성

  • 소스 코드나 컴파일된 목적파일만 봐도 정적 메모리 레이아웃의 내용 추측 가능. 동적 메모리는 X

    4.3) 정적 메모리 레이아웃 검사

  • gcc examples_ch4_1.c -o ex4_1-linux.out는 리눅스 운영 체제 전용으로 미리 정의된 정적 메모리 레이아웃을 포함. 이 실행 파일에 기반해 스폰되는 모든 프로세스에 이 정적 메모리 레이아웃 존재

  • size 명령어: 실행 가능한 목적 파일의 정적 메모리 레이아웃을 출력할 때 사용.(maxOS는 리눅스와 같이 POSIX준수 운영체제라 size 사용가능)

  • 텍스트, 데이터, BSS 세그먼트 = 정적 레이아웃에 속함. 바이트 단위

  • BSS 세그먼트는 초기화되지 않은 전역 변수를 포함. 목적 파일에서 바이트를 할당할 필요 없음, 전역 변수를 저장하는데 필요한 바이트가 얼마인지 아는 것으로 충분.

  • 텍스트 세그먼트의 크기는 리눅스와 macOs에서 다름. 저수준 메모리의 세부 사항은 플랫폼마다 다름.

    4.3.1. BSS 세그먼트

  • 심벌로 시작되는 블록

  • 초기화되지 않은 워드를 위해 예약된 영역을 나타내고자 사용

  • 기본적으로 초기화되지 않은 전역 변수나 0으로 설정된 전역 변수에 사용

  • 프로세스가 로딩될 때 미리 할당. 프로세스가 살아있는 한 절대로 할당 해제 X = 정적인 수명

    4.3.2 데이터 세그먼트

  • 함수 내부에서 선언된 정적 변수는 같은 함수가 여러 번 호출되는 동안 그 값을 유지. 정적 변수는 플랫폼 및 초기화 여부에 따라 데이터 세그먼트 또는 BSS 세그먼트에 저장될 수 있습니다. 함수 내부에서 정적 변수가 선언되는 방식을 나타냄.

  • func 함수에 들어갔다 나오는 횟수와 무관, 최신 값을 유지.
    -> 런타임 시 func 함수는 데이터 세그먼트 또는 BSS 세그먼트에 위치한 두 변수로 접근. = 정적 변수. 변수가 초깃값을 가지면 데이터 세그먼트에 위치, 초기화되지 않으면 BSS 세그먼트 내에 있어야함

  • linux : objdump, macOS: gobjdump. 명령어를 사용해 BSS 세그먼트의 내용을 검사할 수 있음.

  • .data읽는 법 있음

    엔디언
    1. 빅 엔디언: 0x12153467은 0x12153467을 나타내는 빅 엔디언 가장 큰 바이트인 0x12 맨 앞

    1. 리틀 엔디언 0x67341512는 0x12153467을 나타내는 리틀 엔디언. 가장 작은 바이트인 0x67이 맨 앞에
  • 엔디언은 CPU의 속성, CPU가 다르면 최종 목적 파일에서 바이트 순서가 달라짐. 다른 엔디언이 있는 하드웨어에서 실행 가능한 목적 파일 실행X

    4.3.3. 텍스트 세그먼트

  • 링커는 컴파일 결과로 얻은 기계 수준의 명령어를 최종 실행 가능한 목적 파일로 작성.

  • 텍스트 세그먼트, 코드 세그먼트는 프로그램의 모든 기계 수준 명령어 포함. 이 명령어는 정적 메모리 레이아웃에 속해 실행 가능한 목적 파일에 위치해야함. 이 명령어는 프로세서가 가져와서 프로세스가 실행될 때 함께 실행된다.

  • 실행 가능한 목적 파일의 여러 부분을 덤프 objdump사용 가능(리눅스)

  • gcc examples_ch4_5.c -o ex4_5.outobjdump -S ex4_5.out 로 섹션의 내용 확인 가능

  • 기계 수준의 명령어 포함 = .text,.init, .plt와 같은 다양한 섹션

  • 섹션 모두 프로그램이 로드 및 실행될 수 있도록

  • 실행 가능한 목적 파일 내부에 있는 정적 메모리 레이아웃에 나타나는 동일한 텍스트 세그먼트에 속함.

  • 최종 실행 가능한 목적 파일에는 다른 여러 함수 있음.

  • main함수가 C 프로그램에서 호출되는 첫 번째 함수가 아니라는 점을 보여줌. main 함수 전후로 실행되는 로직이 있음! 리눅스에서 이러한 함수는 glibc라이브러리에서 빌려옴. 링커가 이들 모두 합쳐 최종 실행 가능한 목적 파일을 형성

    4.4) 동적 메모리 레이아웃 검사

  • 실제로 프로세스의 런타임 메모리이며 프로세스가 실행되는 동안 존재. 로더가 호출한 프로그램은 실행을 처리.

  • 프로세스를 스폰하고 초기 메모리 레이아웃을 생성하며 이는 동적 메모리 레이아웃이 된다.

  • 이를 만들기 위해 정적 레이아웃에 있는 세그먼트는 실행 가능한 목적 파일로 복제. 새로운 세그먼트 2개 더해짐

  • 실행 중인 프로세스의 메모리 레이아웃 = 세그먼트 5개

  • 3개 - 실행 가능한 목적 파일에 있는 정적 레이아웃에서 직접 복제된 것

  • 새로 추가된 2개 = 스택, 힙 세그먼트 . 이는 동적이며 프로세스가 실행 중일때만 존재

  • 프로세스의 동적 메모리 레이아웃은 5개의 세그먼트 모두 포함해 구성

  • 스택 세그먼트 = 변수가 저장되는 기본 메모리 영역. 크기가 제한. 큰 객체 둘 수 없음

  • 힙 세그먼트 더 크고 조정 가능한 메모리 영역이며 큰 객체나 변수 담을 수 있음. 작업하기 위해 고유 API 필요

  • 동적 메모리 레이아웃은 동적 메모리 할당과 다름!!!

  • 5개의 세그먼트는 메인 메모리에 속하는 부분을 참조. 이는 실행 중인 프로세스에 이미 할당된, 전용 메모리이다. 텍스트 세그먼트를 제외한 이들 세그먼트는, 런타임 시에 내용이 항상 변하므로 동적이다.

  • 프로세스가 실행되는 동안 알고리듬에 의해 이들 세그먼트가 계속수정

  • 프로세스의 동적 메모리 레리아웃 검사 = 프로세스를 실행해야함

    4.4.1. 메모리 매핑

  • 무한 루프 도는 코드 백그라운드로 실행 (./ex4_6.out &)

  • PID: 프로세스의 메모리를 검사하기 위해 사용하는 식별자.

  • 각 메모리 매핑은 프로세스에 속하는 특정 파일이나 세그먼트에 매핑되는 메모리의 전용 영역을 나타냄 = 스택과 힙 세그먼트 모두 각 프로세스에서 고유한 메모리 매핑을 갖는다.

  • procfs사용 - 현재 메모리 매핑 관찰 가능

  • ls -l /proc/PID번호의 내용 각각 프로세스의 특정 속성에 해당. 프로세스의 메모리 매핑을 쿼리하려면 PID 경로 아래에 있는 maps 파일의 내용을 확인해야함.

  • ls -l /proc/PID번호/maps 내용 중 각 행은 프로세스의 동적 메모리 레이아웃의 특정 파일 및 세그먼트에 할당 및 매핑되는 메모리 주소(영역)의 범위를 가르킴

    4.4.2 스택 세그먼트

  • 스택은 모든 프로세스의 동적 메모리에서 매우 중요. 거의 모든 아키텍처에 존재

  • 스택과 힙은 둘 다 프로세스가 실행되는 동안 계속 변하는 동적인 내용 가짐

  • 이를 검사하고 읽으려면 gdb와 같은 디버거 필요

  • 스택 세그먼트는 대체로 크기가 제한. 큰 객체를 저장하기에 좋은 장소가 아님. 스택 세그먼트가 가득 찼다면 프로세스는 더 이상 함수 호출을 실행할 수 없다. 함수 호출 메커니즘은 스택 세그먼트의 기능에 매우 의존

  • 스택 오버플로: 스택 세그먼트가 가득 찼을 때 발생하는 오류

  • 컴파일러는 기본적으로 스택 세그먼트를 사용합니다. 스택 세그먼트는 할당이 이루어지는 첫 번째 장소.

  • 지역 변수를 선언. 지역 변수는 스택 세그먼트의 가장 윗부분에 할당. 선언된 지역 변수의 범위를 떠난다면, 컴파일러는 선언된 지역 변수를 바깥 범위로 꺼내기 위해 가장 앞의 지역 변수를 팝해야함.

  • 변수는 스택 세그먼트에 저장되는 유일한 개체가 아니다. 함수를 호출할마다 스택 프레임이 호출한 새 엔트리가 스택 세그먼트의 위에 놓인다. 호출한 함수로 돌아가거나 결과물을 호출자로 반환할 수 없음.

  • 스택의 크기는 제한. 작은 변수를 선얺는 편이 제일 좋다. 무한한 재귀호출 또는 함수 호출을 지나치게 해서 스택이 너무 많은 스택 프레임으로 가득 차도 안된다.

  • 스택 세그먼트는 여러분의 데이터를 보관하고 알고리듬에서 사용하는 지역 변수를 선언하기 위해 사용하기 장소이다. 프로그램의 실행자인 운영체제가 프로그램을 성공적으로 실행하는 내부적인 메커니즘에 필요한 데이터를 보관하는 장소

  • 스택 세그먼트를 잘못 사용하거나 데이터를 오염시키면 실행 프로세스를 방해하거나 심지어 충돌이 발생하기 때문.

  • 스택은 해당 프로세스 전용이므로 다른 프로세스가 이를 읽거나 수정할 수 없다.

    4.4.3. 힙 세그먼트

  • 힙 세그먼트에 할당된 지역을 찾기 위해 메모리 매핑을 어떻게 사용하는지 보여줌.

  • 무한 루프에 들어가기 전에 힙 세그먼트에서 다수의 바이트를 할당.

  • 실행 프로세스에서 메모리 매핑을 검사할 수 있으며 어느 매핑이 힙 세그먼트를 참조하는지도 확인

  • malloc 함수 힙 세그먼트에 추가 메모리를 할당하는 기본 방식. 할당되어야 하는 바이트의 수를 받아서 제네릭 포인터로 반환

  • 제네릭 포인터(혹은 void 포인터)는 메모리 주소를 포함. 역참조 될 수 없으며 직접 사용될 수 없습니다. 특정 포인터형으로 형변환되어야 함.

  • malloc함수 사용-> [heap]이라고 새로운 매핑 볼 수 있음.

  • 1KB 할당을 위해 132KB가 할당되어있다. 이는 주로 malloc을 나중에 다시 사용할 때 메모리를 추가로 할당하지 않고자 수행된다. 힙 세그먼트에서 메모리 할당에 드는 비용은 저렴하지 않은데, 힙 세그먼트가 메모리 및 시간 오버헤드를 갖기 때문.

  • ptr 포인터가 가리키는 주소. 힙의 메모리 매핑 0x@@@@@@@@ ~ 0x ######## 까지의 주소로 할당되어 있으며, ptr에 저장된 주소는 힙 범위 내에 있으며 16바이트의 오프셋만큼 떨어져 위치

  • 스택 세그먼트의 가장 윗부분에는 지역 변수가 있으며 메모리로 직접 상호작용하고 사용할 수 있지만, 힙 메모리는 포인터를 통해서만 접근할 수 있다.

  • 지역 포인터 변수인 gptrptr은 스택에 할당되었으니 주의. 이들 포인터는 값을 저장할 메모리가 필요하고, 이 메모리는 스택 세그먼트에서 가져온다. 하지만 이 포인터가 가리키는 주소는 힙 세그먼트 내부에 있다. 이것이 힙 세그먼트로 작업할 때의 문제이다. 스택 세그먼트에서 할당받은 지역 포인터를 갖지만, 그 포인터는 실제로 힙 세그먼트에 할당된 지역을 가리킴.

  • 힙 메모리는 프로그램 또는 실제로 개발자가 메모리 할당을 담당.

  • 접근 불가능한 힙 메모리를 할당받는 것은 메모리 누수로 간주. 접근이 불가능하므로 해당 영역의 주소를 나타내기 위해 사용할 수 있는 포인터가 없다는 의미

  • 메모리 누수 증가 -> 허용된 메모리 공간 전체를 다 써서 프로세스가 종료될 수 있음. 메모리 누수 치명적!!!

  • free 함수 호출 확보했던 힙 메모리 블록을 해제하고, 프로그램은 해당 힙 주소를 더 이상 사용하지 못한다.

profile
읽으면 머리에 안들어와서 직접 쓰는 중. 잘못된 부분 지적 대환영

0개의 댓글