<<操作系统>>第三次实验
进程同步

学院:计算机与信息技术
专业:信息安全(保密技术)
班级: 安全1601
姓名: 李超
学号: 16281194

2019年5月10日

1.实验目的
系统调用的进一步理解。
进程上下文切换。
同步与通信方法。
2实验题目
1)通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。
进程关系图:
16281194_李超_操作系统第三次实验

伪代码:
Semaphore mutex1 := 0,mutex2 := 0,mutex3 := 0;
T1( ){
P1();
Signal(mutex1);
}
T2( ){
Wait(mutex1);
P2();
Signal(mutex2);
}
T3( ){
Wait(mutex1);
P3();
Signal(mutex3);
}
T4( ){
Wait(mutex2);
Wait(mutex3);
P4();
}
源代码:
16281194_李超_操作系统第三次实验
16281194_李超_操作系统第三次实验
16281194_李超_操作系统第三次实验
16281194_李超_操作系统第三次实验
16281194_李超_操作系统第三次实验
运行结果:
16281194_李超_操作系统第三次实验
2)火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。(说明:为了更容易产生并发错误,可以在适当的位置增加一些pthread_yield(),放弃CPU,并强制线程频繁切换,例如售票线程的关键代码:
temp=ticketCount;
pthread_yield();
temp=temp-1;
pthread_yield();
ticketCount=temp;
退票线程的关键代码:
temp=ticketCount;
pthread_yield();
temp=temp+1;
pthread_yield();
ticketCount=temp;

售票程序代码:
void *sellticket(){
for(int i = 0;i<sellTimes;i++){
sem_wait(mutex);
int temp = ticketCount;
pthread_yield();
temp–;
pthread_yield();
ticketCount = temp;
sem_post(mutex);
}
}
退票程序代码:
void *returnticket(){
for(int i = 0;i<returnTimes;i++){
sem_wait(mutex);
int temp = ticketCount;
pthread_yield();
temp++;
pthread_yield();
ticketCount = temp;
sem_post(mutex);
}
}
在代码中增加一些 pthread_yield(),放弃 CPU,并强制线程频繁切换,使程序更容
易发生并发错误。
未加同步机制时:
16281194_李超_操作系统第三次实验
可见程序经常会出现错误。
加入一个互斥信号量,作为同步机制。
加入同步机制后:
16281194_李超_操作系统第三次实验
此时的票数统计无误。

3)一个生产者一个消费者线程同步。设置一个线程共享的缓冲区, char buf[10]。一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。要求输出的和输入的字符和顺序完全一致。(在输出线程中,每次输出睡眠一秒钟,然后以不同的速度输入测试输出是否正确)。要求多次测试添加同步机制前后的实验效果。
源代码:
16281194_李超_操作系统第三次实验
16281194_李超_操作系统第三次实验
16281194_李超_操作系统第三次实验
16281194_李超_操作系统第三次实验
16281194_李超_操作系统第三次实验
运行结果:
16281194_李超_操作系统第三次实验
可以看到在text文件中写入,而读出终端中显示读出了text文件的数据。
问题及解决方案:
出现问题:
1)读出奇怪的符号
16281194_李超_操作系统第三次实验
2)重复读文本中的前几个数据
16281194_李超_操作系统第三次实验
即在文件中已经写入了后面的字符,但读出的数据却总是前几个字符。
思考:
我考虑了各种情况,想到进程运行在内存当中,但文件的读写是向外存读写,内存与外存之间存在缓冲区,可能是数据存在缓冲区中。如下图,写缓冲区写满后,则向文件中写入,所以文件中可以写入后面的字符,而内存程序不停地访问读缓冲区的数据,读缓冲区中的数据又不会被更新,所以导致重复读文本的前几个数据。
16281194_李超_操作系统第三次实验
1)读出奇怪的符号:
程序运行开始时,新建一个txt文件,此时内存还没有向外存中写入数据,也就是读写之间存在一个时间差,导致内存无法从读缓冲区中读取有效数据,从而显示读出奇怪的符号。
2)重复读文本中的前几个数据:
在程序将数据写入外存后,读缓冲区从外存中取出前几个数据,内存从读缓冲区中不停地取数据,则只能一直读到缓冲区中的数据,无法读到后续数据。

解决方案:
通过思考得知,只要强制缓冲区清空,或者取消缓冲区,使内存直接从外存中读写数据,则可以解决上述问题。
1)强制清空缓冲区
每读一个数据则强制清空缓冲区:
16281194_李超_操作系统第三次实验
每写一个数据则强制清空缓冲区:
16281194_李超_操作系统第三次实验
2)取消缓冲区
16281194_李超_操作系统第三次实验
4)(下面两个题目,选做一个)
(1)在Pinto操作系统中,增加一个系统调用,系统调用名为test_system_call()。无输入参数,输出为在显示器中打印输出:Hello. This is my test system call.
(2)进程通信问题。阅读并运行共享内存、管道、消息队列三种机制的代码
(参见
https://www.cnblogs.com/Jimmy1988/p/7706980.html
https://www.cnblogs.com/Jimmy1988/p/7699351.html
https://www.cnblogs.com/Jimmy1988/p/7553069.html
实验测试a)通过实验测试,验证共享内存的代码中,receiver能否正确读出sender发送的字符串?如果把其中互斥的代码删除,观察实验结果有何不同?如果在发送和接收进程中打印输出共享内存地址,他们是否相同,为什么?b)有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?c)消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?

5)阅读Pintos操作系统,找到并阅读进程上下文切换的代码,说明实现的保存和恢复的上下文内容以及进程切换的工作流程。

5.1 thread.h源码阅读
从git上下载pintos的源码: git clone https://github.com/laiy/Pintos/tree/master/src

进程的实验先进入到thread.h中寻找,可以看到这是对于一个进程的四个状态的定义,还有优先级的定义。
/* States in a thread’s life cycle. /
enum thread_status
{
THREAD_RUNNING, /
Running thread. /
THREAD_READY, /
Not running but ready to run. /
THREAD_BLOCKED, /
Waiting for an event to trigger. /
THREAD_DYING /
About to be destroyed. /
};
/
Thread identifier type.
You can redefine this to whatever type you like. /
typedef int tid_t;
#define TID_ERROR ((tid_t) -1) /
Error value for tid_t. */

/* Thread priorities. /
#define PRI_MIN 0 /
Lowest priority. /
#define PRI_DEFAULT 31 /
Default priority. /
#define PRI_MAX 63 /
Highest priority. */
再往下看我们可以看到对于进程这个结构体的定义

tid_t tid:线程的线程标识符。每个线程必须具有在内核的整个生命周期内唯一的tid。默认情况下,tid_t是int的typedef(在上面定义过了),每个新线程接收数字上的下一个更高的tid,从初始进程的1开始。
enum thread_status:线程的状态,一共有以下四种:
THREAD_RUNNING:线程在给定时间内正在运行。
THREAD_READY:该线程已准备好运行,但它现在没有运行。
THREAD_BLOCKED:线程正在等待某些事务,
THREAD_DYING:切换到下一个线程后,调度程序将销毁该线程。
char name[16]:线程命名的字符串,至少前几个数组单元为字符。
uint8_t *stack:线程的栈指针。当线程运行时,CPU的堆栈指针寄存器跟踪堆栈的顶部,并且该成员未使用。但是当CPU切换到另一个线程时,该成员保存线程的堆栈指针。保存线程的寄存器不需要其他成员,因为必须保存的其他寄存器保存在堆栈中。
int priority:线程优先级,范围从PRI_MIN(0)到PRI_MAX(63)。较低的数字对应较低的优先级,因此优先级0是最低优先级,优先级63是最高优先级。
struct list_elem allelem:用于将线程链接到所有线程的列表中。每个线程在创建时都会插入到此列表中,并在退出时删除。应该使用thread_foreach()函数来迭代所有线程。
struct list_elem elem:用于将线程放入双向链表:ready_list(准备好运行的线程列表)或sema_down(等待信号量的线程列表)。上面定义过了,default 31。
uint32_t pagedir:页表指针,用于将进程结构的虚拟地址映射到物理地址。
unsigned magic:始终设置为THREAD_MAGIC,用于检测堆栈溢出。

GitHub代码连接:https://github.com/KTlc/OS-bjtulab/blob/master/1.c

相关文章:

  • 2021-04-12
  • 2021-06-07
  • 2021-12-04
  • 2021-12-29
  • 2021-10-08
  • 2022-02-06
  • 2022-12-23
猜你喜欢
  • 2021-08-04
  • 2021-05-22
  • 2021-04-28
  • 2021-05-13
  • 2021-11-05
  • 2021-04-12
相关资源
相似解决方案