一.过程间通信

1.1 什么是过程间通信

咱们运行起来的过程,相互之间资源是独立的,不能在一个过程中间接拜访另一个过程的资源。
然而很多时候不同的过程须要进行信息的交互和状态的传递等,譬如数据传输,一个过程须要将它的数据发送给另一个过程,或者多个过程间资源共享,或者一个过程须要管制另一个过程的执行,再或者,一个过程要给另一个过程发送音讯等,就须要过程间通信( IPC:Inter Processes Communication )。

1.2 过程间通信的形式

过程间通信的形式有很多,文件、管道、信号、共享内存映射、音讯队列、套接字、命名管道等。这里说管道pipe,命名管道fifo,共享内存映射。

二.管道PIPE

2.1 管道概述

管道是一种最根本的IPC机制,也称匿名管道、无名管道,利用于有血缘关系的过程之间,实现数据传递。
所有的 UNIX 零碎都反对这种通信机制。

管道的实质是一块内核缓冲区,由两个文件描述符援用,一个示意读端,一个示意写端。
管道是半双工,规定数据从管道的写端流入管道,从读端流出,当两个过程都终结的时候,管道也主动隐没,管道的读端和写端默认都是阻塞的。

管道的数据一旦被读走,便不在管道中存在,不可重复读取,数据只能在一个方向上流动,若要实现双向流动,必须应用两个管道。

只能在有血缘关系的过程间应用管道

创立管道非常简单,调用pipe函数即可

2.2 pipe函数

#include <unistd.h>int pipe(int pipefd[2]);性能:创立无名管道。参数:    pipefd : 为 int 型数组的首地址,其寄存了管道的文件描述符 pipefd[0]、pipefd[1]。        当一个管道建设时,它会创立两个文件描述符 fd[0] 和 fd[1]。其中 fd[0] 固定用于读管道,而 fd[1] 固定用于写管道。个别文件 I/O的函数都能够用来操作管道(lseek() 除外)。返回值:    胜利:0    失败:-1

代码示例

#include <stdio.h>#include <string.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>/* pipe函数演示 */int main(){    int fd[2];    int ret = pipe(fd); //创立管道    if(ret<0){         //创立管道失败        perror("pipe");        return -1;    }    pid_t pid = fork();    if(pid < 0){        // 创立过程失败        perror("fork");        return -1;    }else if(pid > 0){        // 父过程         close(fd[0]);        // 父过程写入fd[1]        write(fd[1],"hello",strlen("hello"));        wait(NULL);    }else {        // 子过程读取        char buf[128];        memset(buf,0,sizeof(buf));        int n = read(fd[0],buf,sizeof(buf));        printf("%s\n",buf);    }}

2.3 管道的读写行为注意事项

这里假设都是阻塞I/O操作,咱们应用管道须要留神以下4种非凡状况:
1) 如果所有指向管道写端的文件描述符都敞开了,而依然有过程从管道的读端读数据,那么管道中残余的数据都被读取后,再次read会返回0,就像读到文件开端一样。

2) 如果有指向管道写端的文件描述符没敞开,而持有管道写端的过程也没有向管道中写数据,这时有过程从管道读端读数据,那么管道中残余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

3) 如果所有指向管道读端的文件描述符都敞开了,这时有过程向管道的写端write,那么该过程会收到信号SIGPIPE,通常会导致过程异样终止。当然也能够对SIGPIPE信号施行捕获,不终止过程。具体方法信号章节具体介绍。

4) 如果有指向管道读端的文件描述符没敞开,而持有管道读端的过程也没有从管道中读数据,这时有过程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空地位了才写入数据并返回。

默认管道的读写两端为阻塞的IO操作,但也能够设置为非阻塞,办法也很简略,就是应用fcntl函数,设置O_NONBLOCK标记标记。

//获取原来的flagsint flags = fcntl(fd[0], F_GETFL);// 设置新的flagsflag |= O_NONBLOCK;// flags = flags | O_NONBLOCK;fcntl(fd[0], F_SETFL, flags);

2.4 查看缓冲区

管道实质是一个内核缓冲区,那么如何查看缓冲区大小呢?
1)ulimit -a

(base) zhaow@zhaow-610:~$ ulimit -acore file size          (blocks, -c) 0data seg size           (kbytes, -d) unlimitedscheduling priority             (-e) 0file size               (blocks, -f) unlimitedpending signals                 (-i) 62944max locked memory       (kbytes, -l) 65536max memory size         (kbytes, -m) unlimitedopen files                      (-n) 1024pipe size            (512 bytes, -p) 8 // 管道缓冲区大小POSIX message queues     (bytes, -q) 819200real-time priority              (-r) 0stack size              (kbytes, -s) 8192cpu time               (seconds, -t) unlimitedmax user processes              (-u) 62944virtual memory          (kbytes, -v) unlimitedfile locks                      (-x) unlimited

2)fpathconf函数

#include <unistd.h>long fpathconf(int fd, int name);性能:该函数能够通过name参数查看不同的属性值参数:    fd:文件描述符    name:        _PC_PIPE_BUF,查看管道缓冲区大小        _PC_NAME_MAX,文件名字字节数的下限返回值:    胜利:依据name返回的值的意义也不同。    失败: -1

示例

#include <stdio.h>#include <string.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>#include <fcntl.h>int main(){    int fd[2];    int ret = pipe(fd);    if(ret <0){        perror("pipe");        return -1;    }    printf("pipe read size==[%ld]\n", fpathconf(fd[0], _PC_PIPE_BUF));    printf("pipe write size==[%ld]\n", fpathconf(fd[1], _PC_PIPE_BUF));    return 0;}

三.命名管道(FIFO)

3.1 概述

管道(pipe)只能用于“有血缘关系”的过程间通信,为了补救这个缺点,提出了命名管道(FIFO),也叫有名管道、FIFO文件。

命名管道(FIFO)提供了一个路径名与之关联,以 FIFO 的文件模式存在于文件系统中,这样即便与 FIFO 的创立过程不存在亲缘关系的过程,只有能够拜访该门路,就可能彼此通过 FIFO 互相通信,因而,通过 FIFO 不相干的过程也能替换数据。

FIFO是Linux根底文件类型中的一种(文件类型为p,可通过ls -l查看文件类型)。
FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道,过程关上这个文件进行read/write,实际上是在读写内核缓冲区。

3.2 创立fifo

1)命令mkfifo

(base) zhaow@zhaow-610:demo$ mkfifo myfifo(base) zhaow@zhaow-610:demo$ ls -l总用量 0prw-rw-r-- 1 zhaow zhaow 0 8月  21 02:42 myfifo  //文件类型 p

2)函数mkfifo

#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);性能:    命名管道的创立。参数:    pathname : 一般的路径名,也就是创立后 FIFO 的名字。    mode : 文件的权限,与关上一般文件的 open() 函数中的 mode 参数雷同。(0666)返回值:    胜利:0   状态码    失败:如果文件曾经存在,则会出错且返回 -1。

3.3 代码示例

过程A 写入

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>#include <sys/stat.h>#include <fcntl.h>int main(){    //创立fifo文件    // 1.查看是否存在,不存在则创立    int ret = access("./myfifo", F_OK);    if(ret!=0)    {                ret = mkfifo("./myfifo", 0777);        if(ret<0)        {            perror("mkfifo error");            return -1;        }    }    //关上文件    int fd = open("./myfifo", O_RDWR);    if(fd<0)    {        perror("open error");        return -1;    }    //写fifo文件    int i = 0;    char buf[64];    while(1)    {        memset(buf, 0x00, sizeof(buf));        sprintf(buf, "%d:%s", i, "hello world");        write(fd, buf, strlen(buf));        sleep(1);        i++;    }        close(fd);    return 0;}

过程B 读取

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>#include <sys/stat.h>#include <fcntl.h>int main(){    // 1.查看要创立的fifo文件是否存在,若不存在则创立    int ret = access("./myfifo",F_OK);    if(ret != 0){        ret = mkfifo("./myfifo",0777);        if(ret < 0){            perror("mkfifo");            return -1;        }    }    // 关上fifo    int fd = open("./myfifo",O_RDWR);    if(fd<0){        perror("open");        return -1;    }    // 读取    int n;    char buf[128];    while (1)    {        memset(buf,0x00,sizeof(buf));        n = read(fd,buf,sizeof(buf));        printf("buf=[%s]\n",buf);    }        close(fd);    return 0;}

四.共享存储映射

4.1 概述

存储映射I/O (Memory-mapped I/O) 就是使一个磁盘文件与存储空间中的一个缓冲区相映射。
从缓冲区中取数据,就如同读文件中的相应字节;同样,将数据写入缓冲区,则会将数据写入文件。

4.2 存储映射函数

1)mmap

#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);1 用处:一个文件或者其它对象映射进内存)2 参数阐明:    addr :  指定映射的起始地址, 通常设为NULL, 由零碎指定    length:映射到内存的文件长度    prot:  映射区的保护方式, 罕用的三种        1) 读:PROT_READ        2) 写:PROT_WRITE        3) 读写:PROT_READ | PROT_WRITE    flags:  映射区的个性, 能够是        1) MAP_SHARED : 写入映射区的数据会复制回文件, 且容许其余映射该文件的过程共享。        2) MAP_PRIVATE : 对映射区的写入操作会产生一个映射区的复制(copy - on - write), 对此区域所做的批改不会写回原文件。    fd:由open返回的文件描述符, 代表要映射的文件。    offset:以文件开始处的偏移量, 必须是4k的整数倍, 通常为0, 示意从文件头开始映射返回值:    胜利:返回创立的映射区首地址    失败:MAP_FAILED宏

2)munmap

#include <sys/mman.h>int munmap(void *addr, size_t length);性能:    开释内存映射区参数:    addr:应用mmap函数创立的映射区的首地址    length:映射区的大小返回值:    胜利:0    失败:-1

代码示例 父子过程间通信

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>#include <fcntl.h>#include <sys/stat.h>#include <sys/mman.h>int main(){    int fd = open("./ts.log",O_RDWR); //当映射文件大小为0时,不能创立映射区,所以,用于映射的文件必须要有理论大小;    if(fd < 0){        perror("open");        return -1;    }    int len = lseek(fd,0,SEEK_END);    void * addr = mmap(NULL,len,PROT_READ |PROT_WRITE,MAP_SHARED,fd,0);    if(addr == MAP_FAILED){ // mmap创立映射区出错概率十分高,肯定要查看返回值,确保映射区建设胜利再进行后续操作        perror("mmap");        return -1;    }    close(fd); //映射区的开释与文件敞开无关,只有映射建设胜利,文件能够立刻敞开。    pid_t pid = fork();    if(pid < 0){        perror("fork");        return -1;    }else if(pid > 0){        memcpy(addr,"hello",strlen("hello"));        wait(NULL);    }else{        char buf[64];        memset(buf,0x00,sizeof(buf));        memcpy(buf,addr,5);        printf("%s\n",buf);    }    return 0;}

4.3 匿名映射实现父子过程通信

应用映射区来实现文件读写操作非常不便,父子过程间通信也较容易,但比拟麻烦的是,每次创立映射区肯定要依赖一个文件能力实现,为了建设映射区要open一个temp文件,创立好了再unlink、close掉,很麻烦。
Linux零碎提供了创立匿名映射区的办法,无需依赖一个文件即可创立映射区。同样须要借助标记位参数flags来指定。

int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);参数阐明:    4"随便,该地位示意映射区大小,可依理论须要填写。    MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。

代码示例

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>#include <fcntl.h>#include <sys/stat.h>#include <sys/mman.h>int main(){    void * addr = mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);    if(addr==MAP_FAILED)    {        perror("mmap");        return -1;    }    pid_t pid  = fork();    if(pid < 0){        perror("fork");        return -1;    }else if(pid > 0){        memcpy(addr,"hello",strlen("hello"));        wait(NULL);    }else{        char buf[64];        memset(buf,0x00,strlen(buf));        memcpy(buf,addr,5);        printf("%s\n",buf);    }    return 0;}