요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
===========================
Ramfs, rootfs and initramfs
===========================
October 17, 2005
:Author: Rob Landley <rob@landley.net>
What is ramfs?
--------------
Ramfs is a very simple filesystem that exports Linux's disk caching
mechanisms (the page cache and dentry cache) as a dynamically resizable
RAM-based filesystem.
Normally all files are cached in memory by Linux. Pages of data read from
backing store (usually the block device the filesystem is mounted on) are kept
around in case it's needed again, but marked as clean (freeable) in case the
Virtual Memory system needs the memory for something else. Similarly, data
written to files is marked clean as soon as it has been written to backing
store, but kept around for caching purposes until the VM reallocates the
memory. A similar mechanism (the dentry cache) greatly speeds up access to
directories.
With ramfs, there is no backing store. Files written into ramfs allocate
dentries and page cache as usual, but there's nowhere to write them to.
This means the pages are never marked clean, so they can't be freed by the
VM when it's looking to recycle memory.
The amount of code required to implement ramfs is tiny, because all the
work is done by the existing Linux caching infrastructure. Basically,
you're mounting the disk cache as a filesystem. Because of this, ramfs is not
an optional component removable via menuconfig, since there would be negligible
space savings.
ramfs and ramdisk:
------------------
The older "ram disk" mechanism created a synthetic block device out of
an area of RAM and used it as backing store for a filesystem. This block
device was of fixed size, so the filesystem mounted on it was of fixed
size. Using a ram disk also required unnecessarily copying memory from the
fake block device into the page cache (and copying changes back out), as well
as creating and destroying dentries. Plus it needed a filesystem driver
(such as ext2) to format and interpret this data.
Compared to ramfs, this wastes memory (and memory bus bandwidth), creates
unnecessary work for the CPU, and pollutes the CPU caches. (There are tricks
to avoid this copying by playing with the page tables, but they're unpleasantly
complicated and turn out to be about as expensive as the copying anyway.)
More to the point, all the work ramfs is doing has to happen _anyway_,
since all file access goes through the page and dentry caches. The RAM
disk is simply unnecessary; ramfs is internally much simpler.
Another reason ramdisks are semi-obsolete is that the introduction of
loopback devices offered a more flexible and convenient way to create
synthetic block devices, now from files instead of from chunks of memory.
See losetup (8) for details.
ramfs and tmpfs:
----------------
One downside of ramfs is you can keep writing data into it until you fill
up all memory, and the VM can't free it because the VM thinks that files
should get written to backing store (rather than swap space), but ramfs hasn't
got any backing store. Because of this, only root (or a trusted user) should
be allowed write access to a ramfs mount.
A ramfs derivative called tmpfs was created to add size limits, and the ability
to write the data to swap space. Normal users can be allowed write access to
tmpfs mounts. See Documentation/filesystems/tmpfs.rst for more information.
What is rootfs?
---------------
Rootfs is a special instance of ramfs (or tmpfs, if that's enabled), which is
always present in 2.6 systems. You can't unmount rootfs for approximately the
same reason you can't kill the init process; rather than having special code
to check for and handle an empty list, it's smaller and simpler for the kernel
to just make sure certain lists can't become empty.
Most systems just mount another filesystem over rootfs and ignore it. The
amount of space an empty instance of ramfs takes up is tiny.
If CONFIG_TMPFS is enabled, rootfs will use tmpfs instead of ramfs by
default. To force ramfs, add "rootfstype=ramfs" to the kernel command
line.
What is initramfs?
------------------
All 2.6 Linux kernels contain a gzipped "cpio" format archive, which is
extracted into rootfs when the kernel boots up. After extracting, the kernel
checks to see if rootfs contains a file "init", and if so it executes it as PID
1. If found, this init process is responsible for bringing the system the
rest of the way up, including locating and mounting the real root device (if
any). If rootfs does not contain an init program after the embedded cpio
archive is extracted into it, the kernel will fall through to the older code
to locate and mount a root partition, then exec some variant of /sbin/init
out of that.
All this differs from the old initrd in several ways:
- The old initrd was always a separate file, while the initramfs archive is
linked into the linux kernel image. (The directory ``linux-*/usr`` is
devoted to generating this archive during the build.)
- The old initrd file was a gzipped filesystem image (in some file format,
such as ext2, that needed a driver built into the kernel), while the new
initramfs archive is a gzipped cpio archive (like tar only simpler,
see cpio(1) and Documentation/driver-api/early-userspace/buffer-format.rst).
The kernel's cpio extraction code is not only extremely small, it's also
__init text and data that can be discarded during the boot process.
- The program run by the old initrd (which was called /initrd, not /init) did
some setup and then returned to the kernel, while the init program from
initramfs is not expected to return to the kernel. (If /init needs to hand
off control it can overmount / with a new root device and exec another init
program. See the switch_root utility, below.)
- When switching another root device, initrd would pivot_root and then
umount the ramdisk. But initramfs is rootfs: you can neither pivot_root
rootfs, nor unmount it. Instead delete everything out of rootfs to
free up the space (find -xdev / -exec rm '{}' ';'), overmount rootfs
with the new root (cd /newmount; mount --move . /; chroot .), attach
stdin/stdout/stderr to the new /dev/console, and exec the new init.
Since this is a remarkably persnickety process (and involves deleting
commands before you can run them), the klibc package introduced a helper
program (utils/run_init.c) to do all this for you. Most other packages
(such as busybox) have named this command "switch_root".
Populating initramfs:
---------------------
The 2.6 kernel build process always creates a gzipped cpio format initramfs
archive and links it into the resulting kernel binary. By default, this
archive is empty (consuming 134 bytes on x86).
The config option CONFIG_INITRAMFS_SOURCE (in General Setup in menuconfig,
and living in usr/Kconfig) can be used to specify a source for the
initramfs archive, which will automatically be incorporated into the
resulting binary. This option can point to an existing gzipped cpio
archive, a directory containing files to be archived, or a text file
specification such as the following example::
dir /dev 755 0 0
nod /dev/console 644 0 0 c 5 1
nod /dev/loop0 644 0 0 b 7 0
dir /bin 755 1000 1000
slink /bin/sh busybox 777 0 0
file /bin/busybox initramfs/busybox 755 0 0
dir /proc 755 0 0
dir /sys 755 0 0
dir /mnt 755 0 0
file /init initramfs/init.sh 755 0 0
Run "usr/gen_init_cpio" (after the kernel build) to get a usage message
documenting the above file format.
One advantage of the configuration file is that root access is not required to
set permissions or create device nodes in the new archive. (Note that those
two example "file" entries expect to find files named "init.sh" and "busybox" in
a directory called "initramfs", under the linux-2.6.* directory. See
Documentation/driver-api/early-userspace/early_userspace_support.rst for more details.)
The kernel does not depend on external cpio tools. If you specify a
directory instead of a configuration file, the kernel's build infrastructure
creates a configuration file from that directory (usr/Makefile calls
usr/gen_initramfs.sh), and proceeds to package up that directory
using the config file (by feeding it to usr/gen_init_cpio, which is created
from usr/gen_init_cpio.c). The kernel's build-time cpio creation code is
entirely self-contained, and the kernel's boot-time extractor is also
(obviously) self-contained.
The one thing you might need external cpio utilities installed for is creating
or extracting your own preprepared cpio files to feed to the kernel build
(instead of a config file or directory).
The following command line can extract a cpio image (either by the above script
or by the kernel build) back into its component files::
cpio -i -d -H newc -F initramfs_data.cpio --no-absolute-filenames
The following shell script can create a prebuilt cpio archive you can
use in place of the above config file::
#!/bin/sh
# Copyright 2006 Rob Landley <rob@landley.net> and TimeSys Corporation.
# Licensed under GPL version 2
if [ $# -ne 2 ]
then
echo "usage: mkinitramfs directory imagename.cpio.gz"
exit 1
fi
if [ -d "$1" ]
then
echo "creating $2 from $1"
(cd "$1"; find . | cpio -o -H newc | gzip) > "$2"
else
echo "First argument must be a directory"
exit 1
fi
.. Note::
The cpio man page contains some bad advice that will break your initramfs
archive if you follow it. It says "A typical way to generate the list
of filenames is with the find command; you should give find the -depth
option to minimize problems with permissions on directories that are
unwritable or not searchable." Don't do this when creating
initramfs.cpio.gz images, it won't work. The Linux kernel cpio extractor
won't create files in a directory that doesn't exist, so the directory
entries must go before the files that go in those directories.
The above script gets them in the right order.
External initramfs images:
--------------------------
If the kernel has initrd support enabled, an external cpio.gz archive can also
be passed into a 2.6 kernel in place of an initrd. In this case, the kernel
will autodetect the type (initramfs, not initrd) and extract the external cpio
archive into rootfs before trying to run /init.
This has the memory efficiency advantages of initramfs (no ramdisk block
device) but the separate packaging of initrd (which is nice if you have
non-GPL code you'd like to run from initramfs, without conflating it with
the GPL licensed Linux kernel binary).
It can also be used to supplement the kernel's built-in initramfs image. The
files in the external archive will overwrite any conflicting files in
the built-in initramfs archive. Some distributors also prefer to customize
a single kernel image with task-specific initramfs images, without recompiling.
Contents of initramfs:
----------------------
An initramfs archive is a complete self-contained root filesystem for Linux.
If you don't already understand what shared libraries, devices, and paths
you need to get a minimal root filesystem up and running, here are some
references:
- https://www.tldp.org/HOWTO/Bootdisk-HOWTO/
- https://www.tldp.org/HOWTO/From-PowerUp-To-Bash-Prompt-HOWTO.html
- http://www.linuxfromscratch.org/lfs/view/stable/
The "klibc" package (https://www.kernel.org/pub/linux/libs/klibc) is
designed to be a tiny C library to statically link early userspace
code against, along with some related utilities. It is BSD licensed.
I use uClibc (https://www.uclibc.org) and busybox (https://www.busybox.net)
myself. These are LGPL and GPL, respectively. (A self-contained initramfs
package is planned for the busybox 1.3 release.)
In theory you could use glibc, but that's not well suited for small embedded
uses like this. (A "hello world" program statically linked against glibc is
over 400k. With uClibc it's 7k. Also note that glibc dlopens libnss to do
name lookups, even when otherwise statically linked.)
A good first step is to get initramfs to run a statically linked "hello world"
program as init, and test it under an emulator like qemu (www.qemu.org) or
User Mode Linux, like so::
cat > hello.c << EOF
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("Hello world!\n");
sleep(999999999);
}
EOF
gcc -static hello.c -o init
echo init | cpio -o -H newc | gzip > test.cpio.gz
# Testing external initramfs using the initrd loading mechanism.
qemu -kernel /boot/vmlinuz -initrd test.cpio.gz /dev/zero
When debugging a normal root filesystem, it's nice to be able to boot with
"init=/bin/sh". The initramfs equivalent is "rdinit=/bin/sh", and it's
just as useful.
Why cpio rather than tar?
-------------------------
This decision was made back in December, 2001. The discussion started here:
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1538.html
And spawned a second thread (specifically on tar vs cpio), starting here:
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1587.html
The quick and dirty summary version (which is no substitute for reading
the above threads) is:
1) cpio is a standard. It's decades old (from the AT&T days), and already
widely used on Linux (inside RPM, Red Hat's device driver disks). Here's
a Linux Journal article about it from 1996:
http://www.linuxjournal.com/article/1213
It's not as popular as tar because the traditional cpio command line tools
require _truly_hideous_ command line arguments. But that says nothing
either way about the archive format, and there are alternative tools,
such as:
http://freecode.com/projects/afio
2) The cpio archive format chosen by the kernel is simpler and cleaner (and
thus easier to create and parse) than any of the (literally dozens of)
various tar archive formats. The complete initramfs archive format is
explained in buffer-format.rst, created in usr/gen_init_cpio.c, and
extracted in init/initramfs.c. All three together come to less than 26k
total of human-readable text.
3) The GNU project standardizing on tar is approximately as relevant as
Windows standardizing on zip. Linux is not part of either, and is free
to make its own technical decisions.
4) Since this is a kernel internal format, it could easily have been
something brand new. The kernel provides its own tools to create and
extract this format anyway. Using an existing standard was preferable,
but not essential.
5) Al Viro made the decision (quote: "tar is ugly as hell and not going to be
supported on the kernel side"):
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1540.html
explained his reasoning:
- http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1550.html
- http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1638.html
and, most importantly, designed and implemented the initramfs code.
Future directions:
------------------
Today (2.6.16), initramfs is always compiled in, but not always used. The
kernel falls back to legacy boot code that is reached only if initramfs does
not contain an /init program. The fallback is legacy code, there to ensure a
smooth transition and allowing early boot functionality to gradually move to
"early userspace" (I.E. initramfs).
The move to early userspace is necessary because finding and mounting the real
root device is complex. Root partitions can span multiple devices (raid or
separate journal). They can be out on the network (requiring dhcp, setting a
specific MAC address, logging into a server, etc). They can live on removable
media, with dynamically allocated major/minor numbers and persistent naming
issues requiring a full udev implementation to sort out. They can be
compressed, encrypted, copy-on-write, loopback mounted, strangely partitioned,
and so on.
This kind of complexity (which inevitably includes policy) is rightly handled
in userspace. Both klibc and busybox/uClibc are working on simple initramfs
packages to drop into a kernel build.
The klibc package has now been accepted into Andrew Morton's 2.6.17-mm tree.
The kernel's current early boot code (partition detection, etc) will probably
be migrated into a default initramfs, automatically created and used by the
kernel build.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Ramfs와 Linux 캐시 계층
1-37Ramfs는 Linux의 디스크 캐시 메커니즘인 page cache와 dentry cache를 동적으로 크기가 변하는 RAM 기반 파일시스템으로 노출하는 매우 단순한 파일시스템이다.
일반 파일시스템에서도 Linux는 모든 파일을 메모리에 캐시한다. backing store, 대개 파일시스템이 마운트된 블록 장치에서 읽은 데이터 page는 다시 필요할 때를 대비해 남겨 두되 clean, 즉 회수 가능한 상태로 표시한다. VM이 다른 용도로 메모리를 필요로 하면 이 page를 해제할 수 있다.
파일에 쓴 데이터도 backing store에 기록되는 즉시 clean으로 표시하지만, VM이 메모리를 재할당할 때까지 캐시로 유지한다. 디렉터리 접근은 같은 원리의 dentry cache가 크게 가속한다.
Ramfs에는 backing store가 없다. 파일을 쓰면 보통처럼 dentry와 page cache를 할당하지만 이를 내보낼 저장소가 없다. 따라서 page가 clean으로 바뀌지 않고, VM이 메모리를 회수할 때도 해제할 수 없다.
Ramfs 구현 코드가 매우 작은 이유는 기존 Linux 캐시 인프라가 거의 모든 일을 하기 때문이다. 본질적으로 디스크 캐시 자체를 파일시스템으로 마운트한다. 제거해도 절약할 공간이 거의 없으므로 ramfs는 `menuconfig`에서 뺄 수 있는 선택 구성요소가 아니다.
backing store 유무가 clean 전환과 VM 회수 가능 여부를 결정한다.
.. SPDX-License-Identifier: GPL-2.0
===========================
Ramfs, rootfs and initramfs
===========================
October 17, 2005
:Author: Rob Landley <rob@landley.net>
What is ramfs?
--------------
Ramfs is a very simple filesystem that exports Linux's disk caching
mechanisms (the page cache and dentry cache) as a dynamically resizable
RAM-based filesystem.
Normally all files are cached in memory by Linux. Pages of data read from
backing store (usually the block device the filesystem is mounted on) are kept
around in case it's needed again, but marked as clean (freeable) in case the
Virtual Memory system needs the memory for something else. Similarly, data
written to files is marked clean as soon as it has been written to backing
store, but kept around for caching purposes until the VM reallocates the
memory. A similar mechanism (the dentry cache) greatly speeds up access to
directories.
With ramfs, there is no backing store. Files written into ramfs allocate
dentries and page cache as usual, but there's nowhere to write them to.
This means the pages are never marked clean, so they can't be freed by the
VM when it's looking to recycle memory.
The amount of code required to implement ramfs is tiny, because all the
work is done by the existing Linux caching infrastructure. Basically,
you're mounting the disk cache as a filesystem. Because of this, ramfs is not
an optional component removable via menuconfig, since there would be negligible
space savings.
Ramdisk와 tmpfs 비교
38-74과거 ram disk는 RAM 일부를 합성 블록 장치로 만들고 그 위 파일시스템의 backing store로 사용했다. 블록 장치 크기가 고정이므로 그 위의 파일시스템 크기도 고정됐다.
Ram disk는 가짜 블록 장치와 page cache 사이에서 메모리를 불필요하게 복사하고 변경분을 다시 내보내야 하며, dentry도 만들고 없애야 했다. 이 데이터를 포맷하고 해석할 ext2 같은 별도 파일시스템 드라이버도 필요했다.
Ramfs와 비교하면 메모리와 메모리 버스 대역폭을 낭비하고 CPU에 불필요한 일을 만들며 CPU cache까지 오염시킨다. Page table 조작으로 복사를 피할 수 있지만 구현이 복잡하고 실제 비용도 복사와 비슷하다. 모든 파일 접근은 어차피 page·dentry cache를 지나므로 ram disk 계층은 불필요하고 ramfs가 내부적으로 더 단순하다.
Loopback 장치는 메모리 덩어리 대신 파일에서 합성 블록 장치를 만드는 더 유연하고 편리한 방법을 제공해 ramdisk를 더 구식으로 만들었다. 자세한 사용법은 `losetup(8)`을 참고한다.
Ramfs는 데이터를 계속 쓰면 메모리를 전부 채울 수 있다는 단점이 있다. VM은 파일 데이터를 swap이 아니라 backing store에 써야 한다고 보지만 ramfs에는 backing store가 없어 회수할 수 없다. 그러므로 ramfs 마운트의 쓰기 권한은 root 또는 신뢰할 수 있는 사용자에게만 허용해야 한다.
Ramfs에서 파생된 tmpfs는 크기 제한과 데이터를 swap 공간으로 내보내는 기능을 추가했다. 일반 사용자에게 tmpfs 마운트 쓰기 권한을 줄 수 있다. 자세한 내용은 `Documentation/filesystems/tmpfs.rst`에 있다.
중복 캐시, 크기 제한, swap 지원의 차이를 보여 준다.
ramfs and ramdisk:
------------------
The older "ram disk" mechanism created a synthetic block device out of
an area of RAM and used it as backing store for a filesystem. This block
device was of fixed size, so the filesystem mounted on it was of fixed
size. Using a ram disk also required unnecessarily copying memory from the
fake block device into the page cache (and copying changes back out), as well
as creating and destroying dentries. Plus it needed a filesystem driver
(such as ext2) to format and interpret this data.
Compared to ramfs, this wastes memory (and memory bus bandwidth), creates
unnecessary work for the CPU, and pollutes the CPU caches. (There are tricks
to avoid this copying by playing with the page tables, but they're unpleasantly
complicated and turn out to be about as expensive as the copying anyway.)
More to the point, all the work ramfs is doing has to happen _anyway_,
since all file access goes through the page and dentry caches. The RAM
disk is simply unnecessary; ramfs is internally much simpler.
Another reason ramdisks are semi-obsolete is that the introduction of
loopback devices offered a more flexible and convenient way to create
synthetic block devices, now from files instead of from chunks of memory.
See losetup (8) for details.
ramfs and tmpfs:
----------------
One downside of ramfs is you can keep writing data into it until you fill
up all memory, and the VM can't free it because the VM thinks that files
should get written to backing store (rather than swap space), but ramfs hasn't
got any backing store. Because of this, only root (or a trusted user) should
be allowed write access to a ramfs mount.
A ramfs derivative called tmpfs was created to add size limits, and the ability
to write the data to swap space. Normal users can be allowed write access to
tmpfs mounts. See Documentation/filesystems/tmpfs.rst for more information.
항상 존재하는 rootfs
75-90Rootfs는 2.6 계열 시스템에 항상 존재하는 ramfs의 특수 인스턴스다. `CONFIG_TMPFS`가 켜져 있으면 tmpfs를 사용할 수도 있다.
Init 프로세스를 죽일 수 없는 것과 비슷한 이유로 rootfs는 언마운트할 수 없다. 빈 목록을 검사하고 특별 처리하는 코드를 추가하는 대신, 커널의 특정 목록이 절대로 비지 않게 보장하는 편이 더 작고 단순하기 때문이다.
대부분의 시스템은 rootfs 위에 다른 파일시스템을 마운트하고 rootfs 자체는 무시한다. 빈 ramfs 인스턴스가 차지하는 공간은 매우 작다.
`CONFIG_TMPFS`를 활성화하면 rootfs는 기본적으로 ramfs 대신 tmpfs를 사용한다. ramfs를 강제로 쓰려면 커널 명령줄에 `rootfstype=ramfs`를 추가한다.
구성 옵션과 커널 명령줄이 rootfs 구현을 결정한다.
What is rootfs?
---------------
Rootfs is a special instance of ramfs (or tmpfs, if that's enabled), which is
always present in 2.6 systems. You can't unmount rootfs for approximately the
same reason you can't kill the init process; rather than having special code
to check for and handle an empty list, it's smaller and simpler for the kernel
to just make sure certain lists can't become empty.
Most systems just mount another filesystem over rootfs and ignore it. The
amount of space an empty instance of ramfs takes up is tiny.
If CONFIG_TMPFS is enabled, rootfs will use tmpfs instead of ramfs by
default. To force ramfs, add "rootfstype=ramfs" to the kernel command
line.
Initramfs 부팅과 기존 initrd의 차이
91-134모든 2.6 Linux 커널에는 gzip으로 압축한 `cpio` 형식 archive가 들어 있으며 부팅 때 rootfs로 푼다. 압축 해제 후 rootfs에 `init` 파일이 있으면 이를 PID 1로 실행한다. 이 init은 실제 root 장치를 찾아 마운트하는 일을 포함해 나머지 시스템 기동을 책임진다.
내장 cpio archive를 푼 뒤에도 `init`이 없으면 커널은 예전 코드로 넘어가 root partition을 찾아 마운트하고 그 안의 `/sbin/init` 변형을 실행한다.
패키징, 실행 제어, 루트 전환 방식이 다르다.
Initramfs의 `/init`이 제어를 넘기려면 `/` 위에 새 root 장치를 overmount하고 다른 init을 exec한다. Rootfs 자체가 initramfs이므로 rootfs에 `pivot_root`를 적용하거나 언마운트할 수 없다.
find -xdev / -exec rm '{}' ';'
cd /newmount
mount --move . /
chroot .
# stdin/stdout/stderr를 새 /dev/console에 연결
exec /sbin/init
따라서 rootfs의 모든 내용을 지워 공간을 회수하고, 새 root를 이동 마운트하고 chroot한 뒤 표준 입력·출력·오류를 새 `/dev/console`에 연결하고 새 init을 exec해야 한다. 이 절차는 실행할 명령 자체를 먼저 지우는 문제까지 있어 매우 까다롭다.
Klibc 패키지는 이 작업을 대신하는 `utils/run_init.c`를 제공했다. BusyBox를 비롯한 다른 패키지는 같은 명령을 `switch_root`라고 부른다.
`/init`의 존재 여부가 early userspace와 legacy root 탐색을 가른다.
What is initramfs?
------------------
All 2.6 Linux kernels contain a gzipped "cpio" format archive, which is
extracted into rootfs when the kernel boots up. After extracting, the kernel
checks to see if rootfs contains a file "init", and if so it executes it as PID
1. If found, this init process is responsible for bringing the system the
rest of the way up, including locating and mounting the real root device (if
any). If rootfs does not contain an init program after the embedded cpio
archive is extracted into it, the kernel will fall through to the older code
to locate and mount a root partition, then exec some variant of /sbin/init
out of that.
All this differs from the old initrd in several ways:
- The old initrd was always a separate file, while the initramfs archive is
linked into the linux kernel image. (The directory ``linux-*/usr`` is
devoted to generating this archive during the build.)
- The old initrd file was a gzipped filesystem image (in some file format,
such as ext2, that needed a driver built into the kernel), while the new
initramfs archive is a gzipped cpio archive (like tar only simpler,
see cpio(1) and Documentation/driver-api/early-userspace/buffer-format.rst).
The kernel's cpio extraction code is not only extremely small, it's also
__init text and data that can be discarded during the boot process.
- The program run by the old initrd (which was called /initrd, not /init) did
some setup and then returned to the kernel, while the init program from
initramfs is not expected to return to the kernel. (If /init needs to hand
off control it can overmount / with a new root device and exec another init
program. See the switch_root utility, below.)
- When switching another root device, initrd would pivot_root and then
umount the ramdisk. But initramfs is rootfs: you can neither pivot_root
rootfs, nor unmount it. Instead delete everything out of rootfs to
free up the space (find -xdev / -exec rm '{}' ';'), overmount rootfs
with the new root (cd /newmount; mount --move . /; chroot .), attach
stdin/stdout/stderr to the new /dev/console, and exec the new init.
Since this is a remarkably persnickety process (and involves deleting
commands before you can run them), the klibc package introduced a helper
program (utils/run_init.c) to do all this for you. Most other packages
(such as busybox) have named this command "switch_root".
Initramfs archive 구성과 생성
135-2212.6 커널 빌드는 항상 gzip cpio 형식 initramfs archive를 만들고 결과 커널 바이너리에 링크한다. 기본 archive는 비어 있으며 x86에서 134바이트를 차지한다.
`menuconfig`의 General Setup과 `usr/Kconfig`에 있는 `CONFIG_INITRAMFS_SOURCE`로 archive 원본을 지정할 수 있다. 기존 gzip cpio archive, 담을 파일이 있는 디렉터리, 또는 텍스트 명세 파일을 가리킬 수 있다.
dir /dev 755 0 0
nod /dev/console 644 0 0 c 5 1
nod /dev/loop0 644 0 0 b 7 0
dir /bin 755 1000 1000
slink /bin/sh busybox 777 0 0
file /bin/busybox initramfs/busybox 755 0 0
dir /proc 755 0 0
dir /sys 755 0 0
dir /mnt 755 0 0
file /init initramfs/init.sh 755 0 0
각 명세 행은 디렉터리, 장치 노드, 심볼릭 링크, 파일의 archive 경로와 mode·UID·GID, 필요한 장치 종류와 major/minor 또는 원본 파일을 지정한다. 커널 빌드 뒤 `usr/gen_init_cpio`를 인자 없이 실행하면 이 형식의 사용법이 나온다.
명세 파일의 장점은 새 archive에서 권한을 설정하거나 장치 노드를 만들 때 root 권한이 필요 없다는 것이다. 예시의 `file` 두 행은 커널 소스 트리 아래 `initramfs` 디렉터리에서 `init.sh`와 `busybox`를 찾는다. 자세한 내용은 `Documentation/driver-api/early-userspace/early_userspace_support.rst`를 참고한다.
커널은 외부 cpio 도구에 의존하지 않는다. 디렉터리를 지정하면 `usr/Makefile`이 `usr/gen_initramfs.sh`를 호출해 명세 파일을 만들고, `usr/gen_init_cpio.c`에서 빌드한 `usr/gen_init_cpio`에 이를 넣어 패키징한다. 빌드 시 생성기와 부팅 시 extractor 모두 자체 완결적이다.
외부 cpio 유틸리티가 필요한 경우는 명세나 디렉터리 대신 커널 빌드에 넣을 사전 생성 cpio 파일을 직접 만들거나 풀 때뿐이다.
cpio -i -d -H newc -F initramfs_data.cpio --no-absolute-filenames
위 명령은 스크립트 또는 커널 빌드가 만든 cpio 이미지를 구성 파일들로 다시 푼다.
#!/bin/sh
if [ $# -ne 2 ]
then
echo "usage: mkinitramfs directory imagename.cpio.gz"
exit 1
fi
if [ -d "$1" ]
then
echo "creating $2 from $1"
(cd "$1"; find . | cpio -o -H newc | gzip) > "$2"
else
echo "First argument must be a directory"
exit 1
fi
예시 스크립트는 디렉터리와 출력 `.cpio.gz` 이름을 받아 디렉터리 안에서 `find .` 결과를 `cpio -o -H newc`와 `gzip`으로 보낸다.
Cpio 매뉴얼이 일반 archive에 권하는 `find -depth`는 initramfs 생성에 사용하면 안 된다. 커널 cpio extractor는 아직 존재하지 않는 디렉터리 안에 파일을 만들지 않는다. 그러므로 디렉터리 엔트리가 그 안의 파일보다 먼저 archive에 나와야 하며, 위 스크립트는 이 순서를 지킨다.
입력 종류가 달라도 자체 생성기를 거쳐 내장 cpio archive가 된다.
Populating initramfs:
---------------------
The 2.6 kernel build process always creates a gzipped cpio format initramfs
archive and links it into the resulting kernel binary. By default, this
archive is empty (consuming 134 bytes on x86).
The config option CONFIG_INITRAMFS_SOURCE (in General Setup in menuconfig,
and living in usr/Kconfig) can be used to specify a source for the
initramfs archive, which will automatically be incorporated into the
resulting binary. This option can point to an existing gzipped cpio
archive, a directory containing files to be archived, or a text file
specification such as the following example::
dir /dev 755 0 0
nod /dev/console 644 0 0 c 5 1
nod /dev/loop0 644 0 0 b 7 0
dir /bin 755 1000 1000
slink /bin/sh busybox 777 0 0
file /bin/busybox initramfs/busybox 755 0 0
dir /proc 755 0 0
dir /sys 755 0 0
dir /mnt 755 0 0
file /init initramfs/init.sh 755 0 0
Run "usr/gen_init_cpio" (after the kernel build) to get a usage message
documenting the above file format.
One advantage of the configuration file is that root access is not required to
set permissions or create device nodes in the new archive. (Note that those
two example "file" entries expect to find files named "init.sh" and "busybox" in
a directory called "initramfs", under the linux-2.6.* directory. See
Documentation/driver-api/early-userspace/early_userspace_support.rst for more details.)
The kernel does not depend on external cpio tools. If you specify a
directory instead of a configuration file, the kernel's build infrastructure
creates a configuration file from that directory (usr/Makefile calls
usr/gen_initramfs.sh), and proceeds to package up that directory
using the config file (by feeding it to usr/gen_init_cpio, which is created
from usr/gen_init_cpio.c). The kernel's build-time cpio creation code is
entirely self-contained, and the kernel's boot-time extractor is also
(obviously) self-contained.
The one thing you might need external cpio utilities installed for is creating
or extracting your own preprepared cpio files to feed to the kernel build
(instead of a config file or directory).
The following command line can extract a cpio image (either by the above script
or by the kernel build) back into its component files::
cpio -i -d -H newc -F initramfs_data.cpio --no-absolute-filenames
The following shell script can create a prebuilt cpio archive you can
use in place of the above config file::
#!/bin/sh
# Copyright 2006 Rob Landley <rob@landley.net> and TimeSys Corporation.
# Licensed under GPL version 2
if [ $# -ne 2 ]
then
echo "usage: mkinitramfs directory imagename.cpio.gz"
exit 1
fi
if [ -d "$1" ]
then
echo "creating $2 from $1"
(cd "$1"; find . | cpio -o -H newc | gzip) > "$2"
else
echo "First argument must be a directory"
exit 1
fi
.. Note::
The cpio man page contains some bad advice that will break your initramfs
archive if you follow it. It says "A typical way to generate the list
of filenames is with the find command; you should give find the -depth
option to minimize problems with permissions on directories that are
unwritable or not searchable." Don't do this when creating
initramfs.cpio.gz images, it won't work. The Linux kernel cpio extractor
won't create files in a directory that doesn't exist, so the directory
entries must go before the files that go in those directories.
The above script gets them in the right order.
외부 initramfs 이미지
222-239커널에서 initrd 지원을 켜면 initrd 대신 외부 `cpio.gz` archive를 2.6 커널에 전달할 수 있다. 커널은 형식을 initrd가 아니라 initramfs로 자동 감지하고 `/init` 실행을 시도하기 전에 외부 cpio를 rootfs에 푼다.
이 방식은 ramdisk 블록 장치가 필요 없는 initramfs의 메모리 효율과 initrd의 별도 패키징 장점을 함께 가진다. GPL Linux 커널 바이너리와 합치지 않고 initramfs에서 비GPL 코드를 실행하려는 경우에도 유용하다.
외부 archive는 커널 내장 initramfs를 보충할 수도 있다. 외부 archive의 파일은 내장 archive와 충돌하는 파일을 덮어쓴다. 배포판은 커널을 다시 컴파일하지 않고 하나의 커널 이미지에 작업별 initramfs 이미지를 조합할 수 있다.
외부 archive가 뒤에 적용되어 같은 경로를 덮어쓴다.
External initramfs images:
--------------------------
If the kernel has initrd support enabled, an external cpio.gz archive can also
be passed into a 2.6 kernel in place of an initrd. In this case, the kernel
will autodetect the type (initramfs, not initrd) and extract the external cpio
archive into rootfs before trying to run /init.
This has the memory efficiency advantages of initramfs (no ramdisk block
device) but the separate packaging of initrd (which is nice if you have
non-GPL code you'd like to run from initramfs, without conflating it with
the GPL licensed Linux kernel binary).
It can also be used to supplement the kernel's built-in initramfs image. The
files in the external archive will overwrite any conflicting files in
the built-in initramfs archive. Some distributors also prefer to customize
a single kernel image with task-specific initramfs images, without recompiling.
초기 userspace 구성과 시험
240-287Initramfs archive는 Linux를 위한 완전하고 자체 완결적인 root 파일시스템이다. 최소 root를 기동하려면 필요한 공유 라이브러리·장치·경로를 모두 포함해야 한다. 원문은 Bootdisk HOWTO, From PowerUp To Bash Prompt HOWTO, Linux From Scratch를 참고 자료로 제시한다.
`klibc`는 early userspace 코드를 정적으로 링크하기 위한 작은 BSD 라이선스 C 라이브러리와 관련 도구다. 저자는 LGPL의 uClibc와 GPL의 BusyBox도 사용한다고 설명한다.
이론상 glibc도 가능하지만 작은 임베디드 환경에는 부적합하다. 정적 링크한 hello world가 glibc에서는 400KB를 넘지만 uClibc에서는 7KB이며, glibc는 그 밖의 부분을 정적으로 링크해도 이름 조회를 위해 `libnss`를 `dlopen`한다.
좋은 첫 단계는 정적 링크한 hello world를 init으로 실행하는 initramfs를 만들고 QEMU나 User Mode Linux 같은 에뮬레이터에서 시험하는 것이다.
cat > hello.c << EOF
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("Hello world!\n");
sleep(999999999);
}
EOF
gcc -static hello.c -o init
echo init | cpio -o -H newc | gzip > test.cpio.gz
qemu -kernel /boot/vmlinuz -initrd test.cpio.gz /dev/zero
일반 root 파일시스템 디버깅에서 `init=/bin/sh`로 부팅하는 것처럼 initramfs에서는 `rdinit=/bin/sh`를 사용한다.
PID 1이 종료되지 않는 정적 프로그램부터 시작해 archive와 로더를 검증한다.
Contents of initramfs:
----------------------
An initramfs archive is a complete self-contained root filesystem for Linux.
If you don't already understand what shared libraries, devices, and paths
you need to get a minimal root filesystem up and running, here are some
references:
- https://www.tldp.org/HOWTO/Bootdisk-HOWTO/
- https://www.tldp.org/HOWTO/From-PowerUp-To-Bash-Prompt-HOWTO.html
- http://www.linuxfromscratch.org/lfs/view/stable/
The "klibc" package (https://www.kernel.org/pub/linux/libs/klibc) is
designed to be a tiny C library to statically link early userspace
code against, along with some related utilities. It is BSD licensed.
I use uClibc (https://www.uclibc.org) and busybox (https://www.busybox.net)
myself. These are LGPL and GPL, respectively. (A self-contained initramfs
package is planned for the busybox 1.3 release.)
In theory you could use glibc, but that's not well suited for small embedded
uses like this. (A "hello world" program statically linked against glibc is
over 400k. With uClibc it's 7k. Also note that glibc dlopens libnss to do
name lookups, even when otherwise statically linked.)
A good first step is to get initramfs to run a statically linked "hello world"
program as init, and test it under an emulator like qemu (www.qemu.org) or
User Mode Linux, like so::
cat > hello.c << EOF
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("Hello world!\n");
sleep(999999999);
}
EOF
gcc -static hello.c -o init
echo init | cpio -o -H newc | gzip > test.cpio.gz
# Testing external initramfs using the initrd loading mechanism.
qemu -kernel /boot/vmlinuz -initrd test.cpio.gz /dev/zero
When debugging a normal root filesystem, it's nice to be able to boot with
"init=/bin/sh". The initramfs equivalent is "rdinit=/bin/sh", and it's
just as useful.
Tar 대신 cpio를 선택한 이유
288-342이 결정은 2001년 12월에 이뤄졌다. 원문은 초기 논의와 tar 대 cpio 전용 후속 스레드 링크를 제공하며, 아래 요약보다 원 토론을 읽는 편이 낫다고 밝힌다.
첫째, cpio는 AT&T 시절부터 수십 년 된 표준이며 RPM과 Red Hat 장치 드라이버 디스크 등 Linux에서 이미 널리 쓰였다. 전통적인 cpio 명령줄이 매우 불편해 tar보다 덜 인기 있지만, 이는 archive 형식 자체의 품질과는 별개다. `afio` 같은 대체 도구도 있다.
둘째, 커널이 선택한 cpio archive 형식은 수십 가지 tar 변형보다 단순하고 깨끗해 생성과 파싱이 쉽다. 전체 형식은 `buffer-format.rst`에 설명되고 `usr/gen_init_cpio.c`가 만들며 `init/initramfs.c`가 푼다. 세 파일의 사람이 읽을 수 있는 텍스트를 모두 합쳐도 26KB보다 작다.
셋째, GNU가 tar를 표준화한 사실은 Windows가 zip을 표준화한 것과 비슷하게 Linux의 기술 선택을 구속하지 않는다.
넷째, 커널 내부 형식이므로 완전히 새 형식도 만들 수 있었고 커널은 어차피 생성·추출 도구를 자체 제공한다. 기존 표준을 사용하는 편이 낫지만 필수 조건은 아니었다.
다섯째, Al Viro가 커널 쪽에서 tar를 지원하지 않기로 결정하고 그 이유를 설명했으며, 무엇보다 initramfs 코드를 설계하고 구현했다. 원문은 결정과 근거가 담긴 메일 링크를 보존한다.
역사적 친숙함보다 커널 내부 형식의 단순성과 자체 완결성이 핵심이다.
Why cpio rather than tar?
-------------------------
This decision was made back in December, 2001. The discussion started here:
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1538.html
And spawned a second thread (specifically on tar vs cpio), starting here:
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1587.html
The quick and dirty summary version (which is no substitute for reading
the above threads) is:
1) cpio is a standard. It's decades old (from the AT&T days), and already
widely used on Linux (inside RPM, Red Hat's device driver disks). Here's
a Linux Journal article about it from 1996:
http://www.linuxjournal.com/article/1213
It's not as popular as tar because the traditional cpio command line tools
require _truly_hideous_ command line arguments. But that says nothing
either way about the archive format, and there are alternative tools,
such as:
http://freecode.com/projects/afio
2) The cpio archive format chosen by the kernel is simpler and cleaner (and
thus easier to create and parse) than any of the (literally dozens of)
various tar archive formats. The complete initramfs archive format is
explained in buffer-format.rst, created in usr/gen_init_cpio.c, and
extracted in init/initramfs.c. All three together come to less than 26k
total of human-readable text.
3) The GNU project standardizing on tar is approximately as relevant as
Windows standardizing on zip. Linux is not part of either, and is free
to make its own technical decisions.
4) Since this is a kernel internal format, it could easily have been
something brand new. The kernel provides its own tools to create and
extract this format anyway. Using an existing standard was preferable,
but not essential.
5) Al Viro made the decision (quote: "tar is ugly as hell and not going to be
supported on the kernel side"):
http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1540.html
explained his reasoning:
- http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1550.html
- http://www.uwsg.iu.edu/hypermail/linux/kernel/0112.2/1638.html
and, most importantly, designed and implemented the initramfs code.
Early userspace로의 이동
343-368문서 작성 당시인 2.6.16에서 initramfs는 항상 컴파일되지만 항상 사용되지는 않았다. Initramfs에 `/init`이 없을 때만 legacy 부팅 코드로 떨어졌다. 이 fallback은 전환을 부드럽게 하고 초기 부팅 기능을 점차 early userspace, 즉 initramfs로 옮기기 위한 호환 코드였다.
실제 root 장치를 찾고 마운트하는 과정은 복잡하므로 early userspace로 옮길 필요가 있다. Root partition은 RAID나 별도 journal처럼 여러 장치에 걸칠 수 있고, DHCP·MAC 주소 설정·서버 로그인이 필요한 네트워크에 있을 수 있다.
이동식 매체는 동적 major/minor 번호와 지속적 이름 지정 문제 때문에 완전한 udev 구현이 필요할 수 있다. Root는 압축·암호화·copy-on-write·loopback·특이한 partition 형식일 수도 있다.
필연적으로 정책을 포함하는 이런 복잡성은 userspace에서 처리하는 것이 맞다. Klibc와 BusyBox/uClibc 모두 커널 빌드에 넣을 단순 initramfs 패키지를 개발하고 있었다.
Klibc는 당시 Andrew Morton의 2.6.17-mm 트리에 받아들여졌다. 문서는 partition 감지 같은 커널의 기존 초기 부팅 코드가 앞으로 빌드가 자동 생성·사용하는 기본 initramfs로 이동할 가능성을 전망한다.
하드웨어 탐지와 root 조립의 복잡성을 커널에서 초기 userspace로 옮긴다.
Future directions:
------------------
Today (2.6.16), initramfs is always compiled in, but not always used. The
kernel falls back to legacy boot code that is reached only if initramfs does
not contain an /init program. The fallback is legacy code, there to ensure a
smooth transition and allowing early boot functionality to gradually move to
"early userspace" (I.E. initramfs).
The move to early userspace is necessary because finding and mounting the real
root device is complex. Root partitions can span multiple devices (raid or
separate journal). They can be out on the network (requiring dhcp, setting a
specific MAC address, logging into a server, etc). They can live on removable
media, with dynamically allocated major/minor numbers and persistent naming
issues requiring a full udev implementation to sort out. They can be
compressed, encrypted, copy-on-write, loopback mounted, strangely partitioned,
and so on.
This kind of complexity (which inevitably includes policy) is rightly handled
in userspace. Both klibc and busybox/uClibc are working on simple initramfs
packages to drop into a kernel build.
The klibc package has now been accepted into Andrew Morton's 2.6.17-mm tree.
The kernel's current early boot code (partition detection, etc) will probably
be migrated into a default initramfs, automatically created and used by the
kernel build.
요약·해설
ramfs-rootfs-initramfs.rst:1-368Ramfs는 page cache와 dentry cache 자체를 파일시스템으로 노출한다. Backing store가 없어 page를 clean 상태로 바꿀 수 없으므로 VM이 회수하지 못하며, 크기 제한과 swap 지원이 필요한 일반 용도에는 tmpfs가 더 안전하다.
Rootfs는 비어 있는 커널 목록을 피하기 위해 항상 존재하는 ramfs 또는 tmpfs 인스턴스다. Initramfs는 gzip cpio archive를 rootfs에 풀고 `/init`을 PID 1로 실행해 복잡한 root 장치 탐색과 정책을 early userspace로 옮긴다.
Archive 구성에서는 디렉터리가 내부 파일보다 먼저 나와야 한다. 루트 전환에서는 rootfs를 언마운트하거나 `pivot_root`할 수 없으므로 내용을 지우고 새 root를 이동 마운트한 뒤 `switch_root` 방식으로 init을 exec해야 한다.
캐시 기반 저장과 early userspace 부팅이 하나의 흐름으로 이어진다.