以下是个人总结的实验过程及经验分享。
Tips:
• 优先级
–静态优先级(nice):内核不会修改它,不随时间而变化,除非用户通过系统调用setpriority进行修改
–动态优先级(priority):内核根据线程使用CPU的状况、静态优先级nice和系统负荷计算出来,会随时间而变化
最终的调度依据,即调度器只根据动态优先级进行调度
• 在系统启动过程中,g_task_running是NULL!
• task0(tid=0)是系统空闲线程(systemidle thread)
Step1: 添加变量及其初始化
在kernel.h的struct tcb中增加nice(表示线程的静态优先级,ps:一定要加在kstack字段之后、signature字段之前!!!),priority(表示线程的动态优先级,值越大优先级越高),estcpu(表示线程最近使用了多少CPU时间),如下:
int nice; //线程的静态优先级nice,值越小优先级越高
int priority; //表示线程的动态优先级,值越大优先级越高
fixedpt estcpu; //表示线程最近使用了多少CPU时
在task.c的sys_task_create函数中初始化上面三个变量,注意取值范围。nice取值范围[-NZERO,NZERO-1],值越小优先级越高,priority取值范围[PRI_USER_MIN, PRI_USER_MAX],值越大优先级越高。这里都用了宏定义,方便理解和修改。
#define NZERO20
#definePRI_USER_MIN 0
#definePRI_USER_MAX 127
new->nice = 0; //初始化nice=0
new->estcpu=0; //初始化estcpu=0
new->priority=0; //初始化priority=0
在timer.c中增加一个全局变量fixedpt g_load_avg = 0(表示系统的平均负荷,初值为0)
Step2: 增加系统调用
在mechdep.c中增加两个系统调用函数,getpriority(int tid)用于返回线程tid的(nice+NZERO),而setpriority(int tid, int prio)用于把线程tid的nice设为(prio-NZERO)。具体实现如下:
PS: •如果参数tid=0,表示获取/设置当前线程的nice值,而不是task0!
•函数“struct tcb *get_task(int tid)”用于根据tid获取struct tcb的指针。调用时一定要用save_flags_cli/restore_flags保护起来
int sys_getpriority(int tid)
{
//tid=0,表示获取/设置当前线程的nice值,而不是task0
if(tid==0) return g_task_running->nice+NZERO;
uint32_t flags;
struct tcb *tsk;
save_flags_cli(flags);
tsk = get_task(tid);
restore_flags(flags);
return tsk->nice+NZERO;//获取当前线程的nice值
}
/*把线程tid的nice设为(prio-NZERO)prio必须在[0,2*NZERO-1]内成功返回0,失败返回-1*/int sys_setpriority(int tid, int prio){ uint32_t flags; struct tcb *tsk;
if(tid==0) { //save_flags_cli(flags); g_task_running->nice=prio-NZERO;//把线程tid的nice设为(prio-NZERO) //restore_flags(flags); return 0; } /*prio必须在[0,2*NZERO-1]内*/ if(prio<0) prio=0; if(prio>2*NZERO) prio=2*NZERO; /*用save_flags_cli/restore_flags保护起来*/ save_flags_cli(flags); tsk = get_task(tid); restore_flags(flags); //if(tsk==NULL) return -1;//设置失败返回-1 tsk->nice=prio-NZERO; return 0;}
Step3: 实现优先级调度算法
先计算各个线程的动态优先级priority(task0除外!),然后进行调度。
在task.c的函数schedule中实现,如下:
/*********动态优先级调度*************/
void schedule()
{
struct tcb *select = g_task_head;
struct tcb *select_plus = g_task_running;
while(select!=NULL){//遍历链表,计算所有线程的priority的值
select->priority = PRI_USER_MAX -
fixedpt_toint(fixedpt_div(select->estcpu, fixedpt_fromint(4))) -
select->nice*2;
select = select->next;
}
select = g_task_head;
while(select!=NULL){
if((select->tid != 0) &&(select->state == TASK_STATE_READY)){
if(select->priority > select_plus->priority||select_plus->tid==0)
select_plus=select;//选择等待队列中优先级最高的线程
}
select = select->next;
}
if(select_plus == g_task_running) {
if(select_plus->state == TASK_STATE_READY)
return;
select_plus = task0;
}
//printk("0x%d -> 0x%d\r\n", (g_task_running == NULL) ? -1 : g_task_running->tid, select->tid);
if(select->signature != TASK_SIGNATURE)
printk("warning: kernel stack of task #%d overflow!!!", select->tid);
g_resched = 0;
switch_to(select_plus);
}
注:
•线程0(task0,它的ID=0)是一个特殊的线程,仅当没有其他可运行的线程时,才能调度task0运行!
•函数schedule被执行时,CPU的中断已经被关闭。
Step4: 属性计算
在timer.c的定时器中断处理程序isr_timer()中增加属性计算具体如下:
void isr_timer
/** * 定时器的中断处理程序 */
void isr_timer(uint32_t irq, struct context *ctx)
{
g_timer_ticks++;
//sys_putchar('.');
//if(g_task_running == NULL) g_task_running=g_task_head;
if(g_task_running != NULL) {
//如果是task0在运行,则强制调度
if(g_task_running->tid == 0) {
g_resched = 1;
} else {
/*每次定时器中断:g_task_running->estcpu++,task0除外!*/
g_task_running->estcpu = fixedpt_add(g_task_running->estcpu, FIXEDPT_ONE);
/*每隔一秒计算一次*/
if(g_timer_ticks % HZ==0){
int nice;
int nready = 0;//表示处于就绪状态的线程个数,task0除外!
fixedpt ratio;
struct tcb *select=g_task_head;
while(select!=NULL){
//if(select==NULL) break;
if(select->state==TASK_STATE_READY) nready++;
nice=select->nice;
ratio=fixedpt_mul(FIXEDPT_TWO,g_load_avg);//每秒为所有线程更新一次
ratio=fixedpt_div(ratio,fixedpt_add(ratio,FIXEDPT_ONE));
select->estcpu=fixedpt_add(fixedpt_mul(ratio,select->estcpu),fixedpt_fromint(nice));
select=select->next;
}
/*g_load_avg=(59/60) ×g_load_avg+(1/60) × nready,
计算系统的平均负荷g_load_avg*/
fixedpt r59_60 = fixedpt_div(fixedpt_fromint(59), fixedpt_fromint(60));
fixedpt r01_60 = fixedpt_div(FIXEDPT_ONE, fixedpt_fromint(60));
g_load_avg = fixedpt_add(fixedpt_mul(r59_60, g_load_avg),
fixedpt_mul(r01_60, fixedpt_fromint(nready)));
}
//把当前线程的时间片减一
--g_task_running->timeslice;
//如果当前线程用完了时间片,也要强制调度
if(g_task_running->timeslice <= 0) {
g_resched = 1;
g_task_running->timeslice = TASK_TIMESLICE_DEFAULT;
}
}
}
}
Step5: 测试调度器
创建两个冒泡排序的线程,用于对比优先级效果。另外创建一个控制线程,实现循环等待键盘输入,通过上下左右键来实时控制两个线程的优先级。控制实现如下:
void tsk_foo3(void *pv){
int i;
int prio1= getpriority(tid1);//获取线程优先级
int prio2= getpriority(tid2);
for(i=0;i<=prio1;i++) //画出nice的实体化
line(g_graphic_dev.XResolution/2-i*emp2-1,g_graphic_dev.YResolution*3/4-25,
g_graphic_dev.XResolution/2-i*emp2-1,g_graphic_dev.YResolution*3/4+25,RGB(0,255,255));
for(i=0;i<=prio2;i++)
line(g_graphic_dev.XResolution/2+i*emp2+1,g_graphic_dev.YResolution*3/4-25,
g_graphic_dev.XResolution/2+i*emp2+1,g_graphic_dev.YResolution*3/4+25,RGB(255,0,255));
do{
int key = getchar();
if(key==0x4800) {
prio1 = prio1+1;
if(prio1 > PRI_USER_MAX) prio1=PRI_USER_MAX;//控制priority范围
setpriority(tid1,prio1);//up
line(g_graphic_dev.XResolution/2-prio1*emp2-1,g_graphic_dev.YResolution*3/4-25,
g_graphic_dev.XResolution/2-prio1*emp2-1,g_graphic_dev.YResolution*3/4+25,RGB(0,255,255));
}
else if(key==0x5000) {
line(g_graphic_dev.XResolution/2-prio1*emp2-1,g_graphic_dev.YResolution*3/4-25,
g_graphic_dev.XResolution/2-prio1*emp2-1,g_graphic_dev.YResolution*3/4+25,RGB(0,0,0));//消失涂黑
prio1 = prio1-1;
if(prio1 < PRI_USER_MIN) prio1=PRI_USER_MIN;
setpriority(tid1,prio1);//down
}
if(key==0x4d00) {
prio2 = prio2+1;
if(prio2 > PRI_USER_MAX) prio2=PRI_USER_MAX;
setpriority(tid2,prio2); //right
line(g_graphic_dev.XResolution/2+prio2*emp2+1,g_graphic_dev.YResolution*3/4-25,
g_graphic_dev.XResolution/2+prio2*emp2+1,g_graphic_dev.YResolution*3/4+25,RGB(255,0,255));
}
else if(key==0x4b00) {
line(g_graphic_dev.XResolution/2+prio2*emp2+1,g_graphic_dev.YResolution*3/4-25,
g_graphic_dev.XResolution/2+prio2*emp2+1,g_graphic_dev.YResolution*3/4+25,RGB(0,0,0));
prio2 = prio2-1;
if(prio2 < PRI_USER_MIN) prio2=PRI_USER_MIN;
setpriority(tid2,prio2);//left
}
}while(1);
// printf("This is task foo with tid=%d is Over!\r\n", task_getid());
task_exit(0);//不能直接return (void *)0,必须调用task_exit
}
Step6: 主函数执行
在main.c中创建三个线程,分别是用于对比的线程1、2,和用于控制两个线程优先级的控制线程3。设定初始优先级,注意将控制线程的优先级设为最高。
//进入图形模式
int mode=0x143;
init_graphic(mode);
//Apply for the stack
unsigned int stack_size = 1024*1024;
unsigned char *stack_foo1 = (unsigned char *)malloc(stack_size );
unsigned char *stack_foo2 = (unsigned char *)malloc(stack_size );
unsigned char *stack_foo3 = (unsigned char *)malloc(stack_size );
//Creat the thread
int tid_foo1,tid_foo2,tid_foo3;
//setpriority(0,8);
tid_foo1 = task_create(stack_foo1+stack_size, &tsk_foo1, (void *)0);
setpriority(tid_foo1,5);
tid_foo2 = task_create(stack_foo2+stack_size, &tsk_foo2, (void *)0);
setpriority(tid_foo2,8);
tid1 = tid_foo1;
tid2 = tid_foo2;
tid_foo3 = task_create(stack_foo3+stack_size, &tsk_foo3, (void *)0);
setpriority(tid_foo3,2);
//setpriority(0,35);
//wait for the thread to exit
task_wait(tid_foo1, NULL);
task_wait(tid_foo2, NULL);
task_wait(tid_foo3, NULL);
//msleep(5000);
//exit_graphic();
//Free the stack
free(stack_foo1);
free(stack_foo2);
free(stack_foo3);
最终效果图:
关于参数传递:
在本实验中需要向控制线程传递线程1,2的id数,这就涉及参数传递,此处有两种方法:(1)在线程创建中本就有个可传递的参数,是个指针,若需传递多个参数,则建立一个结构体传参即可。(2)创立两个全局变量,将线程1,2 的id数值赋给他们,这个方法不如上一个,若需要传的参数多了或者有还需传递相关函数则这个方法就不太妥当了,但对于少量的参数而言是个偷懒的好方法。
关于定点计算:
内核一般不允许浮点计算,但支持应用程序进行浮点计算,本实验用到定点计算。需在头文件中添加fixedptc.h
转载请说明来源