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. laravel 视图调用方法并传递参数

    视图层 route 中文 路由 <a href="{{route('cc',array('id'=>11111))}}">446454</a> 路由层 ...

  2. uoj#267. 【清华集训2016】魔法小程序(乱搞)

    传送门 感觉很像FFT的过程的说-- 先来考虑\(b\)如何转化成\(c\),那么只要通过它的逆过程就可以了 首先,我们称"魔法"为比较两个数的字典序,记\(x=a_0\),那么把 ...

  3. GPDB外部表创建示例

    创建以|为分隔符的外部表CREATE EXTERNAL TABLE ext_expenses ( name text,date date, amount float4, category text, ...

  4. python3错误之TypeError: 'dict_items' object is not callable

    这种错误出现在循环结构中套循环结构,而循环时内部循环为字典,外部循环为该字典调用items方法后取到的值,内部循环调用外部循环中遍历的结果: 解决方案: 将外部循环的items()方法调用改为.key ...

  5. JSON对象 JSON字符串 JSON数组

    JSON对象: var str2 = { "name" :  "andy", "gender" : "man" , &q ...

  6. C# 面向对象之继承后初始化顺序

    使用继承之后当我们初始化一个子类时子类的初始化顺序为: (1)初始化类的实例字段 (2)调用基类的构造函数,如果没有指明基类则调用System.Object的构造函数; (3)调用子类的构造函数

  7. 关于childNodes和children

    1.childNodes. 会把注释.空文本.非空文本.标签都当做节点. 2.children: 在IE8以后的版本只会找到标签元素.不包括文本.空文本.注释.在IE8以前的版本会找到标签元素和注释, ...

  8. 转 php include

    http://www.w3school.com.cn/php/php_includes.asp PHP include 实例 例子 1 假设我们有一个名为 "footer.php" ...

  9. 转 错误:ORA-28002/ORA-65162 : the password will expire within 7 days 解决方法

    今天在使用sqlplus时出现 =============================================== ERROR:ORA-28002: the password will e ...

  10. Springboot下事务管理的简单使用

    关于事务管理的概念这里就不多介绍了,在我的博客“JDBC事务之理论篇”中也有介绍. 关于Spring的事务管理,主要是通过事务管理器来进行的.这里看个Spring事务管理的接口图:(来自博客https ...