linux0.11进程调度改进,Linux0.11内核--进程的调度(运行态(就绪态)和睡

当进程等待资源或者事件时,就进入睡眠状态。有两种睡眠态,不可中断睡眠态(

TASK_UNINTERRUPTIBLE

)和可中断睡眠态(

TASK_INTERRUPTIBLE

)。

处于可中断睡眠态的进程不光可以由

wake_up

直接唤醒,还可以由信号唤醒。在

schedule()

函数中,会把处于可中断睡眠态并且收到信号的进程变成运行态,使他参与调度选择。

Linux0.11

中进入可中断睡眠状态的方法有

3

调用

interruptible_sleep_on()

函数

调用

sys_pause()

函数

调用

sys_waitpid()

函数。

第一种情况用于等待外设资源时(如等待

I/O

设备),这时当前进程会挂在对应的等待队列上。第二第三种情况用于事件,即等待信号。

进程要进入不可中断睡眠态,只能通过

sleep_on()

函数。要使处于不可中断睡眠态的进程进入运行态,只能由其他进程调用

wake_up()

将它唤醒。当进程等待系统资源(比如高速缓冲块,文件

i

节点或者文件系统的超级块)时,会调用

sleep_on()

函数,使当前进程挂起在相关资源的等待队列上。

这部分代码很短,一共三个函数

sleep_on()

wake_up()

interruptible_sleep_on()

。在

sched.c

中。但是代码比较难理解,因为构造的等待队列是一个隐式队列,利用进程地址空间的独立性隐式地连接成一个队列。这个想法很奇妙。

sleep_on()

/****************************************************************************/

/* 功能:当前进程进入不可中断睡眠态,挂起在等待队列上*/

/* 参数:p 等待队列头*/

/* 返回:(无)*/

/****************************************************************************/

void sleep_on(struct task_struct **p)

{

struct task_struct *tmp;// tmp用来指向等待队列上的下一个进程

if (!p)// 无效指针,退出

return;

if (current == &(init_task.task))// 进程0不能睡眠

panic("task[0] trying to sleep");

tmp = *p;// 下面两句把当前进程放到等待队列头,等待队列是以堆栈方式

*p = current;//管理的。后到的进程等在前面

current->state = TASK_UNINTERRUPTIBLE;// 进程进入不可中断睡眠状态

schedule();// 进程放弃CPU使用权,重新调度进程

// 当前进程被wake_up()唤醒后,从这里开始运行。

// 既然等待的资源可以用了,就应该唤醒等待队列上的所有进程,让它们再次争夺

// 资源的使用权。这里让队列里的下一个进程也进入运行态。这样当这个进程运行

// 时,它又会唤醒下下个进程。最终唤醒所有进程。

if (tmp)

tmp->state=0;

}

这个函数牵涉到

3

个指针,

p

tmp

current

p

是指向指针的指针,实际上

*p

指向的是等待队列头。系统资源(高速缓冲块,文件

i

节点或者文件系统的超级块)的数据结构中都一个

struct task_struct *

类型的指针,指向的就是等待该资源的进程队列头。比如

i

节点中的

i_wait

,高速缓冲块中的

b_wait

,超级块中的

s_wait

*p

对于等待队列上的所有进程都是一样的。

current

指向的是当前进程指针,是全局变量。

tmp

位于当前进程的地址空间内,是局部变量。不同的进程有不同

tmp

变量。等待队列就是利用这个变量把所有等待同一个资源的进程连接起来。具体的说,所有等待在队列上的进程,都是在

sleep_on()

schedule()

中被切换出去的,这些进程还停留在

sleep_on()

函数中,在函数的堆栈空间里面,存放了局部变量

tmp

假如当前进程要进入某个高速缓冲块的等待队列,而且该等待队列上已经有另外两个进程

task1

task2

先后进入。形成的队列如图。等待队列是堆栈式的,先进入队列的进程排在最后。

ba62cf6cedea23c354c2e8010911b4e6.png

在调用了

sleep_on()

的地方,我们可以发现

sleep_on()

往往是放在一个循环中的(比如

wait_on_buffer()

wait_on_inode()

lock_inode()

lock_super()

wait_on_super()

等函数)。当进程从

sleep_on()

返回时,并不能保证当前进程取得了资源使用权,因为调用

wake_up()

进程切换到从

sleep_on()

中苏醒的过程中,发生了进程调度,中间很可能有别的进程取得了资源。

wake_up()

/****************************************************************************/

/* 功能:唤醒等待队列上的头一个进程*/

/* 参数:p 等待队列头*/

/* 返回:(无)*/

/****************************************************************************/

void wake_up(struct task_struct **p)

{

if (p && *p) {

(**p).state=0;// 把队列上的第一个进程设为运行态

*p=NULL;// 把队列头指针清空,这样失去了都其他等待进程的跟踪。

// 一般情况下这些进程迟早会得到运行。

}

}

下面分析

sleep_on()

wait_up()

配合使用的情况

情况一 游离队列的产生

先分析一下

sleep_on()

wake_up()

在通常情况下的工作原理。考虑一个非常简单的情况,假设目前系统只有

3

个进程,且都等在队列上,队列的头指针设为

wait

075c8fba6da92f1eb1badc07840c300f.png

然后系统资源得到释放,当前进程调用

wake_up(wait)

。这时

Task C

变成了运行态。

d7ab4db8e7b4fd8d50188ac8a28c11c6.png

之后进程调度发生,

Task C

被选中,开始运行。

Task C

是从

sheep_on()

中的

schedule()

的后一条语句开始运行,它把

Task B

的状态变成运行态。随后

Task C

退出

sheep_on()

函数,堆栈中的局部变量

tmp

消失,这样再没有指向

Task B

的指针,

Task B

开头的队列游离了。

18aa97102d9eadc99bdc5d30d1e100c4.png

情况

1-1

这时对同一个资源有两个进程是可运行状态,但是当前进程是

Task C

,只要它不调用

schedule

,它是不会被抢断的。因此

Task C

继续运行,取得了它想要的资源,这时

Task C

可以完成它的任务了。当进程调度再次发生时,

Task B

会被选中,同样,

Task B

会把

Task A

变成可运行态,而它自己得到了资源。最终

Task A

也会得到执行。这样,等待在一个资源上的三个任务最终都得到运行。

情况

1-2

假设

Task C

在得到资源后,又主动调用了

schedule()

,进程调度程序这时选中了

Task B

Task B

从上次中断的地方开始运行,即从

sleep_on()

schedule()

后面的语句开始运行。它会把

Task A

也变成可运行状态。然后退出

sleep_on()

tmp

变量消失。但是不幸的是它发现资源仍然被占用,所以再次进入睡眠,又连接到

wait

队列上了。

3facf29e14a1f96381c2c2c95cf7082d.png

从这个情况可以看到,虽然系统运行过程中,可能会把等待队列切分成很多游离队列,但是这些队列头上的进程都是运行态,这保证

schedule()

函数最终还是会找到它。

情况二 游离队列的合并

假设目前进程等待资源的情况如下,某个进程占用资源不放,导致有

7

个进程等待该资源。产生

3

个队列,其中两个游离。

c4f3bcebf51747ee873fcf4c217f1b2a.png

这时调度函数选中

Task E

执行,

Task E

先唤醒

Task D

但发现资源不能用,再次睡眠,把自己移到

wait

队列,脱离了游离队列。调度再次发生。

011bb4be8d63de04767ee39437be04f2.png

假如这时

Task B

得到运行,同样

Task B

也只能唤醒

Task A

,而把自己移动到等待队列

927aaf158d03988e8a39505f07f7a069.png

p { margin-bottom: 0.08in; }

这样,只要游离队列头上的进程是运行态,游离队列可以再次合并到原先的等待队列上。

p { margin-bottom: 0.08in; }

interruptible_sleep_on()

/****************************************************************************/

/* 功能:当前进程进入可中断睡眠态,挂起在等待队列上*/

/* 参数:p 等待队列头*/

/* 返回:(无)*/

/****************************************************************************/

void interruptible_sleep_on(struct task_struct **p)

{

struct task_struct *tmp;// tmp用来指向等待队列上的下一个进程

if (!p)// 无效指针,退出

return;

if (current == &(init_task.task))// 进程0不能睡眠

panic("task[0] trying to sleep");

tmp=*p;// 和sleep_on()一样,构建隐式队列

*p=current;

repeat:current->state = TASK_INTERRUPTIBLE;// 当前进程状态变成可中断睡眠态

schedule();// 重新调度进程

// 当进程苏醒后,从这里继续运行

if (*p && *p != current) {// 如果当前进程之前还有进程,这把头进程唤醒,

(**p).state=0;// 自己进入睡眠态。这样做为了保证队列栈式管理

goto repeat;

}

*p=NULL;// 和wake_up()一样

if (tmp)// 产生了游离队列,需要把头进程唤醒

tmp->state=0;

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值