进程间通信的技术(同步、互斥、传递消息)
1.文件
2.文件锁
3.管道pipe和命名管道FIFO(字节流的,无边界区分)
4.信号
5.套接字
system V、posix
6.消息队列
system v 消息队列:一个进程向另外一个进程发送数据块,有消息边界,不是先进先出,可以指定要接收的数据类型,每条消息最大长度为MSGMAX,每个消息队列中所有消息的大小之和不超过MSGMNB,系统上消息队列总数不超过MSGMNI
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
例:msgget(key,IPC_CREAT|0666)
参数:
key:消息队列关联的键。如果key 设置为IPC_PRIVATE,则每次都会是创建一个新的消息队列返回新的msgid,同时后面的IPC_CREAT、IPC_EXCL是可以忽略,他们是没有作用的。(
IPC_PRIVATE 需要在父子进程都可见的地方调用(即在创建子进程之前),否则不能实现进程间通信
因为通过每个进程IPC_PRIVATE这个key获得的id不一样,其他通过ftok获得的key 在程序每次运行中是一样的。ftok参数一样的话每次程序运行中返回值都一样。
)msgflg:消息队列的建立标志和存取权限。如果仅仅只是打开一个已经存在的,填0就可以
IPC_CREAT如果内核中没有此队列,则创建它,有就打开。
IPC_EXCL当和IPC_CREAT一起使用时,如果队列已经存在,则失败返回-1。
如果单独使用IPC_CREAT,则msgget()要么返回一个新创建的消息队列的标识符, 要么返回具有相同关键字值的队列的标识符 。如果IPC_EXCL和IPC_CREAT一起使用,则msgget()要么创建一个新的消息队列,要么如果队列已经存在则返回一个失败值-1。IPC_EXCL单独使用是没有用处的,总是返回-1
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
cmd:
IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中。
IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。
IPC_RMID:从系统内核中移走消息队列。
……
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgsnd 成功执行时,msgsnd()返回0,失败-1 , msgrcv()返回拷贝到mtext数组的实际字节数。失败 返回-1
msqid:消息队列的识别码。
msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下
struct msgbuf {
long mtype; /* 消息类型,必须 > 0 ,如果不做设置很可能为0,发送就会失败*/
char mtext[1]; /* 消息文本 */
} ;
msgsz:消息的大小。不包含type的消息体的长度
msgtyp:消息类型
msgtyp:等于0 则返回队列的最早的一个消息。大于0,则返回其类型为mtype的第一个消息。msgtyp小于0,则返回其类型小于或等于mtype参数的绝对值的最小的一个消息。
msgflg:这个参数依然是是控制函数行为的标志,取值可以是:0,表示忽略;IPC_NOWAIT,如果消息队列为空,则返回一个ENOMSG,并将控制权交回调用函数的进程。如果不指定这个参数,那么进程将被阻塞直到函数可以从队列中得到符合条件的消息为止。如果一个client 正在等待消息的时候队列被删除,EIDRM 就会被返回。如果进程在阻塞等待过程中收到了系统的中断信号,EINTR 就会被返回。MSG_NOERROR,如果函数取得的消息长度大于msgsz,将只返回msgsz 长度的信息,剩下的部分被丢弃了。如果不指定这个参数,E2BIG 将被返回,而消息则留在队列中不被取出。当消息从队列内取出后,相应的消息就从队列中删除了。MSG_EXCEPT,接收一条不是msgtype的消息
Posix 消息队列:http://www.cnblogs.com/Anker/archive/2013/01/04/2843832.html
Posix消息队列与System V消息队列的区别如下:
1. 对Posix消息队列的读总是返回最高优先级的最早消息,对System V消息队列的读则可以返回任意指定优先级的消息。
2. 当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号或启动一个线程,System V消息队列则不提供类似的机制。
Posix消息队列操作函数如下:
#include <mqueue.h>
typedef int mqd_t;
mqd_t mq_open(const char *name, int oflag, ... /* mode_t mode, struct mq_attr *attr */);
返回: 成功时为消息队列描述字,出错时为-1。
功能: 创建一个新的消息队列或打开一个已存在的消息的队列。
name 必须是“/filename” 的形式,filename中不能有 “/”
#include <mqueue.h>
int mq_close(mqd_t mqdes);
返回: 成功时为0,出错时为-1。
功能: 关闭已打开的消息队列。
#include <mqueue.h>
int mq_unlink(const char *name)
返回: 成功时为0,出错时为-1
功能: 从系统中删除消息队列。
#include <mqueue.h>
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *attr, struct mq_attr *attr);
均返回:成功时为0, 出错时为-1
每个消息队列有四个属性:
struct mq_attr
{
long mq_flags; /* message queue flag : 0, O_NONBLOCK */
long mq_maxmsg; /* max number of messages allowed on queue*/
long mq_msgsize; /* max size of a message (in bytes)*/
long mq_curmsgs; /* number of messages currently on queue */
};
每个消息均有一个优先级,它是一个小于MQ_PRIO_MAX的无符号整数
#define MQ_PRIO_MAX 32768
#include <mqueue.h>
int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio);
返回:成功时为0,出错为-1
ssize_t mq_receive(mqd_t mqdes, char *ptr, size_t len, unsigned int *priop);
返回:成功时为消息中的字节数,出错为-1
消息队列的限制:
MQ_OPEN_MAX : 一个进程能够同时拥有的打开着消息队列的最大数目
MQ_PRIO_MAX : 任意消息的最大优先级值加1
#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *notification);
返回: 成功时为0,出错时为-1
功能: 给指定队列建立或删除异步事件通知,通知有 信号和新建一个线程处理 两种方式
union sigval
{
int sival_int; /* Integer value */
void *sival_ptr; /* pointer value */
};
struct sigevent
{
int sigev_notify; /* SIGEV_{ NONE, ISGNAL, THREAD} */
int sigev_signo; /* signal number if SIGEV_SIGNAL */
union sigval sigev_value; /* passed to signal handler or thread */
void (*sigev_notify_function)(union sigval);
pthread_attr_t *sigev_notify_attribute;
};
(1).如果notification参数非空,那么当前进程希望在有一个消息到达所指定的先前为空的队列时得到通知。我们说“该进程被注册为接收该队列的通知”,且只有一个进程可以注册。
(2).如果notification参数为空指针,而且当前进程目前被注册为接收所指定队列的通知,那么已存在的注册将被撤销。
(3).任意时刻只有一个进程可以被注册为接收某个指定队列的通知。
(4).当有一个消息到达某个先前为空的队列,而且已有一个进程被注册为接收该队列的通知时,只有在没有任何线程阻塞在该队列的mq_receive调用中的前提下,通知才会发出。这就是说,在mq_receive调用中的阻塞比任何通知的注册都优先。
(5).当该通知被发送给它的注册进程时,其注册即被撤销。该进程必须再次调用mq_notify重新注册(如果想要的话。同时最好在得到通知后立刻注册,不要等到接受完数据再处理,因为随时可能在来新数据)。
7.共享内存http://kenby.iteye.com/blog/1164700
#include<sys/mman.h>
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
prot :映射的内存的保护方式--PROT_READ可读、PROT_WRITE可写、PROT_EXEC可执行、PROT_NONE不可访问
flags:MAP_SHARED 变动共享,变动会影响文件; MAP_PRIVATE 变动不影响文件; MAP_FIXED 必须从start开始共享内存,如果页面没有对齐,返回-1;MAP_ANONYMOUS
可以将文件fd映射到共享内存;也可以将flags设置为MAP_ANONYMOUS选择不涉及文件,这时这块共享内存就只能用于亲缘进程,因为没有了文件其他进程也就找不到这块地址空间的联系,亲缘进程由于是共享的,所以都可以得到这块由mmap返回的地址空间的值。
文件大小, mmap的参数 length都不能决定进程能访问的大小, 而是容纳文件被映射部分的最小页面数决定进程能访问的大小
共享内存无法改变文件的大小-------读写共享内存大小就是操作实际的内存,可用的大小是最小页面数的大小,但是更新写回到文件时,还是只有原始文件大小
返回值:返回的指针就指向这个共享内存块,直接对该指针操作即操作内存(如 将该指针强制转换成某个结构体指针,然后对该结构操作)
int munmap(void* start,size_t length);
int msync ( void * addr, size_t len, int flags)
进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()函数来实现磁盘文件内容与共享内存区中的内容一致,即同步操作.
flags:刷新的参数设置,可以取值MS_ASYNC/ MS_SYNC/ MS_INVALIDATE
取值为MS_ASYNC(异步)时,调用会立即返回,不等到更新的完成;
取值为MS_SYNC(同步)时,调用会等到更新完成之后返回;
取MS_INVALIDATE(通知使用该共享区域的进程,数据已经改变)时,在共享内容更改之后,使得与最终副本不一致的文件数据的所有内存中映射副本失效,从而使得共享该文件的其他进程去重新获取最新值;
system v 共享内存
shmget 打开或者创建共享内存、shmat 映射到进程内存地址空间、shmdt 分离映射、shmctl 操作共享内存如删除
posix 共享内存
Posix提供了两种在无亲缘关系进程间共享内存区的方法:http://www.cnblogs.com/Anker/archive/2013/01/19/2867696.html
(1)内存映射文件:先用open函数打开文件系统中的文件获取一个文件描述符,然后调用mmap函数把得到的文件描述符映射到当前进程地址空间,前面介绍了。
(2)共享内存区对象:先用shm_open打开一个Posix IPC名字获取一个文件描述符,然后调用mmap将文件描述符映射到当前进程的地址空间。
者两种方法多需要调用mmap,差别在于作为mmap的参数之一的描述符的获取手段
#include <sys/mman.h>#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
int shm_open(const char *name, int oflag, mode_t mode);//创建或打开,没有设置共享内存的大小
int shm_unlink(const char *name);
int ftruncate(int fd, off_t length); //设置共享内存的大小
int fstat(int fd, struct stat *buf);
8.信号量
system v 信号量
semget、semctl、semop
posix 信号量
有名信号量 sem_open 、sem_close 、 sem_unlink
无名信号量 int sem_init(sem_t *sem, int pshared, unsigned int value) ; sem_destroy
sem_init() 初始化一个定位在 sem 的匿名信号量。value 参数指定信号量的初始值。 pshared 参数指明信号量是由进程内线程共享,还是由进程之间共享。如果 pshared 的值为 0,那么信号量将被进程内的线程共享,并且应该放置在这个进程的所有线程都可见的地址上(如全局变量,或者堆上动态分配的变量)。
如果 pshared 是非零值,那么信号量将在进程之间共享,并且sem应该定位共享内存区域(见 shm_open(3)、mmap(2) 和 shmget(2))。(因为通过 fork(2) 创建的孩子继承其父亲的内存映射,因此它也可以见到这个信号量。)所有可以访问共享内存区域的进程都可以用 sem_post(3)、sem_wait(3) 等等操作信号量。初始化一个已经初始的信号量其结果未定义。
sem_wait 、sem_trywait 、sem_post
posix
9.互斥锁
pthread_mutex_init、 pthread_mutex_lock、 pthread_mutex_unlock、 pthread_mutex_destroy
10.条件变量
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);//向第一个等待的线程发起通知
int pthread_cond_broadcast(pthread_cond_t *cond);// 向所有等待线程发送通知
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
11.读写锁
12 自旋锁
自旋锁性能比互斥锁更高,线程在申请自旋锁的时候,不会被挂起,而是处于忙等到状态,一直在申请锁,所以只适合等等时间段的情况
pthread_spin_init、 pthread_spin_lock、 pthread_spin_unlock、 pthread_spin_destroy
13 文件锁
进程间共享信息的三种方式
转自http://blog.csdn.net/qianquanyiyan/article/details/6197336
1 左边两个进程共享存储于文件系统中某个文件的信息。为了访问这些信息,每个进程都得穿越内核(如:read, write, seek等)。当文件需要更新时,某种形式的同步是需要的,这样可以防止写入者的相互干扰以及写入者对读出者的干扰。
2 中间的两个进程共享驻留于内核的某些信息。
3 右边的的两个进程有一个双方都能访问的共享内存区。每个进程一旦设置好共享内存区就能根本不涉及内核而访问其中的数据。共享内存区的进程需要某种形式的同步。(同一进程的线程间,共享内存区模型是内在的。)
IPC对象的持续性
根据三种进程间通信的方式IPC对象的持续性也可以分为三种
1.随文件系统持续:一直存在,直到显示删除,即使内核自举还存在(如 如果posix中的消息队列、共享内存、信号量采用 文件映射来实现)
2.随内存持续:一直存在,直到显示删除,或者内核自举(如 system v的消息队列、共享内存、信号量)
3.随进程持续:一直存在,直到打开的最后一个进程结束(如pipe 、FIFO)
死锁的产生条件
(1).互斥条件:进程对资源互斥使用,即同一时段内同一个资源仅仅可以被一个进程占有(3) 不可剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。