【C语言】进程间通信

更多信息 进群了解
QQ群:712333606

进程间通信

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

管道pipe()

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

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

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

示例:

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

#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

管道的读写行为

管道读写默认是阻塞的。

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

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

//下面是将读端修改为非阻塞
//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    

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

#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]

命名管道fifo()

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

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

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

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

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

sudo mkfifo ./mfifo

或者使用函数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  路径名只读

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;
}

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;
}

存储映射区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,即不偏移

示例一:

非亲缘关系进程间通信

//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]

从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

匿名映射区

添加匿名标志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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值