回忆一下前面所介绍的几种进程间通信方式的特点:
- 信号相当于软中断,当系统发送某个被设置的信号时就会立刻去执行该信号所对应的程序块,以信号标志的方式完成进程间的通信,不能够发送数据。
- 管道在发送数据方面起到了很好的作用,利用管道流的方式向有共同祖先的进程发送数据,因为
fork()
函数使得子进程继承了父进程的管道,为了解决只能在亲戚进程间的通信问题引出了命名管道,以文件的形式创建了两个管道流传输数据。 - 命名socket是建立了完整的服务器端和客户端的通信体系进行通信,通过创建套接字,并且对其操作,完成服务器端和客户端的数据传输。
- 信号量则运用的操作系统中典型的PV操作,通过创建或获取存在的信号量集,当多个进程对临界区操作时检查临界区此时是否被占用,使得进程间可以按照不同的执行顺序(同步或异步)来完成各自相应的代码和对临界区资源的合理使用。
共享内存原理介绍
共享内存,顾名思义即多个进程多一块相同地址的内存共享,可将该内存看作临界区,当不同的进程对临界区的修改对其它进程是可见的,这就是共享内存的目的。需要提前说明的是在IPC进程间通信的所有方式中,共享内存是最快的。它实现的方式是将一个内存区映射到共享他的不同进程的地址空间中,这样是的进程间的通信不需要通过内核处理,而是直接对该共享的内存区域进行相应操作即可。但共享内存的使用需要用户自己进行同步操作。
共享内存API介绍
该方式的API函数和信号量中的逻辑上有所相似,都是通过函数创建文件或共享内存段的标识码,然后对标识码进行操作:
ftok()获取关键字
除共享内存外在另外两种进程间通信(信号量,消息队列)中都需要key_t类型的关键字ID值,用来唯一指定一个文件或文件夹路径,该ID值可以是我们自己定义的一个整型数值,更多的是通过调用ftok()
来获取ID。在Unix中该函数会将文件的索引节点号取出在前面加上子序号作为key_t的返回值。如果要保证key值不变的话要保证ftok函数指定的文件不被删除,否则不用ftok函数指定一个固定的key值。例如文件索引节点号为13579换算为十六进制则为0x350b,你所指定的子序号为21,换算为16进制为0x15,则最后的key_t值为0x15350b,查询文件的索引节点号的命令是:
ls -i filename //file那么为该文件名
#include <sys/types.h>
#include <sys/ipc.h> //头文件
key_t ftok(const char *pathname, int proj_id);
//调用成功则返回key_t类型的ID,失败则返回-1
- 第一个参数pathnema为一个指向系统存在的文件或文件路径的指针,会使用该文件的索引节点。
- 第二个参数proj_id为用户自己定义的一个子序号,他是一个8bit的整数,取值范围为1~255。
shmget()创建或获取共享标识符
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
//成功返回内存标识符,失败返回-1并设置error
- 第一个参数key若为0则会建立新的共享内存对象,通常此值会被设置为
ftok()
函数返回值。 - 第二个参数size是以字节为单位的指定共享内存容量。
- 第三个参数shmflg是权限标志,当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符;IPC_CREAT|IPC_EXCL如果内核中不存在键值 与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错。shmflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限
shmat()映射共享空间
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg)
//成功则返回指向共享内存地址的指针,失败返回-1并设置error
该函数用来把共享内存区对象映射到调用进程的地址空间。当创建完共享内存时还不能被进程访问因为没有入口地址,而shmat函数就可以启动对共享内存的访问,并且将共享内存连接到当前进程的地址空间,fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach)。
- 第一个参数shmid为
shmget()
函数返回值,即共享内存标识符。 - 第二个参数shmaddr指定共享内存出现在进程内存地址的什么位置,当为NULL时会让内核自己决定一个适合的地址位置。
- 第三个参数shmflg是一组标志位,通常为0,SHM_RDONLY:为只读模式,其他为读写模式。
shmctl()共享空间管理
#include <sys/types.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
//成功则返回0,失败返回-1并设置error
- 第一个参数shmid为
shmget()
函数返回值,即共享内存标识符。 - 第二个参数cmd提供了三种操作方式:IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中;IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内;IPC_RMID:删除这片共享内存
- 第三个参数buf为一个结构体指针,所指向共享内存模式和访问权限的结构,定义如下:
struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
shmdt断开共享连接
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr)
//成功返回0,失败返回-1,并设置error
本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程。
- 第一个参数shmaddr为shmat函数返回的地址指针。
示例代码
下面代码为共享内存写端:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
typedef struct students
{ char name[64];
int age;
}student_s;
int main(int argc, char **argv)
{
int shmid;
int i;
student_s *student;
//创建共享内存
shmid = shmget(ftok("/dev/null", 21), sizeof(student_s), IPC_CREAT|0666);
if( shmid < 0)
{
printf("shmget() create shared memroy failure: %s\n", strerror(errno));
return -2;
}
student = shmat(shmid, NULL, 0);
if( (void *)-1 == student )
{
printf("shmat() alloc shared memroy failure: %s\n", strerror(errno));
return -2;
}
//设置结构体的数据
strncpy(student->name, "panghu", sizeof(student->name));
student->age = 22;
for(i=0; i<3; i++)
{
student->age ++;
printf("Student '%s' age [%d]\n", student->name, student->age);
sleep(1);
}
//下面的从共享内存中读数据因此关闭内存连接再读端中左,否则会读不到数据。
//shmdt(student);
//shmctl(shmid, IPC_RMID, NULL);
return 0;
}
下面代码为共享内存读端:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
typedef struct students
{ char name[64];
int age;
}student_s;
int main(int argc, char **argv)
{
int shmid;
int i;
student_s *student;
//创建共享内存
shmid = shmget(ftok("/dev/null", 21), sizeof(student_s), IPC_CREAT|0666);
if( shmid < 0)
{
printf("shmget() create shared memroy failure: %s\n", strerror(errno));
return -2;
}
student = shmat(shmid, NULL, 0);
if( (void *)-1 == student )
{
printf("shmat() alloc shared memroy failure: %s\n", strerror(errno));
return -2;
}
//循环读取内存中数据
for(i=0; i<3; i++)
{
student->age ++;
printf("Student '%s' age [%d]\n", student->name, student->age);
sleep(1);
}
//完成读取后则需要释放结构体内存断开连接
shmdt(student);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
运行结果如下:
panghu@Ubuntu-14:~$ ./share.write
Student 'panghu' age [23]
Student 'panghu' age [24]
Student 'panghu' age [25]
panghu@Ubuntu-14:~$ ./share.read
Student 'panghu' age [26]
Student 'panghu' age [27]
Student 'panghu' age [28]
panghu@Ubuntu-14:~$ ./share.read
Student '' age [1]
Student '' age [2]
Student '' age [3]
从上面的结果我们可以看出,当写端写入数据后没有断开连接,读端就会从中读取刚才写入的数据,读端完成后释放内存,断开连接,再次运行的时候,就不会读到数据。