unameはどこから情報を取得しているのか?

2022/09/26に公開

unameを実行するとカーネル情報を表示してくれますが、どこから情報を取得しているのでしょうか?

$ uname -r
6.0.0-rc7

init/version.cにあるグローバル変数init_uts_nsから取得しています。

init/version.c
struct uts_namespace init_uts_ns = {
	.ns.count = REFCOUNT_INIT(2),
	.name = {
		.sysname	= UTS_SYSNAME,
		.nodename	= UTS_NODENAME,
		.release	= UTS_RELEASE,
		.version	= UTS_VERSION,
		.machine	= UTS_MACHINE,
		.domainname	= UTS_DOMAINNAME,
	},
	.user_ns = &init_user_ns,
	.ns.inum = PROC_UTS_INIT_INO,
};

各プロセスはtask_struct構造体の中にinit_uts_nsのアドレスを持っていて、
init_uts_ns.name.releaseのように情報を取得しています。
init_uts_nsのアドレスはsystem.Mapにあります。

system.Map
ffffffff8281a750 D loops_per_jiffy
ffffffff8281a760 D envp_init
ffffffff8281a880 d argv_init
ffffffff8281a990 d ramdisk_execute_command
ffffffff8281a9a0 D init_uts_ns
ffffffff8281aba0 D rootfs_fs_type
ffffffff8281abe8 D root_mountflags
ffffffff8281ac00 d kern_do_mounts_initrd_table
ffffffff8281adc0 d initramfs_domain

init_uts_nsのアドレスはffffffff8281a9a0であることがわかります。
Dはグローバル変数の意味です。
https://zenn.dev/linux/articles/3adfe0e6856634

gdbでデバッグしましょう。

gdb
(gdb) p init_uts_ns
$1 = {name = {sysname = "Linux", '\000' <repeats 59 times>, 
    nodename = "(none)", '\000' <repeats 58 times>, 
    release = "6.0.0-rc7", '\000' <repeats 55 times>, 
    version = "#2 SMP PREEMPT_DYNAMIC Tue Oct 4 01:53:58 JST 2022", '\000' <repeats 14 times>, machine = "x86_64", '\000' <repeats 58 times>, 
    domainname = "(none)", '\000' <repeats 58 times>}, 
  user_ns = 0xffffffff82878e60 <init_user_ns>, ucounts = 0x0 <fixed_percpu_data>, ns = {
    stashed = {counter = 0}, ops = 0xffffffff82228dc0 <utsns_operations>, 
    inum = 4026531838, count = {refs = {counter = 3}}}}
    
(gdb) p init_uts_ns.name.release
$2 = "6.0.0-rc7", '\000' <repeats 55 times>

(gdb) p init_uts_ns.name.sysname
$3 = "Linux", '\000' <repeats 59 times>

(gdb) p &init_uts_ns
$5 = (struct uts_namespace *) 0xffffffff8281a9a0 <init_uts_ns>

(gdb) x 0xffffffff8281a9a0
0xffffffff8281a9a0 <init_uts_ns>:	0x756e694c

(gdb) x/s 0xffffffff8281a9a0
0xffffffff8281a9a0 <init_uts_ns>:	"Linux"

(gdb) p &init_uts_ns.name.release
$7 = (char (*)[65]) 0xffffffff8281aa22 <init_uts_ns+130>

(gdb) x/s 0xffffffff8281aa22
0xffffffff8281aa22 <init_uts_ns+130>:	"6.0.0-rc7"

構造体を表示したり、変数からアドレスを取得、アドレス指定で表示をしています。
変数指定する場合にはp, アドレス指定する場合にはxで表示します。
system.Mapの情報を合致していますね。

次はシステムコール内の処理を見ていきましょう。

kernel/sys.c
SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name)
{
        struct new_utsname tmp;
        memcpy(&tmp, utsname(), sizeof(tmp));
        return 0;
}

SYSCALL_DEFINE1は
__x64_sys_newuname __se_sys_newuname __do_sys_newuname
3つの関数へマクロ展開されます。

utsname()をたどると、this_cpu_read_stable()にたどり着きます。

arch/x86/asm/include/current.h
static __always_inline struct task_struct *get_current(void)
{
        return this_cpu_read_stable(current_task);
}

this_cpu_read_stableはマクロなので展開してみます。

(gdb) p this_cpu_read_stable(current_task)
asm("mov" "q " "%%""gs"":" "%" "P[var]" ", " "%[val]" : [val] "=" "r" (pfo_val__) : [var] "p" (&(current_task)));

拡張インラインアセンブラなのですが、人間用にわかりやすく変換します。
asm("movq %%gs:%P[var], %[val]"
:[val]=r(pfo_val__)
:[var]p(&(current_task)));

X86 PerCPUの可変ベースアドレス(gsレジスタ)の原理。
X86では、mov %gs:varという簡単なアセンブリ命令で、現在のCPUのpercpu変数の値を非常に効果的に取り出しています。
この一文で(task_struct *)current_taskのアドレスを取り出しています。
このcurrent_taskの中にunameの情報が入っているわけですね。

★ unameシステムコールで%gsレジスタ取得
アドレス表示
そもそもcurrent_taskのアドレスは変化しているのか?
そのままpfo_val__ = current_taskではダメなのか?
ここでper_cpuでcurrent_taskを取得するとどうなるのか?

per_cpu変数をそのまま取得するとper_cpu(current_task, get_cpu())と同じ
意味なのか?

Discussion