type
status
date
slug
summary
tags
category
icon
password
comment_flag
SLUGS
最近刚做了最后一次实验,实现多线程同步(多线程对同一个缓冲区的使用问题),涉及到信号量的设计,这正是我知识点盲区,所以写得有点久了。
下面进入正题(开始抄老师ppt)。
实验内容
实现信号量
编辑文件kernel/sem.c,实现如下四个函数
int sys_sem_create(int value)value
是信号量的初值
分配内存要用kmalloc
,不能用malloc
!
成功返回信号量ID,否则返回-1
int sys_sem_destroy(int semid)
释放内存要用kfree
,不能用free
!
成功返回0,否则返回-1
int sys_sem_wait(int semid)
P操作,要用save_flags_cli
/restore_flags
和函数sleep_on
成功返回0,否则返回-1
int sys_sem_signal(int semid)
V操作,要用save_flags_cli
/restore_flags
和函数wake_up
成功返回0,否则返回-1
把这四个函数做成系统调用,分别是sem_create/destroy/wait/signal
设计生产者/消费者
- ①在图形模式下将屏幕沿垂直方向分成N份,作为N个缓冲区。其次,创建两个线程,其中一个是生产者,负责生成随机数并填到缓冲区中;另一个线程是消费者,负责把缓冲区中的随机数进行排序
- 生产者生成随机数后,要画到缓冲区
- 消费者完成排序之后,要清除缓冲区
- ②创建一个控制线程,用键up/down控制生产者的优先级,用键right/left控制消费者的优先级
- 把控制线程的静态优先级设置到最高,以保证控制效果
- 在屏幕上用进度条动态显示生产者和消费者的静态优先级
预备知识
信号量
关于信号量
可以参考文章: Linux 0.11下信号量的实现和应用
EPOS互斥
- EPOS运行于单CPU的计算机上 –内核可以用开关中断实现互斥
- 中断的开关由EFLAGS中的IF位决定,指令如下:
- sti,IF=1 中断打开
- cli,IF=0 中断关闭
EFLAGS组成如下:
EPOS提供的中断API:
- 保存EFLAGS的值到一个变量flags中,然后IF=0
save_flags_cli(flags)
- 把变量flags的值恢复到EFLAGS中
restore_flags(flags)
Sample:
线程睡眠/唤醒API
睡眠
void sleep_on(struct wait_queue **head)
参数head是睡眠队列的头指针
唤醒void wake_up(struct wait_queue **head, int n)
参数n表示要唤醒的线程个数,n小于0表示唤醒该队列中的所有线程
sleep_on
和wake_up
必须在关中断环境中运行,即用save_flags_cli
/restore_flags
保护Sample:
实验步骤
信号量系统调用实现
kernel\kernel.h中定义结构体类型
Semaphore
,并声明需要用到的相关全局变量,并且声明系统调用服务例程函数需要对
Semaphore
结构体类型说明的是:
①Sem_t
为struct Semaphore
的别名
②id
来自于GSID
的值,作用是标识唯一的信号量结构体,后面通过它来寻找对应的信号量结构体首地址
③val
是信号量的值,在每一次P/V操作后会增1/减1
④wq
是存放要使用该信号量的线程的等待队列的头指针
⑤next
使用next指针把所用信号量串成一个单向链表。在\kernel\sem.c中完成对全局变量的初始化,以及对系统调用服务例程函数的实现:
注:AddrSem(sid)可以得到id为sid的信号量x地址,但使用AddrSem(sid-1)来得到链表中信号量x的前一个信号量地址是有缺陷的,因为sid-1的信号量可能已被destroy。但本实验不涉及这个问题,你可以采用AddrSem(sid-1)先得到sid-1的信号量地址Y,然后Y->next自然便是信号量x的地址。
系统调用的添加,参考前几次实验的添加步骤,大致如下:
\userapp\include\syscall.h
声明系统调用API
\userapp\lib\syscall-wrapper.S
定义封装例程
\include\syscall-nr.h
定义系统调用号
\kernel\kernel.h
声明系统调用服务例程函数sys_xxx
\kernel\sem.c
实现系统调用服务例程函数
\kernel\machdep.c
在系统调用分发函数syscall()
中根据系统调用号加入SYSCALL_xxx
分支。 跳过。
生产者/消费者设计
后面主要是创建线程,使用线程,画图之类的折腾,略述。
\userapp\main.c中,声明全局变量,信号量ID,buffer大小
注:互斥信号量数量取决于buffer的数量,这样可使生产者在填充新的缓冲区同时,可以让消费者清除旧的缓冲区(针对多消费者多生产者情况)。若只一个,那么只有等生产者线程填充完新的缓冲区,消费者才能清除旧的缓冲区,尽管两者使用的缓冲区并不是同一个。即使生产者线程用完了自己的时间片,由于共用了同一互斥信号量,消费者线程得到时间片也进不了临界区,直到生产者线程完成新缓冲区的填充。
下面说说生产者与消费者线程函数的创建,模板如下(具体代码见尾部):
生产者线程函数模板:
消费者线程函数模板:
实验结果
感谢
感谢洪明坚老师的ppt,感谢学长们的实验报告,感谢网上大神的笔记。