![](https://app.yinxiang.com/shard/s48/res/9d7701c8-b84b-431a-b504-277183ab561a/1.png)
管道
管道是unix系统IPC的最最古老形式,并且所有unix系统都提供此种通信机制。管道有下面两种局限性:
(1).从一开始,它们是半双工的,现在,某些系统提供全双工管道,但为了最佳的可移植性,我们决不应预先假定系统使用此特性。
(2).它们只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道。
而FIFO没有第二种局限性,unix域套接字和命名流管道则没有这两种局限性。尽管有这两种局限性,半双工管道仍然是最常用的IPC形式。
管道的创建
管道是由调用pipe函数而创建的:
#include <unistd.h>
int pipe(int filedes[2]);
返回值:若成功则返回0,若出错则返回-1.
说明:
1.经由参数filedes返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。
2.当管道的一端被关闭后,下列两条规则起作用:
(1).当读一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,以指示达到了文件结束处。
(2).如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回 -1,errno设置为EPIPE。
3.在写管道时,常量PIPE_BUF规定了内核中管道缓冲区的大小。如果对管道调用write,而且要求写的字节数小于等于PIPE_BUF,则此操作不会与其他进程对同一管道的write操作穿插进行。但是,若有多个进程同时写一个管道,而且有进程要求写的字节数超过PIPE_BUF字节数时,则写操作的数据可能相互穿插。用pathconf或fpathconf函数可以确定PIPE_BUF的值。
标准库中的管道操作
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
返回值:若成功则返回文件指针,若出错则返回NULL
int pclose(FILE *fp);
返回值:cmdstring的终止状态,若出错则返回-1
说明:
1.这两个函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止。
2.函数popen先执行fork,然后调用exec以执行cmdstring,并且返回一个标准I/O文件指针。如果type是“r”,则文件指针连接到cmdstring的标准输出。如果type是“w”,则文件指针连接到cmdstring的标准输入。
3.pclose函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose返回的终止状态与shell已执行exit一样。
FIFO(命名管道)
管道只能由有共同祖先的相关进程使用,但是,通过FIFO,不相关的进程也能交换数据。
创建FIFO类似于创建文件,确实,FIFO的路径名存在于文件系统中。
mkfifo
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
返回值:若成功则返回0,若出错则返回-1
说明:
1.mkfifo函数中mode参数的规格说明与open函数中的mode相同。
2.一旦已经用mkfifo创建了一个FIFO,就可用open打开它。其实,一般的文件I/O函数(close read write unlink等)都可用于FIFO。
3.当打开一个FIFO时,非阻塞标志(O_NONBLOCK)产生下列影响:
* 在一般情况中(没有指定O_NONBLOCK),只读open要阻塞到某个其他进程为写而打开此FIFO。类似地,只写open要阻塞到某个其他进程为读而打开它。
* 如果指定了O_NONBLOCK,则只读open立即返回。但是,如果没有进程已经为读而打开一个FIFO,那么只写open将出错返回-1,其errno是ENXIO。
4.类似于管道,若用write写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。若某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束标志。
5.一个给定的FIFO有多个写进程是很常见的。这就意味着如果不希望多个进程所写的数据互相穿插,则需考虑原子操作。正如对于管道一样,常量PIPE_BUF说明了可被原子地写到FIFO的最大数据量。
6.FIFO有下面两种用途:
(1). FIFO由shell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。
(2). FIFO用于客户进程-服务器进程应用程序中,以在客户进程和服务器进程之间传递数据。
XSI IPC: 消息队列 信号量 共享内存
标识符和键
每个内核中的IPC结构(消息队列 信号量或共享内存)都用一个非负整数的标识符加以引用。例如,为了对一个消息队列发送或取消息,只需要知道其队列标识符。与文件描述符不同,IPC标识符不是小的整数,当一个IPC结构被创建,以后又被删除时,与这种结构相关的标识符连续加1,直至达到一个整型数的最大正值,然后又回转到0。
标识符是IPC对象的内部名,为使多个合作进程能够在同一IPC对象上会和,需要提供一个外部方案。为此使用了键(key),每个IPC对象都与一个键相关联,于是键就用作为该对象的外部名。
无论何时创建IPC结构(调用msgget semget或shmget),都应指定一个键,键的数据类型是基本系统数据类型key_t,通常在头文件<sys/types.h>中被定义为长整型,键由内核变换成标识符。
有多种方法使客户进程和服务器进程在同一个IPC结构上会合:
(1). 服务器进程可以指定键IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处(例如一个文件)以便客户进程取用。键IPC_PRIVATE保证服务器进程创建一个新IPC结构。这种技术的缺点是:服务器进程要将整型标识符写到文件中,此后客户进程又要读文件取得此标识符。IPC_PRIVATE键也用于父子进程关系。
(2). 在一个公用头文件中定义一个客户进程和服务器进程都认可的键。然后服务器进程指定此键创建一个新的IPC结构。这种方法的问题是该键可能已与一个IPC结构相结合,在此种情况下,get函数(msgget semget或shmget)出错返回。服务器进程必须处理这一错误,删除已存在的IPC结构,然后试着再去创建它。
(3). 客户进程和服务器进程认同一个路径名和项目ID(项目ID是0~255之间的字符值),接着调用函数ftok将这两个值变换为一个键。然后在方法(2)中使用此键。ftok提供的唯一服务就是由一个路径名和项目ID产生一个键。
ftok
#include <sys/ipc.h>
key_t ftok(const char *path, int id);
返回值:若成功则返回键,若出错则返回(key_t)- 1
说明:
1. path参数必须引用一个现存文件。当产生键时,只使用id参数的低8位。
2.ftok创建的键通常是用下列方式构成的:按给定的路径名取得其stat结构,从该结构中取出部分st_dev和st_ino字段,然后再与项目ID组合起来。 如果两个路径名引用两个不同的文件,那么,对这两个路径名调用ftok通常返回不同的键。但是,因为i节点号和键通常都存放在长整型中,于是创建键时可能会丢失信息。这意味着,如果使用同一项目ID,那么对于不同文件的两个路径名可能产生相同的键。
3.三个get函数(msgget semget和shmget)都有两个类似的参数:一个key和一个整型flag。如若满足下列两个条件之一,则创建一个新的IPC结构(通常由服务器进程创建):
* key是IPC_PRIVATE;
* key当前未与特定类型的IPC结构相结合,并且flag中指定了IPC_CREAT位。
为访问现存的队列(通常由客户进程进行),key必须等于创建该队列时所指定的键,并且不应指定IPC_CREAT。
4.注意,为了访问一个现存队列,决不能指定IPC_PRIVATE作为键,因为这是一个特殊的键值,它总是用于创建一个新队列。为了访问一个用IPC_PRIVATE键创建的现存队列,一定要知道与该队列相结合的标识符,然后在其他IPC调用中使用该标识符。
5.如果希望创建一个新的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 */
.
.
.
};
每种实现在其ipc_perm结构中会包括另外一些成员。如欲了解你所用系统中它的完整定义,请参见<sys/ipc.h>。
在创建IPC结构时,对所有字段都赋初值,以后可以调用msgctl semctl或shmctl修改uid gid和mode字段。为了改变这些值,调用进程必须是IPC结构的创建者或超级用户。更改这些字段类似于对文件调用chown和chmod。
mode字段的值类似于文件中mode的值,但是对于任何IPC结构都不存在执行权限。另外,消息队列和共享存储使用术语读和写,而信号量则使用术语读和更改。
结构限制
![](https://app.yinxiang.com/shard/s48/res/1ad670df-cec4-4796-8067-7f272ad05a6b/2.png)
三种形式的XSI IPC都有内置限制,这些限制的大多数可以通过重新配置内核而加以更改。每种平台都提供它自己的方法,FreeBSD Linux和Mac OS提供了sysctl命令,用该命令观察和修改内核配置参数。
优点和缺点
XSI IPC的主要问题是:IPC结构是在系统范围内起作用的,没有访问计数。例如,如果进程创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容并不会被删除。它们出现在系统中直至出现下述情况:由某个进程调用msgrcv或msgctl读消息或删除消息队列;或某个进程执行ipcrm(1)命令删除消息队列;或由正在再启动的系统删除消息队列。将此与管道相比,当最后一个访问管道的进程终止时,管道就被完全地删除了。对于FIFO而言,虽然当最后一个引用FIFO的进程终止时,管道就被完全地删除了。对于FIFO而言,虽然当最后一个引用FIFO的进程终止时其名字仍保留在系统中,直至显式地删除它,但是留在FIFO中的数据却在此时全部被删除,于是也就徒有其名了。
XSI IPC的另一个问题是:这些IPC结构在文件系统中没有名字。我们不能使用文件系统的函数来访问或修改它们的特性。为了支持它们不得不增加了十几条全新的系统调用(msgget semop shmat等)。我们不能用ls命令见到IPC对象,不能用rm命令删除它们,也不能用chmod命令更改它们的访问权限。于是就不得不增加新的命令ipcs和ipcrm。
因为这些IPC不使用文件描述符,所以不能对它们使用多路转接I/O函数:select 和 poll。这就使得难于一次使用多个IPC结构,以及在文件或设备I/O中使用IPC结构。例如,没有某种形式的忙等待循环,就不能使一个服务器进程等待将要放在两个消息队列任一个中的消息。
![](https://app.yinxiang.com/shard/s48/res/6532ae8c-cd87-4b1e-bf38-7ce3001ab51f/Screenshot-7.png)
因为所有这些形式的IPC都限制用在单主机上,所以他们都是可靠的。当消息通过网络传送时,丢失消息的可能性就要加以考虑。
“流控制”指的是:如果系统资源(缓冲区)短缺或者如果接收进程不能再接收更多消息,则发送进程就要休眠。当流控制条件消失时,发送进程应自动地被唤醒。
消息队列
消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。一般将消息队列简称为队列,其标识符为队列ID。
msgget用于创建一个新队列或打开一个现存的队列。msgsnd将新消息添加到队列尾端。每个消息包含一个正长整型类型字段,一个非负长度以及实际数据字节(对应于长度),所有这些都在将消息添加到队列时,传送给msgsnd,msgrcv用于从队列中取消息。我们并不一定要以先进先出次序取消息,也可以按消息的类型字段取消息。每个队列都有一个msqid_ds结构与其关联:
struct msqid_ds{
struct ipc_perm msg_perm; /* see Section 15.6.2 */
msggnum_t msg_qnum; /* # of messages on queue */
msglen_t msg_qbytes; /* max # of bytes on queue */
pid_t msg_lspid; /* pid of last msgsnd() */
pid_t msg_lrpid; /* pid of last msgrcv() */
time_t msg_stime; /* last - msgsnd() time */
time_t msg_rtime; /* last - msgrcv() time*/
time_t msg_ctime; /* last - change time */
.
.
.
};
下面列出了影响消息队列的系统限制:
![](https://app.yinxiang.com/shard/s48/res/63bb7239-a455-4ab1-81f8-40e23c271c70/Screenshot-8.png)
2.“derived”表示这种限制是从其他限制导出的。例如,在linux系统中,消息最大数基于队列最大数值和队列中允许数据量的最大值。如果最短消息长度是一个字节,则系统范围内的消息数限制是最大消息队列数 * 队列的最大长度(字节)。按上表给出的数据,linux默认配置的最大消息数(系统范围内)是262144。(即使一个消息可能包含0字节数据,linux也将其处理为如同包含1字节那样,其目的是限制队列中的消息数。)
调用的第一个函数是msgget,其功能是打开一个现存队列或创建一个新队列。
#include <sys/msg.h>
int msgget(key_t key, int flag);
返回值:若成功则返回消息队列ID,若出错则返回-1
说明:当创建一个新队列时,初始化msqid_ds结构的下列成员:
ipc_perm结构进行初始化,该结构中mode成员按flag中的相应权限位设置。
msg_qnum msg_lspid msg_lrpid msg_stime和msg_rtime都设置为0。
msg_ctime设置为当前时间。
msg_qbytes设置为系统限制值。
若执行成功,msgget返回非负队列ID。此后,该值就可被用于其他三个消息队列函数。
msgctl
msgctl函数对队列执行多种操作,它和另外两个与信号量和共享内存有关的函数(semctl 和 shmctl )是XSI IPC的类似于ioctl
的函数(亦即垃圾桶函数)。
的函数(亦即垃圾桶函数)。
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf );
返回值:若成功则返回0,若出错则返回-1
说明:cmd参数说明对由msqid指定的队列要执行的命令:
IPC_STAT 取此队列的msqid_ds结构,并将它存放在buf指向的结构中。
IPC_SET 按由buf指向结构中的值,设置与此队列相关结构中的下列四个字段:msg_perm.uid msg_perm.gid msg_perm.mode和msg_qbytes。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid 或msg_perm.uid;另一种是具有超级用户特权的进程,只有超级用户才能增加msg_qbytes的值。
IPC_RMID 从系统中删除该消息队列以及仍在该队列中的所有数据。这种删除立即生效。仍在使用这一消息队列的其他进程在它 们下一次试图对此队列进行操作时,将出错返回EIDRM。此命令只能由下列两种进程执行:一种是其有效用户ID等于 msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。
这三条命令也可用于信号量和共享内存。
msgsnd
调用msgsnd将数据放到消息队列中。
#include <sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
返回值:若成功则返回0,若出错则返回-1
说明:
1.每个消息都由三部分组成,它们是:正长整型类型字段 非负长度以及实际数据字节(对应于长度)。消息总是放在队列尾端。
2.ptr参数指向一个长整型数,它包含了正的整型消息类型,在其后紧跟着消息数据。(若nbytes是0,则无消息数据。)若发送的 最长消息是512字节,则可定义下列结构:
struct mymesg {
long mtype; /* positive message type */
char mtext[512]; /* message data of length nbytes */
};
于是,ptr就是一个指向mymesg结构的指针。接收者可以使用消息类型以非先进先出的次序取消息。
3.参数flag的值可以指定为IPC_NOWAIT。这类似于文件I/O的非阻塞I/O标志。若消息队列已满(或者是队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值),则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。如果没有指定IPC_NOWAIT,则进程阻塞到下述情况出现为止:有空间可以容纳要发送的消息;从系统中删除了此队列;或捕捉到一个信号,并从信号处理程序返回。在第二种情况下,返回EIDRM(“标识符被删除”)。最后一种情况则返回EINTR。、
注意,对删除消息队列的处理不是很完善。因为对每个消息队列并没有设置一个引用计数器(对打开文件则有这种计数器),所以删除一个队列会造成仍在使用这一队列的进程在下次对队列进行操作时出错返回。信号量机制也以同样方式处理其删除。相反,删除一个文件时,要等到使用该文件的最后一个进程关闭了它的文件描述符后,才能删除文件中的内容。
4.当msgsnd成功返回,与消息队列相关的msqid_ds结构得到更新,以表明发出该调用的进程ID(msg_lspid) 进行该调用的时间(msg_stime),并指示队列中增加了一条消息(msg_qnum)。
msgrcv从队列中取用消息:
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);
返回值:若成功则返回消息的数据部分的长度,若出错则返回-1.
说明:
1.ptr参数指向一个长整型数(返回的消息类型存放在其中),跟随其后的是存放实际消息数据的缓冲区。nbytes说明数据缓冲区的长度。若返回的消息大于nbytes,而且在falg中设置了MSG_NOERROR,则该消息被截短。(在这种情况下,不通知我们消息截短了,消息的截去部分被丢弃。)如果没有设置这一标志,而消息又太长,则出错返回E2BIG(消息仍留在队列中)。
2.参数type使我们可以指定想要哪一种消息:
type == 0 返回队列中的第一个消息。
type > 0 返回队列中消息类型为type的第一个消息。
type < 0 返回队列中消息类型值小于或等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。
type值非0用于以非先进先出次序读消息。例如,若应用程序对消息赋优先权,那么type就可以是优先权值。如果一个消息队列由多个客户进程和一个服务器进程使用,那么type字段可以用来包含客户进程的进程ID(只要进程ID可以存放在长整型中)。
3.可以指定flag值为IPC_NOWAIT,使操作不阻塞。这使得如果没有所指定类型的消息,则msgrcv返回-1,errno设置为ENOMSG。如果没有指定IPC_NOWAIT,则进程阻塞直至如下情况出现才终止:有了指定类型的消息,从系统中删除此队列(出错则返回-1且errno置为EIDRM);或捕捉到一个信号并从信号处理程序返回(msgrcv 返回 -1,errno 设置为EINTR)。
4.msgrcv成功执行时,内核更新与该消息队列相关联的msqid_ds结构,以指示调用者的进程ID(msg_lrpid)和调用时间(msg_rtime),并将队列中的消息数(msg_qnum)减1.
5.消息队列原来的实施目的是提供比一般IPC更高速度的进程通信方法,但现在与其他形式的IPC相比,在速度方面已经没有什么差别了(事实上,在原来实施消息队列时,唯一的其他形式IPC就是半双工管道。)在上面谈论优缺点时,考虑到消息队列具有的问题,我们得出的结论是,在新的程序中不应当再使用它们。
信号量
信号量与已经介绍过的IPC不一样,它是一个计数器,用于多进程对共享数据对象的访问。
为了获得共享资源,进程需要执行下列操作:
(1). 测试控制该资源的信号量。
(2).若此信号量为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源单位。
(3).若此信号量的值为0,则进程进入休眠状态,直至信号量值大于0,进程被唤醒后,它返回至第(1)
步。
当进程不再使用由一个信号量控制的共享资源时,该信号量值增1,如果有进程正在休眠等待此信号量,则唤醒他们。
为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常在内核中实现的。
常用的信号量形式被称为二元信号量或双态信号量。它控制单个资源,初始值为1,但是一般而言,信号量的初值可以是任一正值,该值说明有多少个共享资源单位可供共享应用。但是XSI 的信号量与此相比要复杂的多,三种特性造成了这种并非必要的复杂性:
(1). 信号量并非是单个非负值,而必须将信号量定义为含有一个或多个信号量值的集合。当创建一个信号量时,要指定该集合中信号量值的数量。
(2). 创建信号量与对其赋初值分开。这是一个致命的弱点,因为不能原子地创建一个信号量集合,并且对该集合中的各个信号量值赋初值。
(3). 即使没有进程正在使用各种形式的XSI IPC,它们仍然是存在的。有些程序在终止时并没有释放已经分配给它的信号量,所以我们不得不为这种程序担心。undo功能就是假定要处理这种情况的。
内核为每个信号量集合设置了一个semid_ds结构:
struct semid_ds {
struct ipc_perm sem_perm; /* see Section 15.6.2 */
unsigned short sem_nsems; /* # of semaphores in set */
time_t sem_otime; /* last-semop() time */
time_t sem_ctime; /* last-change time */
.
.
.
}; 在具体实现的时候可在semid_ds结构中定义添加成员。
每个信号量由一个无名结构表示,它至少包含下列成员:
struct {
unsigned short semval; /* semaphore value, always >= 0 */
pid_t sempid; /* pid for last operation */
unsigned short semncnt; /* # processes awaiting semval > curval */
unsigned short semzcnt; /* # processes awaiting semval == 0 */
.
.
.
}; 下面是影响信号量集合的系统限制:
![](https://app.yinxiang.com/shard/s48/res/f552bbb1-9906-4fc9-bb2d-16b2ff7cb398/Screenshot-9.png)
semget
要获得一个信号量ID,要调用的第一个函数是semget。
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag );
返回值:若成功则返回信号量ID,若出错则返回-1
说明:创建一个新集合时,对semid_ds结构的下列成员赋初值:
对ipc_perm结构赋初值,该结构中的mode被设置为flag中的相应权限位。
sem_otime设置为0.
sem_ctime设置为当前时间。
sem_nsems设置为nsems。
nsems是该集合中的信号量数。如果是创建新集合(一般在服务器进程中),则必须指定nsems。如果引用一个现存集合(一个客户进程),则将nsems指定为0.
semctl函数包含了多种信号量操作。
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
返回值:对于除GETALL以外的所有GET命令,semctl函数都返回响应的值。其他命令的返回值为0。
说明:
1.注意,依赖于所请求的命令,第四个参数是可选的,如果使用该参数,其类型是semun,它是多个特定命令参数的联合(union):
union semun {
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
unsigned short *array; /* for GETALL and SETALL */
}; 注意,这是一个联合,而非指向联合的指针。
cmd参数指定下列10种命令中的一种,在semid指定的信号量集合上执行此命令。其中有5条命令是针对一个特定的信号量值的,它们用semnum指定该信号量集合中的一个成员。semnum值在0和nsems-1之间(包括0和nsems-1)。
IPC_STAT:对此集合取semid_ds结构,并存放在由arg.buf指向的结构中。
IPC_SET: 按由arg.buf指向结构中的值设置与此集合相关结构中的下列三个字段值:sem_perm.uid sem_perm.gid和 sem_perm.mode。此命令只能由下列两种进程执行:一种是其有效用户ID等于sem_perm.cuid或sem_perm.uid的 进程;另一种是具有超级用户特权的进程。
IPC_RMID: 从系统中删除该信号量集合。这种删除是立即发生的。仍在使用此信号量集合的其他进程在它们下次试图对此信号量集合 进行操作时,将出错返回EIDRM。此命令只能由下列两种进程执行:一种是其有效用户ID等于sem_perm.cuid或 sem_perm.uid的进程;另一种是具有超级用户特权的进程。
GETVAL: 返回成员semnum的semval值。
SETVAL: 设置成员semnum的semval值。该值由arg.val指定。
GETPID: 返回成员semnum的sempid值。
GETNCNT: 返回成员semnum的semncnt值。
GETZCNT: 返回成员semnum的semzcnt值。
GETALL: 取该集合中所有信号量的值,并将它们存放在由arg.array指向的数组中。
SETALL: 按arg.array指向的数组中的值,设置该集合中所有信号量的值。
函数semop自动执行信号量集合上的操作数组,这是个原子操作。
#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);
返回值:若成功则返回0,若出错则返回-1
说明:参数semoparray是一个指针,它指向一个信号量操作数组,信号量操作由sembuf结构表示:
struct sembuf {
unsigned short sem_num; /* member # in set (0,1,..., nsems-1) */
short sem_op; /* operation(negative, 0, or positive )*/
short sem_flg; /* IPC_NOWAIT, SEM_UNDO */
};
参数nops规定该数组中操作的数量(元素数)。
对集合中每个成员的操作由相应的sen_op值规定。此值可以是负值 0或正值。如果有可用资源就取一个来用,如果小于0的话就阻塞进入休眠状态,直到有资源可用为止,若指定了IPC_NOWAIT,则semop出错返回EAGAIN。
共享内存
共享内存是允许两个或更多进程共享一给定的存储区。因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。使用共享内存时要掌握的唯一窍门是多个进程之间对一给定存储区的同步访问。通常,信号量被用来实现对共享内存访问的同步,记录锁也用于这种场合。
内核为每个共享存储段设置了一个shmid_ds结构:
按照支持共享存储段的需要,每种实现会在shmid_ds结构中增加其他成员。shmatt_t类型定义为不带符号整型,它至少与unsigned short 一样大。下面列出了影响共享存储的系统限制:
![](https://app.yinxiang.com/shard/s48/res/413f67b2-1ef5-49f5-9200-a52ea690ead1/Screenshot-10.png)
![](https://app.yinxiang.com/shard/s48/res/060e7f5d-a748-4171-a695-8f019bf327d4/Screenshot-11.png)
为了获得一个共享存储标识符,调用第一个函数通常是shmget。
#include <sys/shm.h>
int shmget(key_t key,size_t size, int flag);
返回值:若成功则返回共享存储ID,若出错则返回-1
说明:当创建一个新段时,初始化shmid_ds结构的下列成员:
初始化ipc_perm结构,该结构中的mode成员按flag中的相应权限位设置。
shm_lpid shm_nattach shm_atime以及shm_dtime都设置为0.
shm_ctime设置为当前时间。
shm_segsz设置为请求的长度(size)。
参数size是该共享存储段的长度(单位:字节)。实现通常将其向上取为系统页长的整数倍。但是,若应用指定的size值并非系统页长的整数倍,那么最后一页的余下部分是不可使用的。如果正在创建一个新段(一般是在服务器进程中),则必须指定其size。如果正在引用一个现存的段(一个客户进程),则将size指定为0,当创建一新段时,段内的内容初始化为0.
shmctl函数对共享存储段执行多种操作。
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
返回值:若成功则返回0,若出错则返回-1
说明:cmd参数指定下列5种命令中一种,使其在shmid指定的段上执行。
![](https://app.yinxiang.com/shard/s48/res/d41dd263-0bfd-41fe-8ad9-bfe3b2aac85e/Screenshot-12.png)
#include <sys/shm.h>
void *shmat(int shmid,const void *addr, int flag);
返回值:若成功则返回指向共享存储的指针,若出错则返回-1
说明:
1.共享存储段连接到调用进程的哪个地址上与addr参数以及在flag中是否指定SHM_RND位有关。
2.如果addr为0,则此段连接到由内核选择的第一个可用地址上。这是推荐的使用方式
3.如果addr非0,并且没有指定SHM_RND,则此段连接到addr所指定的地址上。
4.如果addr非0,并且指定了SHM_RND,则此段连接到(addr--(addr mod ulus SHMLBA))所表示的地址上。SHM_RND命令的意思是“取整”。SHM_RND的意思是“地边界地址倍数”,它总是2的乘方。该算式是将该地址向下取最近1个SHMLBA的倍数。
5.除非只计划在一种硬件上运行应用程序(这在当今是不大可能的),否则不应指定共享段所连接到的地址。所以一般应指定addr为0,以便由内核选择地址。
6.如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段。否则以读写方式连接此段。
7. shmat的返回值是该段所连接的实际地址,如果出错则返回-1。 如果shmat成功执行,那么内核将使该共享存储段shmid_ds结构中的shm_nattch计数器值加1。
当对共享存储段的操作已经结束时,则调用shmdt脱接该段。注意,这并不从系统中删除其标识符以及其数据结构。该标识符仍然存在,直至某个进程(一般是服务器进程)调用shmctl(带命令IPC_RMID)特地删除它。
#include <sys/shm,h>
int shmdt(void * addr);
返回值:若成功则返回0,若出错则返回-1
说明:
1. addr参数是以前调用shmat时的返回值。如果成功,shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1.
2.内核将以地址0连接的共享存储段放在什么位置上与系统密切相关。
3.mmap函数可将一个文件的若干部分映射至进程地址空间。这在概念上类似于用shmat XSI IPC函数连接一共享存储段。两者之间的主要区别是,用mmap映射的存储段是与文件相关联的,而XSI共享存储段则并无这种关联。
![](https://app.yinxiang.com/shard/s48/res/1248ea0a-8295-43c5-94f5-59d4a8803fce/Screenshot-13.png)