一、进程介绍:
1、什么是进程:
进程 = 程序 + 执行,进程是系统进行资源分配和调度的一个独立单位。
进程的鼻祖,idle进程(pid = 0) => kernel kthread进程(pid = 2);
=> 用户进程都是通过Init(pid = 1)进程创建;
2、进程和线程的区别是什么?
线程被称为轻量级的进程;线程和进程的区别在于进程拥有独立的资源空间,线程则共享进程的资源空间;
线程是进程的一个实体,是CPU调度和分派的基本单位。
3、进程的状态:
struct task_struct *task;
task_struct-> state //volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
(1)可运行状态(TASK_RUNNING)(R状态):进程正在执行,或者在队列中等待执行。
(2)可中断的等待状态(TASK_INTERRUPTIBLE)(S状态)(浅度睡眠):
等待资源到来是唤醒,也可以通过其他进程信号或时钟中断唤醒,进入运行队列。
(3)不可中断的等待状态(TASK_UNINTERRUPTIBLE)(D状态):
不可被其他进程信号或时钟中断唤醒,只有它所等待的资源可用的时候,他才会被唤醒。
(4)暂停状态(TASK_STOPPED)(T状态):
进程暂停执行接受某种处理。如正在接受调试的进程处于这种状态。
(5)僵尸状态(TASK_ZOMBIE)(Z状态):
进程已经结束但未释放PCB,父进程还没有调用wait()系统调用。一旦父进程调用了wait(),
进程描述符就会被释放。
4、进程的类型:
(1)实时进程:
对整体时延有严格要求的进程,比如VR设备对进程的实时性要求非常高,否则会造成晕眩;
(2)交互式进程:
人机交互的进程,和鼠标、键盘、触摸屏等相关的应用;
(3)批处理进程:
默默的工作和付出,可能会占用比较多的系统资源,如代码编译;
二、进程的描叙(task_struct)
Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个进程所需的
所有信息。它定义在include/linux/sched.h文件中。
1、进程的状态:
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
进程的可能状态值有很多:
//kernel-4.9/include/linux/sched.h
#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_DEAD 16
#define EXIT_ZOMBIE 32
......
2、进程PID:
pid_t pid;
pid_t tgid;
PID的取值范围是0到32767,即系统中的进程数最大为32768个。
3、进程优先级:
int prio, static_prio, normal_prio;
unsigned int rt_priority;
prio | 动态优先级 |
static_prio | 静态优先级 |
normal_prio | 取决于静态优先级和调度策略 |
rt_priority | 实时优先级 |
(1)静态优先级static_prio:
- 0 ~ 99 用于real-time processes,100 – 139用于普通进程;
- 用户空间可以通过nice()或者setpriority对该静态优先级进行修改,通过getpriority可以获取该值;
- 新创建的进程会继承父进程的static priority;
- 修改了静态优先级,那么normal priority和动态优先级都需要重新计算。
(2)实时优先级rt_priority:
从用户空间的视角来看的scheduling priority,0是普通进程,1~99是实时进程,99的优先级最高。
(3)normal_prio归一化优先级:
normal_prio 的值取决于静态优先级和调度策略,可以通过 _setscheduler 函数来设置 normal_prio 的值 。
对于非实时进程,normal_prio 的值就等于静态优先级值 static_prio;对于实时进程:
normal_prio = MAX_RT_PRIO-1 - p->rt_priority。代码如下:
static inline int normal_prio(struct task_struct *p)
{
int prio;
if (task_has_dl_policy(p))
//deadline的进程比RT进程和normal进程的优先级还要高,它的归一化优先级是负数:-1
prio = MAX_DL_PRIO-1;
else if (task_has_rt_policy(p))
//实时进程的优先级和normalized priority和rt_priority有关
prio = MAX_RT_PRIO-1 - p->rt_priority;
else
//普通进程,normalized priority就是其静态优先级
prio = __normal_prio(p);
return prio;
}
(4)动态优先级prio:
设定动态优先级的代码:p->prio = effective_prio(p),代码如下:
static int effective_prio(struct task_struct *p)
{
p->normal_prio = normal_prio(p);
/*
* If we are RT tasks or we were boosted to RT priority,
* keep the priority unchanged. Otherwise, update priority
* to the normal priority:
*/
if (!rt_prio(p->prio))
return p->normal_prio;
return p->prio;
}
4、进程调度相关:
(1)调度策略:
unsigned int policy;
/*
* 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
(2)调度类:
const struct sched_class *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;//CFS公平调度类
extern const struct sched_class idle_sched_class;
linux系統中,Scheduling Class的优先级顺序为StopTask > RealTime > Fair > IdleTask。
(3)调度实体:
struct sched_entity se; //普通进程的调用实体
struct sched_rt_entity rt; //实时进程的调用实体
其中调度队列runqueue和调度实体之间的关系如下:
调度类 |
描述 |
调度策略 |
dl_sched_class |
deadline调度器 |
SCHED_DEADLINE |
rt_sched_class |
实时调度器 |
SCHED_FIFO、SCHED_RR |
fair_sched_class |
完全公平调度器 |
SCHED_NORMAL、SCHED_BATCH |
idle_sched_class |
idle task |
SCHED_IDLE |
每个CPU都会有一个全局的就绪队列(cpu runqueue),使用struct rq
结构体描述管理就绪态的
struct sched_entity
调度实体,struct rt_rq
是实时调度器就绪队列,struct dl_rq
是Deadline调度器就绪队列。
5、内核栈:
struct task_struct {
......
void *stack;//指向内核栈的指针
}
每一个进程都对应一个task_struct的结构体,每一个task_struct对应一个内核栈stack,对于32bit的系统,
此结机构体的大小为8KB,64bit的为16KB,内核栈和thread_info以及task_struct关系如下图:
内核有一个current的变量用于获取当前进程的task_struct的结构体的地址,
#include <linux/thread_info.h>
#define get_current() (current_thread_info()->task)
#define current get_current()
此current变量来自于thread_info->task,这样通过SP寄存器获取当前内核栈stack的地址,对齐后可以
获得struct thread_info数据结构,自然就能获取到task_struct的信息的。下面看thread_info的结构:
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0 => preemptable, <0 => bug */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
struct cpu_context_save cpu_context; /* cpu context */
__u32 syscall; /* syscall number */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value[2]; /* TLS registers */
#ifdef CONFIG_CRUNCH
struct crunch_state crunchstate;
#endif
union fp_state fpstate __attribute__((aligned(8)));
union vfp_state vfpstate;
#ifdef CONFIG_ARM_THUMBEE
unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif
int cpu_excp;
void *regs_on_excp;
};
(1)preempt_count
preempt_count代表的是该进程是否可以被抢占,根据注释的说明当peermpt_count等于0的时候当前进程
就可以被抢占,当小于0存在bug,当不等于0也就是大于0说明当前进程不可以被抢占。不可抢占的原因很多,
比如当前进程在中断上下文中或者使用了锁(spin_lock的过程中会disable掉抢占的)。
#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
#define preempt_enable() \
do { \
barrier(); \
preempt_count_dec(); \
} while (0)
#define preempt_count_inc() preempt_count_add(1)
#define preempt_count_dec() preempt_count_sub(1)
spin_lock -> preempt_disable ===> preempt_count + 1
spin_unlock -> preempt_enable ===> preempt_count - 1
(2)struct task_struct *task;
指向对应struct task_sturct描叙符;
三、总结:
学习进程管理,需要了解进程的如下信息,后续会继续介绍进程管理的其他内容:
- 进程的状态
- 进程的类型
- 进程的优先级
- 进程的调度策略
- 进程众多结构体的关系
作者:frank_zyp
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文无所谓版权,欢迎转载。