Linux进程调度
1、程序VS进程
程序是存放在磁盘上的一序列代码和数据的可执行映像,是一个静止的实体。
进程是一个执行中的程序,是动态的实体。
2、Linux进程的四大要素
<1>有一段供进程执行的程序,该程序可以被多个进程执行。
<2>:有进程专用的内核空间堆栈。
<3>:进程控制快(task_struct:有了这个数据结构,进程才能成为内核调度的一个基本单位接受内核的调度。
<4>:独立的用户空间
进程:有独立的进程空间
线程:只有前三条,没有第四条。
内核线程:完全没有用户空间。
用户线程:共享用户空间。
3、Linux进程分类:
<1>:交互式进程:这些进程经常和用户发生交互,所以花费一些时间等待用户的操作。当有输入时,进程必须很快的激活。通常,要求延迟在50-150毫秒。典型的交互式进程有:控制台命令,文本编辑器,图形应用程序。
<2>:批处理进程(Batch Process):不需要用户交互,一般在后台运行。所以不需要非常快的反应,他们经常被调度期限制。典型的批处理进程:编译器,数据库搜索引擎和科学计算。
<3>:实时进程:对调度有非常严格的要求,这种类型的进程不能被低优先级进程阻塞,并且在很短的时间内做出反应。典型的实时进程:音视频应用程序,机器人控制等。
批处理进程可能与I/O或者CPU有关,但是实时进程完全通过Linux的调度算法识别。
其实交互式进程和批处理进程很难区别。
4:Linux进程优先级
<1>、静态优先级(priority): 被称为“静态”是因为它不随时间而改变,只能由用户进行修改。它指明了在被迫和其它进程竞争CPU之前该进程所应该被允许的时间片的最大值(20)。
每个普通进程都一个静态优先级,内核为其分配的优先级数为:100(高优先级)-139(低优先级)。数值越大,优先级越低。新创建的进程一般继承父进程的优先级,但是用户可以通过给nice()函数传递“nice value“或者setpriority()改变优先级。
<2>:、动态优先级(counter): counter 即系统为每个进程运行而分配的时间片,Linux 兼用它来表示进程的动态优先级。只要进程拥有CPU,它就随着时间不断减小;当它为0 时,标记进程重新调度。它指明了在当前时间片中所剩余的时间量(最初为20)
事实上,在进程在调度的时候,调度器只察看动态优先级,其值为100-139。通过下面的公式可以根据静态优先计算出相应的动态优先级。
Dynamicy priority = max (100, min (static priority - bonus + 5, 139))
Bonus:0-10,比5小,降低动态优先级,反之,可以提高动态优先级。Bonus和进程的平均睡眠时间有关。
<3>、 实时优先级(rt_priority):值为1000。Linux把实时优先级与counter值相加作为实时进程的优先权值。较高权值的进程总是优先于较低权值的进程,如果一个进程不是实时进程,
其优先权就远小于1000,所以实时进程总是优先。
<4>、Base time quantum:是由静态优先级决定,当进程耗尽当前Base time quantum,kernel会重新分配一个Base time quantum给它。静态优先级和Base time quantum的关系为:
(1) 当静态优先级小于120
Base time quantum(in millisecond)= (140 – static priority) * 20
(2) 当静态优先级大于等于120
Base time quantum(in millisecond)= (140 – static priority) * 5
5、Linux 进程的调度算法
<1>、时间片轮转调度算法(round-robin):SCHED_RR,用于实时进程。系统使每个进程依次地按时间片轮流执行的方式。
<2>、优先权调度算法:SCHED_NORMAL,用于非实时进程。系统选择运行队列中优先级最高的进程运行。Linux 采用抢占式的优级算法,即系统中当前运行的进程永远是可运行进程中优先权最高的那个。
<3>、FIFO(先进先出) 调度算法:SCHED_FIFO,用于实时进程。
6、Linux 进程的调度时机schedule()
方式:主动式和被动式
主动式:在内核中直接调用schedule()
current->state=TASK_INTERRUPTIBLE
TASK_RUNNING
被动式:用户抢占——从系统调用或者中断处理程序返回用户空间
内核抢占——在不支持内核抢占的系统中,进程一旦运行于内核空间就可以一直运行,直到它主动放弃或者时间片耗尽。
在支持内核抢占的系统中,更高优先级的进程可以抢占正在内核空间运行的低优先级进程。
有些特例是不允许抢占的。
<1>、内核正在中断处理
<2>、内核正在中断上下文的Bottom Half处理
<3>、进程正持有Spinlock自旋锁
<4>、内核正在执行调度程序schedule()
7、代码分析
<1>、2.6 版的内核仍然用 task_struct 来表征进程,尽管对线程进行了优化,但线程的内核表示仍然与进程相同。随着调度器的改进,task_struct 的内容也有了改进,交互式进程优先支持、内核抢占支持等新特性,在task_struct 中都有所体现。在 task_struct 中,有的属性是新增加的,有的属性的值的含义发生了变化,而有的属性仅仅是改了一下名字。可称为进程控制块(TCB),主要包含进程标识符、优先级、堆栈空间、进程状态
task_struct源代码定义在kernel/include/Linux/sched.h中
struct task_struct {
volatile longstate; /* -1 unrunnable, 0 runnable, >0 stopped */
structthread_info *thread_info;
atomic_tusage;
unsigned longflags; /* per process flags, defined below */
unsigned longptrace;
int lock_depth; /* Lock depth */
int prio, static_prio;
structlist_headrun_list;
prio_array_t *array;
unsigned long sleep_avg;
long interactive_credit;
unsigned long longtimestamp;
int activated;
unsigned long policy;
cpumask_t cpus_allowed;
unsigned int time_slice, first_time_slice;
structlist_headtasks;
/*
* ptrace_list/ptrace_children formsthe list of my children
* that were stolen by a ptracer.
*/
structlist_head ptrace_children;
structlist_head ptrace_list;
structmm_struct *mm, *active_mm;
};
<2>、核心结构task_struct中的state
进程的状态仍然用 state 表示
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_STOPPED 4
#define TASK_ZOMBIE 8
#define TASK_DEAD 16
2.6新增加了两种状态:TRACED、DEAD。
新增加的TASK_DEAD指的是已经退出且不需要父进程来回收的进程。TASK_TRACED供调试使用。
原有的几个state:
TASK_ZOMBIE一个已经终止的但仍保留有任务的进程(已经死了,户口未注销)。
TASK_RUNNING就绪态(准确的说你应该是task_runable)
TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE不同深度的睡眠态
TASK_STOPPED描述一个已经停止的进程,当进程收到一个特殊信号或被时候ptrace系统调用的进程监控,并将监控权交给监控进程。Linux2.4版,内核在创建进程是,为每个进程配两个连续的物理页面(8KB)它的顶端(低地址部分)用来存储进程的task_struct结构(约1KB),剩下的约7KB就是进程的系统空间堆栈,内核可以通过栈寄存器指针ESP快速地方位该进程。
<3>thread_info
在linux2.6中。这两个页面顶端存放的不再是进程的整个task_struct结构,而是task_struct中的thread_info,task_struct的大部分信息保存在栈外,通过的task指针可以方便地访问到。
thread_info是描述一个描述任务的重要结构体,下面将看到thread_info的数据结构定义(/include/asm-386/thread_.h)
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
unsigned long flags; /* low level flags */
unsigned long status; /* thread-synchronous flags */
__u32 cpu; /* current CPU */
__s32 preempt_count; /* 0 => preemptable, <0 => BUG */
mm_segment_t addr_limit; /* thread address space:
0-0xBFFFFFFF for user-thead
0-0xFFFFFFFF for kernel-thread
*/
struct restart_block restart_block;
unsigned long previous_esp; /* ESP of the previous stack in case
of nested (IRQ) stacks
*/
__u8 supervisor_stack[0];
};
task指针指向其对应的任务控制块
preempt_count是用来表示内核能否被抢占的使能成员,如果它大于0.表示内核不能被抢占:如果等于0,则表示内核处于安全状态(即没有加锁),可以抢占。
Flags里面有一个TIF_NEED_RESCHED(调度标志)位,如果此标志位为1.则表示应该尽快启动调度器。
8、调度步骤
Schedule()函数工作流程:
<1>、清理当前运行中的进程
<2>、选择下一个要运行的进程
<3>、设置新进程的运行环境
<4>、进程上下文切换