Home 모던 리눅스 교과서 2. 리눅스 커널
Post
Cancel

모던 리눅스 교과서 2. 리눅스 커널

리눅스 커널

  • 운영체제의 주요 기능이 다양한 하드웨어를 추상화하고 API를 제공하는 것임을 배웠다.
  • 이 API를 이용한 프로그래밍을 통해 앱들이 어디에서 어떻게 실행되는지 걱정할 필요 없이 애플리케이션을 작성할 수 있다.
  • 커널은 이러한 API를 프로그램에 제공한다.
  • 2장에서는 리눅스 커널이 무엇인지, 그리고 그 구성 요소뿐 아니라 전체로서 이를 어떻게 생각해야 하는지 등을 살펴본다.
  • 이 과정에서 필수 용어를 이해하고 프로그램과 커널 간의 인터페이스를 파악하며 그 기능이 무엇인지에 대한 기본 개념을 갖추는 것이 목표이다.

리눅스 아키텍쳐

Linux-Layer

  • 하드웨어 계층
    • CPU와 메인 메모리부터 디스크 드라이브, 네트워크 인터페이스는 물론 기보드나 모니터 같은 주변 디바이스를 일컫는다.
  • 커널 계층
    • 이번 게시물에서 중점적으로 다룰 내용이다. 커널과 사용자 영역 사이에는 init 시스템과 시스템 서비스(네트워킹 등)처럼 많은 구성요소가 있지만, 이들은 엄밀히 말해 커널의 일부가 아니다.
  • 사용자 영역(user land) 계층
    • 셸(shell)같은 운영체제 구성요소, ps나 ssh 같은 유틸리티, x윈도우 시스템 기반 데스크톱 같은 그래픽 사용자 인터페이스(GUI)를 비롯해 대부분의 앱이 실행되는 곳을 일컫는다.
  • 다른 계층 간의 인터페이스는 리눅스 운영체제 패키지의 일부이며 잘 정의돼 있다. 커널과 사용자 영역 사이에는 시스템 콜(system call, 줄여서 syscall)이라는 인터페이스가 있다.
  • 하드웨어와 커널 사이의 인터페이스는 시스템 콜과 달리 단일 인터페이스가 아닌 하드웨어별로 그룹화된 개발 인터페이스 모음으로 구성된다.
    • CPU 인터페이스
    • 주 메모리 인터페이스
    • 네트워크 인터페이스와 드라이버
    • 파일 시스템과 블록 디바이스 드라이버 인터페이스
    • 캐릭터 디바이스, 하드웨어 인터럽트, 키보드, 터미널, 기타 I/O 등의 입력 디바이스를 위한 디바이스 드라이버
  • 셸이나 grep, find, ping 같은 유틸리티처럼 일반적으로 리눅스 운영체제의 일부로 여기는 많은 것이 실제로는 커널의 일부가 아니라 사용자 영역의 일부이다.

사용자 모드와 커널 모드

  • 실질적으로 하드웨어로의 접근 권한이 얼마나 있고 사용 가능한 추상화가 얼마나 제한됐는지를 나타낸다.
  • 일반적으로 커널모드 는 추상화를 제한함으로써 빠르게 실행함을 의미하는 반면, 사용자 모드 는 상대적으로 느리지만 더 안전하고 편리한 추상화를 의미한다.

CPU 아키텍처

  • 리눅스가 인기 있는 이유 중 하나는 리눅스가 다양한 CPU 아키텍처에서 실행된다는 점 때문이다.
  • 제너릭 코드와 드라이버는 물론이고, 리눅스 커널은 아키텍처별 코드도 포함하고 있다.
  • 이러한 코드 분리 덕분에 리눅스를 새 하드웨어에 이식하고 빠르게 사용하는 것이 가능하다.
  • 현재 리눅스가 실행 중인 CPU를 파악하는 방법
    1. BIOS와 방호 작용하는 dmidecode라는 전용 도구 사용하기(혹은 lscpu)
    2. cat /proc/cpuinfo를 통해 알아보는 것
    3. 간단하게 아키텍처에만 관심이 있는 경우 uname -m

BIOS와 UEFI

  • 유닉스와 리눅스는 전통적으로 자체 부트스트랩을 위해 BIOS(Basic I/O System)를 사용한다.
  • 리눅스 노트북은 전원을 켜고 나면 완전히 하드웨어로 제어된다.
    • 먼저 하드웨어는 BIOS의 일부인 POST(시동 자체 시험)를 실행하도록 연결된다.
    • POST는 하드웨어(RAM 등)가 지정된 대로 동작하는지 확인한다.
  • 모던 환경에서는 이러한 BIOS 기능이 운영체제와 플랫폼 펌풰어 간의 소프트웨어 인터페이스를 정의하는 공개 사양인 UEFI(통일 확장 펌웨어 인터페이스, Unified Extensible Firmware Interface)로 대체 됐다.

x86 아키텍처

  • x86은 원래 인텔에서 개발했으며 나중에 AMD에도 라이선스된 명령어 세트 제품군이다.
  • 커널 내에서 x64는 인텔 64비트 프로세서를 나타내고 x86은 인텔 32비트를 뜻하며, amd64는 AMD 64비트 프로세서를 말한다.
  • x86 CPU 제품군은 데스크톱과 노트북에서 주로 볼 수 있지만 서버에도 널리 사용된다.
    • 특히 x86은 퍼블릭 클라우드 기반으로 강력하고 널리 사용 가능한 아키텍처지만 에너지 효율은 높지 않다.
    • 부분적으로 비순차적 실행(out-of-order excution)에 대한 의존도가 높기 때문에 멜트다운같은 보안 문제와 관련해 최근 많은 관심을 받았다.
  • 리눅스/x86 부팅 프로토콜이나 인텔, AMD 관련 배경 등의 자세한 내용은 x86 관련 커널 설명서를 참조하면 된다.

ARM 아키텍처

  • ARM은 RISC(Reduced Instruction Set Computing)아키텍처 제품군이다.
    • RISC는 일반적으로 더 빠르게 실행할 수 있는 작은 명령어 세트와 많은 일반 CPU레지스터로 구성된다.
    • 때문에 휴대용 디바이스(아이폰, 안드로이드, 라즈베리 파이 같은 IoT 임베디드 시스템)에서 찾아볼 수 있다.
  • ARM은 x86 칩보다 빠르고 저렴하며 발열량이 적기 때문에 데이터 센터에서 AWS 그래비톤 같은 ARM 기반 CPU가 점점 더 많이 사용되는 상황이다.
  • 또한 x86보다 간단하지만 스펙터(Spectre)같은 취약점에 영향을 받지 않는다.
  • 원래 UC버클리에서 개발한 개방형 RISC 표준이다.
    • 현대에는 많은 기업들이 이를 이용한 다양한 구현이 존재한다.
  • 아직 널리 사용되지는 않지만 비교적 새로운 아키텍쳐이다.

커널 구성요소

  • 리눅스 커널은 모놀리식 커널이지만, 코드베이스에는 특정 역할을 식별하고 부여할 수 있는 기능 영역이 나뉘어 있다.
  • 커널은 위에 설명했던 것처럼 하드웨어와 실행하려는 앱 사이에 있다.
  • 커널 코드베이스에서 찾을 수 있는 주요 기능 블록은 아래와 같다.
    • 프로세스 관리 : 실행 파일을 기반으로 프로세스를 시작함
    • 메모리 관리 : 프로세스에 메모리를 할당하거나 파일을 메모리에 매핑함
    • 네트워킹 : 네트워크 인터페이스 관리나 네트워크 스택을 제공함
    • 파일 시스템 : 파일 관리를 제공하고, 파일 생성과 삭제를 지원한다.
    • 이외에 캐릭터 디바이스와 디바이스 드라이버 관리를 함.
  • 이러한 기능 구성요소는 상호 의존성이 있을 때가 많기 때문에 커널의 개발자 모토인 ‘커널은 사용자 영역을 손상시키지 않는다’를 실제로 확실히 지키면서 작업하기 어렵다.

프로세스 관리

  • 커널에는 프로세스 관리와 관련된 부분이 여럿 있다.
  • 그중 일부는 인터럽트 같은 CPU 아키텍처 관련 사항을 처리하고, 다른 부분은 프로그램 실행과 스케줄링에 중점을 둔다.
  • 리눅스에서는 가장 큰 단위부터 가장 작은 단위까지 다음과 같이 나뉜다.
    • 세션
      • 하나 이상의 프로세스 그룹을 포함하고 선택적으로 tty가 연결된 상위 수준의 사용자 대면 유닛을 나타낸다.
      • 커널은 세션ID(SID)라는 번호를 통해 세션을 식별한다.
    • 프로세스 그룹
      • 하나 이상의 프로세스가 포함돼 있으며 한 세션에는 foreground 프로세스 그룹이 둘 이상일 수 없다.
      • 커널은 프로세스 그룹 ID(PGID)라는 숫자를 통해 프로세스 그룹을 식별한다.
    • 프로세스
      • 여러 리소스(주소 공간, 하나 이상의 스레드, 소켓 등)를 그룹으로 추상화한 것이며, 커널은 /proc/self를 통해 현재 프로세스를 사용자에게 노출한다.
      • 커널은 프로세스 ID(PID)라는 숫자를 통해 프로세스를 식별한다.
    • 스레드
      • 커널에 의해 프로세스로 구현된 유닛을 말한다.
      • 즉 스레드를 나타내는 전용 데이터 구조는 없다.
      • 스레드는 특정 리소스(예: 메모리나 signal handler)를 다른 프로세스와 공유하는 프로세스이다.
      • 커널은 스레드ID(TID)와 스레드 그룹ID(TGID)를 통해 스레드를 식별하며, 공유된 TGID값은 멀티스레드 프로세스를 의미한다.
    • 태스크
      • 커널에는 sched.h에 정의된 task_struct라는 데이터 구조가 있으며, 이는 프로세스와 스레드 구현의 기반을 형성한다.
      • 이 데이터 구조는 스케줄링 관련 정보, 식별자(예 : PID, TGID), signal handler, 성능이나 보안과 관련된 기타 정보를 수집한다.
      • 앞서 언급한 모든 유닛은 태스크에서 파생되며 고정된다.
      • 하지만 태스크는 커널 외부에 그대로 노출되는 일이 없다.

        메모리 관리

  • 가상 메모리는 시스템이 물리적으로 갖고 있는 것보다 더 많은 메모리를 갖고 있는 것처럼 보이게 한다.
  • 모든 프로세스는 많은 가상 메모리를 얻는다.
  • 이 부분은 운영체제 게시물에서 자세히 언급했으니 자세히 다루지 않는다.

네트워킹

  • 커널의 중요한 기능 중 하나는 네트워킹이다.
  • 웹을 검색하거나 원격 시스템에 데이터를 복사하려면 네트워크에 의존해야 한다.
  • 리눅스의 네트워크 스택은 계층화된 아키텍처를 따른다.
    • 소켓 : 추상화 커뮤니케이션을 위해 필요함
    • 전송 제어 프로토콜(TCP) 및 사용자 데이터그램 프로토콜(UDP) : 각각 연결형 통신과 비연결형 통신용
    • 인터넷 프로토콜(IP) : 기기의 주소 지정을 위해 필요
  • 위의 세 가지 작업은 커널이 처리한다.
  • HTTP나 SSH 같은 애플리케이션 계층 프로토콜은 주로 사용자 영역에서 구현된다.
  • 자세한 내용은 추후 네트워킹 게시물로 따로 다룰 예정임.

파일 시스템

  • 리눅스는 파일 시스템을 사용해 하드 디스크 드라이브(HDD), 솔리드 스테이트 드라이브(SSD), 플래시 메모리 같은 저장 디바이스의 파일과 디렉터리를 구성한다.
    • ext4, btrfs, NTFS 같은 다양한 유형의 파일 시스템이 있으며 동일한 파일 인스턴스도 여러 개 사용할 수 있다.
  • 가상 파일 시스템(VFS)은 원래 여러 파일시스템 유형과 인스턴스를 지원하기 위해 도입됐다.
  • 이부분 또한 추후 개시물로 자세히 다룰 예정이다.

디바이스 드라이버

  • 드라이버는 커널에서 실행되는 코드이다.
  • 그 역할은 키보드, 마우스, 하드 디스크 드라이브 같은 실제 하드웨어 디바이스나 /dev/pts/ 아래의 의사 터미널 같은 의사 디바이스(pseudo-device)를 관리하는 것이다.
    • 의사 디바이스는 물리 디바이스가 아니지만 물리 디바이스처럼 취급할 수 있다.
  • 또다른 하드웨어 클래스는 그래픽 처리 장치(GPU)로, 전통적으로는 그래픽 출력을 가속화하고 CPU의 부하를 완화할 때 사용됐다.
    • 하지만 최근 몇 년 동안 GPU는 머신러닝에 새롭게 사용되고 있으므로 더 이상 데스크톱 환경에만 국한되지 않는다.
  • 드라이버는 커널에 정적으로 빌드될 수도 있고, 필요할 때 동적으로 로드될 수 있도록 커널 모듈로 빌드될 수도 있다.

시스템 콜

  • 터미널에 touch test.txt를 입력할 경우 상위 수준의 명령을 일련의 구체적인 아키텍처 종송 단계로 전환하도록 리눅스에 요청하게 된다.
  • 즉, 커널이 노출하는 서비스 인터페이스와 해당 사용자 영역의 엔티티 호출은 시스템 호출의 모음이라 하며, 시스템 콜이라 부른다.
  • 어플리케이션에서는 일반적으로 이러한 시스템 콜을 직접 호출하지 않고 C 표준 라이브러리라고 부르는 것을 통해 호출된다.
  • 표준 라이브러리는 래퍼 기능을 제공하며 glibc, musl같은 다양한 구현체에서 사용할 수 있다.
  • 래퍼 라이브러리는 시스템 콜 실행의 반복적인 저수준 처리를 다른다.
  • 시스템 콜은 소프트웨어 인터럽트로 구현되기에 예외 처리기로 제어권을 넘기는 예외를 발생시킨다.
  • open_file("example.txt") 코드를 실행했을 때 내부 동작
    1. syscall.h와 아키텍처 종속 파일(커널은 시스템 콜 테이블 파일 사용)에 정의되어 메모리에 있는 함수 포인터 배열(sys_call_table이라는 변수에 저장됨)을 통해 시스템 콜과 해당 핸들러를 추적함.
    2. 시스템 콜 멀티플렉서처럼 동작하는 system_call() 함수를 사용하면 먼저 하드웨어 컨텍스트를 스택에 저장한 다음 검사를 수행하고, 그 이후에 sys_call_table의 각 시스템 콜 번호의 인덱스가 가리키는 함수로 점프한다.
    3. sysexit로 시스템 콜이 완료되면 래퍼 라이브러리는 하드웨어 컨텍스트를 복원하고, 프로그램 실행은 다시 사용자 영역에서 다시 시작된다.
  • 위와 같은 단계에서 주목할 부분은 시간이 많이 소요되는 작업인 커널 모드와 사용자 영역 모드 간의 전환이다.
  • 실제 내부에서 동작하는 것을 확인하기 위해서는 strace를 사용하면 된다.
  • 커널 구성 요소와 시스템 전체에 걸쳐 널리 사용되는 시스템 콜 목록은 여기를 확인하면 된다.

커널 확장

모듈

  • 모듈이란 요청 시 커널에 로드할 수 있는 프로그램이다.
    • 즉 커널을 다시 컴파일하거나 시스템을 재부팅할 필요가 없다.
  • 최근 리눅스는 대부분의 하드웨어를 자동으로 감지해 해당 모듈을 자동으로 로드한다.
  • 하지만 모듈을 수동으로 로드하고 싶은 경우도 있다.
    • 예를 들어 커널이 비디오 카드를 감지해 일반 모듈을 로드했지만, 해당 비디오 카드 제조업체가 그 대신 사용할 수 있는 더 나은 서드파티 모둘을 제공하는 경우이다.
  • 커널이 실제로 로드한 모듈을 보는 명령어 : lsmod

커널을 확장하는 현대적인 방법: eBPF

  • 커널 기능을 확장하는 방법으로 점점 더 인기를 끌고 있는 기술이다.
  • eBPF는 리눅스 커널의 기능이다.(리눅스 커널 버전 3.15 이상)
  • eBPF는 bpf 시스템 콜을 사용해 리눅스 커널 기능을 안전하고 효율적으로 확장한다.
    • eBPF는 맞춤형 64bit RISC 명령어 세트를 사용한 커널 내 가상 머신으로 구현됐다. eBPF
  • 실제 사용 예시
    • 쿠버네티스에서 포드 네트워킹(pod networking)을 활성화하기 위한 CNI 플러그인
    • 관측가능성용
    • 보안 제어 역할
    • 네트워크 로드밸런싱용
  • 최신 정보

정리

  • 리눅스 커널은 리눅스 운영체제의 근간이므로 어떤 리눅스 배포판을 사용하든, 환경이 데스크톱이든 클라우드이든 관계 없이 구성 요소와 기능에 대한 기본개념을 잘 이해해야한다.
  • 2장에서는 리눅스의 전체 아키텍처와 커널의 역할, 인터페이스를 검토했다.
  • 가장 중요한 것은 커널이 하드웨어의 차이를 추상화하기 때문에 리눅스의 이식성을 매우 높인다는 것이다.
  • 가장 중요한 인터페이스는 시스템 콜 인터페이스로, 이를 통해 파일 열기, 메모리 할당, 네트워크 인터페이스 목록 출력 등의 커널 기능을 사용자가 쓸 수 있도록 노출할 수 있다.
  • 커널 기능을 확장하거나 커널에서 성능이 중요한 작업을 구현하려는 경우 eBPF를 자세히 살펴 보는 것이 좋다.
This post is licensed under CC BY 4.0 by the author.

모던 리눅스 교과서 1. 리눅스 소개

모던 리눅스 교과서 3. 셸과 스크립팅