实验三 同步问题
16281100 雷兵
- 实验目的
- 系统调用的进一步理解。
- 进程上下文切换。
- 同步的方法。
- 实验内容
- 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都有机会先执行,所以会出现两种结果。
- 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次退票:
未添加同步机制
添加同步机制
不添加同步机制的会导致结果错误
- 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;
}
编译运行结果:
未添加同步机制:输出不从头开始且会不断输出重复的内容
添加同步机制:输出的和输入的字符和顺序完全一致
- 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 )
实验测试
- 通过实验测试,验证共享内存的代码中,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进程中共享内存的地址
发送和接收进程中打印输出共享内存地址不相同,
原因:
- 第一次创建完共享内存时,shmat()函数的作用是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,为空表示让系统来选择共享内存的地址。
- 地址空间随机化使每次加载到内存的程序起始地址会随机变化。
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 表示以非阻塞方式
正常情况:
客户端不阻塞,服务器端阻塞:
客户端阻塞,服务器端不阻塞: