linux操作系统进程调度与进程切换
Posted 西邮菜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux操作系统进程调度与进程切换相关的知识,希望对你有一定的参考价值。
一、进程调度是由schedule函数来实现的。
首先进程有五个状态:运行状态、可中断睡眠状态、不可中断睡眠状态、暂停状态、僵死状态。
#define TASK_RUNNING 0 可以运行状态
#define TASK_INTERRUPTIBLE 1 可以使用信号来唤醒变为可运行状态(例:父进程在等待子进程时)
#define TASK_UNINTERRUPTIBLE 2 只能利用wakeup唤醒变为可运行状态
#define TASK_ZOMBIE 3 收到SIGSTOP、SIGTSTP、SIGTTIN三种信号暂停
#define TASK_STOPPED 4 进程停止运行,但父进程未收尸
在schedule中,首先给设立闹钟时间且已到点的进程发送信号,如果进程有非阻塞信号、且进程状态是可中断状态,则将其状态设置为就绪态。在while中遍历每个进程,找出可运行且时间片最大的那个进程,最后判断c的值,如果最大c的值为0,那么代表所有进程时间片全用完了,用for循环根据任务优先级重新分配时间片,如果最大c的值不为0,则退出循环,并执行switch_to()函数进行任务切换。
void schedule(void)
int i,next,c;
struct task_struct ** p;
/* check alarm, wake up any interruptible tasks that have got a signal */
// 从任务数组中最后一个任务开始循环检测alarm。在循环时跳过空指针项。
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
// 如果设置过任务的定时值alarm,并且已经过期(alarm<jiffies),则在
// 信号位图中置SIGALRM信号,即向任务发送SIGALARM信号。然后清alarm。
// 该信号的默认操作是终止进程。jiffies是系统从开机开始算起的滴答数(10ms/滴答)。
if ((*p)->alarm && (*p)->alarm < jiffies)
(*p)->signal |= (1<<(SIGALRM-1));
(*p)->alarm = 0;
// 如果信号位图中除被阻塞的信号外还有其他信号,并且任务处于可中断状态,则
// 置任务为就绪状态。其中'~(_BLOCKABLE & (*p)->blocked)'用于忽略被阻塞的信号,但
// SIGKILL 和SIGSTOP不能呗阻塞。
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
/* this is the scheduler proper: */
while (1)
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
// 这段代码也是从任务数组的最后一个任务开始循环处理,并跳过不含任务的数组槽。比较
// 每个就绪状态任务的counter(任务运行时间的递减滴答计数)值,哪一个值大,运行时间还
// 不长,next就值向哪个的任务号。
while (--i)
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
// 如果比较得出有counter值不等于0的结果,或者系统中没有一个可运行的任务存在(此时c
// 仍然为-1,next=0),则退出while(1)_的循环,执行switch任务切换操作。否则就根据每个
// 任务的优先权值,更新每一个任务的counter值,然后回到while(1)循环。counter值的计算
// 方式counter=counter/2 + priority.注意:这里计算过程不考虑进程的状态。
if (c) break;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
// 用下面的宏把当前任务指针current指向任务号Next的任务,并切换到该任务中运行。上面Next
// 被初始化为0。此时任务0仅执行pause()系统调用,并又会调用本函数。
switch_to(next); // 切换到Next任务并运行。
二、进程切换是由switch_to()函数实现的
一段宏定义代码,首先比较切换的任务是不是当前进程,如果不是,则将current与ecx寄存器转化,
进程切换两步:
1、将任务指针付给current指针。
2、执行上下文切换。
#define switch_to(n) \\
struct long a,b; __tmp; \\
__asm__("cmpl %%ecx,current\\n\\t" \\
"je 1f\\n\\t" \\
"movw %%dx,%1\\n\\t" \\
"xchgl %%ecx,current\\n\\t" \\
"ljmp *%0\\n\\t" \\
"cmpl %%ecx,last_task_used_math\\n\\t" \\
"jne 1f\\n\\t" \\
"clts\\n" \\
"1:" \\
::"m" (*&__tmp.a),"m" (*&__tmp.b), \\
"d" (_TSS(n)),"c" ((long) task[n])); \\
三、休眠进程sleep_on()
当某个进程想访问CPU资源时,但访问不到时,调用该函数将进程休眠等待资源被释放。该函数可以形成一个进程等待链表。
函数执行过程为如果为0号进程则进行提示,将 进程插入等待队列中,并将进程状态改为不可中断睡眠状态(只有wakeup可以唤醒)。然后进行任务调度。在这函数里面需要注意的是等待队列的形成,里面tmp指针是局部变量,每个进程都拥有。
形成类似下图的等待队列:
// 把当前任务置为不可中断的等待状态,并让睡眠队列指针指向当前任务。
// 只有明确的唤醒时才会返回。该函数提供了进程与中断处理程序之间的同步机制。函数参数P是等待
// 任务队列头指针。指针是含有一个变量地址的变量。这里参数p使用了指针的指针形式'**p',这是因为
// C函数参数只能传值,没有直接的方式让被调用函数改变调用该函数程序中变量的值。但是指针'*p'
// 指向的目标(这里是任务结构)会改变,因此为了能修改调用该函数程序中原来就是指针的变量的值,
// 就需要传递指针'*p'的指针,即'**p'.
void sleep_on(struct task_struct **p)
struct task_struct *tmp;
// 若指针无效,则退出。(指针所指向的对象可以是NULL,但指针本身不应该为0).另外,如果
// 当前任务是任务0,则死机。因为任务0的运行不依赖自己的状态,所以内核代码把任务0置为
// 睡眠状态毫无意义。
if (!p)
return;
if (current == &(init_task.task))
panic("task[0] trying to sleep");
// 让tmp指向已经在等待队列上的任务(如果有的话),例如inode->i_wait.并且将睡眠队列头的
// 等等指针指向当前任务。这样就把当前任务插入到了*p的等待队列中。然后将当前任务置为
// 不可中断的等待状态,并执行重新调度。
tmp = *p;
*p = current;
current->state = TASK_UNINTERRUPTIBLE;
schedule();
// 只有当这个等待任务被唤醒时,调度程序才又返回到这里,表示本进程已被明确的唤醒(就
// 续态)。既然大家都在等待同样的资源,那么在资源可用时,就有必要唤醒所有等待该该资源
// 的进程。该函数嵌套调用,也会嵌套唤醒所有等待该资源的进程。这里嵌套调用是指一个
// 进程调用了sleep_on()后就会在该函数中被切换掉,控制权呗转移到其他进程中。此时若有
// 进程也需要使用同一资源,那么也会使用同一个等待队列头指针作为参数调用sleep_on()函数,
// 并且也会陷入该函数而不会返回。只有当内核某处代码以队列头指针作为参数wake_up了队列,
// 那么当系统切换去执行头指针所指的进程A时,该进程才会继续执行下面的代码,把队列后一个
// 进程B置位就绪状态(唤醒)。而当轮到B进程执行时,它也才可能继续执行下面的代码。若它
// 后面还有等待的进程C,那它也会把C唤醒等。在这前面还应该添加一行:*p = tmp.
if (tmp) // 若在其前还有存在的等待的任务,则也将其置为就绪状态(唤醒).
tmp->state=0;
四、wakeup()
// 唤醒*p指向的让任务。*p是任务等待队列头指针。由于新等待任务是插入在等待队列头指针处的,
// 因此唤醒的是最后进入等待队列的任务。
void wake_up(struct task_struct **p)
if (p && *p)
(**p).state=0; // 置为就绪(可运行)状态TASK_RUNNING.
*p=NULL;
以上是关于linux操作系统进程调度与进程切换的主要内容,如果未能解决你的问题,请参考以下文章
Linux 操作系统原理 — 用户进程与内核线程的调度策略与切换开销