PID is the complex process descriptor Allocation and Management in Linux kernel. This article analyzes the relevant data structures and functions. (The Code is based on v3.0.3)
PID-related data structures include:
struct pid{atomic_t count;unsigned int level;struct hlist_head tasks[PIDTYPE_MAX];struct rcu_head rcu;struct upid numbers[1];};
Here, Count refers to the number of times the data structure is referenced.
Level indicates the level of the PID in pid_namespace. When level = 0, it indicates global namespace, that is, the highest level. The data structure of pid_namespace will be explained later.
Each element in the tasks [pidtype_max] array represents a different meaning. Pidtype_max indicates the maximum number of PID types. This value is defined in Enum pid_type.
enum pid_type{PIDTYPE_PID,PIDTYPE_PGID,PIDTYPE_SID,PIDTYPE_MAX};
Pidtype_pid indicates the process Descriptor (PID ). Pidtype_pgid represents a group of process descriptors. A group of processes can form a group with a group descriptor. The advantage is that if there is a signal for this group descriptor, all processes in the group can accept it. Pidtype_sid is used to create another group for the group descriptor to form a session. This is an abstraction at a higher level.
Task [I] points to a hash table. For example, tasks [pidtype_pid] points to the PID hash table.
I have not figured out what the RCU domain is doing :(
Numbers [1] indicates the upid struct. The numbers array is intended to represent different pid_namespace. A pid can belong to different namespaces. Numbers [0] indicates global namespace, and numbers [I] indicates the namespace of the I layer. The higher the I level, the lower the level. Currently, this array has only one element, that is, global namespace. So although the concept of namepace introduces PID, it is not actually used and may be used in future versions.
Next, let's look at the upid data structure.
struct upid {int nr;struct pid_namespace × ns;struct hlist_node pid_chain;};
The numbers field in the PID struct points to the upid struct. In this struct
Nr is the PID value, that is, the pid_t PID value in task_struct.
NS points to the namespace of the PID.
The Linux kernel stores the upid of all processes in a hash table (pid_hash) for convenient search and unified management. Therefore, the upid instance pointed to by numbers [0] In the PID struct is stored in pid_hash. The position of the upid in pid_hash can be found through pid_chain, that is, the node of the hash table.
Next let's take a look at the pid_namespace struct.
struct pid_namespace {struct kref kref;struct pidmap pidmap[PIDMAP_ENTRIES];int last_pid;struct task_struct *child_reaper;struct kmem_cache *pid_cachep;unsigned int level;struct pid_namespace *parent;};
Kref indicates the number of entries pointing to pid_namespace.
The pidmap struct indicates the bitmap allocated to the PID. When you need to allocate a new PID, you only need to find the bitmap, locate the position where bit is 0 and set 1, and then update the statistical data field (nr_free ).
struct pidmap {atomic_t nr_free;void *page;};
Nr_free indicates the number of assigned PIDs.
The page points to the physical page that stores the PID.
Therefore, the pidmap [pidmap_entries] field indicates the allocated PID under the pid_namespace.
Last_pid is used for pidmap allocation. Point to the position of the last allocated PID. (Not quite sure)
Child_reaper points to a process. This process is used to collect dead bodies (reclaim space) when the child process ends ). Currently, only global namespace is supported. child_reaper points to init_task.
The pid_cachedomain points to the address of the slab assigned PID.
Level indicates the level in which the namespace is located. Now it is obviously 0.
Parent points to the parent namespace Of The namespace. It must be null.
After introducing the data structures related to pid_namespace, let's take a look at what they meant. In Linux, The namespace concept is added to achieve virtualization and convenient management. For example, different namespaces may have processes with the same PID. The structure of pid_namespace is hierarchical. In addition, the process in the Child namespace must have a parent namespace ing. This sentence may not be easy to understand. You can combine the following figure
For example, the pid_hash global hash table will store 15 (9 + 3 + 3) upid instances.
We have introduced so many concepts about PID, upid, and pid_namespace. Let's take a look at the relationship between them and task_struct.
The elliptical dotted box in the lower right corner is global pid_hash, and all assigned upid will be saved in this hash table.
The elliptical dotted box in the lower left corner represents the relationship between pid_namespace. Of course, there is only one layer.
The Linux kernel uses task_struct to manage processes. In task_struct, the PID-related domain has
struct task_struct{...pid_t pid;pid_t tgid;struct task_struct *group_leader;struct pid_link pids[PIDTYPE_MAX];struct nsproxy *nsproxy;...};
PID indicates the process descriptor of the process. The following describes how to assign values to the fork function.
Tgid indicates the thread descriptor of the process. In the Linux kernel, there is no special processing for threads, or it is managed by task_struct. From the kernel perspective, the user-state thread is essentially a process. The tgid is the same for different threads in the same process (User State angle), but the PID is different. The main thread is group_leader (the main thread will create all other sub-threads ). If it is a single-threaded process (User State angle), its PID is equal to tgid.
For user-state programs, calling the getpid () function actually returns the tgid. Think about why? :)
In addition to pointing to the main thread in multi-threaded mode, group_leader is also useful. When some processes form a group (pidtype_pgid), this field points to the leader of this group.
The nsproxy Pointer Points to the namespace-related domain.
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;struct net *net_ns;};
You can use the nsproxy domain to know which pid_namespace the task_struct belongs to. Of course, it must be a global namespace. (I have already talked about it many times :))
Other domains are also related to namespace, so we will not explain them here.
PIDs [pidtype_max] points to the PID struct related to the task_struct.
Pid_link is defined as follows:
struct pid_link{struct hlist_node node;struct pid *pid;};
In the Linux kernel, if you want to obtain the PID corresponding to the task_struct, you can call the task_pid () function. The implementation of this function is very simple.
static inline struct pid *task_pid(struct task_struct *task){ return task->pids[PIDTYPE_PID].pid;}
Since then, I have introduced the PID-related data structure. Let's take a look at the PID-related usage.
(1) how to assign a new PID to the fork function?
The fork (), vfork (), and clone () functions ultimately work by calling do_fork. Assigning a new PID is implemented in the copy_process () function. The do_fork () function calls copy_process (). I will introduce the relationship between them in future articles.
static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace){...if (pid != &init_struct_pid) { retval = -ENOMEM; pid = alloc_pid(p->nsproxy->pid_ns); if (!pid) goto bad_fork_cleanup_io;}p->pid = pid_nr(pid);p->tgid = p->pid;if (clone_flags & CLONE_THREAD) p->tgid = current->tgid;...}
I only listed the Code allocated with the PID.
The alloc_pid function will assign a new PID struct. Simply put, the function is to find an unused PID bit on the pidmap. If it cannot be found, it indicates that no PID is available and the PID of The namespace is fully used up. Save it to the pid_hash hash table, and then return the PID struct.
The implementation of the pid_nr function is also very simple.
static inline pid_t pid_nr(struct pid *pid) { pid_t nr = 0; if (pid) nr = pid->numbers[0].nr; return nr; }
Returns the global namespace value of the PID.
The following lines of code are used to differentiate the value of tgid in processes and threads.
PID-related data structures. function definitions can be found in include/Linux/PID. h include/Linux/pid_namespace.h and kernel/PID. c kernel/pid_namespace.c.
Note:
(1) If any error is found in this article, please help me to point it out. Thank you very much!
(2) Welcome to communicate with you.
(3) This article is original. If you need to reprint it, indicate the source.