每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。进程的内存空间是相互独立的,一般而言是不能相互访问的。但很多情况下进程间需要互相通信,来完成系统的某项功能。进程通过与内核及其它进程之间的互相通信来协调它们的行为。进程间通信的方式通常由以下几种:
管道分为有名管道和无名管道。无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系。无明管道一般用于两个不同进程之间的通信。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。有名管道也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。
信号量是一个计数器,可以用来控制多个线程对共享资源的访问.,它不是用于交换大批数据,而用于多线程之间的同步.它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源.因此,主要作为进程间以及同一个进程内不同线程之间的同步手段.Linux提供了一组精心设计的信号量接口来对信号进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。它们声明在头文件sys/sem.h中。
信号是一种比较复杂的信方式,用于通知接收进程某个事件已经发生。
消息队列是消息的链表,存放在内核中并由消息队列标识符标识.消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点.消息队列是UNIX下不同进程之间可实现共享资源的一种机制,UNIX允许不同进程将格式化的数据流以消息队列形式发送给任意进程.对消息队列具有操作权限的进程都可以使用msget完成对消息队列的操作控制.通过使用消息类型,进程可以按任何顺序读信息,或为消息安排优先级顺序。
socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问.共享内存是最快的IPC(进程间通信)方式,它是针对其它进程间通信方式运行效率低而专门设计的.它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步与通信。
本文主要介绍共享内存的通信方式
在Linux系统中,每个进程都有独立的虚拟内存空间,也就是说不同的进程访问同一段虚拟内存地址所得到的数据是不一样的,这是因为不同进程相同的虚拟内存地址会映射到不同的物理内存地址上。但有时候为了让不同进程之间进行通信,需要让不同进程共享相同的物理内存,Linux通过 共享内存 来实现这个功能。
共享内存就是允许两个或多个进程共享一定的存储区。就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。因为数据不需要在客户机和服务器端之间复制,数据直接写到内存,不用若干次数据拷贝,所以这是最快的一种通信方式。
要使用共享内存,首先需要使用 shmget() 函数获取共享内存,shmget() 函数的声明如下:int shmget(key_t key, size_t size, int shmflg);
参数 key 一般由 ftok() 函数生成,用于标识系统的唯一IPC资源。
参数 size 指定创建的共享内存大小。
参数 shmflg 指定 shmget() 函数的动作,比如传入 IPC_CREAT 表示要创建新的共享内存。
函数调用成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败返回-1,并设置错误码。
shmget() 函数返回的是一个标识符,而不是可用的内存地址,所以还需要调用 shmat() 函数把共享内存关联到某个虚拟内存地址上。shmat() 函数的声明如下:void *shmat(int shmid, const void *shmaddr, int shmflg);
参数 shmid 是 shmget() 函数返回的标识符。
参数 shmaddr 是要关联的虚拟内存地址,如果传入0,表示由系统自动选择合适的虚拟内存地址。
参数 shmflg 若指定了 SHM_RDONLY 位,则以只读方式连接此段,否则以读写方式连接此段。
函数调用成功返回一个可用的指针(虚拟内存地址),出错返回-1
当一个进程不需要共享内存的时候,就需要取消共享内存与虚拟内存地址的关联。取消关联共享内存通过 shmdt() 函数实现,声明如下:int shmdt(const void *shmaddr);
参数 shmaddr 是要取消关联的虚拟内存地址,也就是 shmat() 函数返回的值。
函数调用成功返回0,出错返回-1。
当一个共享内存不再使用时,就需要删除它,删除共享内存的函数声明如下:int shmctl(int shm_id, int command, struct shmid_ds *buf);
参数shm_id是shmget函数返回的共享内存标识符。
参数command填IPC_RMID。
参数buf填0。
5、使用举例
下面通过一个例子来介绍一下共享内存的使用方法。在这个例子中,有两个进程,分别为 进程A 和 进程B,进程A 创建一块共享内存,然后写入数据,进程B 获取这块共享内存并且读取其内容。
进程A代码内容:
#include
#include
#include
#include
#include
#define SHM_PATH "/tmp/shm"
#define SHM_SIZE 128
int main(int argc, char *argv[])
{
int shmid;
char *addr;
key_t key = ftok(SHM_PATH, 0x6666);
shmid = shmget(key, SHM_SIZE, IPC_CREAT|IPC_EXCL|0666);
if (shmid < 0) {
printf("failed to create share memory\n");
return -1;
}
addr = shmat(shmid, NULL, 0);
if (addr <= 0) {
printf("failed to map share memory\n");
return -1;
}
sprintf(addr, "%s", "Hello World\n");
return 0;
}
进程B代码内容:
#include
#include
#include
#include
#include
#include
#define SHM_PATH "/tmp/shm"
#define SHM_SIZE 128
int main(int argc, char *argv[])
{
int shmid;
char *addr;
key_t key = ftok(SHM_PATH, 0x6666);
char buf[128];
shmid = shmget(key, SHM_SIZE, IPC_CREAT);
if (shmid < 0) {
printf("failed to get share memory\n");
return -1;
}
addr = shmat(shmid, NULL, 0);
if (addr <= 0) {
printf("failed to map share memory\n");
return -1;
}
strncpy(buf, addr, 128);
printf("%s", buf);
return 0;
}
使用gcc编译,gcc -o A.out A.c ,gcc -o B.out B.c 。编译完成后,测试时先运行进程A,然后再运行进程B,可以看到进程B会打印出 “Hello World”,说明共享内存已经创建成功并且读取。
我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。
共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。