Linux进程管理之pid_struct pid,你连原理都还没弄明白

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

* 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 cgroup_namespace *cgroup_ns;
};


从上面我们可以看到 struct pid\_namespace 的命名和其他的有点不一样。注释说明:pid namespace是个例外——使用task\_active\_pid\_ns来访问pid namespace。这里的 pid namespace是children将使用的namespace。


task\_active\_pid\_ns通过struct task\_struct来获取struct pid\_namespace:



struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)
{
return ns_of_pid(task_pid(tsk));
}
EXPORT_SYMBOL_GPL(task_active_pid_ns);


通过struct task\_struct获取struct pid:



static inline struct pid *task_pid(struct task_struct *task)
{
return task->pids[PIDTYPE_PID].pid;
}



struct pid_link
{
struct hlist_node node;
struct pid *pid;
};



struct task_struct {

/* PID/PID hash table linkage. */
struct pid_link pids[PIDTYPE_MAX];

}


通过struct pid获取struct pid\_namespace,ns\_of\_pid()返回分配了指定pid的pid namespace。



/*
* ns_of_pid() returns the pid namespace in which the specified pid was
* allocated.
*
* NOTE:
* ns_of_pid() is expected to be called for a process (task) that has
* an attached ‘struct pid’ (see attach_pid(), detach_pid()) i.e @pid
* is expected to be non-NULL. If @pid is NULL, caller should handle
* the resulting NULL pid-ns.
*/
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;
}



struct pid_namespace {
struct kref kref;
struct pidmap pidmap[PIDMAP_ENTRIES];
struct rcu_head rcu;
int last_pid;
unsigned int nr_hashed;
struct task_struct *child_reaper;
struct kmem_cache *pid_cachep;
unsigned int level;
struct pid_namespace *parent;
#ifdef CONFIG_PROC_FS
struct vfsmount *proc_mnt;
struct dentry *proc_self;
struct dentry *proc_thread_self;
#endif
#ifdef CONFIG_BSD_PROCESS_ACCT
struct fs_pin *bacct;
#endif
struct user_namespace *user_ns;
struct ucounts *ucounts;
struct work_struct proc_work;
kgid_t pid_gid;
int hide_pid;
int reboot; /* group exit code if this pidns was rebooted */
struct ns_common ns;
};


kref:是一个引用计数器,代表此命名空间在多少进程中被使用。  
 该结构体只有一个成员,为什么只有一个成员也要封装成结构呢?  
 这是为了防止原子变量被不小心直接赋值,这样封装后就不能直接操作该值,让开发者通过对应的函数接口来操作该原子变量。



struct kref {
atomic_t refcount;
};


比如,必须通过调用kref\_init函数来初始化该原子变量:



/**
* kref_init - initialize object.
* @kref: object in question.
*/
static inline void kref_init(struct kref *kref)
{
atomic_set(&kref->refcount, 1);
}


pidmap[]:记录当前系统的PID使用情况。  
 last\_pid:记录上一次分配给进程的PID值。  
 child\_reaper:每个PID命名空间都有一个类似于全局的init进程,其作用和init进程一样,一个局部的init进程。比如:init进程对孤儿进程调用wait,命名空间的局部init进程也要完成该工作。child\_reaper保存了指向该局部init进程的task\_struct的指针。  
 pid\_cachep:指向struct pid高速缓存的指针,sla(u)b分配器指针,slab对象为 struct pid 。



struct pid_namespace *ns;
struct pid *pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);


parent:是指向父命名空间的指针。  
 level:表示当前命名空间在命名空间层次结构中的深度,初始命名空间的level为0,该命名空间的子空间level为1,下一层的子空间level为2,以此类推。level的计算比较重要,因为level较高的命名空间中的ID,对level较低的命名空间来说是可见的,通过给定的level设置,内核即可推断进程会关联到多少个ID。level = 0,代表全局命名空间。


struct pid\_namespace命名空间结构体的分配采用的是sla(u)b分配器进行分配,pid\_ns\_cachep为指向struct pid\_namespace的高速缓存的指针,slab对象为struct pid\_namespace。



static struct kmem_cache *pid_ns_cachep;

static __init int pid_namespaces_init(void)
{
pid_ns_cachep = KMEM_CACHE(pid_namespace, SLAB_PANIC);

return 0;
}


等价于:



pid_ns_cachep = kmem_cache_create(“pid_namespace”, sizeof(struct pid_namespace), __alignof__(struct pid_namespace), (0x00040000UL), NULL)


## 二、struct pid简介


### 2.1 struct pid



// linux-4.10.1/include/linux\pid.h

struct pid
{
atomic_t count;
unsigned int level;
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
struct upid numbers[1];
};


struct pid是内核对PID的内部表示:


count:代表当前使用此struct pid的任务数量。  
 level:表示可以看到该进程的命名空间数目,也就是进程所属的进程号命名空间的层次。level = 0,代表全局命名空间。  
 tasks:是当前使用此struct pid的任务列表。tasks是一个数组,每个数组项都是一个哈希表头,对应于一个进程ID的类型。因为每一个进程ID可能属于几个进程,所有共享同一给定进程ID的task\_struct实例都通过该哈希表连接起来。  
 numbers是一个struct upid实例的数组,每个数组项对应一个命名空间。这里这个数组项只有1个,只有1个时代表这个进程只属于全局的命名空间。但是这个数组位于结构体末端,这样就可以分配更多的内存空间,即可以向这个numbers数组增加更多成员,这样这个进程就可以属于多个局部命名空间了。


其中PIDTYPE\_MAX表示PID类型:



enum pid_type
{
PIDTYPE_PID, //进程的进程号
PIDTYPE_PGID, //进程组领头进程的进程号
PIDTYPE_SID, //会话领头进程的进程号
PIDTYPE_MAX //表示进程号类型的数目 == 3
};


一个进程可能在多个命名空间中可见。而在各个命名空间的局部ID也都不相同


备注:可以使用 int nr(局部进程PID的数值) 和 struct pid\_namespace \*ns 通过 find\_pid\_ns() 找到 struct pid。


### 2.2 struct upid



struct upid {
/* Try to keep pid_chain in the same cacheline as nr for find_vpid */
int nr;
struct pid_namespace *ns;
struct hlist_node pid_chain;
};


struct upid则表示特定的命名空间的可见信息,用于获取struct pid的数值id,在特定的名称空间中被看到。  
 nr:进程ID的数值,保存进程的局部PID值。  
 ns:指向该进程ID所属的命名空间的指针。  
 pid\_chain:哈希表节点,哈希表溢出链表。


所有struct upid实例都保存在一个哈希表中


注意:struct pid的tasks是struct hlist\_head是哈希表表头,struct upid中的struct hlist\_node是哈希表节点。


### 2.3 pid\_t pid/tgid


struct pid是进程ID的内核表现形式,pid\_t pid是用户空间可见的数值PID。内核提供了两者相互转换的辅助函数。


(1)  
 pid\_t pid 是全局的进程号ID,全局ID是在内核本身和初始化命名空间中的唯一ID号,在系统启动期间开始的init进程即属于初始命名空间。对于每一个ID类型,都有一个给定的全局ID,保证在整个系统中是唯一的。



struct task_struct {

pid_t pid;
pid_t tgid;

}


task\_struct结构体中的pid和tgid都是全局ID。


(2)  
 应用层调用getpid函数(返回当前进程的PID),实际上获取的是struct task\_struct 结构体的pid\_t tgid。  
 如果一个进程没有使用线程(即该进程只有一个主线程),则该进程的pid和tgid一样。如果一个进程有多个线程,这多个线程的pid是不一样的,但tgid是一样的。  
 内核中每个任务的 pid\_t pid 都是不一样。



/**
* sys_getpid - return the thread group id of the current process
*
* Note, despite the name, this returns the tgid not the pid. The tgid and
* the pid are identical unless CLONE_THREAD was specified on clone() in
* which case the tgid is the same in all threads of the same group.
*
* This is SMP safe as current->tgid does not change.
*/
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}



static inline pid_t task_tgid_vnr(struct task_struct *tsk)
{
return pid_vnr(task_tgid(tsk));
}



static inline struct pid *task_tgid(struct task_struct *task)
{
return task->group_leader->pids[PIDTYPE_PID].pid;
}


从源码中可以看到是获取线程组组长的pid,即tgid。


(3)  
 应用层调用gettid函数,获取线程标识,也就是获取线程的thread ID (TID),实际上是获取struct task\_struct 结构体的pid\_t pid。



/* Thread ID - the internal kernel “pid” */
SYSCALL_DEFINE0(gettid)
{
return task_pid_vnr(current);
}



static inline pid_t task_pid_vnr(struct task_struct *tsk)
{
return __task_pid_nr_ns(tsk, PIDTYPE_PID, NULL);
}



pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type,
struct pid_namespace *ns)
{
pid_t nr = 0;

rcu\_read\_lock();
if (!ns)
	ns = task\_active\_pid\_ns(current);
if (likely(pid\_alive(task))) {
	if (type != PIDTYPE_PID)
		task = task->group_leader;
	nr = pid\_nr\_ns(rcu\_dereference(task->pids[type].pid), ns);
}
rcu\_read\_unlock();

return nr;

}
EXPORT_SYMBOL(__task_pid_nr_ns);



pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{
struct upid *upid;
pid_t nr = 0;

if (pid && ns->level <= pid->level) {
	upid = &pid->numbers[ns->level];
	if (upid->ns == ns)
		nr = upid->nr;
}
return nr;

}
EXPORT_SYMBOL_GPL(pid_nr_ns);


(4)


对用应用层来说,在多线程进程中,所有线程都有相同的 PID,但每个线程都有唯一的 TID。这是因为应用层调用getpid函数返回的是pid\_t tgid,应用层调用gettid函数返回的是pid\_t pid。而在内核中,同一个进程的多线程,pid\_t tgid都是一样的,但是pid\_t pid都各不相同。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f434865f9d8e446a8367074f058e7467.png)


(5)  
 int nr是局部空间的进程ID号。局部ID属于某个特定的命名空间,不具备全局性,对于每个局部ID类型,在所属的命名空间内部有效,但类型相同、值也相同的ID可能出现在不同的命名空间。


如果struct pid中的成员 level = 0,表示没有局部命名空间,struct pid的struct upid numbers[1],也就只有一个结构体,那么struct upid中局部 int nr 就等于该任务(struct task\_struct)的 pid\_t pid 。


注意:在内核中,全局的进程号ID一般都用 pid\_t 类型描述。局部的进程号ID都用 int 类型 描述。  
 pid\_t 类型其实也是int 类型。



typedef int __kernel_pid_t;
typedef __kernel_pid_t pid_t;


## 三、PID的生成


### 3.1 \_do\_fork


(1) fork():该函数是一个系统调用,可以复制一个现有的进程来创建一个全新的进程,产生一个 task\_struct。  
 (2) pthread\_create():该函数是Glibc中的函数,然后调用clone()系统调用创建一个线程(又叫轻量级进程),产生一个 task\_struct。  
 (3)kthread\_create():创建一个新的内核线程,产生一个 task\_struct。


其实这三个API最后都会调用 \_do\_fork(),不同之处是传入给 \_do\_fork() 的参数不同(clone\_flags)。


\_do\_fork()中会调用alloc\_pid()生成struct pid,同时也会生成数值pid (pid\_t pid),作为\_do\_fork()函数的返回值。



_do_fork()
–>copy_process()
–>alloc_pid()

-->get\_task\_pid()
-->pid\_vnr()
-->put\_pid()


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/f647150f57dcb69720f0619167691bc1.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

lags)。


\_do\_fork()中会调用alloc\_pid()生成struct pid,同时也会生成数值pid (pid\_t pid),作为\_do\_fork()函数的返回值。



_do_fork()
–>copy_process()
–>alloc_pid()

-->get\_task\_pid()
-->pid\_vnr()
-->put\_pid()


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
[外链图片转存中...(img-sbouYyBZ-1713308633242)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值