很多系统调用都需要操纵进程的virtual地址空间,如fork(),exec(),brk(),exit()等。fork()创建子进程,因此需要将父进程的地址空间进行拷贝;exec()需要做的事情最多……“
UNIX内核(12):进程的结构——上下文”中提到了区(region)的概念。实际上,内核是通过对region表项(以下简称region)的操作来实现对进程virtual地址空间的操纵。下面就讲讲region的结构和操作。
Region的结构
1、指向要加载文件的inode的指针;
2、类型,如text、data之类;
3、大小;
4、该region在物理内存中的位置(通过一些辅助的地址映射表来使之与virtual地址关联);
5、状态:可用、已加载、请求、正在加载;
6、引用计数;
对于region的操作有加/解锁、分配、附加region到进程的内存空间、改变大小、从一个文件加载region到进程的内存空间、释放、从进程的内存空间分离region、复制。下面就逐一讨论,在理解时,要时刻记住“
UNIX内核(12):进程的结构——上下文”中的模型,以及与inode的类比。
分配
region存放内核的region表中,在由内核中有空闲/活动region链表,这些链表包含指向region表中项目的指针。为了分配region,首先从free链表头取出一个region置入活动链表中并锁住。由于大部分进程都与可执行文件相关联,因此,设置inode指针指向可执行文件。inode用来标识region,这样其他进程就可以共享该region。此时需要增加inode引用计数是得可执行文件不会被意外删除。
Region * AllocateRegion(INode * pINode, RegionType aType)
{
Region * pReg = pFreeRegionList->GetRegion();
pReg->type = aType;
pReg->inode = pINode;
if (pINode == NULL)
{
pINode->refCount++;
}
pActiveRegionList->PutRegion(pReg);
return pReg->Lock();
}
附加region到进程
首先在pregion中找到一个空闲slot,将region指针置入其中,并记录下该region在进程中的virtual地址。注意新的region的virtual地址不能与已有的regionvirtual地址重合,也不能超过virtual地址上限。
PRegionEntry * AttachRegion(Region * pLockedReg, Process * pTargetProc, VirtualAddr * startingAddr, RegionType aType)
{
PRegionEntry * pre = pPRegion->GetFreeEntry();
pre->pRegion = pLockedReg;
pre->type = aType;
pre->virtualAddr = startingAddr;
assert((startingAddr + pLockedReg->size) < limit);
pre->pRegion->refCount++;
pTargetProc->size += pLockedReg->size;
初始化寄存器三元组; // 如果该region已经附加到另一个进程,则使用已有的页表,否则使用新的页表。
return pre;
}
改变大小
进程可以修改virtual地址空间的大小,比如stack。为了修改大小,最终是由改变region大小来实现。但对于共享的region,是不可能修改大小的。另外就是要注意进程大小限制及地址空间覆盖问题。改变大小之后,内核将分配页表来适应更大的region并分配物理内存(当然,这是针对不支持请求式内存管理的平台)。如果需要分配物理内存,则需要保证在改变大小前有足够的可用内存……再说下去就是内存管理的内容了,日后再议。
void GrowRegion(PRegion * pre, Size * pSize)
{
if (pSize->Positive())
{
检查大小是否合法;
分配内存管理页表;
if (!SupportDemandPaging())
{
分配物理内存;
创建辅助表;
}
}
else // 减小region大小
{
释放相应的物理内存;
释放相应的辅助表;
}
根据需要初始化辅助表;
修改进程的大小;
}
加载
void LoadRegion(PRegion * pre, VirtualAddr startingAddr, INode * pFileForLoad, Offset * offsetInFileOfStartOfRegion, Size * amountOfData)
{
GrowRegion(pre, size);
pre->pRegion->state = BeingLoaded;
Unlock(pre->pRegion);
为读取文件设置u area参数:目的虚地址、从offsetInFileOfStartOfRegion处开始、要读取的数量;
将文件读到region(内部的read()函数);
Lock(pre->pRegion);
pre->pRegion->state = LoadComplete;
唤醒所有等待加载region的进程;
}
释放
void FreeRegion(Region * pLockedReg)
{
if (pRegion->refCount != 0)
{
Unlock(pRegion);
if (pRegion->inode != null)
{
Unlock(pRegion->inode);
}
return;
}
if (pRegion->inode != null)
{
iput(pRegion->inode);
}
释放物理内存;
释放辅助表;
清除pRegion的各字段;
pFreeRegionList->PutRegion(pRegion);
Unlock(pRegion);
}
分离(detach)
void DetachRegion(PRegion * pre)
{
释放相应的辅助的内存管理表;
减小进程大小;
减少Region的引用计数;
if(Region的引用计数为0且没有设置sticky)
{
FreeRegion(pre->pRegion);
}
else
{
解锁inode;
解锁region;
}
}
复制
Region * DupRegion(PRegion * pre)
{
if (region是共享的)
{
pre->pRegion->refCount++;
return pre->pRegion;
}
Region * pReg = AllocateRegion(pre->pRegion->inode, pre->pRegion->type);
根据当前的Region创建内存管理的辅助结构;
分配物理内存; // 注:copy-on-write技术并不要求此时就分配物理内存
将pre->pRegion的物理内存中的内容考到新的Region中;
return pReg;
}
PS:写得有点简单:-)
参考:
The Design of The UNIX Operation System, by Maurice J. Bach
Understanding The Linux Kernel, 3rd edition, by Daniel P. Bovet, Marco Cesati
Copyleft (C) 2007, 2008 raof01. 本文可以用于除商业用途外的所有用途。若要用于商业用途,请与作者联系。
阅读(2851) | 评论(1) | 转发(0) |