Linux系统编程之命名管道与共享内存
在上一篇博客中,我们已经熟悉并使用了匿名管道,这篇博客我们将讲述进程间通信另外两种常见方式——命名管道与共享内存。
1.命名管道
管道是使用文件的方式,进行进程之间的通信。因此对于管道的操作,实际上还是用诸如write,read等接口实现。
匿名管道应用的一个限制就是只能在具有亲缘关系(如父进程与子进程、兄弟进程)之间进行通信。如果想在不相关的进程间进行数据交换,可以使用FIFO文件来做这种工作。
这里的FIFO文件即我们所说的命名管道。必须强调的是,虽然FIFO是一种文件,但实际上数据的读写都是在操作系统开辟的内存缓冲区中进行的,并不会真的写入磁盘中。如果那样,进程间通信的效率将会极大降低!
1.1 创建命名管道
1.1.1 使用命令行创建
在命令行中使用mkfifo 管道名
的方式来创建命名管道。如下图
可以看到文件类型为p,即管道类型的文件。文件大小为0,即便写入到pipe中的数据没有被另一个进程读出,依然是0!因为根本不会将数据写入到磁盘中。
下例中我们将hello world重定向到pipe中,并且从pipe中读出数据显示到屏幕。使用两个shell,进入到同一个目录下,一个在命令行输入echo hello world >pipe
,另一个输入 cat < pipe
, 可以看到在另一个shell的屏幕上出现了hello world。
1.1.2 使用接口创建
在命令行输入man 3 mkfifo
后可以看到如下内容:
该接口一共有两个参数,其中第一个创建的fifo文件的路径,第二个是文件权限,与我们之前学习的文件操作的权限一模一样。返回值如果等于0则创建成功,-1则创建失败。
1.2 匿名管道和命名管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open。
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。其余操方式与文件的操作没有区别。
2. system V共享内存
2.1 什么是共享内存?
共享内存是最快的IPC形式,是系统在内存中开辟一块内存,用于进程之间共享地进行操作。这片内存区域经过页表映射到通信进程各自的地址空间中,不同的进程可以通过操作自己的地址空间,来操作共享内存。如下图所示:
2.2 共享内存函数
共享内存的创建及销毁一般分为以下几步:
- 操作系统在内存中申请一片内存
- 将共享内存挂接到进程地址空间中
- 使用完毕后,将共享内存和进程地址空间去关联
- 操作系统销毁共享内存
因此,操作系统提供了以下几组接口:
2.2.1 ftok函数
key_t ftok(const char *pathname, int proj_id);
该函数有两个参数,第一个是路径名称,第二个是项目id。两者配合用于创建唯一的key值,该值可以在系统内标定唯一的通信资源。(为什么这里不说标定唯一的共享内存,是因为system v包含共享内存,消息队列,信号量三种通信方式。三种通信方式都是使用ftok函数创建唯一的key来标定唯一性的。)
2.2.2 shmget函数
int shmget(key_t key, size_t size, int shmflg);
该函数用来创建共享内存。有三个参数:
key即系统层面上这个共享内存段名字,就是我们用ftok获取的唯一值。
size是共享内存大小,一般是页的整数倍。(一个page是4096 bytes
shmflg是由九个权限标志构成的,它们的用法和创建文件时使用的mode模式标志是一样的。IPC_CREATE 的用法是如果key标定的共享内存不存在则创建,若存在则直接使用。IPC_EXCL一般要配合IPC_CREATE使用,即key标定的共享内存不存在则创建,若存在则报错,常用于申请开辟共享内存的一端。除此之外,在创建共享内存时,还应该标定该共享内存的权限(与文件中设置权限如出一辙),如0664等。
返回值:成功返回一个非负整数,即该共享内存段的标识码(注意,该标识码是用户层面的标识码,简单,方便用户使用!而key是系统层面的!;失败返回-1。)
2.2.3 shmat函数
void* shmat(int shmid, const void* shmaddr, int shmflg);
该函数用于将共享内存挂接到进程地址空间中。
shmid参数是使用shmget获取的用户层面的共享内存标识符。
shmaddr参数是挂接到进程地址空间中的起始虚拟地址,用于一般不关心,设置为NULL,让操作系统自己选择分配。
shmflg参数:0即对该共享内存有读写权限,另一个SHM_RDONLY即对该共享内存有只读权限。
返回值:成功返回一个指针,指向共享内存起始地址;失败返回-1。
2.2.4 shmdt函数
int shmdt (const void* shmaddr);
该函数用于将共享内存与当前进程去关联。
唯一的一个参数shmaddr是由shmat所返回的指针。
去关联成功则返回0,失败则返回-1。
注意:去关联与销毁共享内存是完全不同的两个概念!
2.2.5 shmctl函数
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
功能:用于控制共享内存(常用于销毁共享内存)
第一个参数shmid是shmget函数返回的共享内存标识符,cmd有三个可以采取的动作,常使用IPC_RMID来销毁共享内存,buf指向一个保存着共享内存的模式状态和访问权的数据结构,如果是为了销毁该共享内存,直接置为NULL。
成功返回0,失败返回-1。
3.共享内存的数据结构
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 */
};
最前面的 struct ipc_perm shm_perm
是system v的三种通信方式共有的,该结构体内就有key值。
unsigned short shm_nattch;
这个字段即挂接该共享内存的进程数。
4.使用共享内存的例子
4.1实例代码
Makefile:
.PHONY: all
all: server client
client: client.c comm.c
gcc -o $@ $^
server: server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
comm.h
#ifndef COMM_H
#define COMM_H
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#define PATHNAME "."
#define PROJ_ID 0x66
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
#endif
comm.c
#include "comm.h"
static int commShm(int size, int flags)
{
key_t _key = ftok(PATHNAME, PROJ_ID);
if(_key < 0)
{
perror("ftok");
return -1;
}
int shmid = shmget(_key, size, flags);
if(shmid < 0)
{
perror("shmget");
return -2;
}
return shmid;
}
int destroyShm(int shmid)
{
if(shmctl(shmid, IPC_RMID, NULL) < 0)
{
perror("shmctl");
return -1;
}
return 0;
}
int createShm(int size)
{
return commShm(size, IPC_CREAT|IPC_EXCL|0666);
}
//creatShm是server端创建shm,getShm是client端获取创建好的shm
int getShm(int size)
{
return commShm(size, IPC_CREAT);
}
server.c
#include "comm.h"
int main()
{
int shmid = createShm(4096);
char* addr = (char*)shmat(shmid, NULL, 0);
sleep(2);
while(1)
{
printf("client# %s\n",addr);
sleep(1);
}
shmdt(addr);
sleep(2);
destroyShm(shmid);
return 0;
}
client.c
#include "comm.h"
int main()
{
int shmid = getShm(4096);
sleep(1);
char *addr = (char*)shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(1)
addr[i] = 'A'+i;
i++;
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}
4.2 效果展示
在./server运行之前,在命令行输入ipcs -m命令,查看此时共享内存情况。发现系统中此时没有共享内存。
./server运行后,此时可以看到nattach变为1,即有一个进程挂接到该共享内存。
运行./client后,此时nattch变为2,即此时有两个进程挂接到该共享内存。
在./client端ctrl+C之后,发现./server端还在打印,此时打印的数据不再改变,因为没有进程向共享内存中写入数据。此时nattch变为1。
在关闭./server之后,发现nattch变为1。但是,标识符为5的共享内存仍然存在!!也就是说,共享内存的生命周期随内核!!而不是随进程!!
4.3 共享内存的特性
通过4.2中的例子,我们可以得到共享内存的以下特性:
1.共享内存的生命周期随内存。
2.系统层面是用key来标识共享内存的,而用户层面是通过shmid来进行标识,且shmid比key要简单得多。
3.可以使用ipcs -m
命令来查看共享内存的状态,使用ipcrm -m +shmid
来删除共享内存。
4.删除共享内存是销毁内存中的内存空间,而去关联实际上是删除进程页表中进程地址空间和对应共享内存的映射关系。
5.如果在删除的时候,nattch字段为0,那么内核中描述共享内存的结构体也被释放了。如果在删除的时候,nattch字段不为0,那么key会变为0x00000000.表示当前共享内存不能被其他进程挂接,共享内存的status变为destroy。如下图所示:在client运行过程中使用ipcrm -m 6删除6号共享内存,再使用ipcs -m查看,此时key为0x00000000,status为dest。
在共享内存status为dest时,一旦该共享内存的进程挂接数为0,共享内存将会被立即销毁。如下图所示。在./client被crtl C后,使用ipcs -m命令,此时系统中再无共享内存。
6.共享内存不提供同步与互斥机制(回想一下,管道是否提供)。
7.共享内存是最快的进程通信方式。因为共享内存写是覆盖写的方式,读是直接访问地址。而我们之前所学习的管道,需要通过系统调用(如write,read)来进行数据的读写,相比之下,共享内存的数据拷贝次数更少,因此效率也会更高。
3.其他进程通信方式
除了共享内存,system v还提供了消息队列和信号量两种进程通信方式,其中消息队列还是为了实现进程间的数据交换,而信号量主要是用于实现进程之间的同步与互斥机制。
3.1 system V消息队列
消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。
每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。
3.2 system V信号量
信号量主要用于同步和互斥的。
由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。
系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。在进程中涉及到互斥资源的程序段叫临界区。
关于同步与互斥,我们将在多线程部分重点学习。
Linux系统编程之命名管道与共享内存的更多相关文章
- Linux系统编程之匿名管道
1.进程间通信介绍 1.1 进程通信的基本概念 在之前我们已经学习过进程地址空间.Linux 环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间.任何一个进程的全局变量在另一个进程中都看不 ...
- Linux系统编程——进程间通信:管道(pipe)
管道的概述 管道也叫无名管道,它是是 UNIX 系统 IPC(进程间通信) 的最古老形式,全部的 UNIX 系统都支持这样的通信机制. 无名管道有例如以下特点: 1.半双工,数据在同一时刻仅仅能在一个 ...
- Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道
Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...
- linux系统编程之管道(三)
今天继续研究管道的内容,这次主要是研究一下命名管道,以及与之前学过的匿名管道的区别,话不多说,进入正题: 所以说,我们要知道命名管道的作用,可以进行毫无关系的两个进程间进行通讯,这是匿名管道所无法实现 ...
- Linux系统编程@进程通信(一)
进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...
- 读书笔记之Linux系统编程与深入理解Linux内核
前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...
- Linux 系统编程 学习 总结
背景 整理了Liunx 关于 进程间通信的 很常见的知识. 目录 与 说明 Linux 系统编程 学习:000-有关概念 介绍了有关的基础概念,为以后的学习打下基础. Linux 系统编程 学习:00 ...
- Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号
Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...
- Linux 系统编程 学习:04-进程间通信2:System V IPC(1)
Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...
随机推荐
- Windows主机入侵排查
检查系统信息.用户账号信息 系统信息 ● 查看系统版本以及补丁信息 systeminfo 用户账号信息 ● 基本使用 ○ 创建普通账号并加入administrarors 组 net user test ...
- Noip模拟21(持续翻车)2021.7.20
读题总是读错是不是没救了... T1 Median 中位数:按顺序排列的一组数据中居于中间位置的数. 能用上的高亮符号都用上了... 当时忘了就离谱.... 理解什么是中位数(真是个憨憨)后就可以开始 ...
- VS2017+QT5.12.10+QGIS3.16环境搭建及开发全流程
题记:大力发展生产力,助力高效采集.(转载请注明出处https://www.cnblogs.com/1024bytes/p/15477374.html) 本篇随笔分为五个部分: 一.获取QGIS3.1 ...
- Luogu P3758 [TJOI2017]可乐 | 矩阵乘法
题目链接 让我们先来思考一个问题,在一张包含$n$个点的图上,如何求走两步后从任意一点$i$到任意一点$j$的方案数. 我们用$F_p(i,j)$来表示走$p$步后从$i$到$j$的方案数,如果存储原 ...
- linux 内核源代码情景分析——linux 内核源码中的汇编语言代码
1. 用汇编语言编写部分核心代码的原因: ① 操作系统内核中的底层程序直接与硬件打交道,需要用到一些专用的指令,而这些指令在C语言中并无对应的语言成分: ② CPU中的一些特殊指令也没有对应的C语言成 ...
- x64 InlineHook 黑魔法
目录 x64 InlineHook 黑魔法 为什么不能用X86 的HOOK方式? 原理:jmp + rip 进行寻址6字节方式跳转 手动InlineHook 临时地址x(找一块空内存) 计算偏移 源地 ...
- mysql查看数据库大小
要想知道每个数据库的大小的话,步骤如下: 1.进入information_schema 数据库(存放了其他的数据库的信息) use information_schema; 2.查询所有数据的大小: s ...
- newusers 拷贝服务器A上的用户,批量添加到其它服务器
服务器B 需要添加多个用户,要求与服务器A 的用户列表一致 1.拷贝服务器A 上的 /etc/passwd 中用户信息,用user1-10为例 #grep ^user /etc/passwd > ...
- 【linux命令】 磁盘管理
du du是查看硬盘的使用情况,统计文件或目录的空间大小. -a 显示所有目录或文件的大小 -b 以byte为单位,显示目录或文件的大小 -c 显示目录或文件的总和 -k 以KB为单位输出 -m 以M ...
- k8s入坑之路(9)k8s网络插件详解
Flannel: 最成熟.最简单的选择 Calico: 性能好.灵活性最强,目前的企业级主流 Canal: 将Flannel提供的网络层与Calico的网络策略功能集成在一起. Weave: 独有的功 ...