进程间通信

进程间通信的概念:

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

进程间通信的方式

常用的进程间通信方式有:

管道(使用最简单)

信号(开销最小)

共享映射区(无血缘关系)

本地套接字(最稳定)

管道-pipe

管道的概念:管道是一种最基本的IPC机制,也称匿名管道,应用于有血缘关系的进程之间,完成数据传递。调用pipe函数即可创建一个管道。

管道的本质是一块内核缓冲区,内部用环形队列实现

由两个文件描述符引起,一个表示读端,一个表示写端

规定数据从写端流入管道,从读端流出

当两个进程都终结的时候,管道也自动消失

管道的读端和写端默认都是阻塞的

管道的局限性:

管道中的数据一旦被读走就不在管道中了,不能反复读取。

数据只能在一个方向上流动,若要实现双向流动,必须使用两个管道

只能在有血缘关系的进程间使用管道

pipe函数

int pipe(int fd[2]);//若函数调用成功,fd[0]存放管道的读端,fd[1]存放管道的写端

父子进程使用管道通信

第一步:父进程创建管道

第二步:父进程fork出子进程

第三步:父进程关闭读端,子进程关闭写端

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

int main()
{
    //创建管道
    //int pipe(int pipefd[2]);
    int fd[2];
    int ret = pipe(fd);
    if(ret<0)
    {
        perror("pipe error");
        return -1;
    }

    //创建子进程
    pid_t pid = fork();
    if(pid<0) 
    {
        perror("fork error");
        return -1;
    }
    else if(pid>0)
    {
        //关闭读端
        close(fd[0]);
        sleep(5);
        write(fd[1], "hello world", strlen("hello world"));    

        wait(NULL);
    }
    else 
    {
        //关闭写端
        close(fd[1]);
        
        char buf[64];
        memset(buf, 0x00, sizeof(buf));
        int n = read(fd[0], buf, sizeof(buf));
        printf("read over, n==[%d], buf==[%s]\n", n, buf);
    
    }

    return 0;
}

父子进程通信实现ps aux | grep bash

//使用pipe完成ps aux | grep bash操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    //创建管道
    //int pipe(int pipefd[2]);
    int fd[2];
    int ret = pipe(fd);
    if(ret<0)
    {
        perror("pipe error");
        return -1;
    }

    //创建子进程
    pid_t pid = fork();
    if(pid<0) 
    {
        perror("fork error");
        return -1;
    }
    else if(pid>0)
    {
        //关闭读端
        close(fd[0]);

        //将标准输出重定向到管道的写端
        dup2(fd[1], STDOUT_FILENO);
        
        execlp("ps", "ps", "aux", NULL);

        perror("execlp error");
    }
    else 
    {
        //关闭写端
        close(fd[1]);
    
        //将标准输入重定向到管道的读端
        dup2(fd[0], STDIN_FILENO);

        execlp("grep", "grep", "--color=auto", "bash", NULL);

        perror("execlp error");
    }

    return 0;
}

FIFO

FIFO被称为命名管道,可以用于不相关的进程间通信

创建管道

方式1:使用命名 mkfifo

命令格式: mkfifo 管道名

方式2:使用函数

int mkfifo(const char *pathname, mode_t mode)

使用FIFO完成两个进程间通信

  • 进程A:

  • 创建一个fifo文件:myfifo

  • 调用open函数打开myfifo文件

  • 调用write函数写入一个字符串如:“hello world”(其实是将数据写入到了内核缓冲区)

  • 调用close函数关闭myfifo文件

  • 进程B:

  • 调用open函数打开myfifo文件

  • 调用read函数读取文件内容(其实就是从内核中读取数据)

  • 打印显示读取的内容

  • 调用close函数关闭myfifo文件

fifo_write

//fifo完成两个进程间通信的测试
#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文件
    //int mkfifo(const char *pathname, mode_t mode);
    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);

    //getchar();

    return 0;
}

fifo_read

//fifo完成两个进程间通信的测试
#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文件
    //int mkfifo(const char *pathname, mode_t mode);
    //判断myfofo文件是否存在,若不存在则创建
    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 n;
    char buf[64];
    while(1)
    {
        memset(buf, 0x00, sizeof(buf));
        n = read(fd, buf, sizeof(buf));
        printf("n==[%d], buf==[%s]\n", n, buf);
    }

    //关闭文件
    close(fd);

    //getchar();

    return 0;
}

内存映射区

存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区像映射。

映射工作可以通过mmap函数实现

mmap函数的作用是建立存储缓冲区

函数原型:

void *mmap(void *addr, size_t length, int prot, int flags, intfd, off_t offset)

函数返回的是创建的映射区的首地址

  • addr: 指定映射的起始地址, 通常设为NULL, 由系统指定

  • length:映射到内存的文件长度

  • prot: 映射区的保护方式, 最常用的:

  • 读:PROT_READ

  • 写:PROT_WRITE

  • 读写:PROT_READ | PROT_WRITE

  • flags: 映射区的特性, 可以是

  • MAP_SHARED: 写入映射区的数据会写回文件, 且允许其他映射该文件的进程共享。

  • MAP_PRIVATE: 对映射区的写入操作会产生一个映射区的复制(copy-on-write), 对此区域所做的修改不会写回原文件。

  • fd:由open返回的文件描述符, 代表要映射的文件。

  • offset:以文件开始处的偏移量, 必须是4k的整数倍, 通常为0, 表示从文件头开始映射。

munmap函数

函数的作用:释放由mmap函数建立的存储映射区

函数原型:

int munmap(void *addr,size_t length)//addr表示mmap函数返回的映射区首地址,length是映射区的大小

mmap_write

//使用mmap函数完成两个不相干进程间通信
#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()
{
    //使用mmap函数建立共享映射区
    //void *mmap(void *addr, size_t length, int prot, int flags,
    //              int fd, off_t offset);
    int fd = open("./test.log", O_RDWR);
    if(fd<0)
    {
        perror("open error");
        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)
    {
        perror("mmap error");
        return -1;
    }
    
    memcpy(addr, "0123456789", 10);

    return 0;
}

mmap_read

//使用mmap函数完成两个不相干进程间通信
#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()
{
    //使用mmap函数建立共享映射区
    //void *mmap(void *addr, size_t length, int prot, int flags,
    //              int fd, off_t offset);
    int fd = open("./test.log", O_RDWR);
    if(fd<0)
    {
        perror("open error");
        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)
    {
        perror("mmap error");
        return -1;
    }

    char buf[64];
    memset(buf, 0x00, sizeof(buf));
    memcpy(buf, addr, 10);
    printf("buf=[%s]\n", buf);

    return 0;
}

mmap注意事项

  • 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。

  • 当MAP_SHARED时,映射区的权限必须<=文件打开的权限。

  • 映射区的释放与文件的关闭无关,只要映射创建成功,文件可以立即关闭。

  • 当映射文件大小为0时,不能创建映射区。

  • munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。

  • 文件偏移量必须为0或者4k的整数倍。

  • mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值