단일 프로그래밍 시스템 - 하나의 프로세스만 동작
단점 : 매 시점 하나의 프로그램만 동작 => 자원낭비, 프로세스가 OS를 공격할 수 있음
• 투명성
– 프로세스는 메모리가 공유되는 것을 알지 못함
– 프로세스의 수/위치에 무관하게 프로그래밍
• 보호
– 다른 프로세스나 OS의 메모리(데이터)를 읽거나 수정하지 못함
• 효율성
– 물리메모리 자원(공간)의 낭비 방지 (예, 물리메모리 단편화)
• 공유
– 상호 협력하는 프로세스간의 메모리 공유가 가능해야 함
• Byte단위의 주소(address)로 접근되는 메모리 “주소” 공간
• 목표
– OS는 개별 프로세스에 자신만의(private) 주소공간을 제공하자
• 주소공간이란?
– Code, data,stack, heap 등 프로세스가 동작하는데
사용하는 컴포넌트(segment)가 위치하는 주소공간
– 정적 컴포넌트: code, data (전역변수)
– 동적 컴포넌트:stack, heap
• 왜 프로세스는 동적 메모리가 필요한가?
– 프로그램 컴파일 시점에는 얼마만큼의 메모리를 사용할지 알지 못함
– 정적 메모리 할당은 최소한으로 이루어져야 함
• 최대 메모리 사용량만큼의 정적 메모리 할당은 메모리 낭비 초래
• 재귀적 함수(프로시저)
– 프로시저가 얼만큼 호출될지 알 수 없음
– 지역변수(local variable)의 메모리 공간은 호출마다
• 복잡한 (동적으로 할당되는) 데이터구조들
– 예, linked list, tree, etc.
– struct my_t p = (struct my_t )malloc(sizeof(struct my_t));
• 동적 메모리 할당의 두 가지 방법
– stack, heap
• 함수 호출시 지역변수는 stack에 동적으로 할당됨
– 함수가 호출되었을때 지역변수(local variable)의 메모리가 할당됨
– 함수가 해제될때 지역변수(local variable)의 메모리가 해제됨
main () {
int A = 0;
foo (A);
printf(“A: %d\n”, A);
}
void foo (int Z) {
int A = 2;
Z = 5;
printf(“A: %d Z: %d\n”, A, Z);
}
==> 메인함수 frame, => foo ==> printf ==> 이러한 순서로 쌓임
return시 메모리 반납
• 임의의 시점에 임의의 크기의 메모리를 할당: malloc(), new()
– Heap은 가상메모리 내 공간으로, 할당된 메모리 영역, 해제된 메모리 영역(hole)이 있음.
– 메모리 공간의 할당 해제는 임의의 순서이며 예측 불가
• 장점
– 모든 종류의 data structure에 적용 가능
• 단점
– 할당 속도가 느림 (stack은 expand/shrink만 하면 됨)
– Heap 공간이 조각날 수 있음 (external fragmentation)
– 할당하는 메모리 공간 크기에 따른 내부단편화(internal fragmentation 문제)
• Heap에 대한 OS의 역할
– OS는 heap영역(큰 공간)만 제공하고, C library에서 큰 공간을 쪼개서 malloc()/free()에 서
비스함
• brk() 시스템콜
#include <stdio.h>
int n = 0;
int main ()
{
n++;
printf (“&n = %p, n = %d\n”, &n, n);
}
% ./a.out
&n = 0x0804a024, n = 1
% ./a.out
&n = 0x0804a024, n = 1
– 만약 동일한 프로그램이 동시에 n에 접근한다면?
=> 2개의 프로그램을 동시에 수행하면 각자 개별의 값이 1로 증가만 된 값이 출력
=> 가상메모리이기 때문
• 목표: 다수의 프로세스를 동시에 실행 가능하게 하자
• 제약사항
– 프로그램 바이너리 내에 메모리 주소는 고정되어 있음
– 다수의 프로세스 사이에 메모리 충돌 (같은 주소 접근)을 어떻게 해결?
• 가능한 방법들:
1. 시분할 (time-sharing)
==> 일부 시간 구간동안 메모리 사용
==> 프로세스가 사용하는 시간이 종료되면 디스크에 메모리를 보존하고 물리메모리를 비움
==> 다시 프로세스1을 실행할 때 디스크의 프로세스1 내용을 다시 불러옴
메모리 시분할(time sharing)의 문제점
• 성능이 매우 안좋음
– 매번 프로세스의 메모를 디스크<-> 물리메모리 사이에 복사해야 함
• 대안: 공간 공유(space sharing)
– 메모리에 여러 프로세스의 메모리 데이터가 공존
– à 앞으로의 기법들은 space sharing
Idea: OS가 프로그램의 주소를 프로세스 생성(create)시점에 바꿈
• 각 프로그램이 로딩되는 물리메모리 위치에 기반하여
명령어(instruction)에서 사용하는 주소를 바꿔치기함
– jump,static data 접근 명령어의 주소 변경
=> 스택에 접근하는 변수는 절대적인 주소기반의 어드레싱이 아니라 상대적인 주소를 어드레싱하기때문
=> 스택 변수는 스택 포인터 rbp라는 레지스터에 스택에 현재위치가 저장, rbp를 기준으로 상대적인 위치를 기준으로 로컬 변수를 찾아서 접근하기 때문에 재배치를 할 필요가 없다.
유사하게 힙에서도 재배치 필요x. 멀록을 통해서 하면 프로세스가 로딩된 위치를 기준으로 그 내부에서의 힙에 해당하는 주소에 해당하기 때문.
정적 재배치: 장단점
• 보호 부재
– 프로세스가 다른 프로세스나 OS의 메모리를 읽고 수정할 수 있음
• ((void)0x3000) = 0xfeedbeaf // hard-coded by process 1
• 프로세스의 위치를 변경할 수 없음
– 메모리가 부족할 경우 compaction같은 작업이 쓰일 수 있으나 불가능함
• 재배치로 바뀐 주소를 다시 바꿔줘야 함
=> 메모리 단편화 발생시 메모리 데이터를 옮겨줘야하는데 불가능하다. 어디에 어떤 현재 주소기반의 데이터를 넣어줬을지 모른다.
==> 대안 : 동적 재배치
동적 재배치 (dynamic relocation)
• 프로세스들 사이에 보호 제공
• CPU 하드웨어의 지원 필수
– Memory Management Unit (MMU)
• MMU가 프로세스가 사용하는 주소를 매번 동적으로 재배치함
– 프로세스는 logical 또는 virtual 주소 사용 (자신의 주소공간 내에서)
– 물리메모리(DRAM)에 접근할 때는 physical or real 주소 사용
동적 재배치를 위한 CPU 하드웨어의 지원
• 두개의 동작 모드 (execution mode)
– 특권/커널/보호 모드 (privileged/kernel/protected mode): OS가 동작
• 커널모드로 전환/OS의 핸들러 수행을 유발하는 구조 제공
– 트랩(trap), 시스템콜(system call), 인터럽트(interrupt), 예외(exception)
• 특권 권한의 명령어 수행 가능
– MMU 조작 명령어, 하드웨어 제어 명령어(ex, in/out in x86)
• 모든 물리 메모리 접근 가능
– 유저 모드(user mode): 유저 프로세스 동작
• MMU가 각 유저 프로세스의 가상 주소공간 및 그 내부에 가상 메모리 제공
• OS가 MMU에 설정한 만큼만의 메모리 접근 가능
동적 재배치 기법: Base Register
• 모든 메모리 주소는 MMU의 주소변환(가상->물리)을 통과함
– Logical address에 base register의 값을 더해서 physical address 획득
• 프로세스 생성시 프로세스에 할당된 물리메모리의 시작 주소를 base로 설정
• 컨텍스트 스위치마다 base register의 값을 현재 프로세스의 것으로 변경
동적 재배치 (Base+Bounds)
• 목표: 허용된 범위만 접근하게 하자
• 방법: Bound register를 추가하여 주소공간의 크기를 제한
– Base register: 주소공간의 물리메모리상 시작 주소
– Bounds register: 해당 프로세스의 주소공간의 크기
• Bound의 크기를 벗어나는 logical address를 생성하는 경우 프로그
램 종료시킴
– Segmentation fault!!
Base+Bound 방식의 문제점
• 동적 메모리의 고려 부족
– Heap, Stack이 얼만큼 자라날지 모름
• 사용 안되는 공간의 낭비
– 프로세스간 메모리 공유 불가능
• 같은 프로그램에서 실행된 프로세스의 코드는 같음
• 각자 Code를 가지기 때문에 메모리 공간 낭비
• 해결책
– Segmentation
• Segment(stack, heap, code)별로 base+bound방식
– Paging
결론
• HW+OS 협력하여 메모리 가상화 (virtual memory) 제공
– 각 프로세스에게 마치 자신만 사용하는 메모리(가상 메모리)가 있는 것처럼
환상을 제공
• CPU에 MMU하드웨어 추가 및 주소변환 수행
– 주소변환은 CPU 하드웨어(MMU)에서 빠르게 발생
– 가상메모리의 설정 (base, bound,segment table)은 OS가 메모리 할당 시점에
관리
• Segmentation의 메모리 단편화 문제
– Paging으로 해결 à 현대의 대부분의 CPU architecture (x86, ARM, etc.)