《深入Linux内核架构》读书笔记004——进程表示

总体说明

本文介绍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非常大,这里不再一一介绍,在之后的文章中还会持续说明。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章