实验四 进程通信、内存映射与同步的综合理解
【要求】所有练习题保留题目要求,在题目要求后面作答:
代码要求有注释,代码中适当标注关键代码为红色。
要有运行结果的截图。
每题最后应该有对程序的适当分析和总结!
注意格式排版,内容分析注意列条目,展开清楚地阐述。
1.改写课本例题,分别利用匿名管道与命名管道(文件命名为自己名字全称)实现父子进程间通信和非亲缘关系的两个进程通信,发送的消息为i am your name。注意:重点是对管道通信给出各种同步、阻塞情况的分析说明。
匿名管道(亲缘进程之间进行通信(父子间进行通信)(一个程序)):
代码:
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int pipe_fd[2];
if(pipe(pipe_fd)<0)
{
//pipe函数在内核开辟一块用于通信的缓冲区(缓冲区没有名字,故称匿名管道)
printf("pipe create error\n");
return -1;
}//if
printf("pipe create success\n");
if (fork()>0) //father
{
int r;
char buf[15+1];
printf("***Want to read from son\n");
r=read(pipe_fd[0],buf,15); //从管道读端读取数据到buf,当管道中没有数据时会进入阻塞状态,等待管道中读入数据
buf[r]=0;
printf("***FATHER Got strings: %s\n",buf);
}
else //son
{
const char *test="I am MJG !";
printf("Son sleep:\n");
sleep(5);
/*这里子进程的睡眠是为了验证read函数会不会阻塞,如果阻塞父进程会受到下面读入的数据,否则父进程读不到数据*/
printf("SonWrite after sleep: %s\n",test);//2
write(pipe_fd[1],test,strlen(test));
}
close(pipe_fd[0]);
close(pipe_fd[1]);
} //main
执行结果:
命名管道:
代码:
读管道:
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
if (argc !=2)
{
printf("not enough params,give pipefile name\n");
exit(1);
}
char *pipefile;
pipefile=argv[1];
char buf[100];
int fd,i;
printf("read open namedpipe!!\n");
//以只读的方式打开文件,当写管道进程不打开时则阻塞等待,此时进程只输出了read open namedpipe!!一句话
fd=open(pipefile,O_RDONLY,0);
if (fd<0) //判断是否打开成功
{
printf("no such namedpipe file !!\n");
exit(-1);
}
printf("OK!namedpipe opened for read!\n");
i=read(fd,buf,100);
buf[i]=0;
printf("OK!readed from namedpipe: %s!\n",buf);
return 0;
}
写管道:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
if (argc !=2)
{
printf("not enough params,give pipefile name\n");
exit(1);
}
char *pipefile;
pipefile=argv[1]; //管道名
int fd;
char *teststr="I am MJG!";
printf("OPEN namedpipe--%s for write!\n",pipefile);
//以只写的方式打开文件,当读管道进程不打开时则阻塞等待,此时进程只输出了OPEN namedpipe--MJG for write!一句话
fd=open(pipefile,O_WRONLY,0);
printf("OK!namedpipe--%s opened for write!\n",pipefile);
sleep(5);//故意让写的人先不要写,读的时候则会被堵塞,read和write都是同时进行的,一个sleep则另一个堵塞等待
write(fd,teststr,strlen(teststr));
printf("OK!namedpipe write successfully!\n");
exit(0);
}
命令行下:
编译:
deepin@deepin-MJG:~/Desktop/MJG$ gcc testch04.1.c -o testch04.1
deepin@deepin-MJG:~/Desktop/MJG$ gcc testch04.2.c -o testch04.2
创建管道文件:
deepin@deepin-MJG:~/Desktop/MJG$ mkfifo MJG
指定管道进行通信:
deepin@deepin-MJG:~/Desktop/MJG$ ./testch04.1 MJG
deepin@deepin-MJG:~/Desktop/MJG$ ./testch04.2 MJG
如果不指定管道则会报错(程序内编写的错误结果输出):
not enough params,give pipefile name
执行结果:
注意在写管道进程未打开之前读管道进程会阻塞自己进行等待,同时读管道进程未打开之前写管道进程会阻塞自己进行等待。
- 试对课本【例5-21】改写,一方不断向管道写入字符A,另一方以每隔3秒读1024个字符的速度从管道读数据,体验并总结管道通信时的读写双方的同步效果。
代码:
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
//通过调用pipe函数创建管道
//pipe函数在内核开辟一块用于通信的缓冲区(缓冲区没有名字,故称匿名管道)
//filedes[0]指向管道的读端;filedes[1]指向管道的写端
int pipe_fd[2];
//返回0代表创建失败,一般不会出现该问题
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}//if
printf("pipe create success\n");
//因为采用的是子进程写入数据,父进程每隔三秒读取一次,所以存在如下情况:在写入时数据缓存区满则等待,父进程执行完毕读入操作后结束进程,则进程则不会一直运行下去。
//如果选择父进程一直写入则不会出现该问题(课本上例题则是选择父进程一直写入)
while(1)
{
if (fork()>0) //father
{
//父进程每隔三秒读数据
sleep(3);
close(pipe_fd[1]); //关闭写数据管道一端
int r;
char buf[1024+1];
printf("***Want to read from son\n");
r=read(pipe_fd[0],buf,1024); //从管道读端读取数据到buf
buf[r]=0;
printf("***FATHER Got strings: %s\n",buf);
}
else //son
{
//子进程不断写入数据
close(pipe_fd[0]); //关闭读数据管道一端
char buf[] = "A";
while(1)
{
write(pipe_fd[1], buf, sizeof(buf[0]));
}
}
//sleep(3);
}
}
执行结果:
将每次产生的字符数进行统计(利用Word):
3.试对课本【例5-24】改写,编程实现3个进程通信,一个负责写入,另外两个负责读出,体验3方的通信过程及效果。
代码:
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
//wait(0)同步(等待子进程结束才继续往下运行,这样不会导致数据出错)需要加入以下头文件:
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
int r,i,p1,p2,fd[2];
char buf[10],s[50];
pipe(fd); //创建管道
while((p1=fork())==-1); //创建子程序失败时,循环
if(p1==0)
{
//首先不加锁,体验交替输出,然后加锁,实现同步读取(先读A后读B)
lockf(fd[0],1,0);
printf("child1 process p1 Read!\n");
for(i=1; i<=10; i++)
{
if((r=read(fd[0],s,10))==-1)
printf("can’t read pipe\n");
else
printf("%s",s);
}
printf("child1 Read end.\n");
lockf(fd[0],0,0);
exit(0);
} //son1
else
{
while((p2=fork())==-1);
//创建子程序失败时,循环
if(p2==0)
{
lockf(fd[0],1,0);
printf("child2 process p1 Read!\n");
for(i=1; i<=10; i++)
{
if((r=read(fd[0],s,10))==-1)
printf("can’t read pipe\n");
else
printf("%s",s);
}
printf("child2 Read end.\n");
lockf(fd[0],0,0);
exit(0);
} //son2
//father write
lockf(fd[1],1,0);
sprintf(buf, "AAA\n"); //防止乱码这里简写AAA/BBB
printf("father write(AAA):\n");
for(i=1; i<=10; i++) //同步时分给child1/2读
{
write(fd[1],buf,10); //把buf中的字符写入管道
sleep(1);
}
sprintf(buf, "BBB\n"); //防止乱码这里简写AAA/BBB
printf("father write(BBB):\n");
for(i=1; i<=10; i++) //同步时分给child1/2读
{
write(fd[1],buf,10); //把buf中的字符写入管道
sleep(1);
}
printf("father write end.\n");
lockf(fd[1],0,0);
wait(0);
wait(0);
exit(0);
}
}
结果:
一、对读缓冲区没有加锁,子进程child1和子进程child2根据调度依次对缓冲区内内容进行读取(无法判断A与B中的某一个是哪个子进程读取的),根据下图我们可以发现child1在前半部分调度多,出现了child read end,在此之后都是child2在读取缓冲区内内容。
二、对读缓冲区进行加锁(与上图类似,但内部实现不同,我们可以发现child read end 在前面,因此前半部分对A的读取都是child1执行的操作,后面的B全部是child读取的。)
4.试对课本【例5-26】映射匿名虚存区实现共享内存 进行改写,实现父亲先计算累加100的和后写给孩子,然后孩子读取求和结果后输出。(提示:可定义一个初值为0的信号量控制父子进程的执行顺序。)
代码:
靠匿名映射内存实现共享(没有read,write(读写管道),直接通过内存来进行,操作完成后直接从内核态切入到用户态,效率快):
#include <sys/mman.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#define N 100000
int main()
{
//设置信号量
sem_t x;
sem_init(&x, 0, 1);
int i,sum,fd;
int *result= mmap (0,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,0,0);
int pid=fork();
if (pid==0) //子进程
{
//等待父进程完成加和操作
//sem_wait(&x);
printf("child %d write: result =%d,sum=%d\n",getpid(),*result,sum);
//sem_post(&x);
}
else //父进程
{
//sem_wait(&x);
for(sum=0,i=1; i<N; i++)
sum+=i;
*result=sum;
printf("farther %d : result =%d,sum=%d\n",getpid(),*result,sum);
//sem_post(&x);
}
printf("we’re going to sleep 20 seconds,see us in /proc \n");
sleep(20);
if (pid>0)
munmap(result,4);
sleep(20);
}//main
结果:
如果我们不设置信号量保证顺序问题,则可能会出现子进程首先调度获取内存值结果(此时为0)(由于调度可能首先调度到父进程,我们将求和值设的大一些,是处理机发生调度调度到子进程,此时result为0):
增加信号量后(父进程求和,子进程读取):
#include <sys/mman.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#define N 100
int main()
{
//设置信号量
sem_t x;
sem_init(&x, 0, 1);
int i,sum,fd;
int *result= mmap (0,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,0,0);
int pid=fork();
if (pid==0) //子进程
{
//等待父进程完成加和操作
sem_wait(&x);
printf("child %d write: result =%d,sum=%d\n",getpid(),*result,sum);
sem_post(&x);
}
else //父进程
{
sem_wait(&x);
for(sum=0,i=1; i<N; i++)
sum+=i;
*result=sum;
printf("farther %d : result =%d,sum=%d\n",getpid(),*result,sum);
sem_post(&x);
}
printf("we’re going to sleep 20 seconds,see us in /proc \n");
sleep(20);
if (pid>0)
munmap(result,4);
sleep(20);
}//main
再次运行程序没有问题,结果如下:
分析比较result与sum:result是指针形式,读取的是内存地址(共享的,唯一的),而sum仅仅是正常变量,是双方都具有的副本。
补充(新建的匿名虚存区):