能力值:
( LV9,RANK:260 )
|
-
-
2 楼
欢迎转载,请保留作者信息
bill@华中科技大学
http://billstone.cublog.cn+
十二 消息队列
消息队列是UNIX内核中的一个先进先出的链表结构. 相对于管道, 消息队列有明显的优势, 原因在于:
(1) 消息队列是一种先进先出的队列型数据结构, 可以保证先送的货物先到达, 后送的货物后到达, 避免了插队现象.
(2) 信息队列将输出的信息进行了打包处理, 这样就可以保证以每个消息为单位进行接收了.
(3) 消息队列还可以对信息进行分类处理, 标记各种类别的信息, 这样就可以根据信息类别分别出列.
IPC就是进程间通信, 侠义上讲, IPC指消息队列, 信号量和共享内存三种对象. 通过shell命令ipcs可以查询当前系统的IPC对象信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [bill@billstone Unix_study]$ ipcs
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 196609 bill 777 393216 2 dest
0x00000000 491522 root 644 106496 2 dest
0x00000000 524291 root 644 110592 2 dest
0x00000000 557060 root 644 110592 2 dest
0x00000000 589829 root 644 110592 2 dest
------ Semaphore Arrays --------
key semid owner perms nsems
------ Message Queues --------
key msqid owner perms used-bytes messages
[bill@billstone Unix_study]$
|
消息队列简介
UNIX内核使用结构msqid_ds来管理消息队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue, unused */
struct msg *msg_last; /* last message in queue, unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
|
其中msg结构的定义如下, 我们在实际项目中几乎很少使用如下的结构, 一般都需要我们根据实际的需求自行定义.
1 2 3 4 5 6 | struct msg{
struct msg* msg_next;
long msg_type;
long msg_ts;
short msg_spot;
};
|
理论上可以通过结构msqid_ds的成员msg_first, msg_last和结构msg的成员msg_next遍历全部消息队列并完成管理和维护消息队列的功能, 但实际上这三个成员是UNIX内核的直辖数据, 用户无法引用.
消息队列中消息本身是由消息类型和消息数据组成, 我们常常使用如下结构作为消息模板:
1 2 3 4 | struct msgbuf {
long mtype; /* type of message */
char mtext[1]; /* message text */
};
|
根据消息类型的不同, 我们可以在同一个信息队列中定义不同功能的消息.
使用消息队列
(1) 消息队列的创建
在UNIX中, 采用函数msgget创建消息队列
1 2 3 4 | extern int msgget (key_t __key, int __msgflg) __THROW;
|
函数msgget创建一个新的消息队列, 或者访问一个已经存在的消息队列. 调用成功时返回消息队列的标志符, 否则返回-1.
(2) 消息队列的发送和接收
在UNIX中函数msgsnd向消息队列发送消息, 原型如下:
1 2 3 4 | extern int msgsnd (int __msqid, __const void *__msgp, size_t __msgsz, int __msgflg) __THROW;
|
发送消息一般分五个步骤:
a) 根据自己的需要定义消息结构
1 2 3 4 | struct msgbuf {
long mtype; /* type of message */
char ctext[100]; /* message data */
};
|
b) 打开或创建消息队列
1 2 | msgid = msgget(Key, 0666 | IPC_CREAT);
if (msgid < 0) 打开(或创建)队列失败
|
c) 组装消息
d) 发送消息
1 2 | int ret;
ret = msgsnd(msgid, (void *)&buf, strlen(buf.ctext), IPC_NOWAIT);
|
e) 发送判断
1 2 3 4 | If (ret == -1){
if (errno == EINTR) 信号中断, 重新发送;
else 系统错误
}
|
下面是一个发送消息的实例: 它循环读取键盘输入, 将输入的字符串信息写入到消息队列(关键字为0x1234)中.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | [bill@billstone Unix_study]$ cat msg1.c
extern int errno;
struct mymsgbuf{
long mtype;
char ctext[100];
};
int main(void)
{
struct mymsgbuf buf;
int msgid;
if ((msgid = msgget(0x1234, 0666 | IPC_CREAT)) < 0){
fprintf(stderr, "open msg %X failed\n" , 0x1234);
exit (1);
}
while (1){
printf ( "Please input: " );
memset(&buf, 0, sizeof(buf));
fgets(buf.ctext, sizeof(buf.ctext), stdin);
buf.mtype = getpid();
while ((msgsnd(msgid, (void *)&buf, strlen(buf.ctext), IPC_NOWAIT)) < 0){
if (errno == EINTR)
continue ;
exit (2);
}
if (!strncmp(buf.ctext, "exit" , 4))
break ;
}
return 0;
}
[bill@billstone Unix_study]$
|
运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [bill@billstone Unix_study]$ make msg1
cc msg1.c -o msg1
[bill@billstone Unix_study]$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
[bill@billstone Unix_study]$ . /msg1
Please input: Hello world!
Please input: Nice to meet you!
Please input: bye!
Please input: exit
[bill@billstone Unix_study]$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x00001234 98304 bill 666 41 4
[bill@billstone Unix_study]$
|
在UNIX中函数msgrcv从消息队列中接收消息, 原型如下:
1 2 3 4 | extern int msgrcv (int __msqid, void *__msgp, size_t __msgsz, long int __msgtyp, int __msgflg) __THROW;
|
与函数msgsnd不同,这里参数msgsz指的是缓冲区的最大容量,包括消息类型占用的部分.
这里配合上面的例子设计接收消息的实例: 以阻塞方式不断地从消息队列(关键字为0x1234)中读取消息, 并打印接收到的消息类型, 长度和数据等信息, 当接收到数据内容为'exit'时程序结束.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | [bill@billstone Unix_study]$ cat msg2.c
extern int errno;
struct mymsgbuf{
long mtype;
char ctext[100];
};
int main(void)
{
struct mymsgbuf buf;
int msgid, ret;
if ((msgid = msgget(0x1234, 0666 | IPC_CREAT)) < 0){
fprintf(stderr, "open msg %X failed\n" , 0x1234);
exit (1);
}
while (1){
memset(&buf, 0, sizeof(buf));
while ((ret = msgrcv(msgid, (void *)&buf, sizeof(buf), 0, 0)) < 0){
if (errno == EINTR)
continue ;
fprintf(stderr, "Error no: %d" , errno);
exit (2);
}
fprintf(stderr, "Msg: Type=%d, Len=%d, Text:%s" , buf.mtype, ret, buf.ctext);
if (!strncmp(buf.ctext, "exit" , 4))
break ;
}
return 0;
}
[bill@billstone Unix_study]$
|
运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | [bill@billstone Unix_study]$ make msg2
cc msg2.c -o msg2
[bill@billstone Unix_study]$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x00001234 98304 bill 666 41 4
[bill@billstone Unix_study]$ . /msg2
Msg: Type=15666, Len=13, Text:Hello world!
Msg: Type=15666, Len=18, Text:Nice to meet you!
Msg: Type=15666, Len=5, Text:bye!
Msg: Type=15666, Len=5, Text: exit
[bill@billstone Unix_study]$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x00001234 98304 bill 666 0 0
[bill@billstone Unix_study]$
|
从上面可以看到, 采用消息队列通信比采用管道通信具有更多的灵活性.
系统调用msgctl对消息队列进行各种控制, 包括查询消息队列数据结构, 改变消息队列访问权限, 改变消息队列属主信息和删除消息队列等, 原型如下:
1 2 3 4 | int msgctl(int msqid, int cmd, struct msqid_ds *buf);
|
根据cmd参数对msqid消息队列操作:
a) IPC_RMID: 删除消息队列
b) IPC_STAT: 读取消息队列
c) IPC_SET: 重置消息队列结构msqid_ds中的成员uid, gid, mode及msg_qbytes.
|
能力值:
( LV9,RANK:260 )
|
-
-
3 楼
有名管道FIFO中的问题解决了(红色标记):
在fifo2.c程序中 fgets(buf, strlen(buf), fp);应该调用sizeof函数而非strlen函数,测试成功通过。
|
能力值:
( LV9,RANK:260 )
|
-
-
4 楼
欢迎转载,请保留作者信息
bill@华中科技大学
http://billstone.cublog.cn
十三章 信号量
进程间的通信不仅仅包括数据交流, 也包括过程控制.
信号量是一个可以用来控制进程存储共享资源的计数器, 它可以是跟踪共享资源的生产和消费的计数器, 也可以是协调资源的生产者和消费者之间的同步器, 还可以是控制生产进程和消费进程的互斥开关.
信号量简介
操作系统通过信号量和PV操作, 可以完成同步和互斥操作.
信号量集合由一个或多个信号量集合组成, IPC对象中的'信号量'通常指的是信号量集合, UNIX的内核采用结构semid_ds来管理信号量, 结构如下:
1 2 3 4 5 6 7 8 9 10 | struct semid_ds {
struct ipc_perm sem_perm; /* permissions .. see ipc.h */
__kernel_time_t sem_otime; /* last semop time */
__kernel_time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct sem_queue *sem_pending; /* pending operations to be processed */
struct sem_queue **sem_pending_last; /* last pending operation */
struct sem_undo *undo; /* undo requests on this array */
unsigned short sem_nsems; /* no. of semaphores in array */
};
|
指针sem_base指向一个信号量数组,信号量由结构sem记载,如下所示:
1 2 3 4 5 6 | Struct sem{
unsigned short semval; // 信号量取值
pid_t sempid; // 最近访问进程ID
unsigned short semncnt; // P阻塞进程数
unsigned short semzcnt; // Z阻塞进程数
}
|
在Shell中可以通过'ipcs -a -s'命令查询系统中信号量的基本信息.
(1) 信号量的创建
在UNIX中, 函数semget用来创建信号量, 原型如下:
1 2 | int semget(key_t key, int nsems, int semflg);
|
函数semget创建一个新的信号量, 或者访问一个已经存在的信号量.
(2) 信号量的控制
系统调用semctl用来控制信号量, 原型如下:
1 2 3 4 | int semctl(int semid, int semnum, int cmd, ...);
|
函数semctl对标识号为semid的信号量集合中序号为semnum的信号量进行赋值, 初始化, 信息获取和删除等多相操作, 参数cmd指定了操作的类型, 参数arg指定了函数输入输出的缓冲区, 定义如下:
1 2 3 4 5 6 7 | union semun {
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short *array; /* array for GETALL & SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
void *__pad;
};
|
函数semctl的第四个参数arg在本质上是一个4字节的缓冲区. 调用失败时返回-1并置errno.
本处设计一个类似于命令'ipcs'和命令'ipcrm'的程序ipcsem, 它从命令行参数中获取要执行的操作, 包括创建信号量, 读取信号量信息, 读取信号量取值和删除信号量等, 程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
if (a) fprintf(stderr, "%s failed.\n" , (b)); \
else fprintf(stderr, "%s success.\n" , (b));
int main(int argc, char *argv[1])
{
int semid, index, i;
unsigned short array[100];
struct semid_ds ds;
if (argc != 4)
return 0;
semid = atoi(argv[1]);
index = atoi(argv[2]);
if (argv[3][0] == 'c' ){
VerifyErr(semget(semid, index, 0666|IPC_CREAT|IPC_EXCL) < 0, "Create sem" );
}
else if (argv[3][0] == 'd' ){
VerifyErr(semctl(semid, 0, IPC_RMID, NULL) < 0, "Delete sem" );
}
else if (argv[3][0] == 'v' ){
fprintf(stderr, "T ID INDEX SEMVAL SEMIPID SEMNCNT SEMZCNT\n" );
fprintf(stderr, "s %6d %6d %10d %10d %10d %10d\n" , semid, index,
semctl(semid, index, GETVAL), semctl(semid, index, GETPID),
semctl(semid, index, GETNCNT), semctl(semid, index, GETZCNT));
}
else if (argv[3][0] == 'a' ){
ds.sem_nsems = 0;
VerifyErr(semctl(semid, 0, IPC_STAT, &ds) != 0, "Get Sem Stat" );
VerifyErr(semctl(semid, 0, GETALL, array) != 0, "Get Sem All" );
for (i=0;i<ds.sem_nsems;i++)
fprintf(stderr, "sem no [%d]: [%d]\n" , i, array[i]);
}
else
VerifyErr(semctl(semid, index, SETVAL, atoi(argv[3])) != 0, "Set Sem Val" );
return 0;
}
|
执行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | [bill@billstone Unix_study]$ make ipcsem
cc ipcsem.c -o ipcsem
[bill@billstone Unix_study]$ ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems
0x000003e8 0 bill 666 10
[bill@billstone Unix_study]$ . /ipcsem 2000 2 c
Create sem success.
[bill@billstone Unix_study]$ ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems
0x000003e8 0 bill 666 10
0x000007d0 65537 bill 666 2
[bill@billstone Unix_study]$ . /ipcsem 65537 0 100
Set Sem Val success.
[bill@billstone Unix_study]$ . /ipcsem 65537 0 v
T ID INDEX SEMVAL SEMIPID SEMNCNT SEMZCNT
s 65537 0 100 23829 0 0
[bill@billstone Unix_study]$ . /ipcsem 65537 1 200
Set Sem Val success.
[bill@billstone Unix_study]$ . /ipcsem 65537 0 a
Get Sem Stat success.
Get Sem All success.
sem no [0]: [100]
sem no [1]: [200]
[bill@billstone Unix_study]$ . /ipcsem 65537 0 d
Delete sem success.
[bill@billstone Unix_study]$ ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems
0x000003e8 0 bill 666 10
[bill@billstone Unix_study]$
|
操作信号量
信号量具有P, V和Z三种操作, 在UNIX中, 这些操作可以通过函数semop调用完成, 函数semop可以一次性操作同一信号量集合中的多个信号量. 原型如下:
1 2 3 4 | int semop(int semid, struct sembuf *sops, unsigned nsops);
|
函数semop对标识号为semid的信号量集合中的一个或多个信号量执行信号数值的增加, 减少或比较操作. 参数sops指向一个sembuf结构的缓冲区, nsops指定了缓冲区中存储的sembuf结构的个数.
1 2 3 4 5 | struct sembuf {
unsigned short sem_num; /* semaphore index in array */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
|
其中, 第一个信号的序号是0. sem_op指定了操作的类型:
a) 正数. V操作.
b) 负数. P操作.
c) 0. Z操作. 判断信号量数值是否等于0.
而sem_flg取值有IPC_NOWAIT和SEM_UNDO等.
下面是一个用于临界资源的读写控制和并发进程的同步和互斥控制的实例: 假设进程A是生产者, 进程B是消费者, 系统最多只能同时容纳5个产品, 初始成品数为0. 当产品数不足5时允许进程A生产, 当产品数超过0时允许进程B消费.
这里需要两个信号量模拟生产-消费过程. 信号量A代表了当前生产的数目, 它控制了生产者进程A, 信号量n代表当前尚有n个成品可以生产.
信号B代表了当前的产品数, 他控制消费者进程B, 当信号量为n时剩余n个产品.
生产者进程sema.c如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | [bill@billstone Unix_study]$ cat sema.c
if (a) { fprintf(stderr, "%s failed.\n" , (b)); exit (1); } \
else fprintf(stderr, "%s success.\n" , (b));
int main(void)
{
int semid;
struct sembuf sb;
VerifyErr((semid = semget(2000, 2, 0666)) < 0, "Open Sem 2000" );
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg &= ~IPC_NOWAIT;
VerifyErr(semop(semid, &sb, 1) != 0, "P sem 2000:0" );
fprintf(stderr, "[%d] producing ... ... \n" , getpid());
sleep (1);
fprintf(stderr, "[%d] produced\n" , getpid());
sb.sem_num = 1;
sb.sem_op = 1;
sb.sem_flg &= ~IPC_NOWAIT;
VerifyErr(semop(semid, &sb, 1) != 0, "V sem 2000:1" );
return 0;
}
[bill@billstone Unix_study]$
|
消费者进程semb.c如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | [bill@billstone Unix_study]$ cat semb.c
if (a) { fprintf(stderr, "%s failed.\n" , (b)); exit (1); } \
else fprintf(stderr, "%s success.\n" , (b));
int main(void)
{
int semid;
struct sembuf sb;
VerifyErr((semid = semget(2000, 2, 0666)) < 0, "Open Sem 2000" );
sb.sem_num = 1;
sb.sem_op = -1;
sb.sem_flg &= ~IPC_NOWAIT;
VerifyErr(semop(semid, &sb, 1) != 0, "P sem 2000:1" );
fprintf(stderr, "[%d] consuming ... ... \n" , getpid());
sleep (1);
fprintf(stderr, "[%d] consumed\n" , getpid());
sb.sem_num = 0;
sb.sem_op = 1;
sb.sem_flg &= ~IPC_NOWAIT;
VerifyErr(semop(semid, &sb, 1) != 0, "V sem 2000:1" );
return 0;
}
[bill@billstone Unix_study]$
|
编译程序并使用之前的程序ipcsem创建信号量集合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [bill@billstone Unix_study]$ . /ipcsem 2000 2 c
Create sem success.
[bill@billstone Unix_study]$ ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems
0x000003e8 0 bill 666 10
0x000007d0 98305 bill 666 2
[bill@billstone Unix_study]$ . /ipcsem 98305 0 5
Set Sem Val success.
[bill@billstone Unix_study]$ . /ipcsem 98305 1 0
Set Sem Val success.
[bill@billstone Unix_study]$ . /ipcsem 98305 0 a
Get Sem Stat success.
Get Sem All success.
sem no [0]: [5]
sem no [1]: [0]
[bill@billstone Unix_study]$
|
在一个终端上运行sema:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [bill@billstone Unix_study]$ . /ipcsem 98305 0 a
Get Sem Stat success.
Get Sem All success.
sem no [0]: [3]
sem no [1]: [2]
[bill@billstone Unix_study]$ . /sema
Open Sem 2000 success.
P sem 2000:0 success.
[23940] producing ... ...
[23940] produced
V sem 2000:1 success.
[bill@billstone Unix_study]$ . /ipcsem 98305 0 a
Get Sem Stat success.
Get Sem All success.
sem no [0]: [2]
sem no [1]: [3]
[bill@billstone Unix_study]$
|
在另一个终端上执行semb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [bill@billstone Unix_study]$ . /ipcsem 98305 0 a
Get Sem Stat success.
Get Sem All success.
sem no [0]: [2]
sem no [1]: [3]
[bill@billstone Unix_study]$ . /semb
Open Sem 2000 success.
P sem 2000:1 success.
[23942] consuming ... ...
[23942] consumed
V sem 2000:1 success.
[bill@billstone Unix_study]$ . /ipcsem 98305 0 a
Get Sem Stat success.
Get Sem All success.
sem no [0]: [3]
sem no [1]: [2]
[bill@billstone Unix_study]$
|
读者可以试着连续执行sema几次,观察进程的P阻塞状态。
|
能力值:
( LV2,RANK:10 )
在线值:
|
-
-
5 楼
好文章
在不同系统下
编程也不一样啊
|
能力值:
( LV9,RANK:260 )
|
-
-
6 楼
新系统了,冒个泡
|
能力值:
( LV9,RANK:260 )
|
-
-
7 楼
欢迎转载,请保留作者信息
bill@华中科技大学
http://billstone.cublog.cn
十四章 共享内存
管道, 消息队列和信号量都需要借助第三方对象进行通信; 而共享内存正好弥补了这些缺陷, 它是最快的IPC对象. 在本质上, 共享内存是一端物理内存.
共享内存简介
共享内存中最重要的属性是内存大小和内存地址, 进程在访问共享内存前必须先将共享内存映射到进程空间的一个虚拟地址中, 然后任何对该虚拟地址的数据操作都将直接作用到物理内存上.
共享内存由进程创建, 但是进程结束时共享内存仍然保留, 除非该共享内存被显式地删除或者重启操作系统.
UNIX的内核采用结构shmid_ds来管理消息队列, 它的数据成员与命令'ipcs -a -m'的结果一一对应
1 2 3 4 5 6 7 8 9 10 11 12 13 | struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
|
命令'ipcs -m'查询系统中共享内存的基本信息:
1 2 3 4 5 6 | [bill@billstone bill]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
[bill@billstone bill]$
|
使用共享内存
(1) 共享内存的创建
在UNIX中, 函数shmget用来创建或获取共享内存, 原型如下:
1 2 3 | int shmget(key_t key, int size, int shmflg);
|
同样的, 参数shm_flg有IPC_CREAT和IPC_EXCL两种取值.
(2) 共享内存的映射
与消息队列和信号量不同, 共享内存在获取标志号后, 仍需要调用shmat函数将共享内存映射到进程的地址空间后才能访问, 原型如下
1 2 3 | void *shmat(int shmid, const void *shmaddr, int shmflg);
|
(3) 共享内存的释放
当进程不再需要共享内存时, 可以使用函数shmdt释放共享内存内存映射, 原型如下
1 2 3 | int shmdt(const void *shmaddr);
|
(4) 共享内存的控制
与消息队列和信号量一样, 共享内存也有自给的控制函数shmctl, 原型如下:
1 2 3 | int shmctl(int shmid, int cmd, struct shmid_ds *buf);
|
这里设计一个类似于命令'ipcs'和命令'ipcrm'的程序ipcshm: 它从命令行参数中获取要执行的操作, 包括创建共享内存, 读取共享内存信息和删除共享内存等, 主体代码如下代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
if (a) fprintf(stderr, "%s failed.\n" , (b)); \
else fprintf(stderr, "%s success.\n" , (b));
int main(int argc, char *argv[])
{
int shmid, size;
if (argc != 3)
exit (1);
shmid = atoi(argv[1]);
if (strcmp(argv[2], "v" ) == 0){
StatShm(shmid);
}
else if (strcmp(argv[2], "d" ) == 0){
VerifyErr(shmctl(shmid, IPC_RMID, NULL) < 0, "Delete Shm" );
}
else {
size = atoi(argv[2]);
VerifyErr(shmget(shmid, size, 0666|IPC_CREAT|IPC_EXCL) < 0, "Create Shm" );
}
return 0;
}
|
其中用到了两个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | char * GetFileMode(mode_t st_mode, char *resp){
if (resp == NULL)
return 0;
memset(resp, '-' , 9);
if (st_mode & S_IRUSR) resp[0] = 'r' ;
if (st_mode & S_IWUSR) resp[1] = 'w' ;
if (st_mode & S_IXUSR) resp[2] = 'x' ;
if (st_mode & S_IRGRP) resp[3] = 'r' ;
if (st_mode & S_IWGRP) resp[4] = 'w' ;
if (st_mode & S_IXGRP) resp[5] = 'x' ;
if (st_mode & S_IROTH) resp[6] = 'r' ;
if (st_mode & S_IWOTH) resp[7] = 'w' ;
if (st_mode & S_IXOTH) resp[8] = 'x' ;
return resp;
}
int StatShm(int shmid){
char resp[10];
struct shmid_ds buf;
memset(&buf, 0, sizeof(buf));
memset(resp, 0, sizeof(resp));
shmctl(shmid, IPC_STAT, &buf);
fprintf(stderr, "T ID KEY MODE OWNER GROUP NATTCH SEGSZ CPID LPID\n" );
fprintf(stderr, "m %6d %#6x %s %6d %6d %6d %10d %10d %10d\n" , shmid, buf.shm_perm.__key,
GetFileMode(buf.shm_perm.mode, resp), buf.shm_perm.uid, buf.shm_perm.gid,
buf.shm_nattch, buf.shm_segsz, buf.shm_cpid, buf.shm_lpid);
return 0;
}
|
执行情况如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | [bill@billstone Unix_study]$ gcc -o ipcshm ipcshm.c
[bill@billstone Unix_study]$ . /ipcshm 2000 100
Create Shm success.
[bill@billstone Unix_study]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x000007d0 229377 bill 666 100 0
[bill@billstone Unix_study]$ . /ipcshm 229377 v
T ID KEY MODE OWNER GROUP NATTCH SEGSZ CPID LPID
m 229377 0x7d0 rw-rw-rw- 500 500 0 100 1796 0
[bill@billstone Unix_study]$ . /ipcshm 229377 d
Delete Shm success.
[bill@billstone Unix_study]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
[bill@billstone Unix_study]$
|
(5) 共享内存实例
共享内存的应用可以分为打开(创建)共享内存, 映射共享内存, 读写共享内存和释放共享内存映射等四个步骤.
程序shm1想共享内存中指定位置写入数据.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
if (a) { fprintf(stderr, "%s failed.\n" , (b)); return ; } \
else fprintf(stderr, "%s success.\n" , (b));
int main(void)
{
int shmid, no;
char *pmat = NULL, buf[1024];
VerifyErr((shmid = shmget(0x1234, 10*1024, 0666|IPC_CREAT)) == -1, "Open(Create) Shm" );
VerifyErr((pmat = (char *)shmat(shmid, 0, 0)) == 0, "Link Shm" );
printf ( "Please input NO.(0-9): " );
scanf( "%d" , &no);
VerifyErr(no<0 || no>9, "Input No." );
printf ( "Please input data: " );
memset(buf, 0, sizeof(buf));
getchar(); // 读入 '\n' 回车符
fgets(buf, sizeof(buf), stdin);
memcpy(pmat+no*1024, buf, 1024);
shmdt(pmat);
return 0;
}
|
程序shm2从共享内存指定位置读取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
if (a) { fprintf(stderr, "%s failed.\n" , (b)); return ; } \
else fprintf(stderr, "%s success.\n" , (b));
int main(void)
{
int shmid, no;
char *pmat = NULL, buf[1024];
VerifyErr((shmid = shmget(0x1234, 10*1024, 0666|IPC_CREAT)) == -1, "Open(Create) Shm" );
VerifyErr((pmat = (char *)shmat(shmid, 0, 0)) == 0, "Link Shm" );
printf ( "Please input NO.(0-9): " );
scanf( "%d" , &no);
VerifyErr(no<0 || no>9, "Input No." );
memcpy(buf, pmat+no*1024, 1024);
printf ( "Data: [%s]\n" , buf);
shmdt(pmat);
return 0;
}
|
运行结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | [bill@billstone Unix_study]$ make shm1
cc shm1.c -o shm1
[bill@billstone Unix_study]$ make shm2
cc shm2.c -o shm2
[bill@billstone Unix_study]$ . /shm1
Open(Create) Shm success.
Link Shm success.
Please input NO.(0-9): 1
Input No. success.
Please input data: this is a test !
[bill@billstone Unix_study]$ . /shm2
Open(Create) Shm success.
Link Shm success.
Please input NO.(0-9): 1
Input No. success.
Data: [this is a test !
]
[bill@billstone Unix_study]$
|
|
|
|