IPC(Inter-Process Communication,进程间通讯)可以有三种信息共享方式(随文件系统,随内核,随共享内存)。(当然这里虽然说是进程间通讯,其实也是可以和线程相通的)。

相对的IPC的持续性(Persistence of IPC Object)也有三种:

  1. 随进程持续的(Process-Persistent IPC)

    IPC对象一直存在,直到最后拥有他的进程被关闭为止,典型的IPC有pipes(管道)和FIFOs(先进先出对象)

  2. 随内核持续的(Kernel-persistent IPC)

    IPC对象一直存在直到内核被重启或者对象被显式关闭为止,在Unix中这种对象有System v 消息队列,信号量,共享内存。(注意Posix消息队列,信号量和共享内存被要求为至少是内核持续的,但是也有可能是文件持续的,这样看系统的具体实现)。

  3. 随文件系统持续的(FileSystem-persistent IPC)

    除非IPC对象被显式删除,否则IPC对象会一直保持(即使内核才重启了也是会留着的)。如果Posix消息队列,信号量,和共享内存都是用内存映射文件的方法,那么这些IPC都有着这样的属性。

  不同的Unix IPC的持续性:

  1. 随进程:

    Pipe, FIFO, Posix的mutex(互斥锁), condition variable(条件变量), read-write lock(读写锁),memory-based semaphore(基于内存的信号量) 以及 fcntl record lock,TCP和UDP套接字,Unix domain socket

  2. 随内核:

    Posix的message queue(消息队列), named semaphore(命名信号量), System V Message queue, semaphore, shared memory。

  要注意的是,虽然上面所列的IPC并没有随文件系统的,但是我们就像我们刚才所说的那样,Posix IPC可能会跟着系统具体实现而不同(具有不同的持续性),举个例子,写入文件肯定是一个文件系统持续性的操作,但是通常来说IPC不会这样实现。很少有IPC会实现文件系统持续,因为这会降低性能,不符合IPC的设计初衷。

Unix的IPC有一些是有名的,有一些是无名的,到具体使用的时候就知道了,如果是无名IPC(典型是Pipe),必须是依赖于进程的,但是有名IPC(典型是FIFOs),就可以使用在两个没有依赖性的进程上(依赖性可以表现在,一个进程是另一个进程的子进程)。

Posix IPC


Posix的全称是 "Portable Operating System Interface",Posix不仅仅是一个单一标准,而且是IEEE(Institute for Electrical and Electronics Engineers, Inc. IEEE)指定的一个标准族。
       Posix IPC一共有三个,就是Message Queue(消息队列),semaphores(信号量),Shared Memory(共享内存),在Posix.1中,这三个IPC的命名规则为:

  • 必须符合已有的路径名规则(必须最多由PATH_MAX个字节构成,包括结尾的空字符)。
  • 如果它以斜杠开头,那么对这些函数的不同调用将访问同一个队列,如果它不以斜杠符开头,那么效果取决于实现
  • 名字中的额外斜杠符的解释由实现来定义。

由于Unix系统不同发行版的命名系统的规则都很不一样,所以使用Posix的时候需要注意命名规则。为了预防这种移植性问题,Unix系统给了定义了三个宏:

S_TPYEISMQ(buf)
S_TYPEISSEM(buf)
S_TYPEISSHM(buf)

这三个宏的作用就是为了检测当前系统是否有着对Posix IPC(message queue, semaphores, shared memory)的不同实现方式,buf是一个指向stat结构体的一个结构体(就是那个可以给fstat,lstat或者stat函数填充的那个缓冲结构)。如果当前系统对于特定的IPC有着不同的实现方式,那么对应的宏将会返回非0值,否则就会返回0。 
       然而这三个宏很少使用,因为没有一个系统保证这三个宏对应的Posix IPC(message queue, semaphores, shared memory)会在不同的系统有不同的实现方式。比如在Solaris2.6系统,这三个宏都是返回0的。

我们可以使用下面的函数来新建一个Posix IPC的名字:

char *px_ipc_name(const char *name)
{
char *dir, *dst, *slash;
if((dst = malloc(PATH_MAX)) == NULL)
{
if((dir = getenv("PX_IPC_NAME")) == NULL)
{
#ifdef POSIX_IPC_PREFIX
dir = POSIX_IPC_PREFIX
#else
dir = "/tmp/";
}
}
slash = (dir[strlen(dir) - ] == '/') ? "" : "/";
snprintf(dst, PATH_MAX, "%s%s%s", dir, slash, name);
return dst;
}

Posix IPC的通道的打开与创建

打开Posix的三种IPC通道其实用的是三个不同的函数mq_open(打开消息队列),sem_open(打开信号量),shm_open(打开共享内存),这三种个函数都可以用不同的打开方式来创建IPC通道,除了最平常的O_RDONLY(只读), O_WRONLY(只写), O_RDWR(可读可写)外,还有四个方式

  1. O_CREAT: 
    创建一个不存在的消息通道,信号量或者共享内存IPC,当创建一个新的消息队列,信号量,或者共享内存时,它们的userID会被设置为进程有效的userID。信号量,共享内存的groupID会被设置为进程有效的groupID或者是系统默认groupID;消息队列的groupID会被设置为进程的groupID。
  2. O_EXCL: 当这个标志和O_CREAT一起用的时候,只能创建不存在的消息通道,信号量或者共享内存IPC,如果所创建的IPC已经存在,创建函数(就是那三个函数)将会返回EEXIST错误。(注意单独的O_EXCL是没有意义的)
  3. O_NONBLOCK: 
    这个标志可以让消息队列读一个空的队列或者写一个满的队列。
  4. O_TRUNC 
    只作用于共享内存,如果共享内存以读写方式打开,那么创建的IPC将会以0的长度创建。

关于Posix IPC权限
       新的消息队列,有名信号量或者共享内存区的IPC对象是由oflags参数中含有O/_CREAT标志的mq_open,sem_open或者shm_open函数创建的,这些权限位与IPC类型的每个对象相关联,就像它们与每个Unix文件相关联一样(这个是很容易想象的,因为Unix就是把内存的操作对象映射到文件当中方便操作的。)

当同样由这三个函数打开一个已经存在的消息队列,信号量或者共享内存对象的时候(或者未指定O_CREAT,或者制定了O_CREAT但没有指定O_EXCL,同时对象已经存在),将基于如下信息执行权限测试:

  1. 创建时赋予该IPC对象的权限位;
  2. 所请求的访问类型(O/RDONLY,O/WRONLY或者O_RDWR);
  3. 调用进程的有效用户ID,有效组ID以及各个辅助组ID(如果支持辅助组的话)

  

  大多数Unix内核按照如下步骤执行权限测试(如果如下步骤有哪一步不满足,那么其下面的步骤都不执行,操作视为失败)

  1. 如果当前进程的有效用户ID为0(superuser),那就允许访问
  2. 在当前进程的有效用户ID就等于该IPC对象的属主ID的前提下,如果相应的用户访问权限位已经设置,那就允许访问,否则拒绝访问。 
           这里的相应的访问权限位的意思是:如果当前进程为读访问而打开IPC对象,那么用户读权限位必须设置,如果当前进程为写访问而打开该IPC对象,那么用户写权限位必须设置
  3. 在当前进程的有效组ID或它的某个辅助组ID等于该IPC对象的组ID的前提下,如果相应的组访问权限位已经设置,那么就允许访问,否则拒绝访问。
  4. 如果相应的其他用户权限位已经设置,那么就允许访问,否则拒绝访问。

  

  

System V IPC


System V IPC和Posix IPC其实是本质上是差不多的,不过需要注意的是,System V IPC不是随进程持续的,是随内核持续的。 Posix的IPC的名字可以像文件系统找文件一样找到它们的名字,但是System V IPC不可以找到它们(这些IPC)的名字。

key_t Keys和ftok Function

System V IPC系统创建新的IPC的三个函数分别是msgget(),senget(),shmget()这三个函数的参数都是(key_t key, int mode),其实key是由ftok函数创建的一个键值,ftok函数的声明为:

#include <sys/ipc.h>

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

这个函数假定了这个程序使用了System V IPC进行通讯,客户端和服务器端同意使用具有一定意义的pathname(是已存在的路径名),如果客户端和服务器只用单一通道,那么可以将id为1;如果客户端和服务器需要双向通道(而且是两条),那么可以令一个通道的id为1,另一个为2,只要pathname是一样的,那可以认为客户端和服务器使用是同一种通道进行通讯的。

  

  使用ftok函数内部实现是调用了stat函数,然后进行如下行为(典型实现,不是强制要求,一定要注意pathname是已存在路径):

  1. pathname所在文件系统信息(stat结构的st_dev)(12位)
  2. pathname对应的文件的在本文件系统的索引节点号(stat结构的st_ino)(12位)
  3. 低8位是id值(不能为0(8位)

  

   System V IPC不保证当不同路径时Keys是不一样的,id值绝对不能0(所以很多实现都把id值为0的键值定义为IPC_PRIVATE)。

ipc_perm Structure

System V IPC中,由kernel维持一个IPC的信息结构,就像文件信息结构一样:(注意这个结构和书上的有点出入)

struct ipc_perm
{
__key_t __key; /* Key. */
__uid_t uid; /* Owner's user ID. */
__gid_t gid; /* Owner's group ID. */
__uid_t cuid; /* Creator's user ID. */
__gid_t cgid; /* Creator's group ID. */
unsigned short int mode; /* Read/write permission. */
unsigned short int __pad1;
unsigned short int __seq; /* Sequence number. */
unsigned short int __pad2;
__syscall_ulong_t __glibc_reserved1;
__syscall_ulong_t __glibc_reserved2;
};

System IPC V的两个标志位(IPC_CREAT, IPC_EXCL)的用法基本上和Posix的是一样的,注意System IPC V还多了个IPC_PRIVATE的标志位,这个标志位就是专门用来创建独立的IPC通道的(没有一种pathname和id的组合能创建IPC_PRIVATE)。

IPC权限

事实上System V IPC的权限是由上面的所说的IPC_CREAT, IPC_EXCL和IPC_PRIVATE加上读写权限组成的,读写权限由系统定义的6个宏决定MSG_R, MSG_W, SEM_R, SEM_A, SHM_R, SHM_W共同组成的,具体怎么组成看下表:

  

注意ipc_perm Structure的cuid、 uid创建IPC时会被设置为调用者的user id,cgid、 gid会被设置为调用者的组group id,唯一区别就是creator的ids是不允许被改变的,但是owner的ids是可以通过ctlXXX指令来改变的(ctLXXX指令对应于三种不同的IPC有着三个不同的函数,这三个函数不使用文件模式的掩码修改权限,而是设置为对应函数指定的准确的值)

每当一个进程访问某个IPC对象时,IPC就执行两级检查,该IPC对象被打开(调用getXXX函数)执行一次,以后每次使用该对象时执行一次:

  1. 当每一个进程以某个getXXX函数建立访问某个已经存在的IPC对象的通道时,IPC就执行一次初始检查。验证调用者的oflag参数有没有指定不在该对象ipc_perm结构mode成员中的任何访问位。任何调用进程创建一个IPC时,如果其所指定的oflag对应权限位被禁止,该函数将会返回一个错误。但是其实这种测试是没用的,因为它假定调用者知道自己的权限范畴(用户,组成员或者其他用户),调用进程只用把oflag指定为0就可以绕过这个检查。
  2. 每次调用IPC的权限测试和Posix的权限检查方式一样。

  

identifier Reuse标识符重用

ipc_perm结构还有一个名为seq的变量,它是一个槽位使用情况序列号,该该变量是一个由内核为系统中每个潜在的IPC对象维护的计数器,每当删除一个IPC对象时,内核就递增相应的槽位号,如果溢出则循环为0(注意这只是普通的SVR4实现,Unix98没有强制使用这个技巧,比如我在Ubuntu 16里面就没有这样的实现)。 
       为了防止恶意进程乱读取System V IPC来截获信息,IPC标识符的可能值被设计的非常大,当一个IPC表项被访问时,获取到的IPC值将增加一个IPC表项数,下面以一个进程为例:

int i, msqid;
for (i = ; i < ;i++)
{
msqid = msgget((key_t)IPC_PRIVATE, SVMSG_MODE | IPC_CREAT);
printf("msgid = %d\n",msqid);
msgctl(msqid, IPC_RMID, NULL);
}
return ;

  输出:

  

ipcs and ipcrm Programs

由于System V IPC的三种类型不是以文件系统中的路径名标识的,所以标准的ls和rm程序无法看到他们,也没办法删除,不过任何实现了System V IPC的系统都提供了两个特殊的程序,ipcs和ipcrm,ipcs输出有关System V IPC特性的各种信息,ipcrm删除一个System V 消息队列,信号量或者共享内存区。

Kernel Limits

System V IPC的多数实现有内在的内核限制,比如最大数目等,这些限制往往很小,但是还是可以修改的。

UNIX 进程间通讯(IPC)概念(Posix,System V IPC)的更多相关文章

  1. Linux IPC实践(13) --System V IPC综合实践

    实践:实现一个先进先出的共享内存shmfifo 使用消息队列即可实现消息的先进先出(FIFO), 但是使用共享内存实现消息的先进先出则更加快速; 我们首先完成C语言版本的shmfifo(基于过程调用) ...

  2. Android查缺补漏(IPC篇)-- 进程间通讯基础知识热身

    本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8479282.html 在Android中进程间通信是比较难的一部分,同时又非常 ...

  3. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

  4. 《Unix网络编程》卷2 读书笔记 第3章- System V IPC

    1. 概述 三种类型的System V IPC:System V 消息队列.System V 信号量.System V 共享内存区 System V IPC在访问它们的函数和内核为它们维护的信息上共享 ...

  5. System V IPC 之共享内存

    IPC 是进程间通信(Interprocess Communication)的缩写,通常指允许用户态进程执行系列操作的一组机制: 通过信号量与其他进程进行同步 向其他进程发送消息或者从其他进程接收消息 ...

  6. 四十九、进程间通信——System V IPC 之消息队列

    49.1 System V IPC 介绍 49.1.1 System V IPC 概述 UNIX 系统存在信号.管道和命名管道等基本进程间通讯机制 System V 引入了三种高级进程间通信机制 消息 ...

  7. Android(java)学习笔记232:Android进程间通讯(IPC)之AIDL

    一.IPC inter process communication  进程间通讯 二.AIDL android  interface  defination  language  安卓接口定义语言 满 ...

  8. Android(java)学习笔记175:Android进程间通讯(IPC)之AIDL

    一.IPC inter process communication  进程间通讯 二.AIDL android  interface  defination  language  安卓接口定义语言 满 ...

  9. 进程间通讯IPC的几种方式总结

    Linux进程间的通讯 Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同.前者对Unix早期的进程间通信 ...

随机推荐

  1. HDU5880【AC自动机】

    题意: 给出n个字符串,再给出一个字符串,把之前出现过的字符串全部变成* 思路: AC自动机,Trie树上存的值是一个字符串的长度,也就是往前的长度,然后倒着处理一遍. 感想: 第三题AC自动机,本来 ...

  2. lightoj 1027【数学概率】

    #include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=1e2+10; int ma ...

  3. 小程序地区时间自定义选择器 picker

    进入微信公众平台小程序开发文档搜索 picker 点进去后下滑,点击在开发者工具中预览即可

  4. [Xcode 实际操作]九、实用进阶-(2)遍历设备(输出系统)上的所有字体

    目录:[Swift]Xcode实际操作 在实际工作中,经常需要调整界面元素的字体种类. 本文将演示输出系统提供的所有字体,方便检索和使用. 在项目导航区,打开视图控制器的代码文件[ViewContro ...

  5. localStorage和sessionStorage使用

    localStorage.setItem("key","value");//存数据 localStorage.getItem("key"); ...

  6. 未知宽高div水平垂直居中的3种方法

    方法一 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...

  7. Jmeter-提取Json数据进行关联

      1:Json后置处理器提取结果作为下一个sampler的传入参数 1.1:[线程组]->[简单控制器]->[HTTP sampler]->[Beanshell后置取样器]-> ...

  8. tensorflow:实战Google深度学习框架第四章02神经网络优化(学习率,避免过拟合,滑动平均模型)

    1.学习率的设置既不能太小,又不能太大,解决方法:使用指数衰减法 例如: 假设我们要最小化函数 y=x2y=x2, 选择初始点 x0=5x0=5  1. 学习率为1的时候,x在5和-5之间震荡. im ...

  9. AKOJ-2021-逆序对(归并,二分)

    链接:https://oj.ahstu.cc/JudgeOnline/problem.php?id=2021 题意: 在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们 ...

  10. python 全局变量 局部变量

    ##全局变量,局部变量#在函数内部可以调用全局变量,不能随意改变全局变量#若要在函数内部改变全局变量,需用关键字global #代码中全局变量都大写,局部变量都小写(非必须,一种规范) P = &qu ...