3.7.1. 进程描述
进程是出于执行期的程序以及它所管理的资源(如打开的文件、挂起的信号、进程状态、地址空间等等)的总称。 注意,程序不是进程,实际上两个或者多个进程有可能执行同意程序,而且还可能共享地址空间等资源。
linux通过一个被称为进程描述符的 task_struct
结构体来管理进程,这个结构体包含了一个进程所需要的所有信息. 它定义在 include/linux/sched.h
文件中
3.7.1.1. 进程状态
volatitle long state; /* -1 unrunnable, 0 runnable, > 0 stopped */
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.
*/
/* 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 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
#define TASK_STATE_MAX 0x1000
state域能够取5个互为排斥的值,系统中的每个进程都必然处于以上所列进程状态中的一中。
state |
描述 |
---|---|
TASK_RUNNING |
表示进程要么正在执行或者已经就绪,正在等待cpu时间片的调度 |
TASK_INTERRUPTIBLE |
进程因为等待一些条件而被挂起(阻塞)而处的状态,这些条件主要包括硬 中断、资源、信号…,一旦等待的条件成立,进程就会从该状态迅速转换 为TASK_RUNNING |
TASK_UNINTERRUPTIBLE |
意义与TASK_INTERRUPTIBLE类似,不同的是中断、信号不能唤醒它们,只能 在它所等待的资源可用的时候,才会被唤醒。 |
TASK_STOPPED |
进程被停止执行,当进程收到SIGSTOP SIGTTIN SIGTSTP或者SIGTOU 信号 之后就会进入该状态 |
TASK_TRACED |
进程被debugger等进程监视 |
2个中止状态
只有当进程终止的时候才会达到这两种状态
int exit_state;
int exit_code, exit_signal;
当进程被终止时,其父进程还没有使用wait()等系统调用来获取它的终止信息,此时进程称为僵尸进程 EXIT_ZOMBIE
进程的最终状态 EXIT_DEAD
睡眠状态
TASK_INTERRUPTBLTE 和 TASK_UNINTERRUPTBLTE 这两种状态都属于睡眠状态.
linux内核提供了两种方法将进程置为睡眠状态
如果进程处于可中断模式的睡眠状态,那么可以通过显示的唤醒(wakeup_process())或需要处理的信号来唤醒它
如果进程处于非可中断模式的睡眠状态,那么只能通过显示的唤醒呼叫将其唤醒,除非万不得已否则不建议将进程置为不可中断睡眠模式
内核中还有一种新的睡眠状态, TASK_KILLABLE
它的运行原理与 TASK_UNINTERRUPTIBLE 类似,只不过可以响应致命信号
/* Convenience macros for the sake of set_current_state: */
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED)
#define TASK_IDLE (TASK_UNINTERRUPTIBLE | TASK_NOLOAD)
3.7.1.2. 进程描述符
pid_t pid;
pid_t tgid;
unix系统通过pid来标识进程,linux把不同的pid与系统中每个进程或轻量级线程关联,一个线程所有线程与领头线程具有相同的pid,存入tgid字段,getpid()返回当前进程的tgid 而不是pid的值
#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
#define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \
(sizeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT))
3.7.1.3. 进程内核栈
void *stack;
3.7.1.3.1. 内核栈与线程描述符
对于每个进程,linux内核把两个不同的数据结构紧凑的存放在一个单独为进程分配的内存区域中
一个是内核态的进程堆栈
2)另一个是紧挨着进程描述符的小数据结构 thread_info
,叫做线程描述符
linux把 thread_info 和内核态的线程堆栈放在一起,这块区域通常是8192(占两个页框),其实地址必须是8192的整数倍
内核太的进程访问处于内核数据段的栈,这个栈不同于用户态的进程所用的栈.用户态的进程所用的栈,是在进程线性地址空间中。而内核栈是当前进程从用户空间进入内核空间时, 特权级别发生变化需要切换堆栈,那么内空间中使用的就是这个内核栈
3.7.1.3.2. 内核栈数据接哦古描述thread_info和thread_union
thread_info是体系结构相关的,结构的定义在thread_info.h中, arm64体系结构中位于arch/arm64/include/asm/thread_info.h
/*
* low level task data that entry.S needs immediate access to.
*/
struct thread_info {
unsigned long flags; /* low level flags */
mm_segment_t addr_limit; /* address limit */
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
u64 ttbr0; /* saved TTBR0_EL1 */
#endif
union {
u64 preempt_count; /* 0 => preemptible, <0 => bug */
struct {
#ifdef CONFIG_CPU_BIG_ENDIAN
u32 need_resched;
u32 count;
#else
u32 count;
u32 need_resched;
#endif
} preempt;
};
};
linux 内核中使用一个联合体来表示一个进程的线程描述符和内核栈
union thread_union {
#ifndef CONFIG_ARCH_TASK_STRUCT_ON_STACK
struct task_struct task;
#endif
#ifndef CONFIG_THREAD_INFO_IN_TASK
struct thread_info thread_info;
#endif
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
获取当前CPU上正在运行进程的thread_info
进程最常用的是进程描述符结构task_struct而不是thread_info结构的地址,为了获取当前CPU上运行进程的task_struct结构,内核提供了current宏,由于task_struct task 在thread_info的起始位置,该宏 本质上current_thread_info()->task
static __always_inline struct task_struct *get_current(void)
{
unsigned long sp_el0;
asm ("mrs %0, sp_el0" : "=r" (sp_el0));
return (struct task_struct *)sp_el0;
}
#define current get_current()
#define current_thread_info() ((struct thread_info *)current)
分配和销毁thread_info
进程通过alloc_thread_info_node函数分配它的内核栈,通过free_thread_info函数释放所分配的内核栈
static struct kmem_cache *thread_stack_cache;
static unsigned long *alloc_thread_stack_node(struct task_struct *tsk,
int node)
{
unsigned long *stack;
stack = kmem_cache_alloc_node(thread_stack_cache, THREADINFO_GFP, node);
tsk->stack = stack;
return stack;
}
static void free_thread_stack(struct task_struct *tsk)
{
kmem_cache_free(thread_stack_cache, tsk->stack);
}
void thread_stack_cache_init(void)
{
thread_stack_cache = kmem_cache_create_usercopy("thread_stack",
THREAD_SIZE, THREAD_SIZE, 0, 0,
THREAD_SIZE, NULL);
BUG_ON(thread_stack_cache == NULL);
}
3.7.1.4. 进程标记
- ::
unsigned int flags;
反应进程状态信息,但不是运行状态,用于内核识别进程当前状态,以备下一步操作
flags成员可能取值如下,这些以PF(processflag)开头
/*
* Per process flags
*/
#define PF_IDLE 0x00000002 /* I am an IDLE thread */
#define PF_EXITING 0x00000004 /* Getting 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_NPROC_EXCEEDED 0x00001000 /* set_user() noticed that RLIMIT_NPROC was exceeded */
#define PF_USED_MATH 0x00002000 /* If unset the fpu must be initialized before use */
#define PF_USED_ASYNC 0x00004000 /* Used async_schedule*(), used by module init */
#define PF_NOFREEZE 0x00008000 /* This thread should not be frozen */
#define PF_FROZEN 0x00010000 /* Frozen for system suspend */
#define PF_KSWAPD 0x00020000 /* I am kswapd */
#define PF_MEMALLOC_NOFS 0x00040000 /* All allocation requests will inherit GFP_NOFS */
#define PF_MEMALLOC_NOIO 0x00080000 /* All allocation requests will inherit GFP_NOIO */
#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_MEMSTALL 0x01000000 /* Stalled due to lack of memory */
#define PF_UMH 0x02000000 /* I'm an Usermodehelper process */
#define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_mask */
#define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */
#define PF_MEMALLOC_NOCMA 0x10000000 /* All allocation request will have _GFP_MOVABLE cleared */
#define PF_FREEZER_SKIP 0x40000000 /* Freezer should not count it as freezable */
#define PF_SUSPEND_TASK 0x80000000 /* This thread called freeze_processes() and should not be frozen */
3.7.1.5. 表示进程亲属关系的成员
/* 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;
struct task_struct *group_leader;
linux 系统中所有进程之间都直接或者间接的存在关系
字段 |
描述 |
---|---|
real_patent |
指向父进程,如果创建它的父进程不存在了则指向PID为1的init进程 |
parent |
指向父进程,当它终止时,必须向它的父进程发送信号,一般与real_parent相同 |
children |
表示链表的头部,链表中的所有元素都是它的子进程 |
sibling |
用于吧当前进程插入到兄弟链表中 |
group_leader |
指向其所在进程组的领头进程 |
3.7.1.6. 优先级
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
实时优先级的范围时0-MAX_RT_PRIO-1(即99),而普通进程的静态优先级范围时MAX_RT_PRIO到MAX_PRIO-1(即100-139), 值越大优先级越低
字段 |
描述 |
---|---|
static_prio |
用于保存静态优先级,可以通过nice系统调用来进行修改 |
prio |
用于保存动态优先级 |
normal_pro |
normal_prio的值取决于静态优先级和调度策略 |
rt_priority |
用于保存实时优先级 |
/**
* task_nice - return the nice value of a given task.
* @p: the task in question.
*
* Return: The nice value [ -20 ... 0 ... 19 ].
*/
static inline int task_nice(const struct task_struct *p)
{
return PRIO_TO_NICE((p)->static_prio);
}
prio定义如下
#define MAX_NICE 19
#define MIN_NICE -20
#define NICE_WIDTH (MAX_NICE - MIN_NICE + 1)
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO
#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2)
3.7.1.7. 调度策略相关字段
unsigned int policy;
cpumask_t cpus_mask;
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
struct sched_dl_entity dl;
各字段定义如下
字段 |
描述 |
---|---|
polocy |
调度策略 |
sched_class |
调度类 |
se |
普通进程的调用实体,每个进程都有其中之一的实体 |
rt |
实时进程的调度实体 |
dl |
|
cpus_allowed |
用于控制进程可以在那个CPU上运行 |
policy表示调度策略,目前主要有以下5种
/*
* 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 |
用于普通进程,通过CFS调度器实现 |
cfs |
SCHED_BATCH |
SCHED_NORMAL普通进程的分化版本,采用分时策略,根据动态优先级,分配CPU运算资源. |
cfs |
SCHED_IDLE |
优先级最低,只有在系统空闲时才跑这类进程 |
cfs |
SCHED_FIFO |
先入先出(实时调度策略),相同优先级的先到先服务,高优先级的可以抢占低优先级的任务 |
rt |
SCHED_RR |
轮流调度算法(实时调度策略),采用时间片,相同优先级的当用完时间片以后会被放到队列尾部,以保证 公平性,高优先级的可以抢占低优先级的 |
rt |
SCHED_DEADLINE |
新支持的实时进程调度策略,针对突发型计算,且对延迟和完成时间高度敏感的任务适用. |
3.7.1.8. 调度类
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;
3.7.1.9. 进程地址空间
struct mm_struct *mm;
struct mm_struct *active_mm;
/* Per-thread vma caching: */
struct vmacache vmacache;
字段 |
描述 |
---|---|
mm |
进程所用于的用户地址空间描述符,内核线程mm为NULL |
active_mm |
普通进程与mm的值相同,内核线程时被初始化为前一个运行进程的active_mm的值 |
vmacache |
如果当前内核线程被调度之前运行的是另外一个内核线程的时候,那么mm和active_mm都是NULL
3.7.1.10. 判断标志
int exit_state;
int exit_code;
int exit_signal;
/* The signal sent when the parent dies: */
int pdeath_signal;
/* JOBCTL_*, siglock protected: */
unsigned long jobctl;
/* Used for emulating ABI behavior of previous Linux versions: */
unsigned int personality;
/* Scheduler bits, serialized by scheduler locks: */
unsigned sched_reset_on_fork:1;
unsigned sched_contributes_to_load:1;
unsigned sched_migrated:1;
unsigned sched_remote_wakeup:1;
/* Force alignment to the next boundary: */
unsigned :0;
/* Unserialized, strictly 'current' */
/* Bit to tell LSMs we're in execve(): */
unsigned in_execve:1;
unsigned in_iowait:1;
各字段描述如下
字段 |
描述 |
---|---|
exit_code |
用于设置进程的终止代号,要么是_exit()系统调用参数(正常终止)或者是内核提供的错误代号(异常终止) |
exit_signal |
只有当线程组的最后与i个成员终止时,才会产生一个信号,以通知线程组的领头进程的父进程 |
pdeath_signal |
用于判断父进程终止时发送信号 |
persionality |
用于处理不通的ABI |
in_iowait |
用于判断是否进行iowait计数 |
sched_reset_on_fork |
用于判断是否恢复默认的优先级或者调度策略 |
3.7.1.11. 时间
u64 utime;
u64 stime;
u64 utimescaled;
u64 stimescaled;
u64 gtime;
struct prev_cputime prev_cputime;
unsigned long nvcsw;
unsigned long nivcsw;
u64 start_time;
u64 real_start_time;
字段 |
描述 |
---|---|
utime/stime |
用于记录进程在用户态/内核态下所经过的节拍数字(定时器) |
utimescaled |
用于记录进程在用户态的运行时间,但以处理器的频率为刻度 |
gtime |
以节拍技术的虚拟机运行时间(guest time) |
vncsw/nivcsw |
自愿和非自愿的上下文切换计数 |
start_time/real_s_t |
进程创建时间,real_start_time包含了进程睡眠时间,常用于/proc/pid/stat |
3.7.1.12. 信号处理
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
字段 |
描述 |
---|---|
signal |
指向进程的信号描述符 |
sighand |
指向进程的信号处理程序描述符 |
blocked |
表示被阻塞信号的掩码,real_blocked 表示临时掩码 |
pending |
存放私有挂起信号的数据结构 |
sas_ss_sp |
是信号处理程序备用堆栈的地址,sas_ss_size表示堆栈的大小 |
3.7.1.13. 其他
spinlock_t alloc_lock; //用于保护资源分配或者释放的自旋锁
atomic_t usage; //进程描述符使用计数,被置为2时,表示进程描述符正在被使用而且其相应的进程处于活动状态
struct sched_info sched_info; //用于调度器统计进程的运行信息
struct list_head tasks; //用于构建进程链表
int link_count, total_link_count;
struct fs_struct *fs; //fs用来表示进程与文件系统的联系,包括当前目录和根目录
struct files_struct *files; //files表示进程打开的文件
struct sysv_sem sysvsem; //进程通信(SYSVIPC)
struct thread_struct thread; //处理器特有数据
struct nsproxy *nsproxy; //命名空间
struct bio_list *bio_list; //块设备链表
struct io_context *io_context; //I/O调度器所使用的信息