Linux(内核剖析):04—进程之struct task_struct进程描述符、任务结构介绍

释放双眼,带上耳机,听听看~!

一、进程描述符(struct task_struct)、任务结构

任务队列

  • 内核把进程的列表存放在叫做任务队列(task list) 的

双向循环链表中。链表中的每一 项都是类型为task_struct

  • **备注:**有些操作系统会把任务队列称为任务数组。但是Linux实现时使用的是队列而不是静态数组,所以称为任务队列

Linux(内核剖析):04---进程之struct task_struct进程描述符、任务结构介绍

进程描述符(struct task_struct)

  • task_struct称为

进程描述符(process descriptor) 结构,该结构定义在<linux/sched.h>文件中。进程描述符中包含一个具体进程的所有信息

  • 进程描述符中包含的数据能

**完整地描述一个正在执行的程序:**它打开的文件,进程的地址空间,挂起的信号,进程的状态等

  • task_struct相对较大,在32位机器上,它大约有1.7KB。

  • 以下代码来自Linux-2.6.22/include/linux/sched.h


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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
1struct task_struct {
2   volatile long state;    /* -1 unrunnable, 0 runnable, &gt;0 stopped */
3   void *stack;
4   atomic_t usage;
5   unsigned int flags; /* per process flags, defined below */
6   unsigned int ptrace;
7
8   int lock_depth;     /* BKL lock depth */
9
10#ifdef CONFIG_SMP
11#ifdef __ARCH_WANT_UNLOCKED_CTXSW
12  int oncpu;
13#endif
14#endif
15  int load_weight;    /* for niceness load balancing purposes */
16  int prio, static_prio, normal_prio;
17  struct list_head run_list;
18  struct prio_array *array;
19
20  unsigned short ioprio;
21#ifdef CONFIG_BLK_DEV_IO_TRACE
22  unsigned int btrace_seq;
23#endif
24  unsigned long sleep_avg;
25  unsigned long long timestamp, last_ran;
26  unsigned long long sched_time; /* sched_clock time spent running */
27  enum sleep_type sleep_type;
28
29  unsigned int policy;
30  cpumask_t cpus_allowed;
31  unsigned int time_slice, first_time_slice;
32
33#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
34  struct sched_info sched_info;
35#endif
36
37  struct list_head tasks;
38  /*
39   * ptrace_list/ptrace_children forms the list of my children
40   * that were stolen by a ptracer.
41   */
42  struct list_head ptrace_children;
43  struct list_head ptrace_list;
44
45  struct mm_struct *mm, *active_mm;
46
47/* task state */
48  struct linux_binfmt *binfmt;
49  int exit_state;
50  int exit_code, exit_signal;
51  int pdeath_signal;  /*  The signal sent when the parent dies  */
52  /* ??? */
53  unsigned int personality;
54  unsigned did_exec:1;
55  pid_t pid;
56  pid_t tgid;
57
58#ifdef CONFIG_CC_STACKPROTECTOR
59  /* Canary value for the -fstack-protector gcc feature */
60  unsigned long stack_canary;
61#endif
62  /*
63   * pointers to (original) parent process, youngest child, younger sibling,
64   * older sibling, respectively.  (p-&gt;father can be replaced with
65   * p-&gt;parent-&gt;pid)
66   */
67  struct task_struct *real_parent; /* real parent process (when being debugged) */
68  struct task_struct *parent; /* parent process */
69  /*
70   * children/sibling forms the list of my children plus the
71   * tasks I&#x27;m ptracing.
72   */
73  struct list_head children;  /* list of my children */
74  struct list_head sibling;   /* linkage in my parent&#x27;s children list */
75  struct task_struct *group_leader;   /* threadgroup leader */
76
77  /* PID/PID hash table linkage. */
78  struct pid_link pids[PIDTYPE_MAX];
79  struct list_head thread_group;
80
81  struct completion *vfork_done;      /* for vfork() */
82  int __user *set_child_tid;      /* CLONE_CHILD_SETTID */
83  int __user *clear_child_tid;        /* CLONE_CHILD_CLEARTID */
84
85  unsigned int rt_priority;
86  cputime_t utime, stime;
87  unsigned long nvcsw, nivcsw; /* context switch counts */
88  struct timespec start_time;
89/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
90  unsigned long min_flt, maj_flt;
91
92      cputime_t it_prof_expires, it_virt_expires;
93  unsigned long long it_sched_expires;
94  struct list_head cpu_timers[3];
95
96/* process credentials */
97  uid_t uid,euid,suid,fsuid;
98  gid_t gid,egid,sgid,fsgid;
99  struct group_info *group_info;
100 kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
101 unsigned keep_capabilities:1;
102 struct user_struct *user;
103#ifdef CONFIG_KEYS
104 struct key *request_key_auth;   /* assumed request_key authority */
105 struct key *thread_keyring; /* keyring private to this thread */
106 unsigned char jit_keyring;  /* default keyring to attach requested keys to */
107#endif
108 /*
109  * fpu_counter contains the number of consecutive context switches
110  * that the FPU is used. If this is over a threshold, the lazy fpu
111  * saving becomes unlazy to save the trap. This is an unsigned char
112  * so that after 256 times the counter wraps and the behavior turns
113  * lazy again; this to deal with bursty apps that only use FPU for
114  * a short time
115  */
116 unsigned char fpu_counter;
117 int oomkilladj; /* OOM kill score adjustment (bit shift). */
118 char comm[TASK_COMM_LEN]; /* executable name excluding path
119                  - access with [gs]et_task_comm (which lock
120                    it with task_lock())
121                  - initialized normally by flush_old_exec */
122/* file system info */
123 int link_count, total_link_count;
124#ifdef CONFIG_SYSVIPC
125/* ipc stuff */
126 struct sysv_sem sysvsem;
127#endif
128/* CPU-specific state of this task */
129 struct thread_struct thread;
130/* filesystem information */
131 struct fs_struct *fs;
132/* open file information */
133 struct files_struct *files;
134/* namespaces */
135 struct nsproxy *nsproxy;
136/* signal handlers */
137 struct signal_struct *signal;
138 struct sighand_struct *sighand;
139
140 sigset_t blocked, real_blocked;
141 sigset_t saved_sigmask;     /* To be restored with TIF_RESTORE_SIGMASK */
142 struct sigpending pending;
143
144 unsigned long sas_ss_sp;
145 size_t sas_ss_size;
146 int (*notifier)(void *priv);
147 void *notifier_data;
148 sigset_t *notifier_mask;
149
150 void *security;
151 struct audit_context *audit_context;
152 seccomp_t seccomp;
153
154/* Thread group tracking */
155     u32 parent_exec_id;
156     u32 self_exec_id;
157/* Protection of (de-)allocation: mm, files, fs, tty, keyrings */
158 spinlock_t alloc_lock;
159
160 /* Protection of the PI data structures: */
161 spinlock_t pi_lock;
162
163#ifdef CONFIG_RT_MUTEXES
164 /* PI waiters blocked on a rt_mutex held by this task */
165 struct plist_head pi_waiters;
166 /* Deadlock detection and priority inheritance handling */
167 struct rt_mutex_waiter *pi_blocked_on;
168#endif
169
170#ifdef CONFIG_DEBUG_MUTEXES
171 /* mutex deadlock detection */
172 struct mutex_waiter *blocked_on;
173#endif
174#ifdef CONFIG_TRACE_IRQFLAGS
175 unsigned int irq_events;
176 int hardirqs_enabled;
177 unsigned long hardirq_enable_ip;
178 unsigned int hardirq_enable_event;
179 unsigned long hardirq_disable_ip;
180 unsigned int hardirq_disable_event;
181 int softirqs_enabled;
182 unsigned long softirq_disable_ip;
183 unsigned int softirq_disable_event;
184 unsigned long softirq_enable_ip;
185 unsigned int softirq_enable_event;
186 int hardirq_context;
187 int softirq_context;
188#endif
189#ifdef CONFIG_LOCKDEP
190# define MAX_LOCK_DEPTH 30UL
191 u64 curr_chain_key;
192 int lockdep_depth;
193 struct held_lock held_locks[MAX_LOCK_DEPTH];
194 unsigned int lockdep_recursion;
195#endif
196
197/* journalling filesystem info */
198 void *journal_info;
199
200/* stacked block device info */
201 struct bio *bio_list, **bio_tail;
202
203/* VM state */
204 struct reclaim_state *reclaim_state;
205
206 struct backing_dev_info *backing_dev_info;
207
208 struct io_context *io_context;
209
210 unsigned long ptrace_message;
211 siginfo_t *last_siginfo; /* For ptrace use.  */
212/*
213 * current io wait handle: wait queue entry to use for io waits
214 * If this thread is processing aio, this points at the waitqueue
215 * inside the currently handled kiocb. It may be NULL (i.e. default
216 * to a stack based synchronous wait) if its doing sync IO.
217 */
218 wait_queue_t *io_wait;
219#ifdef CONFIG_TASK_XACCT
220/* i/o counters(bytes read/written, #syscalls */
221 u64 rchar, wchar, syscr, syscw;
222#endif
223 struct task_io_accounting ioac;
224#if defined(CONFIG_TASK_XACCT)
225 u64 acct_rss_mem1;  /* accumulated rss usage */
226 u64 acct_vm_mem1;   /* accumulated virtual memory usage */
227 cputime_t acct_stimexpd;/* stime since last update */
228#endif
229#ifdef CONFIG_NUMA
230     struct mempolicy *mempolicy;
231 short il_next;
232#endif
233#ifdef CONFIG_CPUSETS
234 struct cpuset *cpuset;
235 nodemask_t mems_allowed;
236 int cpuset_mems_generation;
237 int cpuset_mem_spread_rotor;
238#endif
239 struct robust_list_head __user *robust_list;
240#ifdef CONFIG_COMPAT
241 struct compat_robust_list_head __user *compat_robust_list;
242#endif
243 struct list_head pi_state_list;
244 struct futex_pi_state *pi_state_cache;
245
246 atomic_t fs_excl;   /* holding fs exclusive resources */
247 struct rcu_head rcu;
248
249 /*
250  * cache last used pipe for splice
251  */
252 struct pipe_inode_info *splice_pipe;
253#ifdef   CONFIG_TASK_DELAY_ACCT
254 struct task_delay_info *delays;
255#endif
256#ifdef CONFIG_FAULT_INJECTION
257 int make_it_fail;
258#endif
259};
260

二、分配进程描述符(struct thread_info)

  • Linux

通过slab分配器分配task_struct结构,这样能达到
对象复用和缓存着色(cache coloring) 的目的(通过预先分配和重复使用task_sturct,可以避免动态分配和释放所带来的资源消耗)

  • 进程描述符的分配:

  • **在2.6以前的内核中,**各个进程的task struct存放在它们栈的尾端。这样做是为了让那些像x86那样寄存器较少的硬件体系结构只要通过栈指针就能计算出它的位置,而避免使用额外的寄存器专记录

    • 由于

**现在用slab分配器动态生成task_ struct,**所以
只需在栈底(对于向下增长的栈来说)或栈顶 (对于向上增长的栈来说)创建一个新的结构struct thread_info(寄存器较弱的体系结构不是引入thread_info结构的唯一原因。这个新建的结构使在汇编代码中计算其偏移变得非常容易)

struct thread_info

  • 每个任务的thread_info结构在它的内核栈的尾端分配。结构中task域中存放的是指向该任务实际task_struct的指针

  • 以下代码来自Linux-2.6.22/include/asm-i386/thread_info.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1struct thread_info {
2   struct task_struct  *task;        /* main task structure */
3   struct exec_domain  *exec_domain; /* execution domain */
4   unsigned long       flags;        /* low level flags */
5   unsigned long       status;       /* thread-synchronous flags */
6   __u32           cpu;              /* current CPU */
7   int         preempt_count;        /* 0 =&gt; preemptable, &lt;0 =&gt; BUG */
8
9   mm_segment_t        addr_limit;   /* thread address space:
10                                     0-0xBFFFFFFF for user-thead
11                                     0-0xFFFFFFFF for kernel-thread*/
12  void            *sysenter_return;
13  struct restart_block    restart_block;
14  unsigned long           previous_esp;  /* ESP of the previous stack in
15                                            case of nested (IRQ) stacks*/
16  __u8            supervisor_stack[0];
17};
18

Linux(内核剖析):04---进程之struct task_struct进程描述符、任务结构介绍

三、进程描述符的存放(pid、current_thread_info宏)

  • 内核通过一个唯一的

进程标识 (process identification value)或PID来标识每个进程

PID的取值范围

  •  PID是 —个数,表示为pid _t隐含类型,实际上

就是一个int类型

  • 为了与老版本的Unix和Linux兼容,

PID的最大值默认设置为32768
****(short int短整型的最大值),尽管这个值也可以增加到高达400万(这受<linux/threads.h>中所定义PID最大值的限制)。内核把每个进程的PID存放在它们各自的进程描述符中

  • 以下代码来自Linux-2.6.22/include/linux/threads.h


1
2
3
4
5
6
7
8
9
10
11
12
1/*
2 * This controls the default maximum pid allocated to a process
3 */
4#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
5
6/*
7 * A maximum of 4 million PIDs should be enough for a while.
8 * [NOTE: PID/TIDs are limited to 2^29 ~= 500+ million, see futex.h.]
9 */
10#define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \
11  (sizeof(long) &gt; 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT))
12

PID取值范围的修改

  • 这个最大值很重要,因为它实际上就是系统中允许同时存在的进程的最大数目。尽管32768对于一般的桌面系统足够用了,但是大型服务器可能需要更多进程。这个值越小,转一圈就越快,本来数值大的进程比数值小的进程迟运行,但这样一来就破坏了这一原则。如果确实需要的话,可以不考虑与老式系统的兼容,由系统管理员通过

修改/proc/sys/kernel/pid_max来提高上限

current宏

  • 在内核中,访问任务通常需要

获得指向其task_struct的指针。实际上,内核中大部分处理进程的代码都是直接通过task_struct进行的。因此,
通过current宏查找到当前正在运行进程的进程描述符的速度就显得尤为重要

  • 不同体系current宏的实现:

  • 硬件体系结构不同,该宏的实现也不同,它必须针对专门的硬件体系结构做处理

    • 有的硬件体系结构可以拿出一个专门寄存器来存放指向当进程task_struct的指针,用于加快访问速度
    • 而有些像x86这样的体系结 (其寄存器并不富余),就只能在内栈的尾端创建thread_info结构,通过计算偏移间接地查找task_struct结构
  • x86系统上,

current栈指针的后13个有效位屏蔽掉,用来计算出thread_info的偏移。 该操作是通过current_thread_info()函数来完成的。汇编代码如下:


1
2
3
4
5
1//备注:这里假定栈的大小为8KB 。当4KB的栈启用时,就要用4096,而不是8192
2
3movl $-8192, %eax
4andl %esp, %eax
5
  • 最后,current再从thread_info的task域中

提取并返回task_struct的地址:


1
2
1current_thread_info()-&gt;task;
2
  • 对比一下这部分在PowerPC上的实现(IBM基于RISC的现代微处理器),我们可以发现PPC当前的task_struct是保存在一个寄存器中的。也就是说,在PPC上,current宏只需把r2寄存器中的值返回就行了。与x86不一样,PPC有足够多的寄存器,所以它的实现有这样选择的余地。而访问进程描述符是一个重要的频繁操作,所以PPC内核开发者觉得完全有必要为此使用一个专门的寄存器

四、进程状态

  • 进程描述符中的

state域描述了进程的当前状态


1
2
3
4
5
1struct task_struct {
2   volatile long state;    /* -1 unrunnable, 0 runnable, &gt;0 stopped */
3    /*...*/
4};
5
  • 系统中的每个进程都必然处于五种进程状态中的一种。

该域的值也必为下列五种状态标志之一:

  • TASK_RUNNING(运行)——进程是可执行的;它或者正在执行,或者在运行队列中等待执行(运行队列将会在后面文章讨论)。这是进程在用户空间中执行的唯一可能的状态; 这种状态也可以应用到内核空间中正在执行的进程

    • TASK_INTERRUPTIBLE(可中断)——进程正在睡眠(也就是说它被阻塞),等待某些条件的达成。一旦这些条件达成,内核就会把进程状态设置为运行。处于此状态的进程也会因为接收到信号而提前被唤醒并随时准备投入运行。
    • TASK_UNINTERRUPTIBLE (不可中断)——除了就算是接收到信号也不会被唤醒或准备投入运行外,这个状态与可打断状态相同。这个状态通常在进程必须在等待时不受干扰或等待事件很快就会发生时出现。由于处于此状态的任务对信号不做响应,所以较之可中断状态,使用得较少
  • 这就是你在执ps命令时,看到那些被标为D状态而又不能被杀死的进程的原因。由于任务将不响应信号,因此,你不可能给它发送SIGKILL信号。退一步说,即使有办法,终结这样一个任务也不是明智的选择 ,因为该任务有可能正在执行重要的操作,甚至还可能持有一个佶号置

    • __TASK_TRACED——被其他进程跟踪的进程,例如通过ptrace对调试程序进行跟踪
    • __TASK_TSTOPPED(停止)——进程停止执行;进程没有投入运行也不能投入运行。通常这种状态发生在接收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTO U等信号的时候。此外,调试期间接收到任何信号,都会使进程进入这种状态

五、设置当前进程状态(set_task_state)

  • 内核经常需要调整某个进程的状态。这时最好使用

set_task_state(task,state)函数


1
2
1set_task_state(task, state); /* 将任务task的状态设置为state */
2
  • 该函数将指定的进程设置为指定的状态。必要的时候,它会设置内存屏障来强制其他处理器作重新排序。(一般只有在SMP系统中有此必要)否则,它等价于:


1
2
1task-&gt;state = state;
2
  • set_current_state(state)和set_task_state(current,state)含义

是等同的。可以参考< linux/sched.h>中对这些相关函数实现的说明

六、进程上下文(用户空间、内核空间)

  • 可执行程序代码是进程的重要组成部分。这些代码从一个可执行文件载入到进程的地址空间执行
  • 一般程序在

用户空间执行。当一个程序调
执行了系统调用或者触发了某个异常,它就
陷入了内核空间。此时,我们称内核“代表进程执行”并处于进程上下文中。在此上下文中current宏是有效的。除非在此间隙有更高优先级的进程需要执行并由调度器做出了相应调整,否则在内核退出的时候,程序恢复在用户空间会继续执行

七、进程家族树

  • Unix系统的进程之间存在一个

明显的继承关系,在Linux系统中也是如此

  • 所有的进程都是PID为1的init进程的后代。内核在系统启动的最后阶段启动init进程。该进程

读取系统的初始化脚本(initscript)并执行其他的相关程序,最终完成系统启动的整个过程
* 系统中的每个进程
必有一个父进程,相应的,每个进程也可以
拥有零个或多个子进程。拥有同一个父进程的所有进程被称为
兄弟
* 进程间的关系存放在进程描述符中。每个task_struct都包含一个指向其父进程tast_struct、叫做
parent的指针,还包含一个称为
children的子进程链表

  • 所以,对于当前进程,可以通过下面的代码

获得其父进程的进程描述符:


1
2
1struct task_struct *my_parent = current-&gt;parent;
2
  • 同样,也可以按以下方式

依次访问子进程:


1
2
3
4
5
6
7
8
1struct task_struct *task;
2struct list_head *list;
3
4list_for_each(list,&amp;current-&gt;childrn){
5    //task指向当前的某个子进程
6    task=list_entry(list,struct task_struct,sibling);
7}
8
  • init进程的进程描述符是作为init_task静态分配的。下面的代码可以很好地演示所有进程之间的关系:


1
2
3
4
5
6
1struct task_struct *task;
2for(task=current;task!=&amp;init_task;task=task-&gt;parent)
3    /*...*/
4
5/*for循环之后,task指向于init*/
6
  • 实际上,你可以通过这种继承体系从系统的任何一个进程出发查找到任意指定的其他进程。 但大多数时候,只需要通过简单的重复方式就可以遍历系统中的所有进程。这非常容易做到,因为任务队列本来就是一个

**双向的循环链表。**对于给定的进程,
获取链表中的下一个进程:


1
2
1list_entry(task-&gt;tasks.next,struct task_struct, tasks);
2
  • 获取前一个进程的方法与之相同:


1
2
1list_entry(task-&gt;tasks.prev,struct task_struct,tasks);
2
  • 这两个例程分别通过

next_task(task) 宏和prev_task(task)宏实现。而实际上,
for_each_process(task)宏提供了依次访问整个任务队列的能力。每次访问,任务指针都指向链表中的下一个元素:


1
2
3
4
5
6
7
1struct task_struct *task;
2
3for_each__process (task) {
4    /*它打印每一个任务的名称和PID*/
5    printk(&quot;%s[%d]\n&quot;, task-&gt;comm,task-&gt;pid);
6}
7
  • 备注:在一个拥有大量进程的系统中通过重复来遍历所有的进程代价是很大的。因此,如果没有充足的理由(或者别无他法),别这样做

给TA打赏
共{{data.count}}人
人已打赏
安全运维

WordPress网站专用docker容器环境带Waf

2020-7-18 20:04:44

安全运维

运维安全-Gitlab管理员权限安全思考

2021-9-19 9:16:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索