자바스크립트를 활성화 해주세요

[iamroot] 3주차 스터디 회고 (리눅스 커널 내부구조)

 ·  ☕ 5 min read

iamroot 17기 스터디를 다행히 아직 참여하고 있다. 요새 진도 내용을 잘 못 따라가고 있기도 하고, 이전 스터디에 대한 복습이 필요한 것 같아, 내용을 정리하려 한다.

이전보다는 리눅스 커널 소스도 직접 읽어보고, 추가적으로 공부한 지식이 있으니, 당시 있던 의문에 더 정확한 결론을 내릴 수 있을 것 같다.

스터디 초기에는 커널의 내부 구성, 역할 등에 대한 이론을 이해하기 위해 리눅스 커널 내부 구조를 참고해 이론 스터디를 진행했다.

3주차 진도는 Chapter.5 ~ Chapter.7 (p130 ~ p203)이었다.

Chapter.5 파일시스템과 가상 파일시스템

Q. 블록체인 할당 기법이나, FAT 할당 기법이나 linked-list와 비슷해보이는데 무슨 차이인가? (p136)

둘 다 링크드 리스트 형태로 구현된 점은 같은 것으로 보인다. 임의의 위치 seek에 대한 시간 복잡도는 O(n)이 예상된다.

블록체인 할당 기법의 경우, 해당 블록에서 다음 블록에 대한 정보를 알아내서 그 블록으로 이동하는 행위를 반복해야 알 수 있다.

반면 FAT는 전역적으로 존재하는 FAT를 사용하므로, FAT가 저장된 블록만 읽어서 해당 블록을 찾아 이동할 수 있다.

가장 중요한 차이는 파일의 크기에 대해 seek의 복잡도가 O(n)인 것은 같지만, 여기서 표현하지 않은 계수의 크기가 n보다 큰 영향력을 발휘한다.

  • 현재 사용되는 DRAM인 DDR4 중 가장 느린 PC4-12800의 최고 전송 속도는 12800MB/s다.
  • 하드디스크나 SSD를 연결하는 SATA 3 인터페이스의 최고 전송속도는 6.0Gbps다.
  • DRAM은 모든 임의 접근에 대해 비슷한 속도를 내지만, 하드디스크의 경우 탐색시간, 회전 지연 시간 등으로 인해 매우 느려진다.

알고리즘 구현 방식으로 생각하자면, 블록체인 할당 기법은 재귀적(Recursive) 방식으로 구현하는 것이고, FAT는 반복적(Iterative) 방식으로 구현하는 것이라 생각할 수 있다.

Q. ext계열의 indirect block에서, indirect indexing 단계는 가변적으로 구성 가능한가? (p141)

indirect block의 순서에 따라 single, double, triple indirect indexing으로 고정된 것으로 보임.

만약 가변적으로 구성이 가능했다면, 책에서는 파일 크기를 설명할 때 48KB + 4TB * 3 으로 표현했을 것이다. (triple indirect indexing으로 3개 표현)

가변적이었다면 굳이 single, double, triple 뿐만 아니라 quadruple도 가능했을 것이다. 또한 indexing에 있어서 현재 몇단계 indexing을 나타내는 지에 대한 메타데이터 표현도 필요하다.

(페이지 테이블의 경우 페이지 크기의 하위 비트를 메타데이터로 사용하는데, 디스크도 이렇게 메타데이터로 사용할 수 있는 비트가 남는지는 확인이 필요할 것 같다.)

Q. inode 테이블을 모두 사용하는 경우는 언급하는데, 왜 데이터 블록을 모두 사용하는 경우는 언급하지 않는가? (p146)

일단 inode 테이블을 모두 사용할 가능성이 데이터 블록을 모두 사용할 가능성보다 높다. 블록 그룹 내 테이블의 비트맵 비율로 계산하면 inode 하나당 1024개 데이터 블록을 사용해야 동일한 비율로 가득 채움.

현재 블록의 크기가 4KB이므로, 평균 4MB의 크기 이상이 되어야 한다.(디렉터리가 일반적으로 4KB 크기인데도 이를 넘겨야 함.)

현재 블록 그룹에서 데이터 블록을 다 사용하면 다음 블록으로 옮기는 방식으로 구현될 것이라 예상된다.

굳이 inode 테이블을 모두 사용하는 경우를 언급하는 이유는 디스크의 용량이 꽉 차지 않았음에도 꽉 찬것과 똑같은 증상이 일어나기 때문으로 예상된다.

Chapter.6 인터럽트와 트랩 그리고 시스템 호출

Top half, Bottom half

책에서는 인터럽트와 트랩만 구분했지만, softIRQ와 같은 기법에 대한 이야기가 없는 것 같다. 심지어 대표적인 예시인 네트워크 부분에서도 언급이 되지 않은 것으로 보인다.

임베디드 프로그래밍 중, 리얼타임을 지원해야 할 때 사용되는 기법으로, Top half/Bottom half라고 하는데, 인터럽트 핸들링 해야할 양이 많을 때 사용되는 기법이다.

간단하게 요약하자면 인터럽트 핸들링은 최대한 빨리 이뤄져야 한다. 하지만 인터럽트 핸들러가 해야 할 일이 많을 때는 문제가 된다. 이 때 인터럽트 핸들러의 시간을 많이 잡아먹는 로직을 뒤로 미루는 방식이다.

네트워크의 경우를 예시로 든다면 아래와 같다. (네트워크 관련 코드를 직접 분석해 보지 못해 완전히 동일하지는 않을 수도 있지만, 맥락은 비슷할 것으로 예상한다.)

  1. 네트워크 카드가 패킷 수신을 완료하면 인터럽트를 발생시킨다.
    (아마도 DMA 영역에 복사해 놨을 것이다.)
  2. 해당 패킷에는 L2, L3, L4 헤더가 포함되어 있다.
  3. 각 레이어 별 헤더 파싱에 필요한 연산량은 인터럽트 발생 주기보다 시간을 더 많이 소요한다.
  4. 최소한의 데이터 검증만 수행하고 해당 패킷을 다른 버퍼에 복사한다. (Top half)
  5. 추후 커널 태스크에서(softIRQ) 쌓여있는 버퍼의 패킷 헤더 파싱을 수행한다. (Bottom half)
  6. 헤더 파싱 결과 라우팅을 하거나, 버리거나, 유저 프로세스에게 전달한다.

여기서 Top half와 Bottom half의 차이는 CPU time을 보장받을수 있냐/없냐의 차이로 볼 수 있다.

펌웨어 개발할 때는 이런 방식의 후처리 인터럽트를 사용하는 경우가 많았는데, 리눅스 디바이스 드라이버는 어떻게 처리해야 하는지 자세히는 모른다.

커널 내에서는 softIRQ를 사용하고, 디바이스 드라이버는 tasklet으로 Bottom half를 처리하는 것 같다. (내부적으로 tasklet이 softIRQ로 합쳐졌지만, 디바이스 드라이버에서 tasklet 등록 등의 API는 호환을 위해 남아있다고 한다.)

좀 더 자세한건 문c블로그를 참고하자.


JaeSang Yoo
글쓴이
JaeSang Yoo
The Programmer

목차