by cszhao1980
最后,看一下我们的老朋友sched(),上次看到它还是在系统初启时,#0进程在
sched()函数中调用sleep(&runout ,…)睡眠,从而让出cpu,切换至#1进程。
sched()函数是个黑洞,它内部是个死循环,永远也不会退出(除非出错)。也
就是说#0进程将陷入在sched()中,而sched()用来进行调度,自此#0进程就蜕变
为调度进程。事实上, sched()函数也是#0进程的专属函数——只有#0进程才会调用它。
首先看看sched()的入口——这里的入口,不仅指#0进程第一次调用sched()时的入口,
也同时指,当#0进程醒来后,要执行的语句。显然,无论是哪种情况,都会首先到“Loop”处:
1957: loop:
1958: spl6();
1960: for(rp = &proc[0]; rp < &proc[NPROC]; rp++)
1961: if(rp->p_stat==SRUN && (rp->p_flag&SLOAD)==0 &&
1962: rp->p_time > n) {
1963: p1 = rp;
1964: n = rp->p_time;
1965: }
1966: if(n == -1) {
1967: runout++;
1968: sleep(&runout, PSWP);
1969: goto loop;
1970: }
sched()会Loop整个进程表,看是否有进程处于SRUN状态(运行中),但还没有
Load进core map的——没有的话,它就无事可做,于是设置“无事可做”标记(runout),
然后依此为“原因”睡眠。顺便说一下,当其他进程的操作有可能影响到 1961 ~ 1962的
检查结果时,就会调用wakeup函数唤醒#0进程,如setrun()、xswap()函数。
如果有符合条件的进程,会选取换出时间(p_time)最长的那个进程,然后,争取在core中为其分配足够的内存:
1976: spl0();
1977: rp = p1;
1978: a = rp->p_size;
1979: if((rp=rp->p_textp) != NULL)
1980: if(rp->x_ccount == 0)
1981: a =+ rp->x_size;
1982: if((a=malloc(coremap, a)) != NULL)
1983: goto found2;
需要注意的是,如果此时text segment的active进程计数为0,则表示该text segment没有被
load进core空间,因此,在分配空间时会加上text segment的大小。如果成功的分配了内存,
则跳到found2:
2031: found2:
2032: if((rp=p1->p_textp) != NULL) {
2033: if(rp->x_ccount == 0) {
2034: if(swap(rp->x_daddr, a, rp->x_size, B_READ))
2035: goto swaper;
2036: rp->x_caddr = a;
2037: a =+ rp->x_size;
2038: }
2039: rp->x_ccount++;
2040: }
当text segment的“活动进程计数”为0时,则调用swap()函数将text segment从swap file中
load入内存——如果您足够细心的话,您应该还记得此函数也用于将内存image swap out。
注意第2036行,内存地址a向后偏移,重新指向了空闲区域——这个区域将
用来容纳进程的“私有空间(swappable image)”。
下面的语句将进程的“swappable image”Load如core空间,然后跳回loop。
2041: rp = p1;
2042: if(swap(rp->p_addr, a, rp->p_size, B_READ))
2043: goto swaper;
2044: mfree(swapmap, (rp->p_size+7)/8, rp->p_addr);
2045: rp->p_addr = a;
2046: rp->p_flag =| SLOAD;
2047: rp->p_time = 0;
2048: goto loop;
2049:
2050: swaper:
2051: panic("swap error");
注意第2044行——仅从swap空间中free了进程的“swappable image”,而text segment是
常驻在swap文件中的,除非进程exit()。另外还需注意进程表项里SLOAD标志的设置。
现实往往没有这么美好,在1982行分配内存时,有可能会失败,如果失败该如何呢?
总的说来,sched()不会坐以待毙,而是会尽力挑一个“软柿子”进程换出core空间,然
后跳回Loop,重新执行。“软柿子”进程的寻找大致分为两步,
(1) 首先查看是否有暂时无法运行的进程(如睡眠、Stop等),优先将其换出内存;
(2) 如果要换入的进程在磁盘呆了足够久,还会进一步的检查,以看是否可以换出其他进程。
莱昂给出了比较详细的介绍,我就不再赘述了。
还需要特别留意一下runin——当无法换入进程时,0号进程会sleep(&runin)。
最后,让我们看一下何时sche()会被执行。我们前面已经说过,#0进程陷入sched()后,有两种情况:
(1) 无事可做时,sleep(&runout)休眠;
(2) 无法换入时,sleep(&runin)休眠。
而相应的唤醒函数如下:
(1)wakeup(&runout) ---- setrun() ---- wakeup()
---- xswap() ---- …
(2)wakeup(&runin) ---- sleep()
--- clock()
简单的说:
(1) 当有进程睡眠、被唤醒时,有机会进行进程调度;
(2) 时间片处理时,也有机会进行进程调度。
#0进程重生了,它的存在使kernel态代码中的“竞争”情形大大增加,而进程定义的许多
种flag,如SLOCK、SLOAD等也都与#0进程有关,您不妨总结一下。
博客地址:http://blog.csdn.net/cszhao1980
博客专栏地址:http://blog.csdn.net/column/details/lions-unix.html