System V 与 POSIX 简介与对比

当我们在 Linux 系统中进行进程间通信时,例如信号量,消息队列,共享内存等方式,会发现有System V以及POSIX两种类型。今天我们就来简单介绍下它们。

POSIX:

POSIX(Portable Operating System Interface for Computing Systems)是由IEEE 和ISO/IEC 开发的一簇标准。该标准是基于现有的UNIX 实践和经验,描述了操作系统的调用服务接口,用于保证编制的应用程序可以在源代码一级上在多种操作系统上移植运行。它是在1980 年早期一个UNIX 用户组(usr/group)的早期工作的基础上取得的。该UNIX 用户组原来试图将AT&T 的系统V 和Berkeley CSRG的BSD 系统的调用接口之间的区别重新调和集成,从而于1984 年产生了/usr/group 标准。1985 年,IEEE操作系统技术委员会标准小组委员会(TCOS-SS)开始在ANSI 的支持下责成IEEE 标准委员会制定有关程序源代码可移植性操作系统服务接口正式标准。到了1986 年4 月,IEEE 就制定出了试用标准。第一个正式标准是在1988 年9 月份批准的(IEEE 1003.1-1988),也既以后经常提到的POSIX.1 标准。

System V:

System V, 曾经也被称为 AT&T System V,是Unix操作系统众多版本中的一支。它最初由 AT&T 开发,在1983年第一次发布。一共发行了4个 System V 的主要版本:版本1、2、3 和 4。System V Release 4,或者称为SVR4,是最成功的版本,成为一些UNIX共同特性的源头,例如 ”SysV 初始化脚本“ (/etc/init.d),用来控制系统启动和关闭,System V Interface Definition (SVID) 是一个System V 如何工作的标准定义。
AT&T 出售运行System V的专有硬件,但许多(或许是大多数)客户在其上运行一个转售的版本,这个版本基于 AT&T 的实现说明。流行的SysV 衍生版本包括 Dell SVR4 和 Bull SVR4。当今广泛使用的 System V 版本是 SCO OpenServer,基于 System V Release 3,以及SUN Solaris 和 SCO UnixWare,都基于 System V Release 4。
System V 是 AT&T 的第一个商业UNIX版本(UNIX System III)的加强。传统上,System V 被看作是两种UNIX”风味”之一(另一个是 BSD)。然而,随着一些并不基于这两者代码的UNIX实现的出现,例如 Linux 和 QNX, 这一归纳不再准确,但不论如何,像POSIX这样的标准化努力一直在试图减少各种实现之间的不同。

与 System V 对象类似,POSIX IPC 对象的属主、属主的组以及其他用户具有读取和写入权限,但是没有执行权限。POSIX IPC 对象的属主无法将对象分配给其他属主。POSIX IPC 包括以下功能:

1.消息允许进程将已格式化的数据流发送到任意进程。

2.信号量允许进程同步执行。

3.共享内存允许进程共享其部分虚拟地址空间。

与 System V IPC 接口不同,POSIX IPC 接口均为多线程安全接口。

由于之前的文章已经介绍过了System V 的 IPC,所以以下只简单介绍下 POSIX 的 IPC 接口。

POSIX 消息队列:
API API 作用
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr) 创建命名消息队列
mqd_t mq_close(mqd_t mqdes) 结束到开放式消息队列的连接
mqd_t mq_unlink(const char *name) 结束到开放式消息队列的连接,并在最后一个进程关闭此队列时将其删除
mqd_t mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio) 将消息放入队列
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio) 在队列中接收消息
mqd_t mq_notify(mqd_t mqdes, const struct sigevent *notification) 通知进程或线程消息已存在于队列中
mqd_t mq_getattr(mqd_t mqdes, struct mq_attr *attr) 、mqd_t mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr) 设置或获取消息队列属性
POSIX 信号量:
API API 作用
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value) 创建命名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value) 初始化信号量结构
int sem_close(sem_t *sem) 结束到开放式信号量的连接
int sem_unlink(const char *name) 结束到开放式信号量的连接,并在最后一个进程关闭此信号量时将其删除
int sem_getvalue(sem_t *sem, int *sval) 将信号量的值复制到指定整数中
int sem_wait(sem_t *sem) 递减信号量计数,当其他进程拥有信号量时进行阻塞,或者当其他进程拥有信号量时返回错误
int sem_post(sem_t *sem) 递增信号量计数
POSIX 共享内存:
API API 作用
int shm_open(const char *name, int oflag, mode_t mode) 创建共享内存
int shm_unlink(const char *name) 结束到共享内存的连接,并在最后一个进程关闭它时将其删除
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) 映射内存

内存映射机制mmap是POSIX标准的系统调用,有匿名映射和文件映射两种:

1.匿名映射使用进程的虚拟内存空间,它和malloc()类似,实际上有些malloc实现会使用mmap匿名映射分配内存,不过匿名映射不是POSIX标准中规定的。

2.文件映射有MAP_PRIVATE和MAP_SHARED两种。前者使用COW的方式,把文件映射到当前的进程空间,修改操作不会改动源文件。后者直接把文件映射到当前的进程空间,所有的修改会直接反应到文件的page cache,然后由内核自动同步到映射文件上。

相比于IO函数调用,基于文件的mmap的一大优点是把文件映射到进程的地址空间,避免了数据从用户缓冲区到内核page cache缓冲区的复制过程;当然还有一个优点就是不需要频繁的read/write系统调用。

总结

POSIX 在无竞争条件下,不需要陷入内核,其实现是非常轻量级的; System V 则不同,无论有无竞争都要执行系统调用,因此性能落了下风。

总体来说,System V IPC存在时间比较老,许多系统都支持,但是接口复杂,并且可能各平台上实现略有区别。

 

进程通信之XSI IPC

标识符和键:

有三种IPC称为XSI IPC,即消息队列,信号量以及共享存储器。

每个内核中的IPC结构(消息队列,信号量和共享存储器)都用一个非负整数的标识符(identifier)加以引用。例如,为了对一个消息队列发送消息或取消息,只需知道其队列标识符。当一个IPC结构被创建,以后又被删除时,与这种结构相关的标识符连续加1,直至达到一个整型数的最大值,然后又回到0。

标识符是IPC对象的内部名。键(key)是IPC对象的外部名。无论何时创建IPC结构(调用msgget,semget或shmget),都应该指定一个键。键的数据类型是基本系统数据类型key_t,通常在<sys/types.h>中被定义为长整型。键由内核变成标识符。

客户进程和服务器进程认同一个路径名和项目ID(项目ID是0~255之间的字符值),接着调用ftok将这两个值变成为一个键。然后用该键创建一个新的IPC结构或得到一个的IPC结构。ftok提供的唯一服务就是由一个路径名和项目ID产生一个键。

#include <sys/ipc.h>

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

path参数必须引用一个现存文件。当产生键时,只使用id参数的低八位。

三个get函数(msgget,semget和shmget)都有两个类似的参数:一个key和一个整型flag。如果满足以下两个条件之一,则创建一个新的IPC结构:

1). key是IPC_PRIVATE;

2). key当前未与特定类型的IPC结果相结合,并且flag中指定IPC_CREAT位。

为访问现存的队列(通常是客户进程),key必须等于创建该队列时所指定的键,并且不应该指定IPC_CREAT。

如果希望创建一个新的IPC结构,而且要确保不是引用具有同一标识符的一个现行IPC结构,则必须在flag中同时指定IPC_CREAT和IPC_EXCL位。这样做,如果IPC结构已经存在就会出错,返回EEXIST。

权限结构:

XSI IPC为每一个IPC结构设置了一个ipc_perm结构。该结构规定了权限和所有者。它至少包括下列成员:

struct ipc_perm {
    uid_t    uid;     /* owner's effective user id */
    gid_t    gid;     /* owner's effective group id */
    uid_t    cuid;    /* creator's effective user id */
    gid_t    cgid;    /* creator's effective group id */
    mode_t   mode;    /* access modes */
    ...
};

如果想了解你所用系统中它的完整定义,请参见<sys/ipc.h>。

在创建IPC结构时,对所有字段都附初值。调用msgctl、semctl或shmctl修改uid、gid和mode字段。为了改变这些值,调用进程必须是IPC结构的创建者或超级用户。更改这些字段类似于文件调用chown和chmod。

字段mode的值如下所示的值,但是对于任何IPC结构都不存在执行权限。另外,消息队列和共享内存使用术语读(read)和写(write),而信号量则用术语读(rend)和更改(alter)。

权限
用户读(更改) 0400
用户写 0200
组读 0040
组写(更改) 0020
其他读 0004
其他写(更改) 0002

结构限制:

三种形式的XSI IPC都有内置限制(built-in limit)。这些限制的大多数可以通过重新配置内核而加以更改。

在报告和修改限制方面,每种平台都提供它自己的方法。FreeBSD 5.2.1、Linux 2.4.22和Mac OS X 10.3提供了sysctl命令,用该命令观察和修改内核配置参数。Solaris9修改内核配置参数的方法是,修改文件/etc/system,然后重新启动。

在Linux中,你可以运行 ipcs -l以显示IPC相关的限制。

而 POSIX 是新标准,如果只是开发的话,我觉得还是POSIX好,因为其语法简单、使用方便,并且各平台上实现都一样。

System V 的优缺点(来自 UNIX环境高级编程):

XSI IPC的主要问题是:IPC结构是在系统范围内起作用的,没有访问计数。例如,如果进程创建了一个消息队列,在该队列中放入了几条消息,然后终止,但是消息队列及其内容并不会被删除。它们余留在系统中直至出现下述情况:由某个进程调用msgrcv或msgctl读消息或删除消息队列;或某个进程执行ipcrm(1)命令删除消息队列;或由正在再启动的系统删除消息队列。将此与管道相比,当最后一个访问管道的进程终止时,管道就被完全地删除了。对于FIFO而言,虽然当最后一个引用FIFO的进程终止时其名字仍保留在系统中,直至显示地删除它,但是留在FIFO中的数据却在此时全部被删除,于是也就徒有其名了。

XSI IPC的另一个问题是:这些IPC结构在文件系统中没有名字。我们不能用文件I/O和文件和目录章节中所述的函数来访问它们或修改它们的特性。为了支持它们不得不增加了十几条全新的系统调用(msgget、semop、shmat等)。我们不能用ls命令见到IPC对象,不能用rm命令删除它们,也不能用chmod命令更改它们的访问权限。于是,就不得不增加新的命令ipcs(1)和ipcrm(1)。

因为这些IPC不使用文件描述符,所以不能对它们使用多路转接I/O函数:select和poll。这就使得难于一次使用多个IPC结构,以及在文件或设备I/O中使用IPC结构。

 

参考:

https://www.cnblogs.com/GyForever1004/p/8433033.html

https://blog.csdn.net/wzhwho/article/details/3990118

https://blog.csdn.net/big_bit/article/details/51451848