操作系统第三次实验 16281134

实验三 同步与通信

实验目的

  1. 系统调用的进一步理解。
  2. 进程上下文切换。
  3. 同步与通信方法。

实验题目

通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。

  1. 以下为fock.c代码
//fock.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<fcntl.h>
int main()
{
	sem_t *P1_signal,*P2_signal,*P3_signal,*P23_signal;
	//主函数中的进程是P1
	pid_t p2,p3,p4; 
	//创建信号量
	P1_signal=sem_open("P1_signal",O_CREAT,0666,1);
	P2_signal=sem_open("P2_signal",O_CREAT,0666,0);
	P3_signal=sem_open("P3_signal",O_CREAT,0666,0);
	sem_wait(P1_signal);
	p2=fork();//创建进程P2
	if(p2==0)
	{
		sem_wait(P1_signal); //等待p1
		printf("I am the process P2!\n");
		sem_post(P2_signal);
		sem_post(P1_signal);
	}
	if(p2>0)
	{
		p3=fork();
		if(p3==0)
		{
			sem_wait(P1_signal);
			printf("I am the process P3!\n");
			sem_post(P3_signal);
			sem_post(P1_signal);
		}
		if(p3>0)
		{
			printf("I am the process P1!\n");
			sem_post(P1_signal);
			p4=fork();
			if(p4==0)
			{
				sem_wait(P2_signal);
				sem_wait(P3_signal);
				sem_wait(P1_signal);
				printf("I am the process P4!\n");
				sem_post(P2_signal);
				sem_post(P3_signal);
				sem_post(P1_signal);
			}
		}
	}
	sem_close(P1_signal);
	sem_close(P23_signal);
	sem_close(P2_signal);
	sem_close(P3_signal);
	sem_unlink("P1_signal");
	sem_unlink("P23_signal");
	sem_unlink("P2_signal");
	sem_unlink("P3_signal");
	sleep(0.5);
	return 0;


}
  1. 由以下截图可证明:进程p2和p3互斥。
    多次试验满足:P1最先执行,P2、P3互斥执行,P4最后执行。
    在这里插入图片描述

火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。

  1. 添加同步机制前代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
volatile int ticketCount=1000;
void *SaleThread(void *arg)
{
	int num,temp;
	num=1000;
	for(int i=0;i<num;i++)
	{
		if(i % 200 ==0)
			printf("卖%d张票,剩余%d张票\n",i,ticketCount);
		temp=ticketCount;
		//放弃CPU,强制切换到另外一个进程
		pthread_yield();
		temp=temp-1;
		ticketCount-=1;
		pthread_yield();
		ticketCount=temp;
	}
	return NULL;
}

void *RefundThread(void *arg)
{
	int num,temp;
	num=1000;
	for(int i=0;i<num;i++)
	{
		if(i % 200 ==0)
			printf("退%d张票,剩余%d张票\n",i,ticketCount);
		temp=ticketCount;
		pthread_yield();
		temp=temp+1;
		ticketCount+=1;
		pthread_yield();
		ticketCount=temp;
		pthread_yield();
	}
	return NULL;
}
int main(int argc,char *argv[])
{
	printf("初始票数为:%d\n",ticketCount);
	pthread_t p1,p2;
	pthread_create(&p1,NULL,SaleThread,argv[1]);
	pthread_create(&p2,NULL,RefundThread,argv[2]);
	pthread_join(p1,NULL);
	pthread_join(p2,NULL);
	printf("最终票数为:%d\n",ticketCount);
	return 0;

}
  1. 不同步时火车购票情况如下
    由下图可知,不同步时买票与退票不同步,剩余票数不一定与初始票数一致。
    在这里插入图片描述
  2. 添加同步机制
	sem_wait(flag);
	pthread_yield(); 
	sem_post(flag);
  1. 添加同步机制后购票情况
    可以看出卖票与退票数一致
    在这里插入图片描述
  2. 以下为同步后代码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
volatile int ticketCount=1000;
sem_t *flag=NULL;

//买票
void *SaleThread(void *arg)
{
	int num,temp;
	num=1000;
	for(int i=0;i<num;i++)
	{
		//每200张输出一次
		if(i % 200 ==0)
			printf("卖%d张票,剩余%d张票\n",i,ticketCount);
		sem_wait(flag);
		temp=ticketCount;
		pthread_yield();//放弃CPU,切换到另一进程
		temp=temp-1;
		ticketCount-=1;
		pthread_yield();
		ticketCount=temp;
		pthread_yield(); 
		sem_post(flag); 
	}
	return NULL;
}

//退票
void *RefundThread(void *arg)
{
	int num,temp;
	num=1000;
	for(int i=0;i<num;i++)
	{
		//每200张输出一次
		if(i % 200 ==0)
			printf("退%d张票,剩余%d张票\n",i,ticketCount);
		sem_wait(flag); 
		temp=ticketCount;
		pthread_yield();
		temp=temp+1;
		ticketCount+=1;
		pthread_yield();
		ticketCount=temp;
		pthread_yield();
		sem_post(flag);
	}
	return NULL;
}
int main(int argc,char *argv[])
{
	
	flag=sem_open("flag",O_CREAT,0666,1);
	printf("初始票数为:%d\n",ticketCount);
	pthread_t p1,p2;
	pthread_create(&p1,NULL,SaleThread,argv[1]);
	pthread_create(&p2,NULL,RefundThread,argv[2]);
	pthread_join(p1,NULL);
	pthread_join(p2,NULL);
	printf("最终票数为:%d\n",ticketCount);
	sem_close(flag);
	sem_unlink("flag");
	return 0;

}

一个生产者一个消费者线程同步。设置一个线程共享的缓冲区, char buf[10]。一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。要求输出的和输入的字符和顺序完全一致。(在输出线程中,每次输出睡眠一秒钟,然后以不同的速度输入测试输出是否正确)。要求多次测试添加同步机制前后的实验效果。

实验代码如下

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/stat.h>
#include<fcntl.h>
char buf[10];
sem_t *empty=NULL;
sem_t *full=NULL;
void *worker1(void *arg)
{
	
	for(int i=0;i<10;i++)
	{
		sem_wait(empty);
		/* fflush(stdin); */
		/* printf("输入:"); */
		scanf("%c",&buf[i]);
		sem_post(full);
		if(i==9)
		{
			i=-1;
		}
	}	
	return NULL;
}

void *worker2(void *arg)
{
	for(int i=0;i<10;i++)
	{
		sem_wait(full);
		printf("输出:%c\n",buf[i]);
		sem_post(empty);
		sleep(1);
		if(i==9)
		{
			i=-1;
		
		}
	}	
	return NULL;
}

int main(int argc,char *argv[])
{
	empty=sem_open("empty_",O_CREAT,0666,10);
	full=sem_open("full_",O_CREAT,0666,0);
	pthread_t p1,p2;
	pthread_create(&p1,NULL,worker1,NULL);
	pthread_create(&p2,NULL,worker2,NULL);
	pthread_join(p1,NULL);
	pthread_join(p2,NULL);
	sem_close(empty);
	sem_close(full);
	sem_unlink("empty_");
	sem_unlink("full_");
	return 0;

}
  1. 在上一次输出完成之前,再次输入,输出正确
    在这里插入图片描述
  2. 两次输入和超出缓冲区,输出正确
    在这里插入图片描述
  3. 单次输入超出缓冲区,输入正确
    在这里插入图片描述

进程通信问题。阅读并运行共享内存、管道、消息队列三种机制的代码

  1. 通过实验测试,验证共享内存的代码中,receiver能否正确读出sender发送的字符串?

    可以。由下图可知receiver正确接收了sender
    在这里插入图片描述
    如果把其中互斥的代码删除,观察实验结果有何不同?
    (为防止while(1)输出,改为每隔1s循环一次):

去同步后代码sender2

/*
 * Filename: Sender.c
 * Description: 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.h>

int main(int argc, char *argv[])
{
    key_t  key;
    int shm_id;
    int sem_id;
    int value = 0;

    //1.Product the key
    key = ftok(".", 0xFF);

    //2. Creat semaphore for visit the shared memory
    sem_id = semget(key, 1, IPC_CREAT|0644);
    if(-1 == sem_id)
    {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    //3. init the semaphore, sem=0
    if(-1 == (semctl(sem_id, 0, SETVAL, value)))
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }

    //4. Creat the shared memory(1K bytes)
    shm_id = shmget(key, 1024, IPC_CREAT|0644);
    if(-1 == shm_id)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    //5. attach the shm_id to this process
    char *shm_ptr;
    shm_ptr = shmat(shm_id, NULL, 0);
    if(NULL == shm_ptr)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    //6. Operation procedure
    struct sembuf sem_b;
    sem_b.sem_num = 0;      //first sem(index=0)
    sem_b.sem_flg = SEM_UNDO;
    sem_b.sem_op = 1;           //Increase 1,make sem=1
    
    while(1)
    {
        if(1)// == (value = semctl(sem_id, 0, GETVAL)))
        {
            printf("\nNow, snd message process running:\n");
printf("\tThe shared pointer address : %p\n", shm_ptr);
            printf("\tInput the snd message:  ");
            scanf("%s", shm_ptr);

            if(0)// == 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;
        }
    }

    shmdt(shm_ptr);

    return 0;
}

去同步后代码receiver2

/*
 * Filename: Receiver.c
 * Description: 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.h>

int main(int argc, char *argv[])
{
    key_t  key;
    int shm_id;
    int sem_id;
    int value = 0;

    //1.Product the key
    key = ftok(".", 0xFF);

    //2. Creat semaphore for visit the shared memory
    sem_id = semget(key, 1, IPC_CREAT|0644);
    if(-1 == sem_id)
    {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    //3. init the semaphore, sem=0
    if(-1 == (semctl(sem_id, 0, SETVAL, value)))
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }

    //4. Creat the shared memory(1K bytes)
    shm_id = shmget(key, 1024, IPC_CREAT|0644);
    if(-1 == shm_id)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    //5. attach the shm_id to this process
    char *shm_ptr;
    shm_ptr = shmat(shm_id, NULL, 0);
    if(NULL == shm_ptr)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    //6. Operation procedure
    struct sembuf sem_b;
    sem_b.sem_num = 0;      //first sem(index=0)
    sem_b.sem_flg = SEM_UNDO;
    sem_b.sem_op = -1;           //Increase 1,make sem=1
    
    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);
	printf("\tThe shared pointer address : %p\n", shm_ptr);

            if(0)// == 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;
        }
	sleep(1);
    }

    shmdt(shm_ptr);
    //7. delete the shared memory
    if(-1 == shmctl(shm_id, IPC_RMID, NULL))
    {
        perror("shmctl");
        exit(EXIT_FAILURE);
    }

    //8. delete the semaphore
    if(-1 == semctl(sem_id, 0, IPC_RMID))
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }

    return 0;
}


由于未对共享内存清零,会默认输出上次运行结束时的内容。

如果在发送和接收进程中打印输出共享内存地址,他们是否相同,为什么?
在这里插入图片描述
输出共享内存地址,发现并不相同。

  1. 有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
    运行receiver:
    在这里插入图片描述
    阻塞等待,说明实现了同步机制。

    再运行sender:
    在这里插入图片描述
    接收了消息。

  2. 消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
    运行client和server:
    在这里插入图片描述
    输入信息可正常发送并收到回复

    只运行client并发送消息:
    在这里插入图片描述
    可见发送端不会被阻塞

    再运行server:
    在这里插入图片描述
    会将已有内容依次处理,之后由于消息队列中无内容而被阻塞。

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

pintos在thread.h中定义了一个结构体struct thread,这个结构体描述了有关进程的基本信息。

struct thread
  {
    tid_t tid;                          /* Thread identifier. */
    enum thread_status status;          /* Thread state. */
    char name[16];                      /* Name (for debugging purposes). */
    uint8_t *stack;                     /* Saved stack pointer. */
    int priority;                       /* Priority. */
    struct list_elem allelem;           /* List element for all threads list. */
 
    /* Shared between thread.c and synch.c. */
    struct list_elem elem;              /* List element. */
 
#ifdef USERPROG
    /* Owned by userprog/process.c. */
    uint32_t *pagedir;                  /* Page directory. */
#endif
 
    /* Owned by thread.c. */
    unsigned magic;                     /* Detects stack overflow. */
  };
 
 
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. */
  };

中断分两种,一种是IO设备向CPU发出的中断的信息,另一种是CPU决定切换到另一个进程时(轮换时间片)发出的指令。我们现在处理第二种。pintos的中断在interrupt.h和interrupt.c之中。其中这个枚举类型intr_lverl会在后面被反复提到:

enum intr_level
 {
   INTR_OFF,             /* Interrupts disabled. */
   INTR_ON               /* Interrupts enabled. */
 };

其中intr_off表示关中断,on表示开中断。执行原子级别操作的时候,中断必须是关闭状态。
pintos是以ticks作为基本时间单位的,每秒有TIMER_FREQ个ticks:

/* Number of timer interrupts per second. */
#define TIMER_FREQ 100 //系统默认这个宏为100

pintos默认每一个ticks调用一次时间中断。每一个线程最多可以占据CPU一个ticks的时长,之后就必须切换。

与thread有关的函数,以下函数在thread.c中都可以找到:
1.thread_current()获取当前当前的线程的指针。
2.thread_foreach(thread_action_func *func, void *aux) 遍历当前ready queue中的所有线程,并且对于每一个线程执行一次func操作。这里的func是一个任意给定函数的指针,参数aux则是想要传给这个函数的参数。
3.thread_block()和thread_unblock(thread *t)。 这是一对函数,区别在于第一个函数的作用是把当前占用cpu的线程阻塞(就是放到waiting里面),而第二个函数作用是将已经被阻塞的进程t唤醒到ready队列中。
4.timer_interrupt (struct intr_frame *args UNUSED)在timer.c中,pintos在每次时间中断时(即每一个时间单位(ticks))调用一次这个函数。
5.intr_disable ()在interrupt.c中,返回关中断,然后返回中断关闭前的状态。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值