CS/컴퓨터운영체제

OS(0516) - Memory Management

goliot 2023. 5. 16. 11:53
반응형

* a.c와 b.c를 컴파일하면 -> a.o, b.o가 생성됨 -> linker가 이들을 묶어 hello.exe 로 만들어줌

> linker는 각각의 external 변수가 잘 선언되어 있는지 확인

> 각 파일에 있는 symbol table을 linker가 합쳐줌

 

 

#Classifying Information Stored in Memory

- 프로그램 내 역할에 따른 분류:

1. 프로그램 명령어(변경 불가) - text area, 공유됨

2. 상수: pi, maxnum, printf/scanf에서 사용되는 문자열 등(변경 불가)

3. 변수: 로컬 변수(stack), 전역 변수, 함수 매개변수(stack), 동적 할당된 저장소(malloc or new)(heap) -> 변경 가능, 초기화 돼있을수도 아닐수도 - data area

- 보호 상태에 따른 분류:

1. 읽기 및 쓰기 가능: 변수

2. 읽기 전용: 코드, 상수

    > 데이터 및 코드 공유에 중요

- 주소 vs  데이터:

    > 프로그램을 이동시키는 경우 주소를 수정해야 함(재배치, 가비지 콜렉션 - 더이상 쓰지 않는 자원 반납)

        > swap in, swap out 시에 주소 수정

 

#Memory management in a Uniprogrammed System

- OS는 고정된 메모리 세그먼트(일반적으로 가장 높은 메모리)를 할당받음

- 한 번에하나의 프로세스가 단일 메모리 세그먼트에서 실행됨

    > ★프로세스는 항상 주소 0 에서 로드됨 -> 모든 컴파일러는 이것을 가정하고 컴파일

    > 컴파일러와 링커는 물리적 주소를 생성함

    > 최대 주소 = 메모리 크기 - OS 크기 

 

#메모리에 저정된 정보 분류

- Binding time(공간이 언제 할당되나?)

    > Static: 프로그램이 실행되기 전에 할당(덩치가 큼) -> Compile시 linking

        > 프로그램 코드, 정적 전역 변수(초기화 및 초기화되지 않은 변수)

    > Dynamic: 프로그램 실행 중에 할당됨(덩치 작음) -> Runtime에 linking

        > Static보다 훨씬 덩치 작음(ex: if - else문에서 static은 둘다 할당하지만, dynamic은 하나만 할당)

        > 프로시저 스택, 동적 저장소(malloc or new에 의해 할당된 공간)

- 프로세스 메모리의 UNIX view(uniprogramming only for now):

#Segments of a Process

- 프로세스의 메모리는 논리적 세그먼트(text, data, bss, heap, stack)로 분할됨

    > 일부는 읽기 전용, 일부는 읽기 쓰기 가능

    > 일부는 컴파일 시간에 알려지고, 다른 일부는 프로그램 실행 중에 동적으로 성장

- 메모리 세그먼트에 대한 할당은 다음과 같이 이뤄짐:

    > 컴파일러와 어셈블러는 각 소스 파일로부터 코드와 데이터 세그먼트를 포함하는 객체 파일을 생성

    > 링커는 프로그램의 모든 객체 파일을 하나의 완전하고 독립적인 실행 가능한 객체 파일로 결합

    > 로더(OS의 일부)는 실행 가능한 객체 파일을 운영체제가 결정한 위치에 메모리로 로드

    > 프로그램은 실행 중에 new와 malloc을 사용하여 동적으로 메모리를 할당, 함수 호출 시 스택에서 공간을 얻음

 

#Linking

- Functions of a linker:

    > 프로그램의 모든 파일과 라이브러리 결합

    > 각 파일의 세그먼트를 함께 재그룹화(하나의 큰 데이터 세그먼트 등)

    > 재그룹화에 따라 주소를 조정합니다.

    > 결과물은 실행 가능한 프로그램임

- 객체 파일의 내용:

    > 파일 헤더 - 각 세그먼트의 크기와 시작 주소(메모리에서)

    > 코드와 초기화된 데이터를 위한 세그먼트

    > 심볼 테이블(심볼, 주소)
    > 외부 심볼(심볼, 위치)

    > 재배치 정보(심볼, 위치)

    > 디버깅 정보

    > 유닉스 디테일은 "man a.out"

 

#링킹은 왜 어려운가

- 어셈블러가 파일 어셈블시 외부참조(printf, scanf와 같은 심볼) 발견 가능

    > 컴파일러는 객체 코드 생성시 주소 0을 넣음

    > 컴파일러는 외부 심볼가 그 위치(객체 파일에서)를 교차 참조 목록에 기록, 그 목록을 객체 파일에 저장

    > 링커는 파일을 연결하는 과정에서 이러한 외부 참조를 해결해야 함

- 컴파일러는 프로그램이 메모리에서 어디에 위치할지 모름(Multi programming의 경우는 항상 0으로 가정)

    > 컴파일러는 프로그램이 0에서 시작한다고 가정

    > 컴파일러는 재배치 정보(나중에 조정될 주소의 위치)를 기록, 객체 파일에 저장

 

* 링킹이 끝나면 -> .o파일들이 모두 합쳐져 .exe파일이 만들어진 상태

 

#Loading

- 로더는 완성된 프로그램을 실행할 수 있는 메모리에 로드함

    > 코드와 초기화된 데이터 세그먼트를 지정된 위치의 메모리에 로드

    > 초기화되지 않은 데이터(bss)를 위한 공간을 남긴

    > 시작 주소의 값을 OS에 반환

- 로딩에 대한 대안(다음 2개의 강의에서 다룰 내용):
    > Absolute loader - 고정된 위치에 실행 가능한 파일 로드

    > Relocatable loader - OS에서 지정한 임의의 메모리 위치에 프로그램을 로드함(멀티 프로그래밍에 필요, 단일에는 필요 없음)

        > 어셈블러(컴파일러의 마지막 단계)와 링커는 프로그램이 위치 0에서 시작한다고 가정

        > 프로그램이 로드될 때, 로더는 모든 주소를 실제 시작 위치에 더하여 주소를 수정

 

* .c -> .o -> libc.a ->빈자리를 찾고 -> 첫 주소를 OS에 가르쳐주고 -> CPU에 가르쳐주면 그 빈자리부터 시

 

#Running the Program - Static Memory Allocation

- 컴파일, 링크, 로드는 정적 메모리에 대해서는 충분

    > 코드, 상수, 정적 변수 등

- 그러나 다른 경우에는 정적 할당만으로는 충분하지 않음

    > 동적 저장소가 필요한 경우 - 프로그램이 실행될 때 필요한 메모리의 양을 프로그래머가 알 수 없을 수 있음

    > 필요할 때 필요한 만큼의 메모리를 얻기 위해 malloc또는 new를 사용

    > 복잡한 데이터 구조(ex: 트리)의 경우, 노드에 대한 공간을 필요한 시점에 할당

    > OS는 미리 어떤 프로시저가 호출될 지 모름(미리 모든 프로시저의 모든 변수에 공간을 할당하는 것은 낭비)

    > OS는 재귀 프로시저를 처리할 수 있어야 함

 

#Running the Program - Dynamic Memory Allocation

- 동적 메모리는 두가지 기본 작음을 필요로:

1. 동적 저장소 할당

2. 더이상 필요하지 않을 때 메모리 해제 -> 스택과 힙에 대한 방법은 다양함

- 할당의 두 가지 기본적인 방법:

1. 스택(계층적)

    > 할당과 해제가 어느 정도예측 가능할 때 유용

    > 일반적으로 다음과 같이 사용됨:

        > 프로시저에 매개변수 전달

        > 프로시저 내에서 지역 변수에 공간 할당

        > 트리 순회, 식 평가, 파싱(parsing) 등에 사용됨

    > 스택 연산인 push, pop 사용

    > 모든 빈 공간을 구조화된 조직으로 함께 유지

    > 간단하고 효율적이지만 제한적

2. 힙

    > 할당과 해제가 예측할 수 없을 때 사용됨

    > 일반적으로 다음과 같이 사용:

        > 임의의 리스트 구조, 복잡한 데이터 구성 등에 사용됨

    > 공간을 할당하기 위해 new 또는 malloc을 사용하고, 공간을 해제흐기 위해 delete 또는 free 사용

    > 시스템 메모리는 할당된 영역과 빈 영역(구멍)으로 구성

    > 문제: 결국 많은 작은 구멍들이 생기는데, 각각이 유용한 정도보다는 작아서 사용하기 힘듦

        > 이를 fragmentation이라고 하며, 메모리 낭비를 초래

        > 스택 할당의 경우 fragmentation이 문제가 되지 않으므로 항상 스택의 위에서 추가/삭제 진행함

        > 해결법: 구멍의 공간을 재사용하여 구멍의 수를 작게 유지하고 크기를 크게 유지하는 방법을 목표로

    > 스택과 비교할 때 : 더 일반적이지만, 효율성이 낮고 구현이 어려움

 

#Topics in Memory Management

- Uniprogrammed operating systems

    > 어셈블링, 링킹, 로딩

    > static 메모리 할당

    > dynamic 메모리 할당

        > 스택, 힙

        > free list 관리, 메모리  reclamation

- Multiprogrammed operating system

    > 대부분의 topic을 포함

    > static relocation

    > dynamic relocation

        > Virtual vs physical 주소

        > Partitionoing(and compaction)

        > Segmentation

        > Paging

    > Swapping

    > Demand paging

 

#Managing the Free List

- 힙 기반의 동적 메모리 할당 기법은 일반적으로 free list를 유지, 모든 빈 공간을 추적, 관리

- free list manage algorithm:

1. Best fit

    > free block의 연결 리스트 유지

    > 할당마다 전체 목록을 검색하여 요청 크기와 가장 가까운 크기를 가진 빈 공간을 선택

    > 사용되지 않는 공간은 새로운 (더 작은) 빈공간이 됨

    > 메모리 해제 시, 인접한 빈 공간을 결합

2. First fit

    > 목록을 처음부터 검색하여 요청 크기에 충분한 첫 번째 빈 공간을 선택

    > 그렇지 않은 경우에는 Best fit과 동일한 방식으로 처리

 

#Reclaiming Dynamic Memory

- 메모리는 언제 해제되나?

    > 프로그래머가 명령시

    > 자동으로 하는 방법은?

        > 해당 아이템이 공유 아이템이라면 어려움

- reclamation의 잠재적 문제

    > Dangling pointers(무효 포인터) - 해당 항목을 사용하는 모든 부분이 완료되었는지 확인해야 함

    > 메모리 누수 - 해당 메모리를 적절한 시기에 해제하지 않아 메모리를 "lost"하지 않아야함

- 자동 회수 구현:

    > reference counts(참조 카운트)

        > 파일 시스템에서 사용

        > 운영체제는 각 메모리 항목에 대한 활성 포인터 수를 추적

        > 카운트가 0이되면 메모리를 해제

    > Garbage collection

        > LISP에서 사용됨

        > 저장 공간은 명시적으로 해제되지 않음. 프로그래머는 단순히 포인터를 삭제하고 해당 포인터가 가리키는 것 무시

        > 운영 체제가 더 많은 저장 공간이 필요할 떄, 모든 활성 포인터를 재귀적으로 검색, 아무도 사용하지 않는 메모리 회수

        > 응용 프로그램의 life를 쉽게 만들어줌, 그러나 구현 어려움

        > 종종 비용이 많이 들며, CPU time의 20% 사용 가능

        > 메모리 할당과 자동 메모리 해제에 50%시간 사용 가능

 

#Multiprogramming - Goals in Sharing the Memory Space

- 투명성:

    > 여러개의 프로세스가 메모리에서 공존해야 함

    > 어떤 프로세스라도 메모리가 공유된다는 것을 인식해서는 안됨

    > 각 프로세스는 메모리 내의 위치에 관계없이 실행되어야 함

- 안전성:

    > 프로세스는 서로 또는 운영체제를 손상시킬 수 없어야 함

    > 안전을 보장하기 위해 보호 메커니즘 사용

- 효율성:

    > CPU와 메모리의 성능이 공유로 인해 크게 저하되지 않아야 함

 

#Static Relocation

- 가장 높은 주소에 OS

- 컴파일러와 링커는 0에서 시작한다고 가정

- 로드 타임에서 OS는:

    > 프로세스를 완벽히 맞는 memory segment에 할당

    > 프로세스의 주소를 조절

 

#Static vs Dynamic Relocation

- 정적의 문제점:

1. 안전성 - 불만족: 하나의 프로세스가 다른 프로세스의 메모리에 접근하거나 손상 가능, 심지어 OS의 메모리까지 손상 가능

2. 프로세스 크기 변경 불가

3. 실행 시작 후에는 프로세스를 이동할 수 없음

4. MS-DOS, WINdows, Mac OS에서 사용됨

- 대안: 동적 재배치

    > 기본 아이디어는 프로세스가 실행되는 동안 각 메모리 주소를 동적으로 변경

    > 이러한 변환은 하드웨어에 의해 수향, CPU와 메모리 사이에는 메모리 관리 유닛(MMU) 또는 translation unit이 있어 가상 주소를 물리 주소로 변환

    > 이 변환은 프로세스가 수행하는 모든 메모리 참조에 대해 발생

*MMU: table(표)(2*n) -> 좌측도 주소(0~), 우측도 주소(실제 로딩되는 주소의 첫번째~)

*CPU는 0번지에만 접근하면 편하게 모든 주소에 접근 가능

*Loader가 Best fit에 load하고 MMU에 작성

*MMU = 프로세스 개수만큼 존재

 

#Dynamic Relocaiton

- 물리적 주소 공간: OS만 볼 수 있음, 기기의 물리적 메모리 만큼 존재

- 가상 주소 공간: 프로세스가 봄, 명령어 집합 아키텍처가 허용하는 한 존재

    > 현재는 물리적보다 훨씬 작다고 가정

- 여러 프로세스는 물리적 메모리를 공유하지만, 각각은 자신만의 가상 주소 공간만 볼 수 있음

- 이제 운영 체제와 하드웨어는 두 가지 다른 주소를 관리해야함

    > 가상 주소: 프로세스가 보는 주소

    > 물리 주소: 물리적 메모리의 주소(OS가 보는 주소) 

 

반응형

'CS > 컴퓨터운영체제' 카테고리의 다른 글

OS - 0523: Segmentation  (1) 2023.05.23
OS 0523 - Dynamic Relocation  (0) 2023.05.23
OS(0516) - Deadlock 처리 방법  (0) 2023.05.16
OS-Deadlock0509  (0) 2023.05.09
OS - Scheduling  (0) 2023.05.02