总体说明
本文介绍Linux下处理进程所使用的结构体task_struct。
它位于include\linux\sched.h。
对应的结构体如下:
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
int oncpu;
#endif
#endif
int prio, static_prio, normal_prio;
struct list_head run_list;
const struct sched_class *sched_class;
struct sched_entity se;
// 后面略,还有很多。
}
下面会分别介绍各个成员。
state
指定进程的当前状态。
对应的值:
/*
* Task state bitmask. NOTE! These bits are also
* encoded in fs/proc/array.c: get_task_state().
*
* We have two separate sets of flags: task->state
* is about runnability, while task->exit_state are
* about the task exiting. Confusing, but this way
* modifying one set can't modify the other one by
* mistake.
*/
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_STOPPED 4
#define TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
/* in tsk->state again */
#define TASK_DEAD 64
另外还有一个exit_state,值时上面的EXIT_XX那几个。
rlim
对应的是一个数组:
/*
* We don't bother to synchronize most readers of this at all,
* because there is no reader checking a limit that actually needs
* to get both rlim_cur and rlim_max atomically, and either one
* alone is a single word that can safely be read normally.
* getrlimit/setrlimit use task_lock(current->group_leader) to
* protect this instead of the siglock, because they really
* have no need to disable irqs.
*/
struct rlimit rlim[RLIM_NLIMITS];
结构体表示如下:
struct rlimit {
unsigned long rlim_cur;
unsigned long rlim_max;
};
分别表示资源的软限制和硬限制。
通过getrlimit/setrlimit可以操作限制,但是看了代码也看不出来是如何实现的......
具体进程的限制可以通过如下命令查看:
这里的RLIM_NLIMITS的指是15,所以最多有15个限制,不过上图有16个,可能跟使用的版本不同有关(上图是在Ubuntu18.04上查看的)。
nsproxy
进程中包含命名空间相关的指针,对应的结构体:
/*
* A structure to contain pointers to all per-process
* namespaces - fs (mount), uts, network, sysvipc, etc.
*
* '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;
struct user_namespace *user_ns;
struct net *net_ns;
};
进程与命名空间的关系:
命名空间有不少个,具体的说明如下:
通过fork/clone/unshare等系统调用可以创建新的命名空间,通过标识参数来实现,对应的标识有:
/*
* cloning flags:
*/
#define CSIGNAL 0x000000ff /* signal mask to be sent at exit */
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
#define CLONE_FS 0x00000200 /* set if fs info shared between processes */
#define CLONE_FILES 0x00000400 /* set if open files shared between processes */
#define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */
#define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */
#define CLONE_THREAD 0x00010000 /* Same thread group? */
#define CLONE_NEWNS 0x00020000 /* New namespace group? */
#define CLONE_SYSVSEM 0x00040000 /* share system V SEM_UNDO semantics */
#define CLONE_SETTLS 0x00080000 /* create a new TLS for the child */
#define CLONE_PARENT_SETTID 0x00100000 /* set the TID in the parent */
#define CLONE_CHILD_CLEARTID 0x00200000 /* clear the TID in the child */
#define CLONE_DETACHED 0x00400000 /* Unused, ignored */
#define CLONE_UNTRACED 0x00800000 /* set if the tracing process can't force CLONE_PTRACE on this clone */
#define CLONE_CHILD_SETTID 0x01000000 /* set the TID in the child */
#define CLONE_STOPPED 0x02000000 /* Start in stopped state */
#define CLONE_NEWUTS 0x04000000 /* New utsname group? */
#define CLONE_NEWIPC 0x08000000 /* New ipcs */
#define CLONE_NEWUSER 0x10000000 /* New user namespace */
#define CLONE_NEWPID 0x20000000 /* New pid namespace */
#define CLONE_NEWNET 0x40000000 /* New network namespace */
init_nsproxy定义了初始的全局命名空间:
extern struct nsproxy init_nsproxy;
struct nsproxy init_nsproxy = INIT_NSPROXY(init_nsproxy);
对应的宏:
#define INIT_NSPROXY(nsproxy) { \
.pid_ns = &init_pid_ns, \
.count = ATOMIC_INIT(1), \
.uts_ns = &init_uts_ns, \
.mnt_ns = NULL, \
INIT_NET_NS(net_ns) \
INIT_IPC_NS(ipc_ns) \
.user_ns = &init_user_ns, \
}
下面分别说明。
UTS命名空间
结构体如下:
struct uts_namespace {
struct kref kref;
struct new_utsname name;
};
第一个参数在之前介绍内核对象的时候有提到过,是用于跟踪内核中有多少地方使用uts_namespace结构体实例的引用计数。
第二个参数才是真正需要的成员,它也是一个结构体:
struct new_utsname {
char sysname[65];
char nodename[65];
char release[65];
char version[65];
char machine[65];
char domainname[65];
};
它就是一堆字符串的结合体。
这些字符串表示了系统名称,内核发布版本,机器名等信息。
使用如下命令可以查看:
它通过init_uts_ns初始化:
struct uts_namespace init_uts_ns = {
.kref = {
.refcount = ATOMIC_INIT(2),
},
.name = {
.sysname = UTS_SYSNAME,
.nodename = UTS_NODENAME,
.release = UTS_RELEASE,
.version = UTS_VERSION,
.machine = UTS_MACHINE,
.domainname = UTS_DOMAINNAME,
},
};
EXPORT_SYMBOL_GPL(init_uts_ns);
这里的UTS_XXX宏就是一个个的字符串。这次字符串有些不能改,而有些可以修改。
通过copy_utsname可以创建新的UTS命名空间。
为了创建新的UTS命名空间,会生成先前的ust_namespace实例的副本,当前进程的nsproxy实例内部的指针会指向新的副本。
用户命名空间
用户命名空间的结构体如下:
struct user_namespace {
struct kref kref;
struct hlist_head uidhash_table[UIDHASH_SZ];
struct user_struct *root_user;
};
kref不再赘述。
root_user用于负责记录资源消耗,而通过uidhash_table可以访问到这些资源。
注意虽然叫用户命名空间,但是关注点在资源上,之所以叫用户命名空间,应该是因为资源是针对用户的,而用户通过UID来区分。
进程ID
进程相关的ID有很多。
首先是每个进程都有一个在其命名空间下唯一的ID,称为PID:
pid_t pid;
其次,进程可能在某个线程组下,因此包含一个线程组ID,称为TGID:
pid_t tgid;
由于父命名空间可以看到子命名空间的进程,所以某些进程可能具有多个PID(针对不同的命名空间),为此需要了解全局ID和局部ID的概念:
全局ID是在内核本身和初始命名空间中的唯一ID,在系统启动期间开始的init进程即属于初始命名空间。
局部ID属于某个特定的命名空间,不具备全局有效性。
上面提到的PID和TGID就是全局ID。
几个进程可以合并成进程组,对应有进程组ID,称为PGID,它的值就是进程组组长的ID;几个进程组可以合并成一个会话,因此存在一个会话ID,称为SID。它们并没有直接在task_struct中,而是保存在用于信号处理的结构体中:
struct signal_struct *signal; // 位于task_struct
/*
* NOTE! "signal_struct" does not have it's own
* locking, because a shared signal_struct always
* implies a shared sighand_struct, so locking
* sighand_struct is always a proper superset of
* the locking of signal_struct.
*/
struct signal_struct {
// 前略
/*
* pgrp and session fields are deprecated.
* use the task_session_Xnr and task_pgrp_Xnr routines below
*/
union {
pid_t pgrp __deprecated;
pid_t __pgrp;
};
struct pid *tty_old_pgrp;
union {
pid_t session __deprecated;
pid_t __session;
};
// 后略
不过从说明上来看这两个值以后不再使用了。
进程关系
task_struct中有如下的两个成员:
/*
* children/sibling forms the list of my children plus the
* tasks I'm ptracing.
*/
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
它们都是链表,前者保存所有子进程,后者保存具有相同父进程的兄弟进程。
表示进程的结构体task_struct非常大,这里不再一一介绍,在之后的文章中还会持续说明。