Linux 进程 PID 管理

文章目录

  • 1. 前言
  • 2. 进程 PID 相关数据结构
  • 3. 进程 PID 的构建
    • 3.1 第一个进程 PID 构建
    • 3.2 第二个进程 PID 的构建过程
      • 3.2.1 从当前进程复制进程 PID 信息
      • 3.2.2 创建每进程的 PID 管理数据 (`struct pid`) 并初始化
      • 3.2.3 绑定进程和其相关的 PID 管理数据
    • 3.3 进程的 PID 建立过程一般化
  • 4. 进程 PID 管理相关接口
  • 5. 进程 PID 的层级结构
  • 6. 命名空间观察工具

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 进程 PID 相关数据结构

/* include/linux/pid_namespace.h *//* 进程 PID 命名空间对象 */
struct pid_namespace {...struct pidmap pidmap[PIDMAP_ENTRIES]; /* 空闲 PID 管理位图 */...struct kmem_cache *pid_cachep; /* 当前层级 struct pid 对象分配缓存,alloc_pid() 从中分配 struct pid 对象 */unsigned int level; /* PID 命名空间 层级编号,从 0 开始编号 */struct pid_namespace *parent; /* 父级 pid_namespace (@level - 1) */...
};
/* include/linux/nsproxy.h *//* 命名空间代理对象: 包含指向各类型命名空间对象的指针 */
struct nsproxy {...struct pid_namespace *pid_ns_for_children; /* 进程关联的 PID 命名空间 */...
};
enum pid_type
{PIDTYPE_PID, /* 进程 PID */PIDTYPE_PGID, /* 进程组 ID(进程组 领头进程的 PID) */PIDTYPE_SID, /* session ID */PIDTYPE_MAX,/* only valid to __task_pid_nr_ns() *//** 线程组 ID(线程组 group leader 进程的 PIDTYPE_PID 类型 PID),* 可通过进程所在 线程组的 group leader 进程 task_struct::group_leader* 的 task_struct::pids[PIDTYPE_PID] 信息获取,所以无需在进程中维护一个* __PIDTYPE_TGID 的 pid 信息,这也是 __PIDTYPE_TGID 定义在 PIDTYPE_MAX* 之后的原因。* 但这一点,在更新版本的内核中已经有所变化。*/__PIDTYPE_TGID
};/* include/linux/pid.h */
struct upid {/* Try to keep pid_chain in the same cacheline as nr for find_vpid */int nr; /* getpid(), gettid() 等 API 返回的值,来自于这里 */struct pid_namespace *ns; /* 关联的 PID 命名空间 */struct hlist_node pid_chain; /* 用于挂接到全局 PID 哈希表 pid_hash[] */
};struct pid {...unsigned int level; /* 所属 pid_namespace 的层级编号。level 从 0 开始编号 *//** 使用当前 struct pid 的 任务列表:* (1) tasks[PIDTYPE_PID]*     使用当前 struct pid 作为 PID 的进程列表。* (2) tasks[PIDTYPE_PGID]*     使用当前 struct pid 作为 PGID 的进程列表。*     同一 pid_namespace(即同一层级) 内的所有进程共享 pid_namespace 内*     首进程的 struct pid 。* (3) tasks[PIDTYPE_SID]*     使用当前 struct pid 作为 SID 的进程列表。*     同一 pid_namespace(即同一层级)内的所有进程共享 pid_namespace 内*     首进程的 struct pid 。* * 这 3 个哈希链表的构建细节参考函数 attach_pid(), 每个进程通过* struct task_struct::pids[PIDTYPE_*].node 挂接到 struct pid::tasks[PIDTYPE_*] * 哈希链表,PIDTYPE_* 取值为 {PIDTYPE_PID, PIDTYPE_PGID, PIDTYPE_SID} 。*/struct hlist_head tasks[PIDTYPE_MAX];.../** numbers[] 的长度和其所属的 PID 命名空间的层级有关,其长度为 level + 1,* 这由 struct upid 关联的 PID 命名空间 struct upid::ns 中,struct pid * 分配缓存 struct pid_namespace::pid_cachep 决定。* 更多细节见后面的代码分析。*/struct upid numbers[1];
};struct pid_link {struct hlist_node node; /* 用来将进程添加到所用 struct pid 的 tasks[PIDTYPE_*] 哈希链表 */struct pid *pid;
};
/* include/linux/sched.h *//* 进程管理对象 */
struct task_struct {...struct task_struct  *group_leader; /* 【线程组】 leader 进程 */...struct nsproxy   *nsproxy; /* 进程关联的各种命令空间,这里只关注 PID 命名空间 (pid_namespace) */...struct pid_link   pids[PIDTYPE_MAX]; /* 进程的 PID, PGID, SID, TGID 管理数据 */...
};

对上面这些数据结构的作用,择重做一个扼要介绍:

  • struct pid_namespace
    PID 命名空间。一方面,PID 命名空间用来实现 PID 隔离,允许进程在不同的PID 命名空间中有各自独立的 PID;另一方面,PID 命名空间也实现了 PID 的层级结构。PID 命名空间 组织结构如下图:

在这里插入图片描述

上图中,数据标记的格式为:level.PID

. {0.1,0.2,0.3} 表示有 3 个进程,位于 `level 0 PID 命名空间`,它们的 `PID` 分别为 {1,2,3}. {0.4 1.1, 0.5 1.2} 表示有 2 个进程,位于 `level 1 PID 命名空间`,它们在 `level 0 PID 命名空间` 的 PID 分别为 {4,5};它们在 `level 1 PID 命名空间` 的 PID 分别为 {1,2}. {0.6 1.3 2.1,0.7 1.4 2.2} 表示有 2 个进程,位于 `level 2 PID 命名空间`,它们在 `level 0 PID 命名空间` 的 PID 分别为 {6,7};它们在 `level 1 PID 命名空间` 的 PID 分别为 {3,4};它们在 `level 2 PID 命名空间` 的 PID 分别为 {1,2}
  • struct upid
    主要用来记录进程在某一PID 命名空间中的 PID (struct upid::nr),以及 PID 所在的PID 命名空间 (struct upid::ns)。

  • struct pid
    主要用来记录进程所处PID 命名空间层级(struct pid::level),以及在所有PID 命名空间层级中的 PID (struct pid::numbers[]) 。

3. 进程 PID 的构建

3.1 第一个进程 PID 构建

Linux 系统中第一个进程init_task,其 PID 是静态构建的。细节如下:

/* init/init_task.c *//* Initial task structure */
struct task_struct init_task = INIT_TASK(init_task);
EXPORT_SYMBOL(init_task);
/* include/linux/init_task.h *//**  INIT_TASK is used to set up the first task table, touch at* your own risk!. Base=0, limit=0x1fffff (=2MB)*/
#define INIT_TASK(tsk) \
{... \.real_parent = &tsk,      \.parent  = &tsk,      \... \.group_leader = &tsk,      \... \.nsproxy = &init_nsproxy, /* 进程 PID 命名空间管理数据 */ \... \/* 进程 PID 管理数据 */ \.pids = {       \[PIDTYPE_PID]  = INIT_PID_LINK(PIDTYPE_PID),  \[PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID),  \[PIDTYPE_SID]  = INIT_PID_LINK(PIDTYPE_SID),  \},        \... \
}

init_task 的 PID 命名空间管理数据:

/* kernel/nsproxy.c */struct nsproxy init_nsproxy = {....pid_ns_for_children = &init_pid_ns, /* init_task 的 PID 命名空间管理数据 */...
};
/* kernel/pid.c */struct pid_namespace init_pid_ns = { /* 系统第一个、位于 level 0 的 PID 命名空间对象 */....level = 0, /* init_task 位于 level 0 PID 命名空间 */.child_reaper = &init_task,...
#ifdef CONFIG_PID_NS.ns.ops = &pidns_operations,
#endif
};

init_task 的 PID 管理数据:

/* include/linux/init_task.h */#define INIT_PID_LINK(type)      \
{        \.node = {      \.next = NULL,     \.pprev = NULL,     \},       \.pid = &init_struct_pid,    \
}
/* kernel/pid.c */struct pid init_struct_pid = INIT_STRUCT_PID;
#define INIT_STRUCT_PID {      \.count   = ATOMIC_INIT(1),    \.tasks  = {      \{ .first = NULL },     \{ .first = NULL },     \{ .first = NULL },     \},        \.level  = 0,  /* init_task 处于 level 0 的 PID 命名空间 */    \.numbers = { \{      \.nr  = 0,  /* init_task 的 PID, PGID, SID 均为 0 */   \.ns  = &init_pid_ns,  /* init_task 所属的 PID 命名空间 */  \.pid_chain = { .next = NULL, .pprev = NULL }, \}, \}        \
}

从上面的代码可以了解到:

. init_task 处于 level 0 的 PID 命名空间
. init_task 的 PID,PGID,SID 均为 0
. init_task 位于 PID 命名空间 init_pid_ns 内

说了半天,还不知道 Linux 系统中第一个进程init_task,到底是哪位。嗯,start_kernel() 熟悉吧?在 BOOT CPU 上运行的 start_kernel() 所在的执行序列,就是 init_task

3.2 第二个进程 PID 的构建过程

init_taskstart_kernel() 中执行部分系统初始化工作后,将创建系统中第二个进程来执行剩余的初始化工作:

start_kernel().../** 全局 PID 哈希表空间分配,用来存储系统中* 所有层级 PID 命名空间中所有 struct upid 。*/pidhash_init()pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,HASH_EARLY | HASH_SMALL | HASH_ZERO, &pidhash_shift, NULL, 0, 4096);.../** . 设置默认允许的最大、最小 PID 值* . 分配 level 0 PID 命名空间 进程 PID 管理位图* . 创建 level 0 PID 命名空间 的 struct pid 分配缓存*/pidmap_init().../* bump default and minimum pid_max based on number of cpus */pid_max = min(pid_max_max, max_t(int, pid_max, PIDS_PER_CPU_DEFAULT * num_possible_cpus()));pid_max_min = max_t(int, pid_max_min,PIDS_PER_CPU_MIN * num_possible_cpus());pr_info("pid_max: default: %u minimum: %u\n", pid_max, pid_max_min);/* 分配 level 0 PID 命名空间 进程 PID 管理位图 */init_pid_ns.pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL);/* Reserve PID 0. We never call free_pidmap(0) *//* 保留 level 0 PID 命名空间 进程 PID 0 */set_bit(0, init_pid_ns.pidmap[0].page);atomic_dec(&init_pid_ns.pidmap[0].nr_free);/* 创建 level 0 PID 命名空间 的 struct pid 分配缓存 */init_pid_ns.pid_cachep = KMEM_CACHE(pid,SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT);.../* 创建系统中 第二个进程 来执行剩余的初始化工作 */rest_init()pid = kernel_thread(kernel_init, NULL, CLONE_FS);_do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,(unsigned long)arg, NULL, NULL, 0);
/* kernel/fork.c */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;...long nr;...p = copy_process(clone_flags, stack_start, stack_size,child_tidptr, NULL, trace, tls, NUMA_NO_NODE);...if (!IS_ERR(p)) {...struct pid *pid;...pid = get_task_pid(p, PIDTYPE_PID);nr = pid_vnr(pid); /* 进程 在当前层级 PID 命名空间中 的 PID */...put_pid(pid);} else {nr = PTR_ERR(p);}return nr; /* 返回 进程 在当前层级 PID 命名空间 中 的 PID */
}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)
{int retval;struct task_struct *p;.../* 分配进程结构体 task_struct,复制当前进程 @current 的信息到新进程 @p (包括 PID 数据) */p = dup_task_struct(current, node);.../* * 在设置了 CLONE_NEWNS,...,CLONE_NEWPID,...  等标记的情形, * 按需新建各种 namespace (包括 PID 命名空间 pid_namespace) 。* * 创建 第二个进程时没有设置对应标志位,所以不会创建新的 PID 命名空间。*/retval = copy_namespaces(clone_flags, p);.../* 为新进程分配 PID 管理数据 */if (pid != &init_struct_pid) {pid = alloc_pid(p->nsproxy->pid_ns_for_children);...}.../* ok, now we should be set up.. */p->pid = pid_nr(pid); /* 记录进程在 level 0 PID 命名空间 中 的 PID */if (clone_flags & CLONE_THREAD) { /* 线程组内 非 group leader 进程 */.../* 设置 线程组内 非 group leader 进程 的 group leader */p->group_leader = current->group_leader;p->tgid = current->tgid;} else { /* 线程组的 group leader 进程 */...p->group_leader = p; /* 线程组内 group leader 进程 的 group leader 为自身 */p->tgid = p->pid; /* 线程组 group leader 进程: tgid == pid */}...if (likely(p->pid)) {...init_task_pid(p, PIDTYPE_PID, pid); /* 设置进程 @p 的 PID 信息 */if (thread_group_leader(p)) { /* 如果是 线程组 group leader, */init_task_pid(p, PIDTYPE_PGID, task_pgrp(current)); /* 设置进程 @p 的 PGID 信息 */init_task_pid(p, PIDTYPE_SID, task_session(current)); /* 设置进程 @p 的 SID 信息 */...attach_pid(p, PIDTYPE_PGID); /* 将进程 @p 添加到关联 struct pid 的 PGID 类型哈希链表 */attach_pid(p, PIDTYPE_SID); /* 将进程 @p 添加到关联 struct pid 的 SID 类型哈希链表 */...} else {...}attach_pid(p, PIDTYPE_PID); /* 将进程 @p 添加到关联 struct pid 的 PID 类型哈希链表 */nr_threads++;}...
}

3.2.1 从当前进程复制进程 PID 信息

/* kernel/fork.c */p = dup_task_struct(current, node);struct task_struct *tsk;...tsk = alloc_task_struct_node(node); /* 分配进程结构体 task_struct */.../* !!! 复制旧进程的 task_struct 数据 @orig 到 新进程 @tsk (包括 PID 信息) */err = arch_dup_task_struct(tsk, orig);*dst = *src;return 0;...

3.2.2 创建每进程的 PID 管理数据 (struct pid) 并初始化

通过接口 alloc_pid() 创建每进程的 PID 管理数据 (struct pid) 并初始化:

/* kernel/pid.c */struct pid *alloc_pid(struct pid_namespace *ns)
{struct pid *pid;enum pid_type type;int i, nr;struct pid_namespace *tmp;struct upid *upid;int retval = -ENOMEM;pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL); /* 分配 struct pid 对象 */...tmp = ns;pid->level = ns->level;/* 在每个层级的 pid_namespace 中, 分配一个 进程 PID */for (i = ns->level; i >= 0; i--) {nr = alloc_pidmap(tmp); /* 从当前层级 @i 的 pid_namespace 中分配一个空闲的 进程 PID */...pid->numbers[i].nr = nr; /* 记录在当前层级 @i 的 pid_namespace 中的 进程 PID */pid->numbers[i].ns = tmp; /* 记录当前层级 @i 关联的 pid_namesapce 对象 */tmp = tmp->parent; /* 进入父级 pid_namespace (@i - 1) */}.../* 使用 @pid 的 PID,PGID,SID 类型 进程列表 初始为空 */for (type = 0; type < PIDTYPE_MAX; ++type)INIT_HLIST_HEAD(&pid->tasks[type]);upid = pid->numbers + ns->level;spin_lock_irq(&pidmap_lock);.../* 将每层级 pid_namespace 为进程分配的 PID, 插入到全局 PID 哈希表 @pid_hash */for ( ; upid >= pid->numbers; --upid) {hlist_add_head_rcu(&upid->pid_chain,&pid_hash[pid_hashfn(upid->nr, upid->ns)]);upid->ns->nr_hashed++;}spin_unlock_irq(&pidmap_lock);return pid; /* 返回分配的 struct pid 对象 */...
}

3.2.3 绑定进程和其相关的 PID 管理数据

通过接口 init_task_pid() 设定进程 PID、PGID、SID 管理数据,实现将 PID 管理数据 struct pid 绑定到进程;通过接口 attach_pid() 将进程添加到 PID 管理数据 struct pidPID、PGID、SID 类型哈希链表。如此,实现了进程和 PID 管理数据 struct pid 的双向绑定。其中,PGID、SID 的绑定,仅针对线程组的 group leader 进程,非 group leaderPGID、SID 信息,通过 dup_task_struct() 间接或直接复制自其所在 PID 命名空间对象 struct pid_namespace (如 init_pid_ns) 。

init_task_pid(p, PIDTYPE_PID, pid); /* 设置进程 @p 的 PID 信息 */
init_task_pid(p, PIDTYPE_PGID, task_pgrp(current)); /* 设置进程 @p 的 PGID 信息 */
init_task_pid(p, PIDTYPE_SID, task_session(current)); /* 设置进程 @p 的 SID 信息 */static inline struct pid *task_pgrp(struct task_struct *task)
{/* @group_leader 的设置,见前面 copy_process() */return task->group_leader->pids[PIDTYPE_PGID].pid;
}static inline struct pid *task_session(struct task_struct *task)
{/* @group_leader 的设置,见前面 copy_process() */return task->group_leader->pids[PIDTYPE_SID].pid;
}static inline void
init_task_pid(struct task_struct *task, enum pid_type type, struct pid *pid)
{task->pids[type].pid = pid;
}attach_pid(p, PIDTYPE_PGID); /* 将进程 @p 添加到关联 struct pid 的 PGID 类型哈希链表 */
attach_pid(p, PIDTYPE_SID); /* 将进程 @p 添加到关联 struct pid 的 SID 类型哈希链表 */
attach_pid(p, PIDTYPE_PID); /* 将进程 @p 添加到关联 struct pid 的 PID 类型哈希链表 */void attach_pid(struct task_struct *task, enum pid_type type)
{struct pid_link *link = &task->pids[type];hlist_add_head_rcu(&link->node, &link->pid->tasks[type]);
}

经过一番操作,在当前层级的 PID 命名空间,形成了如下图的 PID 数据结构(以 level 0 举例,这正是系统初始创建第二个进程后的情形):

在这里插入图片描述

在这里插入图片描述

3.3 进程的 PID 建立过程一般化

系统中其它进程 PID 的建立过程,和 3.2 中讨论的系统中第二个进程的创建过程,并无本质的不同,读者可自行进行转化理解,本文将不再赘述。

4. 进程 PID 管理相关接口

本小节对 Linux 系统中的进程 PID 管理 API 接口择重进行一些扼要说明。

/* include/linux/pid.h *//* 从 PID 命名空间 @ns 的 pid 缓存,创建一个 struct pid 对象,并插入到全局哈希表 @pid_hash */
extern struct pid *alloc_pid(struct pid_namespace *ns);/* 将进程 @task 添加到 struct pid 类型 @pid_type 哈希表 头部 */
extern void attach_pid(struct task_struct *task, enum pid_type);
/* include/linux/sched.h *//* 返回进程 @task 的 struct pid */
static inline struct pid *task_pid(struct task_struct *task)
{return task->pids[PIDTYPE_PID].pid;
}.../* 返回 进程 @task 所在 [进程组] 的 struct pid */
static inline struct pid *task_pgrp(struct task_struct *task)
{return task->group_leader->pids[PIDTYPE_PGID].pid;
}/* 返回 进程 @task 所在 [session] 的 struct pid */
static inline struct pid *task_session(struct task_struct *task)
{return task->group_leader->pids[PIDTYPE_SID].pid;
}/* 返回进程 @tsk 在 level 0 pid_namespace 中的 PID */
static inline pid_t task_pid_nr(struct task_struct *tsk)
{return tsk->pid;
}/* 返回 进程 @tsk 在 指定 pid_namespace 中 @ns 的 PID */
static inline pid_t task_pid_nr_ns(struct task_struct *tsk, struct pid_namespace *ns)
{return __task_pid_nr_ns(tsk, PIDTYPE_PID, ns);
}/* 返回 进程 @tsk 在当前 pid_namespace 中的 PID */
static inline pid_t task_pid_vnr(struct task_struct *tsk)
{return __task_pid_nr_ns(tsk, PIDTYPE_PID, NULL);
}...

5. 进程 PID 的层级结构

在章节 2.struct pid_namespace 的描述中,用一张图片描述了 PID 的层次结构。struct pid_namespace 的如下字段, 共同构建了 PID 的层次结构:

/* include/linux/pid_namespace.h */struct pid_namespace {...struct kmem_cache *pid_cachep; /* alloc_pid() 分配缓存,决定了 struct pid::numbers[] 数组的长度 */unsigned int level; /* 所属层次编号,从 0 开始编号 */struct pid_namespace *parent; /* 父级 pid_namespace (@level - 1) */...
};

创建新的 PID 命名空间,可通过系统调用 setns()unshare(),或通过 clone() 带上 CLONE_NEWNS, CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWPID, CLONE_NEWNET, CLONE_NEWCGROUP 标志位之一,间接的创建。本文对 setns()clone() 创建 PID 命名空间的过程做简要分析。先看一下 setns() 的创建过程:

/* kernel/nsproxy.c */SYSCALL_DEFINE2(setns, int, fd, int, nstype)
{struct task_struct *tsk = current;struct nsproxy *new_nsproxy;......new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs);...
}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;...new_nsp = create_nsproxy();.../** 这里忽略其它类型 namespace 对象的创建过程,只关注 * PID 命名空间对象 struct pid_namespace 的创建过程:*/new_nsp->pid_ns_for_children =copy_pid_ns(flags, user_ns, tsk->nsproxy->pid_ns_for_children);...return new_nsp;...
}
/* kernel/pid_namespace.c */struct pid_namespace *copy_pid_ns(unsigned long flags,struct user_namespace *user_ns, struct pid_namespace *old_ns)
{...return create_pid_namespace(user_ns, old_ns);
}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 i;int err;...ns = kmem_cache_zalloc(pid_ns_cachep, GFP_KERNEL); ...ns->pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL); /* 分配 PID 分配管理位图空间 */.../** 为当前层级 @level 创建 struct pid 的 kmem_cache 。* 这里的 level + 1 决定了了 struct pid::numbers[] 数组的长度,* 即每个 level 的 pid_namespace 有一个 PID,正如章节 2. 的图片* 所描述的层次结构那样。*/ns->pid_cachep = create_pid_cachep(level + 1);...ns->level = level; /* 设置 pid_namespace 层级编号 */ns->parent = get_pid_ns(parent_pid_ns); /* 设置父级 pid_namespace (@level - 1) */...return ns; /* 返回新建的 PID 命名空间对象 */
}static struct kmem_cache *create_pid_cachep(int nr_ids)
{struct pid_cache *pcache;struct kmem_cache *cachep;...pcache = kmalloc(sizeof(struct pid_cache), GFP_KERNEL);.../* * 为 struct pid_namespace 创建 struct pid 分配缓存。* 从这里看到,新建的 struct pid 分配缓存,struct pid::numbers[] * 数组的长度为 @nr_ids ,也即 @level + 1 。*/snprintf(pcache->name, sizeof(pcache->name), "pid_%d", nr_ids);cachep = kmem_cache_create(pcache->name,sizeof(struct pid) + (nr_ids - 1) * sizeof(struct upid),0, SLAB_HWCACHE_ALIGN, NULL);...pcache->nr_ids = nr_ids;pcache->cachep = cachep;......return pcache->cachep;
}

clone() 调用过程类似:

SYSCALL_DEFINE5(clone, ...)
{return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
}_do_fork()copy_process()retval = copy_namespaces(clone_flags, p);/* 没有设置对相应标志位,则仅仅增加当前 nsproxy 的引用计数 */if (likely(!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |CLONE_NEWPID | CLONE_NEWNET |CLONE_NEWCGROUP)))) {get_nsproxy(old_ns);return 0;}...// 后续过程同 setns()new_ns = create_new_namespaces(flags, tsk, user_ns, tsk->fs);...

6. 命名空间观察工具

lsns

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/45595.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Oracle】实验三 Oracle数据库的创建和管理

【实验目的】 掌握Oracle数据库的创建方法使用DBCA创建数据库在数据库中装入SCOTT用户及其表 【实验内容】 使用DBCA创建数据库&#xff0c;名为MYDB&#xff0c;找到其初始化文件(文本型和服务器型文件都要找到)&#xff0c;查看各类默认位置并记录下来(包括物理文件所在目…

LINUX系统编程:基于环形队列和信号量的生产者消费者模型

目录 1.环形队列 2.加上信号量的理解 3.代码 1.环形队列 环形队列使用vector封装出来的。 环形队列可以实现并发生产和消费&#xff0c;就是在消费的同时也可以生产。 这个是建立在生产者消费者位置不重合的情况下。 因为位置重合之后&#xff0c;环形队列为空或者满&#xf…

Linux DRM 那些事 - HDMI 接口 DTS 配置

本文基于RockPI 4A单板Debian系统 Linux 4.4 内核介绍DRM框架HDMI接口DTS配置。 在DTS中主要实现&#xff1a;HDMI的使能、VOP绑定、IOMUX引脚配置和HDMI控制器配置。 一、HDMI 配置 文件&#xff1a;arch/arm64/boot/dts/rockchip/rk3399-rock-pi-4.dtsi #include "rk3…

C++ 宏

C中的宏是一种预处理指令&#xff0c;用于在编译时将代码中的标识符替换为指定的文本。 #define 指令 1.无参宏定义 无参宏的宏名后不带参数。 其定义的一般形式为&#xff1a; #define 标识符 字符串 其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命…

LLM 合成数据生成完整指南

大型语言模型是强大的工具&#xff0c;不仅可以生成类似人类的文本&#xff0c;还可以创建高质量的合成数据。这种能力正在改变我们进行 AI 开发的方式&#xff0c;特别是在现实世界数据稀缺、昂贵或隐私敏感的情况下。在本综合指南中&#xff0c;我们将探索 LLM 驱动的合成数据…

C语言——流程控制:if...else、switch...case

控制类语句&#xff1a; 逻辑运算符&#xff1a; 选择语句&#xff1a; if...else&#xff1a; if&#xff08;&#xff09;括号内的内容终究会被转换成0,1&#xff0c;满足的话即为1&#xff0c;不满足的话为0。因此要注意&#xff0c;&#xff08;&#xff09;括号内因为条件…

简单实现一个本地ChatGPT web服务(langchain框架)

简单实现一个本地ChatGPT 服务&#xff0c;用到langchain框架&#xff0c;fastapi,并且本地安装了ollama。 依赖安装&#xff1a; pip install langchain pip install langchain_community pip install langchain-cli # langchain v0.2 2024年5月最新版本 pip install bs4 pi…

ChatGPT摆脱“AI味”:全面提升写作质感

ChatGPT在各种写作、创作场景都发挥了很大的价值&#xff0c;即使中文语料库占比不到5%&#xff0c;也能生成流畅的中文文本。但随着使用的深入&#xff0c;大家也逐渐发现了机器生成的内容&#xff0c;往往带有一种僵硬、刻板的“AI味”&#xff0c;尤其在论文、自媒体写作中&…

算法力扣刷题记录 四十三【最大、最小深度问题】

前言 本文学习树的深度问题&#xff1a;二叉树&#xff08;N叉树&#xff09;最大深度、最小深度&#xff1b; 记录 三十九【层序遍历模版应用二】中解决过二叉树的最大深度和最小深度题目。思路是按层遍历&#xff1a; 最大深度&#xff0c;相当于层序遍历结束&#xff1b;…

ZBLOG程序怎么天收录?本人亲自试过请看以下教程(zblog怎么样)

您为管理员组&#xff0c;请查看下方隐藏内容&#xff01; 先去ZBLOG官网下载ZBLOG程序 直达地址https://www.zblogcn.com/ 安装到宝塔里 安装好了之后打开zblog的后台 点开应用中心搜索CMS自适应资讯主题免费 安装即可 安装了之后配置主题内容 有经验者可以去吧动态改成…

pnpm9.5.0(catalog协议)

catalog(目录协议) 目录是工作区功能&#xff0c;用于将依赖版本范围定义为可重用常量&#xff0c;目录中定义的常量可以在package.json中使用&#xff0c; 结合 pnpm-workspace.yaml使用 定义pnpm-workspace.yaml packages&#xff1a;定义多仓库 packages:- packages/*cata…

Flink异常:org/apache/hadoop/hive/ql/parse/SemanticException

在flink项目中跑 上面这段代码出现如下这个异常&#xff0c; java.lang.NoClassDefFoundError: org/apache/thrift/TException 加上下面这个依赖后不报错 <dependency> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId…

【逆向基础】十、工具分享之DIE(Detect It Easy)

一、简介 DIE&#xff08;Detect It Easy&#xff09;是一款可以轻松检测PE文件的程序&#xff1b;其主要作用是查壳&#xff0c;并将pe文件的内容解析出来&#xff0c;包括PE文件中包含的导入函数、导出函数的名称及地址&#xff0c;入口函数地址等&#xff0c;是技术人员分析…

lua 脚本语言 : 基础到高级语法

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

JupyterNotebook中导出当前环境,并存储为requirements.txt

​使用Anaconda管理Python环境时&#xff0c;可以轻松地导出环境配置&#xff0c;以便在其他机器或环境中重新创建相同的环境。可以通过生成一个environment.yml文件实现的&#xff0c;该文件包含了环境中安装的所有包及其版本。但是&#xff0c;常常在一些课程中JupyterNotebo…

微信小程序毕业设计-学习资料库系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

ProbTS:时间序列预测的统一评测框架

编者按&#xff1a;如今&#xff0c;时间序列预测在健康、能源、商业、气候等多个行业发挥着至关重要的作用。它不仅影响着相关资源的分配和调度&#xff0c;还影响着行业的管理和运营决策。但是现有的时间序列预测方法通常缺乏对基础预测需求的全面考虑&#xff0c;无论是经典…

JavaScript青少年简明教程:开发工具与运行环境

JavaScript青少年简明教程&#xff1a;开发工具与运行环境 JavaScript是一种基于对象和事件驱动且具有安全性能的脚本语言。使用它和HTML结合可以开发出交互式的Web页面。 脚本语言是为了缩短传统的编写-编译-链接-运行过程而创建的计算机编程语言。脚本通常是解释执行而非编…

阿里巴巴矢量图标库使用

阿里巴巴矢量图标库官网 添加图标到购物车 悬浮到图标上面会有个购物车icon,点击一下就可以添加购物车了 添加图标到项目 添加完购物车后,右上角会有当前在购物车的数量,点击右上角购物车icon,在新弹窗内点击添加至项目,选择添加到哪个项目(没有项目就创建一个),点击完成,…

Milvus 核心设计(1) ---- 数据一致性的等级及使用场景

目录 背景 Milvus的数据一致性 设置数据一致性等级 等级类型 PACELC定理 level 详细解释 Strong Bounded staleness Session Eventually 总结 背景 分布式上的可扩展性是个比较重要的concept。Chroma 核心之前写过了,他的最大优势在于轻量级且好用。Milvus相对Ch…