어떤 것을 이해하려면, 처음을 봐야 한다. 리눅스라는 운영체제 또한 마찬가지다. 리눅스를 이해하기 위해서는 부팅과정을 알아야 한다.
실제 커널 코드를 분석하면서, detail 하게 이해하는 방법도 있지만, 여기서는 스크립트의 시각에서 리눅스 부팅 과정을 기술한다. 참고로 여기에 나온 내용은 [데브옵스] 라는 책에서 발췌한 것이다.
처음 컴퓨터를 켜서, 로그인 프롬프트가 뜰때까지의 과정을 다룬다.

바이오스

부팅 과정과 관련된 첫 번째 시스템은 바이오스(BIOS)다. 바이오스는 시스템을 부팅할 때 볼 수 있는 첫 화면이고, 시스템마다 화면은 다르지만 시스템을 부팅할 수 있는 하드디스크, USB 메모리, CD-ROM 그리고 기타 하드웨어를 감지 하는 것을 포함해 하드웨어를 초기화한다.
그리고나서 시스템이 성공적으로 부팅할 수 있는 하드웨어를 찾을 때까지 설정된 부팅 기기 순서대로 각 부팅 기기를 하나씩 점검한다.
리눅스 서버의 경우 대개 MBR(master boot record 의 약자로서, 하드디스크의 첫 512 바이트에 해당하는 영역이다)을 읽어 메모리에 적재하고, 부팅 과정을 시작하기 위해 MBR 안에 있는 부트 코드를 실행하는 것을 말한다.

GRUB 과 리눅스 부트로더

바이오스가 하드웨어를 초기화하고 부팅할 첫 기기를 찾고 나면 부트로더가 그 뒷 일을 맡는다. 과거에서는 LILO 라는 프로그램이 사용되었으나, 일반 리눅스 서버에서는 GRUB 라는 프로그램이 사용된다. GRUB 은 하드디스크로 부팅할 때 보통 사용되는 부트로더이고, USB 나 CD-ROM 또는 네트워크로 부팅하는 시스템은 GRUB 대신 각각 syslinux, isolinux, pxelinux 를 부트 로더로 사용할 수도 있다.
syslinux 와 기타 다른 부트로더의 특징은 GRUB 과 다르지만 모두 근본적으로 일부 소프트웨어를 적재하고, 어떤 운영체제를 부팅할 수 있고, 어디에서 각 커널을 찾을 수 있는지, 시스템 부팅 시 어떤 설정을 시스템에 적용해야 하는 가에 대해 부트로더에게 알려줄 설정 파일을 읽는다.

GRUB 이 적재될 때 코드의 일부 작은 부분(stage 1 이라고 하는)이 MBR 에서 실행된다. 부트코드의 446 바이트를 MBR(나머지 부분에 파티션 테이블이 포함된다)에 맞출 수 있기 때문에 GRUB 의 stage 1 코드는 GRUB 이 해당 코드의 나머지 부분을 디스크에 위치시키고 실행하기에 충분하다.
GRUB 코드의 다음 단계에서는 GRUB 이 리눅스 파일시스템에 접근하는 것을 허용하고, 그것으로 어떤 운영체제를 기동할 수 있고, 운영체제가 디스크의 어디에 있는지, 어떤 옵션을 운영체제에 전달해야 하는지를 알려줄 설정파일을 읽고 적재한다.
리눅스의 경우 여기에는 디스크에 있는 다른 여러 커널 버전을 포함할 수 있고 종종 문제 해결에 유용한 특별한 복구모드가 포함된다. 대개 설정 파일에도 모든 부팅 옵션을 보고 편집할 수 있는 일종의 메뉴가 기술되어 있다.

커널과 Initrd

GRUB 에서 특정 커널을 선택했을 때(또는 카운트 다운이 끝나서 하나가 선택됐을 때) GRUB 은 선택된 리눅스 커널을 RAM 에 적재해서 실행하고, 부팅 시 적용될 설정값을 전달한다. 보통은 GRUB 이 커널과 함께 initrd(initial RAM disk, 초기 램 디스크)까지 함께 적재한다.
현대 리눅스 시스템에서 이 파일은 initramfs 파일로 알려져 있는 gzip 으로 압축된 cpio 아카이브 파일로서, 기초적이고 자그마한 리눅스 루트 파일 시스템을 담고 있다. 이 파일 시스템에는 몇 가지 중요한 설정 파일과 커널 모듈, 그리고 커널이 실제 루트 파일 시스템을 찾아 마운트하는 데 필요한 프로그램들이 들어있다.

과거에는 부팅할 때 사용되는 이런 모든 기능들이 리눅스 커널에 직접 포함되어 있었다. 하지만 하드웨어 지원이 갖가지 다양한 파일 시스템과 소프트웨어 RAID, LVM 및 파일시스템 암호화와 같은 추가적인 기능을 제공하는 SCSI 및 IDE 기기를 포함하도록 발전하면서 커널이 너무 비대해졌다.
그래서 이러한 기능들은 시스템에 필요한 모듈만 적재할 수 있게 개별 모듈로 분리되었다. 디스크 드라이버와 파일 시스템 지원이 모듈로 분리되면서 '닭이 먼저냐, 계란이 먼저냐' 라는 문제에 직면했다. 모듈이 루트파일시스템에 있지만 루트파일시스템을 읽을 모듈이 필요하다면 어떻게 해당 파일 시스템을 마운트할 수 있겠는가? 해결책은 이러한 모든 중요 모듈을 initrd 에 넣어두는 것이었다.

커널이 부팅할 때 RAM 에 압축된 initramfs 파일을 풀고 initramfs 의 루트에 있는 init 라는 스크립트를 실행한다. 이 스크립트는 몇 가지 하드웨어를 감지하고 마운트 지점들을 생성하고 루트 파일 시스템을 마운트하는 일종의 표준 쉘 스트립트다. 커널은 GRUB 이 처음 커널을 적재할 때 GRUB 이 전달한 부팅 인자(root=) 중 하나로 루트 파일 시스템의 위치를 전달하기 때문에 루트 파일 시스템이 어디에 있는지 알고 있다.
initramfs 파일이 실제 루트 파일 시스템을 마운트한 이후 수행하는 마지막 과정은 부팅 과정을 처리할 /sbin/init 프로그램을 실행하는 것이다.

/sbin/init

/sbin/init 프로그램은 시스템에서 실행 중인 모든 프로그램의 부모 프로세스다. 이 프로세스는 항상 PID 가 1이며, 운영 중인 리눅스 시스템을 구성하는 나머지 프로세스가 시작되는 것을 책임진다.
리눅스를 잠깐이라도 사용해본 사람이라면, 우분투 서버의 init 과 여러분이 사용했던 init 이 다르다는 사실을 알 것이다. 유닉스 운영체제를 초기화하는 방법에는 몇 가지 다른 표준이 있지만 대부분 고전적인 리눅스 배포판에서는 System V init 모델이라고 알려진 것을 사용해왔다.
반면 일부 현대 리눅스 배포판에서는 Upstart 또는 최근에는 systemd 와 같은 다른 시스템으로 전환되었다. 예를 들면, 우분투 서버는 Upstart 로 전환되었지만, 이전 버전과의 호환성을 위해 런레벨(runlevel)이나 /etc/rc?.d 디렉토리와 같은 대부분의 System V init 의 외적인 구조는 유지해왔다. 하지만 현재 Upstart 가 시스템 내부에서 모든 것을 관리한다. 여러분이 서버를 접할 때 만나게 되는 가장 일반적인 두 개의 init 시스템이 바로 System V 와 Upstart 이므로 이에 대해 설명하겠다.

System V 는 AT&T 에서 개발된 첫 상업용 UNIX 운영체제의 특정한 버전을 말한다. 이 시스템에서 init 프로세스는 바로 다음에 언급할 기본 런레벨을 찾기 위해 /etc/inittabl 이라고 하는 설정파일을 읽는다. 그리고 그 기본 런레벨로 진입해 그 런레벨로 실행하도록 설정된 프로세스를 시작한다.

System V init 프로세스는 런레벨로 알려진 여러 다른 시스템 상태로 정의된다. /etc/inittab 과 더불어 시스템 상의 모든 주요 서비스를 위해 System V init 시스템의 각종 중요 파일과 디렉토리로 시작/종료/초기화 스크립트를 구성한다.

이 디렉토리에는 모든 런레벨에 있는 전체 서비스에 대한 시작 스크립트가 들어있다. 이러한 스크립트는 대부분 표준 쉘 스크립트이며, 기본 표준을 따른다. 각 스크립트는 적어도 어떤 서비스를 시작하고 멈추는 start 와 stop 인자를 전달받는다.
더불어 초기화 스크립트는 일반적으로 restart, status, reload(서비스에 설정 파일의 내용을 다시 적재하는), force-reload(서비스에 설정을 강제로 다시 적재하는) 같은 몇 가지 부가적인 옵션을 전달받는다. 인자 없이 초기화 스크립트를 실행하면 일반적으로 지정 가능한 인자 목록이 표시된다.

이 디렉토리에는 각 런레벨에 대한 초기화 스크립트가 들어있다. 실제로 이 스크립트들은 일반적으로 /etc/init.d 밑에 있는 원본 파일에 대한 심볼릭 링크에 해당한다. 하지만 여기서 주목할 부분은 이러한 디렉토리에 들어 있는 초기화 스크립트에는 S(시작), K(종료), 또는 D(비활성화)나 숫자로 시작하는 특별한 이름이 부여되어 있다는 점이다. init 이 어떤 런레벨로 들어갈 때 이름이 K 로 시작하고, 숫자로 정렬된 모든 스크립트를 stop 인자와 함께 실행하는데, 관련 초기화 스크립트가 그 이전 레벨에서 시작된 경우에만 해당한다.
그리고 나서, 이름이 S로 시작하고 숫자로 정렬된 모든 스크립트를 start 인자와 함께 시작한다. 이름이 D 로 시작하는 스크립트는 모두 무시되는데, 이렇게 함으로써 특정 런레벨에서 어떤 스크립트를 일시적으로 비활성화하거나 해당 심볼릭 링크를 완전히 제거할 수도 있다. 그러므로 S01foo 와 S05bar 라는 두개의 스크립트를 갖고 있다면 init 이 먼저 S01foo start 를 실행하고 난 후 그에 해당하는 특정 런레벨에 진입할 때 S05bar start 가 실행될 것이다.

이 디렉토리에는 어떤 특정한 런레벨로 변하기 전, init 이 시작하는 시점에 실행하는 모든 시스템 초기화 스크립트가 들어있다. 이 디렉토리에 들어 있는 스크립트를 어설프게 수정해서는 안된다. 왜냐하면 이 디렉토리에 들어 있는 스크립트가 지연된다면 단일 사용자 모드로 들어가는 것 조차 방해받을 수 있기 때문이다 .

모든 배포판이 rc.local 을 사용하지는 않지만 전통적으로 이것은 사용자가 수정할 수 있게 따로 마련해둔 스크립트 파일이다. 보통은 init 의 마지막에 실행되므로 여러분의 초기화 스크립트를 별도로 생성할 필요 없이 실행하고 싶은 추가적인 내용을 이 스크립트에 두면 된다.
다음은 표준 System V init 시스템에 대한 부팅 과정의 예다. 먼저 init 이 시작되면 기본 런레벨(이 예제에서는 런레벨 2)을 정하기 위해 /etc/inittab 을 읽는다. 그러고 나서 init 은 /etc/rcS.d 로 가서 이름이 S 로 시작하고 숫자로 정렬된 각 스크립트에 start 인자와 함께 실행한다. 그리고 /etc/rc2.d 디렉토리에 대해서도 위와 같은 절차를 수행한다.
최종적으로 init 은 마쳤지만 런레벨이 변하기를 기다리며 백그라운드에서 실행 상태를 유지한다. 이후 만일 재부팅(init 6)을 실행하면, 앞서 과정을 반복한다.

System V init 은 괜찮은 시스템이고, 수년간 리눅스에서 잘 동작해왔지만 어떤 결점도 없다는 것은 아니다. 한 가지 결점은 서비스가 죽었을 때 자동으로 해당 서비스를 재실행할 수단이 없다는 것이다. 예를 들면, cron 데몬 프로세스가 어떤 이유로 죽는다면 그 프로세스를 지켜보고 다시 시작하기 위해 어떤 다른 도구를 만들어야 한다.

초기화 스크립트의 다른 문제는 일반적으로 런레벨의 변경에 의해서만 영향을 받는다는 것이다. 다시 말해 시스템이 기동되었으나 실제로 기동되지 않았을 때 수동으로 다시 시작하지 않는다면 실행되지 않는다. 네트워크 연결에 의존적인 초기화 스크립트가 좋은 예다. 레드햇과 데비안 기반 시스템에서 각각 네트워크나 네트워킹으로 불리는 초기화 스크립트는 네트워크 연결을 만든다. 네트워크 연결에 의존적인 스크립트는 해당 네트워크 스크립트가 실행되고 난 뒤에 수행되는 것을 보장하기 위해 더 높은 이름을 할당받는다. 그런데 서버에서 네트워크 케이블이 뽑혀 있는 상태로 서버가 기동하면 어떻게 될까?
네크워킹 스크립트가 실행돼겠지만 네트워크 연결이 필요한 모든 초기화 스크립트는 하나씩 시간 만료가 될 것이다. 결국 로그인 프롬프트가 나타날 것이고, 시스템에 로그인할 수 있을 것이다. 로그인한 뒤에 네트워크 케이블을 다시 꼽고 네트워킹 서비스를 다시 시작한다면 네트워크 연결 상태에 있지만 네트워크 연결이 필요한 다른 서비스는 자동으로 다시 시작되지 않을 것이며 하나씩 수동으로 시작해야 한다.

Upstart 는 System V init 프로세스의 몇 가지 단점들을 해결할 뿐더러 서비스를 관리하기에 좀 더 견고한 시스템을 제공한다. Upstart 의 한가지 주요 특징은 이벤트 주도형(event-driven) 이라는 것이다. Upstart 는 어떤 이벤트가 발생하는지 끊임없이 감시하면서 이벤트가 발생햇을 때 해당 이벤트를 기반으로 특정 작업을 수행하도록 설정할 수 있다.
그와 같은 이벤트의 예로는 시스템 시작, 종료, Ctrl-Alt-Del 키 조합 발생, 런레벨 변경 또는 Upstart 스크립트 시작/종료가 있다. 이벤트 주도형 시스템이 전통적인 초기화 스크립트를 어떻게 개선할 수 있는지 확인하기 위해 앞서 언급했던 케이블이 빠진 채로 기동된 시스템을 살펴보자.
이 경우 네트워크 케이블이 꼽혔을 때 실행 되는 Upstart 스크립트를 만들 수 있고 이 스크립트에서는 네트워킹 서비스를 재시작할 수 있다. 그리고나서 네트워킹 서비스가 성공적으로 시작될 때마다 네트워크 연결이 발생하는 어떤 서비스를 구성할 수 있다.
이제 시스템이 부팅되면 네트워크 케이블을 꼽기만 하면 되고, 나머지 과정은 Upstart 스크립트가 처리할 것이다.

Upstart 는 적어도 서비스 측면에서는 아직 완벽하게 System V init 을 대체하지 못한다. 현재 Upstart 는 init 과 /etc/inittab 파일의 기능을 대체하고 런레벨 변경, 시스템 시작과 종료, tty 콘솔을 관리한다. 점점 더 많은 핵심기능이 Upstart 스크립트로 옮겨지고 있지만 여전히 /etc/init.d 와 /etc/rc?.d 에서 각각 일부 표준 초기화 스크립트와 모든 표준 심볼릭 링크를 볼 수 있을 것이다. 차이점은 런레벨이 변경될 때 Upstart 가 서비스를 시작하고 종료한다는 것이다.

Upstart 스크립트는 /etc/init 에 있고, 기존 초기화 스크립트와 다른 구문을 갖는다. 왜냐하면 Upstart 스크립트는 실제로 쉘 스크립트가 아니기 때문이다. 구문에 대한 설명을 돕기 위해 런레벨을 변경하는데 사용되는 Upstart 스크립트(/etc/init/rc.conf)의 예를 보여주겠다.

# rc - System V runlevel compatibility
#
# This task runs the old System V-style rc script when changing
# between runlevels.
 
description "System V runlevel compatibility"
author       "Scott James Remnant <scott@netsplit.com>"
start on runlevel [0123456]
stop on runlevel [!$RUNLEVEL]
export RUNLEVEL
export PREVLEVEL
task
exec /etc/init.d/rc $RUNLEVEL

Upstart 는 대부분의 다른 스크립트와 설정 파일처럼 #으로 시작하는 줄을 주석으로 처리한다. 첫 두 설정 옵션은 start on 과 stop on 이다. 이 줄에 있는 설정은 스크립트의 시작과 종료에 대해 어떤 이벤트가 발생해야 하는지를 정의한다. 이 경우에는 어떤 런레벨로 들어갈때 스크립트가 실행되고, 런레벨이 설정되지 않았을 때 종료된다.
그다음 서너 줄은 몇 가지 환경 변수를 외부로 전달하고, task 옵션은 이 스크립트가 지속되지 않을 것이라고 init 에게 알려준다(실행되고 나서 종료될 것이다)

Upstart 스크립트에서 실행되는 실제 프로그램들은 script 나 exec 옵션으로 정의된다. exec 옵션의 경우 Upstart 는 해당 명령과 exec 옵션에 지정된 모든 인자를 실행하고 그것의 PID 를 추적한다. script 옵션을 가지고 Upstart 는 end script 줄이 나올 때까지 모든 줄을 쉘 스크립트로 처리한다.

Upstart 가 이벤트 주도형으로 설계됐다고 하더라도 여전히 Upstart 작업의 상태를 확인하고 적절하게 그러한 작업을 시작하고 멈추기 위한 수단을 제공한다. 적절한 이름을 지닌 status, start, stop 명령을 이용해 상태, 시작, 종료에 대한 Upstart 스크립트를 확인할 수 있다.
우분투 서버에 있는 하나의 Upstart 작업은 tty1 작업이고, 그것은 tty1 에서 getty 프로그램을 시작한다. 이것은 시스템 관리자가 Alt-F1 눌렀을 때 콘솔을 보여준다. 하지만 어떤 이유로 콘솔이 멈췄다고 생각해보자. 다음은 콘솔 상태를 확인하고 작업을 재시작하는 방법이다.

#status tty1
tty1: start/running, process 789
#stop tty1
tty1 stop/waiting
#start tty1
tty1 start/running, process 2251

아울러 initctl list 로 모든 이용이 가능한 Upstart 작업의 상태를 조회할 수 있다.

#initctl list
mountall-net stop/waiting
rc stop/waiting
rsyslog start/running, process 640
tty4 start/running, process 708
...
  • computer/os/리눅스_부팅과정.txt
  • Last modified: 3 years ago
  • by likewind