kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

cgroup文件系统

http://www.cnblogs.com/lisperl/archive/2012/04/23/2466151.html

Cgroups用户空间管理

Cgroups用户空间的管理是通过cgroup文件系统实现的。

比如要创建一个层级:

1
mount -t cgroup -o cpu,cpuset,memory cpu_and_mem /cgroup/cpu_and_mem

这个命令就创建一个名为cpu_and_mem的层级,这个层级上附加了cpu,cpuset,memory三个子系统,并把层级挂载到了/cgroup/cpu_and_mem.

创建一个cgroup:

1
2
cd /cgroup/cpu_and_mem
mkdir foo

通过以上两个命令,我们就在刚才创建的层级下创建了一个叫foo的cgroup。

你再cd foo,然后ls

你会发现一些文件,这是cgroups相关子系统的控制文件,你可以读取这些控制文件,这些控制文件存储的值就是对相应的cgrouop的控制信息,你也可以写控制文件来更改控制信息。 在这些文件中,有一个叫tasks的文件,里面的包含了所有属于这个cgroup的进程的进程号。

在刚才创建的foo下,你cat tasks,应该是空的,因为此时这个cgroup里面还没有进程。你cd /cgroup/cpu_and_mem 再cat tasks,你可以看到系统中所有进程的进程号,这是因为每创建一个层级的时候,系统的所有进程都会自动被加到该层级的根cgroup里面。Tasks文件不仅可以读,还可以写,你将一个进程的进程号写入到某个cgroup目录下的tasks里面,你就将这个进程加入了相应的cgroup。

Cgroup文件系统的实现

在讲cgroup文件系统的实现之前,必须简单的介绍一下Linux VFS。

VFS是所谓的虚拟文件系统转换,是一个内核软件层,用来处理与Unix标准文件系统的所有系统调用。VFS对用户提供统一的读写等文件操作调用接口,当用户调用读写等函数时,内核则调用特定的文件系统实现。具体而言,文件在内核内存中是一个file数据结构来表示的。这个数据结构包含一个f_op的字段,该字段中包含了一组指向特定文件系统实现的函数指针。当用户执行read()操作时,内核调用sys_read(),然后sys_read()查找到指向该文件属于的文件系统的读函数指针,并调用它,即file->f_op->read().

VFS其实是面向对象的,在这里,对象是一个软件结构,既定义数据也定义了之上的操作。处于效率,Linux并没有采用C++之类的面向对象的语言,而是采用了C的结构体,然后在结构体里面定义了一系列函数指针,这些函数指针对应于对象的方法。

VFS文件系统定义了以下对象模型:
超级块对象(superblock object) 存放已安装文件系统的有关信息。 索引节点对象(inode object) 存放关于具体文件的一般信息。 文件对象(file object) 存放打开文件与进程之间的交互信息 目录项对象(dentry object) 存放目录项与对应文件进行链接的有关信息。

基于VFS实现的文件系统,都必须实现定义这些对象,并实现这些对象中定义的函数指针。cgroup文件系统也不例外,下面我们来看cgroups中这些对象的定义。

cgroup文件系统的定义:

1
2
3
4
5
static struct file_system_type cgroup_fs_type = {
	.name = "cgroup",
	.get_sb = cgroup_get_sb,
	.kill_sb = cgroup_kill_sb,
};

这里有定义了两个函数指针,定义了一个文件系统必须实现了的两个操作get_sb,kill_sb,即获得超级块和释放超级块。这两个操作会在使用mount系统调用挂载cgroup文件系统时使用。

cgroup 超级块的定义:

1
2
3
4
5
6
static const struct super_operations cgroup_ops = {
	.statfs = simple_statfs,
	.drop_inode = generic_delete_inode,
	.show_options = cgroup_show_options,
	.remount_fs = cgroup_remount,
};

Cgroup 索引块定义:

1
2
3
4
5
6
static const struct inode_operations cgroup_dir_inode_operations = {
	.lookup = simple_lookup,
	.mkdir = cgroup_mkdir,
	.rmdir = cgroup_rmdir,
	.rename = cgroup_rename,
};

在cgroup文件系统中,使用mkdir创建cgroup或者用rmdir删除cgroup时,就会调用相应的函数指针指向的函数。比如:使用mkdir创建cgroup时,会调用cgroup_mkdir,然后在cgroup_mkdir中再调用具体实现的cgroup_create函数。

Cgroup 文件操作定义:

1
2
3
4
5
6
7
static const struct file_operations cgroup_file_operations = {
	.read = cgroup_file_read,
	.write = cgroup_file_write,
	.llseek = generic_file_llseek,
	.open = cgroup_file_open,
	.release = cgroup_file_release,
};

在cgroup文件系统中,对目录下的控制文件进行操作时,会调用该结构体中指针指向的函数。比如:对文件进行读操作时,会调用cgroup_file_read,在cgroup_file_read中,会根据需要调用该文件对应的cftype结构体定义的对应读函数。

我们再来看cgroup文件系统中的cgroups控制文件。Cgroups定义一个cftype的结构体来管理控制文件。下面我们来看cftype的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct cftype {
	char name[MAX_CFTYPE_NAME];
	int private; /*
	mode_t mode;
	size_t max_write_len;
 
	int (*open)(struct inode *inode, struct file *file);
	ssize_t (*read)(struct cgroup *cgrp, struct cftype *cft,
	struct file *file,
	char __user *buf, size_t nbytes, loff_t *ppos);
	u64 (*read_u64)(struct cgroup *cgrp, struct cftype *cft);
	s64 (*read_s64)(struct cgroup *cgrp, struct cftype *cft);
	int (*read_map)(struct cgroup *cont, struct cftype *cft,
	struct cgroup_map_cb *cb);
	int (*read_seq_string)(struct cgroup *cont, struct cftype *cft,
				struct seq_file *m);
 
	ssize_t (*write)(struct cgroup *cgrp, struct cftype *cft,
				struct file *file,
				const char __user *buf, size_t nbytes, loff_t *ppos);
	int (*write_u64)(struct cgroup *cgrp, struct cftype *cft, u64 val);
	int (*write_s64)(struct cgroup *cgrp, struct cftype *cft, s64 val);
	int (*write_string)(struct cgroup *cgrp, struct cftype *cft,
				const char *buffer);
	int (*trigger)(struct cgroup *cgrp, unsigned int event);
 
	int (*release)(struct inode *inode, struct file *file);
	int (*register_event)(struct cgroup *cgrp, struct cftype *cft,
	struct eventfd_ctx *eventfd, const char *args); /*
	void (*unregister_event)(struct cgroup *cgrp, struct cftype *cft,
	struct eventfd_ctx *eventfd);
};

cftype中除了定义文件的名字和相关权限标记外,主要是定义了对文件进行操作的函数指针。不同的文件可以有不同的操作,对文件进行操作时,相关函数指针指向的函数会被调用。

综合上面的分析,cgroups通过实现cgroup文件系统来为用户提供管理cgroup的工具,而cgroup文件系统是基于Linux VFS实现的。相应地,cgroups为控制文件定义了相应的数据结构cftype,对其操作由cgroup文件系统定义的通过操作捕获,再调用cftype定义的具体实现。

cgroups 数据结构设计

http://www.cnblogs.com/lisperl/archive/2012/04/18/2455027.html

我们从进程出发来剖析cgroups相关数据结构之间的关系。 在Linux中,管理进程的数据结构是task_struct,其中与cgroups有关的:

1
2
3
4
5
6
#ifdef CONFIG_CGROUPS
	/* Control Group info protected by css_set_lock */
	struct css_set *cgroups;
	/* cg_list protected by css_set_lock and tsk->alloc_lock */
	struct list_head cg_list;
#endif

其中cgroups指针指向了一个css_set结构,而css_set存储了与进程相关的cgroups信息。Cg_list是一个嵌入的list_head结构,用于将连到同一个css_set的进程组织成一个链表。下面我们来看css_set的结构:

1
2
3
4
5
6
7
8
struct css_set {
	atomic_t refcount;
	struct hlist_node hlist;
	struct list_head tasks;
	struct list_head cg_links;
	struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
	struct rcu_head rcu_head;
};

其中refcount是该css_set的引用数,因为一个css_set可以被多个进程共用,只要这些进程的cgroups信息相同,比如:在所有已创建的层级里面都在同一个cgroup里的进程。

hlist是嵌入的hlist_node,用于把所有css_set组织成一个hash表,这样内核可以快速查找特定的css_set。

tasks指向所有连到此css_set的进程连成的链表。

cg_links指向一个由struct cg_cgroup_link连成的链表。

Subsys是一个指针数组,存储一组指向cgroup_subsys_state的指针。一个cgroup_subsys_state就是进程与一个特定子系统相关的信息。通过这个指针数组,进程就可以获得相应的cgroups控制信息了。

下面我们就来看cgroup_subsys_state的结构:

1
2
3
4
5
6
struct cgroup_subsys_state {
	struct cgroup *cgroup;
	atomic_t refcnt;
	unsigned long flags;
	struct css_id *id;
};

cgroup指针指向了一个cgroup结构,也就是进程属于的cgroup。进程受到子系统的控制,实际上是通过加入到特定的cgroup实现的,因为cgroup在特定的层级上,而子系统又是附加到曾经上的。通过以上三个结构,进程就可以和cgroup连接起来了:task_struct->css_set->cgroup_subsys_state->cgroup。

下面我们再来看cgroup的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct cgroup {
	unsigned long flags;       
	atomic_t count;
	struct list_head sibling;  
	struct list_head children; 
	struct cgroup *parent;     
	struct dentry *dentry;     
	struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
	struct cgroupfs_root *root;
	struct cgroup *top_cgroup;
	struct list_head css_sets;
	struct list_head release_list;
	struct list_head pidlists;
	struct mutex pidlist_mutex;
	struct rcu_head rcu_head;
	struct list_head event_list;
	spinlock_t event_list_lock;
};

sibling,children和parent三个嵌入的list_head负责将同一层级的cgroup连接成一颗cgroup树。

subsys是一个指针数组,存储一组指向cgroup_subsys_state的指针。这组指针指向了此cgroup跟各个子系统相关的信息,这个跟css_set中的道理是一样的。

root指向了一个cgroupfs_root的结构,就是cgroup所在的层级对应的结构体。这样以来,之前谈到的几个cgroups概念就全部联系起来了。

top_cgroup指向了所在层级的根cgroup,也就是创建层级时自动创建的那个cgroup。

css_set指向一个由struct cg_cgroup_link连成的链表,跟css_set中cg_links一样。

下面我们来分析一个css_set和cgroup之间的关系。我们先看一下 cg_cgroup_link的结构

1
2
3
4
5
6
struct cg_cgroup_link {
	struct list_head cgrp_link_list;
	struct cgroup *cgrp;
	struct list_head cg_link_list;
	struct css_set *cg;
};

cgrp_link_list连入到cgroup->css_set指向的链表,cgrp则指向此cg_cgroup_link相关的cgroup。

Cg_link_list则连入到css_set->cg_links指向的链表,cg则指向此cg_cgroup_link相关的css_set。

那为什么要这样设计呢?

那是因为cgroup和css_set是一个多对多的关系,必须添加一个中间结构来将两者联系起来,这跟数据库模式设计是一个道理。cg_cgroup_link中的cgrp和cg就是此结构体的联合主键,而cgrp_link_list和cg_link_list分别连入到cgroup和css_set相应的链表,使得能从cgroup或css_set都可以进行遍历查询。

那为什么cgroup和css_set是多对多的关系呢?

一个进程对应css_set,一个css_set就存储了一组进程(应该有可能被几个进程共享,所以是一组)跟各个子系统相关的信息,但是这些信息有可能不是从一个cgroup那里获得的,因为一个进程可以同时属于几个cgroup,只要这些cgroup不在同一个层级。举个例子:我们创建一个层级A,A上面附加了cpu和memory两个子系统,进程B属于A的根cgroup;然后我们再创建一个层级C,C上面附加了ns和blkio两个子系统,进程B同样属于C的根cgroup;那么进程B对应的cpu和memory的信息是从A的根cgroup获得的,ns和blkio信息则是从C的根cgroup获得的。因此,一个css_set存储的cgroup_subsys_state可以对应多个cgroup。另一方面,cgroup也存储了一组cgroup_subsys_state,这一组cgroup_subsys_state则是cgroup从所在的层级附加的子系统获得的。一个cgroup中可以有多个进程,而这些进程的css_set不一定都相同,因为有些进程可能还加入了其他cgroup。但是同一个cgroup中的进程与该cgroup关联的cgroup_subsys_state都受到该cgroup的管理(cgroups中进程控制是以cgroup为单位的)的,所以一个cgrouop也可以对应多个css_set。

那为什么要这样一个结构呢?

从前面的分析,我们可以看出从task到cgroup是很容易定位的,但是从cgroup获取此cgroup的所有的task就必须通过这个结构了。每个进程都会指向一个css_set,而与这个css_set关联的所有进程都会链入到css_set->tasks链表.而cgroup又通过一个中间结构cg_cgroup_link来寻找所有与之关联的所有css_set,从而可以得到与cgroup关联的所有进程。

最后让我们看一下层级和子系统对应的结构体。层级对应的结构体是cgroupfs_root:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct cgroupfs_root {
	struct super_block *sb;
	unsigned long subsys_bits;
	int hierarchy_id;
	unsigned long actual_subsys_bits;
	struct list_head subsys_list;
	struct cgroup top_cgroup;
	int number_of_cgroups;
	struct list_head root_list;
	unsigned long flags;
	char release_agent_path[PATH_MAX];
	char name[MAX_CGROUP_ROOT_NAMELEN];
};

sb指向该层级关联的文件系统超级块

subsys_bits和actual_subsys_bits分别指向将要附加到层级的子系统和现在实际附加到层级的子系统,在子系统附加到层级时使用

hierarchy_id是该层级唯一的id

top_cgroup指向该层级的根cgroup

number_of_cgroups记录该层级cgroup的个数

root_list是一个嵌入的list_head,用于将系统所有的层级连成链表

子系统对应的结构体是cgroup_subsys:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct cgroup_subsys {
	struct cgroup_subsys_state *(*create)(struct cgroup_subsys *ss,
								struct cgroup *cgrp);
	int (*pre_destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
	void (*destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
	int (*can_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
				struct task_struct *tsk, bool threadgroup);
	void (*cancel_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
				struct task_struct *tsk, bool threadgroup);
	void (*attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
			struct cgroup *old_cgrp, struct task_struct *tsk,
			bool threadgroup);
	void (*fork)(struct cgroup_subsys *ss, struct task_struct *task);
	void (*exit)(struct cgroup_subsys *ss, struct task_struct *task);
	int (*populate)(struct cgroup_subsys *ss,
			struct cgroup *cgrp);
	void (*post_clone)(struct cgroup_subsys *ss, struct cgroup *cgrp);
	void (*bind)(struct cgroup_subsys *ss, struct cgroup *root);
 
	int subsys_id;
	int active;
	int disabled;
	int early_init;
	bool use_id;
#define MAX_CGROUP_TYPE_NAMELEN 32
	const char *name;
	struct mutex hierarchy_mutex;
	struct lock_class_key subsys_key;
	struct cgroupfs_root *root;
	struct list_head sibling;
	struct idr idr;
	spinlock_t id_lock;
	struct module *module;
};

Cgroup_subsys定义了一组操作,让各个子系统根据各自的需要去实现。这个相当于C++中抽象基类,然后各个特定的子系统对应cgroup_subsys则是实现了相应操作的子类。类似的思想还被用在了cgroup_subsys_state中,cgroup_subsys_state并未定义控制信息,而只是定义了各个子系统都需要的共同信息,比如该cgroup_subsys_state从属的cgroup。然后各个子系统再根据各自的需要去定义自己的进程控制信息结构体,最后在各自的结构体中将cgroup_subsys_state包含进去,这样通过Linux内核的container_of等宏就可以通过cgroup_subsys_state来获取相应的结构体。

Linux内核的namespace机制分析

http://www.linuxidc.com/Linux/2015-02/113022.htm

1. Linux内核namespace机制

Linux Namespaces机制提供一种资源隔离方案。PID,IPC,Network等系统资源不再是全局性的,而是属于某个特定的Namespace。每个namespace下的资源对于其他namespace下的资源都是透明,不可见的。因此在操作系统层面上看,就会出现多个相同pid的进程。系统中可以同时存在两个进程号为0,1,2的进程,由于属于不同的namespace,所以它们之间并不冲突。而在用户层面上只能看到属于用户自己namespace下的资源,例如使用ps命令只能列出自己namespace下的进程。这样每个namespace看上去就像一个单独的Linux系统。

2 . Linux内核中namespace结构体

在Linux内核中提供了多个namespace,其中包括fs (mount), uts, network, sysvipc, 等。一个进程可以属于多个namesapce,既然namespace和进程相关,那么在task_struct结构体中就会包含和namespace相关联的变量。在task_struct 结构中有一个指向namespace结构体的指针nsproxy。

1
2
3
4
5
6
struct task_struct {
	……..
	/* namespaces */
	struct nsproxy *nsproxy;
	…….
}

再看一下nsproxy是如何定义的,在include/linux/nsproxy.h文件中,这里一共定义了5个各自的命名空间结构体,在该结构体中定义了5个指向各个类型namespace的指针,由于多个进程可以使用同一个namespace,所以nsproxy可以共享使用,count字段是该结构的引用计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 'count' is the number of tasks holding a reference.
 * The count for each namespace, then, will be the number
 * of nsproxies pointing to it, not the number of tasks.
 * The nsproxy is shared by tasks which share all namespaces.
 * As soon as a single namespace is cloned or unshared, the
 * nsproxy is copied
*/
struct nsproxy {
	atomic_t count;
	struct uts_namespace *uts_ns;
	struct ipc_namespace *ipc_ns;
	struct mnt_namespace *mnt_ns;
	struct pid_namespace *pid_ns_for_children;
	struct net           *net_ns;
};

(1) UTS命名空间包含了运行内核的名称、版本、底层体系结构类型等信息。UTS是UNIX Timesharing System的简称。

(2) 保存在struct ipc_namespace中的所有与进程间通信(IPC)有关的信息。

(3) 已经装载的文件系统的视图,在struct mnt_namespace中给出。

(4) 有关进程ID的信息,由struct pid_namespace提供。

(5) struct net_ns包含所有网络相关的命名空间参数。

系统中有一个默认的nsproxy,init_nsproxy,该结构在task初始化是也会被初始化。

1
2
#define INIT_TASK(tsk)  \
{ .nsproxy  = &init_nsproxy,   }

其中init_nsproxy的定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static struct kmem_cache *nsproxy_cachep;
 
struct nsproxy init_nsproxy = {
	.count                       = ATOMIC_INIT(1),
	.uts_ns                      = &init_uts_ns,
#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
	.ipc_ns                        = &init_ipc_ns,
#endif
	.mnt_ns                      = NULL,
	.pid_ns_for_children         = &init_pid_ns,
#ifdef CONFIG_NET
	.net_ns                      = &init_net,
#endif
};

对于 .mnt_ns 没有进行初始化,其余的namespace都进行了系统默认初始。

3. 使用clone创建自己的Namespace

如果要创建自己的命名空间,可以使用系统调用clone(),它在用户空间的原型为

1
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg)

这里fn是函数指针,这个就是指向函数的指针,, child_stack是为子进程分配系统堆栈空间,flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数也就是fn指向的函数参数。下面是flags可以取的值。这里只关心和namespace相关的参数。

CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask

CLONE_NEWNS 当clone需要自己的命名空间时设置这个标志,不能同时设置CLONE_NEWS和CLONE_FS。

Clone()函数是在libc库中定义的一个封装函数,它负责建立新轻量级进程的堆栈并且调用对编程者隐藏了clone系统条用。实现clone()系统调用的sys_clone()服务例程并没有fn和arg参数。封装函数把fn指针存放在子进程堆栈的每个位置处,该位置就是该封装函数本身返回地址存放的位置。Arg指针正好存放在子进程堆栈中的fn的下面。当封装函数结束时,CPU从堆栈中取出返回地址,然后执行fn(arg)函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* Prototype for the glibc wrapper function */
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack,
			int flags, void *arg, ...
			/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );

/* Prototype for the raw system call */
long clone(unsigned long flags, void *child_stack,
			void *ptid, void *ctid,
			struct pt_regs *regs);
我们在Linux内核中看到的实现函数,是经过libc库进行封装过的,在Linux内核中的fork.c文件中,有下面的定义,最终调用的都是do_fork()函数。

#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
				int __user *, parent_tidptr,
				int, tls_val,
				int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
				int __user *, parent_tidptr,
				int __user *, child_tidptr,
				int, tls_val)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
				int, stack_size,
				int __user *, parent_tidptr,
				int __user *, child_tidptr,
				int, tls_val)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
				int __user *, parent_tidptr,
				int __user *, child_tidptr,
				int, tls_val)
#endif
{
	return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}
#endif
3.1 do_fork函数

在clone()函数中调用do_fork函数进行真正的处理,在do_fork函数中调用copy_process进程处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
long do_fork(unsigned long clone_flags,
			unsigned long stack_start,
			unsigned long stack_size,
			int __user *parent_tidptr,
			int __user *child_tidptr)
{
	struct task_struct *p;
	int trace = 0;
	long nr;
 
	/*
	 * Determine whether and which event to report to ptracer.  When
	 * called from kernel_thread or CLONE_UNTRACED is explicitly
	 * requested, no event is reported; otherwise, report if the event
	 * for the type of forking is enabled.
	 */
	if (!(clone_flags & CLONE_UNTRACED)) {
		if (clone_flags & CLONE_VFORK)
			trace = PTRACE_EVENT_VFORK;
		else if ((clone_flags & CSIGNAL) != SIGCHLD)
			trace = PTRACE_EVENT_CLONE;
		else
			trace = PTRACE_EVENT_FORK;
 
		if (likely(!ptrace_event_enabled(current, trace)))
			trace = 0;
	}
 
	p = copy_process(clone_flags, stack_start, stack_size,
					child_tidptr, NULL, trace);
	/*
	 * Do this prior waking up the new thread - the thread pointer
	 * might get invalid after that point, if the thread exits quickly.
	 */
	if (!IS_ERR(p)) {
		struct completion vfork;
		struct pid *pid;
 
		trace_sched_process_fork(current, p);
 
		pid = get_task_pid(p, PIDTYPE_PID);
		nr = pid_vnr(pid);
 
		if (clone_flags & CLONE_PARENT_SETTID)
			put_user(nr, parent_tidptr);
 
		if (clone_flags & CLONE_VFORK) {
			p->vfork_done = &vfork;
			init_completion(&vfork);
			get_task_struct(p);
		}
 
		wake_up_new_task(p);
 
		/* forking complete and child started to run, tell ptracer */
		if (unlikely(trace))
			ptrace_event_pid(trace, pid);
 
		if (clone_flags & CLONE_VFORK) {
			if (!wait_for_vfork_done(p, &vfork))
				ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
		}
 
		put_pid(pid);
	} else {
		nr = PTR_ERR(p);
	}
	return nr;
}
3.2 copy_process函数

在copy_process函数中调用copy_namespaces函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static struct task_struct *copy_process(unsigned long clone_flags,
						unsigned long stack_start,
						unsigned long stack_size,
						int __user *child_tidptr,
						struct pid *pid,
						int trace)
{
	int retval;
	struct task_struct *p;
	/*下面的代码是对clone_flag标志进行检查,有部分表示是互斥的,例如CLONE_NEWNS和CLONENEW_FS*/
	if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
		return ERR_PTR(-EINVAL);
 
	if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
		return ERR_PTR(-EINVAL);
 
	if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
		return ERR_PTR(-EINVAL);
 
	if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
		return ERR_PTR(-EINVAL);
 
	if ((clone_flags & CLONE_PARENT) &&
					  current->signal->flags & SIGNAL_UNKILLABLE)
		return ERR_PTR(-EINVAL);

	……
	retval = copy_namespaces(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_mm;
	retval = copy_io(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_namespaces;
	retval = copy_thread(clone_flags, stack_start, stack_size, p);
	if (retval)
		goto bad_fork_cleanup_io;
	/*do_fork中调用copy_process函数,该函数中pid参数为NULL,所以这里的if判断是成立的。为进程所在的namespace分配pid,在3.0的内核之前还有一个关键函数,就是namespace创建后和cgroup的关系,
	if (current->nsproxy != p->nsproxy) {
		retval = ns_cgroup_clone(p, pid);
		if (retval)
			goto bad_fork_free_pid;
	但在3.0内核以后给删掉了,具体请参考remove the ns_cgroup*/
	if (pid != &init_struct_pid) {
		retval = -ENOMEM;
		pid = alloc_pid(p->nsproxy->pid_ns_for_children);
		if (!pid)
			goto bad_fork_cleanup_io;
	}…..
}

3.3 copy_namespaces 函数

在kernel/nsproxy.c文件中定义了copy_namespaces函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int copy_namespaces(unsigned long flags, struct task_struct *tsk)
{
	struct nsproxy *old_ns = tsk->nsproxy;
	struct user_namespace *user_ns = task_cred_xxx(tsk, user_ns);
	struct nsproxy *new_ns;
	/*首先检查flag,如果flag标志不是下面的五种之一,就会调用get_nsproxy对old_ns递减引用计数,然后直接返回0*/
	if (likely(!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
				  CLONE_NEWPID | CLONE_NEWNET)))) {
		get_nsproxy(old_ns);
		return 0;
	}
	/*当前进程是否有超级用户的权限*/
	if (!ns_capable(user_ns, CAP_SYS_ADMIN))
		return -EPERM;
 
	/*
	 * CLONE_NEWIPC must detach from the undolist: after switching
	 * to a new ipc namespace, the semaphore arrays from the old
	 * namespace are unreachable.  In clone parlance, CLONE_SYSVSEM
	 * means share undolist with parent, so we must forbid using
	 * it along with CLONE_NEWIPC.
	   对CLONE_NEWIPC进行特殊的判断,*/
	if ((flags & (CLONE_NEWIPC | CLONE_SYSVSEM)) ==
		(CLONE_NEWIPC | CLONE_SYSVSEM)) 
		return -EINVAL;
	/*为进程创建新的namespace*/
	new_ns = create_new_namespaces(flags, tsk, user_ns, tsk->fs);
	if (IS_ERR(new_ns))
		return  PTR_ERR(new_ns);
 
	tsk->nsproxy = new_ns;
	return 0;
}
3.4 create_new_namespaces函数

create_new_namespaces创建新的namespace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
static struct nsproxy *create_new_namespaces(unsigned long flags,
		struct task_struct *tsk, struct user_namespace *user_ns,
		struct fs_struct *new_fs)
{
	struct nsproxy *new_nsp;
	int err;
	/*为新的nsproxy分配内存空间,并对其引用计数设置为初始1*/
	new_nsp = create_nsproxy();
	if (!new_nsp)
		return ERR_PTR(-ENOMEM);
	/*如果Namespace中的各个标志位进行了设置,则会调用相应的namespace进行创建*/
	new_nsp->mnt_ns = copy_mnt_ns(flags, tsk->nsproxy->mnt_ns, user_ns, new_fs);
	if (IS_ERR(new_nsp->mnt_ns)) {
		err = PTR_ERR(new_nsp->mnt_ns);
		goto out_ns;
	}
 
	new_nsp->uts_ns = copy_utsname(flags, user_ns, tsk->nsproxy->uts_ns);
	if (IS_ERR(new_nsp->uts_ns)) {
		err = PTR_ERR(new_nsp->uts_ns);
		goto out_uts;
	}
 
	new_nsp->ipc_ns = copy_ipcs(flags, user_ns, tsk->nsproxy->ipc_ns);
	if (IS_ERR(new_nsp->ipc_ns)) {
		err = PTR_ERR(new_nsp->ipc_ns);
		goto out_ipc;
	}
 
	new_nsp->pid_ns_for_children =
		copy_pid_ns(flags, user_ns, tsk->nsproxy->pid_ns_for_children);
	if (IS_ERR(new_nsp->pid_ns_for_children)) {
		err = PTR_ERR(new_nsp->pid_ns_for_children);
		goto out_pid;
	}
 
	new_nsp->net_ns = copy_net_ns(flags, user_ns, tsk->nsproxy->net_ns);
	if (IS_ERR(new_nsp->net_ns)) {
		err = PTR_ERR(new_nsp->net_ns);
		goto out_net;
	}
 
	return new_nsp;
 
out_net:
	if (new_nsp->pid_ns_for_children)
		put_pid_ns(new_nsp->pid_ns_for_children);
out_pid:
	if (new_nsp->ipc_ns)
		put_ipc_ns(new_nsp->ipc_ns);
out_ipc:
	if (new_nsp->uts_ns)
		put_uts_ns(new_nsp->uts_ns);
out_uts:
	if (new_nsp->mnt_ns)
		put_mnt_ns(new_nsp->mnt_ns);
out_ns:
	kmem_cache_free(nsproxy_cachep, new_nsp);
	return ERR_PTR(err);
}
3.4.1 create_nsproxy函数
1
2
3
4
5
6
7
8
9
static inline struct nsproxy *create_nsproxy(void)
{
	struct nsproxy *nsproxy;
 
	nsproxy = kmem_cache_alloc(nsproxy_cachep, GFP_KERNEL);
	if (nsproxy)
		atomic_set(&nsproxy->count, 1);
	return nsproxy;
}

例子1:namespace pid的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sched.h>
#include <string.h>
 
static int fork_child(void *arg)
{
	int a = (int)arg;
	int i;
	pid_t pid;
	char *cmd  = "ps -el;
	printf("In the container, my pid is: %d\n", getpid());
	/*ps命令是解析procfs的内容得到结果的,而procfs根目录的进程pid目录是基于mount当时的pid namespace的,这个在procfs的get_sb回调中体现的。因此只需要重新mount一下proc, mount -t proc proc /proc*/
	mount("proc", "/proc", "proc", 0, "");
	for (i = 0; i <a; i++) {
		pid = fork();
		if (pid <0)
			return pid;
		else if (pid)
			printf("pid of my child is %d\n", pid);
		else if (pid == 0) {
			sleep(30);
			exit(0);
		}
	}
	execl("/bin/bash", "/bin/bash","-c",cmd, NULL);
	return 0;
}
int main(int argc, char *argv[])
{
	int cpid;
	void *childstack, *stack;
	int flags;
	int ret = 0;
	int stacksize = getpagesize() * 4;
	if (argc != 2) {
		fprintf(stderr, "Wrong usage.\n");
		return -1;
	}
	stack = malloc(stacksize);
	if(stack == NULL)
	{
		return -1;
	}
	printf("Out of the container, my pid is: %d\n", getpid());
	childstack = stack + stacksize;
	flags = CLONE_NEWPID | CLONE_NEWNS;
	cpid = clone(fork_child, childstack, flags, (void *)atoi(argv[1]));
	printf("cpid: %d\n", cpid);
	if (cpid <0) {
		perror("clone");
		ret = -1;
		goto out;
	}
	fprintf(stderr, "Parent sleeping 20 seconds\n");
	sleep(20);
	ret = 0;
out:
	free(stack);
	return ret;
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@Ubuntu:~/c_program# ./namespace 7 
Out of the container, my pid is: 8684
cpid: 8685
Parent sleeping 20 seconds
In the container, my pid is: 1
pid of my child is 2
pid of my child is 3
pid of my child is 4
pid of my child is 5
pid of my child is 6
pid of my child is 7
pid of my child is 8
F S  UID  PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 R    0    1    0  0  80  0 -  1085 -      pts/0    00:00:00 ps
1 S    0    2    1  0  80  0 -  458 hrtime pts/0    00:00:00 namespace
1 S    0    3    1  0  80  0 -  458 hrtime pts/0    00:00:00 namespace
1 S    0    4    1  0  80  0 -  458 hrtime pts/0    00:00:00 namespace
1 S    0    5    1  0  80  0 -  458 hrtime pts/0    00:00:00 namespace
1 S    0    6    1  0  80  0 -  458 hrtime pts/0    00:00:00 namespace
1 S    0    7    1  0  80  0 -  458 hrtime pts/0    00:00:00 namespace
1 S    0    8    1  0  80  0 -  458 hrtime pts/0    00:00:00 namespace

例子2:UTS的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#define _GNU_SOURCE
#include <sys/wait.h>
#include <sys/utsname.h>
#include <sched.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
						} while (0)
 
static int              /* Start function for cloned child */
childFunc(void *arg)
{
	struct utsname uts;
	/* Change hostname in UTS namespace of child */
	if (sethostname(arg, strlen(arg)) == -1)
		errExit("sethostname");
	/* Retrieve and display hostname */
	if (uname(&uts) == -1)
		errExit("uname");
	printf("uts.nodename in child:  %s\n", uts.nodename);
	/* Keep the namespace open for a while, by sleeping.
	 *              This allows some experimentation--for example, another
	 *                process might join the namespace. */
	sleep(200);
	return 0;          /* Child terminates now */
}
#define STACK_SIZE (1024 * 1024)    /* Stack size for cloned child */
 
int
main(int argc, char *argv[])
{
	char *stack;            /* Start of stack buffer */
	char *stackTop;                /* End of stack buffer */
	pid_t pid;
	struct utsname uts;
	if (argc < 2) {
		fprintf(stderr, "Usage: %s <child-hostname>\n", argv[0]);
		exit(EXIT_SUCCESS);
	}
	/* Allocate stack for child */
	stack = malloc(STACK_SIZE);
	if (stack == NULL)
		errExit("malloc");
	stackTop = stack + STACK_SIZE;  /* Assume stack grows downward */
	/* Create child that has its own UTS namespace;
	 *              child commences execution in childFunc() */
	pid = clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]);
	if (pid == -1)
		errExit("clone");
	printf("clone() returned %ld\n", (long) pid);
	/* Parent falls through to here */
	sleep(1);          /* Give child time to change its hostname */
 
	/* Display hostname in parent's UTS namespace. This will be
	 *              different from hostname in child's UTS namespace. */
 
	if (uname(&uts) == -1)
		errExit("uname");
	printf("uts.nodename in parent: %s\n", uts.nodename);
	if (waitpid(pid, NULL, 0) == -1)    /* Wait for child */
		errExit("waitpid");
	printf("child has terminated\n");
	exit(EXIT_SUCCESS);
}
1
2
3
4
root@ubuntu:~/c_program# ./namespace_1 test
clone() returned 4101
uts.nodename in child:  test
uts.nodename in parent: ubuntu