리눅스 커널 분석역사와 아키텍처 파헤치기 |
난이도 : 중급 M. Tim Jones, 컨설턴트 엔지니어, Emulex Corp. 2008 년 4 월 01 일 리눅스(Linux®) 커널은 거대하고 복잡한 운영체제의 핵심이며, 커다란 몸집에도 불구하고 하위 시스템과 계층 구조를 사용해서 조직화되어 있습니다. 이 기사에서는 리눅스 커널의 일반적인 구조를 살펴보고 주요 하위 시스템과 핵심 인터페이스를 파악합니다. 좀더 깊이 파고 들고 싶다면 다른 IBM 기사를 읽어보세요. 이 기사의 목표는 리눅스 커널을 소개하고 아키텍처와 주요 컴포넌트를 살펴보는 데 있다. 우선 리눅스 커널 역사부터 간략하게 짚어보기 시작해 다음으로 3만 피트 상공에서 리눅스 커널 아키텍처를 살펴보고, 마지막으로 주요 하위 시스템을 검토하겠다. 리눅스 커널은 코드가 600만 행이 넘으므로 소개글을 너무 장황하지 않게 줄였다. 좀더 깊이 파고 들고 싶다면 참고자료를 살펴보자.
이론의 여지에도 불구하고 리눅스가 최고로 인기 있는 오픈 소스 운영체제라고 하지만, 운영체제 전반의 역사를 미뤄볼 때 아주 짧은 기간 동안 고속 성장을 이룬 기린아다. 초기 컴퓨팅 시절에, 프로그래머는 하드웨어 언어를 사용해 하드웨어 기판 위에서 개발을 진행했다. 운영체제 부족은 단지 (사용자 한 명이 운영하는) 응용 프로그램 하나가 크고 값비싼 장비를 한번에 하나만 사용할 수 있음을 의미했다. 초창기 운영체제는 1950년대 개발되었으며, 개발 과정을 단순하게 만들 목적으로 시작되었다. IBM 701을 위한 GMOS(General Motors Operating System)나 IBM 709를 위해 노스 아메리칸 에비에이션이 개발한 FMS(FORTRAN Monitor System)가 대표적인 예다. 1960년에 MIT와 여러 회사는 GE-645를 위한 멀틱스(Multics, Multiplexed Information and Computing Service)라는 실험적인 운영체제를 개발했다. 이 운영체제 개발사 중 하나인 AT&T는 멀틱스를 포기하고 유닉스(Unics)라 부르는 독자적인 운영체제를 1970년에 개발한다. 이 운영체제와 함께 C 언어가 등장했으며, 운영체제 개발에 이식성을 부여하도록 유닉스를 C로 다시 쓰게 된다. 20년이 지난 다음에 앤드류 타넨바움은 소형 개인용 컴퓨터에서 동작하는 미닉스(MINIX, minimal UNIX)라는 마이크로커널 기반 유닉스(UNIX®)를 만들었다. 이 오픈 소스 운영체제는 1990년대 초반 리누스 토발즈에게 영감을 줘서 리눅스 개발을 시작하게 만들었다(그림 1 참조). 그림 1. 주요 리눅스 커널 버전에 대한 간략한 역사 리눅스는 개인 프로젝트에서 시작해 개발자 수천 명이 참여하는 범 세계적인 프로젝트로 급속히 발전했다. 리눅스에서 가장 중요한 결정은 GNU GPL(General Public License) 도입이었다. GPL 하에서, 리눅스 커널은 상업적인 착취를 피했으며, (리눅스 커널에 비해 방대한 소스를 자랑하는 리차드 스톨먼이 이끄는) GNU 프로젝트 결과로 만들어진 사용자 영역 개발도구를 개발 과정에서 활용하고 있다. GCC(GNU Compiler Collection)와 다양한 셸 지원을 비롯하여 유용한 응용 프로그램이 여기에 포함된다.
이제 GNU/리눅스 운영체제 아키텍처에 대해 조감하겠다. 그림 2에서 보듯이 운영체제를 두 단계로 생각할 수 있다. 그림 2. GNU/리눅스 운영체제의 기본 아키텍처
상단에는 사용자나 응용 프로그램 영역이 위치한다. 여기서 사용자 응용 프로그램이 실행된다. 사용자 영역 아래에서는 커널 영역이 있다. 바로 여기에 리눅스 커널이 존재한다. 또한 GNU C 라이브러리(glibc)가 있다. glibc는 커널과 연결하는 시스템 호출 인터페이스를 제공하며, 사용자 영역 응용 프로그램과 커널 사이에 전환 메커니즘을 제공한다. 이런 메커니즘이 중요한 이유는 커널과 사용자 응용 프로그램은 각기 다른 보호 주소 공간에 위치하고 있기 때문이다. 각 사용자 영역 프로세스가 독자적인 가상 주소 영역을 점유하고 있는데 반해, 커널은 단일 주소 영역을 점유한다. 세부 정보가 필요하면 참고자료 절에 나온 링크를 따라가기 바란다. 리눅스 커널은 세 단계로 세분할 수 있다. 상단에 시스템 호출 인터페이스가 있어서
크고 복잡한 아키텍처를 논할 때, 시스템을 다양한 관점에서 볼 수 있다. 아키텍처를 분해하는 목표 중 하나는 원시 코드를 이해하는 길을 제공하는 데 있는데, 바로 여기서 다룰 내용이다. 리눅스 커널은 중요한 아키텍처 속성 몇몇을 구현한다. 상위 단계와 하위 단계에서 커널은 독립적으로 구분되는 하위 시스템으로 계층화되어 있다. 또한 리눅스가 모놀리틱으로 취급되는 이유는 모든 기본 서비스가 커널로 뭉뚱그려 있기 때문이다. 마이크로커널은 통신, I/O, 메모리와 프로세스 관리와 같은 기본 서비스를 커널이 제공하는 반면에 기타 더 구체적인 서비스가 플러그인으로 마이크로커널 계층과 결합되기 때문에 모놀리틱 커널과 다르다. 양쪽 커널 방식에는 제각각 장점이 있지만 여기서는 논쟁을 피하고 싶다. 시간이 지남에 따라, 리눅스 커널은 메모리와 CPU 사용면에서 둘 다 효율성이 높아졌을 뿐 아니라 안정성도 높아졌다. 하지만 리눅스에서 크기와 복잡성을 제쳐두고 나면 가장 흥미로운 측면으로 이식성이 남는다. 리눅스는 다양한 아키텍처 측면과 관련된 각종 제약과 필요에 따라 수 많은 프로세서와 플랫폼에서 동작하도록 컴파일할 수 있다. 예를 들어, 리눅스는 MMU(Memory Management Unit)가 없는 CPU는 물론이고 있는 CPU에서도 동작한다. 리눅스 커널을 uClinux로 이식한 버전은 MMU가 없는 환경에서 돌아간다. 세부 사항은 참고자료 절을 살펴보자.
이제 그림 3을 나침반 삼아 리눅스 커널에서 주요 컴포넌트 몇몇을 분해해 살펴보자. 그림 3. 아키텍처 관점에서 바라본 리눅스 커널 시스템 호출 인터페이스(SCI)는 사용자 영역에서 커널 쪽으로 함수 호출을 수행하는 수단을 제공하는 얇은 층이다. 직전에 언급했듯이, 이 인터페이스는 아키텍처에 독립적이며, 심지어 동일한 프로세서도 차이가 난다. SCI는 실제로 흥미로운 함수 호출 다중화와 단일화 서비스를 제공한다. SCI 구현은 ./linux/kernel에 들어있으며, 아키텍처에 의존적인 구현은 ./linux/arch에 들어있다. 이 컴포넌트에 대한 세부 사항은 참고자료 절을 살펴보자.
프로세스 관리는 프로세스 실행에 초점을 맞춘다. 커널에는 프로세서의 개별 가상화(스레드, 자료, 스택, CPU 레지스터)를 대표하는 스레드라 불리는 실행 단위가 있다. 사용자 영역에서는 스레드 대신 프로세스라는 용어를 사용한다. 물론 리눅스는 두 개념(프로세스와 스레드)을 구분하지 않는다. 커널은 SCI를 통해 API(Application Program Interface)를 제공해 새로운 프로세스를 생성하고(fork, exec, 그리고 POSIX(Portable Operating System Interface) 함수), 프로세스를 멈추며(kill, exit), 프로세스 사이에 통신과 동기화를 수행한다(signal, 그리고 POSIX 메커니즘). 또한 프로세스 관리에서 활성 스레드 사이에서 CPU를 공유할 필요가 있다. 커널은 상수 시간에 동작이 가능한 뛰어난 스케줄링 알고리즘을 제공하는데, 여기에는 CPU를 놓고 경쟁하는 스레드 개수에 무관한 특성이 있다. 이런 스케줄러는 O(1) 스케줄러라고 부르며, 스레드 여러 개를 스케줄하거나 하나를 스케줄하거나 걸리는 시간이 똑같음을 나타낸다. O(1) 스케줄러는 또한 다중 프로세서를 지원한다(Symmetric MultiProcessing). 프로세스 관리 코드는 ./linux/kernel과 아키텍처에 밀접한 코드가 담긴 ./linux/arch에서 찾을 수 있다. 이 알고리즘을 좀더 자세히 알고 싶다면 참고자료 절을 살펴보자. 커널이 관리하는 또 다른 중요한 자원은 메모리다. 효율성을 위해 하드웨어가 관리하는 가상 메모리를 사용해, 메모리는 (대다수 아키텍처에서 4KB 크기인) 페이지라는 단위로 관리된다. 리눅스는 물리와 가상 메모리 사상을 위한 하드웨어 메커니즘은 물론이고 사용 가능한 메모리를 관리하는 수단을 포함한다. 하지만 메모리 관리는 4KB짜리 버퍼 관리를 넘어선다. 리눅스는 슬랩 할당자와 같은 4KB 버퍼가 넘어가는 추상화를 제공한다. 이 메모리 관리 기법은 기본으로 4KB 버퍼를 활용하지만 내부에서 구조체를 할당하면서 페이지가 꽉 찼는지, 일부만 사용 중인지, 비어있는지를 추적한다. 이런 기법은 더 큰 시스템의 요구에 따라 동적으로 메모리를 늘이고 줄이는 정책을 허용한다. 여러 사용자가 메모리를 사용하다 보면 가용 메모리가 바닥나는 경우가 생긴다. 이런 이유로 인해, 페이지는 메모리에서 벗어나 디스크로 이동할 수 있다. 이와 같은 과정을 스왑이라고 부르는 이유는 페이지가 메모리에서 스왑아웃되어 하드 디스크로 이동하기 때문이다. 메모리 관리 코드는 ./linux/mm에서 찾아보자. 가상 파일 시스템(VFS, Virtual File System)이 리눅스 커널에서 흥미로운 분야인 이유는 파일 시스템을 위한 공통 인터페이스 추상화를 제공하기 때문이다. VFS는 커널이 지원하는 시스템 호출 인터페이스와 파일 시스템 사이에 스위치 계층을 제공한다(그림 4 참조). 그림 4. VFS는 사용자와 파일 시스템 사이를 이어주는 스위치 구성을 제공한다. VFS 상단에는 open, close, read, write와 같은 공통 API 추상화 함수가 위치한다. VFS 하단에는 상위 단계 함수를 구현하는 방법을 정의하는 파일 시스템 추상화가 위치한다. (대략 50가지가 넘는) 특정 파일 시스템을 위한 플러그 인이 들어있다. 파일 시스템 코드는 ./linux/fs에서 찾아보자. 파일 시스템 층 아래에서는 버퍼 캐시가 있어서, 파일 시스템 층에 (특정 파일 시스템에 무관한) 공통 함수 집합을 제공한다. 이 캐시 층은 짧은 시간 동안 자료를 보존하는 (아니면 확실하지는 않지만 필요할 때 가용한 자료를 앞서 읽는) 방법으로 물리 디바이스 접근을 최적화한다. 설계에 따라, 네트워크 스택은 프로토콜 자체를 본 따 만든 계층화된 아키텍처를 따른다. IP(Internet Protocol)가 (보통 TCP(Transmission Control Protocol)로 알려진) 전송 프로토콜 아래에 존재하는 핵심 네트워크 계층 프로토콜이다. TCP 위에는 소켓 층이 있으며, SCI가 소켓 층을 호출한다. 소켓 계층은 네트워크 하위 시스템으로 이어지는 표준화된 API이며, 다양한 네트워킹 프로토콜에 대한 사용자 인터페이스를 제공한다. 가공되지 않은 프레임 접근부터 IP 프로토콜 자료 유닛(PDU)과 TCP/UDP(User Datagram Protocol)에 이르기까지, 소켓 층은 종단점 사이를 연결하고 자료를 전송하는 표준화된 방법을 제공한다. 네트워크 관련 코드는 ./linux/net에서 찾기 바란다. 리눅스 커널에 존재하는 엄청난 분량의 코드는 특정 하드웨어 디바이스를 사용하도록 만들어주는 디바이스 드라이버 단에 존재한다. 리눅스 소스 트리는 블루투스, I2C, 직렬 포트와 같은 다양한 지원 디바이스로 나뉘어진다. 디바이스 드라이버 코드는 ./linux/drivers에서 찾기 바란다. 리눅스 코드 대다수가 동작하는 아키텍처에 독립적인 반면에 일반적인 동작과 효율적인 동작을 위해 반드시 아키텍처를 고려해야 하는 요소가 있기 마련이다. ./linux/arch 하위 디렉터리는 커널 코드 중 아키텍처에 밀접한(집합적으로 BSP를 형성하는) 몇몇 하위 디렉터리 형태로 아키텍처 의존 부문을 정의한다. 전형적인 데스크톱 환경은 i386 디렉터리를 사용한다. 각 아키텍처 하위 디렉터리는 부트, 커널, 메모리 관리와 같은 커널의 특별한 측면에 초점을 맞춘 또 다른 하위 디렉터리를 포함한다. 아키텍처 의존 코드는 ./linux/arch에서 찾기 바란다.
리눅스 커널의 이식성과 효율성만으로 양이 안 찬다면, 지금까지 분해 과정에서 분류되지 않은 몇 가지 다른 측면을 살펴보기로 하자. 실제 업무 환경에서 동작하는 운영체제이자 오픈 소스인 리눅스는 새로운 프로토콜과 이런 프로토콜을 발전시키기 위한 테스트 베드로 적합하다. 리눅스는 전통적인 TCP/IP를 비롯하여 광범위한 네트워크 프로토콜을 지원하며, (1기가비트 이더넷이나 10기가비트 이더넷을 능가하는) 고속 네트워크를 위한 확장이 준비되어 있다. 리눅스는 또한 SCTP(Stream Control Transmission Protocol)와 같은 프로토콜을 지원하는데, 이 프로토콜은 (전송 단계 프로토콜을 대체하도록) TCP 위에서 여러 가지 첨단 기능을 제공한다. 리눅스는 또한 동적 커널로, 실행 중에 소프트웨어 컴포넌트 추가와 삭제가 가능하다. 이를 동적으로 적재가능한 커널 모듈이라 부르며, (모듈을 필요로 하는 특정 디바이스를 발견할 때) 필요한 경우 부트 시점이나 부팅 후 특정 시점에 사용자 개입으로 커널 메모리에 올릴 수 있다. 최근 리눅스 발전 동향으로 다른 운영체제를 위한 운영체제로 사용하도록 만드는 하이퍼바이저 기술이 떠오른다. 최근에 일어난 커널 변경은 커널 기반 가상 머신(KVM)이라고 부르는 기능이다. 이런 변경으로 인해 KVM이 활성화된 커널 위에서 다른 운영체제가 동작하도록 허용하는 사용자 영역 인터페이스가 새롭게 등장했다. 또 다른 리눅스 인스턴스는 물론이고 마이크로소프트 윈도우도 가상화할 수 있다. 유일한 제약은 기반 프로세서가 새로운 가상화 명령을 지원해야만 한다는 점이다. 세부 정보는 참고자료 절을 살펴보기 바란다.
이번 기사는 리눅스 커널 아키텍처, 특질, 기능을 맛만 보는 선에서 끝났다. 커널 내용에 대한 세부 정보는 모든 리눅스 배포판에 제공되는 Documentation 디렉터리를 살펴보면 된다. 여기서 다른 주제에 관심이 간다면 이 기사 끝 부분에 나오는 참고자료 절을 찾아서 더 많은 정보를 얻기 바란다. 교육
제품 및 기술 얻기
토론
|