ext4_file_open()の226行目のif文内を実行するのはどんな時なんだろう?と思って調べてみたらinit処理に関係している部分だったのでinitramfsのinitを読んでみた。
218 static int ext4_file_open(struct inode * inode, struct file * filp) 219 { 220 struct super_block *sb = inode->i_sb; 221 struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); 222 struct vfsmount *mnt = filp->f_path.mnt; 223 struct path path; 224 char buf[64], *cp; 225 226 if (unlikely(!(sbi->s_mount_flags & EXT4_MF_MNTDIR_SAMPLED) && 227 !(sb->s_flags & MS_RDONLY))) { 228 sbi->s_mount_flags |= EXT4_MF_MNTDIR_SAMPLED;
if文内にprintk()を追加してカーネルの再コンパイルと再起動。
if (unlikely(!(sbi->s_mount_flags & EXT4_MF_MNTDIR_SAMPLED) && !(sb->s_flags & MS_RDONLY))) { + pr_info("[-]AAA: pid:%d %s\n", current->pid, current->comm); + pr_info("[-]AAA: %s\n", filp->f_path.dentry->d_name.name); sbi->s_mount_flags |= EXT4_MF_MNTDIR_SAMPLED;
起動したらdmesgで確認。
masami@kerntest:~/linux-kernel/fs/ext4 (ktest)$ dmesg | grep AAA [ 0.702590] [-]AAA: pid:1 switch_root [ 0.702592] [-]AAA: systemd
テストした環境はパーティションは/だけでファイルシステムはext4。ここが実行されたのは1度だけでプロセスはswitch_root、オープンしようとしたファイルはsystemd。ここのifの条件はスーパーブロックのマウントフラグを見ているのでext4のパーティションが複数ある場合、そのパーティションにあるファイルを最初に(書き込み可で)開くタイミングで実行されるんでしょうね。2回目はEXT4_MF_MNTDIR_SAMPLEDビットが立っているから条件は成立しないし。
では、initramfsにあるinitの主な処理を見てみよう。Arch LinuxのinitramfsはArch Linux独自のmkinitcpioというコマンドで作られてます。mkinitcpioで作られたinitramfsはcpioのアーカイブをgzipしたものなので以下のように展開。
$ gzip -dc /boot/initramfs-linux.img | cpio -iv
展開後のディレクトリ構成は以下のような形。
masami@saga:~/tmp/initramfs$ ls -la total 76 drwxr-xr-x 11 masami masami 4096 Jan 6 11:40 . drwxr-xr-x 3 masami masami 4096 Jan 6 00:26 .. lrwxrwxrwx 1 masami masami 7 Jan 6 00:24 bin -> usr/bin -rw-r--r-- 1 masami masami 2490 Jan 6 00:24 buildconfig -rw-r--r-- 1 masami masami 64 Jan 6 00:24 config drwxr-xr-x 2 masami masami 4096 Jan 6 00:24 dev drwxr-xr-x 4 masami masami 4096 Jan 6 00:24 etc drwxr-xr-x 2 masami masami 4096 Jan 6 00:24 hooks -rwxr-xr-x 1 masami masami 2385 Jan 6 00:24 init -rw-r--r-- 1 masami masami 12469 Jan 6 00:24 init_functions lrwxrwxrwx 1 masami masami 7 Jan 6 00:24 lib -> usr/lib lrwxrwxrwx 1 masami masami 7 Jan 6 00:24 lib64 -> usr/lib drwxr-xr-x 2 masami masami 4096 Jan 6 00:24 new_root drwxr-xr-x 2 masami masami 4096 Jan 6 00:24 proc drwxr-xr-x 2 masami masami 4096 Jan 6 00:24 run lrwxrwxrwx 1 masami masami 7 Jan 6 00:24 sbin -> usr/bin drwxr-xr-x 2 masami masami 4096 Jan 6 00:24 sys drwxr-xr-x 2 masami masami 4096 Jan 6 00:24 tmp drwxr-xr-x 5 masami masami 4096 Jan 6 00:24 usr -rw-r--r-- 1 masami masami 2 Jan 6 00:24 VERSION
initが本体でinit_functionsがサポートライブラリでどちらもシェルスクリプト。シェルは/usr/bin/ashで実体はbusyboxです。
最初に実行するのはこなんだけど、./usr/bin/にはsystemd-timestampがないので今は実行されていない。
if [ -x /usr/bin/systemd-timestamp ]; then RD_TIMESTAMP=$(systemd-timestamp) fi
擬似ファイルシステム、tmpfs等のマウント。
mount -t proc proc /proc -o nosuid,noexec,nodev mount -t sysfs sys /sys -o nosuid,noexec,nodev mount -t devtmpfs dev /dev -o mode=0755,nosuid mount -t tmpfs run /run -o nosuid,nodev,mode=0755
カーネルコマンドラインの読み込み。
parse_cmdline </proc/cmdline
/hookにあるファイルのうち実行したくないファイルから実行権限を外す。
for d in ${disablehooks//,/ }; do [ -e "/hooks/$d" ] && chmod 644 "/hooks/$d" done
このdisablehooksはカンマ区切りの文字列でカーネルのコマンドラインで渡すことができる。
今あるhookはudebだけだった。
masami@saga:~/tmp/initramfs$ ls hooks/ . .. udev
hookを実行。
run_hookfunctions 'run_earlyhook' 'early hook' $EARLYHOOKS
$EARLYHOOKSは/configで定義されている変数。
EARLYHOOKS="udev" HOOKS="udev" LATEHOOKS="" CLEANUPHOOKS="udev"
カーネルモジュールの読み込み。ここではカーネルコマンドラインでearlymodules=foo,barのように渡されたものが対象。
if [ -n "$earlymodules$MODULES" ]; then modprobe -qab ${earlymodules//,/ } $MODULES fi
再度hookの実行。
run_hookfunctions 'run_hook' 'hook' $HOOKS
rootデバイスの設定。
rootdev=$(resolve_device "$root") && root=$rootdev unset rootdev
必要ならfsck。
fsck_root
本当の/になる/new_rootをマウント。
# Mount root at /new_root "$mount_handler" /new_root
$mount_handlerはデフォルトではdefault_mount_handler()が設定されている。
default_mount_handlerの処理は対象がブロックデバイスか調べるのと、ブロックデバイスだったらマウントする。
マウントに失敗した場合とかマウント後に何か処理をしたいような場合にシェルを立ち上げる処理が入るけどそこは飛ばして、最後にルートファイルシステムの切り替え。
exec env -i \ "TERM=$TERM" \ /usr/bin/switch_root /new_root $init "$@"
switch_rootはutil-linuxにあるコマンド。$initはinitスクリプトの最初のほうで以下のように定義済み。
init=/sbin/init
/sbin/initは新しい/では以下のようにsystemdを指す。
masami@saga:~/tmp/initramfs$ ls -la /sbin/init lrwxrwxrwx 1 root root 22 Dec 15 22:56 /sbin/init -> ../lib/systemd/systemd
これでext4_file_open()のif文内実行時のプロセスがswitch_rootだった理由が見えたので次はオープンしようとしたファイルがsystemdだったのはなんでかというのを見てみる。
これにはutli-linuxのswitch_root.cを見る必要がある。
int main(int argc, char *argv[]) { char *newroot, *init, **initargs; atexit(close_stdout); if (argv[1] && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) usage(stdout); if (argv[1] && (!strcmp(argv[1], "--version") || !strcmp(argv[1], "-V"))) { printf(UTIL_LINUX_VERSION); return EXIT_SUCCESS; } if (argc < 3) usage(stderr); newroot = argv[1]; init = argv[2]; initargs = &argv[2]; if (!*newroot || !*init) usage(stderr); if (switchroot(newroot)) errx(EXIT_FAILURE, _("failed. Sorry.")); if (access(init, X_OK)) warn(_("cannot access %s"), init); execv(init, initargs); err(EXIT_FAILURE, _("failed to execute %s"), init); }
なるほど、execv()で/sbin/initをオープンしているからですね。
ついでの実験。ext4_file_open()に以下を追加。
+ if (!strcmp("a.out", current->comm)) + pr_info("[-]: %s\n", filp->f_path.dentry->d_name.name);
これをコンパイルして実行。
#include <unistd.h> int main(int argc, char **argv) { char *path = "/bin/ls"; char *av[] = { "ls", "/", NULL }; execve(path, av, NULL); return 0; }
dmesgで確認。
[ 13.647315] [-]: ld.so.cache [ 13.647336] [-]: libc-2.18.so [ 13.647512] [-]: ls [ 13.647708] [-]: ld-2.18.so
このa.outの実行時に開かれるファイルは4ファイル。
Linuxカーネル Hacks ―パフォーマンス改善、開発効率向上、省電力化のためのテクニック
- 作者: 池田宗広,大岩尚宏,島本裕志,竹部晶雄,平松雅巳,高橋浩和
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/07/26
- メディア: 単行本(ソフトカバー)
- 購入: 4人 クリック: 50回
- この商品を含むブログ (4件) を見る