我们要讨论5种类型的进程间通信方式。
1> 共享内存,允许进程通过简单的读写一块指定的内存来通信。
2> 内存映射,和共享内存类似。不同的是它和文件系统中的一个文件关联。
3> 管道,相关进程间通信。
4> FIFO 和管道类似。不同的是它允许不相关的进程间通信,因为这管道在文件系统中被赋予了名字。
5> 基于socket的进程间通信。这里的进程可以是在同一台机器上,也可以是不在同一台机器上。
IPC(Interprocess communication)依据下面的条件而不同:
@ 通信是否只在相关进程(进程有一个相同的祖先)之间进行,还是也在一个文件系统中的非相关进程间,或者任何一台接在互联网中的主机。
@ 是不是通信进程只进行读操作或者只进行些操作。
@ 允许相互通信的进程的数量。
@ 相互通信的进程是不是由IPC同步,比如,一个读进程被阻塞直到有数据可读。
5.1 共享内存
共享内存是最简单的进程间通信的方式。它允许两个或则多个进程存取同一块内存,好像它们都调用了malloc并且返回了同一个实际内存地址。
当有一个进程改变了内存中的内容,所有其他进程可以看到这个改变。
5.1.1 快速的本地通信
共享内存是最快的进程间通信方式,因为所有的进程共享相同的内存块。对共享内存块的存取如同存取一个进程自己的内存一样,不需要系统调用或者进入内核。它也避免了不必要的数据拷贝。
因为内核没有对共享内存的访问进行同步控制,所以你必须提供自己的同步措施。使用信号量就是一个常用的策略。
5.1.2 内存模型
一个进程在使用共享内存段之前应该先申请内存段。所有的其它想存取这个内存段的进程必须链接(attach)上这个段。使用完毕后应该解除链接(detache)。有时候,申请内存段的进程还应该释放掉这个内存段。
在Linux中,每个进程的虚拟内存都被分成了页,页中存有实际的数据。即使每个进程有它自己的地址,进程间映射可以指向相同的页。这就为共享内存提供了可能。
分配一个新的共享内存段会产生虚拟内存页的建立。因为所有的进程都要访问相同的共享内存段,但是只有一个进程可以分配到新的共享 内存段。分配一个已经存在的段不会 新的页,但是它确实返回了已经存在的页的描述符。进程要使用共享内存段,先要进行链接操作,这将增加
从它自身的虚拟内存空间到共享段的页的映射项。当不需要时,这些映射项应该被删除。当所有的进程都不再需要这段共享内存空间时,一个进程必须释放掉这段内存。
所有的共享内存段大小都是系统页的整数倍。在Linux中,页的大小为 4KB ,但是你应该通过 getpagesize来读取这个值。
5.1.3 分配
一个进程要申请一块共享内存段,使用shmget.
下面是man shmget后的提示,
int shmget(key_t key, size_t size, int shmflg);
描述:
shmget() 返回与key参数有关的共享内存段的描述符。
如果 key 值为 IPC_PRIVATE 或者
如果key 值不是IPC_PRIVATE 但是于key有关的共享内存段还没有被其他进程申请,并且shmflg中有指定IPC_CREAT标志,
那么,一块新的共享内存段将建立,它的大小为 size* PAGE_SIZE。
如果shmflg中既指定了IPC_CREAT又指定了IPC_EXCL,并且与 key相关的共享内存段已经存在,shmget()返回错误码 EEXIST.
sgnflg可以为:
IPC_CREAT,将会创建一个新的段。如果没有指定这个标志,shmget将会找到与key相关的段检查当前进程是否有访问这个段的权限。
IPC_EXCL,和IPC_CREAT一块使用来确保如果相关段已经存在时shmget调用失败。
mode flags ,制定了owner,group,world,对该共享段的存取权限。和open中一样。目前,执行权限控制并未用到。比如,S_IRUSR和
S_IWUSR指定了拥有者的读和写权限,S_IROTH和S_IWOTH指定了其进程的读写权限。
SHM_HUGETLB,允许共享段使用"超级块"
SHM_NORESERVE,不为该段保留交换空间(swap space)。当一个swap space保留时,其他进程就可能修改它的内容。
当一个共享段建立时,它的内容被初始化为0.
如下,建立了一块共享内存,只有拥有者有读写权限,
iint segment_id = shmget (shm_key, getpagesize (),
IPC_CREAT | S_IRUSR | S_IWUSER);
5.1.4 关联和去掉关联(Attachment and Detachmet)
要利用共享段,进程要调用嗯shmat。
void *shmat(int shmid, const void *shmaddr, int shmflg);
描述:
shmat() 将shmid指定的共享段关联到调用进程。关联的本进程的地址由shmaddr指定,shmaddr可以是,
NULL,由系统选择一个合适的地址来关联共享段。
非NULL,并且 shmflg中指定了SHM_RND,关联的本进程的地址将会向下取到最近的一个SHMLBA的整数倍。否则shmaddr必须为按页对齐的。
SHM_RDONLY, 共享段将关联为只读类型,进程必须有该共享段的读权限。否则为读写类型,这样进程应该有读写权限。这里没有只写的概念。
SHM_REMAP,指示当前的关联映射应该取代已经存在的在该映射地址范围内的关联映射。通常,如果替换发生,调用会返回EINVAL. 这样shmaddr必须不能为NULL.
调用成功,返回 相关联的共享内存的地址。
5.1.5 共享内存的控制和去除关联
系统调用shmctl可以返回一个共享内存段的信息,并且我们可以修改这些信息。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
描述:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
cmd:
IPC_STAT,复制与shmid 指定的共享段的有关信息到 shmid_ds指向的结构。调用者要有读权限。
IPC_SET,
IPC_RMID,标志将被销毁的段。这个段只有在所有的进程都解除与之的关联之后才执行销毁。
。。。
5.1.6. 实例
以下例子演示了怎么使用共享内存,
Listing 5.1 (shm.c) Exercise Shared Memory#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/stat.h>
int main(void)
{
int segment_id;
char* shared_memory;
struct shmid_ds shmbuffer;
int segment_size;
const int shared_segment_size = 0x6400;
/* Allocate a shared memory segment. */
segment_id = shmget (IPC_PRIVATE, shared_segment_size,
IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
/* Attach the shared memory segment */
shared_memory = (char*) shmat (segment_id, 0, 0);
printf("shared memory attached at address %p /n", shared_memory);
/* Determine the segment's size */
shmctl (segment_id, IPC_STAT, &shmbuffer);
segment_size = shmbuffer.shm_segsz;
printf ("segment size: %d bytes/n", segment_size);
/* write a string to the shared memory segment. */
sprintf (shared_memory, "Hello, shared memory.");
/* Detach the shared memory segment. */
shmdt (shared_memory);
/* Reattach the shared memory segment ,at a different address. */
shared_memory = (char*) shmat (segment_id, (void*) 0x5000000, 0);
printf ("shared memory reattached at address %p/n", shared_memory);
/* Print out the string from shared memory. */
printf ("%s/n", shared_memory);
/* Detach the shared memory segment. */
shmdt (shared_memory);
/* Deallocate the shared memory segment. */
shmctl (segment_id, IPC_RMID, 0);
return 0;
}
5.1.7 调试
ipcs 命令可以显示系统的进程间通信工具的信息。
如果内存段被一个程序错误的留下,你可以使用ipcrm命令清理掉它,
% ipcrm -m 1627649