리눅스 커널
- 운영체제의 주요 기능이 다양한 하드웨어를 추상화하고 API를 제공하는 것임을 배웠다.
- 이 API를 이용한 프로그래밍을 통해 앱들이 어디에서 어떻게 실행되는지 걱정할 필요 없이 애플리케이션을 작성할 수 있다.
- 커널은 이러한 API를 프로그램에 제공한다.
- 2장에서는 리눅스 커널이 무엇인지, 그리고 그 구성 요소뿐 아니라 전체로서 이를 어떻게 생각해야 하는지 등을 살펴본다.
- 이 과정에서 필수 용어를 이해하고 프로그램과 커널 간의 인터페이스를 파악하며 그 기능이 무엇인지에 대한 기본 개념을 갖추는 것이 목표이다.
리눅스 아키텍쳐
- 하드웨어 계층
- CPU와 메인 메모리부터 디스크 드라이브, 네트워크 인터페이스는 물론 기보드나 모니터 같은 주변 디바이스를 일컫는다.
- 커널 계층
- 이번 게시물에서 중점적으로 다룰 내용이다. 커널과 사용자 영역 사이에는 init 시스템과 시스템 서비스(네트워킹 등)처럼 많은 구성요소가 있지만, 이들은 엄밀히 말해 커널의 일부가 아니다.
- 사용자 영역(user land) 계층
- 셸(shell)같은 운영체제 구성요소, ps나 ssh 같은 유틸리티, x윈도우 시스템 기반 데스크톱 같은 그래픽 사용자 인터페이스(GUI)를 비롯해 대부분의 앱이 실행되는 곳을 일컫는다.
- 다른 계층 간의 인터페이스는 리눅스 운영체제 패키지의 일부이며 잘 정의돼 있다. 커널과 사용자 영역 사이에는 시스템 콜(system call, 줄여서 syscall)이라는 인터페이스가 있다.
- 하드웨어와 커널 사이의 인터페이스는 시스템 콜과 달리 단일 인터페이스가 아닌 하드웨어별로 그룹화된 개발 인터페이스 모음으로 구성된다.
- CPU 인터페이스
- 주 메모리 인터페이스
- 네트워크 인터페이스와 드라이버
- 파일 시스템과 블록 디바이스 드라이버 인터페이스
- 캐릭터 디바이스, 하드웨어 인터럽트, 키보드, 터미널, 기타 I/O 등의 입력 디바이스를 위한 디바이스 드라이버
- 셸이나 grep, find, ping 같은 유틸리티처럼 일반적으로 리눅스 운영체제의 일부로 여기는 많은 것이 실제로는 커널의 일부가 아니라 사용자 영역의 일부이다.
사용자 모드와 커널 모드
- 실질적으로 하드웨어로의 접근 권한이 얼마나 있고 사용 가능한 추상화가 얼마나 제한됐는지를 나타낸다.
- 일반적으로 커널모드 는 추상화를 제한함으로써 빠르게 실행함을 의미하는 반면, 사용자 모드 는 상대적으로 느리지만 더 안전하고 편리한 추상화를 의미한다.
CPU 아키텍처
- 리눅스가 인기 있는 이유 중 하나는 리눅스가 다양한 CPU 아키텍처에서 실행된다는 점 때문이다.
- 제너릭 코드와 드라이버는 물론이고, 리눅스 커널은 아키텍처별 코드도 포함하고 있다.
- 이러한 코드 분리 덕분에 리눅스를 새 하드웨어에 이식하고 빠르게 사용하는 것이 가능하다.
- 현재 리눅스가 실행 중인 CPU를 파악하는 방법
- BIOS와 방호 작용하는
dmidecode
라는 전용 도구 사용하기(혹은lscpu
) cat /proc/cpuinfo
를 통해 알아보는 것- 간단하게 아키텍처에만 관심이 있는 경우
uname -m
- BIOS와 방호 작용하는
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)같은 취약점에 영향을 받지 않는다.
- 자세한 내용은 ARM 관련 커널 설명서를 참고하면된다.
RISC-V 아키텍처
- 자세한 내용은 ARM 관련 커널 설명서를 참고하면된다.
- 원래 UC버클리에서 개발한 개방형 RISC 표준이다.
- 현대에는 많은 기업들이 이를 이용한 다양한 구현이 존재한다.
- 아직 널리 사용되지는 않지만 비교적 새로운 아키텍쳐이다.
커널 구성요소
- 리눅스 커널은 모놀리식 커널이지만, 코드베이스에는 특정 역할을 식별하고 부여할 수 있는 기능 영역이 나뉘어 있다.
- 커널은 위에 설명했던 것처럼 하드웨어와 실행하려는 앱 사이에 있다.
- 커널 코드베이스에서 찾을 수 있는 주요 기능 블록은 아래와 같다.
- 프로세스 관리 : 실행 파일을 기반으로 프로세스를 시작함
- 메모리 관리 : 프로세스에 메모리를 할당하거나 파일을 메모리에 매핑함
- 네트워킹 : 네트워크 인터페이스 관리나 네트워크 스택을 제공함
- 파일 시스템 : 파일 관리를 제공하고, 파일 생성과 삭제를 지원한다.
- 이외에 캐릭터 디바이스와 디바이스 드라이버 관리를 함.
- 이러한 기능 구성요소는 상호 의존성이 있을 때가 많기 때문에 커널의 개발자 모토인 ‘커널은 사용자 영역을 손상시키지 않는다’를 실제로 확실히 지키면서 작업하기 어렵다.
프로세스 관리
- 커널에는 프로세스 관리와 관련된 부분이 여럿 있다.
- 그중 일부는 인터럽트 같은 CPU 아키텍처 관련 사항을 처리하고, 다른 부분은 프로그램 실행과 스케줄링에 중점을 둔다.
- 리눅스에서는 가장 큰 단위부터 가장 작은 단위까지 다음과 같이 나뉜다.
- 세션
- 하나 이상의 프로세스 그룹을 포함하고 선택적으로
tty
가 연결된 상위 수준의 사용자 대면 유닛을 나타낸다. - 커널은 세션ID(SID)라는 번호를 통해 세션을 식별한다.
- 하나 이상의 프로세스 그룹을 포함하고 선택적으로
- 프로세스 그룹
- 하나 이상의 프로세스가 포함돼 있으며 한 세션에는 foreground 프로세스 그룹이 둘 이상일 수 없다.
- 커널은 프로세스 그룹 ID(PGID)라는 숫자를 통해 프로세스 그룹을 식별한다.
- 프로세스
- 여러 리소스(주소 공간, 하나 이상의 스레드, 소켓 등)를 그룹으로 추상화한 것이며, 커널은 /proc/self를 통해 현재 프로세스를 사용자에게 노출한다.
- 커널은 프로세스 ID(PID)라는 숫자를 통해 프로세스를 식별한다.
- 스레드
- 커널에 의해 프로세스로 구현된 유닛을 말한다.
- 즉 스레드를 나타내는 전용 데이터 구조는 없다.
- 스레드는 특정 리소스(예: 메모리나 signal handler)를 다른 프로세스와 공유하는 프로세스이다.
- 커널은 스레드ID(TID)와 스레드 그룹ID(TGID)를 통해 스레드를 식별하며, 공유된 TGID값은 멀티스레드 프로세스를 의미한다.
- 태스크
- 세션
- 가상 메모리는 시스템이 물리적으로 갖고 있는 것보다 더 많은 메모리를 갖고 있는 것처럼 보이게 한다.
- 모든 프로세스는 많은 가상 메모리를 얻는다.
- 이 부분은 운영체제 게시물에서 자세히 언급했으니 자세히 다루지 않는다.
네트워킹
- 커널의 중요한 기능 중 하나는 네트워킹이다.
- 웹을 검색하거나 원격 시스템에 데이터를 복사하려면 네트워크에 의존해야 한다.
- 리눅스의 네트워크 스택은 계층화된 아키텍처를 따른다.
- 소켓 : 추상화 커뮤니케이션을 위해 필요함
- 전송 제어 프로토콜(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")
코드를 실행했을 때 내부 동작- syscall.h와 아키텍처 종속 파일(커널은 시스템 콜 테이블 파일 사용)에 정의되어 메모리에 있는 함수 포인터 배열(
sys_call_table
이라는 변수에 저장됨)을 통해 시스템 콜과 해당 핸들러를 추적함. - 시스템 콜 멀티플렉서처럼 동작하는
system_call()
함수를 사용하면 먼저 하드웨어 컨텍스트를 스택에 저장한 다음 검사를 수행하고, 그 이후에sys_call_table
의 각 시스템 콜 번호의 인덱스가 가리키는 함수로 점프한다. sysexit
로 시스템 콜이 완료되면 래퍼 라이브러리는 하드웨어 컨텍스트를 복원하고, 프로그램 실행은 다시 사용자 영역에서 다시 시작된다.
- syscall.h와 아키텍처 종속 파일(커널은 시스템 콜 테이블 파일 사용)에 정의되어 메모리에 있는 함수 포인터 배열(
- 위와 같은 단계에서 주목할 부분은 시간이 많이 소요되는 작업인 커널 모드와 사용자 영역 모드 간의 전환이다.
- 실제 내부에서 동작하는 것을 확인하기 위해서는
strace
를 사용하면 된다. - 커널 구성 요소와 시스템 전체에 걸쳐 널리 사용되는 시스템 콜 목록은 여기를 확인하면 된다.
커널 확장
모듈
- 모듈이란 요청 시 커널에 로드할 수 있는 프로그램이다.
- 즉 커널을 다시 컴파일하거나 시스템을 재부팅할 필요가 없다.
- 최근 리눅스는 대부분의 하드웨어를 자동으로 감지해 해당 모듈을 자동으로 로드한다.
- 하지만 모듈을 수동으로 로드하고 싶은 경우도 있다.
- 예를 들어 커널이 비디오 카드를 감지해 일반 모듈을 로드했지만, 해당 비디오 카드 제조업체가 그 대신 사용할 수 있는 더 나은 서드파티 모둘을 제공하는 경우이다.
- 커널이 실제로 로드한 모듈을 보는 명령어 :
lsmod
커널을 확장하는 현대적인 방법: eBPF
- 커널 기능을 확장하는 방법으로 점점 더 인기를 끌고 있는 기술이다.
- eBPF는 리눅스 커널의 기능이다.(리눅스 커널 버전 3.15 이상)
- eBPF는
bpf
시스템 콜을 사용해 리눅스 커널 기능을 안전하고 효율적으로 확장한다. - 실제 사용 예시
- 쿠버네티스에서 포드 네트워킹(pod networking)을 활성화하기 위한 CNI 플러그인
- 관측가능성용
- 보안 제어 역할
- 네트워크 로드밸런싱용
- 최신 정보
정리
- 리눅스 커널은 리눅스 운영체제의 근간이므로 어떤 리눅스 배포판을 사용하든, 환경이 데스크톱이든 클라우드이든 관계 없이 구성 요소와 기능에 대한 기본개념을 잘 이해해야한다.
- 2장에서는 리눅스의 전체 아키텍처와 커널의 역할, 인터페이스를 검토했다.
- 가장 중요한 것은 커널이 하드웨어의 차이를 추상화하기 때문에 리눅스의 이식성을 매우 높인다는 것이다.
- 가장 중요한 인터페이스는 시스템 콜 인터페이스로, 이를 통해 파일 열기, 메모리 할당, 네트워크 인터페이스 목록 출력 등의 커널 기능을 사용자가 쓸 수 있도록 노출할 수 있다.
- 커널 기능을 확장하거나 커널에서 성능이 중요한 작업을 구현하려는 경우 eBPF를 자세히 살펴 보는 것이 좋다.