实验三 同步问题
16281259 鲁鑫
- 通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。
代码如下:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t* mySem_2 = NULL; //创建信号量
sem_t* mySem_3 = NULL;
sem_t* mySem_23 = NULL;
int main(){
pid_t pid_1,pid_root;
mySem_2 = sem_open("P2", O_CREAT, 0666, 0);
mySem_3 = sem_open("P3", O_CREAT, 0666, 0);
mySem_23 = sem_open("P23", O_CREAT, 0666, 0);
pid_root = getpid();
for(int i = 0; i<= 1; i++){
pid_1 = fork();
if (i==0) {
if (pid_1 > 0){
} else if (pid_1 == 0)
{
} else
{
perror("fork!\n");
}
} else
{
if (pid_1 > 0)
{
if(getpid() == pid_root)
{
} else
{
pid_1 = fork();
if(pid_1 == 0){
sem_wait(mySem_2);
sem_wait(mySem_3);
printf("I am the process P4\n");
} else
{
printf("I am the process P1\n");
sem_post(mySem_23);
}
}
} else if (pid_1 == 0)
{
if(getppid()!=pid_root){
sem_wait(mySem_23);
printf("I am the process P3\n");
sem_post(mySem_23);
sem_post(mySem_3);
} else
{
sem_wait(mySem_23);
printf("I am the process P2\n");
sem_post(mySem_23);
sem_post(mySem_2);
}
} else
{
perror("fork!");
}
}
}
int i = 3;
while(i--){
sleep(1);
}
sem_close(mySem_2);
sem_close(mySem_3);
sem_close(mySem_23);
sem_unlink("P2");
sem_unlink("P3");
sem_unlink("P23");
return 0;
}
运行结果如下图:
2.火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。
实验代码如下:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <sched.h>
int count=1000;//票数
int temp=0;
sem_t empty;//票余量
sem_t full;//售票量
void *sale(void *arg){
while(1){
sem_wait(&empty);
temp=count;
pthread_yield();//诱发并发错误
temp-=1;
pthread_yield();
count=temp;
printf("sale:%d ticket left\n",count);
usleep(500);//执行完一次操作,睡眠500微秒
sem_post(&full);
}
}
void *back(void *arg){
while(1){
sem_wait(&full);
temp=count;
pthread_yield();
temp=temp+1;
pthread_yield();
count=temp;
printf("back:%d ticket left\n",count);
usleep(500);
sem_post(&empty);
}
}
int main(int argc,char *argv[])
{
sem_init(&empty,0,1000);
sem_init(&full,0,0);
pthread_t p1,p2;
pthread_create(&p1,NULL,sale,NULL);
pthread_create(&p2,NULL,back,NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
sem_destroy(&empty);
sem_destroy(&full);
return 0;
}
运行结果部分截图如下:
售票的线程在每次售票之前都会检查是否票有余量,退票的进程每次进行前都会检查是否有票售出。
实验中没有多次测试添加同步机制前后的实验效果,因为实验中售票的次数很多,如果不添加同步机制的话,直至票售完后才会进行退票操作,不易观察,也不符合实际情况。
3.一个生产者一个消费者线程同步。设置一个线程共享的缓冲区, char buf[10]。一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。要求输出的和输入的字符和顺序完全一致。(在输出线程中,每次输出睡眠一秒钟,然后以不同的速度输入测试输出是否正确)。要求多次测试添加同步机制前后的实验效果。
实验代码如下:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include<assert.h>
char buffer[10]={0};
sem_t empty;//空闲buffer
sem_t full;//已写入buffer
void *producer(void *arg)
{
for(int i=0;i<10;)
{
sem_wait(&empty);
scanf("%c",&buffer[i]);
i++;
i%=10;//保证缓存区不会溢出
sem_post(&full);
}
}
void *consumer(void *arg)
{
for(int j=0;j<10;)
{
sem_wait(&full);
printf("%c ",buffer[j]);
j++;
j%=10;
sleep(1);
sem_post(&empty);
}
}
int main(int argc,char *argv[])
{
sem_init(&empty,0,10);//初始化信号量
sem_init(&full,0,0);
pthread_t p1,p2;
pthread_create(&p1,NULL,producer,NULL);
pthread_create(&p2,NULL,consumer,NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
sem_destroy(&empty);
sem_destroy(&full);
return 0;
}
运行结果截图:
输入一串字符后,经过一段时间后在下一行输出。输入和输出的次序相同。
4.进程通信问题。阅读并运行共享内存、管道、消息队列三种机制的代码
- 实验测试,验证通过共享内存的方式,receiver能否接收并读出sender发送的字符串
sender1.c,receiver1.c运行结果:
可以看到接收者可以接收到发送者发送的字符串。其中sender1第一个输入数据没有被接收到是因为没有运行receiver1。
如果删除共享内存的互斥代码,观察实验还能否成功
sender1中的互斥代码段:
while(1)
{
if(0 == (value = semctl(sem_id, 0, GETVAL)))
{
printf("\nNow, snd message process running:\n");
printf("\tInput the snd message: ");
scanf("%s", shm_ptr);
if(-1 == semop(sem_id, &sem_b, 1))
{
perror("semop");
exit(EXIT_FAILURE);
}
}
//if enter "end", then end the process
if(0 == (strcmp(shm_ptr ,"end")))
{
printf("\nExit sender process now!\n");
break;
}
}
receiver.c中的互斥代码段:
while(1)
{
if(1 == (value = semctl(sem_id, 0, GETVAL)))
{
printf("\nNow, receive message process running:\n");
printf("\tThe message is : %s\n", shm_ptr);
if(-1 == semop(sem_id, &sem_b, 1))
{
perror("semop");
exit(EXIT_FAILURE);
}
}
//if enter "end", then end the process
if(0 == (strcmp(shm_ptr ,"end")))
{
printf("\nExit the receiver process now!\n");
break;
}
}
代码中根据value = semctl(sem_id, 0, GETVAL),value的值为1时进行读,为0时进行写,实现互斥。
将其删掉再次运行:
可以看到,接收者并不能正确接收到发送者发送的内容,会多输出上一次操作,发送者发送到共享内存里的内容。
- 有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
无论是无名管道还是有名管道,如果在读端和写端同时打开了多个进程用于读写,它们之间的读写是不确定的,必须通过其他的同步机制实现多进程通讯的同步,所以有名管道和无名管道并没有实现同步机制。 - 消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
消息队列支持双向通信,克服了管道只能承载无格式字节流的缺点,在进程间只有少量数据传输的前提下实现了同步机制。
可以看到发送者和接收者是同步的。
5.阅读Pintos操作系统,找到并阅读进程上下文切换的代码,说明实现的保存和恢复的上下文内容以及进程切换的工作流程。
进程切换就是从正在运行的进程中收回处理器,然后再使待运行进程来占用处理器。所谓从某个进程收回处理器,实质上就是将此进程存放在处理器的寄存器中的数据暂时存储起来,从而让其他进程使用寄存器。被中止运行进程的中间数据将被保存在进程的私有堆栈中让其他进程来占用处理器,实质上是把某个进程存放在私有堆栈中寄存器的数据(前一次此进程被中止时的数据)再恢复到处理器的寄存器中去,并把待运行进程的断点送入处理器的程序指针,于是待运行进程就开始被处理器运行了,即此进程已占有处理器的使用权了。
一个标准的Linux内核可以支持运行50~50000个进程运行,对于普通的CPU,内核会调度和执行这些进程。每个进程都会分到CPU的时间片来运行,当一个进程用完时间片或者被更高优先级的进程抢占后,它会备份到CPU的运行队列中,同时其他进程在CPU上运行。这个进程切换的过程被称作上下文切换。
在切换时,一个进程存储在处理器各寄存器中的中间数据叫做进程的上下文,所以进程的切换实质上就是被中止运行进程与待运行进程上下文的切换。在进程未占用处理器时,进程的上下文是存储在进程的私有堆栈中的。
参考博客:
https://www.cnblogs.com/Jimmy1988/p/7699351.html
https://www.cnblogs.com/Jimmy1988/p/7706980.html
https://www.cnblogs.com/Jimmy1988/p/7553069.html
https://blog.youkuaiyun.com/OuWenZhPi/article/details/89107557
代码:
https://github.com/luxinlx/16281259-bjtu.edu.cn/tree/master/实验三代码