一:简介
为了管理进程,内核必须对每个进程所做的事情进行清除的描叙。
比如:内核必须知道进程优先级,他是正在CPU上运行还是因为某些事件被阻塞了,给它分配了什么样的地址空间,允许它访问哪个文件等等。
这就是进程描述符(process descriptor)的作用,进程描述符都是task_struct类型的结构,它里面的字段包含了与一个进程相关的所有信息。每一个进程都要有一个task_struct数据结构。
在Linux中 进程(process)和 任务(task) 是同一个意思。
研究 Linux版本为 2.6.38.8
二:task_struct 结构体成员
task_struct 结构体是linux内核源码中非常复杂的一个结构体,成员非常多,task_struct 成员在线查看。
2.1 进程状态
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
state表示当前进程的运行状态
状态值:
#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
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_STATE_MAX 512
#define TASK_STATE_TO_CHAR_STR "RSDTtZXxKW"
extern char ___assert_task_state[1 - 2*!!(
sizeof(TASK_STATE_TO_CHAR_STR)-1 != ilog2(TASK_STATE_MAX)+1)];
/* Convenience macros for the sake of set_task_state */
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED)
5个互斥状态
状态 | 描述 |
---|---|
TASK_RUNNING | 表示进程要么正在执行,要么正要准备执行(已经就绪),正在等待cpu时间片的调度 |
TASK_INTERRUPTIBLE | 进程因为等待一些条件而被挂起(阻塞)而所处的状态。这些条件主要包括:硬中断、资源、一些信号……,一旦等待的条件成立,进程就会从该状态(阻塞)迅速转化成为就绪状态TASK_RUNNING |
TASK_UNINTERRUPTIBLE | 意义与TASK_INTERRUPTIBLE类似,除了不能通过接受一个信号来唤醒以外,对于处于TASK_UNINTERRUPIBLE状态的进程,哪怕我们传递一个信号或者有一个外部中断都不能唤醒他们。只有它所等待的资源可用的时候,他才会被唤醒。这个标志很少用,但是并不代表没有任何用处,其实他的作用非常大,特别是对于驱动刺探相关的硬件过程很重要,这个刺探过程不能被一些其他的东西给中断,否则就会让进城进入不可预测的状态 |
TASK_STOPPED | 进程被停止执行,当进程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信号之后就会进入该状态 |
TASK_TRACED | 表示进程被debugger等进程监视,进程执行被调试程序所停止,当一个进程被另外的进程所监视,每一个信号都会让进城进入该状态 |
2个终止状态
其实还有两个附加的进程状态既可以被添加到state域中,又可以被添加到exit_state域中。只有当进程终止的时候,才会达到这两种状态.
/* task state */
int exit_state;
int exit_code, exit_signal;
状态 | 描述 |
---|---|
EXIT_ZOMBIE | 进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息,此时进程成为僵尸进程 |
EXIT_DEAD | 进程的最终状态 |
2.2 进程标识符
pid_t pid; //进程的标识符
pid_t tgid; //线程组标识符
在Linux系统中,一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID,并被存放在tgid成员中。只有线程组的领头线程的pid成员才会被设置为与tgid相同的值。注意,getpid()系统调用返回的是当前进程的tgid值而不是pid值
2.3 flags 进程标记符
unsigned int flags; /* per process flags, defined below */
反映进程状态信息。
这个和上面的进程状态state有什么区别呢?
这个虽然是反映进程状态信息,但不是运行状态,而是与管理有关的其他信息。
它的取值范围:
/*
* Per process flags
*/
#define PF_KSOFTIRQD 0x00000001 /* I am ksoftirqd */
#define PF_STARTING 0x00000002 /* being created */
#define PF_EXITING 0x00000004 /* getting shut down */
#define PF_EXITPIDONE 0x00000008 /* pi exit done on shut down */
#define PF_VCPU 0x00000010 /* I'm a virtual CPU */
#define PF_WQ_WORKER 0x00000020 /* I'm a workqueue worker */
#define PF_FORKNOEXEC 0x00000040 /* forked but didn't exec */
#define PF_MCE_PROCESS 0x00000080 /* process policy on mce errors */
#define PF_SUPERPRIV 0x00000100 /* used super-user privileges */
#define PF_DUMPCORE 0x00000200 /* dumped core */
#define PF_SIGNALED 0x00000400 /* killed by a signal */
#define PF_MEMALLOC 0x00000800 /* Allocating memory */
#define PF_USED_MATH 0x00002000 /* if unset the fpu must be initialized before use */
#define PF_FREEZING 0x00004000 /* freeze in progress. do not account to load */
#define PF_NOFREEZE 0x00008000 /* this thread should not be frozen */
#define PF_FROZEN 0x00010000 /* frozen for system suspend */
#define PF_FSTRANS 0x00020000 /* inside a filesystem transaction */
#define PF_KSWAPD 0x00040000 /* I am kswapd */
#define PF_OOM_ORIGIN 0x00080000 /* Allocating much memory to others */
#define PF_LESS_THROTTLE 0x00100000 /* Throttle me less: I clean memory */
#define PF_KTHREAD 0x00200000 /* I am a kernel thread */
#define PF_RANDOMIZE 0x00400000 /* randomize virtual address space */
#define PF_SWAPWRITE 0x00800000 /* Allowed to write to swap */
#define PF_SPREAD_PAGE 0x01000000 /* Spread page cache over cpuset */
#define PF_SPREAD_SLAB 0x02000000 /* Spread some slab caches over cpuset */
#define PF_THREAD_BOUND 0x04000000 /* Thread bound to specific cpu */
#define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */
#define PF_MEMPOLICY 0x10000000 /* Non-default NUMA mempolicy */
#define PF_MUTEX_TESTER 0x20000000 /* Thread belongs to the rt mutex tester */
#define PF_FREEZER_SKIP 0x40000000 /* Freezer should not count it as freezable */
#define PF_FREEZER_NOSIG 0x80000000 /* Freezer won't send signals to it */
状态 | 描述 |
---|---|
PF_FORKNOEXEC | 表示进程刚被创建,但还没有执行 |
PF_SUPERPRIV | 表示进程拥有超级用户特权 |
PF_SIGNALED | 表示进程被信号杀出 |
PF_EXITING | 表示进程开始关闭 |
2.4 进程内核栈
在内核态(比如应用进程执行系统调用)时,进程运行需要自己的堆栈信息(不是原用户空间的栈),而是使用内核空间中的栈,这个栈就是进程的内核栈。
struct task_struct
{
// ...
void *stack; // 指向内核栈的指针
// ...
};
对每个进程,Linux内核都把两个不同的数据结构紧凑的存放在一个单独为进程分配的内存区域中
- 一个是内核态的进程堆栈,
- 另一个是紧挨着进程描述符的小数据结构thread_info,叫做线程描述符。
Linux把thread_info(线程描述符)和内核态的线程堆栈存放在一起,这块区域通常是8192K(占两个页框),其实地址必须是8192的整数倍。
内核态的进程访问处于内核数据段的栈,这个栈不同于用户态的进程所用的栈。
用户态进程所用的栈,是在进程线性地址空间中;
而内核栈是当进程从用户空间进入内核空间时,特权级发生变化,需要切换堆栈,那么内核空间中使用的就是这个内核栈。因为内核控制路径使用很少的栈空间,所以只需要几千个字节的内核态堆栈。
2.4.1 内核栈数据结构
task_struct数据结构中的stack成员指向thread_union结构(Linux内核通过thread_union联合体来表示进程的内核栈)。
Linux内核中使用一个联合体来表示一个进程的线程描述符和内核栈:
union thread_union
{
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
struct thread_info是记录部分进程信息的结构体,其中包括了进程上下文信息:
struct thread_info {
struct pcb_struct pcb; /* palcode state */
struct task_struct *task; /* main task structure */ /*这里很重要,task指针指向的是所创建的进程的struct task_struct
unsigned int flags; /* low level flags */
unsigned int ieee_state; /* see fpu.h */
struct exec_domain *exec_domain; /* execution domain */ /*表了当前进程是属于哪一种规范的可执行程序,
//不同的系统产生的可执行文件的差异存放在变量exec_domain中
mm_segment_t addr_limit; /* thread address space */
unsigned cpu; /* current CPU */
int preempt_count; /* 0 => preemptable, <0 => BUG */
int bpt_nsaved;
unsigned long bpt_addr[2]; /* breakpoint handling */
unsigned int bpt_insn[2];
struct restart_block restart_block;
};
thread_info 作用:
这个结构体保存了进程描述符中中频繁访问和需要快速访问的字段,内核依赖于该数据结构来获得当前进程的描述符(为了获取当前CPU上运行进程的task_struct结构,内核提供了current宏。
#define get_current() (current_thread_info()->task)
#define current get_current()
内核需要存储每个进程的PCB信息, linux内核是支持不同体系的, 但是不同的体系结构可能进程需要存储的信息不尽相同,这就需要我们实现一种通用的方式, 我们将体系结构相关的部分和无关的部门进行分离,用一种通用的方式来描述进程, 这就是struct task_struct, 而thread_info就保存了特定体系结构的汇编代码段需要访问的那部分进程的数据,我们在thread_info中嵌入指向task_struct的指针, 则我们可以很方便的通过thread_info来查找task_struct
2.5 进程亲属关系成员
/*
* pointers to (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
/*
* children/sibling forms the list of my natural children
*/
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
struct task_struct *group_leader; /* threadgroup leader */
在Linux系统中,所有进程之间都有着直接或间接地联系,每个进程都有其父进程,也可能有零个或多个子进程。拥有同一父进程的所有进程具有兄弟关系。
说明:
real_parent: 指向其父进程,如果创建它的父进程不再存在,则指向PID为1的init进程
parent: 指向其父进程,当它终止时,必须向它的父进程发送信号。它的值通常与real_parent相同
children : 子进程列表
sibling: 连接到父进程的子进程列表
group_leader: 指向其所在进程组的领头进程
2.6 进程调度
2.6.1 优先级
int prio, static_prio, normal_prio;
unsigned int rt_priority;
字段 | 说明 |
---|---|
static_prio | 用于保存静态优先级,可以通过nice系统调用来进行修改 |
rt_priority | 用于保存实时优先级 |
normal_prio | 的值取决于静态优先级和调度策略 |
prio | 用于保存动态优先级 |
2.6.2 调度策略
调度策略相关字段
/* http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.5#L1426 */
unsigned int policy;
/* http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.5#L1409 */
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
cpumask_t cpus_allowed;
policy: 调度策略
sched_class: 调度类
se: 普通进程的调用实体,每个进程都有其中之一的实体
rt: 实时进程的调用实体,每个进程都有其中之一的实体
cpus_allowed: 用于控制进程可以在哪里处理器上运行
调度策略分类
/*
* Scheduling policies
*/
#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
/* SCHED_ISO: reserved but not implemented yet */
#define SCHED_IDLE 5
#define SCHED_DEADLINE 6
SCHED_NORMAL :(也叫SCHED_OTHER)用于普通进程,通过CFS调度器实现。SCHED_BATCH用于非交互的处理器消耗型进程。SCHED_IDLE是在系统负载很低时使用。** CFS调度器类
SCHED_BATCH: SCHED_NORMAL普通进程策略的分化版本。采用分时策略,根据动态优先级(可用nice()API设置),分配 CPU 运算资源。注意:这类进程比上述两类实时进程优先级低,换言之,在有实时进程存在时,实时进程优先调度。但针对吞吐量优化。 ** CFS调度器类
SCHED_IDLE: 优先级最低,在系统空闲时才跑这类进程(如利用闲散计算机资源跑地外文明搜索,蛋白质结构分析等任务,是此调度策略的适用者)。 CFS调度器类
SCHED_FIFO: 先入先出调度算法(实时调度策略),相同优先级的任务先到先服务,高优先级的任务可以抢占低优先级的任务。 RT调度器类
SCHED_RR: 轮流调度算法(实时调度策略),后 者提供 Roound-Robin 语义,采用时间片,相同优先级的任务当用完时间片会被放到队列尾部,以保证公平性,同样,高优先级的任务可以抢占低优先级的任务。不同要求的实时任务可以根据需要用sched_setscheduler()API 设置策略。 RT调度器类
SCHED_DEADLINE :**新支持的实时进程调度策略,针对突发型计算,且对延迟和完成时间高度敏感的任务适用。基于Earliest Deadline First (EDF) 调度算法 。
调度类
sched_class结构体表示调度类,目前内核中有实现以下四种:
extern const struct sched_class stop_sched_class;
extern const struct sched_class dl_sched_class;
extern const struct sched_class rt_sched_class;
extern const struct sched_class fair_sched_class;
extern const struct sched_class idle_sched_class;
说明:
idle_sched_class :每个cup的第一个pid=0线程:swapper,是一个静态线程。调度类属于:idel_sched_class, 所以在ps里面是看不到的。一般运行在开机过程和cpu异常的时候做dump。
stop_sched_class: 优先级最高的线程,会中断所有其他线程,且不会被其他任务打断。作用:1.发生在 cpu_stop_cpu_callback 进行cpu之间任务migration;2.HOTPLUG_CPU的情况下关闭任务。
rt_sched_class: RT,作用:实时线程
fair_sched_class: CFS(公平),作用:一般常规线程
目前系統中,Scheduling Class的优先级顺序为StopTask > RealTime > Fair > IdleTask
开发者可以根据己的设计需求,來把所属的Task配置到不同的Scheduling Class中
2.7 进程地址空间
struct mm_struct *mm, *active_mm;
/* per-thread vma caching */
u32 vmacache_seqnum;
struct vm_area_struct *vmacache[VMACACHE_SIZE];
#if defined(SPLIT_RSS_COUNTING)
struct task_rss_stat rss_stat;
#endif
#ifdef CONFIG_COMPAT_BRK
unsigned brk_randomized:1;
#endif
mm: 进程所拥有的用户空间内存描述符,内核线程无的mm为NULL
active_mm: active_mm指向进程运行时所使用的内存描述符, 对于普通进程而言,这两个指针变量的值相同。但是内核线程kernel thread是没有进程地址空间的,所以内核线程的tsk->mm域是空(NULL)。但是内核必须知道用户空间包含了什么,因此它的active_mm成员被初始化为前一个运行进程的active_mm值。
brk_randomized: 用来确定对随机堆内存的探测。参见LKML上的介绍
**rss_stat **:用来记录缓冲信息
2.8 信号处理
/* signal handlers */
struct signal_struct *signal;
struct sighand_struct *sighand;
1583
sigset_t blocked, real_blocked;
sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
struct sigpending pending;
1587
unsigned long sas_ss_sp;
size_t sas_ss_size;
说明:
signal: 指向进程的信号描述符
sighand: 指向进程的信号处理程序描述符
blocked: 表示被阻塞信号的掩码,real_blocked表示临时掩码
pending: 存放私有挂起信号的数据结构
sas_ss_sp: 是信号处理程序备用堆栈的地址,sas_ss_size表示堆栈的大小
2.9 文件
/* file system info */
int link_count, total_link_count;
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
fs:用来表示进程与文件系统的联系,包括当前目录和根目录。
files:表示进程当前打开的文件。
2.10 中断
#ifdef CONFIG_GENERIC_HARDIRQS
/* IRQ handler threads */
struct irqaction *irqaction;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
unsigned int irq_events;
unsigned long hardirq_enable_ip;
unsigned long hardirq_disable_ip;
unsigned int hardirq_enable_event;
unsigned int hardirq_disable_event;
int hardirqs_enabled;
int hardirq_context;
unsigned long softirq_disable_ip;
unsigned long softirq_enable_ip;
unsigned int softirq_disable_event;
unsigned int softirq_enable_event;
int softirqs_enabled;
int softirq_context;
#endif
2.11 I/O调度的信息
struct io_context *io_context;
2.12 进程通信
SYSVIPC
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
struct sysv_sem sysvsem;
#endif
管道
struct pipe_inode_info *splice_pipe;
2.13 进程链表
多个进程使用一个链表来进行管理
struct list_head tasks;
2.14 socket控制消息
struct list_head *scm_work_list;