type
status
date
slug
summary
tags
category
icon
password
comment_flag
SLUGS
最近刚做了最后一次实验,实现多线程同步(多线程对同一个缓冲区的使用问题),涉及到信号量的设计,这正是我知识点盲区,所以写得有点久了。
于是参考此处,略补了一点知识:https://github.com/Wangzhike/HIT-Linux-0.11/tree/master/5-semaphore
下面进入正题(开始抄老师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组成如下:
notion image
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_onwake_up必须在关中断环境中运行,即用save_flags_cli/restore_flags保护
Sample:

实验步骤

信号量系统调用实现

kernel\kernel.h中定义结构体类型Semaphore,并声明需要用到的相关全局变量,并且声明系统调用服务例程函数
需要对Semaphore结构体类型说明的是: ①Sem_tstruct 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的地址。
系统调用的添加,参考前几次实验的添加步骤,大致如下:
  1. \userapp\include\syscall.h 声明系统调用API
  1. \userapp\lib\syscall-wrapper.S 定义封装例程
  1. \include\syscall-nr.h 定义系统调用号
  1. \kernel\kernel.h 声明系统调用服务例程函数sys_xxx
  1. \kernel\sem.c 实现系统调用服务例程函数
  1. \kernel\machdep.c 在系统调用分发函数syscall()中根据系统调用号加入SYSCALL_xxx分支。 跳过。

生产者/消费者设计

后面主要是创建线程,使用线程,画图之类的折腾,略述。 \userapp\main.c中,声明全局变量,信号量ID,buffer大小
注:互斥信号量数量取决于buffer的数量,这样可使生产者在填充新的缓冲区同时,可以让消费者清除旧的缓冲区(针对多消费者多生产者情况)。若只一个,那么只有等生产者线程填充完新的缓冲区,消费者才能清除旧的缓冲区,尽管两者使用的缓冲区并不是同一个。即使生产者线程用完了自己的时间片,由于共用了同一互斥信号量,消费者线程得到时间片也进不了临界区,直到生产者线程完成新缓冲区的填充。
下面说说生产者与消费者线程函数的创建,模板如下(具体代码见尾部): 生产者线程函数模板:
消费者线程函数模板:

实验结果

notion image

感谢

感谢洪明坚老师的ppt,感谢学长们的实验报告,感谢网上大神的笔记。

代码附录

main.c
lab4.h
 
ArchLinux安装(一)操作系统实验——线程创建与调度
Loading...