이틀만에 TIL을 다시 적어본다:) 어제는 막상 정리하자니 하나의 글로 쓸만한 분량이 안되는 것 같아서, 하루 쉬었다가 오늘 한 번에 정리하기로 하였다.
어제를 간단히 정리하자면, 일단 공부 시간이 많지 않았다. 전날 새벽에 공부 끝나고 같은 반 동기들과 나가서 한 잔 하다보니 점심 때 나왔고, 저녁에는 크래프톤 설명회도 있어서 여유 시간이 많지 않았다. 게다가 설명회 끝나고 팀원들과 함께 이번 프로젝트의 첫 번째 과제 구현에 도전했지만, 보기 좋게 실패하고 말았다. 정확히 말하면 실패인지 아닌지 알 수조차 없었는데, 깃북에 검색해보니 아직 테스트를 돌려볼 수 있는 단계가 아닌듯했다. 보다시피 anonymous page 관련해서 중간까지는 구현해야 test를 돌려볼 수 있는 모양이다. 하지만 우리는 Memory Management까지만 해보았으니, 제대로 돌아갈 리 없었다. 아쉬웠지만, 나는 아침형으로 라이프 스타일을 바꾼지 좀 되었기에 다음 날을 기약하며 하루를 정리했다.
물론 하루를 알차게 보내지 못한 점은 아쉽지만, 전날 보냈던 시간들을 후회하진 않는다. "어제 왜 그랬을까, 그러지 말걸" 과 같이 지나간 시간을 후회하는 과거형 후회보다는 "다음에는 이렇게 하지 않아야겠다" 와 같은 미래형 후회를 하는 것이 훨씬 바람직하다고 믿기 때문이다. 이건 참고로 정글에 들어와서 읽은 '자존감 수업'이란 책에서 상당히 감명 깊었던 내용으로, 책의 내용에 따라 후회할 일이 생기더라도 미래형 후회를 하려 노력하다보니 전반적인 삶의 질이 높아졌다. 정글에서 잘 생존하고 있는 원동력 중 하나이다 :)
오늘은 아침 일찍 나와서, 프로젝트 뿐만 아니라 '가상 메모리' 전반에 대한 공부를 하고자 하였다. 어제 크래프톤에서 오셨던 빼어난 정글 선배들에게 자극을 받아서인지, 뭐든지 더 잘 알아보고 싶다는 욕심이 생겼기 때문이다. 전날 구현을 어찌저찌 시도는 했지만, 그냥 OS와 VM 자체에 대한 전반적인 이해도가 부족하다는 생각에 분함을 느꼈던 점도 한 몫 했다.
인터럽트, 시스템 콜, 유저모드, 커널모드 - 쉬운코드
우선 위의 동영상을 시청하며 slid 를 활용해 아래와 같이 정리해가며 공부했다. 가이드에 따라 구현은 했지만, 전반적인 흐름을 완벽히 이해하긴 어려웠다. 하지만 오늘 오전에 위의 동영상 자료와 함께 곰곰이 생각해보니, 이전에 PintOS에서 구현한 개념에 대한 그림이 좀 더 선명하게 그려지는 느낌이다.
우리가 개발하는 프로그램은 일반적으로 user mode에서 실행된다. 만약 프로그램 실행 중에, 인터럽트가 발생하거나 시스템 콜을 호출하게 되는 경우라면 어떻게 될까? 실행 중이던 프로그램의 CPU 상태를 저장하고, kernel mode로 전환된다. 인터럽트는 시스템에서 발생한 다양한 종류의 이벤트, 혹은 그런 이벤트를 알리는 메커니즘을 뜻한다. 인터럽트는 위와 같은 경우에 발생하는데, 그 중 PintOS Project 1에서는 timer로 interrupt를 발생시키는 경우를 다룬 것이었다.
시스템 콜은 프로그램이 OS커널이 제공하는 서비스를 이용하고 싶을 때 호출하는 것으로, 위와 같이 다양한 종류가 존재한다. Project 2에서는 바로 이 시스템 콜을 호출하는 경우를 다룬 것이었다....!
다시 돌아와서, kernel mode로의 전환이 완료되면 커널 이 인터럽트 혹은 시스템 콜을 직접 처리하게 된다. 즉, CPU에서 커널 코드가 실행되게 된다. 참고로 커널 은, OS의 핵심으로 시스템 전반을 관리하고 감독 하는 역할을 담당하며, 하드웨어와 관련된 작업을 직접 수행 한다.
해당 인터럽트 혹은 시스템 콜에 대한 처리가 완료되면, 다시 스케줄링을 통해 어떤 프로그램의 CPU 상태를 복원하고, 다시 통제권을 user mode로 넘기게 된다. PintOS의 경우에는 이를 Priority Scheduling 을 통해 어떤 프로그램이 실행될 것인지를 결정해주었던 것이 이 과정에 해당하는 것이다. 그러니까 복원되는 프로그램이 꼭 직전에 실행 중이었던 프로그램이라는 보장이 없던 것이었다.
이렇게 해서 인터럽트와 시스템 콜이 유저 모드와 커널 모드 간에 제어권을 넘겨주는 전환을 발생시킨다는 개념을 잡고 나니, 그동안 모호했던 그림이 확실히 나아진 것 같아서 아주 기분이 좋다. 특히, 내가 작성한 코드가 high-level programming language가 wrapping해서 제공한 시스템 콜을 사용해왔다는 사실까지 알고나니, 기계와 한층 더 가까워진 느낌이다.
또한, CSAPP 9장을 전체적으로 다시 읽기 시작했는데 저번과 다르게 이번에는 정리를 병행하고, 번역본이 아니라 원서로 읽기로 했다. 내가 좋은 개발자를 넘어 뛰어난 개발자가 되기 위해 필요하다고 믿는 역량 중 하나는 영어인데, 아무래도 모국어로 된 자료보다 영어로 된 자료가 훨씬 풍부하고 양질의 자료 또한 많기 때문이다. 오죽하면 우리가 작성하는 코드도 어차피 다 영어니까, 결국 영어는 잘하면 잘할수록 좋을 것 같다.
위의 영상을 추천해준 정글 선배도 회사 선택 이유 중 하나가 다른 사내 구성원들과 영어를 쓸 일이 많아서였다고 했다. 또한 나에게 정글을 추천해 준 개발자 형도 영어의 중요성을 강조하며 영어를 꽤 하는게 분명 메리트가 될 것이라고 했는데, PintOS 깃북을 읽을 때나 CSAPP 원서를 읽을 때 확실히 다른 사람들보다 읽는 속도나 문맥 이해도가 빠르다보니 이점이 있다는 점에서 그 뜻을 체감하는 중이다.
어쨌든 CSAPP 번역본을 읽다보면 아쉬운 점이 상당히 많다. 어떤 문장을 아예 생략하기도 하고, 혹은 어떤 true여야 하는 전제를 반대로 false로 번역한다든지의 결정적인 오역이 있기도 했다. 오늘도 역시나 번역 누락과 오역을 몇 가지 발견했다. 아무리 파파고와 구글 번역이 발전했다곤 하지만 전반적인 문맥이나 흐름을 잡는데는 아직 부족해보인다. 그래서 CSAPP는 절대로 번역본만 읽어서는 안되고 원서도 함께 읽어야한다고 생각한다. 오늘은 위와 같이 CSAPP Chapter 9의 9.1장부터 9.4장까지 읽어보았다.
가상메모리(VM)의 등장 배경은, 어떻게 메모리를 보다 효율적으로 사용하고, 더 적은 에러를 갖도록 관리할 수 있을지에 대한 고민에서 시작되었다. 이를 통해 등장한 메인 메모리(물리 메모리)의 추상화 개념이 바로 가상 메모리라고 한다.초기 PC는 위와 같이, 물리주소를 활용해 곧장 메모리에 접근했다. 하지만, 이런 방식은 아무래도 보안에도 취약할 뿐더러, 정확히 physical memory 크기만큼만 사용할 수 있다는 점에서 다소 아쉽다.반면, 최근의 PC들은 가상주소를 사용해 메모리에 접근하는 virtual addressing 방식을 채택하고 있다:) 이러한 virtual addressing 방식에서 다음의 2가지 개념이 등장한다.
address space(주소공간)은, 음수가 아닌 정수 주소들로 구성된 정렬된 집합이다. 주소공간의 크기는 가장 큰 주소를 표시하는데 필요한 비트 수로 나타낸다. 가령, 어떤 주소공간이 N=2ⁿ 크기라면 이를 n-비트 주소공간이라고 부른다.
연속된 정수로 이루어진 경우에는 linear address space(선형주소공간)이라고 부른다. 편의를 위해 virtual address space, physical address space는 다음과 같이 연속된 정수로 이루어진 공간이라고 가정한다.
가상메모리는 Disk에 저장되어 있는 N개의 byte 크기 셀들이 연속되어 있는 배열으로 구성된다. 이 때, 각 Byte가 고유의 가상주소를 갖기에, 이 가상주소는 가상메모리 배열에 접근할 때 index로서 사용될 수 있다.
일반적으로 Disk에 저장된 배열의 내용은 메인 메모리에 캐시되어 있으며, 다른 캐시들과 마찬가지로 Disk의 데이터도 블록 단위로 나뉘게 된다. 각 블록이 Disk(low-level)과 메인 메모리(high-level) 사이에서 transfer unit 역할을 수행하게 되는 것이다.
가상메모리도, Disk에 저장된 배열로서 역시나 고정 크기의 블록들로 나누게 된다. 이처럼 가상메모리를 나누어 준 블록이 바로 가상페이지(virtual page)에 해당한다. 물리메모리 역시 블록으로 나누어 줄 수 있으며, 이 블록을 물리페이지(physical page) 혹은 페이지프레임(page frame)라고 부르는 것이다.
참고로, 가상메모리를 구성하는 가상페이지들은 언제나 다음의 3가지 부분집합으로 구성되며 중첩되지 않는다. 즉, 하나의 가상페이지는 하나의 부분집합에만 속하게 된다.메인 메모리의 경우, DRAM 캐시를 기반으로 하기에 첫 하나의 바이트를 읽어오는 비용이 크다. 따라서, 메인메모리에 캐싱해주게 되는 이 가상페이지를 4KB에서 2MB 정도로 상당히 크게 구성하는 경향이 있는 것이다.
또한 DRAM 캐시는 miss에 대한 패널티 역시 상당하다는 점에서 fully-associative 라는 특징을 갖는다. 즉, 위와 같이 physical page가 어떤 virtual page든 포함해줄 수 있도록 함으로써 miss를 최소화하는 것이다.
마지막으로, 본래 virtual page가 저장된 Disk에 접근하는 소요시간이 아주 길다는 점에서, DRAM 캐시는 언제나 write-back을 한다. 즉, 데이터를 써주더라도 캐시에만 업데이트를 하다가 필요할 때에만 디스크에 써주는 방식을 채택하고 있다는 것이다.
가상메모리 시스템은 virtual page가 DRAM의 캐싱되어 있는지 그 캐싱여부를 확인할 수 있어야 한다. 여기서 페이지 테이블 개념이 등장하는 것이다. 위의 페이지 테이블을 활용하면, virtual page의 캐싱여부를 확인하는 접근이 들어오는 경우 캐싱이 되어있다면 PAGE HIT 가 발생한다.반면, 캐싱이 되어있지 않다면 다음과 같이 PAGE FAULT를 유발하게 된다.한편 Disk와 메인 메모리 간에 페이지를 왔다갔다 시키는 것을 swapping 혹은 paging이라고 한다. 이 때, swap-in 은 페이지가 디스크로부터 DRAM으로 이동해 캐싱되는 것을 뜻하며, swap-out 은 페이지가 DRAM으로부터 디스크로 이동해 캐싱이 해제되는 것을 뜻한다. 이러한 swap을 발생시키는 방법 중, miss가 발생할 때까지 기다렸다가 그 때에만 swap을 진행하는 방식을 demand paging(요구 페이징)이라고 한다. 모든 최신 OS가 이 기법을 활용하며, 소스코드와 깃북을 살펴본 바에 따르면 PintOS 역시 마찬가지이다.
만약 단순히 새로운 가상페이지를 할당하는 경우, 아래의 그림과 같이 진행됨을 역시 확인해주어야 한다. 이전에 없던 페이지가 할당됨으로써, 그에 대응하는 PTE를 업데이트 해주는 것이다.지금까지 봤을 땐, 가상메모리는 page fault가 발생함에 따라 많은 작업을 요하고, 따라서 아주 비효율적일 것처럼 보인다. 하지만 가상메모리가 잘 작동하는 이유 중 하나는 바로 locality(지역성)이다. 프로그램들이 실행 중에 참조하는 페이지들의 크기 총합이 physical memory를 초과하더라도, 어느 특정 시점에든 항상 physical memory보다는 작은 크기의 active page들에 대해 작동할 것이라는 점이 locality에 의해 보장되기 때문 이다.
만약 이 active pages 집합 자체가 physical memory보다 커지는 경우에는 페이지들이 swap in, swap out을 반복하게 되며, 이를 thrashing이라고 한다. 보통의 경우라면 가상메모리가 원활히 동작하겠지만, 만약 어떤 프로그램의 퍼포먼스가 너무 저조하다면 이런 thrashing을 의심해볼 필요가 있다는 것이다.
이제까지의 논의에서는 페이지 테이블이 유일하다고 가정하고, 따라서 physical address space에 하나의 virtual address space만이 매핑되는 셈이었다. 하지만 실제 OS는 프로세스 별로 page table을 각각 따로 제공하고 있다.
위에서 소개한 demand paging, 그리고 별개의 구분된 가상주소공간의 개념은 OS에서 어떻게 메모리가 사용되고 관리되는지에 큰 영향을 미쳤다. 가상메모리가 linking, loading, code&data sharing, 그리고 memory allocation to application을 다음과 같이 간단하게 만들어준 것이다.
PintOS 3번째 프로젝트를 시작하고 이제 사흘정도 되었다. 이번에는 공부할 양이 너무나도 많아서 어떤 것부터 공부해야 할지 고민도 되고 마음도 불안한 것이 사실이다. 하지만, 초조해 할 시간에 하나라도 더 들여다보고 공부하다보면 분명 나에게 피가 되고 살이 될 것이라고 믿는다.
주말에 일정이 있어 오늘 저녁에 올라왔지만, 잠깐 헤어숍에 다녀온 시간 외에는 전부 열심히 공부하고 정리한 결과, 이렇게 개념을 어느정도 잡을 수 있었어서 기쁘고 뿌듯하다:) 내일도 틈틈이 깃북과 소스코드를 보고 TIL을 작성할 수 있을 정도로 열심히 공부해봐야겠다. 화이팅 !