2024年Linux最全Linux进程管理之pid_struct pid(1),字节跳动面试真题

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

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

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

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

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

需要这份系统化的资料的朋友,可以点击这里获取!

* 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()

生成struct pid:



struct task_struct *p;
struct pid *pid = alloc_pid(p->nsproxy->pid_ns_for_children)


生成pid\_t pid:



struct task_struct *p = copy_process();
struct pid * pid = get_task_pid(p, PIDTYPE_PID);
long nr = pid_vnr(pid);
put_pid(pid);
return nr;



pid_t pid_vnr(struct pid *pid)
{
return pid_nr_ns(pid, task_active_pid_ns(current));
}
EXPORT_SYMBOL_GPL(pid_vnr);



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);


### 3.2 alloc\_pid



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;

(1)
pid = kmem\_cache\_alloc(ns->pid_cachep, GFP_KERNEL);
if (!pid)
	return ERR\_PTR(retval);

(2)
tmp = ns;
pid->level = ns->level;
for (i = ns->level; i >= 0; i--) {
	nr = alloc\_pidmap(tmp);
	if (nr < 0) {
		retval = nr;

最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

资料预览

给大家整理的视频资料:

给大家整理的电子书资料:

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

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

需要这份系统化的资料的朋友,可以点击这里获取!

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

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

资料预览

给大家整理的视频资料:

[外链图片转存中…(img-t7WBXEP2-1715165949328)]

给大家整理的电子书资料:

[外链图片转存中…(img-CFx8oMV2-1715165949328)]

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

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

需要这份系统化的资料的朋友,可以点击这里获取!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值