首先,Lenix定义了一个全局进程数组,这个数组就是用来分配进程对象的,也就是PCB
proc_t * proc_current;
static proc_t proc_pool[PROC_MAX];
static proc_t * proc_idle;
proc_current,表示当前进程,用于记录当前在CPU上运行的进程,这样可以简化访问当前进程的操作。
proc_idle,表示系统空转进程
proc_pool,进程池,Lenix就在这里面分配进程对象。
Lenix首先要对进程管理模块进行初始化。
void Proc_initial(void)
{
memset(proc_pool,0,PROC_MAX*sizeof(proc_t));
proc_current = proc_pool;
proc_idle = proc_current;
strcpy(proc_idle->proc_name,"lenix");
proc_idle->proc_pid = 0;
proc_idle->proc_stat = PROC_STAT_RUN;
}
进程管理模块初始化的主要工作有2个,1是将进程池用0进程初始化,第二个是创建系统进程,也就是0号进程。这个0号进程是手工创建的,现在由于进程对象包含的信息不多,初始化所需要做的工作不多,仅仅是设置了进程名,pid和进程状态。
创建进程
proc_t * Proc_create(const char * name,void * entry,void * param,void * stack)
{
int i;
proc_t * proc;
uint_t * sp;
proc = NULL;
for( i = 1 ; i < PROC_MAX ; i++)
{
if( PROC_STAT_FREE == proc_pool[i].proc_stat )
{
proc = proc_pool + i;
proc->proc_stat = -1;
break;
}
}
if( NULL == proc )
{
printf("no proc space\n");
return NULL;
}
/* 设置运行环境 */
sp = stack;
sp--;
*sp-- = 0; /* 进程退出代码 */
*sp-- = (uint_t)param; /* 进程入口参数 */
*sp-- = (uint_t)Proc_exit; /* 进程退出函数入口 */
*sp-- = (uint_t)entry; /* 进程入口 */
*sp-- = 0x200; /* 进程对应的机器状态字 */
*sp-- = 0; /* 以下为寄存器环境 */
*sp-- = 0;
*sp-- = 0;
*sp-- = 0;
*sp-- = 0;
*sp-- = 0;
*sp-- = 0;
*sp = 0;/**/
strcpy(proc->proc_name,name);
proc->proc_stack = sp;
proc->proc_entry = entry;
proc->proc_cpu_time = CPU_TIME;
proc->proc_stat = PROC_STAT_RUN;
return proc;
}
在这个函数中,首先扫描线程池,查看线程池中是否有空闲的进程对象可用,判断的依据是状态是否为PROC_STAT_FREE,进程状态的定义在proc.h中进行定义
#define PROC_STAT_FREE 0
#define PROC_STAT_RUN 1
#define PROC_STAT_WAIT 2
#define PROC_STAT_SLEEP 3
如果没有空闲对象,则返回。用返回NULL表示无可用进程对象可用。
在分配到进程对象后,就对进程运行环境进程初始化,将寄存器初始值、进程入口地址、进程参数、进程退出程序入口以及退出代码。
设置完环境后,记录下栈指针以及进程状态等信息后,到此,创建就完成了。当然,这只是具备了基本功能,还没有处理其他问题,例如没有处理互斥问题,以及生成PID。但是已经可以演示。
注意,这里并没有所谓的加入就绪队列的代码。其实,已经加入,因为进程池就是就绪队列,因为调度程序直接在进程池中查找可以运行的进程。
进程退出
void Proc_exit(int code)
{
proc_current->proc_exit_code = code;
proc_current->proc_stat = PROC_STAT_FREE;
Proc_sched();
}
实现其实很简单,就是将进程状态置为PROC_STAT_FREE即可完成,因为将状态置为PROC_STAT_FREE后,就可以重新进入分配,而且不会参与调度。在改变状态后,立即执行调度,就可以完成进程的退出。
进程切换
void * Proc_switch_prepare(void * sp,proc_t * next)
{
proc_current->proc_stack = sp;
proc_current = next;
//printf("current proc; %s\n",proc_current->proc_name);
return proc_current->proc_stack;
}
void Proc_sched(void)
{
int i;
proc_t * proc;
proc = NULL;
for( i = 1 ; i < PROC_MAX ; i++)
{
if( PROC_STAT_RUN != proc_pool[i].proc_stat || proc_pool[i].proc_alarm )
continue;
if( NULL == proc )
proc = proc_pool + i;
if( proc_pool[i].proc_sched_factor > proc->proc_sched_factor )
proc = proc_pool + i;
}
if( NULL == proc )
proc = proc_idle;
//printf("proc name: %s\n",proc->proc_name);
proc->proc_sched_factor = 0;
proc->proc_cpu_time = CPU_TIME;
if( proc == proc_current )
{
return ;
}
//printf("switch to :%s %p\n",proc->proc_name,proc->proc_stack);
Proc_switch_to(proc);
}
调度程序首先挑选出可以运行的进程,在进程池中扫描,跳过不是运行态的进程,然后挑选出调度因子最大的进程。如果没有运行的进程,则将空转进程作为挑选到的进程。挑选出进程后,就分配CPU时间,并将调度因子置0,这样可以保证下次调度的时候,进程调度因子很小,从而减少得到CPU的机会。分配好CPU时间后,如果挑选出来的进程就是自身,不做切换,如果不是自身,就要进程CPU切换。
Proc_switch_to比较特别,这是一个汇编程序,因为需要直接操作栈环境
; void Proc_switch_to(proc_t * next)
_Proc_switch_to:
pushf
push ax
push cx
push bx
push dx
push sp
push bp
push si
push di
mov bx,sp
mov ax,[bx + 20]
push ax ; 在这里传递参数要注意,
push bx ; 这里是要传递保存环境的栈指针,而不是当前栈指针
call _Proc_switch_prepare
mov sp,ax
pop di
pop si
pop bp
pop ax;
pop dx
pop bx
pop cx
pop ax
popf
ret
这个函数的任务是完成进程切换,具体工作是保存当前CPU寄存器到进程栈中,然后调用Proc_switch_prepare做切换前的准备,主要是维护proc_current,然后Proc_switch_prepare返回新进程的栈指针,这样就可以通过栈恢复进程的寄存器环境。