갑자기 부팅 과정…?

  • 이 전까지는 기본적인 스토리지 세팅 과정이었음
  • 근데 갑자기 리눅스가 부팅이 안되면 어떡함
  • 그래서 다시 한 번 뒤로 돌아가서, 부팅 과정부터 보기로 함

전체적인 부팅 과정

  1. 전원 버튼 누름
  2. 펌웨어 켜짐
  3. 부트로더 불러오기
  4. 커널 + initramfs 파일 읽기
  5. systemd 켜기
  6. 사용 가능
  • 그래서, 이번에는 펌웨어/부트로더/커널/systemd 에 대해 하나씩 파헤쳐 볼 예정임

1. 펌웨어

개요

  • 우리는 흔히 기계를 하드웨어와 소프트웨어로 나누어서 생각함
  • 하드웨어 : 실제 물체. 기계 장비. CPU, RAM, GPU 등등
  • 소프트웨어 : 하드웨어를 기동한 뒤에 사용할 수 있는 “수정 가능한” 것들. 프로그램이라 부르는 것들
  • 그럼 펌웨어는 무엇인가? 하드웨어와 소프트웨어를 잇는 다리 역할을 하는 “특수한” 소프트웨어
    • 참고 : firm 이 단단하다라는 뜻임. 변경이 어렵기 때문에 소프트웨어보단 단단해서 붙은 듯

소프트웨어와의 좀 더 큰 차이점

  • 소프트웨어는 기본적으로 “RAM”에서 CPU 자원을 받아다가 작동함
  • 그런데, 펌웨어는 나중에 변경되거나 지워지면 안되기 때문에 제조 단계에서 ROM에 작성됨
  • 그렇기 때문에 펌웨어는 “ROM”에서 작동함
  • 그리고 당연하지만 OS랑도 다름. OS는 펌웨어 위에 얹어져서 쓰임.

작동 순서

  1. 하드웨어 초기화 : 처음에 전원이 들어오면 펌웨어가 물리적으로 연결된 CPU, GPU, RAM 등등을 인식하고 정상적으로 작동할 수 있도록 설정함
  2. 운영체제 준비 : 운영체제가 들어있는 장치를 우선적으로 판단하고, 운영체제를 메모리로 올림

종류

  • BIOS 와 UEFI가 있음.
  • 두 개는 차이점이 꽤 있지만, 그 중 가장 중요한 차이점은 OS 부팅을 위한 방법이 다르다는 점
BIOSUEFI
부팅 방법디스크의 0번 섹터 (MBR)을 읽음특정 파일을 읽음
디스크 레이블 타입MBRGPT
디스크 제한2TB사실상 없음
안전 모드없음있음

디스크 레이블 타입

  • 디스크의 파티션 구조와 정보를 정의하는 방식
  • 대표적으로 MBR과 GPT가 있으나, 간혹 특정 시스템의 경우 자체적인 타입이 있는 경우가 있으므로 확인 필요함

MBR

  • Master Boot Record
  • 디스크의 첫 번째 섹터 (512 바이트)에 파티션 정보와 부트 로더를 기록함
    • 그래서 앞에 섹터 고장나면 못씀
  • BIOS 기반 시스템에서 쓰던 예전 방식
  • 최대 4개의 Primary Partition을 둘 수 있음
  • 디스크 최대 크기는 2TB

GPT

  • GUID Partition Table
  • DOS라고도 부름
  • UEFI 표준에 맞춰 설계됨
  • 각각의 파티션에 아이디(GUID)를 부여해 구분함
  • 파티션 테이블을 앞이랑 뒤에 이중으로 저장함
    • 게다가 CRC32 체크섬이 포함되어서 무결성 검사도 함
  • 파티션 수 제한은 128개
  • 디스크 최대 크기는 9.2ZB

UEFI 의 독특한 방식

  • UEFI 의 경우 부팅하기 위한 “특정 파일을 읽기” 때문에, 그 파일을 저장하고 불러오기 위한 파티션이 필요함

  • 그걸 ESP (EFI System Partition) 이라고 함

  • ESP는 파일 시스템으로 FAT32 방식을 사용하며, 100~600 MB 정도의 용량을 가짐

  • 어디에 있는지 볼 수 있음.

  • (CentOS 기준임)

  • 여기 보면 첫 번째 디스크 (sda) 가 파티션 세 개로 나뉜 것을 볼 수 있음
  • 세 개의 파티션 중에 sda1을 보면, 마운트 포인트가 /boot/efi 임
  • 이게 ESP임.

  • 여기 보면 .efi 로 끝나는 파일들이 있음. 이 파일들을 사용해서 시스템을 부팅하는 것.

안전 모드?

  • UEFI에는 안전 모드가 있음.
  • 서명된 안전한 부트로더만 가져와서 실행시키는 방법임.
  • 그럼 그 서명된 애들을 누가 알아볼까?
  • OS마다 다른데, RedHat 계열의 경우 shim 을 씀
    • 왜 쓰는가? 좀 있다가 나올 부트로더인 grub 은 마이크로소프트에서 인증을 안해줬음
    • 그래서 대신에 인증해준 shim을 한번 거쳐서 부팅함
  • 따라서 진짜 안전한 애들만 부팅이 됨

2. 부트로더 불러오기

개요

  • 펌웨어가 하드웨어와 소프트웨어 사이를 잇는 다리라면,
  • 부트로더는 펌웨어와 커널 사이를 잇는 다리 역할을 함.
    • 커널이 뭔지는 조금 뒤에 설명함

작동 순서

  1. 커널 선택
  2. initramfs 선택
  3. 커널 파라미터 전달
  4. 커널 실행

종류

  • 부트로더는 종류가 다양한데, 현재 리눅스 계열에서 가장 널리 쓰이는 부트로더는 grub 이다.

    • 여기서 실습하는 CentOS 또한 grub 을 사용한다. 그래서 grub에 대한 설명을 포함할 것이다.
  • U-Boot 라고 임베디드 쪽에서 많이 쓰는 부트로더가 있다고 한다.

GRUB

  • Grand Unified Bootloader.
  • 위에서 말했듯이, 현재 리눅스 계열에서 가장 많이 쓰이는 부트로더다.

구조

  • 자동생성이 되는 파일이 있고, 수정 가능한 파일이 있음.

  • 수정 가능한 파일은 다시 두 가지로 나뉨

    • 전역 설정 (/etc/default/grub)
    • 스크립트 조각들 (/etc/grub.d/ 하위의 파일들)
  • 전역 설정 파일 예시

  • 스크립트 조각들 예시

  • 수정 가능한 파일들은 grub2-mkconfig 명령을 통해 합쳐져서 자동생성이 되는 파일로 만들어진다.

  • 참고 : 스크립트 조각들의 경우, 오름차순으로 명령을 합친다. (00 41)

  • 자동 생성된 파일은 펌웨어의 종류에 따라 위치가 달라진다.

    • BIOS : /boot/grub2/grub.cfg
    • UEFI : /boot/efi/EFI/<OS_또는_Vendor>/grub.cfg

GRUB는 커널에 과연 무엇을 넘겼는가?

  • 아래의 명령어를 실행하면 확인할 수 있음
cat /proc/cmdline
  • 출력 예시

  • 부팅해야되는 이미지는 여기있고… 루트는 어디고… 어쩌구저쩌구.

3. 커널 + initramfs 불러오기

커널?

  • 운영체제 중에서 메모리에 “항시” 올라가 있는 핵심 부분을 말함
  • CPU, GPU, RAM 같은 자원과 응용 프로그램을 잇는 역할을 함
  • 메모리, 프로세스 관리 + 서비스 요청 수신의 역할도 함

initramfs?

  • Initial RAM File System.
  • 커널이 올라오고 나서 실제 파일 시스템을 로드하기 전까지 잠깐동안 초기 환경 세팅을 제공하는 임시 메모리 기반 파일 시스템임
  • 예전에 있던 initrd (Initial RAM Disk) 를 대체함
  • 초소형 리눅스임. 안에 init 이라는 최초 실행 파일이 있음
    • 그래서 initramfs 안에서는 init 이 PID 1번임.
  • 초기 드라이버를 로드함
  • 실제 파일 시스템을 찾고 마운트도 함 (이게 약간 주 목적임)
  • 이름에서 보면 알겠지만 디스크가 아니고 RAM에 들어오는거라 되게 빠름

커널이 있는데 initramfs가 왜 필요함?

  • 중요한게 있는데, 커널은 켜졌을 때 디스크의 존재 자체를 모름
  • 파일 시스템 드라이버도 모름
  • 그래서 initramfs가 커널 이후에 올라와서 드라이버들을 로드해주는거임
  • 결과적으로 보면 커널은 루트 디렉토리가 마운팅되기 전까지 initramfs한테 의존하고 있음

initramfs는 어떻게 수정하나요

  • 이전 글들에서 자주 봤었던 dracut 이라는 프로그램이 그 역할을 함.
  • dracut은 기본적으로 아래의 파일들을 참고함
분류파일
루트 파일 시스템/etc/fstab, /proc/cmdline
RAID/etc/mdadm.conf
LVM/etc/lvm/lvm.conf
암호화/etc/crypttab
dracut 설정/etc/dracut.conf, /etc/dracut.conf.d/*
드라이버/sys, /lib/modules/
udev/etc/udev/rules.d/*
  • 참고 : 커널 업데이트 하면 dracut으로 그 커널 버전에 맞는 initramfs 새로 만들어야 함.
# $(uname -r) => 커널 버전
dracut -H -f /boot/initramfs-$(uname -r).img $(uname -r)

4. systemd

개요

  • initramfs를 통해 루트 시스템을 마운팅하고 난 뒤에 최초로 실행되는 프로그램이 systemd(/sbin/init) 임

하는 일

  • 고아 프로세스 처리 : 부모가 사라진 프로세스를 종료 처리

  • SIGCHLD(자식 프로세스의 종료 시) 처리 : 좀비 프로세스 처리

  • 시스템 종료

  • 서비스 생명주기 관리

  • 참고 : systemd가 비정상 종료되면 커널 패닉 or 시스템 정지됨

  • 참고 2 : 예전에 쓰던 SysVInit 은 순차적 스크립트 실행이었는데, 그걸 보완하기 위해 나옴

    • 의존성 기반 병렬 실행이 가능하고, 상태 추적도 가능함

타겟

  • 원하는 특정 상태에 도달하기 위해 실행해야 하는 systemd 유닛 (서비스) 집합
  • .target 이라는 확장자로 존재함
  • 위치 : /lib/systemd/system/ 그리고 /etc/systemd/system/

  • 되게 많음
  • 이 중에서 자주 쓰이는 타겟은 다음 표에 있음
  • 표 출처
Target의미
sysinit.target파일시스템 마운트, swap 설정 등 초기화
basic.target기본 서비스 준비 (dbus, 로그 등)
network.target네트워크 장치만 감지됨 (연결 여부는 미확정)
network-online.target네트워크가 완전히 연결됨 (DNS, 외부 통신 가능)
multi-user.target텍스트 기반 로그인 환경 완성 (SSH, DB 등 대부분의 서버 서비스 실행)
graphical.targetGUI 환경까지 실행 (X11, Wayland 포함)
default.target시스템이 부팅 시 기본적으로 도달하는 상태 (보통 multi-user 또는 graphical)
  • 또한 실제 서비스 예시도 있음. (출처 위 표와 같음)
Target실제 사용하는 서비스 예시
network.targetDHCP 클라이언트, UFW 방화벽
network-online.targetNFS 클라이언트, Airflow, Kafka consumer
multi-user.targetNginx, PostgreSQL, Docker, sshd
graphical.targetgdm, sddm, GNOME, KDE
sockets.targetdocker.socket, cups.socket
timers.targetapt-daily.timer, logrotate.timer

서비스를 타겟에 등록하는 방법

0. 예시 시나리오

  • 현재 내가 만든 프로그램이 /usr/local/bin/my_server 안에 있음
  • 이 프로그램은 서버 프로그램이라서, multi-user.target에 등록시키는게 적합하다고 판단했다는 상황

1. 서비스부터 만들기

  • 프로그램을 서비스로 만들려면 /etc/systemd/system/ 폴더 안에 서비스 유닛(파일)을 만들어야 함
  • 예시
# /etc/systemd/system/my_server.service
[Unit] # 조건, 순서
Description=My First Server
After=network.target # 네트워크 타겟이 충족된 이후에 실행 (느슨함)
 
[Service] # 실행 방법
ExecStart=/usr/local/bin/my_server # 실행 파일 위치
Restart=always # 종료되었을 때 재시작 여부
 
[Install] # 어느 Target에 묶일지 
WantedBy=multi-user.target # 이 타겟 상태가 된 후에 실행함. 실패해도 이후를 진행함
  • 참고 : 만약에, “무조건 A 서비스가 실행된 후에 B 서비스가 실행되어야만 해!” 라고 한다면 아래의 설정을 함
# /etc/systemd/system/B.service
[Unit]
Requires=A.service
  • 이 경우, A가 실행되지 않았으면 아예 target도 실패처리되기 때문에 부팅에 문제가 생길 수 있음 주의

2. systemd에게 추가된 서비스를 인식시키기

systemctl daemon-reload # 변경 된 unit 파일들을 다시 읽어서 systemd에 반영함
systemctl enable <서비스유> # 자동 부팅 활성화 + 지정 타겟.target.wants 디렉토리에 심볼릭 링크 생성
  • 예시
systemctl daemon-reload
systemctl enable my_server.service
  • 참고 : 만약 재시작 하기 전에 지금 당장 키고싶으면 아래의 명령어 실행
systemctl start <서비스유>
  • 예시
systemctl start my_server.service

특정 타겟 서비스들 확인하기

systemctl list-dependencies <타겟파일>
  • 예시
systemctl list-dependencies multi-user.target

유용한 타겟 관련 명령어들

  • 부팅을 할 때 최종적으로 도달하려는 타겟(default.target)을 확인하는 명령어
systemctl get-default

  • default.target을 다른걸로 바꾸는 명령어
    • 참고 : default.target은 특정 파일이 아니고 링크기 때문에, 다른 타겟 파일로 링크를 옮기는 것 뿐임
systemctl set-default <변경할_타겟파일>
  • 예시
systemctl set-default rescue.target
  • 부팅 이후에 어떤 유닛이 켜지지 못했는지 확인하는 명령어
systemctl list-units --failed

  • 참고 : 일회성으로만 (다음 부팅만) 타겟을 바꾸고 싶으면 GRUB에서 바꾸는게 맞음

다음에 할 것

  • 일부러 부팅이 안되게끔 하고 고치는 실습 해보기