Linux学习

内核代码阅读:
bricktou.cn
http://lxr.free-electrons.com/

进程

进程概述
操作系统暴露出来的接口,称作系统调用。对系统调用进行封装可以形成各种库。

进程存活于内存中,每个进程都在内存中分配有一篇属于自己的内存空间。当进程fork的时候,Linux在内存中开辟出一片新的内存空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。老进程成为新进程的父进程(parent process),而相应的,新进程就是老的进程的子进程(child process)。一个进程除了有一个PID之外,还会有一个PPID(parent PID)来存储的父进程PID。如果我们循着PPID不断向上追溯的话,总会发现其源头是init进程。所以说,所有的进程也构成一个以init为根的树状结构。

计算机在开机的时候,内核只会建立一个进程,init进程,其他所有进程都是由init进程fork出来的进程。新进程通过老进程复制自身得到。

fork通常作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。实际上,子进程总可以查询自己的PPID来知道自己的父进程是谁,这样,一对父进程和子进程就可以随时查询对方。

#include <unistd.h>  
#include <stdio.h>   
int main ()   
{   
    pid_t fpid; //fpid表示fork函数返回的值  
    int count=0;  
    fpid=fork();   
    if (fpid < 0)   
        printf("error in fork!");   
    else if (fpid == 0) {  
        printf("我是子进程,我的进程ID是 %d/n",getpid());   
        count++;  
    }  
    else {  
        printf("我是父进程,我的进程ID是 %d/n",getpid());   
        count++;  
    }  
    printf("统计结果是: %d/n",count);  
    return 0;  
}

运行结果是:

我是子进程,我的进程ID是 5574
统计结果是: 1
我是父进程,我的进程ID是 5573
统计结果是: 1

当子进程终结时,它会通知父进程,并清空自己所占据的内存,并在内核里留下自己的退出信息(exit code,如果顺利运行,为0;如果有错误或异常状况,为>0的整数)。在这个信息里,会解释该进程为什么退出。父进程在得知子进程终结时,有责任对该子进程使用wait系统调用。这个wait函数能从内核中取出子进程的退出信息,并清空该信息在内核中所占据的空间。但是,如果父进程早于子进程终结,子进程就会成为一个孤儿(orphand)进程。孤儿进程会被过继给init进程,init进程也就成了该进程的父进程。init进程负责该子进程终结时调用wait函数。

如果父进程不对子进程调用wait函数,就会造成子进程的退出信息滞留在内核中的状况,这样的情况下,子进程成为僵尸(zombie)进程。当大量僵尸进程积累时,内存空间会被挤占。

进程描述符

进程描述符,用于描述进程,包含所有进程管理的信息。

Linux-进程描述符 task_struct 详解 - John_ABC - 博客园 (cnblogs.com)

nsproxy结构体是namespace。

struct task_struct{
    //......
    //......
    
    struct nsproxy *nsproxy;        //namespace
    
    #ifdef CONFIG_CGROUPS           //cgroups
        struct css_set *cgroups; 
        struct list_head cg_list;
    #endif
    
    //......
    //......
};

fork分析

在这里插入图片描述

fork和vfork函数

#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
/* can not support in nommu mode */
return -EINVAL;
#endif
}
#endif

#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
0, NULL, NULL, 0);
}
#endif

fork和vfork的区别:

fork创建的子进程,会完整的复制父进程的缓冲区。vfork创建的子进程,不会复制父进程的缓冲区,与父进程共用父进程的缓冲区,子进程修改该缓冲区,父进程会受到影响,父进程修改缓冲区,子进程也会受到影响。且vfork创建的子进程会优先执行。

long _do_fork(unsigned long clone_flags,
      unsigned long stack_start,
      unsigned long stack_size,
      int __user *parent_tidptr,
      int __user *child_tidptr,
      unsigned long tls)
{
    struct task_struct *p;
    int trace = 0;
    long nr;
    
//......
    p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
}
//......

在do_fork函数中调用了copy_process()函数。

namespaces

浅析Linux namespace_energysober的博客-CSDN博客

namespace一共有6个类型

类型功能说明
UTS namespace提供主机名隔离能力
User Namespace提供用户隔离能力
PID Namespace提供进程隔离能力
Net Namespace提供网络隔离能力
MNT Namespace提供磁盘挂载点和文件系统的隔离能力
IPC Namespace提供进程间通信的隔离能力

Linux namespace是作用于进程的,也就是说每个进程都有属于自己的namespce。在每个进程的进程描述符中,有对应的namespace结构体。

在进程描述符中的nsproxy表示namespace结构体。

struct nsproxy *nsproxy;

nsproxy

代码路径:/include/linux/nsproxy.h

struct mnt_namespace;
struct uts_namespace;
struct ipc_namespace;
struct pid_namespace;
struct cgroup_namespace;
struct fs_struct;

/*
 * A structure to contain pointers to all per-process
 * namespaces - fs (mount), uts, network, sysvipc, etc.
 *
 * The pid namespace is an exception -- it's accessed using
 * task_active_pid_ns.  The pid namespace here is the
 * namespace that children will use.
 *
 * '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;
    struct time_namespace *time_ns;                //新版本才有的
    struct time_namespace *time_ns_for_children;   //新版本才有的
    struct cgroup_namespace *cgroup_ns;
};
extern struct nsproxy init_nsproxy;

从定义可以看出,nsproxy存储了一组指向各个类型Namespace的指针,为进程访问各个Namespace起了一个代理的作用。由于可能有多个进程所在的Namespace完全一样,nsproxy可以在进程间共享,count字段负责记录该结构的引用数。

nsproxy中一共有6个结构体,分别于namespace的6个类型一一对应。

(问题1:源码注释中说pid_namespace是子进程使用的namespace?)
(问题2:现在的linux是不是一共有7种namespace,要把新的time_namespace加上?)

copy_process()函数

/*
 * This creates a new process as a copy of the old one,
 * but does not actually start it yet.
 *
 * It copies the registers, and all the appropriate
 * parts of the process environment (as per the clone
 * flags). The actual kick-off is left to the caller.
 */
static __latent_entropy 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,
                unsigned long tls,
                int node)
{
    //......
    cgroup_fork(p);
    //......
    if (retval)
        goto bad_fork_cleanup_signal;
    retval = copy_namespaces(clone_flags, p);
    //......
}

copy_process()函数调用了copy_namespaces()函数。

copy_namespaces()函数

/*
 * called from clone.  This now handles copy for nsproxy and all
 * namespaces therein.
 */
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;
    
    if (likely(!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
          CLONE_NEWPID | CLONE_NEWNET |
          CLONE_NEWCGROUP)))) 
    {
        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.
 */
    if ((flags & (CLONE_NEWIPC | CLONE_SYSVSEM)) ==
    (CLONE_NEWIPC | CLONE_SYSVSEM)) 
        return -EINVAL;
    
    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;
}

如果不是创建新的namespace,则使用get_nsproxy()函数。
如果是创建新的namespace,则使用create_new_namespaces()函数。

create_new_namespaces()

/*
 * Create new nsproxy and all of its the associated namespaces.
 * Return the newly created nsproxy.  Do not attach this to the task,
 * leave it to the caller to do proper locking and attach it to task.
 */
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;
    
    new_nsp = create_nsproxy();
    if (!new_nsp)
        return ERR_PTR(-ENOMEM);
    
    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->cgroup_ns = copy_cgroup_ns(flags, user_ns,
        tsk->nsproxy->cgroup_ns);
    if (IS_ERR(new_nsp->cgroup_ns)) {
        err = PTR_ERR(new_nsp->cgroup_ns);
        goto out_cgroup;
    }
    
    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:
    put_cgroup_ns(new_nsp->cgroup_ns);
    out_cgroup:
    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);
}

先调用create_nsproxy 创建一个nsproxy
copy_mnt_ns 创建一个mnt_namespace
copy_utsname 创建一个uts_namespace
copy_ipcs 创建一个ipc_namespace
copy_pid_ns 创建一个pid_namespace
copy_cgroup_ns 创建一个cgroup_namespace
copy_net_ns 创建一个net

UTSNamespace

简介

原文链接:Uts_namespace分析

UTS命名空间是Linux内核Namespace(命名空间)的一个子系统,主要用来完成对容器HOSTNAME和domain的隔离,同时保存内核名称、版本、以及底层体系结构类型等信息。

UTS命名空间是扁平化的结构,不同的命名空间之间没有层级关系。

Uts命名空间的用来隔离系统的这些信息,使得用户在容器中查看到的信息是当前容器的系统、版本,不同于主机的,内核通过uts_namespace对当前系统中多个容器的这些信息进行统一管理,每一个容器对应有一个自己的uts_namespace,用来隔离容器的内核名称、版本等信息,不同容器查看到的都是属于自己的信息,相互间不能查看。

用法

当前一个系统的uts信息可以通过uname命令来查看,uname用来获取当前机器和操作系统的相关信息,可以显示linux主机所用的操作系统的版本、硬件名称等基本信息。

用法:uname [选项]...
输出一组系统信息。如果不跟随选项,则视为只附加 -s 选项。

  -a, --all                以如下次序输出所有信息。其中若 -p 和
                             -i 的探测结果不可知则被省略:
  -s, --kernel-name        输出内核名称
  -n, --nodename           输出网络节点上的主机名
  -r, --kernel-release     输出内核发行号
  -v, --kernel-version     输出内核版本
  -m, --machine            输出主机的硬件架构名称
  -p, --processor          输出处理器类型(不可移植)
  -i, --hardware-platform  输出硬件平台或(不可移植)
  -o, --operating-system   输出操作系统名称
      --help            显示此帮助信息并退出
      --version         显示版本信息并退出

以上所有信息都存储在一个叫做new_utsname的结构体中,用户通过uname命令查看其中的内容。

new_utsname结构体又保存在uts_namespace结构体中。

代码路径:/include/linux/utsname.h

struct uts_namespace {
    //struct kref kref;//老版本有
    struct new_utsname name;
    struct user_namespace *user_ns;
    struct ucounts *ucounts;//是否是老版本的kref?
    struct ns_common ns;
};
extern struct uts_namespace init_uts_ns;

Uname是glibc分装的函数。其实现是依据linux的系统调用newuname。

Uname对应内核的一个系统调用newuname,通过调用newuname来调用new_utsname。

疑问:ucounts是否是老版本的kref?

new_utsname是保存所有UTS信息的结构体。

user_namespace是指向结构体user_namespace的指针。见user_namespace章节。

代码路径:/include/uapi/linux/utsname.h

struct new_utsname {
	char sysname[__NEW_UTS_LEN + 1];
	char nodename[__NEW_UTS_LEN + 1];
	char release[__NEW_UTS_LEN + 1];
	char version[__NEW_UTS_LEN + 1];
	char machine[__NEW_UTS_LEN + 1];
	char domainname[__NEW_UTS_LEN + 1];
};

保存的数据是uname -a查看到的对应数据

[xxxxx ~]$ uname -a
Linux xxxxx 4.19.112-2.el8.x86_64 #1 SMP Wed Jun 10 09:04:49 EDT 2020 x86_64 x86_64 x86_64 GNU/Linux
sysname           -s               内核名称

nodename          -n               主机在网络节点上的名称或主机名称

release           -r               linux操作系统内核版本号

version           -v               显示操作系统是第几个 version 版本

machine           -m               x86_64 输出主机的硬件架构(CPU架构)名称

processor         -p               x86_64 处理器类型

hardware-platform -i               x86_64 硬件平台类型

operating-system  -o               GNU/Linux 操作系统名

domainname                         domain名

实现过程

fork->copy_process()->copy_name_spaces()->create_new_namespaces()->copy_utsname()->clone_uts_ns()
->create_uts_ns()

copy_utsname()

代码路径:/kernel/utsname.c

struct uts_namespace *copy_utsname(unsigned long flags,
	struct user_namespace *user_ns, struct uts_namespace *old_ns)
{
	struct uts_namespace *new_ns;

	BUG_ON(!old_ns);
	get_uts_ns(old_ns);

	if (!(flags & CLONE_NEWUTS))
		return old_ns;

	new_ns = clone_uts_ns(user_ns, old_ns);

	put_uts_ns(old_ns);
	return new_ns;
}

如果flags不是CLONE_NEWUTS,则直接返回旧的namespace,并且引用计数直接+1。(在get_uts_ns函数中,层层调用,最终在__refcount_inc()调用__refcount_add()函数进行实现)。

clone_uts_ns()

代码路径:/kernel/utsname.c

static struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns,
					  struct uts_namespace *old_ns)
{
	struct uts_namespace *ns;
	struct ucounts *ucounts;
	int err;

	err = -ENOSPC;
	ucounts = inc_uts_namespaces(user_ns);
	if (!ucounts)
		goto fail;

	err = -ENOMEM;
	ns = create_uts_ns();
	if (!ns)
		goto fail_dec;

	err = ns_alloc_inum(&ns->ns);
	if (err)
		goto fail_free;

	ns->ucounts = ucounts;
	ns->ns.ops = &utsns_operations;

	down_read(&uts_sem);
	memcpy(&ns->name, &old_ns->name, sizeof(ns->name));
	ns->user_ns = get_user_ns(user_ns);
	up_read(&uts_sem);
	return ns;

fail_free:
	kmem_cache_free(uts_ns_cache, ns);
fail_dec:
	dec_uts_namespaces(ucounts);
fail:
	return ERR_PTR(err);
}

clone_uts_ns()只复制了旧namespace的name,也就是new_utsname结构体。

struct new_utsname {
	char sysname[__NEW_UTS_LEN + 1];
	char nodename[__NEW_UTS_LEN + 1];
	char release[__NEW_UTS_LEN + 1];
	char version[__NEW_UTS_LEN + 1];
	char machine[__NEW_UTS_LEN + 1];
	char domainname[__NEW_UTS_LEN + 1];
};
create_uts_ns()

代码路径:/kernel/utsname.c

static struct uts_namespace *create_uts_ns(void)
{
	struct uts_namespace *uts_ns;

	uts_ns = kmem_cache_alloc(uts_ns_cache, GFP_KERNEL);
	if (uts_ns)
		refcount_set(&uts_ns->ns.count, 1);
	return uts_ns;
}

refcount_set(&uts_ns->ns.count, 1);其中,ns.count是uts_namespace结构体中的ns_common结构体中的count。用于计数引用次数。即有多少个进程挂载在该uts_namespace下。

uts_namespace创建的场景

该节内容来自:Uts_namespace分析

前面的几个函数都是uts的使用接口函数,包括创建、clone等。那么到底在什么场景下会使用到这些具体的接口函数,这里详细说明一下。

clone系统调用

执行clone系统调用(fork)创建一个子进程时,通过指定具体的clone标识(clone_flags)来为子进程创建新的命名空间。Uts命名空间对应的clone_flag为CLONE_NEWUTS。

具体的函数调用关系如下图:
clone
在clone系统调用(fork)处理中,在copy进程时,会做namespace的相关处理,如果指定了CLONE_NEWUTS,则会新建一个uts_namespace。否则直接沿用父进程的namespace。

set_ns系统调用

通过系统调用直接set_ns设置命名空间时,会调用到copy_utsname接口函数,但由于set_ns中不会指定具体的clone_flag(也不需要),因此在copy_utsname中也不会调用clone_uts的操作,而是直接返回原来的uts_namespace。

处理的逻辑如下:
set_ns
可以看到,在调用create_new_namespace时,指定的flag为0,这样在copy的时候就不会执行clone_uts_ns的操作而直接返回了。

set_ns系统调用所设置的uts_namespace是这个执行进程所属的命名空间。

unshare系统调用

unshare系统调用允许进行调用的进程用原名称空间的拷贝替代所选资源的名称空间,用于进程在不创建一个新的进程的情况下共享它的资源,这些资源包括进程上下文、命名空间等。

Unshare系统调用通过设置不同的unshare_flags来指定是否创建新的namespace。其处理过程类似clone的中的uts_namespace的copy过程。

处理过程如下:
unshare

IPC Namespace

PID Namespace

原文链接
Pid namespace是对进程pid的容器虚拟化,从pid的维度实现容器间的隔离。即在一个容器中只能看到属于该pidns的pid,从而在某种程度上实现了进程间的隔离。

在一个容器中只能看到该容器pidns内的pid,但在宿主机上可以看到所有进程的pid,所以从看到的pid的角度,这是有层级关系的,对应的,pidns在实现上也是有层级关系的,在高层次pidns中可以看到低层次pidns的信息,反之则不行。一个简单的示意图如下所示:
PID namespace
每个命名空间中的pid为1的进程都为该命名空间的init进程。例如,在上图中,父命名空间中的pid=2的进程,在左子命名空间中则是左子命名空间的init进程。

pid_namespace()

struct pid_namespace {
	struct idr idr;
	struct rcu_head rcu;
	unsigned int pid_allocated;
	struct task_struct *child_reaper; 	// 父进程结束后,需要该child_reaper进程对其托管
	struct kmem_cache *pid_cachep;
	unsigned int level;					// 记录该pidns的深度
	struct pid_namespace *parent;		// 父pidns
#ifdef CONFIG_BSD_PROCESS_ACCT
	struct fs_pin *bacct;
#endif
	struct user_namespace *user_ns;
	struct ucounts *ucounts;
	int reboot;	/* group exit code if this pidns was rebooted */
	struct ns_common ns;
} __randomize_layout;
在进程中的pid
struct task_struct{
    //......
    //......
    
	pid_t				pid;
	pid_t				tgid;
    
    //......
    //......
};
pid结构体
struct pid
{
	refcount_t count;
	unsigned int level;								// 这个pid的深度
	spinlock_t lock;
	/* lists of tasks that use this pid */
	struct hlist_head tasks[PIDTYPE_MAX];			// 使用这个pid的进程链表
	struct hlist_head inodes;
	/* wait queue for pidfd notifications */
	wait_queue_head_t wait_pidfd;
	struct rcu_head rcu;
	struct upid numbers[1];							// 这个pid在不同命名空间中的显示
};

这里最重要的成员变量就是numbers数组,它表示一个进程在每个namespace里的id,这里的id就是getpid()所得到的值。Numbers[0]表示最顶层的namespace,level=0,numbers[1]表示level=1的namespace,依此类推。

upid结构体
struct upid {
	int nr;							// pid值
	struct pid_namespace *ns;		// 进程所在的pid namespace
	//struct hlist_node pid_chain;	// 链表节点
	//新版本内核没有链表节点了
};

实现过程

copy_pid_ns()
struct pid_namespace *copy_pid_ns(unsigned long flags,
	struct user_namespace *user_ns, struct pid_namespace *old_ns)
{
	if (!(flags & CLONE_NEWPID))
		return get_pid_ns(old_ns);
	if (task_active_pid_ns(current) != old_ns)
		return ERR_PTR(-EINVAL);
	return create_pid_namespace(user_ns, old_ns);
}

调用create_pid_namespace()

create_pid_namespace()
static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns,
	struct pid_namespace *parent_pid_ns)
{
	struct pid_namespace *ns;
	unsigned int level = parent_pid_ns->level + 1;
	struct ucounts *ucounts;
	int err;

	err = -EINVAL;
	if (!in_userns(parent_pid_ns->user_ns, user_ns))
		goto out;

	err = -ENOSPC;
	if (level > MAX_PID_NS_LEVEL)
		goto out;
	ucounts = inc_pid_namespaces(user_ns);
	if (!ucounts)
		goto out;

	err = -ENOMEM;
	ns = kmem_cache_zalloc(pid_ns_cachep, GFP_KERNEL);
	if (ns == NULL)
		goto out_dec;

	idr_init(&ns->idr);

	ns->pid_cachep = create_pid_cachep(level);
	if (ns->pid_cachep == NULL)
		goto out_free_idr;

	err = ns_alloc_inum(&ns->ns);
	if (err)
		goto out_free_idr;
	ns->ns.ops = &pidns_operations;

	refcount_set(&ns->ns.count, 1);
	ns->level = level;
	ns->parent = get_pid_ns(parent_pid_ns);
	ns->user_ns = get_user_ns(user_ns);
	ns->ucounts = ucounts;
	ns->pid_allocated = PIDNS_ADDING;

	return ns;

out_free_idr:
	idr_destroy(&ns->idr);
	kmem_cache_free(pid_ns_cachep, ns);
out_dec:
	dec_pid_namespaces(ucounts);
out:
	return ERR_PTR(err);
}

pidns比较绕:
例如:
父进程A,创建了子进程B。
A.task_struct.nsproxy.pid_namespace保存的是子进程的pid_namespace。

A.task_struct.nsproxy.pid_namespace -> B的pid_namespace

在pid_namespace结构体中,有一个parent结构体,保存的是父进程的pid_namespace。那么

A.task_struct.nsproxy.pid_namespace.parent -> A的pid_namespace

因此,在B的nsproxy.pid_namespace中访问parent,访问到的也是B自己的pid_namespace,并不会访问到父进程A的pid_namespace。

在进程A中

A.task_struct.pid.upid.pid_namespace -> A的pid_namespace

A.task_struct.pid.nr则是A在A的pid_namespace中的pid值。

读取当前的pid_namespace值
struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)
{
	return ns_of_pid(task_pid(tsk));
}
static inline struct pid_namespace *ns_of_pid(struct pid *pid)
{
	struct pid_namespace *ns = NULL;
	if (pid)
		ns = pid->numbers[pid->level].ns;
	return ns;
}

问题:
在pid结构体的定义中,定义struct upid numbers[1]; numbers是一个数组长度为1的数组,这里的pid->level是可以大于1的。(在create_pid_namespace()函数中,level = parent->level+1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值