在前面的文章中, 我们学习了进程通信的几种方式, 并且也接触到了内核控制进程的结构块task_struct
, task_struct
的内容主要会分为以下几个部分, 通过这一篇文章可以学习这些部分的大体内容.
task_struct
task_struct
任务ID: 用于区分进程, 是进程的身份证, 比如pid就属于任务ID
亲缘关系: 包含兄弟进程, 父子进程的信息
任务状态: 用于标识当前进程的运行状态, 比如running, runable, stop, wait等;
权限: 进程权限信息, 包括本进程对外以及外对本进程的权限
运行统计: 包括启动时间, cpu占用时间等信息
调度相关: 包含进程优先级, 调度策略等信息
信号处理: 阻塞/等待等信息, 以及信号处理函数(见进程控制和进程通信三 )
内存管理: 进程虚拟内存空间
文件与文件系统: 进程文件
内核栈: 内核栈地址
任务ID
和任务ID相关的成员大概有以下:
1
2
3
pid_t pid; // 进程id
pid_t tgid; // 线程group id
struct task_struct *group_leader; // 线程group leader
group_leader
是指向线程group第一个task的指针. 比如在setpgid
函数中会用到.
Linux将current
包装为了一个宏, 用来获取在当前CPU上运行的task_struct
地址.
1
2
3
4
5
6
DECLARE_PER_CPU(struct task_struct *, current_task);
static __always_inline struct task_struct *get_current(void)
{
return this_cpu_read_stable(current_task);
}
#define current get_current()
一般, 我们可以调用getpid
获取进程的进程id, 实际上getpid
返回的是task_struct
的tgid
. (当然这个函数的调用栈不是这么简单的, 还涉及到了Linux的namespace概念, 这里就先简单处理了, 以下的接口同样简单处理)
1
2
3
4
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
同样, 我们也可以用gettid
获取当前线程的线程id, 实际上gettid
返回的是task_struct
的pid
.
1
2
3
4
SYSCALL_DEFINE0(gettid)
{
return task_pid_vnr(current);
}
另外, 也可以通过getppid
获取父进程的进程id, 实际上getppid
返回的是task_struct
的real_parent
的tgid
.
1
2
3
4
5
6
7
8
SYSCALL_DEFINE0(getppid)
{
int pid;
rcu_read_lock();
pid = task_tgid_vnr(rcu_dereference(current->real_parent));
rcu_read_unlock();
return pid;
}
对内核来说, 没有区分进程和线程的概念, 在内核中, 这两种概念都叫task, 由task_struct
结构体管理. 所以, 怎么来区分进程和线程呢? 内核会用pid
和tgid
两个成员区分, pid
表示的当前task_struct
的id, 可以认为是task_struct
的标识符, 每个task都不一样. 如果某个进程/线程创建了一个子线程, 那么就会生成一个新的pid
, 但是会继承父结点的tgid
.
pid和tgid
为什么有这两个概念? 我猜是因为从单核单进程时代到多进程时代迁移的遗留问题.
亲缘关系
与task亲缘关系相关的成员如下:
1
2
3
4
5
6
7
/* Real parent process: */
struct task_struct __rcu *real_parent;
/* Recipient of SIGCHLD, wait4() reports: */
struct task_struct __rcu *parent;
/* Children/sibling form the list of natural children: */
struct list_head children;
struct list_head sibling;
real_parent
指向的是真正的parent进程, 如下是进程clone
时的一段代码, 可以看到real_parent
是指向子(新)进程的父进程. 如果创建的是thread, 则和父进程的real_parent
相同.
1
2
3
4
5
6
7
8
/* CLONE_PARENT re-uses the old parent */
if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
p->real_parent = current->real_parent;
p->parent_exec_id = current->parent_exec_id;
} else {
p->real_parent = current;
p->parent_exec_id = current->self_exec_id;
}
parent
指向的是给当前进程传递SIGCHLD
信号的进程, 这个进程一般是debug进程, 比如GDB调试进程, 这时候parent
就是指向的GDB进程.
比如ptrace
的link阶段, 就会更新parent
的指向.
1
2
3
4
5
6
7
8
void __ptrace_link(struct task_struct *child, struct task_struct *new_parent,
const struct cred *ptracer_cred)
{
BUG_ON(!list_empty(&child->ptrace_entry));
list_add(&child->ptrace_entry, &new_parent->ptraced);
child->parent = new_parent;
child->ptracer_cred = get_cred(ptracer_cred);
}
关于parent
的参考信息可见kernelnewbies:parent和real_parent .
children
用于存放子进程指针, sibling
存放兄弟进程指针;
任务状态
与任务状态相关的一些成员如下:
1
2
3
4
5
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
/* Per task flags (PF_*), defined further below: */
unsigned int flags;
int exit_state;
以下是关于state的描述:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE