(莱昂氏unix源代码分析导读-29) swap in/out (上)

(莱昂氏unix源代码分析导读-29) swap in/out (下)

                                                                              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状态(运行中),但还没有

Loadcore 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 segmentactive进程计数为0,则表示该text segment没有被

loadcore空间,因此,在分配空间时会加上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 segmentswap file

load入内存——如果您足够细心的话,您应该还记得此函数也用于将内存image swap out。

 

注意第2036行,内存地址a向后偏移,重新指向了空闲区域——这个区域将

用来容纳进程的“私有空间(swappable image)”。

 

下面的语句将进程的“swappable imageLoadcore空间,然后跳回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)休眠。

 

而相应的唤醒函数如下:

 

1wakeup(&runout)   ----  setrun() ---- wakeup()

                      ----  xswap() ---- …

 

2wakeup(&runin)   ----  sleep()

                      ---  clock()

 

简单的说:

(1)         当有进程睡眠、被唤醒时,有机会进行进程调度;

(2)         时间片处理时,也有机会进行进程调度。

 

#0进程重生了,它的存在使kernel态代码中的“竞争”情形大大增加,而进程定义的许多

flag,如SLOCKSLOAD等也都与#0进程有关,您不妨总结一下。

 

 

博客地址:http://blog.csdn.net/cszhao1980

博客专栏地址:http://blog.csdn.net/column/details/lions-unix.html