实验  进程通信、内存映射与同步的综合理解

要求所有练习保留题目要求,在题目要求后面作答:

代码要求有注释,代码中适当标注关键代码为红色。

要有运行结果的截图。

 每题最后应该有对程序的适当分析和总结!

注意格式排版,内容分析注意条目,展开清楚地阐述。

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

 

执行结果:

注意在写管道进程未打开之前读管道进程会阻塞自己进行等待,同时读管道进程未打开之前写管道进程会阻塞自己进行等待。

 

  1. 试对课本【例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仅仅是正常变量,是双方都具有的副本。

补充(新建的匿名虚存区):