블로그 이전했습니다. https://jeongzero.oopy.io/
Kenrel of linux - File system of Linux(2)
본문 바로가기
컴퓨터 관련 과목/운영체제 & 커널

Kenrel of linux - File system of Linux(2)

728x90

저번시간에 설명한 vma에 대해서 좀더 자세하게 알아보는걸로 강의가 시작된다.

1. VMA


VMA에 대해서 다시한번 설명을 해보자. 현재 실행중인 프로세스마다 각각의 task_struct 즉, PCB가 존재하고 PCB의 mm struct를 따라가보면 여러 필드가 있고, 그중 mmap 필드를 통해 현재 프로세스가 사용중인 메모리의 정보를 알수 있다.

실제 mmap 필드는 vm_ares_struct 현태로 되어있으며 각 vm_area_struct가 우측 노란색 address space 중 흰색 영역의 정보를 가지고 있다.

vm_aread_struct에는 현재 예를 들어 코드 영역이면 코드영역의 시작주소, 끝 주소, 권한, 등의 정보가 들어있다.

저 한줄한줄이 vm_area_struct 필드에 담겨있다.

vm_area structrue는 실제 위과 같이 구성되어 있다.

  • vm_start : 시작 주소
  • vm_end : 끝 주소
  • vm_ops : 읽거나 쓰거나 등등을하는 operations. 해당 영역에는 page mapping table과 관련된 정보도 들어있는데, 이는 뒤에서 자세히 설명할것이다.
  • vm_mm : points back to this VMA's mm_strcut
  • vm_next : list of VMA's
  • vm_file : 디스크 어디서부터 왔는지

정리를 해보자. VMA는 현재 메모리 내에 접근할수 있는 주소 영역이다. 해당 memory area에는 접근에 관련된 권한이 설정되어있으며, 내용으로는 코드 영역, bss 영역, 스택영역 등이 존재한다.

이러한 영역은 커널이 런타임시에 동적으로 추가시키거나 삭제시킬수 있다.

2. Paging


여기까지의 설명으로는 만약 우리가 a.out 이라는 프로그램을 만들었고, a.out을 실행시키면 디스크에 들어있는 a.out의 memory area 영역들이 메모리에 올라오게 된다. 헌데 만약 a.out의 크기가 현재 메모리의 사이즈보다 훨씬 크면 어떻게 될까?

그런 경우엔 a.out을 동일한 사이즈로 나누게 된다. 이 단위를 페이지라고 하며, 메모리에선 이러한 페이지들을 관리하기 위한 table이 존재한다. 테이블에는 각 페이지를 가져와야할때를 위해 disk address가 인덱스마다 들어있다.

그럼 만약 a.out의 main부터 실행하려고 한다면 이 테이블을 먼저 참조하게 되고, 해당 테이블에 main과 관련된 페이지가 없다면 disk address를 참조하여 디스크로부터 해당 페이즈를 메모리에 가져온다. 이렇게 현재 실행하려는 페이지를 디스크로부터 메모리로 가져오는 일을 page fault라고 한다.

이렇게 page fault가 발생해서 disk에서 특정 page가 메모리로 올라오면, table의 disk address는 메모리 어디에 올라왔다는 정보를 저장한다. 따라서 이 table 이름을 page mapping table 이라고 부른다.

따라서 a.out의 페이지가 100개라고 해도 100가 전부다 올라오는게 아니라 현재 당장 수행하려는 페이지만 올라와도 a.out은 잘 돌아간다.

페이지 사이즈가 4KB 라고 가정해보자. 만약 32bit 시스템에서는 4KB 사이즈의 페이지에 접근하기 위해선 4000 = 2^12 즉, 대략 12bit이 필요하다. 그럼 자연스레 나머지 20bit는 현재 찾으려는 page가 어디있는지로 사용된다.

따라서 20bit로 페이지 주소를 지칭하는데 사용하고, 나머지 12bit으로 실제 페이지의 특정 바이트를 가져온다. 따라서 32bit 시스템에서는 2^20의 페이지를 거느릴수 있다.(64bit 에서는 2^52) 2^20 이상의 페이지가 존재하는 프로그램은, 그게 사용되야할때 메모리로 올라오는 page fault가 발생한다.

정리를 해보면 32bit 시스템에서는 page table을 위해 2^20개의 엔트리가 필요하고, 64bit 시스템에서는 page table을 위해 2^52개의 엔트리가 필요하다.

그런데 이러한 엔트리로 각각의 프로세스를 관리하는건 너무 큰 낭비이다. memory area가 메모리에 연속해서 존재하는것도 아니고;; 32bit라고 하면 2^20 엔트리중 사용되는 부분을 제외하면 사용안되는 부분은 낭비되고 있다는 소리이다.

32bit 머신이라고 했을때 2^20개의 엔트리를 가진다고 했다. 즉, 2^20 사이즈의 배열이라고 생각하면 편한데, 이 2^20의 엔트리가 전부다 사용되는건 아니고, 위 사진 처럼 이 중 3개 엔트리(ex, 코드 세그먼트, text 세그먼트, 라이브러리 세그먼트)만 사용되고 나머지는 낭비되고 있다. 얼마나 비효율적인가?

따라서 20bit으로 사용되었던 부분을 절반으로 나누고, 나눈 10bit를 특별한 용도로 사용하였다. 바로 10bit로 directory의 역할을 하는 테이블을 하나 추가하였다. 10bit → 2^10 대략 1000개의 엔트리를 가지는 테이블에 실제 사용되고 있는 엔트리에만 해당 세그먼트의 정보를 담는다.

즉, 만약 directory 0번째 엔트리가 사용중이면 해당 엔트리에 2^10개의 엔트리를 만들고 해당 페이지로 예를 들어 코드영역 세그먼트를 관리하는 식으로 한다는 말이다.

이렇게 관리를 하면 하나의 프로세스당 2^20개의 엔트리가 필요하지 않고 사용되는 세그먼트들만 2^10개의 엔트리를 할당해줄수 있고, 사용하지 않는 엔트리는 생성하지 않아 낭비가 발생하지 않는다.

이렇게 테이블 사이즈를 확 줄일수 있게 되었다. 설명한 용어를 정리하면 다음과 같다

page directory를 위한 bit를 directory number, 그 directory에서 생성한 페이지 테이블을 page_number, 실제 페이지 테이블에 수행하려는 페이지의 특정 바이트를 offset 이라고 부른다.

총정리를 해보자.

하나의 프로세스마다 PCB가 존재하고, 그 PCB는 6개로 구성된다. 그중 메모리와 관련된 정보는 struct mm에 들어있고, mm_struct에는 또 여러 필드들이 있는데, vm_area_struct 필드엔 현재 메모리에 올라와있는 세그먼트들의 시작,끝 주소와 권한, 파일명 등의 정보가 들어있다.

그리고 mm_struct안에 pgd(page directory)에는 2^10 사이즈의 엔트리를 갖는 디렉토리가 있고, 해당 디렉토리에 실제 set 된 세그먼트를 위해 각각 2^10 엔트리를 갖는 page table 엔트리를 생성해준다.

이 페이지 테이블 엔트리를 보고 만약 여기에 수행하려는 페이지 영역이 올라와있으면, 메모리 어디에 매핑 되어있는지에 대한 정보를 알수있고, 만약 없으면 disk address를 참조하여 디스크로부터 해당 페이즈를 가져오게 된다. 이를 page fault가 났다라고 한다.

3. 강의 총 정리


여기까지가 강의의 끝이다. 여태까지 리눅스 커널에서의 시스템콜, 스케줄링, 메모리 관리, 파일 시스템 등을 위주로 학습을 하였다. 이 이외에도 리눅스 커널에 관련된 알아야할 정보들이 아주 많다. 네트워크 시스템과 관련하여 TCP/IP 관련 처리, EXT2, EXT3 같은 파일시스템 종류들 등..

헌데 해당 강의에서는 저런 내용은 다루지 않았다. 그 이유는 리눅스가 개발된 의의로 설명할수 있다고 한다.

만약 우리가 땅을 샀다고 하자. 그리고 그 땅위에 여러 건물을 올리고 싶다. 땅은 이제 있으니 건물을 올리기 위한 작업이 필요하다. 각 건물들마다 크기도 다 다르고, 구성되는게 다르기 때문에 어떻게 땅위에 기반을 다질껀지가 중요하다.

검은색이 땅이라면 건물 하단에 표시되는 네모가 바로 그 건물이 올라오기 위한 기반이다. 나는 땅위에 각 건물들을 올리기 위한 기반을 일단 다지면 된다. 체육관은 사람이 많이 들어오고 애기들이 주로 오니까 바닥은 뭐로 하고, 그 위에는 나무 장판이 무조건 들어와야하고.. 등등의 설계 및 구현만 하면 되는 것이다.

그게 완성되면 학교 건물을 짓는 전문가가 와서 학교를 지으면 된다. 전문가는 내가 다져놓은 기반 설계에 맞는 나무 장판을 골라서 딱 껴 넣으면 끝이다. 체육관도 마찬가이지오 PC방도 마찬가지이다. 이렇게 우리가 여태 배웠던 건 리눅스 커널의 platform 즉 기반을 다지는 설계 및 구현에 대한 전체적인 흐름을 학습한 것이다.

이렇게 다져놓은 platform 기반 위에 Plug-in-Modules인 Ext2,Ext3,TCP/IP,Camera 등등이 꼽혀서 사용된다. 따라서 학습한 platform 이야 말로 매우 중요한 부분이라고 볼수있다.

추가적으로 원래의 유닉스는 Monolithic kernel 아키텍처를 따랐다. 해당 커널은 단일 커널로써, 커널 내부에 모든게 다 들어와서 동작하는 구조이다. 이해하기 쉽게 아래의 그림을 보자

모놀리틱 시스템의 전자회로는 저렇게 되있다고 생각하면 된다. 회로 내부에 모든 기능이 다 들어가 있다. 마치 위 사진의 네트워크 케이블처럼 말이다. 하나가 고장나면 전체를 뜯어서 수정하거나 많은 소요 시간이 든다. 단 모든게 다 회로에 들어가있기 때문에 성능은 빠르다.

이거와 반대로 modular 시스템은 회로 위에 각 디바이스들을 plug 할수있게 하였다. NIC, 메모리, 등등의 인터페이스가 카드가 말이다. 마치 각 레고블럭들을 이용해서 하나로 만든것처럼 말이다.

이렇게 리눅스는 유닉스 기반으로 생성된 시스템이기 때문에 초기엔 모놀리틱 시스템으로 구현되었다.

하지만 우리가 현재 많이 쓰는 GNU/linux 시스템은 크게는 모놀리틱 시스템을 따르지만 실제로 내부에는 plug-in 모듈처럼 각 디바이스들을 모듈화시켜 관리할수 있게 구현되어 있다.

커널은 이렇게 모놀리틱 커널이외에도, 커널 내부에서 모든걸 관리하는게 아닌 유저랜드와 구분지어서 관리하는 마이크로 커널, 이 두개를 합친 하이브리드 커널 등의 종류가 있다. 자세한건 또 후에 공부해봐야 겠다.

끝으로 강의를 통해 느낀점이 있다.

사실 강의의 대부분은 학교에서 배웠던 내용이다. 하지만 그때의 이해도와 지금의 이해도는 너무나도 차이가 나는거 같다.

시스템 해킹 공부를 하면서, 내가 학교때 가라로 공부했구나를 깨달아서 부족한 부분을 다시 채웠다. 확실히 시험을 위한 공부가 아닌, 스스로 부족한 부분을 위해 공부를 하다보니 더 잘 이해가 되고 또 하다보니 욕심이 생겨서 공부의 난이도도 점차 올라갔다.

여하튼 커널 익스플로잇 과 관련하여 한번 조저보려고 할 찰나에, 해당 온라인 강좌를 알게 되었고, 12개 정도의 강의를 학습하고 정리하면서 정말 많은 기초를 다시 다잡을수 있었다. 무엇이든 기초가 제일 중요한거 같다.

728x90