virtual memory(가상 메모리) / COW와 mmap()
가상메모리란, cpu가 실제 메모리처럼 사용하는 추상적인 메모리 공간을 의미한다.
즉 물리적으로 존재하는 메모리는 아니다.
사용 이유
기술 발전에 따라 실행파일이 물리 메모리보다 더 커지는 문제가 발생했다.
이를 해결하기 위해 필요한 부분만 물리 메모리에 올려 실행할 수 있게 만들었다.
물리 메모리만 사용시
물리메모리(메인메모리)에 프로그램 전체를 올리는 방법이다.
cpu가 물리주소로 직접 물리 메모리에 접근한다.
가상 메모리 사용시
위 그림과 다르게 MMU(Memory Management Unit)라는 것이 추가로 생겼음을 알 수 있다.
MMU는 가상주소를 물리주소로 변환해주는 하드웨어 장치다.
즉 cpu에서 MMU에게 가상주소를 보내면 MMU가 그에 맞는 물리주소로 변환하고, 해당 물리 주소로 접근해 데이터를 가져오거나 저장한다.
가상 메모리의 장점
실제 물리메모리용량보다 더 큰 공간을 사용할수있도록 한다.
각 프로세스는 다른 프로세스의 메모리에 접근할 수 없게 되어 있어 프로세스 간의 충돌을 방지한다
구조, 동작 방식
아래 그림은 페이지 테이블과 물리 메모리의 구조를 보여준다.
페이지 테이블이란? 가상 주소와 물리주소 매핑정보를 저장한 테이블이다. MMU가 이를 사용하여 가상주소와 대응되는 물리주소를 찾는다.
그림의 가운데에 있는 것이 페이지 테이블이고, 오른쪽 위에 있는 것은 물리메모리, 오른쪽 아래에 있는것은 disk다.
페이지 테이블은 여러개의 PTE(Page Table Entry)로 이루어진 배열이다.
각 PTE에는 물리주소(PP)와 Valid 등이 저장되어있고, PTE의 인덱스값이 가상주소(VP)이다. 즉 PTE 1은 VP값이 1인 것과 대응된다.
Valid는 해당 페이지가 현재 물리메모리에 적재되어 있는지 여부를 표시한다. valid값이 1이면 적재된 상태, 0이면 적재되어있지 않은 상태이다.
물리메모리에 존재하는 경우 (valid가 1인 경우)
만약 cpu가 VP 1인 페이지를 가져오는 요청을 보낸 경우
페이지테이블의 PTE 1의 valid값이 1이므로 물리메모리의 PP 1의 값을 바로 가져온다.
물리메모리에 적재된 상태이므로 바로 접근 가능하다.
물리메모리에 존재하지 않는 경우 (Valid가 0인 경우)
cpu가 VP 3인 데이터값을 가져오는 요청을 보낸 경우
페이지 테이블의 PTE 3의 valid값이 0이므로 물리메모리에 적재되어있지 않다.
따라서 VP 3의 데이터를 disk에서 찾아 물리메모리에 적재 후 물리메모리의 값을 읽어오게 된다.
만약 물리메모리에 공간이 다 찬경우 아래 순서로 진행된다.
1. 특정 알고리즘을 통해 물리 메모리의 특정 페이지를 disk로 내린다.
2. 접근하려는 페이지를 disk에서 물리메모리로 복사한다.
3. 페이지 테이블을 수정한다
공유 메모리
A프로세스와 B프로세스가 어떤 .so 라이브러리를 공유하면,
각각의 페이지 테이블에서 VP 2인 페이지가 같은 물리주소를 가리키게 된다.
이 경우 한 프로세스에서 파일 값을 변경하면 다른 프로세스에도 값이 변경되는 문제가 발생한다. 따라서 권한을 read only로 설정해야 한다.
접근 권한
PTE에는 물리주소, Valid 뿐만 아니라 페이지 각각의 접근 권한도 저장되어있다.
커널 모드만 접근 가능여부(SUP), 읽기 가능여부(READ), 쓰기 가능여부(WRITE), 실행 가능여부(EXEC)로 권한 설정이 가능하다.
가상 주소, 물리 주소 할당 방식
PTBR : 현재 프로세스의 페이지 테이블의 시작주소를 가리킨다. 포인터 역할을 한다.
가상 주소는 VPN+VPO로 이루어지고, 물리 주소는 PPN+PPO로 이루어진다.
VPN : 프로세스의 페이지테이블을 찾기 위한 인덱스. 즉 프로세스를 구분하는 번호이며 프로세스가 같으면 VPN도 같다.
VPO : 해당 페이지테이블 내에서의 현 offset
PPN : 프로세스를 구분하는 번호이며 프로세스가 같으면 PPN도 같다.
VPO와 PPO는 값이 같다. 즉 가상메모리와 물리메모리의 offset이 같다.
Copy-on-Write (COW)
메모리 효율성을 높이기 위한 기법.
복사할 때 진짜로 복사하지 않고, 쓰기(Write)가 발생할 때만 진짜로 복사하는 방식이다.
이 기법을 사용하는 예로 fork()가 있다.
fork()는 부모 프로세스가 자식 프로세스를 생성할때 사용하는 시스템콜인데,
이때 부모 프로세스 전부를 복사하여 메모리 할당하면 비용이 너무 크므로 가상메모리에 할당하고 부모와 자식이 같은 물리 메모리를 공유하도록 한다. 이후 쓰기(write)가 발생하면 그때 자식 프로세스를 물리메모리에 따로 할당한다.
fork()에 대한 설명은 아래글에 담겨있다.
https://annyeong46.tistory.com/105
[OS] 프로세스 실행~종료 과정 (fork(), exec(), exit(), wait())
목차터미널 프로세스를 예로 들어보자- 시스템콜?- 터미널 프로그램 예시 코드터미널 명령어 실행부터 종료까지0. 현상태1. 터미널 프로세스 복제하기 (fork())2. 로더를 통한 메모리 덮어쓰고 실
annyeong46.tistory.com
COW 활용 : mmap()
mmap()은 COW 기법을 활용하는 함수다.
사용 목적
반복적인 파일 읽기/쓰기 작업에 효과적이다.
기본적으로 프로세스에서 파일을 읽거나 갱신할 때 read(), write() 시스템콜을 사용한다.
read()/write() 호출시 OS는 disk에 있는 파일을 즉시 커널의 물리메모리에 복사한다
이 과정에서 시스템콜, 인터럽트, 스케줄링이 발생하여 성능에 영향을 미친다.
read()/write() 함수는 호출시마다 매번 이 단계를 거치기 때문에 여러번의 읽기/쓰기 작업이 일어날 경우 속도가 현저히 느려진다.
mmap() 사용시 이 단계를 한번만 거치고 이후 여러번 읽기/쓰기를 할때에는 메모리에서 바로 처리된다.
따라서 반복적 파일 읽기/쓰기 작업에 mmap()을 사용한다.
동작순서
1. mmap() 실행, 페이지테이블에 해당 파일 주소 매핑 (=COW 기법)
페이지테이블에만 적재하고 물리메모리에는 적재하지 않은 상태다. fork()호출했을 때와 같은 상태라고 볼 수 있다.
2. 해당 메모리 접근 시도(읽기or쓰기), file을 물리메모리에 복사
disk에 있는 파일 데이터를 물리메모리에 복사 후 물리메모리의 데이터에 접근한다.
3. 메모리 재접근 시도(읽기or쓰기), 물리메모리의 데이터에 바로 접근
이미 물리메모리에 복사한 상태이므로 바로 접근 가능하다.
4. 파일 종료(close)
파일 쓰기처리한 경우 물리 메모리의 파일 데이터가 disk에 업데이트된다.
mmap() 정의
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
매개변수 설명
- addr: 매핑할 시작 주소 (보통 NULL로 두면 커널이 자동 할당)
- length: 매핑할 바이트 수
- prot: 페이지 보호 속성 (PROT_READ, PROT_WRITE, 등)
- flags: 매핑 타입 (MAP_SHARED, MAP_PRIVATE 등)
- fd: 매핑할 파일 디스크립터
- offset: 파일 내 오프셋 (페이지 크기 단위여야 함)
* flags의 MAP_SHARED를 통해 공유 라이브러리를 구현할 수 있다.
사용 예시
int main() {
int fd = open("example.txt", O_RDONLY);
char *addr = mmap(NULL, 128, PROT_READ, MAP_PRIVATE, fd, 0); //페이지테이블 매핑
write(STDOUT_FILENO, addr, sb.st_size); // 파일 내용 출력
munmap(addr, sb.st_size); //페이지테이블에서 제거, disk에 변경사항 저장
close(fd);
return 0;
}