Linux进程间通信(二)

简介: Linux进程间通信

三、System V IPC

3.1 共享内存

3.1.1 共享内存原理

共享内存让不同进程看到同一份资源的方式是:在物理内存中申请一块内存空间,然后将这块内存空间分别与各个进程各自的页表之间进行联系,再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置,使得虚拟地址和物理地址之间建立起映射关系,至此进程便看到同一份物理内存


b372b02a3ef8452291859893a1ddbf8b.png


3.1.2 "描述"共享内存

在系统当中可能会有大量的进程在进行通信,因此系统当中就可能存在大量的共享内存,那么操作系统必然要对其进行管理,所以共享内存除了在内存当中真正开辟空间之外,为了维护管理共享内存,系统一定要"描述"共享内存

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

当申请一块共享内存后,为了要让实现通信的进程能够看到同一个共享内存,因此每个共享内存被申请时都有一个key值,这个key值用于标识系统中共享内存的唯一性。

可以看到上面"描述"共享内存的结构体的第一个成员是shm_perm,shm_perm是一个ipc_perm类型的结构体变量,每个共享内存的key值存储在shm_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;
};

注意:shmid_ds和ipc_perm结构体分别在/usr/include/linux/shm.h和/usr/include/linux/ipc.h中定义


3.1.3 查看共享内存信息

使用ipcs命令可以查看共享内存的信息。但是该命令会默认列出消息队列、共享内存以及信号量相关的信息,若只想查看某一个的相关信息,可以选择携带选项


-q:列出消息队列相关信息

-m:列出共享内存相关信息

-s:列出信号量相关信息

ipcs命令列出的每列信息的含义如下:

7c8c9d58df7c4e868d3a18730610e81d.png



注意: key是内核层面上保证共享内存唯一性的方式;shmid是在用户层上保证共享内存的唯一性


3.1.4 共享内存的创建

使用shmget()函数进行共享内存的创建


int shmget(key_t key, size_t size, int shmflg);

返回值:


shmget调用成功,返回一个有效的共享内存标识符(用户层标识符)

shmget调用失败,返回-1

参数:


参数key,表示待创建共享内存在系统当中的唯一标识(需要自行生成)

参数size,表示待创建共享内存的大小

参数shmflg,表示创建共享内存的方式,可添加共享内存权限(按位或)

注意: 具有标定某种资源能力的东西被称作句柄(譬如FILE即文件句柄),而shmget函数的返回值实际上就是共享内存的句柄,其可以在用户层标识共享内存。当共享内存被创建后,在后续使用共享内存的相关接口时,都需要通过这个句柄对指定共享内存进行操作


使用ftok函数获取参数key


key_t ftok(const char *pathname, int proj_id);

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


注意:


  1. 使用ftok函数生成key值可能会产生冲突,此时可以对传入ftok函数的参数进行修改
  2. 需要进行通信的各个进程,在使用ftok函数获取key值时,都需要采用同样的路径名和和整数标识符,进而生成同一种key值,然后才能找到同一个共享资源
  3. pathname所指定的文件必须存在且可存取(有权限)

参数shmflg



选项(组合) 作用
IPC_CREAT 若不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;若存在这样的共享内存,则直接返回该共享内存的句柄
IPC_CREAT | IPC_EXCL 若内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;若存在这样的共享内存,则出错返回
IPC_EXCL 单独使用无任何作用

使用组合IPC_CREAT,一定会获得一个共享内存的句柄,但无法确认该共享内存是否是新建的共享内存

使用组合IPC_CREAT | IPC_EXCL,只有shmget函数调用成功时才会获得共享内存的句柄,并且该共享内存一定是新建的共享内存

3.1.5 共享内存的释放

当进程运行完毕后申请的共享内存依旧存在,并没有被操作系统释放,因为共享内存的生命周期是随内核的。若进程不主动删除创建的共享内存,那么共享内存就会一直存在,直到关机重启(system V IPC都是如此)。

用命令释放共享内存资源

使用ipcrm -m shmid命令

b3a114126a7b49e88e58f39fe5508dbe.png

使用程序接口释放共享内存资源

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数shmid,表示所控制共享内存的用户级标识符

参数cmd,表示具体的控制动作

第参数buf,用于获取或设置所控制共享内存的数据结构

参数cmd:


a6354191be5647ccbd2622a3f6f2e36a.png


返回值:


shmctl调用成功,返回0

shmctl调用失败,返回-1

3.1.6 共享内存的关联

建立共享内存与进程地址空间的映射关系时需要使用shmat函数

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数shmid,表示待关联共享内存的用户级标识符

参数shmaddr,指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自行决定合适的地址位置

参数shmflg,表示关联共享内存时设置的某些属性

参数shmflg:


b64fd89bcbc94ba2988eaf6c5d8fd14f.png


返回值:


shmat调用成功,返回共享内存映射到进程地址空间中的起始地址

shmat调用失败,返回(void*)-1

3.1.7 共享内存的去关联

取消共享内存与进程地址空间之间的关联需使用shmdt()函数

int shmdt(const void *shmaddr);

shmaddr参数:


待去关联共享内存的起始地址,即调用shmat函数时得到的起始地址

返回值:


shmdt调用成功,返回0

shmdt调用失败,返回-1

3.1.8 利用共享内存实现serve&&client通信

服务端创建一个新的共享内存并建立连接

//my_server.cxx
#include "com.h"
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 | 0666); //创建新的共享内存
  if (shm < 0) {
    perror("shmget");
    return 2;
  }
  char* mem = (char*)shmat(shm, NULL, 0); //关联共享内存
    //服务端不断读取共享内存当中的数据并输出
    while (1) {
      printf("client# %s\n", mem);
      sleep(1);
    }
  shmdt(mem); //共享内存去关联
  shmctl(shm, IPC_RMID, NULL); //释放共享内存
  return 0;
}


客户端连接的是已创建好的共享内存,并往其中不断写入数据

//my_client.cxx
#include "com.h"
int main()
{
  key_t key = ftok(PATHNAME, PROJ_ID); //获取与server进程相同的key值
  if (key < 0) {
    perror("ftok");
    return 1;
  }
  int shm = shmget(key, SIZE, IPC_CREAT); //获取server进程创建的共享内存的用户层id
  if (shm < 0) {
    perror("shmget");
    return 2;
  }
  char* mem = (char*)shmat(shm, NULL, 0); //关联共享内存
    //客户端向共享内存写入数据
    int i = 0;
    while (1) {
      mem[i++] = 'A' + i;
      mem[i] = '\0';
      sleep(1);
    }
  shmdt(mem); //共享内存去关联
  return 0;
}

服务端和客户端代码中都包含该头文件,确保生成的key、路径、连接的共享内存相同


#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#define PATHNAME "/home/bjy/BaoLinux/code/signal_com/shm/shm_2" //路径名
#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小


4513c4096c5040e18ac3d00b1e07c4b8.png

3.1.9 共享内存与管道对比

共享内存创建好后就不需要调用系统接口进行通信了,而管道创建好后仍需要read、write等系统接口进行通信。共享内存是所有进程间通信方式中最快的一种通信方式

980e3480b30b4a2c8d026c5e23f4e8c7.png



使用管道通信的方式,将文件中的数据从一个进程传输到另一个进程需要进行四次拷贝操作:


  1. 服务端将信息从输入文件复制到服务端的临时缓冲区中
  2. 将服务端临时缓冲区的信息复制到管道中
  3. 客户端将信息从管道复制到客户端的缓冲区中
  4. 将客户端临时缓冲区的信息复制到输出文件中

4eb1c03645b0484c9527783306984ca4.png


使用共享内存进行通信,将文件中的数据从一个进程传输到另一个进程只需要进行两次拷贝操作:


1.从输入文件到共享内存

2.从共享内存到输出文件

共享内存同样也存在问题,其并没有提供任何的保护机制,类似于同步与互斥


3.2 消息队列

该技术已面临淘汰,等博主有时间会进行更新


3.3 信号量

后续博主有时间会进行更新


目录
相关文章
|
7月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
5月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
237 67
|
4月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
117 16
|
4月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
93 20
|
9月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
260 1
|
3月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
75 0
|
3月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
102 0
|
3月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
66 0
|
3月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
68 0
|
6月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
221 4