进程间通信(Interprocess Communication, IPC):管道、FIFO、消息队列、信号量、共享存储、套接字
#include <unistd.h> int pipe(int fd[2]); // 返回值:若成功,返回0,若出错,返回-1. // 函数调用成功返回r/w两个文件描述符。无需open,但需手动close。 // 规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样 // 向管道文件读写数据其实是在读写内核缓冲区。
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。父子进程间通信的步骤:
父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
#include <sys/stat.h> int mkfifo(const char *path, mode_t mode); //返回值:若成功,返回0;若出错,返回-1。 // 参数pathname为路径名,创建管道的名字 // mode为创建fifo的读写权限。
mkfifo
函数的作用是在文件系统中创建一个真实存在文件,该文件用于提供FIFO功能,即命名管道。前边讲的那些管道都没有名字,因此它们被称为匿名管道,或简称管道。第一步:获得key值。因为消息队列独立于进程而存在,所以为了区别不同的消息队列,需要以key值标记消息队列,这样两个不相关的进程可以通过事先约定的key值通过消息队列进行消息收发。
#include <sys/types.h> #include <sys/ips.h> key_t futon(char *pathname, int projid) // pathname:文件名(含路径),通常设置为当前目录“.” // projid:子序号,虽然为int类型,但只使用了低8位,通常用一个字母表示 // 返回值:成功返回key值,失败返回-1;
第二步:创建或打开消息队列
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflag) /* 功能:用于创建一个新的或打开一个已经存在的消息队列,此消息队列与key相对应。 参数: key:函数ftok的返回值或IPC_PRIVATE,为IPC_PRIVATE时表示创建自己的消息队列,这样就只能用于和自己有亲缘关系的进程间通信。 msgflag: IPC_CREAT:创建新的消息队列。 IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。 IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。 返回值: 调用成功返回队列标识符,否则返回-1. */
第三步:将消息添加到消息队列
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflag) /* 功能: 将新消息添加到队列尾端,即向消息队列中发送一条消息。 参数: msqid:消息队列的标识符。 msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下 struct msgbuf { long mtype; /* 消息类型,必须 > 0 */ char mtext[1]; /* 消息文本 */ }; msgsz:消息的大小,消息的总大小不能超过8192字节,在<linux/msg.h>有个宏控制,#define MSGMAX 8192。 msgflag: IPC_NOWAIT: 指明在消息队列没有足够空间容纳要发送的消息时,msgsnd立即返回。 0:msgsnd调用阻塞直到条件满足为止.(一般选这个) */
第四步:从消息队列读取消息:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtype, int msgflag) /* 功能: 从队列中接收消息 参数: msqid:已打开的消息队列id msgp:存放消息的结构体指针。msgp->mtype与第四个参数是相同的。 msgsz:消息的字节数,指定mtext的大小。 msgtype:消息类型,消息类型 mtype的值。如果为0,则接受该队列中的第一条信息,如果小于0,则接受小于该值的绝对值的消息类型,如果大于0,接受指定类型的消息,即该值消息。 msgflag:函数的控制属性。 msgflag: MSG_NOERROR:若返回的消息比msgsz字节多,则消息就会截短到msgsz字节,且不通知消息发送进程. IPC_NOWAIT:调用进程会立即返回.若没有收到消息则返回-1. 0:msgrcv调用阻塞直到条件满足为止. 在成功地读取了一条消息以后,队列中的这条消息将被删除。 返回值:成功执行时,msgrcv()返回0, 失败返回-1 */
共享存储允许一个或多个进程共享一个给定的存储区,因为数据不需要在客户端进程和服务器进程之间进行复制,所以这是最快的一种IPC,对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。
不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制——互斥锁和信号量都可以。
第一步:创建/打开共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); /* 功能:分配一块共享内存 参数: key: 函数ftok的返回值或IPC_PRIVATE,为IPC_PRIVATE时创建的共享内存只能用于有亲缘关系的进程通信; size:建立共享内存的大小(单位字节),通常取系统页长的整数倍,若size值不是系统页的整数倍,则最后一页剩余部分是不可用的; shmflg: 0:取共享内存标识符,若不存在则函数会报错 IPC_CREAT:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符 IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错 16 返回值:成功,返回共享内存标识符,失败返回-1; */
第二步:映射共享内存
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg) /* 功能:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问 参数: msqid:共享内存标识符 shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置 shmflg:SHM_RDONLY:为只读模式,否则为读写模式 返回值: 成功,返回共享内存地址,出错返回-1 */
速度快,效率高;
需要有同步机制;
创建/打开共享内存
映射共享内存,即把即把指定的共享内存映射到进程的地址空间用于访问
撤销共享内存映射
删除共享内存