进程间通信

进程间通信

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核在内核中开辟一块缓冲区,进程1把数据从用户空间考到内核缓存区,进程2再内核缓存区把数据读走,内核提供这种机制称为进程间通信(IPC,InterProcess Communication)。

在进程间完成数据传递需要借助操作系统提供的特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:

  • 管道(使用简单)
  • 信号(开销最小)
  • 共享映射区(无血缘关系)
  • 本地套接字(最稳定)

管道

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

  • 1 其本质是一个伪文件(实为内核缓冲区)
  • 2 由两个文件描述符引用,一个表示读端,一个表示写端。
  • 3 规定数据从管道的写端流入管道,从读端流出。

管道原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

管道的局限性

  • 数据自己读不能自己写;
  • 数据一旦被读走,便不再管道中存在,不可反复读取;
  • 由于管道采用半双工通信方式,因此,数据只能在一个方向上流动。(发收)
  • 只能在有公共祖先的进程间使用管道。

pipe函数

用于创建管道,特点是单向流动

int pipe(int pipefd[2]); 成功:0;失败:-1,设置erro

函数调用成功返回r/w两个文件描述符无需open,但需手动close。规定,fd[0]->r; fd[1]->w;就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实在读写内核缓存区。(这里的文件描述符为什么不是指针类型?)

管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子间进程通信呢?通常采用如下步骤:

父子间进程利用管道通信

解释

  • 1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端
  • 2.父进程调用fork函数创建子进程,那么子进程也有两个文件描述符指向 同一管道
  • 3.父进程关闭管道读端子进程关闭管道写端父进程可以从管道中写入数据,子进程将管道中的数据读出

由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。  

过程反过来让父进程去读数据,子进程去写数据,情况又是怎样?

nxt:由于程序运行先从父进程开始,然后通过fork函数创建一个子进程;程序进入父进程先让父进程休眠,子进程占用cpu开始写数据,数据写完休眠一会,父进程开始读取数据

练习:父子进程使用管道通信,父写入字符串,子进程读出并,打印到屏幕。 【pipe.c】

思考:为甚么,程序中没有使用sleep函数,但依然能保证子进程运行时一定会读到数据呢?

//pipe.c
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>

void sys_err(const char *str){
        perror(str);
        exit(1);
}

int main(void){
        pid_t pid;
        char buf[1024];
        //定义两个文件描述符fd[0]、fd[1]
        int fd[2];
        char *p = "test for pipe\n";

        if(pipe(fd) == -1){
                sys_err("pipe");
        }
        pid = fork();
        printf("fork pid %d\n",pid);
        if(pid < 0){
                sys_err("fork err");
        }else if(pid == 0){//son
                close(fd[1]);//close write fd
                printf("the child process read data\n");
                sleep(3);
                int len = read(fd[0],buf,sizeof(buf));//从管道读数据
                write(STDOUT_FILENO,buf,len);//将读取数据写到屏幕
                close(fd[0]);//关闭读文件描述符
        }else{//parnet
                close(fd[0]);//关闭读文件描述符
                write(fd[1],p,strlen(p));//父进程向管道写数据
                //sleep(3);
                printf("parnet write data\n");
                wait(NULL);//回收子进程
                close(fd[1]);//关闭写文件描述符
        }
        return 0;

}

运行结果

f[0]为读文件描述符,f[1]为写文件描述符,父进程关闭读文件描述符f[0],通过写文件描述符f[1]向管道里写数据,写完关闭f[1];子进程关闭写文件描述符f[1],通过读文件描述符f[0]从管道里读数据,读完关闭f[0],从而 实现父子进程间通信;

练习:使用管道实现父子进程间通信,完成:ls | wc –l。假定父进程实现ls,子进程实现wc。

ls命令正常会将结果集写出到stdout,但现在会写入管道的写端;wc –l 正常应该从stdin读取数据,但此时会从管道的读端读。   

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
        pid_t pid;
        int fd[2];
        pipe(fd);//?? r,w
        pid = fork();//创建子进程
        printf("fork child process pid %d\n",pid);
        if(pid == 0){//child
                close(fd[1]);
                printf("child porcess\n");
                dup2(fd[0],STDIN_FILENO);//erro return -1
                execlp("wc","wc","-1",NULL);
        }else{
                close(fd[0]);
                printf("parnet process \n");
                dup2(fd[1],STDOUT_FILENO);//dup2(fd,STDOUT_FILENO);//dup2(3,1); fd,stdout重定向//int dup2(int oldfd, int newfd);
                execlp("ls","ls",NULL);//在进程中启动其他进程
        }
        return 0;

}

运行结果

练习:使用管道实现兄弟进程间通信兄:ls  弟: wc -l  等待回收子进程

要求,使用“循环创建N个子进程”模型创建兄弟进程,使用循环因子i表示。注意管道读写行为。

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main(){
        pid_t pid;
        int fd[2],i;

        pipe(fd);
        for(i =0;i < 2; i++){
                if((pid = fork()) == 0){//判断当前进程pid=0为子进程,跳出当前循环,不创建进程 
                        break;
                }

        }
        printf("fork pid %d\n",pid);
        printf("I am current process pid %d\n",getpid());
        if(i == 0){
                printf("I am %dth child process,pid == %d\n",i,getpid());
                close(fd[0]);
                dup2(fd[1],STDOUT_FILENO);//重定向,将文件描述符STDOUT_FILENO赋给fd[1]
                execlp("ls","ls",NULL);//在进程开启其他进程
        }else if(i == 1){
                printf("I am %dth child process,pid == %d\n",i,getpid());
                close(fd[1]);
                dup2(fd[0],STDIN_FILENO);
                execlp("wc","wc","-l",  NULL);
        }else{
                printf("process,pid == %d\n",getpid());
                close(fd[0]);
                close(fd[1]);
                for(i=0;i<2;i++){
                        printf(" wait number %dth process\n",i);
                        wait(NULL);//回收子进程

                }
        }
        return 0;
}

运行结果

启动程序之后,父进程创建两个进程,父进程创建 子进程之后,子进程作为新的父进程再次创建新的子进程,两个进程间通信本质还是父子进程间通信;获取当前父进程的pid,之后创建子进程

测试:是否允许,一个pipe有一个写端,多个读端呢?是否允许有一个读端多个写端呢? 【pipe3.c】

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<string.h>

int main(){
        pid_t pid;
        int fd[2],i,n;
        char buf[1024];
        //创建管道,及检查返回值
        int ret =pipe(fd);
        printf("pipe return value %d\n",ret);

        if(ret == -1){
                perror("pipe error");
                exit(1);
        }
        printf("the currnet process %d\n",getpid());
        //循环创建子进程
        for(i =0;i < 2; i++){
                if((pid = fork()) == 0){
                        break;
                }else if(pid == -1){
                        perror("pipe error");
                        exit(1);
                }
        }
        //父进程创建的 子进程会执行主函数程序
        if(i == 0){
                printf("the %dth child process pid %d\n",i,getpid());
                close(fd[0]);
                write(fd[1],"1 hello\n",strlen("1 hello\n"));
        }else if(i == 1){
                 printf("the %dth child process pid %d\n",i,getpid());
                close(fd[0]);
                read(fd[1],"2 world\n",strlen("2 word\n"));
        }else{
                 printf("the %dth child process pid %d\n",i,getpid());
                close(fd[1]);
                n = read(fd[0],buf,1024);
                write(STDOUT_FILENO,buf,n);
                //执行完主函数,父进程回收子进程
                for(i=0;i<2;i++)
                {
                         printf("wait the %dth child process\n",i);
                         wait(NULL);//回收子进程
                }
        }
        // 父进程结束,父进程是否会被回收?
        return 0;
}

运行结果

从测试情况看,一个pipe不允许一个读端多个写端

课后作业: 统计当前系统中进程ID大于10000的进程个数。

 

管道缓冲区大小

使用ulimit -a命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。通常为:pipezie (512bytes,-p)8

也可使用fpathconf函数,借助参数  选项来查看。使用该宏引入头文件<unistd.h>

long fpathconf(int fd,int name);
//成功:返回管道的大小          失败:-1,设置erro

管道的优劣

  • 优点:简单,相比信号,套接字实现进程间通信,简单很多;
  • 缺点:1 只能单向通信双向通信需建立两个管道
  •             2 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用FIFO有名管道解决。

FIFO

FIFO常称为为命名管道,以区分管道(pipe)。管道只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。

FIFO是linux基础文件类型中的一种,但,FIFO文件在磁盘上没有数据,仅仅用来标识内核中一条通道各进程可以打开这个文件进行read/write,实际上是读写内核通道,这样就实现了进程间通信。

创建方式:

  1. 命令:mkfifo管道名
  2. 库函数:int mkfifo(const char *pathname,mode_t mode);成功:返回0,失败:-1

一旦使用mkfifo创建一个FIFO,就可使用open打开它,常见文件I/O函数可用于fifo。如close、read、write、unlink等

//fifo_test_w.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main(int argc,char *argv[]){
        if(argc != 2){
                printf("./a.out fifoname\n");
                return -1;

        }
        //当前目录有一个myfifo文件
        //打开fifo文件
        int fd = open(argv[1],O_WRONLY);
        //写
        char buf[256];
        int num =1;
        while(1){
                memset(buf,0x00,sizeof(buf));
                sprintf(buf,"xiaoming%04d",num++);
                write(fd,buf,strlen(buf));
                sleep(1);
        }
        //关闭描述符
        close(fd);
        return 0;
}

在一个进程中打开一个文件,并向文件里写数据。

打开另一个进程,从上述已经被写入的文件中读取数据,进而实现了两个不相关进程之间的通信。

//fifo_test_r.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main(int argc,char *argv[]){
        if(argc != 2){
                printf("./a.out fifoname\n");
                return -1;

        }
        //当前目录有一个myfifo文件
        //打开fifo文件
        int fd = open(argv[1],O_RDONLY);
        //写
        char buf[256];
        int ret ;
        while(1){
                ret = read(fd,buf,sizeof(buf));
                //sleep(1);
                if(ret > 0){
                        printf("read:%s\n",buf);
                }else{
                        break;
                }
        }
        //关闭描述符
        close(fd);
        return 0;
}

 

共享存储映射

文件进程间通信

使用文件也可完成IPC,理论依据是fork之后,父子进程共享文件描述符,即共享打开文件

练习:编程测试,父子进程共享打开的文件。借助文件进行进程间通信。    

//fork_share_fd.c
#include<stdio.h>
#include<unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(){
        pid_t pid;
        int fd1,fd2;
        char buf[1024];
        char *str = "----------test for shared fd in parnet and chile process ------";
        pid = fork();
        if(pid < 0){
                perror("fork");
                exit(1);
        }else if(pid > 0){
                fd2 = open("test.txt",O_RDWR);
                if(fd2 < 0){
                        perror("open erro");
                        exit(1);
                }
                sleep(1);
                int len = read(fd2,buf,sizeof(buf));
                write(STDOUT_FILENO,buf,len);
                wait(NULL);
                printf("I am parnet pid = %d,parnet ID = %d\n",getpid(),getppid());
        }else if(pid == 0){
                fd1 = open("test.txt",O_RDWR);
                if(fd1 < 0){
                        perror("open error");
                        exit(1);
                }
                write(fd1,str,strlen(str));
                printf("I am child pid = %d,child ID = %d\n",getpid(),getppid());
        }
        return 0;

test.txt文件不存在,出现错误

新建test.txt文件,或者可以送I/O函数,若文件不存在则自动创建文文件

思考,无血缘关系的进程可以打开同一个文件进行通信吗?为什么?

存储映射I/O

存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间的一个缓冲区向映射。当从缓冲区中取数据,就相当于读文件中的相应字节。类似于,将数据存入缓冲区,则相应的字节就自动写入文件,这样可在不使用read和write函数的情况下,使用地址(指针)完成I/O操作。

首先应通知内核,将一个指定文件映射到存储区域中。该映射工作可通过mmap函数来实现。借助指针访问磁盘文件,

mmap函数

void *mmap(void *addr,size_t lenght,int prot,int flags,int fd,off_t offset);
//返回:成功:返回创建的映射区首地址,失败:MAP_FAILED宏

参数

  1. addr:建立的映射区的首地址,由Linux内核指定。使用时,直接传递NULL;
  2. length:欲创建映射区的大小;
  3. prot:映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE;
  4. flags:标志位参数(用于设定更新物理区域、设置共享、创建匿名映射区);
  5.            MAP_SHARED:会将映射区的操作反映到物理设备(磁盘)上;
  6.           MAP_PRIVATE:映射区所做的修改不会反映大物理设备
  7. fd:用来建立映射区的文件描述符;
  8. offset:映射文件的偏移(4k的整数倍)截取文件的一部分映射到内存,产生一个空间;
//mmap_test.c
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>
#include<fcntl.h>
#include <sys/mman.h>
int main(void){
        int len,ret;
        char *p = NULL;
        int fd = open("mytest.txt",O_CREAT|O_RDWR,0644);//打开文件,返回文件描述符fd
        if(fd < 0){
                perror("open erro:");
                exit(1);
        }
        len = ftruncate(fd,4);//创建映射区大小,ftunrcate(文件描述符,需要大小):获取文件大小
        if(len == -1){
                perror("ftruncate erro:");
                exit(1);
        }
        //共享存储映射实现进程间通信
        p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//mmap函数调用成功返回创建的映射区首地址
        if(p == MAP_FAILED){
                perror("mmap erro:");
                exit(1);
        }
        strcpy(p,"asd");//写数据
        munmap(p,4);
        close(fd);
        return 0;
}

 

munmap函数

同malloc函数申请内存空间类似,mmap建立的映射区在使用结束后,也应调用类似free函数来释放;

int munmmap(void *addr,size_t length);
//成功:0,失败:-1

借鉴malloc和free函数原型,尝试装自定义函数smalloc,sfree来完成映射区的建立和释放。思考函数接口该如何设计?

利用mmap函数创建映射区,但这里文件描述符fd=-1是什么意思?

//smalloc.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

void *smalloc(size_t size){
        void *p;
        p = mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
        if(p == MAP_FAILED){
                p =NULL;
        }
        return p;
}
void sfree(void *ptr,size_t size){
        munmap(ptr,size);

}
int  main(void){
        int *p;
        pid_t pid;
        p = smalloc(4);
        pid = fork();

        if(pid == 0){
                *p = 2000;
                printf("child *p =%d\n",*p);
        }else if(pid > 0){
                printf("parnet sleep 1 s\n");
                sleep(1);
                printf("parnet *p = %d\n",*p);
        }
        sfree(p,4);
        return 0;
}

运行结果

asd

//mmap.c
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>

void sys_err(char *str){
        perror(str);
        exit(1);
}

int main(void){
        char *mem;
        int len =0;

        int fd = open("hello244",O_RDWR|O_CREAT|O_TRUNC,0644);
        if(fd < 0)
                sys_err("open error");
        ftruncate(fd,20);//???/

        printf("the length of file = %d\n",len);

        mem = mmap(NULL,20,PROT_WRITE,MAP_SHARED,fd,0);//返回创建内存映射区的首地址,
        if(mem == MAP_FAILED)
                sys_err("mmap err:");
        close(fd);

        strcpy(mem,"xxx");//相当于操作指针,给该内存空间赋值
        printf("%s\n",mem);

        if(munmap(mem,4)<0)
                sys_err("munmap");
        return 0;
}
  

mmap注意事项

思考:

  1. 可以open的时候,O_CREAT一个新文件来创建映射区吗?
  2. 如果open时,O_RDONLY,mmap时PROT参数指定PROT_READ|PROT_WRITE会怎么样?
  3. 文件描述符先关闭,对mmap映射有没有影响?
  4. 文件偏移为1000会怎么样?
  5. 对mem越界操作会怎么样?
  6. 如果mem++,munmap可否成功?
  7. mmap什么情况下会调用失败?
  8. 如果不检测mmap的返回值,会怎么样?
     

解答:

  1. 创建映射区的过程中,隐含着一次对映射文件的读操作文件权限至少有PROT_READ,只有O_CREAT权限是不够的。
  2. 不能,当MAP_SHARED时,要求:映射区的权限应<=文件打开的权限(处于对映射区的保护),而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制
  3. 映射区的释放与文件关闭无关,只要映射建立成功文件可以立即关闭
  4. 文件偏移量必须为4k的整数倍,ls - l -h 文件名,offset文件偏移量4k的整数倍,映射有MMU完成映射,MMU单位为4k
  5. 注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小,mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
  6. munmap传入的地址一定是mmap的返回地址,坚决杜绝指针++操作;
  7. mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功在进行后续操作;

mmap父子进程通信

父子等有血缘关系的进程之间也可以通过mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时指定对应的标志位参数flags:

MAP_PRIVATE:(私有映射)父子进程各自独占映射区;

MAP_SHARED:(共享映射)父子间共享映射区;

练习:父进程创建映射区,然后fork子进程子进程修改映射区内容,而后,父进程读取映射区内容,查验是否共享

//fork_mmap.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;
int main(void){
        int *p;
        pid_t pid;
        int fd;
        fd = open("temp",O_RDWR|O_CREAT|O_TRUNC,0644);
        if(fd < 0){
                perror("open error");
                exit(1);
        }
        unlink("temp");//
        ftruncate(fd,4);//
        p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//父子进程共享缓存区
        //p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);//父子进程独享缓存区
        if(p == MAP_FAILED){
                perror("mmap error");
                exit(1);
        }
        close(fd);

        pid = fork();
        if(pid == 0){
                *p =2000;
                var =1000;
                printf("child *p=%d,var = %d\n",*p,var);
        }else{
                sleep(1);
                printf("parnet *p =%d,var =%d\n",*p,var);
                wait(NULL);

                int ret = munmap(p,4);
                if(ret == -1){
                        perror("munmap error");
                        exit(1);
                }
        }
        return 0;
}
   
共享缓存区
父子进程各自独享缓存区

父子进程共享缓存区,(父进程休眠1秒,子进程占用CPU)子进程更改缓存区内*p的值为2000,子进程结束之后,父进程占用cpu,*p值仍为2000;而父子进程各自独享缓存区,子进程更改缓存区内*p的值,对父进程中*p值没有影响。

结论:父子进程共享:1打开的文件,2 mmap建立映射区(但必须要使用MAP_SHARED

匿名映射

使用映射区来完成文件读写操作十分方便,父子间通信比较容易实现,但缺点是每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了之后在unlink、close掉,比较麻烦。可直接使用匿名映射区来代替。Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区,同样需要借助标志位参数flags来指定。

使用MAP_ANONYMOUS(或MAP_ANON),如

int *p = mmap(NULL,4,PROT_READ|PORT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);

 "4"随意举例,该位置表大小,可依实际需要填写。

//fork_map_anon_linux.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(void){
        int *p;
        pid_t pid;

        p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);//MAP_ANONYMOUS
        if(p == MAP_FAILED){
                perror("mmap error");
                exit(1);
        }

        pid = fork();
        if(pid == 0){
                *p =2000;
                printf("child *p =%d\n",*p);
        }else{
                sleep(1);
                printf("parnet *p = %d\n",*p);
        }
        munmap(p,4);
        return 0;

}

需要注意的是,MAP_ANONYMOUS和MAP_ANON这两个宏是linux操作系统特有的宏,在类Unix中如无该宏定义,可使用如下两步来完成匿名映射区的建立。

  • ① fd = open("/dev/zero", O_RDWR);
  • ② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(void){
        int *p;
        pid_t pid;
        int fd;
        fd = open("/dev/zero",O_RDWR);

        p = mmap(NULL,400,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);//MAP_SHARED
        if(p == MAP_FAILED){
                perror("mmap error");
                exit(1);
        }

        pid = fork();
        if(pid == 0){
                *p =2000;
                printf("child *p =%d\n",*p);
        }else{
                sleep(1);
                printf("parnet *p = %d\n",*p);
        }
        munmap(p,400);
        return 0;
}

MAP_SHARED模式运行结果

 

mmap无血缘关系进程间通信

实质上mmap是内核借助文件帮我们创建了一个映射区多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程也可以使用mmap来完成通信。只要设置相应的标志位参数flags即可。若想实现共享,应使用flags为MAP_SHARED。

// mmap_r.c 
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>

typedef struct STU{
        int id;
        char name[20];
        char sex;
}STU;

void sys_err(char *str){
        perror(str);
        exit(-1);
}

int main(int argc,char *argv[]){
        int fd;
        STU student;
        STU *mm;
        if(argc < 2){
                printf("./a.out file_shared\n");
                exit(-1);
        }
        fd = open(argv[1],O_RDONLY);
        if(fd == -1){
                sys_err("open error");
        }
        mm = mmap(NULL,sizeof(student),PROT_READ,MAP_SHARED,fd,0);
        if(mm == MAP_FAILED){
                sys_err("mmap error");
        }
        close(fd);
        while(1){
                printf("id=%d\tname=%s\t%c\n",mm->id,mm->name,mm->sex);
                sleep(2);
        }
        munmap(mm,sizeof(student));
        return 0;
}


mmap_w.c

//mmap_w.c
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>

typedef struct STU{
        int id;
        char name[20];
        char sex;
}STU;

void sys_err(char *str){
        perror(str);
        exit(-1);
}

int main(int argc,char *argv[]){
        int fd;
        STU student = {10,"xiaoming",'m'};
        STU *mm;
        if(argc < 2){
                printf("./a.out file_shared\n");
                exit(-1);
        }
        fd = open(argv[1],O_RDWR|O_CREAT,0664);
        if(fd == -1){
                sys_err("open error");
        }
        ftruncate(fd,sizeof(student));

        mm = mmap(NULL,sizeof(student),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
        if(mm == MAP_FAILED){
                sys_err("mmap error");
        }
        close(fd);
        while(1){
                memcpy(mm,&student,sizeof(student));
                student.id++;
                //printf("id=%d\tname=%s\t%c\n",mm->id,mm->name,mm->sex);
                sleep(1);
        }
        munmap(mm,sizeof(student));
        return 0;
}
     

程序开始进程向mmap_w_file文件中写数据;

程序开始进程从mmap_w_file中读数据

 

相关函数及描述

 int ftruncate(int fd, off_t length);

DESCRIPTION
       The truncate() and ftruncate() functions cause the regular file named by path or referenced by fd to be truncated to a size of precisely length bytes.

       If the file previously was larger than this size, the extra data is lost.  If the file previously was shorter, it is extended, and the extended part reads as null bytes ('\0').

       The file offset is not changed.

       If  the size changed, then the st_ctime and st_mtime fields (respectively, time of last status change and time of last modification; see stat(2)) for the file are updated, and the
       set-user-ID and set-group-ID permission bits may be cleared.

       With ftruncate(), the file must be open for writing; with truncate(), the file must be writable.

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

int main(int argc,char *argv[]);

解释:argc为程序生成的可执行文件运行时带的所有参数数量(包括自身)

argv[0]:为具体参数例程中的参数argv[0] :./mmap_w

argv[1] :mmap_w_file
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值