【C语言】进程间通信

简介: 【C语言】进程间通信

【C语言】进程间通信

进程间通信

以下内容通过pipe、fifo、mmap来进行进程间通信

管道pipe()

  • 管道pipe也称为匿名管道,只有在有血缘关系的进程间进行通信。管道的本质就是一块内核缓冲区。
  • 进程间通过管道的一端写,通过管道的另一端读。管道的读端和写端默认都是阻塞的。
  • 管道中的内容读取了就没了,不能重复读取
  • 如果想要数据双向流动,那么需要两个管道
  • 管道的内部实现是一个环形队列,通过命令 ulimit -a 进行查看大小
pipe size    (512 bytes, -p) 8
AI 代码解读
  • 使用命令查看管道大小
printf("pipe size==[%ld]\n", fpathconf(fd[0], _PC_PIPE_BUF));
//输出
pipe size==[4096]
AI 代码解读

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

int pipe(int pipefd[2]);
返回值
    成功返回0,然后使用pipefd[2]来操作管道的读写
    失败返回-1
AI 代码解读

示例:

使用管道来进行进程间通信

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

int main()
{
   
    // pid_t fork(void);
    int fd[2];
    int ret = pipe(fd);
    if (ret < 0)
    {
   
        perror("pipe error");
        return -1;
    }

    ret = fork();
    if (ret > 0)
    {
   
        //father
        //close read
        printf("here is father,child is [%d]\n",ret);
        close(fd[0]);
        write(fd[1],"hello",strlen("hello"));
        pid_t waitp = wait(NULL);
        if (waitp>0)
        {
   
            printf("child [%d] is over\n",waitp);
        }

    }
    else if (ret == 0)
    {
   
        //child
        //close write
        close(fd[1]);
        char buf[64];
        memset(buf,0x00,sizeof(buf));
        read(fd[0],buf,sizeof(buf));
        printf("father say:[%s]\n",buf);
    }
    else
    {
   
        perror("fork error");
        exit(-1);
    }

    return 0;
}
//输出
here is father,child is [24961]
father say:[hello]
child [24961] is over
AI 代码解读

管道的读写行为

管道读写默认是阻塞的。

读操作
    如果有数据,read正常读,返回读出的字节数
    如果没有数据
        - 如果写端全部关闭,read返回0
        - 如果还有写端,read阻塞
写操作
    如果读端全部关闭,管道破裂,进程终止,内核发送SIGPIPE信号给当前进程
    如果读端没有没有全部关闭
        - 如果缓冲区写满了,write阻塞
        - 如果缓冲区没有满,继续执行write写操作
AI 代码解读

如果将管道读端或者写端设置为非阻塞的,需要进行如下操作

//下面是将读端修改为非阻塞
//1.获取读端的文件属性
int flags = fcntl(fd[0], F_GETFL, 0); 
//2.添加非阻塞属性
flags |= O_NONBLOCK;
//3.设置读端属性
fcntl(fd[0], F_SETFL, flags);

若是读端设置为非阻塞:
    写端没有关闭,管道中没有数据可读,则read返回-1;
    写端没有关闭,管道中有数据可读,则read返回实际读到的字节数
    写端已经关闭,管道中有数据可读,则read返回实际读到的字节数
    写端已经关闭,管道中没有数据可读,则read返回0
AI 代码解读

使用单个进程对上述进行验证

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

int main()
{
   
    // pid_t fork(void);
    int fd[2];
    int ret = pipe(fd);
    if (ret < 0)
    {
   
        perror("pipe error");
        return -1;
    }

    // 设置管道读端为非阻塞
    int flags = fcntl(fd[0], F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(fd[0], F_SETFL, flags);

    // 关闭写端
    write(fd[1],"hello",strlen("hello"));
    close(fd[1]);

    char buf[64];
    memset(buf, 0x00, sizeof(buf));
    ssize_t len = read(fd[0], buf, sizeof(buf));
    if (len == 0)
    {
   
        printf("len is [%ld]\n", len);
    }
    else if (len < 0)
    {
   
        printf("len is [%ld]\n", len);
    }
    else
    {
   
        printf("len is [%ld]\n", len);
        printf("str is [%s]\n",buf);
    }

    return 0;
}
//输出
len is [5]
str is [hello]
AI 代码解读

命名管道fifo()

FIFO可以用于没有血缘关系的进程间通信。FIFO是Linux基本文件类型的一种,文件类型为p。

简单来说,FIFO可以理解为一个特殊的文件,创建它之后,可以使用 ls或ll来进行查看文件基本信息。

FIFO就是标识内核的一条管道,进程可以通过read/write进行读写操作。实际就是进程间在对内核缓冲区在进行读写操作从而进行通信。

注意事项 :这里mkfifo函数的路径需要是Linux本地文件夹下,不能是挂载的文件夹路径,此外需要当前用户有相应权限,如果没有权限可以使用sudo利用root角色来执行

还可以使用命令的方式来提前创建fifo文件

sudo mkfifo ./mfifo
AI 代码解读

或者使用函数mkfifo()进行创建

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
第一个参数是创建fifo的路径,第二个参数是fifo的权限

返回值
    等于0 创建成功
    其他 创建失败,由下面进行判断具体原因
        EACCES 路径名中的一个目录不允许搜索(执行)权限
        EDQUOT 用户在文件系统上的磁盘块或索引节点配额已用完。
        EEXIST 路径名已存在。
        ENAMETOOLONG 路径名的总长度大于PATH_MAX,或者单个文件名组件的长度大于NAME_MAX。
        ENOENT 路径名中的目录组件不存在
        ENOSPC 目录或文件系统没有空间容纳新文件。
        ENOTDIR 在路径名中用作目录的组件实际上不是目录。
        EROFS  路径名只读
AI 代码解读

fifo_write.c

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

int main()
{
   
    int ret = mkfifo("/home/myfifo", 0777);
    if (ret != 0)
    {
   
        perror("mkfifo error");
        return -1;
    }

    int fd = open("/home/myfifo",O_RDWR);
    if (fd<0)
    {
   
        perror("open error");
        return -1;
    }

    int i = 0;
    char buf[64];
    while(1)
    {
   
        memset(buf, 0x00, sizeof(buf));
        sprintf(buf, "%d:%s", i, "hello");
        write(fd, buf, strlen(buf));
        sleep(1);

        i++;
    }

    //关闭文件
    close(fd);

    return 0;
}
AI 代码解读

fifo_read.c

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

int main()
{
   

    int fd = open("/home/myfifo",O_RDWR);
    if (fd<0)
    {
   
        perror("open error");
        return -1;
    }

    char buf[64];
    while(1)
    {
   
        memset(buf, 0x00, sizeof(buf));
        ssize_t len = read(fd, buf, sizeof(buf));
        printf("str len is [%ld], [%s]\n",len,buf);
    }

    //关闭文件
    close(fd);
    return 0;
}
AI 代码解读

存储映射区mmap()

存储映射区就是将一个磁盘文件的空间映射到虚拟内存。可用于无亲缘关系进程。

此外,Linux系统中,每个进程都有独立的虚拟地址空间,因此两个非亲缘关系的进程,它们各自进程里使用的虚拟地址是不同的,因此在不同进程中返回的mmap地址值也不同。

当两个进程调用mmap方法时,分别返回的是两个进程自己的对应的虚拟地址。它们不同的虚拟地址对应同一块存储映射区,映射的是同一块物理内存,从而实现进程间通信。

mmap()函数介绍:

映射文件或者设备到内存。munmap相反,是取消映射。

#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返回值
    成功则返回系统分配的内存地址
    失败则返回MAP_FAILED
munmap返回值
    成功则返回0
    失败则返回-1

参数
    addr: 自己指定地址,或者设置为NULL,让系统分配。
    length: 指定映射的长度
    prot: 映射区读写权限 
        读:PROT_READ 写:PROT_WRITE 执行:PROT_EXEC
    flags: 映射区的特性。
        MAP_SHARED: 写入映射区的数据会写回文件, 且允许其他映射该文件的进程共享。
        MAP_PRIVATE: 对此区域所做的修改不会写回原文件。
    fd: 打开文件的文件描述符
    offset: 打开文件的偏移量,通常设置为0,即不偏移
AI 代码解读

示例一:

非亲缘关系进程间通信

//mmap_write.c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
   
  // void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t
  // offset); int munmap(void *addr, size_t length);
  int fd = open("./test.txt", 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);
  memset(addr, 0x00, strlen(addr));
  printf("write process map addr is %p\n", addr);
  close(fd);
  if (MAP_FAILED == addr) {
   
    perror("mmap error");
    return -1;
  }

  memcpy(addr, "hello world", strlen("hello world"));

  return 0;
}

//mmap_read.c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
   
  // void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t
  // offset); int munmap(void *addr, size_t length);
  int fd = open("./test.txt", 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);

  printf("read process map addr is %p\n", addr);
  close(fd);
  if (MAP_FAILED == addr) {
   
    perror("mmap error");
    return -1;
  }

  char *p = (char *)addr;
  printf("str is [%s]\n", p);

  return 0;
}

//1.在当前目录下创建一个文件test.txt
//2.这个文件不能为空文件,随便有什么内容都行
//3.先执行write,后执行read
//执行
./mmap_write
//输出
write process map addr is 0x7fd47bfd5000
//执行
./mmap_read
//输出
read process map addr is 0x7fb90928b000
str is [hello world]
AI 代码解读

从mmap映射获取的地址可以看出,非亲缘关系的进程的确具有不同的虚拟地址,它们映射出来的内存地址通常也是不同的。

示例二:

父子进程使用mmap进行通信

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

int main()
{
   
    // void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
    // int munmap(void *addr, size_t length);
    int fd = open("./test", 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);
    memset(addr, 0x00, strlen(addr));

    close(fd);
    if (MAP_FAILED == addr)
    {
   
        perror("mmap error");
        return -1;
    }

    int ret = fork();
    if (ret > 0)
    {
   
        // father
        memcpy(addr, "hello world", strlen("hello world"));
        wait(NULL);

        ret = munmap(addr, len);
        if (ret < 0)
        {
   
            perror("munmap error");
            return -1;
        }
        else
        {
   
            printf("mmap released\n");
        }
    }
    else if (ret == 0)
    {
   
        // child
        sleep(1);
        char *p = (char *)addr;
        printf("str is [%s]\n", p);
    }
    else
    {
   
        perror("fork error");
        return -1;
    }

    return 0;
}
//输出
str is [hello world]
mmap released
AI 代码解读

匿名映射区

添加匿名标志MAP_ANONYMOUS,并手动设置映射区大小

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

int main()
{
   
    // void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
    // int munmap(void *addr, size_t length);
    // int fd = open("./test", O_RDWR);
    // if (fd < 0)
    // {
   
    //     perror("open error");
    //     return -1;
    // }
    // int len = lseek(fd, 0, SEEK_END);
    int len =4096;

    void *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    memset(addr, 0x00, strlen(addr));

    //close(fd);
    if (MAP_FAILED == addr)
    {
   
        perror("mmap error");
        return -1;
    }

    int ret = fork();
    if (ret > 0)
    {
   
        // father
        memcpy(addr, "hello world", strlen("hello world"));
        wait(NULL);

        ret = munmap(addr, len);
        if (ret < 0)
        {
   
            perror("munmap error");
            return -1;
        }
        else
        {
   
            printf("mmap released\n");
        }
    }
    else if (ret == 0)
    {
   
        // child
        sleep(1);
        char *p = (char *)addr;
        printf("str is [%s]\n", p);
    }
    else
    {
   
        perror("fork error");
        return -1;
    }

    return 0;
}
//输出
str is [hello world]
mmap released
AI 代码解读
目录
打赏
0
4
5
0
24
分享
相关文章
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
214 1
从C语言到C++_28(红黑树RedBlackTree)概念+插入接口实现(上)
从C语言到C++_28(红黑树RedBlackTree)概念+插入接口实现
100 4
从C语言到C++_29(红黑树封装set和map)红黑树迭代器的实现(下)
从C语言到C++_29(红黑树封装set和map)红黑树迭代器的实现
103 3
从C语言到C++_29(红黑树封装set和map)红黑树迭代器的实现(上)
从C语言到C++_29(红黑树封装set和map)红黑树迭代器的实现
99 0
从C语言到C++_28(红黑树RedBlackTree)概念+插入接口实现(下)
从C语言到C++_28(红黑树RedBlackTree)概念+插入接口实现
79 0
C语言 红黑树分析与实现
红黑树的资料网上资料很多,对红黑树的定义、性质、以及操作都做了详细的分析,这篇博文也参考了网上的部分文章,不过主要是学习了腾讯课堂-零声king老师的课之后,对红黑树的一些理解。肯定有一些错误的地方,如果觉得不对,可以给我指出
163 0
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
313 23
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
109 1
一文彻底搞清楚C语言的函数
|
6月前
|
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
268 15
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
|
6月前
|
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
121 24
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问