2024年C C++最新Linux系统-进程间通信_linux 进程间通讯(3),字节C C++面试必问

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

父进程进行读,子进程进行写;父进程进行写,子进程进行读

  • 示图:

image-20220318234608191

  • 注意:
  1. 只有在先fork之前读写打开文件,父子进程才能共享相同的文件指针数组,进一步灵活控制读写
  2. 管道只能够进行单向通信,关闭对应的读写端也是为了避免误操作
  3. 从管道写端写入的数据会被内核缓冲,直到从管道的读端被读取
  • 以文件描述符视角理解:

image-20220319093022133

  • 以内核角度理解:

image-20220319093214615

  • 注意:
  1. 管道就是特殊的文件,管道的使用和文件一致
  2. 但是依靠管道通信的本质上依靠管道的缓冲区进行读写,其缓冲并不会真正的刷新到磁盘上
  • 管道读写规则:
  1. 写端不写,读端无数据可读

O_NONBLOCK disable:read调用阻塞,即进程暂停执行,进行等待写端写入数据

O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN

  1. 写端不写,并将写端文件关闭

如果所有管道写端对应的文件描述符被关闭,则read返回0

  1. 读端不读,写端一直写

O_NONBLOCK disable: write调用阻塞,直到有进程读走管道缓冲区的数据

O_NONBLOCK enable: write调用返回-1,errno值为EAGAIN

  1. 读端不读,并将读端文件关闭

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程被终止退出

  • 示图:

image-20220319103050416

  1. 数据写入的原子性

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性

注:原子性是指 一个操作是不可中断的,要么全部执行成功要么全部执行失败,即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰

  • 管道特点:
  1. 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常父子进程之间就可应用该管道
  2. 管道提供流式服务,面向字节流,读写以字节为单位进行
  3. 进程退出,管道释放,所以管道的生命周期随进程内核会对管道操作进行同步与互斥,即保证数据的原子性
  4. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
  • 示图:

image-20220319101539384

2、命名管道

  • 概念:
  1. 对于匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信
  2. 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道
  • 命名管道创建命令:
mkfifo filename

  • 示例:

image-20220319103748275

  • 命名管道创建函数原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char \*filename, mode_t mode);

注:第一个参数即为管道的名称,第二个参数即为创建管道文件的权限,创建成功返回0,否则返回-1

  • 示例:
int main()
{
    mkfifo("fifo", 0644);
    return 0;
}

  • 匿名管道与命名管道的区别
  1. 匿名管道由pipe函数创建并打开,依靠父子进程的共享特性看到同一份文件资源
  2. 命名管道由mkfifo函数创建并主动调用函数打开,依靠文件路径的唯一性让不同进行找到并打开同一份文件资源
  3. FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义
  • 命名管道的打开规则
  1. 如果当前打开操作是为读而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

O_NONBLOCK enable:立刻返回成功

  1. 如果当前打开操作是为写而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO

O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

  • 示例:用命名管道实现server&client通信
server.c:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define FIFO "fifo"

int main()
{
    //创建命名管道
    if(mkfifo(FIFO,0644)<0)
    {
        perror("mkfifo");
        exit(1);
    }
    //打开管道文件
    int fd=open(FIFO,O_RDONLY);
    if(fd<0)
    {
        perror("open");
        exit(2);
    }
    //服务端进行客户端信息
    while(1)
    {
        char buffer[128]={0};
        //输出标识词
        printf("client#");
        fflush(stdout);
        //读取管道数据
        ssize_t s=read(fd,buffer,sizeof(buffer)-1);
        if(s>0)
        {
            buffer[s]=0;
            printf("%s",buffer);
        }
        else if(s==0)
        {
            printf("write close,child quit\n");
            break;
        }
        else 
            break;
    }
    
    return 0;
}
client.c:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO "fifo"

int main()
{
    //打开管道文件
    int fd=open(FIFO,O_WRONLY);
    if(fd<0)
    {
        perror("open");
        exit(2);
    }
    //向服务端发送消息
    while(1)
    {
        char buffer[128]={0};
        //输出标识词
        printf("please enter#");
        fflush(stdout);
        //读入数据
        ssize_t s=read(0,buffer,sizeof(buffer)-1);
        if(s>0)//写入到管道
        {
            buffer[s]=0;
            write(fd,buffer,strlen(buffer));
        }
        else 
            break;
    }
    
    return 0;
}

  • 效果:

image-20220319114500703

三、system V

1、共享内存概念及原理

  • 概念:
  1. 管道通信本质是基于文件的,也就是说操作系统并没有为此做过多的设计工作,而system V IPC是操作系统特地设计的一种通信方式;但是不管怎么样,它们的本质都是一样的,都是在想尽办法让不同的进程看到同一份由操作系统提供的资源
  2. 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
  • system V IPC提供的通信方式有以下三种:
  1. system V共享内存
  2. system V消息队列
  3. system V信号量

注:system V共享内存和system V消息队列是以传送数据为目的的,而system V信号量是为了保证进程间的同步与互斥而设计的,虽然system V信号量和通信好像没有直接关系,但属于通信范畴

  • 共享内存的基本原理:
  1. 用户申请共享内存:OS在物理内存当中申请一块内存空间
  2. 进程主动挂接共享内存:OS将这块内存空间分别与各个进程的进程地址空间建立映射关系(共享内存映射进进程地址空间的共享区)
  3. 各进程看到同一空间资源:OS将映射后的的共享内存的虚拟地址返回给进程
  • 示图:

image-20220319114553110

注:这里所说的开辟物理空间、建立映射等操作都是调用系统接口完成的,也就是说这些动作都由操作系统来完成

  • 共享内存数据结构:
  1. 各个进程都可以进行申请共享内存,那么共享内存的需求就可能非常多,而OS也需要进行对共享内容的管理,而管理的本质就是:先描述,再组织
  2. 所以共享内存除了在内存当中真正开辟空间之外,系统一定还要为共享内存维护相关的内核数据结构
  • shmid_ds结构定义:
struct shmid\_ds {
    struct ipc\_perm shm_perm; /\* operation perms \*/
    int shm_segsz; /\* size of segment (bytes) \*/
    __kernel_time_t shm_atime; /\* last attach time \*/
    __kernel_time_t shm_dtime; /\* last detach time \*/
    __kernel_time_t shm_ctime; /\* last change time \*/
    __kernel_ipc_pid_t shm_cpid; /\* pid of creator \*/
    __kernel_ipc_pid_t shm_lpid; /\* pid of last operator \*/
    unsigned short shm_nattch; /\* no. of current attaches \*/
    unsigned short shm_unused; /\* compatibility \*/
    void \*shm_unused2; /\* ditto - used by DIPC \*/
    void \*shm_unused3; /\* unused \*/
};

  • 注意:
  1. 当申请了一块共享内存后,为了让要实现通信的进程能够找到同一个共享内存进行挂接,每一个共享内存结构体中会存储一个key值,这个key值用于标识系统中共享内存的唯一性
  2. 上面共享内存数据结构的第一个成员shm_perm,每个共享内存的key值存储在shm_perm这个结构体变量当中
  • ipc_perm结构体的定义:
struct ipc\_perm{
	__kernel_key_t  key;
	__kernel_uid_t  uid;
	__kernel_gid_t  gid;
	__kernel_uid_t  cuid;
	__kernel_gid_t  cgid;
	__kernel_mode_t mode;
	unsigned short  seq;
};

  • 共享内存使用过程:
  1. 调用系统接口进行在物理内存中申请共享内存空间
  2. 调用接口将申请到的共享内存挂接到地址空间,建立映射关系
  3. 使用之后调用接口将共享内存与地址空间去关联,取消映射关系
  4. 调用接口释放共享内存空间,将物理内存归还给系统

2、共享内存使用接口介绍

1、共享内存资源的查看
  • 如何查看共享内存资源:

使用ipcs命令查看有关进程间通信设施的信息

  • 选项:
-q:列出消息队列相关信息
-m:列出共享内存相关信息
-s:列出信号量相关信息

注:单独使用ipcs命令时,会默认列出消息队列、共享内存以及信号量相关的信息

  • 示图:

image-20220319134909250

  • ipcs输出信息含义:
标题含义
key系统区别各个共享内存的唯一标识
shmid共享内存的用户层id(句柄)
owner共享内存的拥有者
perms共享内存的权限
bytes共享内存的大小
nattch关联共享内存的进程数
status共享内存的状态

注:key标识共享内存唯一性的方式,而shmid是用于用户指明操作对象,key和shmid之间的关系类似于inode和fd之间的的关系

2、共享内存的创建和释放
  • ftok函数的函数原型:
key_t ftok(const char \*pathname, int proj_id);

  • 解释:

功能:将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构当中

  • 注意:
  1. pathname所指定的文件必须存在且可存取;使用ftok函数生成key值存在可能会产生冲突
  2. 进行通信的各个进程在使用ftok函数获取key值时,需要采用同样的路径名和和整数标识符,进而生成同一种key值找到同一份共享内存
  • shmget函数的函数原型:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

  • 解释:
  1. 功能:向系统申请共享内存
  2. 参数:第一个参数key,表示待创建共享内存在系统当中的唯一标识;第二个参数size,表示待创建共享内存的大小;第三个参数shmflg,表示创建共享内存的方式
  3. 返回值:shmget调用成功,返回一个有效的共享内存标识符,用于进行操作;shmget调用失败,返回-1

注:这里shmget函数的返回值实际上就是共享内存的句柄,这个句柄可以在用户层标识共享内存,当共享内存被创建后,我们在后续使用共享内存的相关接口时,都是需要通过这个句柄对指定共享内存进行各种操作

  • 第三个参数shmflg常用组合方式:
组合方式作用
IPC_CREAT如果内核中不存在与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则直接返回该共享内存的句柄,即该共享内存可能是已有的也可能的新建的
IPC_CREATIPC_EXCL
  • 示例:
#include <stdio.h>
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <unistd.h>
		
#define PATHNAME "./server.c" 
#define PROJ\_ID 0x6666 
#define SIZE 4096 

int main()
{
	key_t key = ftok(PATHNAME, PROJ_ID); //获取key值
	if (key < 0){
		perror("ftok");
		return 1;
	}
	int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL); //创建共享内存
	if (shm < 0){
		perror("shmget");
		return 2;
	}
	printf("key: %x\n", key); 
	printf("shm: %d\n", shm); 
	return 0;
}

  • 效果:

image-20220319134959008

  • 注意:
  1. 进程运行完毕后,申请的共享内存依旧存在,即共享内存的生命周期是随内核的,也就是说共享内存并不会主动随进程的退出而释放
  2. 如果进程不主动删除创建的共享内存,那么共享内存就会一直存在,直到关机重启(system V IPC都是如此)
  3. 在命令行中我们可以使用命令ipcrm -m shmid释放共享内存

示图:image-20220319140001394

  • shmctl函数的函数原型:
int shmctl(int shmid, int cmd, struct shmid\_ds \*buf);

  • 解释:
  1. 功能:控制对应的共享内存资源
  2. 参数:第一个参数shmid表示要控制的共享内存;第二个参数cmd,表示具体的控制动作;第三个参数buf,用于获取或设置所控制共享内存的数据结构
  3. 返回值:shmctl调用成功,返回0;shmctl调用失败,返回-1
  • shmctl函数的第二个参数常用的传入选项:
选项作用
IPC_STAT获取共享内存的当前关联值,此时参数buf作为输出型参数
IPC_SET在进程有足够权限的前提下,将共享内存的当前关联值设置为buf所指的数据结构中的值
IPC_RMID删除释放共享内存段

注:一般使用接口进行释放对应的共享内存资源

3、共享内存的链接与去连接
  • shmat函数的函数原型:
void \*shmat(int shmid, const void \*shmaddr, int shmflg);

  • 解释:
  1. 功能:将共享内存与进程建立映射关系
  2. 参数:第一个参数表示要关联的共享内存的对应的shmid;第二个参数shmaddr指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自己决定一个合适的地址位置;第三个参数shmflg,表示关联共享内存时设置的某些属性,一般设置为0
  3. 返回值:shmat调用成功,返回共享内存映射到进程地址空间中的起始地址;shmat调用失败,返回(void*)-1
  • shmat函数第三个参数的常用传入选项:
选项作用
SHM_RDONLY关联共享内存后只进行读取操作
SHM_RND若shmaddr不为NULL,则关联地址自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr%SHMLBA)
0默认为读写权限
  • shmdt函数的函数原型:
int shmdt(const void \*shmaddr);

  • 解释:
  1. 功能:取消共享内存与进程的映射关系
  2. 参数:待去关联共享内存的起始地址,即调用shmat函数时得到的起始地址
  3. 返回值:shmdt调用成功,返回0;shmdt调用失败,返回-1
4、接口使用示例

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

0

  1. 返回值:shmat调用成功,返回共享内存映射到进程地址空间中的起始地址;shmat调用失败,返回(void*)-1
  • shmat函数第三个参数的常用传入选项:
选项作用
SHM_RDONLY关联共享内存后只进行读取操作
SHM_RND若shmaddr不为NULL,则关联地址自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr%SHMLBA)
0默认为读写权限
  • shmdt函数的函数原型:
int shmdt(const void \*shmaddr);

  • 解释:
  1. 功能:取消共享内存与进程的映射关系
  2. 参数:待去关联共享内存的起始地址,即调用shmat函数时得到的起始地址
  3. 返回值:shmdt调用成功,返回0;shmdt调用失败,返回-1
4、接口使用示例

[外链图片转存中…(img-IK0Sb5ov-1715552115865)]
[外链图片转存中…(img-iGsqV9Xs-1715552115866)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值