最新Linux进程管理之pid_struct pid(1),阿里高级算法专家公开10份资料

最后的话

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

资料预览

给大家整理的视频资料:

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

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

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

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

文章目录

前言

一、struct task_struct

列出struct task_struct结构体中与pid有关的成员:

struct task\_struct {
	......
	pid\_t pid;
	pid\_t tgid;
	
	/\* PID/PID hash table linkage. \*/
	struct pid\_link pids[PIDTYPE_MAX];
	struct list\_head thread_group;

	/\* namespaces \*/
	struct nsproxy \*nsproxy;
}

/\*
 \* 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 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];


为了做好运维面试路上的助攻手,特整理了上百道 **【运维技术栈面试题集锦】** ,让你面试不慌心不跳,高薪offer怀里抱!

这次整理的面试题,**小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。**

![](https://img-blog.csdnimg.cn/img_convert/e4105c6067af165e0ab8a3a0af1db4c2.png)

本份面试集锦涵盖了

*   **174 道运维工程师面试题**
*   **128道k8s面试题**
*   **108道shell脚本面试题**
*   **200道Linux面试题**
*   **51道docker面试题**
*   **35道Jenkis面试题**
*   **78道MongoDB面试题**
*   **17道ansible面试题**
*   **60道dubbo面试题**
*   **53道kafka面试**
*   **18道mysql面试题**
*   **40道nginx面试题**
*   **77道redis面试题**
*   **28道zookeeper**

**总计 1000+ 道面试题, 内容 又全含金量又高**

*   **174道运维工程师面试题**

> 1、什么是运维?

> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?

> 3、现在给你三百台服务器,你怎么对他们进行管理?

> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点

> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?

> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?

> 7、Tomcat和Resin有什么区别,工作中你怎么选择?

> 8、什么是中间件?什么是jdk?

> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?

> 10、什么叫CDN?

> 11、什么叫网站灰度发布?

> 12、简述DNS进行域名解析的过程?

> 13、RabbitMQ是什么东西?

> 14、讲一下Keepalived的工作原理?

> 15、讲述一下LVS三种模式的工作过程?

> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

> 17、如何重置mysql root密码?

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

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618542503)**

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

么区别,工作中你怎么选择?

> 7、Tomcat和Resin有什么区别,工作中你怎么选择?

> 8、什么是中间件?什么是jdk?

> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?

> 10、什么叫CDN?

> 11、什么叫网站灰度发布?

> 12、简述DNS进行域名解析的过程?

> 13、RabbitMQ是什么东西?

> 14、讲一下Keepalived的工作原理?

> 15、讲述一下LVS三种模式的工作过程?

> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

> 17、如何重置mysql root密码?

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

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618542503)**

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值