-
OSTEP 중간 후기os 2025. 8. 23. 21:28
operating system three easy pieces 라는 책이다.
운영체제를 이루는 세 가지 큰 기둥을 중심으로 설계 원칙을 설명한다.
기둥1 - Virtualization
기둥2 - Concurrency
기둥3 - Persistence
1. Virtualization
옛날 옛적엔 하나의 머신에서는 동시에 단 하나의 프로그램만 수행했다.
그러다가 머신 하나에서 여러 개의 프로그램을 돌려야 할 필요성이 생겼고, 이를 이루기 위해서는 아래 문제를 해결해야 했다.
문제: 어떻게 하나의 CPU로 여러 개의 프로그램을 돌리지?(mechanism)
-> 프로그램들을 번갈아 가면서 돌리자(context switching). 다시 돌아올 때 필요한 정보를 저장해두자(process).
근데 어떤 프로세스한테 CPU를 줘야하지?(policy)
-> 작업이 할당된 순으로 돌리자, 빨리 끝나는 순으로 돌리자, 사용자 응답시간을 줄이는 순으로 돌리자
빨리 끝나는 순으로 돌리면 응답이 지연되고, 응답시간을 줄이면 느리게 끝나는데 어떡하지?
-> 작업에 우선순위를 부여하고 우선순위가 높은 녀석들을 먼저 처리하자.
우선순위는 언제낮추지?
-> 일단은 작업이 들어오면 최고 우선순위로 두고 CPU 받아서 일정시간 사용했는데도 작업이 남아있으면 우선순위를 낮춰주자.
각 우선순위에 속하는 작업 리스트를 관리하기 위해 자료구조로 큐 여러개를 사용하자.(multi level feedback queue)
우선순위 낮은 작업들은 CPU 못 받는 상태가 계속될 수 있으니까 주기적으로 우선순위 높여주자(priority boosting)
물리 메모리에 프로세스 여러 개를 올리니까 프로세스간에 격리가 안된다
-> 가상 메모리 레이어를 추가해서 한 프로세스가 다른 프로세스를 못보게 하자.
가상메모리 영역 전체를 물리메모리에 올리니까 물리메모리 공간이 부족하다.
-> code, data, heap, stack 영역을 각각 segment로 나눈 후에 각 영역이 커질수록 segment를 늘리는 식으로 하자.
물리 메로리에 여러 프로세스를 올려야 하는데 segment 사이에 공간이 애매하게 남아서 segment를 올릴 수가 없다.
(external fragmentation)
-> 페이징 방식으로 잘라서 올리자. 가상 메모리의 연속된 4KB 공간(page)과 물리 메모리의 연속된 4KB 공간(frame) 을 연결짓고 data, code, heap, stack 영역에 추가공간이 필요할 때 빈 frame을 하나 찾아서 page와 연결짓자.
페이지를 할당해야하는데 프로세스를 많이 띄워둔 상태라서 비어 있는 frame이 없는데 어떡하지?
-> 가장 필요없는 page와 연결된 frame를 사용하되 기존에 적혀 있는 정보는 따로 저장해두자.(Swap Out/In, Page Replacement Policy, LRU, Clock Algorithm)
page와 frame 연결 정보가 어딘가에 저장되어 있어야 하는데 어디에 있지?
-> 페이지 테이블을 만들고 page에 해당하는 frame 매핑정보를 저장해두자. 페이지 테이블 크기는 페이지와 똑같게 만들자.
32bit 아키텍처, 64bit 아키텍처 되니까 가상주소공간이 너무 커져서 페이지 테이블 하나에 페이지, 프레임 매핑정보를 다 저장할 수가 없네?
-> multi level page table 로 트리구조 만들어서 페이지 테이블을 가리키는 페이지 테이블을 만들자.
가상주소를 물리주소로 바꾸는데 페이지 테이블을 몇 번씩 건너가면서 봐야하는게 시간이 많이 걸리네?
-> 페이지, 프레임 매핑정보를 캐싱해두자(TLB, Translation Lookaside Buffer)
2. Concurrency
같은 작업을 수행하는데 프로그램을 두개 각각 띄워서 같은 data, code 영역이 중복해서 물리 메모리에 올릴 필요가 있나?
-> 하나의 프로세스에 여러 개의 실행흐름을 만들자(thread). 이러면 하나의 code, data 영역을 공유해서 사용할 수 있다. 쓰레드는 각자 실행흐름이 있으니까 자신만의 스택을 만들어주자.
쓰레드도 프로세스처럼 context switching을 해야하니까 스위칭할 때 필요한 정보를 저장해주자.
-> thread control block(TCB) 구조체를 메모리에 유지하고 여기 안에다가 필요한 레지스터 정보를 저장하자(program counter, 각종 레지스터값)
100번 1을 더하는 작업을 하는 프로그램을 쓰레드 2개로 돌리니까 값이 200이 아니네?
-> 메모리에서 레지스터로 값 10을 올리고(load) 1을 더한 순간 컨텍스트 스위칭이 일어나서 다른 쓰레드가 메모리에서 레지스터로 값 10을 올리고 1을 더하고 메모리로 save를 하면 메모리에 11이 저장되고 다시 컨텍스트 스위칭이 일어나서 11을 save한다. 이러면 각 쓰레드에서 1을 더하는 작업이 수행됐지만 값은 12가 아니라 11 이 된다.
쓰레드간 공유하는 변수에 접근해서 load 와 save 를 하는 구간을 임계구역(critical section)으로 정의하고 임계구역에 접근할 땐 한 쓰레드만 접근해서 사용할 수 있도록 하자(상호배제, mutual exclusion)
-> mutex, lock, semaphore, spinlock
상호배제는 해야하는데 락은 사용하기 싫다.
-> 하드웨어가 지원하는 atomic operation을 사용하자(compare and swap, test and set)
3. Persistence
CPU가 작업하다가 디스크->메모리 데이터 로딩 필요할 때 작업 멈추고 메모리에 데이터 로딩해야 한다.
-> DMA(Direct Memory Access) 하드웨어를 컴퓨터에 추가해서 얘가 로딩하게 만들고 CPU는 다 로딩될 때까지 다른 프로세스 작업 하다가 로딩 다 돼면 인터럽트 받아서 다시 이어서 원래 작업하자
파일을 어떻게 열지?
-> foo.txt 파일을 열려면 open('./foo.txt') 라는 syscall 로 열고 반환값은 file descriptor(fd)로 불리는 int 값이다. 해당 값은 프로세스당 하나씩 생성되는 file descriptor table의 인덱스이고 fd로 테이블에 접근해서 entry의 주소값을 얻을 수 있다. 각 entry는 open file table 이라는 머신에 하나 존재하는 table에 존재하고 entry는 inode를 가리킨다.
결국 foo.txt 라는 문자열을 open 의 인자로 주면 이에 해당하는 inode를 찾아준다는건데, inode가 뭐지?
-> inode는 index node의 약자이고, 파일을 이루는 데이터 블록의 위치정보와 가장 최근에 접근한 시간 등 메타정보를 저장하고 있다. 파일을 만들 때 inode 하나를 할당받는다. 적절한 inode를 할당해주는 녀석이 file system 이라는 소프트웨어이다. foo.txt 라는 파일은 어떤 디렉토리 안에 존재하고, 디렉토리 또한 파일이며 이에 해당하는 데이터 블록 안에 foo.txt->inode number 연결 정보가 저장되어 있다. 따라서 ./foo.txt를 open하면 우선 ./ 디렉토리의 데이터블록을 찾아서 그 안에 적혀 있는 foo.txt->13(예시) 이라는 정보를 읽은 후, inode number=13에 해당하는 inode를 디스크에서 찾아서(offset 기반) 메모리에 올려서 내용을 읽어서 해당 파일이 가지고 있는 데이터 블록들의 위치정보를 구할 수 있다.
계속 읽고 있다.