进程间通信方法
1.进程间通信方法
进程用户空间是相互独立的,是不能相互访问的。但很多情况下进程间需要互相通信,来完成系统的某项功能。
通信方法:管道(匿名管道和命名管道)、共享内存、消息队列、IPC信号量、套接字(socket)。
2.管道
管道是半双工的,需要两端通信时,需要建立两个管道。
管道分为匿名管道(pipe)和命名管道(FIFO),匿名管道只能在父子进程或者兄弟进程(同属一个父进程创建的多个子进程)之间使用,而命名管道可以在不相关的进程中使用。
【1】匿名管道(PIPE)
pipe函数:int pipe(int fd[2]);
调用成功后,fd[0]和fd[1]分别构成管道的两端。即往fd[1]中写入数据,从fd[0]读取数据,并且不能反过来使用,数据基于fifo的原则进行处理,即写入1,2,3时,读取到的也是1,2,3。
父子进程使用管道通信的方式:父进程调用pipe创建管道,之后调用fork函数创建子进程,此时父子进程共用管道的文件描述符。之后父进程关闭fd[0],并且往fd[1]中写入数据,子进程关闭fd[1],并且从fd[0]读取数据(父子的读写操作可以互换)。
因此,如果父子进程要实现全双工通信,则在fork之前需要创建两个管道。
【2】命名管道(FIFO)
mkfifo()
【3】问题:
问题1:父子进程通信时,父进程只使用fd[1],子进程只使用fd[0],因此两端需要各关闭一个,等结束时,再关闭各自使用的fd。(这个点就是父子进程最后都要关闭各自的fd[0]和fd[1],因为都占用的管道描述符的引用计数,但是并不是fork之后必须马上关闭其中一个)
3.共享内存
共享内存区是可用IPC形式里面最快的。共享内存允许多个进程同时访问同一内存区,进程会将内存区映射到自己的地址空间中。这样进程间数据的传递不再涉及内核,减少了数据复制的动作。例如一个客户从服务器读的操作,使用管道消息队列等形式的话,需要内核将数据复制到进程空间的服务器上,然后服务器写到内核空间的IPC上。这样一次读取或者写入需要将数据复制两次。
共享内存允许两个不相关的进程访问同一段物理内存。共享内存为多个进程之前共享和传递数据提供了一种有效的方式。但是它并没有提供同步机制,所以需要使用其他机制来同步访问。
【1】操作函数
int shmget(key_t key, size_t size, int shmflg);
第一个参数,共享内存段的命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1。
第二个参数,size指定需要共享的内存容量。
第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。
key通常使用ftok函数生成一个键值。ftok函数:key_t ftok(const char *pathname, int proj_id); ftok函数能够通过一个路径和子序号产生一个键值。两个不相关的进行,可以使用ftok函数得到相同的key来操作同一个共享内存。需要注意的是如果第一个进程使用ftok之后生成一个key,此时将pathname对应的文件删除后再重新建立,则后面进程生成的key值可能会改变导致不能访问之前建立的共享内存,这是因为文件的索引节点可能会改变。
key也可以直接使用IPC_PRIVATE,由于该值表示key的值为0,所以不相关的进程就不能通过key得到共享内存,因此这种方式生成的IPC对象,和匿名管道(pipe)类似,只能用于相关的进程之间通信,最常用的就是父子进程。
shmflg的值为IPC_CREAT表示:如果不存在key值的共享存储空间,则创建共享存储空间并返回一个共享存储标识符。如果存在,则直接返回共享存储标识符。
shmflg的值为IPC_CREAT|IPC_EXCL表示:如果不存在key值的共享存储空间,则创建共享存储空间并返回一个共享存储标识符。如果存在,则返回-1。
例如:int id = shmget(key,4096,IPC_CREAT|IPC_EXCL|0666);
创建一个大小为4096个字节的权限为0666(所有用户可读可写)的共享存储空间,并返回一个整形共享存储标识符,如果key值已经存在有共享存储空间了,则出错返回-1。
void *shmat(int shm_id, const void *shm_addr, int shmflg);
第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接(attach)到当前进程的地址空间。
第一个参数,shm_id是由shmget函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0。
该函数调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1。
int shmdt(const void *shmaddr);
参数shmaddr是shmat函数返回的地址指针,该函数用于将共享内存从当前进程中分离(detach)。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。调用成功时返回0,失败时返回-1。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
第一个参数,shm_id是shmget函数返回的共享内存标识符。
第二个参数,command是要采取的操作,它可以取下面的三个值:
IPC_STAT:将共享内存关联的数据结构复制到buf,即该标志用来获取共享内存的内核配置信息。
IPC_SET:把共享内存的当前关联值设置为shmid_ds结构中给出的值,即该标志用来设置共享内存的内核配置信息。
IPC_RMID:删除共享内存段,注意这个标记实际是给共享内存设置了删除标记,当最后使用它的进程调用shmdt时,该共享内存被删除。
第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。
【2】问题
问题1:调用shmget(IPC_PRIVATE, 100*1024*1024, 0666|IPC_CREAT);后,此时并没有申请任何虚拟空间内存和物理内存。
调用p = shmat(ret, 0, 0);后,此时因为把共享内存连接到当前进程虚拟地址空间,所以虚拟内存(VIRT)占用会增加100M,而物理内存(RES)和共享内存(SHR)没有增加。
调用memset(p, 0, 30*1024*1024);后,此时需要把数据写入物理内存,所以物理内存(RES)会增加30M,共享内存(SHR)也会增加30M。
4.消息队列
消息队列是在两个进程之间传递数据块的简单有效的方式,每个数据块有一个类型字段,因此接收方可以通过参数只接收某一个类型的消息。要点就是消息队列是基于数据块的,而管道是基于字节流。
【1】操作函数
int msgget(key_t key, int msgflg);
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);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
5.套接字
套接字包括socket和无名套接字socketpair。
【1】socketpair
int socketpair(int domain, int type, int protocol, int sv[2]);
该函数创建一对无名的套接字描述符,描述符是一个二元数组,这对套接字可以进行双工通信,每一个描述符既可以读也可以写。向sv[0]中写入,就可以从sv[1]中读取(只能从sv[1]中读取),也可以在sv[1]中写入,然后从sv[0]中读取。
domain:只能是AF_UNIX或者AF_LOCAL。