《UNIX环境高级编程》笔记--XSI IPC

来源:互联网 发布:rng sky 知乎 编辑:程序博客网 时间:2024/05/21 10:43

1.XSI IPC

有三种IPC我们称作XSI IPC,即消息队列,信号量和共享存储器,他们之间有很多相似之处。

1.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。

1.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字段值类似于普通文件的访问权限,但是没有执行权限。

1.3.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.可以用非先进先出方式处理。

2.消息队列

消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。
msgget函数用于创建一个新队列或打开一个现存的队列,msgsnd将新消息添加到队列尾端。每个消息包含一个正长整型
字段,一个非负长度以及实际数据字节,所有这些都再将消息添加到队列时,传送给msgsnd。msgrcv用于从队列中取消息,
我们并不一定以先进先出次序取消息,也可以按消息的类型字段取消息。
每个队列都有一个msqid_ds结构与其相关联:
struct msqid_ds{
struct ipc_perm  msg_perm; //ipc结构
msgqnum_t  msg_qnum; //队列中消息的数量
msglen_t msg_qbytes; //队列中最大的字节数
pid_t msg_lspid; //最后调用msgsnd的pid
pid_t msg_lrpid; //最后调用msgrcv的pid
time_t msg_stime; //最后调用msgsnd函数的时间
time_t msgrtime; //最后调用msgrcv函数的时间
time_t msg_ctime; //最后change的时间
......
};
此结构规定了队列的当前状态。
下表列出了影响消息队列的系统限制。表中“notsup”表示相关平台不支持该特性。“derived”表示这种限制时从其他限制导出来的。

调用第一个函数通常是msgget,其功能是打开一个现存队列或创建一个新队列。
#include<sys/msg.h>·int msgget(key_t key, int flag); //成功则返回消息队列ID,出错则返回-1.
本文中已经说明了将key变换成一个标识符的规则,并且讨论是否创建一个新队列或访问一个现存队列。当创建一个新队列
时,初始化msqid_ds结构的下列成员:
ipc_perm结构按照上面所述进行初始化,该结构中mode按flag中相应权限位设置。
msg_qnum,msg_lspid,msg_lrpid,msg_stime和msg_rtime都设置为0.
msg_ctime设置为当前时间。
msg_qbytes设置为系统限制值。

msgctl函数对队列执行多种操作。
#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将数据放到消息队列中。
#include<sys/msg.h>int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);//成功则返回0,出错则返回-1.
每个消息由三部分组成,它们是:正长整型类型字段、非负长度以及实际数据字节。
ptr参数指向一个长整型数,它包含了正的整型消息类型,在其后紧跟着消息数据(若nbytes是0,则无消息数据),若发送
的最长消息是512字节,则可定义下面结构。
struct mymsg{
long mtype;
char mtext[512];
}
于是,ptr就是一个指向mymsg结构的指针,而nbytes为512,不是sizeof(struct mymsg)。
参数flag可以指定为IPC_NOWAIT。类似于文件IO的非阻塞IO标识,若消息队列已满,则指定IPC_NOWAIT使得msgsnd
立即出错返回EAGAIN。如果没有指定IPC_NOWAIT,则进程阻塞到直到下述情况出现为止:
1.有空间可以容纳要发送的消息
2.从系统中删除此队列。这种情况下,返回EIDRM。
3.捕捉到一个信号,并从信号处理程序返回。这种情况下,放回EINTR。
当flag为0时,忽略该参数。

调用msgrcv从队列中取用消息。
#include<sys/msg.h>ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);//成功返回消息的数据部分长度,出错返回-1.
如同msgsnd中一样,ptr参数指向一个长整型数,跟随其后的是存放实际消息数据的缓冲区。nbyte说明数据缓冲区的长度,
为512,不是sizeof(struct mymsg)。
若返回的消息大于nbytes,而且在flag中设置了MSG_NOERROR,则该消息被截短。如果没有设置这一标志,而且消息又
太长,则出错返回E2BIG(消息仍然在队列中)
参数type可以指定想要哪一种消息:
type == 0 返回队列中的第一个消息。
type > 0 返回队列中消息类型为type的第一个消息。
type < 0 返回队列中消息类型值小于或等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。
当flag为0时,忽略该参数。

实践:
msg.c:创建消息队列,然后写入消息。
#include<stdio.h>#include<stdlib.h>#include<sys/ipc.h>#include<sys/msg.h>#include<string.h>#include<sys/stat.h>#define MSG_FILE "msgfile"#define BUFF_SIZE 255struct msgtype{        long mtype;        char buffer[BUFF_SIZE+1];};int main(void){        struct msgtype msg;        key_t key;        int msgqid;        int result;        if((key = ftok(MSG_FILE,1)) == -1){                perror("ftok");                return -1;        }        if((msgqid = msgget(key, IPC_CREAT|IPC_EXCL|S_IRUSR|S_IWUSR)) == -1 ){                perror("msgget");                return -1;        }        memset(&msg, 0,sizeof(struct msgtype));        msg.mtype = 1;        printf("msgqid:%d\n",msgqid);        strncpy(msg.buffer,"hello word",BUFF_SIZE);        result = msgsnd(msgqid,&msg,256,0);        if(result == -1){                perror("msgsnd");                return -1;        }        return 0;}

msg2.c:读取消息队列中的内容。
#include<stdio.h>#include<stdlib.h>#include<sys/ipc.h>#include<sys/msg.h>#include<string.h>#include<sys/stat.h>#define MSG_FILE "msgfile"#define BUFF_SIZE 255struct msgtype{        long mtype;        char buffer[BUFF_SIZE+1];};int main(void){        struct msgtype msg;        key_t key;        int msgqid;        int result;        if((key = ftok(MSG_FILE,1)) == -1){                perror("ftok");                return -1;        }        if((msgqid = msgget(key, S_IRUSR|S_IWUSR)) == -1 ){                perror("msgget");                return -1;        }        memset(&msg, 0,sizeof(struct msgtype));        result = msgrcv(msgqid,&msg,256,0,0);        if(result == -1){                perror("msgcrv");                return -1;        }        printf("msg.mtype:%ld\n",msg.mtype);        printf("msg.buffer:%s\n",msg.buffer);        return 0;}
运行结果:
root@gmdz-virtual-machine:~# ./msg
msgqid:425984
root@gmdz-virtual-machine:~# ipcs -q (使用该命令查看消息队列信息,如果要删除某队列,使用ipcrm -q $msqid)
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages
0x0101001f 458752     root       600        256          1
root@gmdz-virtual-machine:~# ./msg2
msg.mtype:1
msg.buffer:hello word

3.信号量

信号量是一个计数器,用于多进程对共享数据对象的访问。
为了获得共享资源,进程需要执行下列操作:
1.测试控制该资源的信号量。
2.若此信号量的值为正,则进程可以使用该资源,进程将信号量减1,表示它使用一个资源单位。
3.若此信号量的值为0,则进程进入休眠状态,直到信号量值大于0。
当进程不再使用一个信号量控制的共享资源时,该信号量加1。如果有进程正在休眠等待此信号量,则唤醒它们。
为了正确地实现信号量,信号量值的测试及加减操作应当是原子操作。

内核为每个信号量集合设置一个semid_ds结构:
struct semid_ds{
struct ipc_perm sem_perm;
unsigned short sem_nsems; //信号量在信号量集中的编号
time_t sem_otime; 最后调用semop()的时间。
time_t sem_ctime; 最后进行change的时间。
....
}
每个信号量由一个无名结构表示,它至少包含下列成员。
struct{
unsigned short semval; //信号量值,>=0
pid_t sempid; //最后使用信号量的pid
unsigned short semcnt; //等待semval变为大于其当前值的线程或进程数
unsigned short semzcnt; //等待semval变成0的线程或进程数
}

下表为影响信号量的系统限制。


要获得一个信号量ID,调用semget。
#include<sys/mem.h>int semget(key_t key, int nsems, int flag); //成功则返回信号量ID,出错则返回-1.
上节中说明了将key变换成标识符规则,讨论是否创建一个新集合,或是引用一个现存的集合。创建一个新集合时,对
semid_ds结构的下列成员赋初值。
1.按上节所述,对ipc_perm结构赋初值。结构中的mode被置为flag中的相应权限位。
2.sem_otime设置为0.
3.sem_ctime设置为当前时间。
4.sem_nsems设置为nsems。
nsems是该集合中的信号量数,如果是创建新集合,则必须指定nsems。如果引用一个现存的集合,则将nsems指定为0.

semctl函数包含多种信号量操作。
#include<sys/sem.h>int semctl(int semid, int semnum, int cmd .../* union semun arg */); 
依赖于所请求的命令,第四个参数是可选的,如果使用该参数,则其类似是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参数指定下列10中命令中的一种,在semid指定的信号量集合上执行此命令,其中有5条命令是针对一个特定的信号量值
的,他们用semnum指定该信号量集合中的一个成员。semnum值在0和nsems-1之间。(包含0和nsems-1)
IPC_STAT:对此集合取semid_ds结构,并存放在由arg.buff指向的结构中。
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指向的数组中的值,设置该集合中所有信号量的值。
除GETALL以外的所有GET命令semctl函数都返回相应的值。其他命令返回值为0.

函数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; //信号集中的成员号(0,1....,nsems-1)
short sem_op; //加减操作
short sem_flg; //IPC_NOWAIT, SEM_UNDO
};
参数nops规定该数组中的操作的数量。

1.最易于处理的情况是sem_op为正。这对应于进程释放占用的资源数,sem_op值加到信号量的值上。如果指定了undo
标志,则也从该进程的此信号量调整值中减去sem_op。
2.若sem_op为负,则表示要获取由该信号量控制的资源。
如果信号量的值大于或等于sem_op的绝对值,则从信号量值中减去sem_op的绝对值。这保证信号量的结果大于或等于0。
如果指定了undo标志,则sem_op的绝对值也加到该进程的此信号量调整值上。(如果进程意外结束而没有释放资源,则
由系统通过信号量调整值来处理)
如果信号量值小于sem_op的绝对值,则
a.若指定了IPC_NOWAIT,则semop出错返回EAGAIN。
b.若未指定IPC_NOWAIT,则该信号量的semncnt值+1.然后调用进程被挂起直至下列事件之一发生;
(a).此信号量变成大于或等于sem_op的绝对值。此信号量的semncnt值-1,并从信号量中减去sem_op的绝对值。如果
     指定了undo标志,则sem_op的绝对值也加到该进程的此信号量调整值上。
(b).从系统中删除了此信号量。在此情况下,函数出错返回EIDRM。
(c).进程捕捉到一个信号,并从信号处理程序返回。在此情况下,此信号量的semncnt值-1,函数出错返回EINTR。
3.若sem_op为0,这表示调用进程希望等待到该信号量值变成0.
如果信号量值当前是0,则此函数立即返回。
如果信号量值非0,则:
a.若指定了IPC_NOWAIT,则出错返回EAGAIN。
b.若未指定IPC_NOWAIT,则该信号量的semzcnt值+1.然后调用进程被挂起。直至下列事件之一发生为止:
(a).此信号值变成0,此信号量的semzcnt值-1.
(b).从系统中删除了此信号量,在此情况下,函数出错返回EIDRM。
(c).进程捕捉到一个信号,从信号处理程序返回,在此情况下此信号量的semzcnt值-1,并且函数出错返回EINTR。

实践:
semv.c
#include <stdio.h>#include <sys/sem.h>#include <string.h>#include <fcntl.h>#define SEM_FILE "semfile"int main(void){        int ret;        key_t key;        int semid;        struct sembuf semoparrayw[] = {{0,0,SEM_UNDO}};        struct sembuf semoparray[] = {{0,1,SEM_UNDO}};        if((key = ftok(SEM_FILE,2)) == -1){                perror("ftok");                return -1;        }        if((semid = semget(key,1,IPC_CREAT|IPC_EXCL|S_IRUSR|S_IWUSR)) == -1){                perror("semget");                return -1;        }        while(1){                if((ret = semop(semid,semoparrayw,1)) == -1){                        perror("semop");                        return -1;                }                if((ret = semop(semid,semoparray,1)) == -1){                        perror("semop");                        return -1;                }                printf("1 process,add 1.\n");                sleep(2);        }        return 0;}

semp.c
#include <stdio.h>#include <sys/sem.h>#include <string.h>#include <fcntl.h>#define SEM_FILE "semfile"int main(void){        int ret;        key_t key;        int semid;        struct sembuf semoparray[] = {{0,-1,SEM_UNDO}};        if((key = ftok(SEM_FILE,2)) == -1){                perror("ftok");                return -1;        }        if((semid = semget(key,0,S_IRUSR|S_IWUSR)) == -1){                perror("semget");                return -1;        }        while(1){                if((ret = semop(semid,semoparray,1)) == -1){                        perror("semop");                        return -1;                }                printf("p process,subtract 1.\n");                sleep(2);        }        return 0;}

在不同的终端中运行:
root@virtual-machine:~# ./semv
1 process,add 1.
1 process,add 1.
1 process,add 1.
1 process,add 1.
1 process,add 1.
1 process,add 1.
1 process,add 1.
1 process,add 1.
1 process,add 1.
^C

root@virtual-machine:~# ./semp
p process,subtract 1.
p process,subtract 1.
p process,subtract 1.
p process,subtract 1.
p process,subtract 1.
p process,subtract 1.
p process,subtract 1.
p process,subtract 1.
^C

semv增加1个信号量,并等待被变成0,semp消耗信号量,两个进程交替打印信息。

4.共享存储

共享存储允许两个或更多进程共享一给定的存储区,因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种
IPC。使用共享存储时,多个进程之间对一给定存储区的同步访问。若服务器进程正在将数据放入共享存储区,则在它做完
这一操作之前,客户进程不应该去取这些数据。

内核为每个共享存储段设置了一个shmid_ds结构。
struct shmid_ds{
struct ipc_perm shm_perm;
size_t shm_segsz; //共享段的字节数
pid_t shm_lpid; //最后一次执行shmop的进程的pid
pid_t shm_cpid; //创建者的pid
shmatt_t shm_nattch; //当前attach的人数
time_t shm_atime; //最后attach时间
time_t shm_dtime; //最后detach时间
time_t shm_ctime; //最后change的时间
......
};

下图为影响共享存储的系统限制。



为了获得一个共享存储标识符,调用的第一个函数是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指定为0。当创建一个新段时,段内容初始
化为0。

shmctl函数对共享存储段执行多种操作。
#include<sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf); //成功则返回0,出错则返回-1.
cmd参数指定下列5种命令中一种,使其在shmid指定的段上执行。
IPC_STAT:取此段的shmid_ds结构,并将它放到由buf指向的结构中。
IPC_SET:按buf指向结构中的值设置于此段相关结构中的下列三个字段:shm_perm.uid,shm_perm.gid以及shm_perm.mode。
此命令只有由下列两种进程执行:一种是其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程;另一种是具有超级用户
特权的进程。
IPC_RMID:从系统中删除该共享存储段。因为每个共享存储段有一个连接计数。(shmid_ds结构中的shm_nattch字段),
所以除非使用该段的最后一个进程终止或与该段脱节。否则不会删除该存储段。不管此段是否仍在使用,该标识符立即被删除,
所以不能使用shmat与该段连接。此命令只能由下列两种进程执行:一种是其有效用户ID等于shm.perm.cuid或shm_perm.uid
的进程,另一种是超级用户进程。
Linux和Solaris提供了另外两种命令,但他们并非SUS的组成部分。
SHM_LOCK:将共享存储段锁定在内存中。此命令由超级用户执行。
SHM_UNLOCK:解锁共享存储段,此命令智能由超级用户执行。

一旦创建了一个共享存储段,进程就可调用shmat将其连接到它的地址空间中。
#include<sys/shm.h>void *shmat(int shmid, const void *addr, int flag); //若成功则返回指向共享内存的指针,出错则返回-1。
共享存储段连接到调用进程的那个地址上与addr参数以及在flag中是否制定SHM_RND位有关。
1.如果addr为0,则此段连接到内核选择的第一个可用地址上。这是推荐使用方式。
2.如果addr非0,并且没有指定SHM_RND(取整),则此段连接到addr所指定的地址上。
3.如果addr非0,并且指定了SHM_RND,则此段连接到(addr-(addr modulus SHMLBA))所表示的地址上。SHMLBA的
意思是低边界地址倍数,它总是2的乘方。该算式是将地址向下取最近1个SHMLBA的倍数。
如果flag为SHM_RDONLY,则以只读方式连接此段。否则以读写方式连接此段。
如果shmat执行成功,则shmid_ds结构中的shm_nattch计数器+1.

对共享存储段操作结束后,调用shmdt脱接该段。这并不是从系统中删除其标识符以及数据结构。
#include<sys/shm.h>int shmdt(void *addr); //成功则返回0,出错则返回-1.
addr参数是以前调用shmat时返回的值。如果成功,shmdt将使相关shmid_ds结构中的shm_nattch计数器-1。


实践:
shmw.c
#include<stdio.h>#include<sys/shm.h>#include<fcntl.h>#include<string.h>#define SHM_FILE "shmfile"int main(void){        key_t key;        int shmid;        char* des;        char str[10] = "123456789";        if((key = ftok(SHM_FILE,2)) == -1){                perror("ftok");                return -1;        }        if((shmid = shmget(key, 10, IPC_CREAT|IPC_EXCL|S_IRUSR|S_IWUSR)) == -1){                perror("shmget");                return -1;        }        if((des = (char*)shmat(shmid, 0, 0)) == (char*)-1){                perror("shmat");                return -1;        }        strncpy(des,str,10);        if(shmdt(des) == -1){                perror("shmdt");                return -1;        }        return 0;}

shmr.c
#include<stdio.h>#include<sys/shm.h>#include<fcntl.h>#include<string.h>#define SHM_FILE "shmfile"int main(void){        key_t key;        int shmid;        char* shmaddr;        char str[10] = "000000000";        if((key = ftok(SHM_FILE,2)) == -1){                perror("ftok");                return -1;        }        if((shmid = shmget(key, 0, S_IRUSR|S_IWUSR)) == -1){                perror("shmget");                return -1;        }        if((shmaddr = (char*)shmat(shmid, 0, 0)) == (char*)-1){                perror("shmat");                return -1;        }        strncpy(str,shmaddr,10);        printf("%s\n",str);        if(shmdt(shmaddr) == -1){                perror("shmdt");                return -1;        }        return 0;}

运行结果:
root@virtual-machine:~# ./shmw

root@virtual-machine:~# ./shmr
123456789
一个进程将信息写到共享内存中,另一个能够成功读取到。
0 0
原创粉丝点击