15.6 XSI IPC
有三种IPC我们称作XSI IPC,即消息队列,信号量和共享存储器,他们之间有很多相似之处。
15.6.1 标识符和键
每个内核中的IPC结构(消息队列,信号量或共享存储段)都用一个非负整数的标识符加以引用。标识符是IPC对象的内部名,为使多个合作进程能够在同一IPC对象上会合。需要提供一个外部名方案。为此使用了键(key),每个IPC对象都与一个键相关联,于是键就用作该对象的外部名。
无论何时创建IPC结构,都应执行一个键,键的数据类型是基本系统数据类型key_t,通常在头文件<sys/types.h>中被定义为长整形。键由内核变换成标识符。
有多种方法使客户进程和服务器进程在同一IPC结构上会合。
1.服务器进程可以指定键IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处,键IPC_PRIVATE保证服务器进程创建一个新IPC结构。服务器进程要将整型标识符写到文件中,此后客户进程又要读文件取得此标识符。
2.在一个公用头文件中定义一个客户进程和服务器进程都认可的键。然后服务器进程指定键创建一个新的IPC结构,这种方法的问题是该键可能已与一个IPC结构相结合,在此情况下,get函数(msgget,semget,shmget)出错返回。服务器进程必须处理这一错误,删除已存在的IPC结构,然后试着再创建它。
3.客户进程和服务器进程认同一个路径名和项目ID(项目ID是0~255之间的字符值),接着调用函数ftok将这两个值变换成一个键,然后在方法2中使用此键。ftok提供的唯一服务就是由一个路径名和项目ID产生一个键。
#include<sys/ipc.h>
key_t ftok(const char *path, int id); //成功则返回键,出错则返回(key_t)-1.
path参数必须引用一个现存文件,当产生键时,只使用id参数的低8位。
ftok创建的键通常是用下列方式构成的:按给定的路径名取得其stat结构,从该结构中取出部分st_dev和st_ino字段,然后再与项目ID结合起来。如果两个路径名引用两个不同的文件,那么对这两个路径名调用ftok通常返回不同的键。但是因为i节点号和键通常都存放在长整型中,于是创建键时可能会丢失信息,这意味着,如果使用同一个项目ID,那么对于不同文件的两个路径名可能产生相同的键。
三个get函数(msgget,semget和shmget)都有两个类似的参数:一个key和一个整型flag。如果满足下列两个条件之一,则创建一个新的IPC结构:
1.key是IPC_PRIVATE。
2.key当前未与特定类型的IPC结构相结合,并且flag中指定了IPC_CREAT位。
注意:
1.为了访问现存的队列,key必须等于创建该队列时所指定的键,并且不应指定IPC_CREAT。
2.为了访问一个现存队列,决不能指定IPC_PROVATE作为键。因为这是一个特殊的键值,用于创建一个新队列。
3.如果希望创建一个新的IPC结构,而且要确保不是引用具有同一标识符的一个现行IPC结构,那么必须在flag中同时指定IPC_CREAT和IPC_EXECL位。这样做了以后,如果IPC结构已经存在,就会造成出错,返回EEXIST。
15.6.2 权限结构
XSI IPC为每一个IPC结构设置了一个ipc_perm结构。该结构规定了权限的所有者。它至少包含以下成员:
struct ipc_perm
{
uid_t uid; //拥有者的有效用户ID
gid_t gid; //拥有者有效组ID
uid_t cuid; //创建者有效用户ID
gid_t cgid; //创建者有效组ID
mode_t mode; //访问权限
.........
};
在创建IPC结构时,对所有字段都赋初值。以后,可以调用msgctl,semctl或shmctl修改uid,gid和mode字段,为了改变这些值,调用进程必须是IPC结构的创建者或超级用户。更改这些字段类似于对文件调用chown和chmod。
mode字段值类似于普通文件的访问权限,但是没有执行权限。
15.6.4 XSI IPC优点和缺点
缺点:
1.IPC结构在系统范围内起作用,没有访问计数,例如,如果进程创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容不会被删除。与管道相比,当最后一个访问管道的进程终止时,管道就完全被删除了,对于FIFO而言,虽然当最后一个引用FIFO的进程终止时其名字仍保留在系统中,直至显式地删除它,但是留在FIFO中的数据却在此时全部被删除。
2.这些IPC结构在文件系统中没有名字,我们不能使用stat系列函数访问和修改他们的特性。为此不得不增加全新的系统调用(msgget,semop,shmat等)。我们不能使用ls命令见到IPC对象,不能使用rm命令删除它们,也不能使用chmod等函数更改他们的访问权限。于是就不得不增加新的命令ipcs和ipcrm。
3.IPC不使用文件描述符,所以不能对它们使用多路转换IO函数:select和poll。
优点:
1.可靠
2.流是受控的
3.面向记录
4.可以用非先进先出方式处理。
15.7 消息队列
消息队列是消息的链接表,保存在内核,通过消息队列的引用标识符来访问消息,消息队列对每个消息指定了特定的消息类型,接收消息的进程可以请求接收下一条消息,也可以请求接收下一条特定类型的消息。系统内核维护的消息队列的结构如下:
#include <bits/msq.h>
struct msqid_ds
{
struct ipc_perm msg_perm; /* IPC对象的属性信息和访问权限 */
struct msg *msg_first; /* 指向消息队列的第一个消息 */
struct msg *msg_last; /* 指向消息队列的最后一个消息 */
time_t msg_stime; /* time of last msgsnd command */
time_t msg_rtime; /* time of last msgrcv command */
time_t msg_ctime; /* time of last change */
unsigned long int msg_cbytes; /* 当前消息队列中消息的总字节数 */
msgqnum_t msg_qnum; /* 当前队列中消息的个数 */
msglen_t msg_qbytes; /* 队列允许存放的最大字节数 */
pid_t msg_lspid; /* pid of last msgsnd() 即最后执行msgsnd函数的进程的进程ID */
pid_t msg_lrpid; /* pid of last msgrcv() 即最后执行msgrcv函数的进程的进程ID */
};
其中 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 user id */
mode_t mode; /* access modes */
};
内核维护的消息队列链表结构形式如下图所示:
消息队列所传递的信息有两部分组成,即消息类型及其所传递数据,一般用一个结构表示,通常消息类型是一个正的长整型数表示,而数据根据需要设定,例如设定一个传送1024字节长度的字符数据的消息结构如下。获取消息队列中的消息时,不一定按照先进先出顺序,也可以按照消息的类型字段进行获取。
struct msgbuf
{
long msgtype;
char msgtext[1024];
};
消息队列的创建与打开
要是没有消息队列,首先必须要创建一个消息队列,msgget 函数能够实现该功能:
/*
* 函数功能:创建一个新的消息队列或打开一个现有的消息队列;
* 返回值:若成功则返回消息队列的ID,若出错则返回-1;
* 函数原型:
*/
#include <sys/msg.h>
int msgget(key_t key, int flag);
/*
* 说明:
* 参数key是消息队列的键;
* 参数flag表示调用函数的操作类型,也可用于设置访问权限;
*/
当成功创建一个新消息队列时,msqid_ds 结构的成员被初始化为如下值:
msg_perm 结构的 uid 和 cuid 成员被设置成当前进程的有效用户ID,gid 和 cgid 成员被设置成当前进程的有效组ID;
flag中的读写权限位存放在msg_perm.mode 中;
msg_qnum, msg_lspid, msg_lrpid, msg_stime 和 msg_rtime 被置为0;
msg_ctime 被设置成当前时间;
msg_qbytes被设置成系统限制值;
创建一个消息队列的测试程序:
#include "apue.h"
#include <fcntl.h>
#include <sys/msg.h>
#define PATH_NAME "./Queue"
int main(void)
{
key_t key;
int fd;
if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
err_quit("open error");
close(fd);
//生成键值
key = ftok(PATH_NAME, 0);
int msgID;
if ((msgID = msgget(key, IPC_CREAT | 0666)) == -1)
err_quit("msgget error");
printf("key: %x\n", key);
printf("msgID: %d\n", msgID);
exit(0);
}
输出结果:
[root@localhost 15]# ./a.out
key: 235a2
msgID: 0
创建消息队列之后可以在终端上输入命令查看消息队列情况:
[root@localhost 15]# ipcs -q -i 0
Message Queue msqid=0
uid=0 gid=0 cuid=0 cgid=0 mode=0666
cbytes=0 qbytes=65536 qnum=0 lspid=0 lrpid=0
send_time=Not set
rcv_time=Not set
change_time=Sun Jan 11 14:20:53 2015
消息队列的操作
向消息队列中发送消息,我们可以调用 msgsnd 函数实现该功能:
/*
* 函数功能:向消息队列中发送消息;
* 返回值:若成功则返回0,若出错则返回-1;
* 函数原型:
*/
#include <sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
/*
* msqid是消息队列的引用标识符;
* ptr是一个void指针,指向要发送的消息;
* nbytes表示要发送消息的字节数;
* flag用于指定消息队列已满时的处理方法,当消息队列为满时,若设置为IPC_NOWAIT,则立刻出错返回EAGAIN;
* 否则发送消息的进程被阻塞,直到消息队列中空间或消息队列被删除或捕捉到信号时,函数返回;
*/
从消息队列中接收消息,我们可以调用 msgrcv 函数实现该功能:
/*
* 函数功能:从消息队列中接收消息;
* 返回值:若成功则返回消息的数据部分的长度,若出错则返回-1;
* 函数原型:
*/
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);
/*
* msqid是消息队列的引用标识符;
* ptr是一个void指针,指向要存放消息数据的缓冲区;
* nbytes表示要存放消息数据缓冲区的长度;
*
* 当返回的消息实际长度大于nbytes时,根据flag的设置进行处理:若设置为MSG_NOERROR,则消息被截短,否则出错返回E2BIG;
* 若指定的type无效时,若flag设置为IPC_NOWAIT,则立即出错返回,且errno设为ENOMSG,否则接收消息的进程将被阻塞,
* 直到type有效或者消息队列被删除或者捕捉到信号;
*
* type的取值如下:
* (1)type=0 接收消息队列中的第一条消息;
* (2)type>0 接收消息队列中类型为type的第一条消息;
* (3)type<0 接收消息队列中类型值小于或等于type绝对值的所有消息中类型最小的消息中的第一条消息;
* type为非0,则用于非先进先出顺序读消息;
*/
消息队列的控制
对消息队列的具体控制操作可以通过函数 msgctl 来实现:
/*
* 函数功能:消息队列的控制;
* 返回值:若成功则返回0,若出错则返回-1;
* 函数原型:
*/
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
根据 cmd 不同的取值有不同的操作,其中cmd 参数取值如下:
IPC_STAT:获取消息队列中的 msqid_ds 结构,并把它保存在 buf指向的缓冲区;
IPC_SET:按参数 buf 指向的结构中的值设置该消息队列对应的 msqid_ds 结构中四个字段—— 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;另一种是具有超级用户权限的进程。
测试程序:
#include "apue.h"
#include <sys/msg.h>
#include <fcntl.h>
#define PATH_NAME "./Queue"
key_t MakeKey(const char *pathname);
int main(void)
{
int msgid;
int status;
key_t key;
key = MakeKey(PATH_NAME);
char str1[] = "test message: Wellcome.";
char str2[] = "test message: goodbye.";
struct msgbuf
{
long msgtype;
char msgtext[MAXLINE];
}sndmsg, rcvmsg;
if((msgid = msgget(key, IPC_CREAT | 0666)) == -1)
err_quit("msgget error");
sndmsg.msgtype = 100;
sprintf(sndmsg.msgtext, str1);
if(msgsnd(msgid, (struct msgbuf *)&sndmsg, sizeof(str1)+1, 0) == -1)
err_quit("msgsnd error");
sndmsg.msgtype = 200;
sprintf(sndmsg.msgtext, str2);
if(msgsnd(msgid, (struct msgbuf *)&sndmsg, sizeof(str2)+1, 0) == -1)
err_quit("msgsnd error");
if((status = msgrcv(msgid, (struct msgbuf*)&rcvmsg, 128, 100, IPC_NOWAIT)) == -1)
err_quit("msgrcv error");
printf("Recevied message:\n%s\n", rcvmsg.msgtext);
if((status = msgrcv(msgid, (struct msgbuf*)&rcvmsg, 128, 200, IPC_NOWAIT)) == -1)
err_quit("msgrcv error");
printf("Recevied message:\n%s\n", rcvmsg.msgtext);
msgctl(msgid, IPC_RMID, 0);
exit(0);
}
key_t MakeKey(const char *pathname)
{
int fd;
if((fd = open(pathname, O_CREAT, 0666)) < 0)
err_quit("open error");
close(fd);
return ftok(pathname, 0);
}
输出结果:
[root@localhost 15]# gcc 15-6.c
[root@localhost 15]# ./a.out
Recevied message:
test message: Wellcome.
Recevied message:
test message: goodbye.
[root@localhost 15]#
15.8 信号量
这里所介绍的信号量是一种计数信号量集,它是一个计数器,用于多进程对共享数据对象的访问。共享资源通常分为两类:一类是互斥共享资源,即任一时刻只允许一个进程访问该资源;另一类是同步共享资源,即同一时刻允许多个进程访问该资源;信号量是解决互斥共享资源的同步问题而引入的机制。
当有进程要求使用共享资源时,需要执行以下操作:
(1)系统首先要检测控制该该资源的信号量;
(2)若该资源的信号量值大于0,则进程可以使用该资源,此时,进程将该资源的信号量值减1;
(3)若该资源的信号量值为0,则进程进入休眠状态,直到信号量值大于0时进程被唤醒,访问该资源;
当进程不再使用由一个信号量控制的共享资源时,该信号量值增加1,如果此时有进程处于休眠状态等待此信号量,则该进程会被唤醒。
每个信号量集都有一个与其相对应的结构,该结构定义如下:
/* Data structure describing a set of semaphores. */
struct semid_ds
{
struct ipc_perm sem_perm; /* operation permission struct */
struct sem *sem_base; /* ptr to array of semaphores in set信号量 */
unsigned short sem_nsems; /* # of semaphores in set */
time_t sem_otime; /* last-semop() time */
time_t sem_ctime; /* last-change time */
};
/* Data structure describing each of semaphores. */
struct sem
{
unsigned short semval; /* semaphore value, always >= 0 */
pid_t sempid; /* pid for last successful semop(), SETVAL, SETALL */
unsigned short semncnt; /* # processes awaiting semval > curval */
unsigned short semzcnt; /* # processes awaiting semval == 0 */
};
信号量集的结构图如下所示:
信号量集的创建与打开
信号量的创建函数定义如下:
/* 函数功能:创建一个新的信号量或打开一个现有的信号量;
* 返回值:若成功则返回信号量ID,若出错则返回-1;
* 函数原型:
*/
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag);
/*
* 说明:
* key是信号量集的键;
* nsems表示信号量的个数;
* flag用于表示调用函数的操作类型,也可用于设置信号量集的访问权限;
* flag访问权限值:IPC_CREAT,IPC_EXCL以及IPC指定的权限位,若IPC_CREAT | IPC_EXCL,当信号量集返回出错,errno设为EEXIST;
*/
当成功创建了一个新信号量集,则相应的 semid_ds 结构成员会被初始化为以下值, 但是必须注意,信号量集中的每个信号量并没有被初始化,必须调用函数 semctl 对其进行初始化:
sem_perm 结构的 uid 和 cuid 成员被置为调用进程的有效用户ID,gid 和 cgid 成员被置为调用进程的有效组ID。
flag 参数中的读写权限位存入sem_perm.mode。
sem_otime 被置为0,sem_ctime 则被置为当前时间。
sem_nsems被置为nsems参数的值。
与该信号量集合中每个信号量关联的各个 sem 结构并不初始化。这些结构是在以 SET_VAL 或 SETALL 命令调用 semctl 时初始化的。
信号量的操作
对信号量的操作,可以使用函数 semop 实现,具体实现如下:
/* 函数功能:对信号量进行操作;
* 返回值:若成功则返回0,若出错则返回-1;
* 函数原型:
*/
#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);
/*
* 说明:
* semid是信号量的引用ID;
* nops是semoparray数组元素的个数;
* semoparray是一个指针,指向信号量操作数组;
*/
semoparray 是一个指向数组的指针,数组每个元素表示一个操作, 由于此函数是一个原子操作,一旦执行就将执行数组中的所有操作。其中 sembuf 结构定义如下:
struct sembuf
{
unsigned short sem_num; /* number # in set (0,1,..., nsems-1) */
short sem_op; /* operation (negative, 0, or positive) */
short sem_fig; /* IPC_NOWAIT, SEM_UNDO */
}
每个信号量成员操作 sem_op 不同的取值,会执行不同的操作,下面是不同取值对应的操作:
(1)若 sem_op 大于0,表示进程对资源使用完毕,交回该资源,即对应进程释放占用的资源数;此时信号量集 semid_ds 结构成员sem_base.semval 将加上 sem_op 的值,即信号量值增加 sem_op;若此时,信号量指定了标志 sem_flg 为 SEM_UNDO 时,则信号量调整值减去 sem_op 的绝对值。
(2)若 sem_op 等于0,则表示进程等待,直到信号量值 sem_base.semval变为0。
(3)若 sem_op 小于0,表示希望使用由该信号量控制的资源,此时需比较 sem_base.semval和 sem_op 的绝对值的大小;
如果 sem_base.semval 大于等于 sem_op 的绝对值,说明有足够的资源给进程使用,则从信号量值 sem_base.semval 减去 sem_op 的绝对值。若此时,信号量指定了标志 sem_flg 为 SEM_UNDO 时,则信号量调整值加上 sem_op 的绝对值。
如果 sem_base.semval 小于 sem_op 的绝对值,表示资源不够进程使用,若信号量指定了标志 sem_flg 为 IPC_NOWAIT 时,则该函数出错返回 EAGAIN;否则 semid_ds 结构成员 sem_base.semncnt 将加1,进程等待直到 sem_base.semval 大于等于 sem_op 的绝对值或该信号量被删除。
信号量的控制
对信号量的具体控制可以通过函数 semctl 来实现,该函数定义如下:
/* 函数功能:对信号量进行控制;
* 函数原型:
*/
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, .../* union semun arg */);
/*
* 该函数的返回依赖参数cmd;
* semid是信号量集的ID;
* semnum是指定某个特定的信号量;
* cmd是希望该函数执行的操作;
*/
其中联合体 semun 参数结构如下:
union semun
{
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
unsigned short *array; /* for GETALL and SETALL */
};
该函数的 cmd 参数可以取如下值:
IPC_STAT:获取此信号量集合的 semid_ds 结构,保存在arg.buf 所指向的缓冲区中;
IPC_SET:按参数 arg.buf 指向的结构中的值来设定该信号量对应的 semid_ds 结构中 sem_perm 中的uid,gid,mode;
IPC_RMID:从系统中删除该信号量集合。这种删除立即发生,仍在使用该信号量集的其他进程,在下次对该信号量集进行操作的时候,会发生错误并返回 EIDRM;
GETVAL:获取 semid 所表示的信号量集的semnum 所指定信号量的值;
SETVAL:设置获取 semid 所表示的信号量集的semnum 所指定信号量的值,该值由第四个参数 arg 中的 val 指定;
GETPID:获取 semid 所表示的信号量集中最后一个操作 semop 函数的进程ID,即 semid_ds 结构中的 sem.sempid的值;
GETNCNT:获取 semid 所表示的信号量集中的等待给定信号量锁的进程数目,即 semid_ds 结构中的 sem.semncnt 的值;
GETZCNT:获取 semid 所表示的信号量集中的等待信号量成为0的进程数目,即 semid_ds 结构中的 sem.semzcnt 的值;
GETALL:获取 semid 所表示的信号量集中信号量的个数,并保存在 arg.array 中;
SETALL:按 arg.array 所指向的数组中的值,设置集合中信号量的个数;
测试程序:
#include "apue.h"
#include <sys/sem.h>
#include <fcntl.h>
#include <sys/ipc.h>
//#define PATH_NAME "./Sem"
key_t MakeKey(const char *pathname);
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}; //信号量值
int main(int argc, char *argv[])
{
int semid, nsems, i;
key_t key;
struct semid_ds seminfo;
unsigned short *ptr;
union semun arg;
if(argc != 2)
err_quit("usage: a.out <pathname>");
key = MakeKey(argv[1]);
if((semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666)) >= 0)
{
arg.val = 2;
semctl(semid, 0, SETVAL, arg);
}
else if(errno == EEXIST)
{
semid = semget(key, 1, 0666);
arg.val = 4;/* 设置信号量值*/
semctl(semid, 0, SETVAL, arg);
}
else
err_quit("semget error");
arg.buf = &seminfo;
/* 对信号量集取ipc_perm结构,并保存在array.buf所指的缓冲区中 */
if(semctl(semid, 0, IPC_STAT, arg) < 0)
err_quit("semctl error");
nsems = arg.buf->sem_nsems;/* 获取信号量的个数 */
ptr = (unsigned short *)calloc(nsems, sizeof(unsigned short));
arg.array = ptr;
/* 获取信号量集中信号量的值,并保存在arg.array所指向的数组中,即保存在ptr所指的地址 */
semctl(semid, 0, GETALL, arg);
for(i = 0; i < nsems; i++)
printf("semval[%d] = %d\n", i, ptr[i]);
exit(0);
}
key_t MakeKey(const char *pathname)
{
int fd;
if((fd = open(pathname, O_CREAT, 0666)) < 0)
err_quit("open error");
close(fd);
return ftok(pathname, 0);
}
输出结果:
./sem Sem
semval[0] = 4
15.9 共享存储(共享内存)
共享内存是允许两个或多个进程共享同一块内存区域,并通过该区域实现数据交换的进程间通信机制。通常是由一个进程开辟一块共享内存区域,然后允许多个进程对此区域进行访问。由于不需要任何介质,而是数据由内存直接映射到进程空间,即数据不需要在客户进程和服务进程之间复制,所以共享内存是最快的 IPC机制。共享内存必须解决多个进程之间同步访问的问题,必须控制同一时刻只允许一个进程对共享内存区域进行写入数据操作,否则会造成数据混乱。同步访问问题可以使用信号量或者记录锁进行解决。
每个共享内存都有相对应的 shmid_ds 结构,其定义如下:
/* 共享内存 */
/* shmid_ds 结构 */
struct shmid_ds
{
struct ipc_perm shm_perm; /* operation permission struct */
size_t shm_segsz; /* size of segment in bytes */
pid_t shm_lpid; /* pid of last shmop() operation */
pid_t shm_cpid; /* pid of creator */
shmatt_t shm_nattch; /* number of current attaches */
time_t shm_atime; /* last-attach time */
time_t shm_dtime; /* last-detach time */
time_t shm_ctime; /* last-change time */
};
共享内存创建与打开
为了能够使用共享内存,我们要创建一个共享内存区域,并获取该共享内存的标识符,可以调用函数 shmget 实现该功能:
/*
* 函数功能:创建一个新的共享内存区域或打开一个现有的共享内存区域;
* 返回值:若成功则返回共享内存ID,若出错则返回-1;
* 函数原型:
*/
#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
/*
* 说明:
* key是共享内存的键值;
* size是共享内存区域的大小;
* flag设置共享内存的访问权限;
* (1)当key为IPC_PRIVATE时,此时flag的取值对该函数不起作用;
* (2)当key不为IPC_PRIVATE时,且flag设置为IPC_CREAT,则执行操作由key值决定;
* (3)当key不为IPC_PRIVATE时,且flag同时设置为IPC_CREAT | IPC_EXCL,则只执行创建共享内存操作,此时key必须不同于内核已存在的共享内存的键值,否则出错返回;
*/
共享内存与地址空间的连接和断开
当一个共享内存被创建或打开之后,进程若要使用该共享内存,则必须要是该共享内存连接到它的地址空间,可以调用函数 shmat 实现该功能,若共享内存不再使用时,必须从内核系统中删除,可以调用函数 shmdt 实现:
/*
* 函数功能:将共享内存连接到它的地址空间;
* 返回值:若成功则返回指向共享内存的指针,若出错则返回-1;
* 函数原型:
*/
#include <sys/shm.h>
void *shmat(int shmid, void *addr, int flag);
/*
* 说明:
* shmid是共享内存的ID;
* adrr和flag共同决定函数的返回:
* (1)若addr为0,则此段连接到由内核选择的第一个可用地址上,此时flag取任何值都无效;
* (2)若addr不为0,而flag未设置SHM_RND,则共享内存区域连接到由addr指定的地址处;
* (3)若addr不为0,而flag设置了SHM_RND,则共享内存区域连接到由(adrr-(addr mod ulus SHMLBA))指定的地址处;
* 其中SHM_RND表示取整,SHMLBA表示低边界地址倍数;
* 若flag指定SHM_RDONLY,则以只读方式连接,否则以读写方式连接;
*/
/*
* 函数功能:将共享内存与它的地址空间断开;
* 返回值:若成功则返回0,若出错则返回-1;
* 函数原型:
*/
#include <sys/shm.h>
int shmdt(void *addr);
共享内存的控制操作
对共享内存区域进行多中控制操作可以通过函数 shmctl 实现,其定义如下:
/*
* 函数功能:对共享内存进行控制操作;
* 返回值:若成功则返回0,若出错则返回-1;
* 函数原型:
*/
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
该函数提供了三个 cmd 命令:
IPC_RMID:从系统中删除由 shmid 标识的共享内存区并拆除它。
IPC_SET :给所指定的共享内存区设置其shmid_ds 结构的以下三个成员:shm_perm.uid,shm_perm.gid和shm_perm.mode,他们的值来自buff参数指向的结构中的相应成员。shm_ctime的值也用当前时间替换。
IPC_STAT :(通过buff参数)向调用者返回所指定共享内存区当前的 shmid_ds 结构,将其保存在buf所指向的缓冲区。
另外 Linux 提供了下列另外两种命令:
SHM_LOCK:将共享内存锁定在内存中,此命令只能超级用户才可以执行。
SHM_UNLOCK:解锁共享内存,此命令只能超级用户才可以执行。
测试程序:创建一个共享内存区,然后打印该共享内存区的属性;
#include "apue.h"
#include <sys/shm.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/types.h>
#define ARRAY_SIZE 4000
#define MALLOC_SIZE 10000
#define SHM_SIZE 10000
#define SHM_MODE (SHM_R | SHM_W)
void printf_shm(struct shmid_ds *buf);
key_t MakeKey(const char *pathname);
char array[ARRAY_SIZE];
int main(int argc, char *argv[])
{
if(argc != 2)
err_quit("usage: a.out <pathname>");
int shmid;
char *ptr;
void *shmptr;
struct shmid_ds shmids;
key_t key;
key = MakeKey(argv[1]);
printf("array[] from %x to %x\n", &array[0],&array[ARRAY_SIZE]);
printf("stack around %x\n", &shmid);
if((ptr = (char *)malloc(MALLOC_SIZE)) == NULL)
err_sys("malloc error");
printf("malloced from %x to %x\n", ptr, ptr+MALLOC_SIZE);
if((shmid = shmget(key, SHM_SIZE, SHM_MODE | IPC_CREAT)) < 0)
err_quit("shmget error");
if((shmptr = shmat(shmid, 0, 0)) == (void *)-1)
err_sys("shmat error");
if(shmctl(shmid, IPC_STAT, &shmids) == -1)
err_sys("shmctl error");
printf_shm(&shmids);
if(shmctl(shmid, IPC_RMID, 0) < 0)
err_sys("shmctl error");
exit(0);
}
key_t MakeKey(const char *pathname)
{
int fd;
if((fd = open(pathname, O_CREAT, 0666)) < 0)
err_quit("open error");
close(fd);
return ftok(pathname, 0);
}
void printf_shm(struct shmid_ds *buf)
{
printf("Struct shmid_ds:\n");
printf("\tshm_nattch = %d\n", buf->shm_nattch);
printf("\tshm_segsz = %d\n", buf->shm_segsz);
printf("\tStruct ipc_perm:\n");
printf("\t\tuid = %d\n", buf->shm_perm.uid);
printf("\t\tgid = %d\n", buf->shm_perm.gid);
printf("\t\tcuid = %d\n", buf->shm_perm.cuid);
printf("\t\tcgid = %d\n", buf->shm_perm.cgid);
return;
}
输出结果:
[root@localhost 15]# ./a.out Shm
array[] from 8049fc0 to 804af60
stack around bff84794
malloced from 821d008 to 821f718
Struct shmid_ds:
shm_nattch = 1
shm_segsz = 10000
Struct ipc_perm:
uid = 0
gid = 0
cuid = 0
cgid = 0
[root@localhost 15]#