読者です 読者をやめる 読者になる 読者になる

TenForward

技術ブログ。はてなダイアリーから移転しました

setns を UTS Namespace をネタにおっかける

masami256 さんのコンテナシリーズ、勉強になりますね。Linux カーネルの Namespace (名前空間) を扱うシステムコールのうち、既存のプロセスの Namespace を制御する unshare(2) と setns(2) の処理の解説ですね。

このエントリを元に私も setns を追ってみました。

kernel/nsproxy.c に setns が定義されています。

SYSCALL_DEFINE2(setns, int, fd, int, nstype)
{
	const struct proc_ns_operations *ops;
	struct task_struct *tsk = current;
	struct nsproxy *new_nsproxy;
	struct proc_ns *ei;
	struct file *file;
	int err;
    :

setns は man setns すると、

NAME
       setns - reassociate thread with a namespace

SYNOPSIS
       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <sched.h>

       int setns(int fd, int nstype);

とあるようにファイルディスクリプタ (fd) と Namespace のタイプを渡します。これは私の以前の発表資料なんかでも軽く触れています。

ここの fd って何だったでしょう? これは 3.8 kernel で導入された /proc/PID/ns 以下のそれぞれの Namespace を表す特殊なシンボリックリンクの fd でした。これは以前の私のブログで紹介しています。

(3.8 ってもう 1 年以上前なのね…)

setns の実装に戻って…、まずはこの fd から file 構造体を取得しています。

	file = proc_ns_fget(fd);
	if (IS_ERR(file))
		return PTR_ERR(file);

file から inode を求めて、proc_inode (proc の inode 構造体) を取得してから、proc_ns 構造体を取得しています。

	err = -EINVAL;
	ei = get_proc_ns(file_inode(file));

ちなみに proc_ns 構造体は include/linux/proc_ns.h のこの辺りにあります。プロセスの Namespace と、その Namespace の操作を表すものですかね? (自信なし) これで、該当の Namespace で行いたい処理を行う関数が取得できるみたいですね。

struct proc_ns_operations {
	const char *name;
	int type;
	void *(*get)(struct task_struct *task);
	void (*put)(void *ns);
	int (*install)(struct nsproxy *nsproxy, void *ns);
	unsigned int (*inum)(void *ns);
};

struct proc_ns {
	void *ns;
	const struct proc_ns_operations *ns_ops;
};

extern const struct proc_ns_operations netns_operations;
extern const struct proc_ns_operations utsns_operations;
extern const struct proc_ns_operations ipcns_operations;
extern const struct proc_ns_operations pidns_operations;
extern const struct proc_ns_operations userns_operations;
extern const struct proc_ns_operations mntns_operations;

例えば先のブログエントリでも使った UTS Namespace だと kernel/utsname.c 内で

const struct proc_ns_operations utsns_operations = {
	.name		= "uts",
	.type		= CLONE_NEWUTS,
	.get		= utsns_get,
	.put		= utsns_put,
	.install	= utsns_install,
	.inum		= utsns_inum,
};

と定義されてます。

さて setns に戻って、ei は proc_ns 構造体ですから、setns に与えた fd が UTS Namespace を表している場合は ops は utsns_operations が入りますので、ops->type は CLONE_NEWUTS となりますね。これと setns で与えた Namespace のタイプが合致するかをチェックしていますね。fd が UTS Namespace 以外なのに CLONE_NEWUTS を指定したりするとエラーになります。

	ops = ei->ns_ops;
	if (nstype && (ops->type != nstype))
		goto out;

create_new_namespace の解説は『Linux:実行中プロセスの名前空間切り替え - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ』にお任せして、new_nsproxy を取得した後は、proc_ns_oprations 構造体で定義されている install を呼んでいます。

	new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs);
	if (IS_ERR(new_nsproxy)) {
		err = PTR_ERR(new_nsproxy);
		goto out;
	}

	err = ops->install(new_nsproxy, ei->ns);
	if (err) {
		free_nsproxy(new_nsproxy);
		goto out;
	}

この install ですが、UTS Namespace の場合は先の定義にあるように utsns_install という関数です。これは kernel/utsname.c にあって、こんなのです。

static int utsns_install(struct nsproxy *nsproxy, void *new)
{
	struct uts_namespace *ns = new;

	if (!ns_capable(ns->user_ns, CAP_SYS_ADMIN) ||
	    !ns_capable(current_user_ns(), CAP_SYS_ADMIN))
		return -EPERM;

	get_uts_ns(ns);
	put_uts_ns(nsproxy->uts_ns);
	nsproxy->uts_ns = ns;
	return 0;
}

new には ei->ns が代入されていますので、つまり指定した fd が示す Namespace ですね。これを nsproxy->uts_ns に入れていますので、つまりは fd が示す Namespace をタスクの Namespace にセットしているということになります。

ちなみに get_uts_ns は与えたオブジェクトに対する参照カウンタをインクリメントするもので、put_uts_ns は逆にデクリメントするもののようですね。Namespace を参照するものがなくなれば Namespace は消滅しますから、そのためのカウンタの操作でしょうか。

switch_task_namespace についてはやっぱり『Linux:実行中プロセスの名前空間切り替え - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ』をどうぞ。

結局、私がここで書いた所は枝葉のような処理ばかりでしたが (なので Linux:実行中プロセスの名前空間切り替え - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ には書かれてないんですね ;-)、すっきりしました。