이번글은 페이징에 대한 글입니다.
이전에 https://cjbworld.tistory.com/36 스와핑에 대해서 설명하던 도중 '외부 단편화'를 해결하는 방법으로 잠깐 소개했었는데요, 이번글은 외부 단편화를 해결하기도 하고 메모리 보다 큰 프로세스를 '적재' 할 때에도 사용되는 '가상 메모리'에 대한 정리글입니다!
가상 메모리란?
가상 메모리는 실행하고자 하는 프로세스의 일부만 메모리에 적재하여 실제 물리 메모리 크기보다 도 큰 프로세스를 실행할 수 있게하는 기술 입니다.
이런 가상 메모리를 관리하는 기법 중에는 크게 '페이징'과 '세그멘테이션'이 있지만 페이징에 대해 알아보도록 하겠습니다.
아무튼 가상 메모리를 관리하는 기법인 '페이징'을 사용하는 외부 단편화 문제와 물리 메모리의 크기보다 더 큰 프로세스를 적재하여 실행할 수 있게 해줍니다.
페이징이란?
스와핑 글에서 보듯이 외부 단편화가 발생하는 근본적인 이유는 각기 다른 프로세스들을 메모리에 '연속적'으로 적재하였기 때문입니다. 그리고 실행이 끝난 프로세스는 메모리에서 삭제하고 다른 프로세스를 적재할려고 하니 맞는 메모리 크기가 없어서 메모리 낭비가 발생했던 것이구요...ㅠ
페이징은 메모리와 프로세스를 '일정한 단위'로 자르고 이를 메모리에 불연속적으로 할당하여 외부 단편화를 방지합니다.
페이징은 프로세스의 논리 주소 공간을 '페이지'라는 단위로 자르고, 메모리의 물리 주소 공간을 '프레임'이라는 페이지와 동일한 단위로 자른 뒤 페이지를 프레임에 할당하는 '가상 메모리 관리 기법'입니다.
중간에 page table은 조금있다가 설명드릴 것인데 위 그림처럼 page를 프레임에 할당을 해줍니다.
페이징에서도 '스와핑'을 사용할 수 있는데요, 페이징을 사용하는 프로세스에서는 프로세스 전체가 '스왑 인/아웃' 되는 것이 아니라 '페이지 단위로 스왑 인/아웃'이 됩니다. 이를 '페이지 인/아웃'이라고 합니다.
이를 통해 프로세스 전체를 메모리에 적재하여 실행하지 않고 실행에 필요한 일부 페이지만 메모리에 적재하고 당장 실행에 필요하지 않은 페이지들은 보조기억장치에 남겨둡니다. 그래서 물리 메모리 크기보다 큰 프로세스를 실행할 수 있는 것입니다.
페이지 테이블(page table)
제가 아까 페이지들을 메모리에 불연속적으로 배치한다고 말씀 드렸는데요 근데 이렇게 페이지들을 불연속적으로 메모리에 배치를 하다보면 CPU가 '다음에 실행할 위치'를 찾기가 어려워 집니다.
그래서 '물리 주소'에는 불연속적으로 배치를 하더라도 CPU가 접근하는(바라 보는) '논리 주소'에는 연속적으로 배치 되도록 '페이지 테이블'을 사용합니다.
페이지 테이블은 CPU가 논리 주소에 찍힌 페이지의 번호만 보고 바로 물리 주소에 있는 프레임 번호에 접근할 수 있게 해주는 역할을 합니다.
프로세스는 프로세스마다 각자의 프로세스 테이블이 있습니다.
그림으로 간단하게 그리면 위와 같습니다.
하지만 이런 장점밖에 안보이는 가상 메모리 관리 기법인 페이징에서도 '문제'가 발생할 수 있는데요,
바로 '내부 단편화'입니다.
만약 물리 주소와 논리 주소를 페이지라는 단위로 일정하게 자른다고 말씀드렸는데 만약 페이지의 단위가 10kb이고 일부만 적재할 프로세의 크기가 34kb 라면은 프로세스를 4개의 단위로 자르고 메모리에 올리게 됩니다.
그러면 마지막에 자른 부분에서 6kb바이트가 안쓰는 채로 남아있게 되는데 이를 '내부 단편화'라고 합니다.
페이지의 크기를 너무 작게 설정하면 페이지 테이블의 크기가 커질 것이고, 그렇다고 너무 크게 하면은 내부 단편화 문제가 심해질 것입니다. 그래서 페이지의 크기를 적절히 조절하는 것이 중요합니다.
리눅스에서는
위처럼 페이지의 크기를 확인할 수 있습니다. (저는 가상 머신을 통해 리눅스로 확인하였습니다)
TLB
프로세스 마다 프로세스 테이블을 가지고 있고 각 프로세스의 페이지 테이블은 메모리에 적재되어 있습니다.
그리고 CPU 안에 있는 '페이지 테이블 베이스 레지스터(PTBR)'는 각 프로세스의 페이지 테이블이 적재된 주소를 가르키고 있습니다.
이전 글인 https://cjbworld.tistory.com/34 논리 주소와 물리주소에 대한 글에서 CPU가 논리 주소에서 물리 주소로 접근 할때 MMU의 베이스 레지스터값을 통해 실제 물리 주소에 접근한다고 설명드렸었는데 비슷한 상황입니다.
이런식으로 가르키는 상황입니다.
이런 프로세스의 페이지 테이블 정보는 각 프로세스의 PCB에 보관이 됩니다. 그리고 프로세스의 문맥 교환이 발생하면 다른 레지스터들과 마찬가지로 함께 변경됩니다.
근데 이렇게 하면 CPU가 하고자 하는 것은 실제 물리주소에 있는 프레임 번호를 알기 위해서 page table을 사용했던 것인데 메모리에 올라간 페이지 테이블을 확인해서 프레임 번호를 알아내는데 한번, 그리고 알아낸 프레임 번호로 다시 접근(메모리로) 한번, 이렇게 총 두번이나 메모리에 접근하게 됩니다.
이런 문제를 해결하기 위해 CPU 옆에는 (MMU내에) 'TLB(Transaction Lookaside Buffer)'라는 페이지 테이블의 캐시 메모리를 둡니다.
TLB은 페이지 테이블의 캐시이기 때문에 페이지 테이블의 일부 내용을 저장합니다.
이또한 '참조 지역성'의 원리에 근거해 최근에 사용한 페이지 위주로 가져와 저장합니다.
캐시 히트/미스 와 마찬가지로 TLB또한 TLB 히트/미스 가 있습니다.
CPU가 제어신호로 발생시킨 논리 주소에 대한 페이지 번호가 있으면 'TLB 히트', 그렇지 않으면 'TLB 미스'입니다.
TLB미스가 발생하면 어쩔 수 없이 메모리에 있는 페이지 테이블에 접근하는 수밖에 없습니다.
page table entry
다시 정리하면 프로세스 마다 페이지 테이블을 가지고 있는데 이 페이지 테이블은 메모리에 적재됩니다.
그리고 페이징 시스템에서의 모든 논리 주소는 기본적으로 'page number'와 'offset'으로 이루어져 있습니다.
만약 CPU 프로세스 A의 페이지 2에서 10정도 만큼 떨어진 주소에 접근하고 싶다면은 위의 그림처럼 page number를 가져와서 page offset에서 벗어나지 않는 범위에서 접근 할 수 있도록 해줍니다.
즉, 논리주소 pair<page number, offset> 은 pair<frame number, offset>으로 변환됩니다.
이렇게 논리 주소의 page number를 타고들어가 페이지 테이블에 <page number, frame number>로 들어가 프레임 번호에 접근(물리 주소 접근)하는 식인데
여기 페이지 테이블에 접근 할 때 각각의 행(row)에 접근하는데 이 행(row)들을 '페이지 테이블 엔트리(page table entry)'라고 합니다.
페이지 테이블의 각각의 행(row)들은 중요한 정보들을 가지고 있습니다. 대표적으로 '유효 비트', '보호 비트', '참조 비트', '수정 비트'의 값들을 가지고 있습니다.
- '유효 비트'는 현재 해당 페이지에 접근 가능한지 여부를 알려줍니다. 유효 비트는 현재 페이지가 메모리에 적재되어 있는지 아닌지아니면 보조기억 장치에 있는지를 알려주는 비트입니다. (메모리에 적재 = 1, 보조기억 장치에 있다 = 0) 만약 유효비트가 0인경우에 해당 페이지로 접근하려고 하면 'page fault'라는 예외가 발생합니다.
- '보호 비트'는 페이지 보호 기능을 위해 존재하는 비트입니다. 보호 비트를 통해 현재 페이지가 읽고 쓰기가 가능한지 아니면 읽기만 가능한지를 알 수 있습니다. 읽기만 가능한 경우 0, 1일 경우 rw가 가능한 페이지를 나타냅니다. 저희가 소스코드를 작성하면 이 소스코드들은 메모리의 코드영역(읽기 전용 영역)에 적재되는데 이곳의 데이터를 읽는 것을 막아주는 것이 보호 비트입니다.
현재 LinkTest라는 폴더 내에서 허가 권환 확인 명령어인 ls -al명령어를 통해서 권환들을 보면 보호비트가 어떻게 켜져 있는지 알 수 있습니다. r--이라면 읽기 전용이고 rw-면은 읽고 쓰기가 둘다 가능합니다.
리눅스의 umask의 기본값은 022입니다.
파일의 경우 기본 666, 디렉토리의 경우 777입니다. 이들에게 AND연산을 하여 022와 666에 and연산을 하면
644가 나오게 되겠네요
그러면 rw-rw-r--이 나오는데 ls -al명령어를 직접 쳐보신 다음에 비교해보시면 될거같습니다.
- '참조 비트'는 CPU가 페이지에 접근한 적이 있는지 여부를 나타냅니다.
- '수정 비트'(더티 비트)'는 해당 페이지에 데이터를 쓴적이 있는지 없는지를 나타냅니다. 수정 비트는 페이지가 메모리에서 사라질 때 보조기억 장치에 쓰기 작업을 해야하는지, 할 필요가 없는지를 판단하기 위해 존재합니다.
CPU는 메모리를 읽기도 하지만 쓰기도 합니다. 읽기만 한 경우 수정 비트가 0의 값을 가져 보조기억 장치에 있는 페이지와 스왑 인/아웃을 진행하면 되지만 한번 쓰기를 한 경우에는 보조기억 장치에 저장된 페이지의 내용과 메모리에 저장된 페이지의 내용은 서로 다른 값을 가지게 됩니다. 그래서 수정비트가 1인경우에 스왑 아웃하기전에 변경된 값을 보조기억 장치에 기록하기 위해 존재하는 비트입니다.
정리하자면
- 외부 단편화 문제와 물리 메모리 보다 큰 프로세스를 메모리에 적재하기 위한 방법으로 '가상 메모리'라는 개념을 사용한다. 이때 가상 메모리의 기법으로 '페이징'과 '세그멘테이션'이 있는데 일반적으로 페이징기법을 많이 사용한다.
- 페이징은 페이라는 일정한 단위로 메모리와 프로세스를 자라서 프로세스를 메모리에 '불연속적으로' 배치한다. 이때 불연속적으로 배치하면 CPU가 메모리로의 접근이 어려워 지기 때문에 TLB라는 캐시를 사용해서 접근한다.(TLB 히트/미스)
- page table의 각 행(row)들을 page table entry라고 부르며 page table entry에는 여러 비트들로 프레임 번호와 함께 중요한 정보들이 같이 들어있다. 여러 비트들의 종류로는 유효 비트, 보호비트(rwx), 참조 비트, 수정 비트가 있다.
감사합니다 :)
'CS' 카테고리의 다른 글
스레드란? (1) | 2024.09.18 |
---|---|
Stack Frame과 함수 호출 규약(__stdcall) (1) | 2024.04.28 |
[CS] 스와핑(Swapping) (0) | 2023.08.09 |
[CS] 논리주소와 물리주소 (0) | 2023.08.07 |
[CS] Context Switching(문맥 교환) (0) | 2023.08.05 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!