操作系统实验三报告:同步问题

实验三 同步问题

16281100 雷兵

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

程序设计思路:

利用信号量实现前驱关系,P1与P2、P3共享信号量sem1,初始值为0,将signal()操作放在P1最后,在P2、P3开始进行wait()操作,最后放signal()操作,从而实现先执行P1后,再执行P2、P3且P2、P3互斥;

P2、P3分别与P4共享sem2,sem3信号量,初始值都为0,将signal()操作放在P2、P3最后,在P4开始对两信号量进行wait()操作,从而实现P4最执行。
进程前驱图:

操作系统实验三报告:同步问题

程序代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<fcntl.h>
int main()
{
	sem_t *sem1,*sem2,*sem3;
	pid_t p2,p3,p4;
	sem1=sem_open("sem1",O_CREAT,0666,0);
	sem2=sem_open("sem2",O_CREAT,0666,0);
	sem3=sem_open("sem3",O_CREAT,0666,0);
	p2=fork();
	if(p2<0)
	{
		perror("创建进程p2出错!");
	}
	if(p2==0)
	{
		sem_wait(sem1);
		printf("I am the process P2!\n");
		sem_post(sem1);
		sem_post(sem2);
	}
	if(p2>0)
	{
		p3=fork();
		if(p3<0)
		{
			perror("创建进程p3出错!");
		}
		if(p3==0)
		{
			sem_wait(sem1);
			printf("I am the process P3!\n");
			sem_post(sem1);
			sem_post(sem3);
		}
		if(p3>0)
		{
			printf("I am the process P1!\n");
			sem_post(sem1);
			p4=fork();
			if(p4<0)
			{
				perror("创建进程p4出错!");
			}
			if(p4==0)
			{
				sem_wait(sem2);
				sem_wait(sem3);
				printf("I am the process P4!\n");
				sem_post(sem2);
				sem_post(sem3);
			}
		}
	}
	sem_close(sem1);
	sem_close(sem2);
	sem_close(sem3);
	sem_unlink("sem1");
	sem_unlink("sem2");
	sem_unlink("sem3");
	return 0;
}

编译运行结果:

p2与p3等待p1结束后执行且互斥,P1结束后,P2和P3都有机会先执行,所以会出现两种结果。

操作系统实验三报告:同步问题

操作系统实验三报告:同步问题

  1. 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;

 

程序设计思路:

设互斥信号量mutex初始化为1互斥共享火车票余票数ticketCount,设信号量sold初始化为0来控制退票数,设信号量residue初始化为1000来控制售票数。

 

程序代码:

其中注释掉的部分为实现同步机制的代码

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int ticketCount=1000;
//sem_t *mutex=NULL;
//sem_t *sold=NULL;
//sem_t *residue=NULL;
void *saleT()
{
    int temp;
    for(int i=1;i<=1000;i++)
    {
        //sem_wait(residue);
        //sem_wait(mutex);
        temp=ticketCount;
        sched_yield();
        temp=temp-1;
        sched_yield();
        ticketCount=temp;
        if(i % 10 ==0)
        printf("售票%d张,剩余票数为:%d\n",i,ticketCount);
        //sem_post(sold);
        //sem_post(mutex);
    }
    return NULL;
}

void *returnT()
{
    int temp;
    for(int i=1;i<=100;i++)
    {
        //sem_wait(sold);
        //sem_wait(mutex);
        temp=ticketCount;
        sched_yield();
        temp=temp+1;
        sched_yield();
        ticketCount=temp;
        if(i % 10 ==0)
        printf("退票%d张,剩余票数为:%d\n",i,ticketCount);
        //sem_post(residue);
        //sem_post(mutex);
    }
    return NULL;
}
int main()
{
    //mutex=sem_open("mutex",O_CREAT,0666,1);
    //sold=sem_open("sold",O_CREAT,0666,0);
    //residue=sem_open("residue",O_CREAT,0666,1000);
    printf("剩余票数为:%d\n",ticketCount);
    pthread_t p1,p2;
    pthread_create(&p1,NULL,saleT,NULL);
    pthread_create(&p2,NULL,returnT,NULL);
    pthread_join(p1,NULL);
    pthread_join(p2,NULL);
    printf("剩余票数为:%d\n",ticketCount);
    //sem_close(mutex);
    //sem_close(sold);
    //sem_close(residue);
    //sem_unlink("mutex");
    //sem_unlink("sold");
    //sem_unlink("residue");
    return 0;
}

编译运行结果:

进行1000次售票,100次退票:

未添加同步机制

操作系统实验三报告:同步问题

添加同步机制

操作系统实验三报告:同步问题

 

进行1000次售票,500次退票:

未添加同步机制

操作系统实验三报告:同步问题

添加同步机制

操作系统实验三报告:同步问题

 

进行1000次售票,1002次退票:

未添加同步机制

操作系统实验三报告:同步问题

添加同步机制

操作系统实验三报告:同步问题

不添加同步机制的会导致结果错误

 

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

 

程序设计思路:

设信号量sem_empty表示空缓冲区数,设信号量sem_full表示已被装入缓冲区数,控制输出线程在输入线程输入字符后再输出,输入线程等输出线程输出使得有空缓冲区时再输入字符,从而不会造成未输出的缓冲区字符被复写。

 

程序代码:

#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>
#include <sched.h>

sem_t* sem_empty = NULL;
sem_t* sem_full = NULL;

char buf[10];
int i = 0;
int j = 0;

void *input_c(){
    while(1){
        sem_wait(sem_empty);
        i=(i+1)%10;
        scanf("%c",&buf[i]);
        sem_post(sem_full);
    }
}

void *print_c(){
    while(1){
        sleep(1);
        sem_wait(sem_full);
        j=(j+1)%10;
        printf("%c",buf[j]);
        sem_post(sem_empty);
    }
}

int main(){
    pthread_t p1,p2;
    sem_empty = sem_open("sem_empty", O_CREAT, 0666, 10);
    sem_full = sem_open("sem_full", O_CREAT, 0666, 0);
    pthread_create(&p1,NULL,read_char,NULL);
    pthread_create(&p2,NULL,print_char,NULL);
    pthread_join(p1,NULL);
    pthread_join(p2,NULL);
    sem_close(sem_empty);
    sem_close(sem_full);
    sem_unlink("sem_empty");
    sem_unlink("sem_full");
    return 0;
}

编译运行结果:

未添加同步机制:输出不从头开始且会不断输出重复的内容

操作系统实验三报告:同步问题

添加同步机制:输出的和输入的字符和顺序完全一致

操作系统实验三报告:同步问题

 

  1. 4)进程通信问题。阅读并运行共享内存、管道、消息队列三种机制的代码
(参见
https://www.cnblogs.com/Jimmy1988/p/7706980.html

        https://www.cnblogs.com/Jimmy1988/p/7699351.html

        https://www.cnblogs.com/Jimmy1988/p/7553069.html  )

实验测试

  1. 通过实验测试,验证共享内存的代码中,receiver能否正确读出sender发送的字符串?如果把其中互斥的代码删除,观察实验结果有何不同?如果在发送和接收进程中打印输出共享内存地址,他们是否相同,为什么?

实验代码:

sender.c:
/*
 * 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(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;
        }
    }
    shmdt(shm_ptr);
    return 0;
}

receiver.c:
#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);

            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;
        }
    }
    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)删除互斥访问内存相关的代码

删除互斥共享内存代码相关修改:

sender.c:

while(1)

{

    printf("\nNow, snd message process running:\n");

    printf("\tInput the snd message:  ");

    scanf("%s", shm_ptr);

    //if enter "end", then end the process

    if(0 == (strcmp(shm_ptr ,"end")))

    {

        printf("\nExit sender process now!\n");

        break;

    }

}

receiver.c:

while(1)

{

    printf("\nNow, receive message process running:\n");

    printf("\tThe message is : %s\n", shm_ptr);

 

    //if enter "end", then end the process

    if(0 == (strcmp(shm_ptr ,"end")))

    {

        printf("\nExit the receiver process now!\n");

        break;

    }

    sleep(3);

}

编译运行结果:

receiver不断输出sender发送的一条消息,直到sender输入新的消息,receiver改变其输出

操作系统实验三报告:同步问题

3)打印Sender和Receiver进程*享内存的地址

发送和接收进程中打印输出共享内存地址不相同,

原因:

  1. 第一次创建完共享内存时,shmat()函数的作用是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,为空表示让系统来选择共享内存的地址。
  2. 地址空间随机化使每次加载到内存的程序起始地址会随机变化。

操作系统实验三报告:同步问题

 

b)有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?

无名管道pipe.c:

#include <stdio.h>
#include <unistd.h>     //for pipe()
#include <string.h>     //for memset()
#include <stdlib.h>     //for exit()
int main()
{
    int fd[2];
    char buf[20];
    if(-1 == pipe(fd))
    {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    write(fd[1], "hello,world", 12);
    memset(buf, '\0', sizeof(buf));
    read(fd[0], buf, 12);
    printf("The message is: %s\n", buf);
    return 0;
}

 

有名管道:

fifo_send.c:
/*
 *File: fifo_send.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <fcntl.h>
#define FIFO "/tmp/my_fifo"
int main()
{
    char buf[] = "hello,world";

    //`. check the fifo file existed or not
    int ret;
    ret = access(FIFO, F_OK);
    if(ret == 0)    //file /tmp/my_fifo existed
    {
        system("rm -rf /tmp/my_fifo");
    }
    //2. creat a fifo file
    if(-1 == mkfifo(FIFO, 0766))
    {
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }
    //3.Open the fifo file
    int fifo_fd;
    fifo_fd = open(FIFO, O_WRONLY);
    if(-1 == fifo_fd)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    //4. write the fifo file
    int num = 0;
    num = write(fifo_fd, buf, sizeof(buf));
    if(num < sizeof(buf))
    {
        perror("write");
        exit(EXIT_FAILURE);
    }
    printf("write the message ok!\n");
    close(fifo_fd);
    return 0;
}

fifo_rcv.c:
/*
 *File: fifo_rcv.c
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <fcntl.h>
#define FIFO "/tmp/my_fifo"
int main()
{
    char buf[20] ;
    memset(buf, '\0', sizeof(buf));
    //`. check the fifo file existed or not
    int ret;
    ret = access(FIFO, F_OK);
    if(ret != 0)    //file /tmp/my_fifo existed
    {
        fprintf(stderr, "FIFO %s does not existed", FIFO);
        exit(EXIT_FAILURE);
   }
    //2.Open the fifo file
    int fifo_fd;
    fifo_fd = open(FIFO, O_RDONLY);
    if(-1 == fifo_fd)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    //4. read the fifo file
    int num = 0;
    num = read(fifo_fd, buf, sizeof(buf));
    printf("Read %d words: %s\n", num, buf);
   close(fifo_fd);
    return 0;
}

运行结果:

无名管道实现了同步机制:

进程在向管道内存中读写数据之前,会检查内存是否被锁定及是否还有剩余空间或数据,并在操作前会进行上锁操作,实现互斥访问。

操作系统实验三报告:同步问题

 

有名管道实现了同步机制:

读写进程都为阻塞状态,先执行的进程都会阻塞等待,待另一个进程执行后才正常执行。

通过,fifo_fd=open(FIFO,O_RDONLY)设置阻塞状态,通过fifo_fd=open(FIFO,O_RDONLY | O_NONBLOCK)设置为非阻塞状态。

正常运行:

操作系统实验三报告:同步问题

读写进程都是非阻塞

操作系统实验三报告:同步问题

读进程阻塞,写进程非阻塞操作系统实验三报告:同步问题

读进程非阻塞,写进程阻塞

操作系统实验三报告:同步问题

 

c)消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?

实验代码:

server.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <signal.h>

#define BUF_SIZE 128

//Rebuild the strcut (must be)
struct msgbuf
{
    long mtype;
    char mtext[BUF_SIZE];
};
int main(int argc, char *argv[])
{
    //1. creat a mseg queue
    key_t key;
    int msgId;
    
    key = ftok(".", 0xFF);
    msgId = msgget(key, IPC_CREAT|0644);
    if(-1 == msgId)
    {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    printf("Process (%s) is started, pid=%d\n", argv[0], getpid());

    while(1)
    {
        alarm(0);
        alarm(600);     //if doesn't receive messge in 600s, timeout & exit
        struct msgbuf rcvBuf;
        memset(&rcvBuf, '\0', sizeof(struct msgbuf));
        msgrcv(msgId, &rcvBuf, BUF_SIZE, 1, 0);                
        printf("Receive msg: %s\n", rcvBuf.mtext);
        
        struct msgbuf sndBuf;
        memset(&sndBuf, '\0', sizeof(sndBuf));

        strncpy((sndBuf.mtext), (rcvBuf.mtext), strlen(rcvBuf.mtext)+1);
        sndBuf.mtype = 2;

        if(-1 == msgsnd(msgId, &sndBuf, strlen(rcvBuf.mtext)+1, 0))
        {
            perror("msgsnd");
            exit(EXIT_FAILURE);
        }
            
        //if scanf "end~", exit
        if(!strcmp("end~", rcvBuf.mtext))
             break;
    }     
    printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());
    return 0;
}

client.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <signal.h>
#define BUF_SIZE 128
//Rebuild the strcut (must be)
struct msgbuf
{
    long mtype;
    char mtext[BUF_SIZE];
};
int main(int argc, char *argv[])
{
    //1. creat a mseg queue
    key_t key;
    int msgId;
        printf("THe process(%s),pid=%d started~\n", argv[0], getpid());
    key = ftok(".", 0xFF);
    msgId = msgget(key, IPC_CREAT|0644);
    if(-1 == msgId)
    {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    //2. creat a sub process, wait the server message
    pid_t pid;
    if(-1 == (pid = fork()))
    {
        perror("vfork");
        exit(EXIT_FAILURE);
    }
    //In child process
    if(0 == pid)
    {
        while(1)
        {
            alarm(0);
            alarm(100);     //if doesn't receive messge in 100s, timeout & exit
            struct msgbuf rcvBuf;
            memset(&rcvBuf, '\0', sizeof(struct msgbuf));
            msgrcv(msgId, &rcvBuf, BUF_SIZE, 2, 0);                
            printf("Server said: %s\n", rcvBuf.mtext);
        }
                exit(EXIT_SUCCESS);
    else    //parent process
    {
        while(1)
        {
            usleep(100);
            struct msgbuf sndBuf;
            memset(&sndBuf, '\0', sizeof(sndBuf));
            char buf[BUF_SIZE] ;
            memset(buf, '\0', sizeof(buf));
            printf("\nInput snd mesg: ");
            scanf("%s", buf);
            strncpy(sndBuf.mtext, buf, strlen(buf)+1);
            sndBuf.mtype = 1;
            if(-1 == msgsnd(msgId, &sndBuf, strlen(buf)+1, 0))
            {
                perror("msgsnd");
                exit(EXIT_FAILURE);
            }            
            //if scanf "end~", exit
            if(!strcmp("end~", buf))
                break;
        }
        printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());
    }
    return 0;
}

运行结果:

int msgrcv(int msqid, void *ptr, size_t length, long type, int flag);

int msgsnd(int msqid, const void *ptr, size_t length, int flag);

以上两个函数实现客户端与服务端之间的消息传输,末尾的参数flag为0表示以阻塞方式,设置IPC_NOWAIT 表示以非阻塞方式

正常情况:

操作系统实验三报告:同步问题

客户端不阻塞,服务器端阻塞:

操作系统实验三报告:同步问题

客户端阻塞,服务器端不阻塞:

操作系统实验三报告:同步问题