进程间通信

一个管道文件的通信半双工的,且不能用lseek()函数操作,其他操作与普通文件一样。

1、匿名pipe

#include <unistd.h>
int pipe(int pipefd[2]);
  • 功能:创建无名管道。
  • 参数:
    pipefd : 为 int 型数组的首地址,其存放了管道的文件描述符 pipefd[0]pipefd[1]。当一个管道建立时,它会创建两个文件描述符 fd[0]fd[1]。其中 fd[0] 固定用于读管道,而 fd[1] 固定用于写管道。一般文件 I/O的函数都可以用来操作管道(lseek() 除外)。
  • 返回值:
    成功:0
    失败:-1

2、命名pipe

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
  • 功能:
    命名管道的创建。
  • 参数:
    pathname : 普通的路径名,也就是创建后 FIFO 的名字。
    mode : 文件的权限,与打开普通文件的 open() 函数中的 mode 参数相同。(0644)
  • 返回值:
    成功:0 状态码
    失败:如果文件已经存在,则会出错且返回 -1。
  • 注意:
    管道默认大小65536字节

管道读写四种情况

  1. 写端文件描述符全部关闭 (管道写端引用计数为0) ,管道没数据,read会返回0,就像读到文件末尾一样,不设置errno
  2. 写端没关闭,管道没数据,则read阻塞。若读端设置非阻塞,read返回-1,errno设置为EAGAIN
  3. 读端都关闭,写端进程会收到信号SIGPIPE,进程异常终止。
  4. 读端没关闭,管道写满,写端阻塞。若写端设置非阻塞,write返回-1,errno设置为EAGAIN

3、内存映射:mmap()函数

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • 功能:
    一个文件或者其它对象映射进内存
  • 参数:
    addr : 文件映射到内存哪个位置, 通常设为NULL, 由系统指定
    length:映射到内存的文件长度
    prot: 映射区的保护方式, 最常用的 :
    a) 读:PROT_READ
    b) 写:PROT_WRITE
    c) 读写:PROT_READ | PROT_WRITE
    flags: 映射区的特性, 可以是
    a) MAP_SHARED(直写法): 写入映射区的数据会复制回文件, 且允许其他映射该文件的进程共享。
    b) MAP_PRIVATE : 对映射区的写入操作会产生一个映射区的复制(copy - on - write), 对此区域所做的修改不会写回原文件
    fd:由open返回的文件描述符, 代表要映射的文件。
    offset:以文件开始处的偏移量, 必须是4k(内存页面大小)的整数倍, 通常为0, 表示从文件头开始映射
  • 返回值:
    成功:返回创建的映射区首地址
    失败:MAP_FAILED宏
#include <sys/mman.h>
int munmap(void *addr, size_t length);
  • 功能:
    释放内存映射区
  • 参数:
    addr:使用mmap函数创建的映射区的首地址
    length:映射区的大小
  • 返回值:
    成功:0
    失败:-1

多个进程通过对同一个文件映射达到共享内存的目的,对共享内存的操作需要配合信号量等同步机制

注意事项

  1. 创建映射区的过程中,隐含着一次对映射文件的读操作。
  2. 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。
  3. 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。
  4. 特别注意,当映射文件大小为0时,不能创建映射区。所以,用于映射的文件必须要有实际大小。mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
  5. munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。
  6. mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

4、进程同步(信号量)

访问这种资源的代码称为临界区,需要一种手段(同步原语)让进程之间相互通气以便互斥访问临界区。

信号量

信号通常是系统用来通知进程,当一个信号量为0,说明没有唤醒进程的操作,如果大于0表示有多个唤醒操作。对信号量有两个操作:down(P : proberen 尝试)和up(V : verhogen 增加),分别为消耗一个信号和增加一个信号。down和up为原子操作。

down:检查信号量如果大于0,则信号量减一,否则阻塞。

up:如果有进程在这个信号量上阻塞(信号量当然为0),则唤醒一个进程,否则信号量加一。

互斥量

最大值为1的信号量。

条件变量

条件变量要配合一个互斥锁,是一个全局变量,作用是使线程阻塞直到等待一个条件成立再继续运行。条件变量是一个队列,线程检测到条件不满足会将自己加入(wait())这个队列里等待被唤醒。另外的线程在改变条件的时候也可以唤醒(signal())队列中的一个或全部线程。注意这个唤醒操作是将线程从条件变量的队列里出列,进入互斥锁的阻塞队列竞争互斥锁,而不是唤醒就直接开始执行下边的代码。没有被唤醒的线程会继续在条件变量的队列中,即使互斥锁被释放也不会去获得锁。

一个典型的条件变量使用:

wait端:

std::mutex mtx;
std::condition_variable_any cond;

void child() {
  mtx.lock();
  while(not some condiiton) //使用while为了避免“虚假唤醒”
    cond.wait(mtx); // 释放锁,加入条件变量的阻塞队列
  //被唤醒,获得锁,继续执行
  // do somthing
  mtx.unlock();
}

void parent() {
	mtx.lock();
  //do somthing,条件满足
  cond.notify_one(); // 唤醒一个线程
  cond.notify_all(); // 唤醒全部线程
	mtx.unlock();
}

5、消息队列

消息队列是一个消息的链表,保存在内核中。每个消息队列有唯一的 key
消息队列允许一个或多个进程向它写入与读取消息。消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。也就是说,消息队列是异步的,但这也造成了一个缺点,就是接收者必须轮询消息队列,才能收到最近的消息。

6、信号

7、套接字socket

posted @ 2022-01-18 14:33  hellozhangjz  阅读(28)  评论(0编辑  收藏  举报