본문으로 이동
  • 현재 모든 문서는 작성 중입니다. 보시는 데 불편함이 있더라도 양해 부탁드립니다.

운영체제 기본/Linux/x86-64/BIOS 부팅/루트 파일시스템과 init 선택: 두 판 사이의 차이

~ldmsys
출처·문체 정리: 커널 원문은 git.kernel.org, em dash는 콜론으로 변경
BIOS GRUB 부팅 경로 보강과 각론 형식 통일
1번째 줄: 1번째 줄:
[[운영체제 기본|운영체제 기본 목차]] · [[운영체제 기본/x86-64 Linux의 부팅|부팅 목차]]
[[운영체제 기본|운영체제 기본 목차]] · [[운영체제 기본/x86-64 Linux의 부팅|부팅 목차]]


'''시작 상태:''' PID 1이 <code>kernel_init()</code>을 실행하고 있다. initramfs 준비와 나머지 커널 초기화가 끝났으며 실행할 사용자 프로그램을 선택해야 한다.
Linux v6.12의 PID 1은 <code>kernel_init()</code>에서 실행할 init 파일을 고르고, <code>kernel_execve()</code>로 사용자 실행 이미지를 준비한다. 파일을 찾을 루트 파일시스템과 사용자 모드로 복귀할 CPU 상태가 모두 갖춰져야 첫 사용자 프로그램을 실행할 수 있다.


'''끝 상태:''' 같은 PID 1이 선택된 init의 실행 이미지를 갖고 사용자 모드에서 실행된다.
<span id="1._init을_실행하려면_먼저_그_파일이_있어야_한다"></span>
== init을 실행하려면 먼저 그 파일이 있어야 한다 ==


기준: '''Linux v6.12, x86-64'''.
실행 파일을 열려면 경로 탐색을 시작할 루트 파일시스템이 필요하다. VFS는 루트에서 각 디렉터리 이름을 해석해 inode를 찾고 파일 연산에 연결한다. 경로와 마운트, 열린 파일 객체의 관계는 [[운영체제 기본/Linux의 파일시스템/VFS와 파일 객체#경로에서 파일 객체의 대상을 찾는다|VFS와 파일 객체]]를 참고한다. 초기에는 rootfs가 있고, initramfs 아카이브의 파일들이 여기에 풀린다. <code>init/initramfs.c</code>의 <code>populate_rootfs()</code>는 비동기 작업을 등록하며, 그 작업이 내장 아카이브와 조건에 따라 외부 이미지를 푼다. <code>wait_for_initramfs()</code>는 이 작업이 끝나기 전에 init 파일에 접근하는 일을 막는다.<ref name="ramfs">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/init/initramfs.c?h=v6.12 v6.12 init/initramfs.c], populate_rootfs, do_populate_rootfs, wait_for_initramfs.</ref>
 
== 1. init을 실행하려면 먼저 그 파일이 있어야 한다 ==
 
실행 파일을 열려면 파일을 찾을 루트 파일 시스템이 필요하다. 초기에는 rootfs가 있고, initramfs 아카이브의 파일들이 여기에 풀린다. <code>init/initramfs.c</code>의 <code>populate_rootfs()</code>는 비동기 작업을 등록하며, 그 작업이 내장 아카이브와 조건에 따라 외부 이미지를 푼다. <code>wait_for_initramfs()</code>는 이 작업이 끝나기 전에 init 파일에 접근하는 일을 막는다.<ref name="ramfs">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/init/initramfs.c?h=v6.12 v6.12 init/initramfs.c], populate_rootfs, do_populate_rootfs, wait_for_initramfs.</ref>


initramfs의 <code>/init</code>을 실행할 수 있으면 루트 장치를 찾고 마운트하는 복잡한 처리를 초기 사용자 공간에 맡길 수 있다. 그렇지 않으면 커널의 <code>prepare_namespace()</code> 경로가 장치 탐색을 기다리고 루트 장치를 해석해 마운트한다. 어느 경로를 택했는지에 따라 이후 실행 파일을 찾는 루트도 달라진다.<ref name="main">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/init/main.c?h=v6.12 v6.12 init/main.c], kernel_init_freeable, kernel_init.</ref><ref>[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/init/do_mounts.c?h=v6.12 v6.12 init/do_mounts.c], prepare_namespace.</ref>
initramfs의 <code>/init</code>을 실행할 수 있으면 루트 장치를 찾고 마운트하는 복잡한 처리를 초기 사용자 공간에 맡길 수 있다. 그렇지 않으면 커널의 <code>prepare_namespace()</code> 경로가 장치 탐색을 기다리고 루트 장치를 해석해 마운트한다. 어느 경로를 택했는지에 따라 이후 실행 파일을 찾는 루트도 달라진다.<ref name="main">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/init/main.c?h=v6.12 v6.12 init/main.c], kernel_init_freeable, kernel_init.</ref><ref>[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/init/do_mounts.c?h=v6.12 v6.12 init/do_mounts.c], prepare_namespace.</ref>


== 2. rdinit=와 init=는 다른 단계에 적용된다 ==
<span id="2._rdinit=와_init=는_다른_단계에_적용된다"></span>
== rdinit=와 init=는 다른 단계에 적용된다 ==


초기 rootfs의 실행 후보는 기본적으로 <code>/init</code>이며 <code>rdinit=</code>으로 바꾼다. <code>kernel_init_freeable()</code>은 이 경로를 <code>init_eaccess()</code>로 사전 검사한다. 검사에 실패하면 초기 후보를 해제하고 <code>prepare_namespace()</code>를 호출한다.<ref name="main"/>
초기 rootfs의 실행 후보는 기본적으로 <code>/init</code>이며 <code>rdinit=</code>으로 바꾼다. <code>kernel_init_freeable()</code>은 이 경로를 <code>init_eaccess()</code>로 사전 검사한다. 검사에 실패하면 초기 후보를 해제하고 <code>prepare_namespace()</code>를 호출한다.<ref name="main"/>


그 뒤 <code>kernel_init()</code>의 실행 순서는 다음과 같다. '''인자 준비·로그·메모리 정리 등을 생략한 선택 흐름'''이다.<ref name="main"/>
그 뒤 <code>kernel_init()</code>은 다음 순서로 실행 후보를 선택한다.<ref name="main"/>


<pre>
<pre>
남아 있는 초기 후보(/init 또는 rdinit=) 실행
남아 있는 초기 후보(/init 또는 rdinit=) 실행
   성공 → 종료
   성공 → 사용자 실행 이미지 준비 완료
   실패 → 다음 선택 단계
   실패 → 다음 선택 단계


init=가 지정되었으면 실행
init=가 지정되었으면 실행
   성공 → 종료
   성공 → 사용자 실행 이미지 준비 완료
   실패 → panic
   실패 → panic


33번째 줄: 31번째 줄:
</pre>
</pre>


여기서 “종료”는 후보 탐색의 종료이지 PID 1의 종료가 아니다. <code>init=</code>를 지정해도 초기 후보가 먼저 성공하면 그 경로가 선택된다. 또한 초기 후보가 사전 검사를 통과했지만 실제 exec에서 실패한 경우에는 <code>prepare_namespace()</code>를 다시 호출하지 않는다. 따라서 위 후보들을 항상 “최종 디스크 루트에서 차례로 찾는 이름”으로 해석할 수는 없다.<ref name="main"/>
<code>init=</code>를 지정해도 초기 후보가 먼저 성공하면 그 경로가 선택된다. 또한 초기 후보가 사전 검사를 통과했지만 실제 exec에서 실패한 경우에는 <code>prepare_namespace()</code>를 다시 호출하지 않는다. 따라서 위 후보들을 항상 “최종 디스크 루트에서 차례로 찾는 이름”으로 해석할 수는 없다.<ref name="main"/>


== 3. exec는 PID 1을 새로 만들지 않는다 ==
<span id="3._exec는_PID_1을_새로_만들지_않는다"></span>
== exec는 PID 1을 새로 만들지 않는다 ==


<code>run_init_process()</code>는 인자와 환경을 준비해 <code>kernel_execve()</code>에 넘긴다. exec는 새 태스크 생성이 아니라 현재 태스크의 실행 이미지 교체다. PID 1은 유지되고, 실행할 프로그램의 코드·데이터와 사용자 실행 상태가 준비된다.<ref name="main"/><ref name="exec">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/exec.c?h=v6.12 v6.12 fs/exec.c], kernel_execve 및 bprm_execve.</ref>
<code>run_init_process()</code>는 인자와 환경을 준비해 <code>kernel_execve()</code>에 넘긴다. exec는 새 태스크 생성이 아니라 현재 태스크의 실행 이미지 교체다. PID 1은 유지되고, 실행할 프로그램의 코드·데이터와 사용자 실행 상태가 준비된다.<ref name="main"/><ref name="exec">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/exec.c?h=v6.12 v6.12 fs/exec.c], kernel_execve 및 bprm_execve.</ref>
41번째 줄: 40번째 줄:
사용자 공간의 <code>execve()</code>가 성공하면 원래 프로그램의 다음 문장으로 돌아오지 않는다. 그렇다고 커널 내부의 <code>kernel_execve()</code>도 성공 시 C 함수 반환을 하지 않는 것은 아니다. 이 함수는 성공 상태를 반환할 수 있다. 반환을 받은 <code>kernel_init()</code>도 0을 반환한다. 이는 새 init이 실행을 마치고 돌아온 결과가 아니라, '''사용자 실행 상태를 준비한 커널 코드가 복귀하는 과정'''이다.<ref name="main"/><ref name="exec"/>
사용자 공간의 <code>execve()</code>가 성공하면 원래 프로그램의 다음 문장으로 돌아오지 않는다. 그렇다고 커널 내부의 <code>kernel_execve()</code>도 성공 시 C 함수 반환을 하지 않는 것은 아니다. 이 함수는 성공 상태를 반환할 수 있다. 반환을 받은 <code>kernel_init()</code>도 0을 반환한다. 이는 새 init이 실행을 마치고 돌아온 결과가 아니라, '''사용자 실행 상태를 준비한 커널 코드가 복귀하는 과정'''이다.<ref name="main"/><ref name="exec"/>


== 4. 사용자 모드로 넘어가는 마지막 경로 ==
<span id="4._사용자_모드로_넘어가는_마지막_경로"></span>
== 사용자 모드로 넘어가는 마지막 경로 ==


새 태스크의 x86 시작 경로에는 <code>ret_from_fork_asm</code>과 <code>ret_from_fork()</code>가 있다. 후자는 등록된 시작 함수를 호출한다. PID 1에서는 그 함수가 <code>kernel_init()</code>이다. exec 성공 후 시작 함수가 돌아오면 <code>ret_from_fork()</code>는 <code>syscall_exit_to_user_mode()</code>를 거쳐 복귀 준비를 마친다.<ref name="process">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/process.c?h=v6.12 v6.12 arch/x86/kernel/process.c], ret_from_fork.</ref>
새 태스크의 x86 시작 경로에는 <code>ret_from_fork_asm</code>과 <code>ret_from_fork()</code>가 있다. 후자는 등록된 시작 함수를 호출한다. PID 1에서는 그 함수가 <code>kernel_init()</code>이다. exec 성공 후 시작 함수가 돌아오면 <code>ret_from_fork()</code>는 <code>syscall_exit_to_user_mode()</code>를 거쳐 복귀 준비를 마친다.<ref name="process">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/process.c?h=v6.12 v6.12 arch/x86/kernel/process.c], ret_from_fork.</ref>
48번째 줄: 48번째 줄:


initramfs의 <code>/init</code>이 실제 루트를 준비한 뒤 다른 init으로 exec하는 것은 이후 사용자 공간의 처리다. 커널이 <code>/init</code>을 실행한 다음 반드시 <code>/sbin/init</code>도 실행하는 구조는 아니다.<ref>[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/filesystems/ramfs-rootfs-initramfs.rst?h=v6.12 v6.12 Documentation/filesystems/ramfs-rootfs-initramfs.rst], What is initramfs? 절.</ref>
initramfs의 <code>/init</code>이 실제 루트를 준비한 뒤 다른 init으로 exec하는 것은 이후 사용자 공간의 처리다. 커널이 <code>/init</code>을 실행한 다음 반드시 <code>/sbin/init</code>도 실행하는 구조는 아니다.<ref>[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/filesystems/ramfs-rootfs-initramfs.rst?h=v6.12 v6.12 Documentation/filesystems/ramfs-rootfs-initramfs.rst], What is initramfs? 절.</ref>
'''소스 탐색:''' [https://elixir.bootlin.com/linux/v6.12/ident/kernel_init kernel_init] · [https://elixir.bootlin.com/linux/v6.12/ident/kernel_execve kernel_execve] · [https://elixir.bootlin.com/linux/v6.12/ident/ret_from_fork ret_from_fork].


'''이전:''' [[운영체제 기본/x86-64 Linux의 부팅/PID 1 생성|PID 1 생성]] · [[운영체제 기본/x86-64 Linux의 부팅|부팅 목차]]
'''이전:''' [[운영체제 기본/x86-64 Linux의 부팅/PID 1 생성|PID 1 생성]] · [[운영체제 기본/x86-64 Linux의 부팅|부팅 목차]]


== 참고 문헌 ==
<references />
<references />

2026년 10월 31일 (토) 08:38 판

운영체제 기본 목차 · 부팅 목차

Linux v6.12의 PID 1은 kernel_init()에서 실행할 init 파일을 고르고, kernel_execve()로 사용자 실행 이미지를 준비한다. 파일을 찾을 루트 파일시스템과 사용자 모드로 복귀할 CPU 상태가 모두 갖춰져야 첫 사용자 프로그램을 실행할 수 있다.

init을 실행하려면 먼저 그 파일이 있어야 한다

실행 파일을 열려면 경로 탐색을 시작할 루트 파일시스템이 필요하다. VFS는 루트에서 각 디렉터리 이름을 해석해 inode를 찾고 파일 연산에 연결한다. 경로와 마운트, 열린 파일 객체의 관계는 VFS와 파일 객체를 참고한다. 초기에는 rootfs가 있고, initramfs 아카이브의 파일들이 여기에 풀린다. init/initramfs.c의 populate_rootfs()는 비동기 작업을 등록하며, 그 작업이 내장 아카이브와 조건에 따라 외부 이미지를 푼다. wait_for_initramfs()는 이 작업이 끝나기 전에 init 파일에 접근하는 일을 막는다.[1]

initramfs의 /init을 실행할 수 있으면 루트 장치를 찾고 마운트하는 복잡한 처리를 초기 사용자 공간에 맡길 수 있다. 그렇지 않으면 커널의 prepare_namespace() 경로가 장치 탐색을 기다리고 루트 장치를 해석해 마운트한다. 어느 경로를 택했는지에 따라 이후 실행 파일을 찾는 루트도 달라진다.[2][3]

rdinit=와 init=는 다른 단계에 적용된다

초기 rootfs의 실행 후보는 기본적으로 /init이며 rdinit=으로 바꾼다. kernel_init_freeable()은 이 경로를 init_eaccess()로 사전 검사한다. 검사에 실패하면 초기 후보를 해제하고 prepare_namespace()를 호출한다.[2]

그 뒤 kernel_init()은 다음 순서로 실행 후보를 선택한다.[2]

남아 있는 초기 후보(/init 또는 rdinit=) 실행
  성공 → 사용자 실행 이미지 준비 완료
  실패 → 다음 선택 단계

init=가 지정되었으면 실행
  성공 → 사용자 실행 이미지 준비 완료
  실패 → panic

CONFIG_DEFAULT_INIT가 비어 있지 않으면 실행 시도
/sbin/init → /etc/init → /bin/init → /bin/sh 순서로 시도
모두 실패 → panic

init=를 지정해도 초기 후보가 먼저 성공하면 그 경로가 선택된다. 또한 초기 후보가 사전 검사를 통과했지만 실제 exec에서 실패한 경우에는 prepare_namespace()를 다시 호출하지 않는다. 따라서 위 후보들을 항상 “최종 디스크 루트에서 차례로 찾는 이름”으로 해석할 수는 없다.[2]

exec는 PID 1을 새로 만들지 않는다

run_init_process()는 인자와 환경을 준비해 kernel_execve()에 넘긴다. exec는 새 태스크 생성이 아니라 현재 태스크의 실행 이미지 교체다. PID 1은 유지되고, 실행할 프로그램의 코드·데이터와 사용자 실행 상태가 준비된다.[2][4]

사용자 공간의 execve()가 성공하면 원래 프로그램의 다음 문장으로 돌아오지 않는다. 그렇다고 커널 내부의 kernel_execve()도 성공 시 C 함수 반환을 하지 않는 것은 아니다. 이 함수는 성공 상태를 반환할 수 있다. 반환을 받은 kernel_init()도 0을 반환한다. 이는 새 init이 실행을 마치고 돌아온 결과가 아니라, 사용자 실행 상태를 준비한 커널 코드가 복귀하는 과정이다.[2][4]

사용자 모드로 넘어가는 마지막 경로

새 태스크의 x86 시작 경로에는 ret_from_fork_asm과 ret_from_fork()가 있다. 후자는 등록된 시작 함수를 호출한다. PID 1에서는 그 함수가 kernel_init()이다. exec 성공 후 시작 함수가 돌아오면 ret_from_fork()는 syscall_exit_to_user_mode()를 거쳐 복귀 준비를 마친다.[5]

어셈블리로 돌아온 뒤 사용자 모드 복귀 코드로 분기한다. v6.12에는 FRED 사용 여부에 따른 대체 경로가 있으므로, 마지막 전환을 모든 구성에서 동일한 명령 하나로 고정할 수 없다. exec의 이미지 준비와 CPU의 사용자 모드 전환은 이처럼 연결되어 있지만 서로 다른 단계다.[6]

initramfs의 /init이 실제 루트를 준비한 뒤 다른 init으로 exec하는 것은 이후 사용자 공간의 처리다. 커널이 /init을 실행한 다음 반드시 /sbin/init도 실행하는 구조는 아니다.[7]

이전: PID 1 생성 · 부팅 목차

참고 문헌

  1. ↑ v6.12 init/initramfs.c, populate_rootfs, do_populate_rootfs, wait_for_initramfs.
  2. ↑ 2.0 2.1 2.2 2.3 2.4 2.5 v6.12 init/main.c, kernel_init_freeable, kernel_init.
  3. ↑ v6.12 init/do_mounts.c, prepare_namespace.
  4. ↑ 4.0 4.1 v6.12 fs/exec.c, kernel_execve 및 bprm_execve.
  5. ↑ v6.12 arch/x86/kernel/process.c, ret_from_fork.
  6. ↑ v6.12 arch/x86/entry/entry_64.S, ret_from_fork_asm.
  7. ↑ v6.12 Documentation/filesystems/ramfs-rootfs-initramfs.rst, What is initramfs? 절.