권영진 교수님 OS 강의 2
정리 이전
권영진 교수님께서 pdf를 주셔서
해당 pdf의 일부 이미지를 참고하였고,
정리 중 일부 내용을 추가하였다
OS의 역할 간략 정리
-
- 추상화 (이건 지난 시간 + 파일 시스템)
- 가상 CPU, VM 과 파일 시스템 => 프로세스
- 보호 & 고립
- 자원 관리
파일 시스템
파일 시스템은 전반적으로는
가상 메모리 와 유사하다
가상 주소 -> 물리 주소 의 형태 처럼
file의 offset을 인덱싱 하여, block 에 있는 실제 파일 주소를 받는 방식이다
(이는 소프트웨어를 사용하여 처리한다)
(성능 차이가 적기에 그러하다)
위의 그림처럼
파일 시스템은 여럿이 존재하며,
각각의 시스템 콜을 각각의 파일 시스템이 처리하도록
중간에 하나의 계층이 존재한다 (Virtual File System)
(이는 ‘layer of abstraction : 추상화 계층’ 방식)
VFS가 현재 파일 시스템 구성 상황에 따라서
시스템 콜의 요청을 처리할 수 있는 파일 시스템에 요청한다
(VFS에 새로운 파일 시스템이 등록하는 것을 mount 라고 한다)
또한, 어떤 시스템은 buffer cache가 필요한데
우리가 무언가를 입력했을 때, 바로 disk에 입력해버리면
disk에 다 쓸때까지 어마어마한 오버헤드가 걸리기에
‘모든’ 일을 처리할 때까지 잠시 대기시킬만한 공간이 필요해서 그렇다
buffer cache를 사용할 때 유의할 점이 존재하는데,
- 최신 데이터는 DRAM의 buffer cache를 뜻한다
- fsync 같은 동기화 시스템 콜이 호출되어야 disk에 값을 쓰게 된다(persist)
위 유의할 과정 중 발생이 가능한 것은 ‘일관성’에 해당하는 문제이다
일관성을 위해서 필요한 것은 ‘원자성’과 ‘지속성’이다
이러한 ‘일관성’을 보장하기 위해
‘저널링’ 방식을 통해 진행하는데
이 중 ‘원자성’을 보장하려면 ‘소프트웨어’에서 관리를 해줘야 한다
(즉, 우리가 한다)
(파일 시스템에서 mount 를 할 때, atomic 해야 하나,
모든 케이스에 atomic을 걸면 성능저하가 발생한다
따라서 케이스에 따라 atomic을 걸어줄지 걸어주지 않을지 잘 판단)
대표적인 예시로 ‘일관성 문제’가 발생한다 할 때,
우리는 atomic 한지를 생각해야한다
write를 통하여 데이터를 ‘써’준다고 할 때,
‘fsync’를 통해 데이터를 disk에 동기화해야 한다
그런데 User가 데이터를 쓸 때,
User Space Write -> Page Cache Write -> Disk Write 인 상황이기에
Page Cache에서 Disk로 넘어간다는 ‘보장’은 없다
그렇기에 fsync를 통하여 쓰는 ‘순서’를 보장해야 한다
(같은 원리로, Create 등을 통해 ‘파일 디렉토리’를 만들더라도
어느 시점에 데이터가 쓰여질지 순서가 보장되지 않기에
Page Cache에 존재하는 데이터를 fsync를 통해 순서를 보장한다)
보호와 고립(Protection & Isolation)
보호
초기 OS 디자인은 User 와 Kernel의 구분이 다소 모호하였기에
이들은 같은 메모리 공간을 사용하였다
다만 문제가 있었는데
APP가 ‘깽판’을 칠 수가 있었다.
- Kernel 영역에 접근
- 다른 APP 영역에 접근
- 무한 루프 등을 통한 CPU 점유
그렇기에 이러한 3가지 문제를 해결해야
각각의 APP를 안전하게 다룰 수 있었다
그렇기에 이러한 보호를 위하여 각각의 해결책이 제시되었다
-
- 명령어 제한
- halt, mov 같은 명령어를 인터프리터가 확인하고 제한
(다만 이는 ‘느리기에’ 커널에서는 하드웨어에게 전임한다)
- 명령어 제한
-
- 다른 APP의 주소 보호
- 읽기/쓰기 권한 확인
- 다른 APP의 주소 보호
- Time Intrrupt를 발생시켜 다시 OS가 컨트롤을 가져온다
‘하드웨어’에게 ‘높은 권한’을 줌으로서
OS는 빠른 속도를 얻을 수 있었다
(OS는 모든 APP 중에서 가장 높은 권한을 가지나, 하드웨어보다는 낮은 권한을 가진다)
대표적으로 잘못된 명령어를 만난 경우, 하드웨어가
exception을 던지고, 이를 OS가 캐치한 후
process 를 kill할지 결정한다
이러한 ‘보호’를 매커니즘화 하여
하드웨어가 ‘반환’하면
OS는 ‘정책’에 따라 처리한다
(이러한 ‘권한’의 ‘분리’는 중요한 디자인 패턴이다)
(권리를 분리해 놓았기에 Ring mode라고도 한다)
ex) Page Table의 세팅은 OS가 하지만
그것이 올바른지의 여부는 MMU(하드웨어)가 한다
-
- Segment Paging(TMI)
- 가상 메모리의 변환 기법 중 세그먼트와 페이징을 모두 사용한 기법
여기서 인식해야 할 점은
바로 ‘선형 주소 공간’ 인데, 이는 사실 ‘가상 주소 공간’과 거의 유사하다
‘하나의 프로세스’에서 존재하는 Code, data, stack, heap 같은 ‘영역’은
일종의 ‘segment’이며
주어진 ‘논리 주소’를 통하여 segment Table을 통해
해당하는 ‘논리 주소’가 어떠한 ‘영역’의 Page 인지 판별한다
(이러한 ‘논리주소’ -> ‘선형주소’ 의 일은 CPU 내부에서 일어난다)
이 ‘선형 주소’는 실제 OS가 사용하는 가상 메모리 기법에 따라 구성요소가 달라지지만
여기서는 ‘선형 주소’를 통해 ‘페이징’ 기법을 이용하게 되므로
mlpt의 방식인 ‘Page Directory’, ‘Page Table’, ‘Offset’에 대한 정보를 담는다
(기본적으로 Segment Fault 가 뜨는 이유는
해당하는 논리 주소에 해당하는 ‘세그먼트’가 정의되어 있지 않거나,
접근할 수 없는 경우이므로
우리가 NULL 을 참조하려 한다면, 보통 세그먼트가 없다는 것을 의미하여 발생한다)또한 DPL을 통하여 접근하는 선형 주소가 어느 영역(User / kernel)인지
확인할 수 있다추가적으로 PTE의 User/Supervisor 부분을 통해서도
해당 페이지가 User 모드에서 접근이 가능한지, Kernel에서만 접근 가능한지
파악 할 수 있다
고립
기존의 ‘보호’에 해당하는 문제를 해결하기 위하여
(APP가 ‘커널’에 접근할 수 있는 문제)
OS 디자이너들은 Dual-mode OS를 내놓았다(위의 사진)
그러나 여전히 APP간의 침범이 가능하였고
그로 인하여 ‘고립’ 이라는 개념이 고안되었다
‘고립’의 조건은
- 다른 APP의 Data(heap/stack/data/code 등등)에 접근 불가(read/write)
- 다른 APP의 ‘코드’를 ‘수행’할 수 없어야 함(Jump 등)
다양한 방식을 통하여 이러한 ‘고립’을 보장할 수 있다
(ex : 인터프리터가 ‘명령어’를 확인하고 cut 한다)
하지만 Kernel에서 이 방식은 느리기에
마찬가지로 ‘하드웨어’에게 맡긴다
(user <-> kernel 의 모드 스위칭,
메모리 보호, 인터럽트 및 예외처리)
이처럼 ‘고립’을 통하여
완성된 개념이 하나 있었는데
그것이 바로 ‘Process’ 이다
고로, 이제 각각의 프로세스는 분리되어 있으며
각각의 주소를 가지기에(각각의 가상 주소 공간 / 선형 주소 공간)
우리는 ‘스케쥴링’에 따라 프로세스를 바꿀 때
‘문맥 전환(Context Switching)’이 필요하게 된다
다만 이 때, ‘고립’으로 인해 한가지 문제가 생기는데
‘process’ 끼리 통신이 필요한 경우,
별도의 처리를 해주어야 한다는 점이다
=> 이런 상황을 처리하기 위한 것이 ‘IPC’!
IPC는 크게 2가지 방식이 존재한다
-
- shared memory
- mmap 등을 사용하여 ‘공유되는’ 메모리를 사용한다
각각의 프로세스에서 메모리가 업데이트 된 것을 파악하고 공유를 받는다
(캐시의 존재로 인해, 같은 캐시를 불러오기에 다른 프로세스에서
값을 직접 볼 수 있음)
(다른 프로세스에게 ‘값’이 바뀌었다는 것을 알려줘야 함 : 동기화 문제)
(1. ‘시그널’을 이용 -> 다만 시그널은 비동기적이기에 시간이 걸림)
(2. polling -> cpu를 많이 씀(busy waiting과 비슷))
공유 메모리를 사용하기에 process 간 빠른 공유가 가능하다
- shared memory
-
- message passing
- 정해진 인터페이스를 통하여 다른 프로세스에게 데이터를 copy시킴
복사하는 방식이기에 ‘안정적’이다
데이터가 적으면 message passing을 사용하며,
데이터가 크면 shared memory의 사용이 고려된다
(데이터가 커도, 안정적이여야 한다면 message passing이 고려됨) - message passing
자원 관리
OS는 자원을 잘 관리해야 하는 역할 또한 가지고 있음
그렇기에 ‘여러’ 프로세스가
가능한 공정하게 자원을 나누어 써야 한다
그렇기에 2가지 방식의 ‘공유’가 이루어지는데
-
- Time Sharing
- CPU, Scheduling
-
- 스케쥴링
- 상태 머신을 통해 구현된다
이러한 스케쥴링은 ‘선점’과 ‘비선점’으로 분류된다
-
- FIFO
- 쉬운 구현,
convoy effect
(앞에 들어간 것의 완료시간이 오래걸리면,
전체 반응 시간이 느려짐)
참고사항 : FiFO는 ‘기아’ 상태를 발생시키지 않음
- FIFO
-
- SJF
- 좋은 반응 시간
기아 상태 발생
(우선순위가 계속 밀려서, 리소스를 받지 못하는 프로세스)
- SJF
-
- rr
- 기아 현상이 없으며, 좋은 반응시간
다만, timeslice가 커질수록 FIFO의 단점을 따라가며
timeslice가 작으면 Context Switching이 너무 자주 발생하여
오버헤드가 커진다
- rr
- 스케쥴링
- Time Sharing
-
- Space Sharing
- Memory, 페이지 교체
(page eviction : File-backed)
(swap : anonymous)
메모리 타입에 따른 Page Fault 처리
Anonymous- 최초 : 0으로 밀어버림
(보통 초기화를 위해 올라오게 된 Anonymous)
(0으로 밀어야 ‘고립’의 원칙을 지켜 보안을 유지) - Swap : disk에서 찾음
- File-backed
- file location에서 찾음
=> page fault 발생 시,
PT세팅 후, CPU에 재요청하라 한다 - Space Sharing
Page Replacement Policy
페이지를 disk에서 가져오고, 내리는 방식은
아~주 느린 방식이므로 가능한 페이지를 교체하지 않는 것이
성능 향상에 도움이 된다
(DRAM도 빠른편이 아니지만, disk는 DRAM보다 만배 이상 느리다…)
혹시, 나중에 호출될 페이지를 알게 된다면
가장 나중에 쓰이는 페이지를 빼면 되지만
(이를 Optimal 이라 하며, 비교군으로 쓰인다)
그것은 불가능하다
따라서 과거의 ‘접근 횟수’를 보고 예측한다
- receny : 얼마나 오래 전에 ‘읽었는지’
- frequency : 얼마나 자주 읽었는지
=> 지역성에 근거
(반복문에 존재로 인해 ‘같은 주소’에 반복적으로 접근한다)
(그렇기에 OS는 ‘과거’의 데이터를 기반으로 예측을 한다)
요점은 이러한 예측으로
‘Page Fault’를 줄이는것!
-
- LRU
- 보통 이것을 구현할 때, linked list를 이용한다
다만 ‘모든 메모리 접근 시’에 대하여 관측이 필요하기에
성능이 박살난다
(그래야 linked list의 위치를 바꿔줄 수 있음)
- LRU
-
- Clock
- LRU를 실제로 쓸 수 없기에
고안딘 열화판
대부분의 OS는 Clock 기반 정책을 사용하는데
이 때 사용되는 것이 Access 비트이다
(참조 비트 라고도 한다)
- Clock
댓글남기기