운영체제 기본/Linux/x86-64/BIOS 부팅/루트 파일시스템과 init 선택: 두 판 사이의 차이
잔글 관리자님이 운영체제 기본/x86-64 Linux의 부팅/루트 파일시스템과 init 선택 문서를 운영체제 기본/x86-64 Linux의 BIOS 부팅/루트 파일시스템과 init 선택 문서로 이동했습니다: x86-64 전용 각론의 아키텍처와 장치·부팅 범위를 제목에 명시 |
Linux 계층형 경로에 맞춰 문서 링크 갱신 |
||
| (같은 사용자의 중간 판 9개는 보이지 않습니다) | |||
| 1번째 줄: | 1번째 줄: | ||
[[운영체제 기본| | {{이전 다음 문서 | ||
|이전=← 이전 문서<br />[[운영체제 기본/Linux/x86-64/BIOS 부팅/PID 1 생성|PID 1 생성]] | |||
|목차=[[운영체제 기본/Linux/x86-64/BIOS 부팅|BIOS 부팅 목차]] | |||
}} | |||
Linux v6.12의 PID 1은 초기 루트 파일시스템을 준비하고 <code>kernel_init()</code>에서 실행할 init 파일을 고른다. initramfs의 <code>/init</code>을 사용할지, 루트 장치를 마운트한 뒤 다른 경로를 찾을지는 이 단계에서 결정된다. | Linux v6.12의 PID 1은 초기 루트 파일시스템을 준비하고 <code>kernel_init()</code>에서 실행할 init 파일을 고른다. initramfs의 <code>/init</code>을 사용할지, 루트 장치를 마운트한 뒤 다른 경로를 찾을지는 이 단계에서 결정된다. | ||
< | == init 실행을 위한 루트 파일시스템 준비 == | ||
실행 파일을 열려면 경로 탐색을 시작할 루트 파일시스템이 필요하다. 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> | |||
실행 | 초기 rootfs의 실행 후보(기본 <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> | ||
== 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"/> | ||
| 33번째 줄: | 32번째 줄: | ||
<code>init=</code>를 지정해도 초기 후보가 먼저 성공하면 그 경로가 선택된다. 또한 초기 후보가 사전 검사를 통과했지만 실제 exec에서 실패한 경우에는 <code>prepare_namespace()</code>를 다시 호출하지 않는다. 따라서 위 후보들을 항상 “최종 디스크 루트에서 차례로 찾는 이름”으로 해석할 수는 없다.<ref name="main"/> | <code>init=</code>를 지정해도 초기 후보가 먼저 성공하면 그 경로가 선택된다. 또한 초기 후보가 사전 검사를 통과했지만 실제 exec에서 실패한 경우에는 <code>prepare_namespace()</code>를 다시 호출하지 않는다. 따라서 위 후보들을 항상 “최종 디스크 루트에서 차례로 찾는 이름”으로 해석할 수는 없다.<ref name="main"/> | ||
== 선택한 | == init 실행과 공통 exec 경로 == | ||
부팅 중 PID 1은 아직 사용자 프로그램을 실행한 적이 없으므로, 사용자 코드가 <code>execve()</code> 시스템 콜을 호출하는 방식으로 첫 프로그램을 시작할 수 없다. 커널의 <code>kernel_init()</code>에서 <code>run_init_process()</code>가 선택한 경로와 인자, 환경을 <code>kernel_execve()</code>에 전달한다.<ref name="main"/> | |||
<code>kernel_execve()</code>는 커널에 있는 인수 문자열을 복사한다는 차이가 있지만, <code>bprm_execve()</code>에서 일반 실행 경로와 합쳐져 같은 binfmt 핸들러와 ELF 로더를 사용한다. 파일 형식 판별, ELF 세그먼트 매핑, 초기 스택과 사용자 레지스터 준비의 공통 원리는 [[운영체제 기본/Linux/ELF 로딩|Linux의 ELF 로딩]]에서 설명한다.<ref name="exec">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/exec.c?h=v6.12 Linux v6.12, fs/exec.c], <code>kernel_execve()</code>, <code>bprm_execve()</code>.</ref> | |||
PID 1은 그대로 유지되고 실행 이미지가 준비된다. 앞서 [[운영체제 기본/Linux/x86-64/BIOS 부팅/PID 1 생성|PID 1 생성]]에서 만든 태스크가 이제 첫 사용자 프로그램을 실행할 주소 공간과 스택, 레지스터 상태를 갖게 되는 것이다. | |||
< | == PID 1의 첫 사용자 모드 진입 == | ||
< | 일반 <code>execve()</code>는 시스템 콜의 사용자 복귀 경로로 나가지만, PID 1의 첫 실행은 초기 태스크의 커널 시작 함수가 반환하는 경로에서 사용자 모드로 나간다. 성공하면 <code>kernel_execve()</code>에서 돌아온 <code>kernel_init()</code>가 반환한다. x86-64에서는 이 시작 함수를 호출했던 <code>ret_from_fork()</code>가 사용자 복귀 준비를 수행하고, 이어 <code>ret_from_fork_asm</code>의 어셈블리 복귀 경로가 로더가 준비한 레지스터 상태를 복원한다. <code>iretq</code>로 사용자 권한과 진입 주소, 스택을 적용하면 비로소 첫 사용자 명령어를 실행한다.<ref name="x86-fork">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/process.c?h=v6.12 Linux v6.12, arch/x86/kernel/process.c], <code>ret_from_fork()</code>.</ref><ref name="x86-entry">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/entry_64.S?h=v6.12 Linux v6.12, arch/x86/entry/entry_64.S], <code>ret_from_fork_asm</code>, 사용자 모드 복귀 경로.</ref> | ||
비교하면, RISC-V RV64의 첫 사용자 진입에서는 어셈블리 <code>ret_from_fork</code>가 <code>kernel_init()</code>을 호출하고, 반환 뒤 <code>syscall_exit_to_user_mode()</code>를 거쳐 <code>ret_from_exception</code>으로 이어진다. 저장된 상태를 복원한 뒤 <code>sret</code>로 사용자 모드에 진입한다. 이는 x86 BIOS 부팅 경로와 구별되는 RV64의 사용자 진입 방식이다.<ref name="rv-return">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/riscv/kernel/entry.S?h=v6.12 Linux v6.12, arch/riscv/kernel/entry.S], <code>ret_from_fork</code>, <code>ret_from_exception</code>.</ref> | |||
커널이 C 함수 호출처럼 init의 <code>main()</code>을 부르는 것은 아니다. 새 실행 이미지와 사용자 상태를 준비한 뒤 그 상태로 사용자 모드에 진입한다. 두 아키텍처의 레지스터 준비와 복귀 명령의 공통 비교는 [[운영체제 기본/Linux/ELF 로딩/초기 스택과 사용자 모드 진입#사용자 모드 진입: x86-64와 RISC-V|초기 스택과 사용자 모드 진입]]에서 다룬다. | |||
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 Linux v6.12, 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 Linux v6.12, ramfs-rootfs-initramfs.rst], What is initramfs? 절.</ref> | ||
| 45번째 줄: | 50번째 줄: | ||
== 참고 문헌 == | == 참고 문헌 == | ||
<references /> | <references /> | ||
{{이전 다음 문서 | |||
|이전=← 이전 문서<br />[[운영체제 기본/Linux/x86-64/BIOS 부팅/PID 1 생성|PID 1 생성]] | |||
|목차=[[운영체제 기본/Linux/x86-64/BIOS 부팅|BIOS 부팅 목차]] | |||
}} | |||
{{운영체제 기본 둘러보기}} | |||
2026년 11월 1일 (일) 02:24 기준 최신판
Linux v6.12의 PID 1은 초기 루트 파일시스템을 준비하고 kernel_init()에서 실행할 init 파일을 고른다. initramfs의 /init을 사용할지, 루트 장치를 마운트한 뒤 다른 경로를 찾을지는 이 단계에서 결정된다.
init 실행을 위한 루트 파일시스템 준비
실행 파일을 열려면 경로 탐색을 시작할 루트 파일시스템이 필요하다. VFS는 루트에서 각 디렉터리 이름을 해석해 inode를 찾고 파일 연산에 연결한다. 경로와 마운트, 열린 파일 객체의 관계는 VFS와 파일 객체를 참고한다. 초기에는 rootfs가 있고, initramfs 아카이브의 파일들이 여기에 풀린다. init/initramfs.c의 populate_rootfs()는 비동기 작업을 등록하며, 그 작업이 내장 아카이브와 조건에 따라 외부 이미지를 푼다. wait_for_initramfs()는 이 작업이 끝나기 전에 init 파일에 접근하는 일을 막는다.[1]
초기 rootfs의 실행 후보(기본 /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]
init 실행과 공통 exec 경로
부팅 중 PID 1은 아직 사용자 프로그램을 실행한 적이 없으므로, 사용자 코드가 execve() 시스템 콜을 호출하는 방식으로 첫 프로그램을 시작할 수 없다. 커널의 kernel_init()에서 run_init_process()가 선택한 경로와 인자, 환경을 kernel_execve()에 전달한다.[2]
kernel_execve()는 커널에 있는 인수 문자열을 복사한다는 차이가 있지만, bprm_execve()에서 일반 실행 경로와 합쳐져 같은 binfmt 핸들러와 ELF 로더를 사용한다. 파일 형식 판별, ELF 세그먼트 매핑, 초기 스택과 사용자 레지스터 준비의 공통 원리는 Linux의 ELF 로딩에서 설명한다.[4]
PID 1은 그대로 유지되고 실행 이미지가 준비된다. 앞서 PID 1 생성에서 만든 태스크가 이제 첫 사용자 프로그램을 실행할 주소 공간과 스택, 레지스터 상태를 갖게 되는 것이다.
PID 1의 첫 사용자 모드 진입
일반 execve()는 시스템 콜의 사용자 복귀 경로로 나가지만, PID 1의 첫 실행은 초기 태스크의 커널 시작 함수가 반환하는 경로에서 사용자 모드로 나간다. 성공하면 kernel_execve()에서 돌아온 kernel_init()가 반환한다. x86-64에서는 이 시작 함수를 호출했던 ret_from_fork()가 사용자 복귀 준비를 수행하고, 이어 ret_from_fork_asm의 어셈블리 복귀 경로가 로더가 준비한 레지스터 상태를 복원한다. iretq로 사용자 권한과 진입 주소, 스택을 적용하면 비로소 첫 사용자 명령어를 실행한다.[5][6]
비교하면, RISC-V RV64의 첫 사용자 진입에서는 어셈블리 ret_from_fork가 kernel_init()을 호출하고, 반환 뒤 syscall_exit_to_user_mode()를 거쳐 ret_from_exception으로 이어진다. 저장된 상태를 복원한 뒤 sret로 사용자 모드에 진입한다. 이는 x86 BIOS 부팅 경로와 구별되는 RV64의 사용자 진입 방식이다.[7]
커널이 C 함수 호출처럼 init의 main()을 부르는 것은 아니다. 새 실행 이미지와 사용자 상태를 준비한 뒤 그 상태로 사용자 모드에 진입한다. 두 아키텍처의 레지스터 준비와 복귀 명령의 공통 비교는 초기 스택과 사용자 모드 진입에서 다룬다.
initramfs의 /init이 실제 루트를 준비한 뒤 다른 init으로 exec하는 것은 이후 사용자 공간의 처리다. 커널이 /init을 실행한 다음 반드시 /sbin/init도 실행하는 구조는 아니다.[8]
참고 문헌
- ↑ v6.12 init/initramfs.c, populate_rootfs, do_populate_rootfs, wait_for_initramfs.
- ↑ 2.0 2.1 2.2 2.3 2.4 v6.12 init/main.c, kernel_init_freeable, kernel_init.
- ↑ v6.12 init/do_mounts.c, prepare_namespace.
- ↑ Linux v6.12, fs/exec.c,
kernel_execve(),bprm_execve(). - ↑ Linux v6.12, arch/x86/kernel/process.c,
ret_from_fork(). - ↑ Linux v6.12, arch/x86/entry/entry_64.S,
ret_from_fork_asm, 사용자 모드 복귀 경로. - ↑ Linux v6.12, arch/riscv/kernel/entry.S,
ret_from_fork,ret_from_exception. - ↑ Linux v6.12, ramfs-rootfs-initramfs.rst, What is initramfs? 절.