Linux编程(10)_进程通信

1 进程通信相关概念

1 什么是IPC

进程间通信, InterProcess Communication

2 进程间通信常用4种方式
  • 管道 (使用最简单)
  • 信号 (开销最小)
  • 共享内存/映射区 (无血缘关系)
  • 本地套接字 (最稳定)

2 管道(匿名)

1 管道的概念

本质:

内核缓冲区

伪文件 - 不占用磁盘空间

特点:

两部分:

读端,写端,对应两个文件描述符
数据写端流入, 读端流出

操作管道的进程被销毁之后,管道自动被释放了

管道默认是阻塞的。

读写

#### 2 管道的原理

内部实现方式:队列

环形队列
特点:先进先出

缓冲区大小:
默认4k, 大小会根据实际情况做适当调整

3 管道的局限性

队列:

数据只能读取一次,不能重复读取

全双工(Full Duplex)是指在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的网卡一般都支持全双工。
半双工(Half Duplex)所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆车通过,当目前有两量车对开,这种情况下就只能一辆先过,等到头儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台.
单工通信是指通信线路上的数据按单一方向传送.

匿名管道:适用于有血缘关系的进程

4 创建匿名管道

int pipe(int fd[2]); 数组有两个元素, 一个读端一个写端, 各自对应一个文件描述符

  • fd - 传出参数,
  • fd[0] - 读端
  • fd[1] - 写端

例:

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

int main()
{
    int fd[2];
    // 创建管道
    int ret = pipe(fd);
    // 如果返回值为-1, 则创建失败
    if(ret == -1) 
    {   
        perror("pipe error");
        exit(1);          
    }   

    // 读端
    printf("pipe[0] = %d\n", fd[0]);
    // 写端
    printf("pipe[1] = %d\n", fd[1]);

    close(fd[0]);
    close(fd[1]);

    return 0; 
}
5 父子进程使用管道通信

思考:

  • 单个进程能否使用管道完成读写操作?

    可以

  • 父子进程间通信是否需要sleep函数?

    父写 - 写的慢

    子读 - 读的快

    不用, 因为管道是阻塞的

  • 注意事项

    先创建管道, 再创建子进程

  • 父子进程实现 ps aux | grep "bash"

    数据重定向: dup2

    execlp

    image.png

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

    int main(int argc, const char* argv[])
    {
    // 创建管道
    int fd[2];
    int ret = pipe(fd);
    if(ret == -1)
    {
    perror(“pipe error”);
    exit(1);
    }

    // 创建子进程
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }
    
    // ps aux | grep bash
    // 父进程执行 ps aux , 写管道, 关闭读端
    if(pid > 0)
    {
        //关闭读端
        close(fd[0]);
        // 数据写到管道,STDOUT_FILENO 指向fd[1]的指向,也就是管道的写端
        dup2(fd[1], STDOUT_FILENO);
        // 执行命令ps -aux
        execlp("ps", "ps", "aux", NULL);
        // 如果上面失败则:
        perror("execlp ps");
        exit(1);
    }
    // 子进程 grep bash 从管道中搜索, 读管道, 关闭写端
    else if(pid == 0)
    {
        // 关掉写端
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execlp("grep", "grep", "bash","--color=auto",  NULL);
        perror("execlp grep");
        exit(1);
    }
    close(fd[0]);                                                                    
    close(fd[1]);
    
    return 0;
    

    ​“`

  • 兄弟进程实现ps aux | grep "bash"

    image.png

    
    #include <stdio.h>
    
    
    #include <unistd.h>
    
    
    #include <stdlib.h>
    
    
    #include <sys/types.h>
    
    
    #include <sys/stat.h>
    
    
    #include <string.h>
    
    
    #include <sys/wait.h>
    
    
    int main(int argc, const char* argv[])
    {
        int fd[2];
        int ret  = pipe(fd);
        if(ret == -1)
        {
            perror("pipe error");
            exit(1);
        }
    
        printf("fd[0] = %d\n", fd[0]);
        printf("fd[1] = %d\n", fd[1]);
    
        //创建两个子进程
        int i = 0;
        int num = 2;
        for(; i<num; ++i)
        {
            pid_t pid = fork();
            //如果是子进程则跳出
            if(pid == 0)
            {
                break;
            }
        }
    
        //父进程回收紫禁城的pcb
        if(i == num)
        {
            close(fd[0]);
            close(fd[1]);
    
            //循环回收子进程
            //WNOHANG 非阻塞回收
            pid_t wpid;
            while( (wpid = waitpid(-1, NULL, WNOHANG)) != -1 )
            {
                if(wpid == 0)
                {
                    continue;
                }
                printf("died child pid = %d\n", wpid);
            }
        }
        else if(i == 0)
        {
            // ps aux
            close(fd[0]);
            dup2(fd[1], STDOUT_FILENO);
            execlp("ps", "ps", "aux", NULL);
        }
        else if(i == 1)
        {
            // grep bash
            close(fd[1]);
            dup2(fd[0], STDIN_FILENO);
            execlp("grep", "grep", "bash", NULL);
        }
    
        return 0;

6 管道读写行为
读操作
  • 有数据 read(fd) - 正常读, 返回读出的字节

  • 无数据

    • 可能写端全部关闭

      read解除阻塞, 返回0

      相当于读文件读到了尾部

    • 没有全部关闭

      read阻塞

写操作
  • 读端全部被关闭

    • 管道破裂, 进程被中止

      内核给当前进程法信号SIGPIPE, 中止进程

  • 读端没有全部关闭

    • 缓冲区写满

      write函数阻塞

    • 缓冲区没满

      write继续写, 知道满

如何设置非阻塞?
  • 默认读写两端都阻塞

  • 设置读端为非阻塞pipe(fd)

    • fcntl - 变参函数

      • 复制文件描述符
      • 修改文件的属性 - open的时候对应的flag属性
    • 设置方法:

      获取原来的flags: int flags = fcntl(fd[0], F_GETFL);

      设置新的flags: 非阻塞

      flag |= O_NONBLOCK

      fcntl(fd[0], F_SETFL, flags);

7 查看缓冲区大小
  1. 命令ulimit -a

  2. 函数fpathconf

    例: long num = fpathconf(fd[0], _PC_PIPE_BUF);

3 fifo

1 特点
  • 有名管道
  • 在磁盘上为类型为p(管道)的文件
  • 伪文件, 在磁盘大小永远为0
  • 数据在内核中对应的缓冲区
  • 半双工通信
2 使用场景

无血缘关系进程间通信

3 创建方式
  1. 命令 mkfifo 管道名
  2. 函数 mkfifo
4 fifo文件使用IO函数进行操作
  • open/close
  • read/write
  • 不能lseek
5 进程间通信
  1. fifo文件 — myfifo

    • 两个不相干的进程A(a.c) B(b.c)

    a.c –> read

    • int fd = open(“myfifo”, O_RDONLY);
    • read(fd, buf, sizeof(buf));
    • close(fd);

    • b.c — write

    int fd1 = open(“myfifo”, O_WRONLY);

    write(fd1, “hello”, 5);

    close(fd1);

4 内存映射区

1 mmap – 创建内存映射区
  • 作用: 将磁盘文件的数据映射到内存, 用户通过修改内存就能修改磁盘文件

  • 函数原型

    void *mmap
    {
        void *adrr, //映射区首地址, 传NULL
        size_t length, //映射区的大小, 最小4k, 不能为0, 一般和文件同大小
        int prot, //映射区权限 PROT_READ--映射区必须要有读权限 
        int flags, //标志位参数 
        //MAP_SHARED共享的,数据会同步到磁盘. MAP_PRIVATE私有的,不会同步
        int fd, //文件描述符, 需要映射的源文件, 需要先open一个文件
        off_t offset //映射文件的指针偏移量, 为4k的整数倍
    }
  • 返回值:

    成功: 返回映射区的首地址

    失败: 返回MAP_FAILED, 就是(void *)-1

2 munmap – 释放内存映射区
  • 函数原型 int munmap(void *addr, size_t length);

    addr–映射区的首地址

    length–映射区的长度

3 注意事项
  • 如修改内存映射区是地址的指针, 则释放失败, 可以复制之后再操作

    char *pt = ptr

  • open的文件的权限应 >= 映射区的权限

4 有血缘关系的进程间通信

父子进程间永远共享:

  • 文件描述符

  • 内存映射区

    
    #include <stdio.h>
    
    
    #include <unistd.h>
    
    
    #include <stdlib.h>
    
    
    #include <sys/types.h>
    
    
    #include <sys/stat.h>
    
    
    #include <string.h>
    
    
    #include <sys/mman.h>
    
    
    #include <fcntl.h>
    
    
    #include <sys/wait.h>
    
    
    
    int main(int argc, const char* argv[])
    {
        //获取文件描述符
        int fd = open("english.txt", O_RDWR);
        if(fd == -1)
        {
            perror("open error");
            exit(1);
        }
    
        // get file length
        // len > 0
        int len = lseek(fd, 0, SEEK_END);
    
        void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if(ptr == MAP_FAILED)
        {
            perror("mmap error");
            exit(1);
        }
        close(fd);
    
        char buf[4096];
        // 从内存中读数据
        printf("buf = %s\n", (char*)ptr);
        strcpy(ptr, "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyaaaaa");
    
    
        // 进程间通信
        pid_t pid = fork();
        if(pid == -1)
        {
            perror("fork error");
            exit(1);
        }
        //父进程
        if(pid >0)
        {
            //写数据
            strcpy((char*)ptr, "come on!");
            //回收
            wait(NULL);
        }
        else if(pid == 0)
        {
            //读数据
            printf("%s\n", (char*)ptr);
        }
    
    //    ptr++;
        int ret = munmap(ptr, len);
        if(ret == -1)
        {
            perror("munmap error");
            exit(1);
        }
    
        return 0;

匿名映射区:

int main(int argc, const char* argv[])
{
    //创建匿名映射区
    int len = 4096;
    void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
    if(ptr == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }

    // 进程间通信
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }
    //父进程
    if(pid >0)
    {
        //写数据
        strcpy((char*)ptr, "come on!");
        //回收
        wait(NULL);
    }
    else if(pid == 0)
    {
        //读数据
        printf("%s\n", (char*)ptr);
    }

    //释放内存映射区
    int ret = munmap(ptr, len);
    if(ret == -1)
    {
        perror("munmap error");
        exit(1);
    }

    return 0;
5 无血缘关系的进程间通信

a.c和b.c, 只能借助磁盘文件创建映射区, 不阻塞

  • a.c

    int fd = open(“hello”);

    void* ptr = mmap(, , , ,fd, 0);

    int main(int argc, char *argv[])
    {
        int fd = open("temp", O_RDWR | O_CREAT, 0664);
    
        void* ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if(ptr == MAP_FAILED)
        {
            perror("mmap");
            exit(1);
        }
    
        while(1)
        {
            char*p = (char*)ptr;
            p += 1024;
            strcpy(p, "hello parent, i am your 朋友!!!\n");
            sleep(2);
        }
    
        // 释放
        int ret = munmap(ptr, 4096);
        if(ret == -1)
        {
            perror("munmap");
            exit(1);
        }
    
        return 0;
    }

  • b.c

    int fd1 = open(“hello”);

    void* ptr1 = mmap( , , , , fd1, 0);

    int main(int argc, char *argv[])
    {
        int fd = open("temp", O_RDWR | O_CREAT, 0664);
        ftruncate(fd, 4096);
        int len = lseek(fd, 0, SEEK_END);
    
        void* ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if(ptr == MAP_FAILED)
        {
            perror("mmap");
            exit(1);
        }
    
        while(1)
        {
            sleep(1);
            printf("%s\n", (char*)ptr+1024);
        }
    
        // 释放
        int ret = munmap(ptr, len);
        if(ret == -1)
        {
            perror("munmap");
            exit(1);
        }
    
        return 0;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值