physical memory
프로세스에서 피지컬 메모리를 사용할 경우 문제점
- process protection
- isolation
피지컬 메모리를 사용하면 악성 프로세스를 막을 수 있는 방법이 없다. 커널도 같은 공간을 사용하기 때문에 over write 될 수 있다. 이는 매우 심각한 문제이다.
또한 프로세스는 고정된 포인터를 사용하기 때문에, 프로세스가 여러 개가 된다면 문제가 생길 수 있다. 2번 프로세스에서 1번 프로세스의 코드에 접근할 가능성이 있다.
해결 방법
- 고정된 메모리 주소
- 프로그램을 로드할 때 주소를 수정
- PC(EIP) 와 비교한 상대 주소 사용
- 하드웨어 지원을 이용 (가상 메모리)
고정된 메모리 주소
위에서 살펴본 상황과 같다. 프로세스가 하나일 경우에는 별다른 문제가 없을 것이라 예상할 수 있다. 똑같은 프로그램을 두 번 실행한다고 가정해 보자. 함수 포인터가 고정되어 있기 때문에 프로세스2 에서도 프로세스1의 메모리에 접근하게 된다. 이 문제를 해결하려면 함수 포인터 등을 수정해서 컴파일한 파일을 만들어 실행해야 한다. 매우 비효율적이다.
프로그램 로딩시 주소를 수정
프로그램을 실행하면 바로 main 함수를 실행하는 것이 아니라 스택을 세팅하고 여러 가지 작업을 하게 된다. main 함수가 실행되기 전에 수행되는 이 때 함수 포인터 값에 프로세스 시작주소를 더하도록 한다. 그렇게 되면 여러 개의 파일을 가지고 있을 필요가 없다. 모든 주소값을 더해줘야 하기 때문에 로딩 시간이 길어지고 여전히 비효율적이라는 느낌이 든다.
상대 주소 이용
함수를 호출할 때 정해진 주소가 아니라 현재 PC(IP)에서 얼만큼 이동하라는 명령을 내린다. 실제 함수가 어느 위치에 저장되어 있는지 숨길 수 있다는 장점을 갖기 때문에 보안을 중요시할 경우 지금도 사용하고 있는 방법이라고 한다. 하지만 PC 에서는 사용하지 않는다.
가상 메모리 이용
가상 메모리를 사용하려면 CPU 에서 MMU(Memory Management Unit)를 갖고 있어야 한다. MMU 는 가상 메모리 주소를 피지컬 주소로 변환해 주는 역할을 한다.
가상 메모리란?
프로세스가 논리적으로 가질 수 있는 주소로 실제로는 존재하지 않는다. 프로세스 입장에서는 메모리의 모든 공간을 자신만 사용하고 있다고 생각한다. 프로세스는 항상 가상 메모리 주소를 사용하고, 피지컬 주소에는 커널만이 접근할 수 있다. 가상 메모리 주소를 변환해 주는 것은 CPU 의 MMU 이고, 가상 메모리를 관리하는 것은 OS 이다.
address translation 은 MMU 에 구현되어 있지만 주소 변환을 위해 필요한 정보를 전달해 줘야 한다.
가상 메모리를 사용하는 여러 가지 방법이 있다.
- base and bound registers
- segmentation
- page tables
- Multi-level page tables
간단하고 옛날에 사용하던 것부터 복잡하고 현대에 사용하는 순이다.
Base and Bounds Registers
base 와 bound 레지스터를 이용하는 방법이다. 연속적인 가상 메모리 공간을 연속적인 피지컬 메모리 공간과 매핑시켜 주는 방법이다.
base : 시작 주소
bound : 메모리 크기
0x0023 이라는 가상 주소를 얻으면 base + 0x0023 주소로 접근한다.
간단하게 구현할 수 있고 관리도 간단하다. 함수포인터 고정 문제와 프로텍션 문제도 해결할 수 있다.
if(physAddr >= BASE + BOUND) // 예외 발생
한계
메모리 공유가 어렵다. 연속적인 공간을 사용하기 때문에 메모리의 일부만 공유하고 싶어도 어렵다.
프로세스 내부에서는 프로텍션이 불가능하다. 프로그래머의 실수로 자기 자신의 코드를 지워버리는 상황이 발생해도 보호받을 수 없다.
내부 단편화 문제가 발생한다. 스택과 힙은 동적 메모리 영역이다. 스택 오버 플로우가 발생할 수 있기 때문에 어느 정도 공간을 확보해 줘야 하는데, 낭비되는 공간이 생긴다.
Segmentation
프로그램 로딩 부분에서 등장했던 세그먼트가 드디어 나왔다. 세그멘테이션은 Base and Bounds Registers 를 개선하여 일반화한 방법이다. 스택과 힙 등 여러 영역으로 나누어 관리한다. 스택과 힙 등으로 나누었고 피지컬 주소는 다를 수 있지만 프로세스 입장에서는 연속적이라고 생각한다.
주소를 사용할 때 두 가지 영역으로 나뉜다. 14 비트 주소라고 가정하면, 앞의 두 비트는 세그먼트 비트, 나머지 12비트는 offset 비트로 나눌 수 있다. 따라서 이 주소 체계를 쓸 경우 하나의 세그먼트당 4kb 의 영역을 사용할 수 있다.
주소가 0x0023 이라고 가정하면 00 0000 0001 0111 이런 주소가 된다. 앞의 두 비트가 세그먼트 비트이고 이 비트를 참고하여 BASE 값을 가져온다. 0000 0001 0111 부분은 offset 비트로 이 값에 BASE 를 더해주면 피지컬 주소가 나온다.
Segment = (VirtualAddress & SEG_MASK) >> SEG_SHIFT
Offset = VirtualAddress & OFF_MASK
강의 자료로 나온 코드이다. 상수들은 SEG_MASK = 11000000000000, OFF_MASK = 00111111111111 이런 값이 들어 있으리라 예상할 수 있다. 관련된 알고리즘으로 비트마스크가 있다.
운영체제와 CPU 는 역할 분담을 한다. CPU 는 메모리 주소를 변환해 주고, 운영체제는 세그먼트와 인덱스를 관리하며 세그먼트 테이블을 만들어 준다. base 와 bound 값도 운영체제에서 정한다. 컨텍스트 스위칭이 일어나면 기존의 스택, 레지스터를 포함하여 세그먼트 테이블도 어딘가에 저장하고 복원해 주는 것도 운영체제의 역할이다.
세그먼트는 연속적이지 않아도 되며, 실제 사용중인 메모리만 매핑되어 있다. 영역이 커지면 새로 할당하는 것도 가능하다. 각 세그먼트마다 permission(rwx) 을 설정할 수 있고, 스택 오버 플로우 어택을 방어할 수 있다. 공유 메모리도 쉽게 만들 수 있다.
32비트 이상의 운영체제에서는 세그먼트를 지원하긴 하지만 사용하지 않는다. 페이지를 이용한다. (세그먼트를 세팅하긴 한다. ring 정보도 추가되어 있다.)
segmentation fault
C언어에서 포인터 연산 잘못했을 때 발생하는 오류인데, 지금 공부하고 있는 세그멘테이션에서 오류 이름의 유래가 나왔다고 한다.
장점 : base and bouns registers 의 장점을 모두 갖는다. 내부 단편화 문제를 해결했다. 공유 메모리를 쉽게 만들 수 있다. 퍼미션을 따로 설정할 수 있다.
단점 : 외부 단편화 문제가 생긴다. 이 문제를 해결하려면 모든 프로세스를 중지시켜 놓고 메모리 컴팩션을 해야 하는데, 모든 오버헤드가 너무 크므로 현실적으로 불가능하다.
- 인천대학교 컴퓨터공학부 3학년 전공필수 과목인 박문주 교수님의 운영체제 강의를 듣고 작성한 글입니다.
'공부 > OS' 카테고리의 다른 글
[운영체제] 가상 메모리 2 (paging) (0) | 2021.05.02 |
---|---|
운영체제 중간고사 후기 (2) | 2021.04.21 |
[운영체제] 프로세스 스케줄링 2 (process scheduling) (0) | 2021.04.20 |
[운영체제] 프로세스 스케줄링 1 (process scheduling) (0) | 2021.04.19 |
[운영체제] 프로그램 로딩(program loading) (0) | 2021.04.19 |