swap函数
enum BufFlag /* b_flags中标志位 */
{
B_WRITE = 0x1, /* 写操作。将缓存中的信息写到硬盘上去 */
B_READ = 0x2, /* 读操作。从盘读取信息到缓存中 */
B_DONE = 0x4, /* I/O操作结束 */
B_ERROR = 0x8, /* I/O因出错而终止 */
B_BUSY = 0x10, /* 相应缓存正在使用中 */
B_WANTED = 0x20, /* 有进程正在等待使用该buf管理的资源,清B_BUSY标志时,要唤醒这种进程 */
B_ASYNC = 0x40, /* 异步I/O,不需要等待其结束 */
B_DELWRI = 0x80 /* 延迟写,在相应缓存要移做他用时,再将其内容写到相应块设备上 */
};
bool Swap(int blkno, unsigned long addr, int count, enum Buf::BufFlag flag);
/* Swap I/O 用于进程图像在内存和盘交换区之间传输
* blkno: 交换区中盘块号;addr: 进程图像(传送部分)内存起始地址;
* count: 进行传输字节数,byte为单位;传输方向flag: 内存->交换区 or 交换区->内存。 */
bool BufferManager::Swap(int blkno, unsigned long addr, int count, enum Buf::BufFlag flag)
{
User& u = Kernel::Instance().GetUser();
X86Assembly::CLI();
/* swbuf正在被其它进程使用,则睡眠等待 */
while ( this->SwBuf.b_flags & Buf::B_BUSY )
{
this->SwBuf.b_flags |= Buf::B_WANTED;
u.u_procp->Sleep((unsigned long)&SwBuf, ProcessManager::PSWP);
}
this->SwBuf.b_flags = Buf::B_BUSY | flag;
this->SwBuf.b_dev = DeviceManager::ROOTDEV;
this->SwBuf.b_wcount = count;
this->SwBuf.b_blkno = blkno;
/* b_addr指向要传输部分的内存首地址 */
this->SwBuf.b_addr = (unsigned char *)addr;
this->m_DeviceManager->GetBlockDevice(Utility::GetMajor(this->SwBuf.b_dev)).Strategy(&this->SwBuf);
/* 关中断进行B_DONE标志的检查 */
X86Assembly::CLI();
/* 这里Sleep()等同于同步I/O中IOWait()的效果 */
while ( (this->SwBuf.b_flags & Buf::B_DONE) == 0 )
{
u.u_procp->Sleep((unsigned long)&SwBuf, ProcessManager::PSWP);
}
/* 这里Wakeup()等同于Brelse()的效果 */
if ( this->SwBuf.b_flags & Buf::B_WANTED )
{
Kernel::Instance().GetProcessManager().WakeUpAll((unsigned long)&SwBuf);
}
X86Assembly::STI();
this->SwBuf.b_flags &= ~(Buf::B_BUSY | Buf::B_WANTED);
if ( this->SwBuf.b_flags & Buf::B_ERROR )
{
return false;
}
return true;
}
- 一次和磁盘的交互都是512字节,因为一个盘块是512字节,因此count应当小于等于512字节
发生内存交换的情况
当内存空间不足以装下所有就绪进程
/*
* 进程图像内存和交换区之间的传送。如果有进程想要换入内存,而内存
* 中无法找到能够容纳该进程的连续内存区,则依次将低优先权睡眠状态(SWAIT)-->
* 暂停状态(SSTOP)-->高优先权睡眠状态(SSLEEP)-->就绪状态(SRUN)进程换出,
* 直到腾出足够内存空间将想要换入的进程调入内存
*/
void Sched();
void ProcessManager::Sched()
{
Process* pSelected;
User& u = Kernel::Instance().GetUser();
int seconds;
unsigned int size;
unsigned long desAddress;
/*
* 选择在交换区驻留时间最长,处于就绪状态的进程换入
*/
goto loop;
sloop:
this->RunIn++;
u.u_procp->Sleep((unsigned long)&RunIn, ProcessManager::PSWP);
loop:
X86Assembly::CLI();
seconds = -1;
for ( int i = 0; i < ProcessManager::NPROC; i++ )
{
if ( this->process[i].p_stat == Process::SRUN
&& (this->process[i].p_flag & Process::SLOAD) == 0
&& this->process[i].p_time > seconds )
{
pSelected = &(this->process[i]);
seconds = pSelected->p_time;
}
}
/* 如果没有符合条件的进程,0#进程睡眠等待有需要换入的进程 */
if ( -1 == seconds )
{
this->RunOut++;
u.u_procp->Sleep((unsigned long)&RunOut, ProcessManager::PSWP);
goto loop;
}
/* 如果有进程满足条件,需要换入,则检查是否有足够内存 */
X86Assembly::STI();
/* 计算进程换入需要的内存大小 */
size = pSelected->p_size;
/*
* 如果存在共享正文段,但是没有进程图像在内存中,引用该正文段的进程,
* 即共享正文段不再内存中,换入时需要读入正文段在交换区中的副本
*/
if ( pSelected->p_textp != NULL && 0 == pSelected->p_textp->x_ccount )
{
size += pSelected->p_textp->x_size;
}
/* 如果内存分配成功,则进行实际换入操作 */
desAddress = Kernel::Instance().GetUserPageManager().AllocMemory(size);
if ( NULL != desAddress )
{
goto found2;
}
/*
* 分配内存失败情况下,换出内存中进程,腾出空间。
* 换出原则:从易到难;依次将低优先权睡眠状态(SWAIT)-->
* 暂停状态(SSTOP)-->高优先权睡眠状态(SSLEEP)-->就绪状态(SRUN)进程换出。
*/
X86Assembly::CLI();
for ( int i = 0; i < ProcessManager::NPROC; i++ )
{
bool pFlagIsSLOAD = (this->process[i].p_flag & (int(Process::SSYS) | int(Process::SLOCK) | int(Process::SLOAD))) == int(Process::SLOAD);
bool statIsSWAITOrSSTOP = (this->process[i].p_stat == Process::SWAIT || this->process[i].p_stat == Process::SSTOP);
if (pFlagIsSLOAD && statIsSWAITOrSSTOP)
{
goto found1;
}
}
/*
* 在换出高优先权睡眠状态(SSLEEP)、就绪状态(SRUN)进程而腾出内存之前,
* 检查待换入进程在交换区驻留时间是否已达到3秒,低于则不予换入
*/
if ( seconds < 3 )
{
goto sloop;
}
seconds = -1;
for ( int i = 0; i < ProcessManager::NPROC; i++ )
{
bool pFlagIsSLOAD = (this->process[i].p_flag & (int(Process::SSYS) | int(Process::SLOCK) | int(Process::SLOAD))) == int(Process::SLOAD);
bool pStatIsSWAITOrSSTOP = this->process[i].p_stat == Process::SWAIT || this->process[i].p_stat == Process::SSTOP;
if ( pFlagIsSLOAD && pStatIsSWAITOrSSTOP && pSelected->p_time > seconds ) {
pSelected = &(this->process[i]);
seconds = pSelected->p_time;
}
}
/* 如果要换出SSLEEP、SRUN状态进程,先检查该进程驻留内存时间是否超过2秒,否则不予换出 */
if ( seconds < 2 )
{
goto sloop;
}
/* 换出pSelected指向的被选中进程 */
found1:
X86Assembly::STI();
pSelected->p_flag &= ~Process::SLOAD;
this->XSwap(pSelected, true, 0);
/* 腾出内存空间后再次尝试换入进程 */
goto loop;
/* 已经分配好足够的内存,进行实际的换入操作 */
found2:
BufferManager& bufMgr = Kernel::Instance().GetBufferManager();
/*
* 如果存在共享正文段,但是没有进程图像在内存中,引用该正文段的进程,
* 即共享正文段不再内存中,换入时需要读入正文段在交换区中的副本
*/
if ( pSelected->p_textp != NULL )
{
Text* pText = pSelected->p_textp;
if ( pText->x_ccount == 0 )
{
/* 因为共享正文段,和进程ppda、数据段、堆栈段在交换区中是分开存放的,所以先换入共享正文段 */
if ( bufMgr.Swap(pText->x_daddr, desAddress, pText->x_size, Buf::B_READ) == false )
{
goto err;
}
/* 共享正文段在内存中的起始地址 */
pText->x_caddr = desAddress;
desAddress += pText->x_size;
}
pText->x_ccount++;
}
/* 换入剩余部分图像:ppda、数据段、堆栈段 */
if ( bufMgr.Swap(pSelected->p_addr /* blkno */, desAddress, pSelected->p_size, Buf::B_READ) == false )
{
goto err;
}
Kernel::Instance().GetSwapperManager().FreeSwap(pSelected->p_size, pSelected->p_addr /* blkno */);
pSelected->p_addr = desAddress;
pSelected->p_flag |= Process::SLOAD;
pSelected->p_time = 0;
goto loop;
err:
Utility::Panic("Swap Error");
}
- 每次时钟中断,p_time 都会加一,这样sched会按照p_time从小到大的顺序一次把就绪进程移动到磁盘交换区上。
进程图像的换入
- 如果有代码段,判断代码段是否需要进入内存,如果需要进入内存,要调用swap函数,并把x_ccount++
- 随后释放磁盘上进程图像可交换部分所占的空间,把进程图像也搬到内存当中
- 修改换入进程的p_addr p_flag 加上SLOAD 并设置p_time =0
- 结束之后,代码段的x_daddr会保留,x_caddr被正确赋值能够在内存上找到代码段。
0# 进程负责一个死循环去维护上面的进程保证他们能够不停的执行。只要0#进程在台上,就会一直去查找是否有就绪的进程可以换入,如果没有,那么去sleep(RunOut,-100),等随后由于这个原因而唤醒。在setRun函数当中,一个进程发现自己的图像不再内存上,会唤醒0#进程来换入自己的进程。
换入进程发现没有多余的空间
原则:
- 无SSYS 和 SLOCK 标志位
- 先换出低睡进程 SWAIT
- 对于所有的高睡进程,以及就绪进程,把这两类进程中最久没有被调用的进程换出。 SLEEP|SRUN
/*
* 将进程从内存换出至磁盘交换区上
* pProcess: 指向要换出的进程
* bFreeMemory: 是否释放进程图像占据的内存
* size: 除共享正文段外,进程可交换部分图像长度;参数size为0时,直接使用p_size
*/
void XSwap(Process* pProcess, bool bFreeMemory, int size);
void ProcessManager::XSwap( Process* pProcess, bool bFreeMemory, int size )
{
if ( 0 == size)
{
size = pProcess->p_size;
}
/* blkno记录分配到的交换区起始扇区号 */
int blkno = Kernel::Instance().GetSwapperManager().AllocSwap(pProcess->p_size);
if ( 0 == blkno )
{
Utility::Panic("Out of Swapper Space");
}
/* 递减进程图像在内存中,且引用该正文段的进程数 */
if ( pProcess->p_textp != NULL )
{
pProcess->p_textp->XccDec();
}
/* 上锁,防止同一进程图像被重复换出 */
pProcess->p_flag |= Process::SLOCK;
if ( false == Kernel::Instance().GetBufferManager().Swap(blkno, pProcess->p_addr, size, Buf::B_WRITE) )
{
Utility::Panic("Swap I/O Error");
}
if ( bFreeMemory )
{
Kernel::Instance().GetUserPageManager().FreeMemory(size, pProcess->p_addr);
}
/* 把进程图像在交换区起始扇区号记录在p_addr中,SLOAD是0、进程是盘交换区上的进程了 */
pProcess->p_addr = blkno;
pProcess->p_flag &= ~(Process::SLOAD | Process::SLOCK);
/* 最近一次被换入或换出以来,在内出或交换区驻留的时间长度清零 */
pProcess->p_time = 0;
if ( this->RunOut )
{
this->RunOut = 0;
Kernel::Instance().GetProcessManager().WakeUpAll((unsigned long)&RunOut);
}
}
- 减少x_ccount的值
- 上锁防止被重复换出
- 运行到发现没有进程可以调出,sleep(&runin,-100); 其他进程再sleep函数的时候,就要让0#进程试试能不能把这个进程去换出,这个时候要求换出的进程p_time值要大于2,换入的进程p_time值要大于3
内存空间不足以存放新进程
//这段代码来自于Newproc
if ( desAddress == 0 ) /* 内存不够,需要swap */
{
current->p_stat = Process::SIDL;
/* 子进程p_addr指向父进程图像,因为子进程换出至交换区需要以父进程图像为蓝本 */
child->p_addr = current->p_addr;
SaveU(u.u_ssav);
this->XSwap(child, false, 0);
child->p_flag |= Process::SSWAP;
current->p_stat = Process::SRUN;
}
- 父进程执行Xswap,二次保存的ssav的结果,栈顶是newproc指针的结果。于是子进程应该使用ssav的newproc栈帧回到自己的执行状态。
- 子进程有SSWAP标志位,要用ssav如替换rsav把无用的核心栈栈帧刷掉。
内存空间不足以扩展自己的图像
void Process::Expand(unsigned int newSize) { UserPageManager& userPgMgr = Kernel::Instance().GetUserPageManager(); ProcessManager& procMgr = Kernel::Instance().GetProcessManager(); User& u = Kernel::Instance().GetUser(); Process* pProcess = u.u_procp; unsigned int oldSize = pProcess->p_size; p_size = newSize; unsigned long oldAddress = pProcess->p_addr; unsigned long newAddress; /* 如果进程图像缩小,则释放多余的内存 */ if ( oldSize >= newSize ) { userPgMgr.FreeMemory(oldSize - newSize, oldAddress + newSize); return; } /* 进程图像扩大,需要寻找一块大小newSize的连续内存区 */ SaveU(u.u_rsav); newAddress = userPgMgr.AllocMemory(newSize); /* 分配内存失败,将进程暂时换出到交换区上 */ if ( NULL == newAddress ) { SaveU(u.u_ssav); procMgr.XSwap(pProcess, true, oldSize); pProcess->p_flag |= Process::SSWAP; procMgr.Swtch(); /* no return */ } /* 分配内存成功,将进程图像拷贝到新内存区,然后跳转到新内存区继续运行 */ pProcess->p_addr = newAddress; for ( unsigned int i = 0; i < oldSize; i++ ) { Utility::CopySeg(oldAddress + i, newAddress + i); } /* 释放原来占用的内存区 */ userPgMgr.FreeMemory(oldSize, oldAddress); X86Assembly::CLI(); SwtchUStruct(pProcess); RetU(); X86Assembly::STI(); u.u_MemoryDescriptor.MapToPageTable(); }发现图像空间不够的时候,给自己一个SSWAP标志位,把自己的整体和自己的扩展部分当成一个整体放在盘交换区上,像是一个刚刚创建的子进程一样归来。
神秘的0号进程
可以再睡眠中参与swtch函数的调度,梦游说是. 然而,中间这一棒只能由0号进程来完成,只有它能够自然醒。