自学内容网 自学内容网

进程间通信

一、进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程。
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个(一组)进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态变化。
  • 进程间通信(传输数据,同步执行流,消息通知等)只是手段,目的是实现多进程协同。

二、管道

1、概念

  • 管道是指从一个进程连接到另一个进程的一个数据流,它是Unix中最古老的进程间通信的形式。
  • 管道都是单向传输内容的,传输的都是"资源",即数据。

2、示意图

在这里插入图片描述

三、匿名管道

1、pipe函数

(1)函数

在这里插入图片描述

(2)概念

  • pipe函数创建一个可用于进程间通信的单向数据通道。
  • 数组pipefd用于返回引用读端和写端这两个文件描述符。pipefd[0]是管道的读取端,pipefd[1]是管道的写入端。写入管道写入端的数据由内核缓冲,直到从读取端读取。

2、示例代码

#include<iostream>
#include<string>
#include<cstring>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;

int main()
{
    int pipefd[2] = {0};
    int res = pipe(pipefd);
    assert(res != -1);
    pid_t id = fork();
    assert(id != -1);
    if(id == 0)
    {
        close(pipefd[1]);
        char receive_buff[1024] = {0};
        while(true)
        {
            ssize_t s = read(pipefd[0], receive_buff, sizeof(receive_buff) - 1);
            if(s == 0)
            {
                cout << "write over, i over" << endl;
                break;
            }
            receive_buff[s] = 0;
            cout << "child receive, pid=" << getpid() << ",father said: " << receive_buff << endl;
        }
        exit(0);
    }
    close(pipefd[0]);
    int cnt = 0;
    string s = "snow dragon writed";
    char send_buff[1024] = {0};
    while(cnt < 6)
    {
        snprintf(send_buff, sizeof(send_buff), "%s : %d", s.c_str(), cnt++);
        write(pipefd[1], send_buff, strlen(send_buff));
        sleep(1);
    }
    close(pipefd[1]);
    cout << "write over" << endl;
    pid_t ret = waitpid(id, nullptr, 0);
    cout << "wait sucess, id= " << id << ", ret= " << ret << endl;
    assert(ret > 0);

    return 0;
}

3、运行结果

在这里插入图片描述

4、单进程使用匿名管道示意图

在这里插入图片描述

5、父子进程使用匿名管道示意图

在这里插入图片描述

6、读写规则

  • 当管道内没有数据可读时,read调用阻塞,即进程暂停执行,一直等到有数据来为止。
  • 当管道内存储的数据满时,write调用阻塞,直到有进程读走数据为止。
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0。
  • 如果所有管道读端对应的文件描述符被关闭,则write会产生信号SIGPIPE,进而可能导致write进程退出。
  • 当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性,否则不保证。

7、特点

  • 只能用于具有共同祖先(具有亲缘关系)的进程之间进行通信(常用于父子进程间通信)。即一个管道由一个进程创建,然后该进程调用fork函数,然后父、子进程之间就可用该管道进行通信。
  • 管道使进程间协同得以实现,且提供了访问控制,即内核会对管道操作进行同步与互斥。
  • 管道提供的是面向流式(字节流)的通信服务。
  • 管道是基于文件的,而文件的生命周期是随进程的,所以,管道的生命周期是随进程的,进程退出,管道就会被释放。
  • 管道是半双工的,即数据只能向一个方向流动。当需要双方同时通信时,可以建立两个管道去实现这一目的。

四、命名管道

1、概念

  • 管道应用的一个限制是只能在具有共同祖先(具有亲缘关系)的进程间通信,如上方的匿名管道。
  • 想在不相关的进程之间交换数据的话,可以使用FIFO文件,即命名管道,它是一种特殊类型的文件,不会与外设(磁盘)进行io操作。

2、mkfifo函数

(1)函数

在这里插入图片描述

(2)概念

  • mkfifo函数创建一个名称为pathname的FIFO特殊文件。mode指定该FIFO文件的权限。 它由进程的umask以通常的方式修改。
  • FIFO特殊文件有名字,可以被打开,并且不会将内存数据刷新到磁盘中。类似于管道,只是它的创建方式不同。但FIFO特殊文件不是匿名通信通道,而是通过调用mkfifo函数输入到文件系统中。
  • 以这种方式创建FIFO特殊文件后,任何进程都可以打开它进行读取或写入,其使用方式与普通文件相同。 但是,它必须同时在两端打开,然后才能对其执行任何输入或输出操作。 打开FIFO文件进行读取通常会阻塞,直到其他进程打开相同的FIFO文件进行写入,反之亦然。

3、打开规则

  • 如果当前打开操作是以读方式打开管道(FIFO文件)时,阻塞直到有相应进程以写方式打开该命名管道(FIFO文件)。
  • 如果当前打开操作是以写方式打开管道(FIFO文件)时,阻塞直到有相应进程以读方式打开该命名管道(FIFO文件)。

五、system V共享内存

1、概念

  • 共享内存是最快的IPC形式,当这样的内存映射到共享它的进程的地址空间中时,这些进程间的数据传递不再涉及到内核,即进程不再通过执行进入内核的系统调用来传递彼此的数据。
  • 共享内存没有进行同步与互斥,即不具备访问控制。

2、共享内存示意图

在这里插入图片描述

3、相关函数

(1)ftok函数

【1】函数

在这里插入图片描述

【2】概念
  • ftok函数使用由给定路径名(pathname)命名的文件的标识(必须引用现有的、可访问的文件)和最少有效8 bits的proj_id(必须为非零)来生成key_t类型的 System V IPC 值key,生成的该值适用于 msgget、semget 和 shmget。
  • 当使用相同的 proj_id 值时,对于命名同一文件的所有路径名(pathname),结果值都是相同的。 当(同时存在的)文件标识(pathname)或proj_id 不同时,返回的值应是不同的。

(2)shmget函数

【1】函数

在这里插入图片描述

【2】概念
  • 如果存在与参数key对应的内存段,shmget函数返回与key值关联的System V共享内存段的标识符。 如果不存在与key对应的内存段,并在shmflg中指定了IPC_CREAT时,创建一个新的共享内存段,其大小等于size的值(四舍五入为 PAGE_SIZE) 的倍数,然后返回与key值关联的System V共享内存段的标识符。
  • 如果 shmflg 同时指定了IPC_CREAT和IPC_EXCL,并且 key 值关联的System V共享内存段已经存在,则shmget函数失败,并将errno设置为EEXIST。
  • IPC_CREAT:创建新区段。 如果未使用此标志,则shmget函数将找到与key关联的内存段,并检查用户是否有权访问该内存段。当单独使用此标志时,如果创建共享内存时,底层已经存在该区段,则获取它并返回;如果不存在,则创建内存段并返回。
  • IPC_EXCL:与IPC_CREAT一起使用,以确保在内存段已存在时发生故障。而单独使用IPC_EXCL时,没有意义。
  • 只有在创建共享内存段的时候才用到key值,在其他大部分情况下,用户访问共享内存段用的都是shmid,即shmget函数的返回值,它是共享内存段的用户层标识符。

(3)shmat和shmdt函数

【1】函数

在这里插入图片描述

【2】概念
  • shmat函数将shmid标识的 System V 共享内存段附加到调用进程的地址空间。如果shmaddr为 NULL,则系统会选择一个合适(未使用)的地址来链接该内存段。
  • 调用shmat函数成功后,返回附加的共享内存段的地址;失败则返回(void *) -1,并将 errno 设置为指示错误的原因。
  • shmat函数的返回值可由用户的意愿强转为其他类型以供使用,该返回值在shmdt中作为参数。
  • shmdt将位于shmaddr指定地址的共享内存段与调用进程的地址空间分离(不等于删除共享内存段)。 待分离段的shmaddr必须是已经被附加的,且必须为shmat调用的返回值,即为shmat所返回的指针。

(4)shmctl函数

【1】函数

在这里插入图片描述

【2】概念
  • shmctl函数在 System V 共享内存段上执行 cmd 指定的控制操作,操作对象为标识符shmid指定的共享内存段。
  • buf 参数是指向 shmid_ds 结构的指针,在 <sys/shm.h> 中的定义如下所示:
    在这里插入图片描述
【3】cmd的有效值
  • IPC_STAT:将信息从与 shmid 关联的内核数据结构复制到 buf 指向的shmid_ds结构中。 调用方必须具有共享内存段的读取权限。
  • IPC_SET:将 buf 指向的 shmid_ds 结构的某些成员的值写入到与此共享内存段关联的内核数据结构中,同时更新其shm_ctime成员。可以更改以下字段:shm_perm.uid、shm_perm.gid 和 shm_perm.mode(最低有效 9 位)。 调用进程的有效 UID 必须与共享内存段的所有者 (shm_perm.uid) 或创建者 (shm_perm.cuid) 匹配,否则调用方必须具有特权。
  • IPC_RMID:标记销毁标识符shmid指定的共享内存段。 只有在最后一个进程将其分离后(即,当关联结构shmid_ds的shm_nattch成员为零时),该段才会真正地被销毁。 调用方必须是所有者或创建者,或者具有特权。 如果某个段已被标记为销毁,则将设置IPC_STAT检索的关联数据结构中 shm_perm.mode 字段的(非标准)SHM_DEST标志。调用方必须确保段最终被销毁;否则,其错误的页面将保留在内存或swap中。

4、命令行操作共享内存段

  • ipcs -m:查看存在的共享内存段。
  • ipcrm -m shmid:删除shmid标识的共享内存段,即释放资源。

六、进程互斥

  • 临界资源(互斥资源):多个进程(执行流)都能看到的公共资源。
  • 临界区:进程中访问临界资源的程序段。
  • 互斥:为了更好地进行临界区的保护,让多执行流在任何时刻,都只能有一个进程进入临界区。
  • 原子性:要么不做,要么做完,没有中间状态。

本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得博主写得不错,请点赞、收藏加关注支持一下💕💕💕


原文地址:https://blog.csdn.net/Snow_Dragon_L/article/details/139073922

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!