실행 파일을 열 때마다 운영체제는 새 프로세스를 만든다. 프로세스는 실행중인 프로그램이며 메모리에 로딩. 고유의 프로세스 식별자(PID)를 가짐.
운영체제는 새 프로세스의 스폰과 로디을 담당하는 유일한 개체
프로세스를 생성할 때 운영체제는 프로세스를 위한 메모리를 가장 먼저 할당. 정의된 메모리 레이아웃을 적용.
일반적인 프로세스의 메모리 레이아웃은 분할. 각 부분을 세그먼트라고 함.
각 세그먼트는 정해진 작업을 수행하며 특정 자료형을 저장하는 메모리 영역
메모리 레이아웃에 포함되는 목록
어떤 세그먼트는 실행 가능한 목적 파일 내에 존재. 다른 세그먼트는 프로세스가 스폰되었을 때 프로그램이 런타임 동안 동적으로 생성된다.
실행 가능한 목절 파일은 프로세스와 같은 것이 아님!!!
- 실행 가능한 목적 파일 = 기계 명령어를 포함, 컴파일러에 의해 생성. 그저 파일에 불과 이 파일은 향후 프로세스를 스폰하는 토대가 되는, 미리 만들어진 초기 레이아웃 작업을 포함.
- 프로세스는 실행 중인 프로그램이며 실행 가능한 목적 파일에 의해 스폰된 것, 메인 메모리 영역을 소비. CPU는 계속 메모리의 명령어를 가져와서 실행. 운영체제 내에서 실행중인 개체
실행 중인 프로세스의 메모리 레이아웃에서, 몇몇 세그먼트는 토대가 되는 실행 가능한 목적파일에서 직접 만든다. = 정적 메모리 레이아웃
다른 세그먼트는 프로세스가 로딩될 때, 프로그램이 실행되는 동안 동적으로 생성 = 동적 메모리 레이아웃
정적 메모리 레이아웃과 동적 메모리 레이아웃은 사전에 정의된 세그먼트의 집합.
정적 메모리 레이아웃의 내용 소스 코드를 컴파일할 때 컴파일러가 실행 가능한 목적 파일에 미리 작성
동적 메모리 레이아웃의 내용은 프로그램의 명령어에 따라 작성
소스 코드나 컴파일된 목적파일만 봐도 정적 메모리 레이아웃의 내용 추측 가능. 동적 메모리는 X
gcc examples_ch4_1.c -o ex4_1-linux.out
는 리눅스 운영 체제 전용으로 미리 정의된 정적 메모리 레이아웃을 포함. 이 실행 파일에 기반해 스폰되는 모든 프로세스에 이 정적 메모리 레이아웃 존재
size
명령어: 실행 가능한 목적 파일의 정적 메모리 레이아웃을 출력할 때 사용.(maxOS는 리눅스와 같이 POSIX준수 운영체제라 size 사용가능)
텍스트, 데이터, BSS 세그먼트 = 정적 레이아웃에 속함. 바이트 단위
BSS 세그먼트는 초기화되지 않은 전역 변수를 포함. 목적 파일에서 바이트를 할당할 필요 없음, 전역 변수를 저장하는데 필요한 바이트가 얼마인지 아는 것으로 충분.
텍스트 세그먼트의 크기는 리눅스와 macOs에서 다름. 저수준 메모리의 세부 사항은 플랫폼마다 다름.
심벌로 시작되는 블록
초기화되지 않은 워드를 위해 예약된 영역을 나타내고자 사용
기본적으로 초기화되지 않은 전역 변수나 0으로 설정된 전역 변수에 사용
프로세스가 로딩될 때 미리 할당. 프로세스가 살아있는 한 절대로 할당 해제 X = 정적인 수명
함수 내부에서 선언된 정적 변수는 같은 함수가 여러 번 호출되는 동안 그 값을 유지. 정적 변수는 플랫폼 및 초기화 여부에 따라 데이터 세그먼트 또는 BSS 세그먼트에 저장될 수 있습니다. 함수 내부에서 정적 변수가 선언되는 방식을 나타냄.
func 함수에 들어갔다 나오는 횟수와 무관, 최신 값을 유지.
-> 런타임 시 func 함수는 데이터 세그먼트 또는 BSS 세그먼트에 위치한 두 변수로 접근. = 정적 변수. 변수가 초깃값을 가지면 데이터 세그먼트에 위치, 초기화되지 않으면 BSS 세그먼트 내에 있어야함
linux : objdump
, macOS: gobjdump
. 명령어를 사용해 BSS 세그먼트의 내용을 검사할 수 있음.
.data읽는 법 있음
엔디언
1. 빅 엔디언: 0x12153467은 0x12153467을 나타내는 빅 엔디언 가장 큰 바이트인 0x12 맨 앞
- 리틀 엔디언 0x67341512는 0x12153467을 나타내는 리틀 엔디언. 가장 작은 바이트인 0x67이 맨 앞에
엔디언은 CPU의 속성, CPU가 다르면 최종 목적 파일에서 바이트 순서가 달라짐. 다른 엔디언이 있는 하드웨어에서 실행 가능한 목적 파일 실행X
링커는 컴파일 결과로 얻은 기계 수준의 명령어를 최종 실행 가능한 목적 파일로 작성.
텍스트 세그먼트, 코드 세그먼트는 프로그램의 모든 기계 수준 명령어 포함. 이 명령어는 정적 메모리 레이아웃에 속해 실행 가능한 목적 파일에 위치해야함. 이 명령어는 프로세서가 가져와서 프로세스가 실행될 때 함께 실행된다.
실행 가능한 목적 파일의 여러 부분을 덤프 objdump
사용 가능(리눅스)
gcc examples_ch4_5.c -o ex4_5.out
후 objdump -S ex4_5.out
로 섹션의 내용 확인 가능
기계 수준의 명령어 포함 = .text
,.init
, .plt
와 같은 다양한 섹션
섹션 모두 프로그램이 로드 및 실행될 수 있도록
실행 가능한 목적 파일 내부에 있는 정적 메모리 레이아웃에 나타나는 동일한 텍스트 세그먼트에 속함.
최종 실행 가능한 목적 파일에는 다른 여러 함수 있음.
main함수가 C 프로그램에서 호출되는 첫 번째 함수가 아니라는 점을 보여줌. main 함수 전후로 실행되는 로직이 있음! 리눅스에서 이러한 함수는 glibc
라이브러리에서 빌려옴. 링커가 이들 모두 합쳐 최종 실행 가능한 목적 파일을 형성
실제로 프로세스의 런타임 메모리이며 프로세스가 실행되는 동안 존재. 로더가 호출한 프로그램은 실행을 처리.
프로세스를 스폰하고 초기 메모리 레이아웃을 생성하며 이는 동적 메모리 레이아웃이 된다.
이를 만들기 위해 정적 레이아웃에 있는 세그먼트는 실행 가능한 목적 파일로 복제. 새로운 세그먼트 2개 더해짐
실행 중인 프로세스의 메모리 레이아웃 = 세그먼트 5개
3개 - 실행 가능한 목적 파일에 있는 정적 레이아웃에서 직접 복제된 것
새로 추가된 2개 = 스택, 힙 세그먼트 . 이는 동적이며 프로세스가 실행 중일때만 존재
프로세스의 동적 메모리 레이아웃은 5개의 세그먼트 모두 포함해 구성
스택 세그먼트 = 변수가 저장되는 기본 메모리 영역. 크기가 제한. 큰 객체 둘 수 없음
힙 세그먼트 더 크고 조정 가능한 메모리 영역이며 큰 객체나 변수 담을 수 있음. 작업하기 위해 고유 API 필요
동적 메모리 레이아웃은 동적 메모리 할당과 다름!!!
5개의 세그먼트는 메인 메모리에 속하는 부분을 참조. 이는 실행 중인 프로세스에 이미 할당된, 전용 메모리이다. 텍스트 세그먼트를 제외한 이들 세그먼트는, 런타임 시에 내용이 항상 변하므로 동적이다.
프로세스가 실행되는 동안 알고리듬에 의해 이들 세그먼트가 계속수정
프로세스의 동적 메모리 레리아웃 검사 = 프로세스를 실행해야함
무한 루프 도는 코드 백그라운드로 실행 (./ex4_6.out &
)
PID: 프로세스의 메모리를 검사하기 위해 사용하는 식별자.
각 메모리 매핑은 프로세스에 속하는 특정 파일이나 세그먼트에 매핑되는 메모리의 전용 영역을 나타냄 = 스택과 힙 세그먼트 모두 각 프로세스에서 고유한 메모리 매핑을 갖는다.
procfs
사용 - 현재 메모리 매핑 관찰 가능
ls -l /proc/PID번호
의 내용 각각 프로세스의 특정 속성에 해당. 프로세스의 메모리 매핑을 쿼리하려면 PID 경로 아래에 있는 maps 파일의 내용을 확인해야함.
ls -l /proc/PID번호/maps
내용 중 각 행은 프로세스의 동적 메모리 레이아웃의 특정 파일 및 세그먼트에 할당 및 매핑되는 메모리 주소(영역)의 범위를 가르킴
스택은 모든 프로세스의 동적 메모리에서 매우 중요. 거의 모든 아키텍처에 존재
스택과 힙은 둘 다 프로세스가 실행되는 동안 계속 변하는 동적인 내용 가짐
이를 검사하고 읽으려면 gdb와 같은 디버거 필요
스택 세그먼트는 대체로 크기가 제한. 큰 객체를 저장하기에 좋은 장소가 아님. 스택 세그먼트가 가득 찼다면 프로세스는 더 이상 함수 호출을 실행할 수 없다. 함수 호출 메커니즘은 스택 세그먼트의 기능에 매우 의존
스택 오버플로: 스택 세그먼트가 가득 찼을 때 발생하는 오류
컴파일러는 기본적으로 스택 세그먼트를 사용합니다. 스택 세그먼트는 할당이 이루어지는 첫 번째 장소.
지역 변수를 선언. 지역 변수는 스택 세그먼트의 가장 윗부분에 할당. 선언된 지역 변수의 범위를 떠난다면, 컴파일러는 선언된 지역 변수를 바깥 범위로 꺼내기 위해 가장 앞의 지역 변수를 팝해야함.
변수는 스택 세그먼트에 저장되는 유일한 개체가 아니다. 함수를 호출할마다 스택 프레임이 호출한 새 엔트리가 스택 세그먼트의 위에 놓인다. 호출한 함수로 돌아가거나 결과물을 호출자로 반환할 수 없음.
스택의 크기는 제한. 작은 변수를 선얺는 편이 제일 좋다. 무한한 재귀호출 또는 함수 호출을 지나치게 해서 스택이 너무 많은 스택 프레임으로 가득 차도 안된다.
스택 세그먼트는 여러분의 데이터를 보관하고 알고리듬에서 사용하는 지역 변수를 선언하기 위해 사용하기 장소이다. 프로그램의 실행자인 운영체제가 프로그램을 성공적으로 실행하는 내부적인 메커니즘에 필요한 데이터를 보관하는 장소
스택 세그먼트를 잘못 사용하거나 데이터를 오염시키면 실행 프로세스를 방해하거나 심지어 충돌이 발생하기 때문.
스택은 해당 프로세스 전용이므로 다른 프로세스가 이를 읽거나 수정할 수 없다.
힙 세그먼트에 할당된 지역을 찾기 위해 메모리 매핑을 어떻게 사용하는지 보여줌.
무한 루프에 들어가기 전에 힙 세그먼트에서 다수의 바이트를 할당.
실행 프로세스에서 메모리 매핑을 검사할 수 있으며 어느 매핑이 힙 세그먼트를 참조하는지도 확인
malloc 함수
힙 세그먼트에 추가 메모리를 할당하는 기본 방식. 할당되어야 하는 바이트의 수를 받아서 제네릭 포인터로 반환
제네릭 포인터(혹은 void 포인터)는 메모리 주소를 포함. 역참조 될 수 없으며 직접 사용될 수 없습니다. 특정 포인터형으로 형변환되어야 함.
malloc
함수 사용-> [heap]이라고 새로운 매핑 볼 수 있음.
1KB 할당을 위해 132KB가 할당되어있다. 이는 주로 malloc을 나중에 다시 사용할 때 메모리를 추가로 할당하지 않고자 수행된다. 힙 세그먼트에서 메모리 할당에 드는 비용은 저렴하지 않은데, 힙 세그먼트가 메모리 및 시간 오버헤드를 갖기 때문.
ptr 포인터가 가리키는 주소. 힙의 메모리 매핑 0x@@@@@@@@ ~ 0x ######## 까지의 주소로 할당되어 있으며, ptr에 저장된 주소는 힙 범위 내에 있으며 16바이트의 오프셋만큼 떨어져 위치
스택 세그먼트의 가장 윗부분에는 지역 변수가 있으며 메모리로 직접 상호작용하고 사용할 수 있지만, 힙 메모리는 포인터를 통해서만 접근할 수 있다.
지역 포인터 변수인 gptr
과 ptr
은 스택에 할당되었으니 주의. 이들 포인터는 값을 저장할 메모리가 필요하고, 이 메모리는 스택 세그먼트에서 가져온다. 하지만 이 포인터가 가리키는 주소는 힙 세그먼트 내부에 있다. 이것이 힙 세그먼트로 작업할 때의 문제이다. 스택 세그먼트에서 할당받은 지역 포인터를 갖지만, 그 포인터는 실제로 힙 세그먼트에 할당된 지역을 가리킴.
힙 메모리는 프로그램 또는 실제로 개발자가 메모리 할당을 담당.
접근 불가능한 힙 메모리를 할당받는 것은 메모리 누수로 간주. 접근이 불가능하므로 해당 영역의 주소를 나타내기 위해 사용할 수 있는 포인터가 없다는 의미
메모리 누수 증가 -> 허용된 메모리 공간 전체를 다 써서 프로세스가 종료될 수 있음. 메모리 누수 치명적!!!
free
함수 호출 확보했던 힙 메모리 블록을 해제하고, 프로그램은 해당 힙 주소를 더 이상 사용하지 못한다.