个人知识库 个人知识库
首页
关于
  • C语言
  • CPlusPlus
  • Linux
  • PHP
  • Nginx
  • MySQL
  • Redis
  • Docker
  • Kubernetes
  • SRS
阅读
常用工具
  • 分类
  • 标签
  • 归档
GitHub

Agnes001

坚持是一件很伟大的事业
首页
关于
  • C语言
  • CPlusPlus
  • Linux
  • PHP
  • Nginx
  • MySQL
  • Redis
  • Docker
  • Kubernetes
  • SRS
阅读
常用工具
  • 分类
  • 标签
  • 归档
GitHub
  • C语言

  • CPlusPlus

  • Lua技术栈

  • edoyun

  • 内存管理

  • 数据结构

  • 网络编程

  • Linux

    • Linux基础
    • 系统编程

      • 概述
      • 文件io
      • 文件系统
      • 进程控制
      • 进程间通信
        • 进程间通信相关概念
        • 管道
          • pipe函数
          • 管道的读写行为
        • fifo有名管道
          • 创建方式:
        • mmap 共享存储映射
          • mmap函数-创建内存映射
          • munmap函数 释放内存映射
          • 创建匿名映射区 - 有血缘关系
      • 信号
      • 线程
    • 基础命令
    • itcast
    • 文件io
    • gdb
    • Ubuntu安装eclipse
    • gcc安装
    • 系统编程
    • linux内核多线程
  • 池化技术

  • 操作系统

  • python

  • 编程技术
  • Linux
Agnes001
2022-04-26

进程间通信

pipe-父子、兄弟进程间通信 fifo-无血缘关系的进程间通信 mmap

# 进程间通信相关概念

  1. IPC (InterProcess Communication)
  2. 进程间通信常用的4中实现方式
  • 管道 - 简单
  • 信号 - 系统开销小
  • 共享映射区 - 有无血缘关系的进程间通信都可以
  • 本地套接字 - 稳定

# 管道

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

  1. 其本质是一个伪文件(实为内核缓冲区)
  2. 由两个文件描述符引用,一个表示读端,一个表示写端。
  3. 规定数据从管道的写端流入管道,从读端流出;操作管道的进程被销毁之后,管道自动被释放;管道默认是阻塞的,因此不需要使用sleep函数。 管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。 管道的局限性: ① 数据一旦被读走,便不在管道中存在,不可反复读取。 ② 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。 ③ 只能在有血缘关系的进程间使用管道。 常见的通信方式有,单工通信、半双工通信、全双工通信。

# pipe函数

创建管道

//成功:0;失败:-1,设置errno
//pipefd[0]-读端 pipefd[1]-写端
//pipefd无需open,但需手动close。
int pipe(int pipefd[2]);		
int main()
{
  pid_t pid;
  char buf[1024];
  int fd[2];
  char *p = "test for pipe\n";

  int ret = pipe(fd);
  if(ret == -1){
    perror("pipe error\n");
    exit(1);
  }
  
  pid_t pid = fork();
  if(pid<0){
    perror("fork error\n");
    exit(1);
  }
  if (pid == 0) {
        close(fd[1]);
        int len = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
        close(fd[0]);
   } else {
       close(fd[0]);
       write(fd[1], p, strlen(p));
       wait(NULL);
       close(fd[1]);
   }
   
  //  if (pid == 0) {  //child
	// 	close(fd[1]);	                //子进程从管道中读数据,关闭写端
	// 	dup2(fd[0], STDIN_FILENO);		//让wc从管道中读取数据
	// 	execlp("wc", "wc", "-l", NULL);	//wc命令默认从标准读入取数据

	// } else {

	// 	close(fd[0]);	//父进程向管道中写数据,关闭读端
	// 	dup2(fd[1], STDOUT_FILENO);		//将ls的结果写入管道中
	// 	execlp("ls", "ls", NULL);		//ls输出结果默认对应屏幕
	// }

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

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

  return 0;
}

dup2函数 原型:int dup2(int oldfd, int newfd); 参数:oldfd是一个文件描述符,newfd一个文件描述符 功能:将newfd指向oldfd所指的文件,相当于重定向功能。如果newfd已经指向一个已经打开的文件,那么他会首先关闭这个文件,然后在使newfd指向oldfd文件;如果newfd和oldfd指向同一个文件,那么不会关闭,直接返回。

# 管道的读写行为

① 读管道:

  1. 管道中有数据,read返回实际读到的字节数。
  2. 管道中无数据: (1) 管道写端被全部关闭,read返回0 (好像读到文件结尾) (2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu) ② 写管道:
  3. 管道读端全部被关闭,管道破裂,进程异常终止(内核给当前进程发SIGPIPE信号)
  4. 管道读端没有全部关闭: (1) 管道已满,write阻塞。 (2) 管道未满,write将数据写入,并返回实际写入的字节数。 ③ 如何设置非阻塞?
  • 默认读写两端都阻塞
  • 设置读端为非阻塞pipe
//获取原来的flags
int flags = fcntl(fd[0], F_GETFL);
//设置新的flags
flags |= O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);

# fifo有名管道

FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。

# 创建方式:

  1. 命令:mkfifo 管道名
  2. 库函数:int mkfifo(const char *pathname, mode_t mode); 成功:0; 失败:-1 一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

void sys_err(char *str)
{
    perror(str);
    exit(1);
}
int main(int argc, char *argv[])
{
    int fd, len;
    char buf[4096];

    if (argc < 2) {
        printf("./a.out fifoname\n");
        return -1;
    }
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) 
        sys_err("open");
    while (1) {
        len =read(fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
        sleep(1);           //多個读端时应增加睡眠秒数,放大效果.
    }
    close(fd);

    return 0;
}

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

void sys_err(char *str)
{
    perror(str);
    exit(-1);
}

int main(int argc, char *argv[])
{
    int fd, i;
    char buf[4096];

    if (argc < 2) {
        printf("Enter like this: ./a.out fifoname\n");
        return -1;
    }
    /*
    int ret = access("myfifo", F_OK);
    if (ret != 0) {
        mkfifo("myfifo", 0664);
    }
    */

    fd = open(argv[1], O_WRONLY);
    if (fd < 0) 
        sys_err("open");

    i = 0;
    while (1) {
        sprintf(buf, "hello itcast %d\n", i++);
        write(fd, buf, strlen(buf));
        sleep(2);
    }
    close(fd);

    return 0;
}

# mmap 共享存储映射

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。 使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。

# mmap函数-创建内存映射

将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件

// 返回:成功:返回创建的映射区首地址;失败:MAP_FAILED宏
// 参数:	
// 	addr: 	建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
// 	length: 欲创建映射区的大小
// 	prot:	映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
// 	flags:	标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
// 		  	MAP_SHARED:  会将映射区所做的操作反映到物理设备(磁盘)上。
// 		  	MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
// 	fd: 	用来建立映射区的文件描述符
// 	offset: 	映射文件的偏移(4k的整数倍)
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset); 

# munmap函数 释放内存映射

同malloc函数申请内存空间类似的,mmap建立的映射区在使用结束后也应调用类似free的函数来释放。

//成功:0; 失败:-1
int munmap(void *addr, size_t length);	
	借鉴malloc和free函数原型,尝试装自定义函数smalloc,sfree来完成映射区的建立和释放。思考函数接口该如何设计?

使用mmap当前磁盘文件的内容

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

int main()
{
  //打开一个文件
  int fd = open("english.txt", O_RDWR);
  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("map error");
    exit(1);
  }
  printf("%s", (char*)ptr);
  //释放内存映射区
  munmap(ptr, len);
  close(fd);

  return 0;
}

# 创建匿名映射区 - 有血缘关系

//使用MAP_ANONYMOUS (或MAP_ANON), 如: 
//-1表示不用fd,匿名映射
int *p = mmap(NULL, 4094, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
// 需注意的是,MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。在类Unix系统中如无该宏定义,可使用如下两步来完成匿名映射区的建立。
// 	① fd = open("/dev/zero", O_RDWR);
// 	② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
编辑此页
进程控制
信号

← 进程控制 信号 →

Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式