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的多数实现有内在的内核限制,比如最大数目等,这些限制往往很小,但是还是可以修改的。

最新文章

  1. Android之弹出/隐藏系统软键盘
  2. iOS开发人员不容错过的10大工具
  3. .net C# 对虚拟目录IIS的操作
  4. Android使用的Eclipse NDK开发较详细一篇文章
  5. netbeans环境的建立
  6. Happy Number
  7. jsp链接数据库
  8. 高灵活低耦合Adapter快速开发攻略
  9. 一步一步重写 CodeIgniter 框架 (12) —— 代码再重构,回归 CI
  10. ASP.Net页面传值比较
  11. 关于layer的坑
  12. OCP 认证考试报名费技巧题库051052053解析合格线
  13. 本人正竞选CSDN博客之星,欢迎各位来访的朋友能为我投上一票
  14. 安卓界面之Toolbar+tablayout+viewpager仿WhatsApp界面样式
  15. vsftp安装与配置for Linux
  16. phpBB3.2开发环境配置
  17. js object对象赋值bug和对象复制clone方法
  18. 2018.09.28 bzoj3743: [Coci2015]Kamp(树形dp)
  19. Ubuntu14.04更换阿里云源
  20. gps各种地图坐标系转换

热门文章

  1. vc编程中出现 fatal error C1010: 在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include &quot;stdafx.h&quot;”?
  2. laravel 布局 详解(实例)
  3. bzoj 4032: [HEOI2015]最短不公共子串【dp+SAM】
  4. 洛谷P4151 [WC2011]最大XOR和路径(线性基)
  5. APP为什么会被打回来??
  6. mysql项目实战经验
  7. day04 基本类型包装类
  8. 转 SecureCRT中文乱码解决方法
  9. C#泛型学习笔记
  10. javaoo面向对象