进程管理
1.设计
在KePOS系统中,涉及到多进程场景,设计进程的初始化,进程的切换,进程的销毁;并且设计了一个可重入自旋锁来实现进程的同步和互斥。
上图代表着每一个用户进程的线性地址空间,高地址为该进程的内核线性地址空间,低地址为该进程的用户线性地址空间。在每一个用户进程的页表中,都保存有完整的内核线性地址映射,和已经分配的用户线性地址映射。地址0x7fffffffffff为该进程的栈基址(栈向低地址扩展)。系统调用时,仍然使用用户空间的栈,这样设计的原因是:我将系统调用也当作该进程发生的一种普通函数调用,只不过处理的代码发生在该进程的内核空间。
2.相关结构体
1.TaskManage
TaskManage结构体只有一个全局变量taskManage,用来管理所有的进程。该结构体管理3个进程链表(readys,running,waits),readys保存所有就绪态的进程,running指向当前运行中的进程,waits保存着所有的将要被销毁的进程,count用来分配新建的进程id。
struct TaskManage{
struct Task * readys;
struct Task * running;
struct Task * waits;
unsigned long count;
};
2.Task
Task结构体代表一个进程,cr3表示该进程的第一级页表的位置,pages表示该进程分配到的页面,structPage表示一个用户进程的栈所在的页面号,该属性对于内核进程无效,内核进程设计为直接将一块物理内存独占作为该内核进程的栈,所以不需要页面切换。state表示进程的状态,path为该进程在磁盘上的存输位置,window为该进程的窗口(如果进程没有窗口则为null),registers为该进程的寄存器值,用于进程切换时保护和恢复上下文。next和prev用于链接进程,使得进程保存在taskManage的链表中。
struct Task{
unsigned long id;
unsigned long cr3;
struct PageBuffer pages;
unsigned long structPage;
unsigned long state;
unsigned long path;
struct Window * window;
struct Register registers;
struct Task * next;
struct Task * prev;
}
3.Register
该结构体表示在进程切换时,要保存的寄存器;在把A进程切换为B进程时,会把cpu寄存器的当前值保存到A进程的register结构体中,然后将进程B的register结构体中的值,恢复到cpu寄存器,这2步通过时钟中断来完成。
struct Register{
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long r11;
unsigned long r10;
unsigned long r9;
unsigned long r8;
unsigned long rbx;
unsigned long rcx;
unsigned long rdx;
unsigned long rsi;
unsigned long rdi;
unsigned long rbp;
unsigned long ds;
unsigned long es;
unsigned long rax;
unsigned long rip;
unsigned long cs;
unsigned long rflagc;
unsigned long rsp;
unsigned long ss;
};
4.ReeTrantLock
在多进程环境中,需要有锁来实现进程间的同步和互斥。ReeTrantLock结构体为一个可重入的自旋锁,count表示锁状态,如果count>0表示有进程已经加锁,nowTask指向当前持有锁的进程,方便该进程可以重新获取锁。
struct ReeTrantLock{
volatile unsigned long count;
volatile struct Task * nowTask;
}
3.实现
1.用户进程初始化
//传入任务的第一个代码的线性地址
void initUserTask(void * userTask){
//初始化一个task节点,用于保存进程的相关信息
struct Task * newTask = (struct Task *)getMemoryBlock(sizeof(struct Task));
initTaskNode(newTask,64);
//分配新的页基地址
unsigned long newCR3 = (struct Task *)getMemoryBlock(4096);
newTask->cr3 = KernelVirAddressToPhyAddress(newCR3);
//分配2页,并获取其物理地址
unsigned long newPageID1 = findFreeBlockInBitmap(memory.zone[RAM].bitmap, memory.zone[RAM].length, 1);
modifyBitmap(memory.zone[RAM].bitmap, memory.zone[RAM].length, newPageID1);
unsigned long phyAddress1 = getPageByPageID(newPageID1)->physicsAddress;
set((unsigned int *)KernalPhyAddressToVirAddress(phyAddress1),(char)0x00,PAGE_2M_SIZE);
setPageAttribute(getPageByPageID(newPageID1),1,USER);
unsigned long newPageID2 = findFreeBlockInBitmap(memory.zone[RAM].bitmap, memory.zone[RAM].length, 1);
modifyBitmap(memory.zone[RAM].bitmap, memory.zone[RAM].length, newPageID2);
unsigned long phyAddress2 = getPageByPageID(newPageID2)->physicsAddress;
set((unsigned int *)KernalPhyAddressToVirAddress(phyAddress2),(char)0x00,PAGE_2M_SIZE);
setPageAttribute(getPageByPageID(newPageID2),1,USER);
//将新的页插入buffer。
insertPagesBuffer(&newTask->pages,newPageID1);
newTask->structPage = newPageID2;
//设置页映射,首先分配2页,第一个页映射为线性地址0x0,第2个页映射为线性地址0x00007fffffe00000
unsigned long rspVirAdd = 0x00007fffffffffff;
unsigned long ripVirAdd = 0x00007fffffe00000;
virPageTOPhyPage(ripVirAdd,phyAddress2,newTask->cr3,7);
virPageTOPhyPage(0x0000000000000000,phyAddress1,newTask->cr3,7);
//copy进程代码到进程的0x0线性地址,因为用户程序代码被loader保存在物理内存中
copy((unsigned long)userTask,KernalPhyAddressToVirAddress(phyAddress1),200000);
//设置该进程的初始化寄存器
newTask->registers.cs = 0x23;
newTask->registers.rip = 0x0000000000000000; //rip初始为线性地址空间0地址
newTask->registers.ss = 0x1b;
newTask->registers.ds = 0x1b;
newTask->registers.es = 0x1b;
newTask->registers.rsp = rspVirAdd; //rsp初始为0x00007fffffffffff
newTask->path = NULL;
newTask->registers.rbp = newTask->registers.rsp;
newTask->registers.rflagc |= (1UL << 9);
newTask->id = taskManage.count;
taskManage.count += 1;
//初始化后,将该进程插入到taskManage的readys链表中
insertTaskBehand(taskManage.readys->prev,newTask);
newTask->state = READY;
newTask->window = NULL;
//初始化该进程的内核态页表-设置该进程的内核地址空间映射
initKernelPage(newTask->cr3);
}
2.进程切换
通过中断/异常管理,知道在中断时,系统会自动保存rsp,rip,cs,ss寄存器,在中断处理函数入口处也会保存通用寄存器值,在中断返回出口时,首先会恢复通用寄存器值,最后iretq会弹出rsp,rip,cs,ss,然后会跳转到目标程序执行。利用该特征,通过时钟中断完成进程切换,每一个时间片为10ms。saveTaskRegisterTogStick(struct Task * task,unsigned long rsp)函数用于将中断入口压倒栈中的通用寄存器值保存到当前进程的task结构体的registers中。backTaskRegisterTogStick(nowTask,rsp)函数用于将当前进程task结构体registers中保存的值放到中断栈中。通过时钟中断返回时,恢复现场就可以跳转到新进程执行,切换后同时也要完成页表的切换。
//时钟中断处理函数
void timeHandle(unsigned long rsp){
//.......
//一个时间片10ms,进行一次任务切换
changeTask(rsp);
//.......
}
//任务切换,输入当前栈指针
void changeTask(unsigned long rsp){
//如果没有就绪的任务,直接返回
if((taskManage.readys->prev) == &NullTaskReady){
return;
}
//如果有就绪,但是没有运行中的任务,则直接切换。
if((taskManage.running->prev) == &NullTaskRunning){
struct Task * nowTask = taskManage.readys->next;
deleteTargetTask(nowTask);
insertTaskBehand(taskManage.running->prev,nowTask);
nowTask->state = RUNNING;
backTaskRegisterTogStick(nowTask,rsp);
//设置CR3为新进程的CR3,完成页表切换
modifyCR3(nowTask->cr3);
}else{
//两个队列都不为空,把就绪队列的第一个加入running队列,把running的第一个移到就绪队列。
struct Task * nowReadTask = taskManage.readys->next;
struct Task * nowRunningTask = taskManage.running->next;
deleteTargetTask(nowReadTask);
deleteTargetTask(nowRunningTask);
insertTaskBehand(taskManage.readys->prev,nowRunningTask);
insertTaskBehand(taskManage.running->prev,nowReadTask);
nowRunningTask->state = READY;
nowReadTask->state = RUNNING;
saveTaskRegisterTogStick(nowRunningTask,rsp);
backTaskRegisterTogStick(nowReadTask,rsp);
modifyCR3(nowReadTask->cr3);
}
}
3.进程退出
首先内核进程初始化后,不会退出,所以只考虑用户进程的退出。
1.用户进程的执行流程:
下面这个_start在链接阶段,会被链接到0x0线性地址处,通过上面的说明,0x0线性地址是用户进程的第一条指令的地址。该程序功能时跳转到starttask函数执行。
_start:
leaq starttask(%rip) ,%rax
callq *%rax
nop
反汇编代码
0000000000000000 <_start>:
;线性地址 机器码 汇编代码
0: 48 8d 05 e6 01 00 00 lea 0x1e6(%rip),%rax # 1ed <starttask>
7: ff d0 callq *%rax
9: 90 nop
starttask函数的功能为跳转到main函数执行,并且在main执行完后,会执行一个exit系统调用退出。
void starttask(){
__asm__ __volatile__ (
"leaq main(%%rip) , %%rax \n\t"
"callq *%%rax \n\t"
:
:
:"memory"
);
//执行结束退出
exit();
}
exit系统调用的最终处理函数如下:
该函数将当前进程插入到taskManage的waits链表,然后执行死循环等待进程切换,因此进程切换只在readys链表和running之间,所以该进程被切换后将不会在执行。内核中有一个清理进程,会监控waits链表,然后逐个清理waits链表里的进程。
//任务运行结束,任务退出
unsigned long sys_taskExit(){
//将当前进程插入到等待队列,等待删除
reetrantlock();
struct Task * nowRunningTask = taskManage.running->next;
//关中断,原子操作
deleteTargetTask(nowRunningTask);
insertTaskBehand(taskManage.waits->prev,nowRunningTask);
//开中断,原子操作完成
reetrantUnLock();
//等待时钟中断切换到下一个任务
while(True);
}
2.清理
//该进程负责清除wait队列中任务
void cleanTask(){
while (True){
while(taskManage.waits->prev != &NullTaskWait){
exitNowtask(taskManage.waits->prev);
}
}
}
//清理进程
void exitNowtask(struct Task * nowRunningTask){
reetrantlock();
deleteTargetTask(nowRunningTask);
//归还当前task获得的所有页面
unsigned long pageId = nowRunningTask->structPage;
modifyBitmap(memory.zone[RAM].bitmap, memory.zone[RAM].length, pageId);
setPageAttribute(getPageByPageID(pageId),1,0);
while(!isPagesEmpty(&nowRunningTask->pages)){
pageId = deleteAndreturnPage(&nowRunningTask->pages);
modifyBitmap(memory.zone[RAM].bitmap, memory.zone[RAM].length, pageId);
setPageAttribute(getPageByPageID(pageId),1,0);
}
//归还CR3占用的空间
backCR3page(nowRunningTask->cr3);
//归还pagsbuffer
backPagesBuffer(&nowRunningTask->pages);
//归还该task结构体占用空间
backMemoryBlock(nowRunningTask);
//归还窗口占用的空间
if(nowRunningTask->window->windata != NULL) deleteTaskWindow(nowRunningTask->window);
reetrantUnLock();
}
4.可重入锁实现
因为进程只在中断时,才会切换;所以可以利用关中断来完成原子操作。
1.加锁
//加锁
void reetrantlock(){
//原子操作,关中断
cli();
//如果获得锁的进程不是当前进程,则判断锁是否可以被当前进程获取
struct Task * noTask = taskManage.running->prev;
int i;
if(reeTrantLock.nowTask != noTask){
//当前程序获取到锁
if(reeTrantLock.count == 0){
reeTrantLock.count ++;
reeTrantLock.nowTask = noTask;
}else{
//当前线程获取锁失败,自旋。
while(reeTrantLock.count != 0){
sti();
//等待一段时间在获取锁
for(i = 0;i<1000;i++);
cli();
}
//获得到锁
reeTrantLock.count ++;
reeTrantLock.nowTask = noTask;
}
}else{
reeTrantLock.count ++;
}
//开中断
sti();
}
2.解锁
//解锁
void reetrantUnLock(){
//原子操作
cli();
//给当前锁的引用计数减去1
reeTrantLock.count --;
//如果当前锁的计数为0,该进程释放持有锁
if(reeTrantLock.count == 0){
reeTrantLock.nowTask = NULL;
}
sti();
}
4.详细代码
KeeProMise/KePOS: Design and implement your own operating system (github.com)