linux进程通信

本文详细介绍了IPC(进程间通信)的各种方式,包括管道(pipe)、有名管道(fifo)、mmap文件映射、本地socket、信号量和共享内存,以及它们的优缺点和使用案例。重点演示了管道的双向通信解决方案和mmap在父子进程间共享内存的应用。
摘要由CSDN通过智能技术生成

IPC (interProcess Communication) 进程间通信,通过内核提供的缓冲区,进行数据的交换机制。

IPC通信的几种方式:

  • pipe 管道 最简单
  • fifo 有名管道
  • mmap 文件映射共享IO, 速度最快
  • 本地socket最稳定
  • 信号 携带的信号量最小
  • 共享内存
  • 消息队列

1. 管道pipe

  • 管道通信优劣:
    优点:简单,相比信号和套接字实现进程通信简单很多
    缺点:1. 只能单向通信(半双工),双向通信需要建立两个管道?2. 只能用于父子、兄弟(有共同祖先)进程之间的通信。该问题可以有FIFO有名管道解决。

  • man pipe 查看函数

    #include <unistd.h>
    int pipe(int pipefd[2]);
    
    #define _GNU_SOURCE             /* See feature_test_macros(7) */
    #include <fcntl.h>              /* Obtain O_* constant definitions */
    #include <unistd.h>
    int pipe2(int pipefd[2], int flags);
    
  • 进程通信demo

    #include <stdio.h>
    #include <unistd.h>
    int main()
    {
        printf("Begin ..\n");
        int fd[2];
        pipe(fd); // 先创建管道,再创建子进程
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            write(fd[1], "hello", 5);
        } else if(pid > 0) {
            // 父进程
            char buff[12] = {0};
            int ret = read(fd[0], buff, sizeof(buff)); // 阻塞等待
            if(ret > 0) {
                write(STDOUT_FILENO, buff, ret);
            }
        }
        printf("End ...\n");
        return 0;
    }
    
  • 读管道
    写端全部关闭 --read读到0,相当于读到文件末尾
    写端没有完全关闭,有数据 read 读到数据,没有数据 read 阻塞 fcntl函数可以更改非阻塞
    写端全部关闭demo

    #include <stdio.h>
    #include <unistd.h>
    int main()
    {
        printf("Begin ..\n");
        int fd[2];
        pipe(fd); // 先创建管道,再创建子进程
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            sleep(3);
            close(fd[0]); // 关闭读端
            write(fd[1], "hello", 5);
            close(fd[1]); // 关闭写端
            sleep(10);
        } else if(pid > 0) {
            // 父进程
            char buff[12] = {0};
            close(fd[1]); // 关闭写端
            while (1) {
                int ret = read(fd[0], buff, sizeof(buff)); // 阻塞等待
                if (ret == 0) { // 如果写端全部关闭(所有写端),read会读到0
                    printf("\n read over! \n");
                    break;
                }
                if(ret > 0) {
                    write(STDOUT_FILENO, buff, ret);
                }
            }
        }
        printf("End ...\n");
        return 0;
    }
    

    输出:

    Begin ..
    hello
     read over! 
    End ...
    
  • 写管道
    读端全部关闭 产生一个信号SIGPIPE,程序异常终止
    读端未全部关闭,管道已满 write 阻塞,管道未满 write 正常写入

    读端全部关闭程序

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    int main()
    {
        printf("Begin ..\n");
        int fd[2];
        pipe(fd); // 先创建管道,再创建子进程
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            sleep(3);
            close(fd[0]); // 关闭读端
            write(fd[1], "hello", 5);
            close(fd[1]); // 关闭写端
            sleep(10);
        } else if(pid > 0) {
            // 父进程
            char buff[12] = {0};
            close(fd[1]); // 关闭写端
            close(fd[0]); // 关闭读端
            int status;
            wait(&status);
            if (WIFSIGNALED(status)) {
                printf("killed by %d\n", WTERMSIG(status));
            }
            while (1) {
                int ret = read(fd[0], buff, sizeof(buff)); // 阻塞等待
                if (ret == 0) {
                    printf("\n read over! \n");
                    break;
                }
                if(ret > 0) {
                    write(STDOUT_FILENO, buff, ret);
                }
            }
        }
        printf("End ...\n");
        return 0;
    }
    

    输出结果

    Begin ..
    killed by 13
    

    补充: ulimit -a 查看所有系统资源的上限

2. FIFO有名管道

区分与pipe,pipe只能再有血缘关系的进程之间通信,而fifo可以实现非血缘关系的进程之间的通信。
FIFO是linux系统基础文件的一种,但是,FIFO文件在磁盘上没有数据块,仅仅用来表示内核中一条通道。各个进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。

  • 创建一个管道伪文件
    mkfifo myfifo 创建命令
    也可以使用函数int mkfifo(cosnt char* pathname, mode_t mode);

  • 内核会针对fifo文件开辟一个缓冲区,操作fifo文件,实现进程间的通信。(实际上就是文件读写)

  • FIFO写数据demo

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <string.h>
    
    int main(int argc, char *argv[])
    {
        if (argc != 2) {
            printf("./a.out fifoname\n");
        }
        // 当前目录有一个myfifo文件
        // 打开fifo文件
        int fd = open(argv[1], O_WRONLY);
        // 写数据
        char buf[256];
        int num = 1;
        while (num < 100) {
            memset(buf, 0x00, size(buf));
            sprintf(buf, "xiaoming%02d", num++);
            write(fd, buf, strlen(buf));
            sleep(1);
    	}
        close(fd);
        return 0;
    }
    

    FIFO读数据demo

    int main(int argc, char* argv[])
    {
        if (argc != 2) {
            printf("./a.out fifoname\n");
        }
        // 当前目录有一个myfifo文件
        // 打开fifo文件
        int fd = open(argv[1], O_WRONLY);
        char buf[256];
        int ret;
        while (1){
            // 循环读
            memset(buf, 0x00, sizeof(buf));
            ret = read(fd, buf, sizeof(buf));
            if (ret < 0) {
                printf("read:%s", buf);
            }
        }
        close(fd);
        return 0;
    }
    

    注:打开fifo文件的时候,read会阻塞等待write端open,write端也会阻塞等待另一端打开。

3. mmap

  • 函数
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);
int munmap(void *addr, size_t length);

创建映射区 mmap 函数参数:

  • addr: 传 NULL
  • length: 映射区长度
  • prot:
    PROT_READ 可读
    PROT_WRITE 可写
  • flags:
    MAP_SHARED 共享的(进程间通信必须是共享的)
    MAP_PRIVATE 私有的
  • fd 文件描述符,open打开一个文件
  • offset 偏移量
  • 返回值
    成功, 返回可用的内存首地址
    失败, 返回 MAP_FAILED

释放映射区 munmap函数:

  • addr 传 mmap返回值
  • length mmap创建的长度
  • 返回值
    成功返回 0
    失败返回 -1

创建映射区程序demo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
int main()
{
    int fd = open("mem.txt", O_RDWR);
    // 创建映射区
    char *mem = (char *)mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, 0);
    if (mem == MAP_FAILED) {
        perror("mmap err");
        return -1;
    }
    strcpy(mem, "hello");

    //释放mmap
    munmap(mem, 8);
    close(fd);
    return 0;
}
  • mmap 9问
    在这里插入图片描述

  • 使用mmap实现父子进程之间通信

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    int main()
    {
        int fd = open("mem.txt", O_RDWR);
        // 创建映射区
        char *mem = (char *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if (mem == MAP_FAILED) {
            perror("mmap err");
            return -1;
        }
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            *mem = 'a';
            printf("child, *mem = %s\n", mem);
            sleep(3); // 等待父进程修改
            printf("child, *mem = %s\n", mem);
        } else if (pid > 0) {
            // 父进程
            sleep(1); // 等待子进程修改
            printf("parent, *mem = %s\n", mem);
            *mem = 'b';
            printf("parent, *mem = %s\n", mem);
            wait(NULL);
        }
    
        //释放mmap
        munmap(mem, 8);
        close(fd);
        return 0;
    }
    
    
    • 输出结果
    child, *mem = a
    parent, *mem = a
    parent, *mem = b
    child, *mem = b
    

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

  • 匿名映射
    通过使用mmap发现,使用映射区完成文件的读写非常方便,父子进程间通信也比较容易。缺陷是,每次创建一个映射区必须依赖一个文件才能实现。通过创建一个映射区要open一个temp文件,最后必须close,比较麻烦。使用匿名映射无需依赖一个文件即可创建一个映射区,需要借助标志位参数flags来指定。
    在这里插入图片描述

  • 匿名映射程序demo

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    int main()
    {
        // 创建映射区
        char *mem = (char *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
        if (mem == MAP_FAILED) {
            perror("mmap err");
            return -1;
        }
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            *mem = 'a';
            printf("child, mem = %s\n", mem);
            sleep(3); // 等待父进程修改
            printf("child, mem = %s\n", mem);
        } else if (pid > 0) {
            // 父进程
            sleep(1); // 等待子进程修改
            printf("parent, mem = %s\n", mem);
            *mem = 'b';
            printf("parent, mem = %s\n", mem);
            wait(NULL);
        }
    
        //释放mmap
        munmap(mem, 8);
        return 0;
    }
    
  • 使用mmap实现无血缘关系的进程通信
    写端demo

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    struct Student{
        int id;
        char name[20];
    };
    
    
    int main(int argc, char *argv[])
    {
        if (argc != 2) {
            printf( "./main filename\n");
            return -1;
        }
        // 1. 打开文件
        int fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0666);
        int len = sizeof(Student);
        // ftruncate 修改文件大小为 len
        ftruncate(fd, len);
    
        // 2. mmap
        Student *stu = (Student *)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (stu == MAP_FAILED) {
            perror("mmap error");
        }
    
        // 3. 修改内存数据
        unsigned int num = 1;
        while (1) {
            stu->id = num;
            sprintf(stu->name, "xiaoming-%03d", num++);
            sleep(1);
        }
    
        // 4. 释放映射区和关闭文件描述符
        munmap(stu, len);
        close(fd);
    
        return 0;
    }
    
    

    读端demo

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    struct Student{
        int id;
        char name[20];
    };
    
    
    int main(int args, char* argv[])
    {
        int fd = open(argv[1], O_RDWR);
        int len = sizeof(Student);
        Student *stu = (Student *)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (stu == MAP_FAILED) {
            perror("mmap err");
        }
        while(1) {
            printf("id = %d, name = %s\n", stu->id, stu->name);
            sleep(1);
        }
    
        munmap(stu, len);
        close(fd);
        return 0;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值