信号量、消息队列和共享内存

来源:互联网 发布:淘宝客营销计划是什么 编辑:程序博客网 时间:2024/05/16 07:49

1.信号量是一种特殊的变量,它只能取正整数值,对这些正整数只能进行两种操作:等待和信号。P代表等待,V代表信号。

2.最简单的信号量是一个只能取“0”和“1”值的变量,也就是常说的“二进制信号量”。可以取多种正整数值的信号量叫做“通用信号量”。

3.PV操作的定义非常简明。假设我们有一个信号量变量sv,则
P(sv) 如果sv的值大于零,就给它减去1;如果sv的值等于零,就挂起该进程的执行
V(sv) 如果有其他进程因等待sv变量而被挂起,就让它恢复执行;如果没有进程因等待sv变量而被挂起,就给它加上1

4.信号量函数的定义
#include <sys/sem.h>

int semctl(int sem_id, int sem_num, int command, ...);
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
注意:在实际工作中,还经常需要用到头文件sys/types.h和sys/ipc.h,某些特殊操作需要使用这两个头文件里的有关定义。

5.共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。所有进程都可以访问共享内存中的地址。

6.共享内存本身没有提供任何同步功能,共享内存的访问同步问题必须由程序员负责。

7.共享内存函数

#include <sys/shm.h>

void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t key, size_t size, int shmflg);

注:头文件sys/types.h和sys/ipc.h一般不能少


1.shmget函数
通过shmget函数来创建共享内存:
int shmget(key_t key, size_t size, int shmflg);

类似于信号量的情况,程序需要提供一个键字参数,也就是这个共享段的名字,而shmget函数返回一个供后续共享内存函数使用的共享内存标识码。有一个特殊的键值IPC_PRIVATE,它的作用是创建本进程私用的共享内存。

第二个参数size以字节为单位给出了需要共享的内存量。

第三个参数shmflg由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。由IPC_CREAT定义的特殊位必须与其他标志位按位OR(或)在一起才能创建出一个新的共享内存段来。设置IPC_CREAT标志并传递已存在的共享内存段的键字不会产生错误。如果IPC_CREAT标志用不着,就会忽略其作用。IPC_CREAT表示如果共享内存不存在,则创建一个共享内存,否则打开操作。

如果共享内存创建成功,shmget将返回一个非负整数,即该段共享内存的标识码;如果失败,返回“-1”。


2.shmat函数
在共享内存段刚被创建的时候,任何进程还都不能访问它。为了建立对这个共享内存段的访问渠道,必须由我们来把它连接到某个进程的地址空间。这项工作是由shmat函数完成的,下面是它的定义:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
第一个参数shm_id是shmget返回的共享内存标识码。

第二个参数shm_addr是把共享内存连接到当前进程去的时候准备放置它的那个地址。这通常是一个空指针,表示把选择共享内存出现的地址这项工作交给系统去完成。

第三个参数shmflg是一组按位OR(或)在一起的标志。它的两个取值可能是SHM_RND(这个标识与shm_addr一起控制着共享内存连接的地址)和SHM_RDONLY(它使连接的共享内存成为一个只读区间)。很少有需要控制共享内存连接的地址的情况,一般都是由系统替你挑选一个地址,否则就会使你的软件对硬件的依赖过高。

如果shmat调用操作成功,它将返回一个指针,指针指向共享内存的第一个字节;如果失败,它将返回“-1”。
共享内存的读写权限有它自己的属主(即共享内存的创建者)、它的访问权限和当前进程的属主情况来决定。共享内存的访问权限类似于文件的访问权限。
这里有个例外,就是“shmflg & SHM_RDONLY”为true时的情况。此时这段共享内存将不允许写操作的执行,哪怕它的访问权限允许写操作都不行。


3.shmdt函数
shmdt函数的作用是把共享内存与当前进程脱离开。它的参数是一个由shmat返回的地址指针。如果操作成功,它将返回“0”,失败则返回“-1”。需要注意的是脱离共享内存并不等于删除它,只是当前进程不能再继续访问它而已。

4.shmctl函数
与信号量那么复杂的控制函数相比,共享内存的控制函数要简单的多。下面是它的定义:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
shmid_ds结构至少应该包含以下几个成员:
struct shmid_ds
{
    uid_t shm_perm.uid;
    uid_t shm_perm.gid;
    mode_t shm_perm.mode;    
};

第一个参数shm_id是由shmget函数返回的共享内存标识码。

第二个参数command是将要采取的动作。它有三个可取值:
IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID 删除共享内存段
第三个参数buf是一个指针,它指向一个保存着共享内存模式状态和访问权限的数据结构。

如果操作成功,它将返回“0”,失败则返回“-1”。X/Open没有规定试图删除一个正处于连接状态的共享内存段时会发生什么事情。一般的做法是,这个已经被删除了的连接态共享内存段还能继续使用,直到从最后一个进程上脱离为止。但是因为这种行为不属于技术规范里的规定,所以最好不要太依赖它。


8.消息队列函数
#include <sys/msg.h>

int msgctl(int msgid, int cmd, struct msgid_ds *buf);
int msgget(key_t key, int msgflg);
int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
与信号量和共享内存的情况类似,头文件sys/types.h和sys/ipc.h一般也不能少。

1.msgget函数
我们用函数来创建和访问一个消息队列:
int msgget(key_t key, int msgflg);

和其他IPC功能类似,必须由程序提供一个键字参数key,也就是某个消息队列的名字。特殊键值IPC_PRIVATE的作用是创建一个仅能由本进程访问的私用消息队列。第二个参数msgflg也由九个权限标志构成。由IPC_CREAT定义的特殊位必须与其他标志位按位OR(或)在一起才能创建出一个新的消息队列。即使在设置了IPC_CREAT标志后给出的是一个现有的消息队列的键字,也不是一个错误。如果该消息队列已经存在,就忽略IPC_CREAT标志的作用。

如果操作成功,msgget将返回一个正整数,即一个消息队列标识码;如果失败,返回“-1”。

2.msgsnd函数
msgsnd函数的作用是让我们把一条消息添加到消息队列里去:
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

消息的结构在两方面受到制约。首先,它必须小于系统规定的上限值;其次,它必须以一个“long int”长整数开始,接收者函数将利用这个长整数确定消息的类型。在用到消息的时候,最好是把你的消息结构定义为下面这个样子:
struct my_message
{
    long int message_type;
};

因为在接收消息时肯定要用到message_type,所以不能放着它不填。我们必须在自己的数据结构里加上这个长整数,最好是把它初始化为一个确定的已知值。

第一个参数msgid是由msgget函数返回的消息队列标识码。

第二个参数msg_ptr是一个指针,指针指向准备发送的消息,而消息必须像刚才说的那样以一个“long int”长整数开始.

第三个参数msg_sz是msg_ptr指向的消息的长度。这个长度不能把保存消息类型的那个“long int”长整数计算在内。

第四个参数msgflg控制着当前消息队列满或到达系统上限时将要发生的事情。如果msgflg中的IPC_NOWAIT标志被置位,这个函数就会立刻返回,消息不发了,返回值是“-1”;如果msgflg中的IPC_NOWAIT标志被清除,发送者进程就会被挂起,等待队列中腾出空间来。

如果操作成功,这个函数将返回“0”;如果失败,返回“-1”。如果调用成功,就会对消息做一个拷贝并把它放到队列里去。

3.msgrcv函数
msgrcv函数的作用是从一个消息队列里检索消息:
int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);

第一个参数msgid是由msgget函数返回的消息队列标识码。

第二个参数msg_ptr是一个指针,指针指向准备接收的消息,而消息必须像前面msgsnd函数部分介绍的那样以一个“long int”长整型开始。

第三个参数msg_sz是msg_ptr指向的消息的长度,不包括保存消息类型的那个长整数。

第四个参数msgtype是一个“long int”长整数,它可以实现接收优先级的简单形式。如果msgtype的值是0,就提取队列中的第一个可用消息;如果它的值大于0,消息类型与之相同的第一个消息将被检索出来。如果它的值小于0,则消息类型值等于或小于msgtype的绝对值的第一个消息将被检索出来。
这样说着挺复杂,但用起来就好理解了。如果你只是想按照消息的发送顺序来检索他们,把msgtype设置为0就行了。如果你只是想检索某一特定类型的消息,把msgtype设置为相应的类型值就行。如果你想检索类型等于或小于“n”的消息,就把msgtype设置为“-n”。

第五个参数msgflg控制着队列中没有相应类型的消息可供接收时将要发生的事情。如果msgflg中的IPC_NOWAIT标志被置位,这个函数就会立刻返回,返回值是“-1”;如果msgflg中的IPC_NOWAIT标志被清除,接收者进程就会挂起,等待一条对应类型的消息到达。

如果操作成功,msgrcv函数将返回实际放到接收缓冲区里去的字符个数,而消息则被拷贝到msg_ptr指向的用户缓冲区里,然后删除队列里的数据。如果失败,返回“-1”。


4.msgctl函数
最后一个消息队列函数是msgctl,它的作用与共享内存的控制函数很相似。
int msgctl(int msgid, int cmd, struct msgid_ds *buf);

而msgid_ds结构至少应该包含一下成员:
struct msgid_ds
{
    uid_t msg_perm.uid;
    uid_t msg_perm.gid;
    mode_t msg_perm.mode;
};

第一个参数msgid是由msgget函数返回的消息队列的标志码。

第二个参数command是将要采取的动作。它有三个可能值:
IPC_STAT 把msgid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET 在进程有足够权限的前提下,把消息队列的当前关联值设置为msgid_ds数据结构中给出的值
IPC_RMID 删除消息队列
如果操作成功,它将返回“0”,失败则返回“-1”。如果删除一个消息队列的时候还有进程等在msgsnd或msgrcv函数里,这两个函数将会失败。