本文最后更新于:2023年9月25日 下午
OrangeS使用信号量PV操作实现读者优先、写者优先 0 代码说明
code\reader文件夹为读者优先
code\writer文件夹为写者优先
基于6r源码进行设计
1 添加新task
2 封装系统调用 2.1 myprint proto.h中
1 2 PUBLIC void myprint (char * s) ; PUBLIC void sys_myprint (char * s) ;
修改const.h,#define NR_SYS_CALL 2
在global.c中,修改
1 2 PUBLIC system_call sys_call_table[NR_SYS_CALL] = {sys_get_ticks, sys_myprint};
syscall.asm
1 2 3 4 5 6 7 8 _NR_myprint equ 1 ; global myprint ... myprint: mov eax, _NR_myprint mov ecx,[esp+4] int INT_VECTOR_SYS_CALL ret
proc.c,这里用了一些小技巧,通过一个参数就能实现彩色打印,就是判断进程的偏移量,选择是使用disp_str还是disp_color_str
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 PUBLIC void sys_myprint (char * s) { int offset = p_proc_ready - proc_table; switch (offset) { case 0 : disp_color_str(s, BRIGHT | MAKE_COLOR(BLACK, RED)); break ; case 1 : disp_color_str(s, BRIGHT | MAKE_COLOR(BLACK, GREEN)); break ; case 2 : disp_color_str(s, BRIGHT | MAKE_COLOR(BLACK, BLUE)); break ; case 5 : disp_str(s); break ; case 4 : disp_color_str(s, BRIGHT | MAKE_COLOR(BLACK, PURPLE)); break ; case 3 : disp_color_str(s, BRIGHT | MAKE_COLOR(BLACK, YELLO)); break ; default : disp_str(s); break ; } }
因为进行了参数传递,所以需要在进行系统调用时候也将参数压栈且出栈
kernal.asm
1 2 3 4 5 6 7 8 9 sys_call: call save sti push ecx call [sys_call_table + eax * 4] add esp,4 mov [esi + EAXREG - P_STACKBASE], eax cli ret
2.2 mysleep 同上的步骤跳过,仅叙述不同的
给PROCESS结构体加上 int wakeup_ticks; // 睡醒的时刻
```c /====================================================================== sys_sleep ====================================================================== / PUBLIC void sys_sleep(int milli_seconds) { p_proc_ready->wakeup_ticks = get_ticks() + milli_seconds / (1000 / HZ); schedule(); }
1 2 3 4 5 6 7 8 9 10 11 3 . 添加辅助方法,用于判断进程是否可用 ```c PUBLIC int isRunnable(PROCESS* p){ if (p->wakeup_ticks <= get_ticks()&&p->isBlock == 0&&p-> isDone ==0 ){ return 1 ; }else { return 0 ; } }
2.3 PV操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 PUBLIC void sys_p (void *mutex) { disable_irq(CLOCK_IRQ); Semaphore *semaphore = (Semaphore *)mutex; semaphore->value--; if (semaphore->value < 0 ) { sleep(semaphore); } enable_irq(CLOCK_IRQ); } PUBLIC void sys_v (void *mutex) { disable_irq(CLOCK_IRQ); Semaphore *semaphore = (Semaphore *)mutex; semaphore->value++; if (semaphore->value <= 0 ) { wakeup(semaphore); } enable_irq(CLOCK_IRQ); } PUBLIC void sleep (Semaphore *mutex) { mutex->queue [-mutex->value - 1 ] = p_proc_ready; p_proc_ready->isBlock = 1 ; schedule(); } PUBLIC void wakeup (Semaphore *mutex) { PROCESS *wake = mutex->queue [0 ]; int i,index=0 ; for (i = 0 ; i < (-mutex->value+1 ); ++i) { if (mutex->queue [i]->priority > wake->priority){ wake = mutex->queue [i]; index = i; } } for (i = index; i < (-mutex->value); ++i) { mutex->queue [i] = mutex->queue [i + 1 ]; } wake->isBlock = 0 ; }
3 读者优先 3.1 理解
如果有读者在读,写者就得一直等待;
如果写者进程在读,读者进程到来后,还没写的进程必须让读者先读
3.2 修改PROCESS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct s_proc { STACK_FRAME regs; u16 ldt_sel; DESCRIPTOR ldts[LDT_SIZE]; int ticks; int wakeup_ticks; int needTime; int isBlock; int isDone; char type; u32 pid; char p_name[16 ]; } PROCESS;
对此进行一些说明
ticks,废除了,没用
wakeup_ticks,使用mysleep后睡眠
needTime,需要的时间片
isBlock,阻塞状态,pv操作使用,如果在P时候被sleep,则Block;V时wake,则取消Block
3.3 时间中断处理 1 2 3 4 5 6 7 8 9 10 PUBLIC void clock_handler (int irq) { ticks++; p_proc_ready->ticks--; schedule(); }
去掉原有代码一些不必要的东西
3.4 信号量PV操作 1 2 3 4 5 6 7 8 9 10 11 typedef struct semaphore { int value; PROCESS* queue [NR_TASKS]; }Semaphore; EXTERN Semaphore readMutex; EXTERN Semaphore writeMutex; EXTERN Semaphore countMutex; EXTERN Semaphore writeMutexMutex;
读者进程伪代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void reader () { while (1 ){ P(&countMutex); if (readPreparedCount == 0 ){ P(&writeMutex);} readPreparedCount++; V(&countMutex); P(&readMutex); readCount++; readCount--; V(&readMutex); P(&countMutex); readPreparedCount--; if (readPreparedCount == 0 ){ V(&writeMutex); } V(&countMutex); p_proc_ready->isDone = solveHunger; milli_delay(10 ); } }
写者进程伪代码
1 2 3 4 5 6 7 8 9 10 11 12 void writer () { while (1 ){ P(&writeMutexMutex); P(&writeMutex); V(&writeMutex); V(&writeMutexMutex); p_proc_ready->isDone = solveHunger; milli_delay(10 ); } }
F进程
1 2 3 4 5 6 7 8 9 void F () { while (1 ) { ... mysleep(10 ); } }
3.5 调度算法 想法:
选择优先级最高的进程,优先运行useTime最少的进程
因为F进程优先级最高,所以如果有F,一定运行F,所以需要将F进程使用mysleep进行睡眠,避免其使用时间片
而在ABC进程中,没读一次进行milli_delay(10);操作,也就是让这个时间片内不允许做任何事,在进入到下个时间片后,程序由时间中断跳入schedule,进行进程调度,然后再解决下个时间片的事情
F的实现方式其实是一种伪实现的方式,本质是在同一个时间片内先时间中断,然后由调度算法必定命中F,打印信息后,进入mysleep(10);,也就是等待下个时间片,然后在mysleep中再次进行schedule(),执行读写进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 PUBLIC void schedule () { disable_irq(CLOCK_IRQ); check(); PROCESS *select = proc_table+5 ; if (isRunnable(select)){ p_proc_ready = select; }else { while (!isRunnable(pre_proc)) { pre_proc++; if (pre_proc==select){ pre_proc = proc_table; } } p_proc_ready = pre_proc; pre_proc++; if (pre_proc==select){ pre_proc = proc_table; } } if (p_proc_ready->type=='r' ||p_proc_ready->type=='w' ){ nowStatus = p_proc_ready->type; } enable_irq(CLOCK_IRQ); }
3.6 结果
为了体现读者优先,我们在读者进程开头加上mysleep(10),也就是让写者进程先到达
并发量1 读者进程到达后,写者进程被饿死,这很好理解,因为不断有读者进程在排队,而读者进程优先级高,所以轮不到写者进程
)
并发量2 读者进程到达后,写者进程被饿死,这很好理解,因为不断有读者进程在排队,而读者进程优先级高,所以轮不到写者进程
并发量3 当进程量=并发量时,读者进程到达后,写者进程不被饿死
因为总会有时刻,读并发==0,这时候读进程会V(&writeMutex);,使写进程进入
比如在箭头所示的时间点,A完成了第二次读,BC完成了第一次读,都退出了,并发量=0
4 写者优先 4.1 理解
已有进程运行时,有写者和读者进入,即使读者晚于写者到来,也优先进行写操作
例如,对于到达顺序【R1】【W1】【R2】【W2】【R3】而言,运行顺序为
【R1】【W1】【W2】【R2】【R3】
写者进程到来后,已经进入队列的读进程可以先读,还没到来的都必须等写完才可以读
4.2 信号量PV操作 使用的信号量如下
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct semaphore { int value; PROCESS* queue [NR_TASKS]; }Semaphore; EXTERN Semaphore readMutex; EXTERN Semaphore writeMutex; EXTERN Semaphore readCountMutex; EXTERN Semaphore writeCountMutex; EXTERN Semaphore readPermission; EXTERN Semaphore readPermissionMutex;
读进程伪代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void reader () { while (1 ){ P(&readPermissionMutex); P(&readPermission); P(&readCountMutex); if (readPreparedCount == 0 ){ P(&writeMutex); } readPreparedCount++; V(&readCountMutex); V(&readPermission); V(&readPermissionMutex); P(&readMutex); readCount++; readCount--; V(&readMutex); P(&readCountMutex); readPreparedCount--; if (readPreparedCount == 0 ){ V(&writeMutex); } V(&readCountMutex); p_proc_ready->isDone = solveHunger; milli_delay(10 ); } }
写进程伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void writer (char process) { while (1 ){ P(&writeCountMutex); writeCount++; if (writeCount==1 ){ P(&readPermission); } V(&writeCountMutex); P(&writeMutex); V(&writeMutex); P(&writeCountMutex); writeCount--; if (writeCount == 0 ){ V(&readPermission); } V(&writeCountMutex); p_proc_ready->isDone = solveHunger; milli_delay(10 ); } }
4.3 结果
为了体现写者优先,我们在写者进程开头加上mysleep(10),也就是让读者进程先到达
根据前面的解释,由于写并发量为1,写进程为2,所以读者进程在写者进程到来后会被饿死
并发量1
并发量2
并发量3
5 解决饥饿问题 进程结构体引入变量isDone,所有进程都运行过一遍后,isDone重置,以白色输出<RESTART>
在main.c中修改此行可对解决饥饿进行开关
解决饥饿后的并发量为3写者优先: