分类:
2011-10-20 14:17:41
原文地址:System V进程间通信--共享内存 作者:kouyanghao
看完System V共享内存部分后感觉还可以, 但写起程序后就出现很多问题, 有的问题虽然解决了, 但是却不知道其中的原理, 于是请教别人查阅资料最终问题得到解决,感觉自己得到了不少,有必要将自己这阵子学习共享内存时的点滴记录下来, 于是打算还是写成日志的形式如下:
众所周知共享内存是IPC中最快, 最有效的一种进程间通信方式, 一旦这样的内存区映射到进程的地址空间,这些进程间的数据传递就不再涉及内核. 那么共享内存为什么称之为最有效的进程间通信方式, 下面以一个客户--服务器文件复制程序为例作简单的介绍:
总体上可分为四步,即数据被复制四次.
1. 服务器从输入文件读数据. 将文件的数据从内核读入到自己的内存空间,然后从内核复制到服务器进程.
2. 服务器往一个管道, FIFO, 消息队列以一条消息的形式写入这些数据,这些IPC形式通常是需要把这些数据从进程复制到内核.
3.客户从该IPC通道读这些数据, 把这些数据从内核复制到进程.
4.将这些数据从客户缓冲区复制到输出文件.
相对于管道, FIFO, 消息队列的四次复制过程, 共享内存完成相同功能之需要两次数据复制, 具体描述如下:
1.将数据从共享文件复制到共享内存区.
2.将数据从共享内存区复制到输出文件.
每个共享内存区,
内核都为其维护一个如下的数据结构,
它定义在头文件
struct shmid_ds{
struct ipc_perm; //操作的权限
size_t shm_segsz; //共享内存的大小
pid_t shm_lpid; //上一次操作时的pid
pid_t shm_cpid; //创建者的pid
shmatt_t shm_nattch; //当前附接数
shmatt_t shm_cnattch; //内核的附接数
time_t shm_atime; //最后一次附接的时间
time_t shm_dtime; //最后一次脱接的时间
time_t shm_ctime; //最后一次改变该结构的时间
};
下面介绍有关System V的几个操作共享内存的函数:
1. 创建一个共享内存或者访问一个已经存在的共享内存的函数.
int shmget(key_t key, size_t size, int oflag);
2. 将共享内存挂接到进程地址空间的函数:
void *shmmat(int shmid, const void* shmaddr, int flag);
3. 删除共享内存和进程地址空间的联系的函数:
int shmdt(const void *shmaddr);
4. 删除, 或者访问共享内存相应数据结构的函数:
int shmctl(int shmid, int cmd, struct shmid_ds *buff);
下面将逐一详细介绍每一个函数:
1. int shmget(key_t key, size_t size, int oflag);
当创建或者访问一个已经存在的共享内存时, 我们用shmget函数, 该函数返回一个共享内存标识符, 这里我认为有几点需要注意:
1.如果是创建一个共享内存区, 则size必须时一个大于0的整数, 如果是访问一个已经存在的共享内存区则size一定得为0;
2.函数的第一个参数key可以是一个整型, 也可以是IPC_PRIVATE.
3.oflag可以指定为IPC_CREAT或者IPC_CREAT | IPC_EXCL,二者的区别在于第二个在该共享内存不存在时不仅创建它, 而且还会提供检测功能, 即在该共享内存存在的时候,提示错误, 但二者是一个原子操作.
4.用该函数创建或打开一个共享内存区时, 调用成功返回一个内存标识符, 表明该共享内存已经存在, 同时相应的数据结构已经被初始化.只不过还没有将该共享内存区挂接到进程的相应地址空间. 因而暂时还不能访问该共享内存区.
2. void *shmat(int shmid, const void* shmaddr, int flag);
刚才提到创建或打开一个新的共享内存后,只有经过shmat函数将共享内存区附接到进程的地址空间后, 才可以访问该共享内存,对其进行各种操作.
这里依然有几点需要注意:
1.该函数的返回值是所指定的共享内存区在调用进程内的起始地址.如果shmaddr是一个空指针, 则系统替调用者选择一个地址, 这种方式通常是提倡的方法. 如果shmaddr不是一个空指针, 则返回地址取决于调用者是否给flag指定了SHM_RND值,如果没有指定, 那么相应的共享内存区附接到由shmaddr指定的地址, 反之,附接到由shmaddr指定的地址向下舍入一个SHMLBA常值.
3. int shmdt(const void *shmaddr);
当某个进程完成对一个共享内存的操作时,就可以通过这个函数断接和共享内存的链接,这里仍然有几点需要注意:
1.成功使用该函数后, 并没有删除共享内存区, 只是解除了进程和共享内存的一种链接关系.通过重新建立附接关系, 仍然可以访问该共享内存区.
2.当一个进程终止时, 它当前附接的所有共享内存区都自动断接掉,但该共享内存区还存在, 只不过需要重新建立这种附接关系.
4. int shmctl(int shmid, int cmd, struct shmid_ds *buff);
该函数对共享内存区提供了多种操作, 具体如下:
1. 当我们想将一个共享内存区交给系统回收时, 以便可以将其分配给其他进程使用时, 我们可以调用shmctl函数, 这时只需指定其参数为IPC_RMID即可删除该共享内存区.
当我们要用到共享内存区数据结构shmid_ds种的某些字段时, 我们只需将函数参数设置成IPC_STAT即可.
有时候需要设置共享内存区shmid_ds的一下三个成员: shm_perm.uid, shm_perm.gid, shm_perm.mode, 他们的值来自buff参数指向的结构的相应成员.shm_ctime的值也用当前时间替换.
下面是有关共享内存部分有关函数应用的程序:
题目描述: 父子进程通过竞争的方式来创建共享内存, 然后利用shmat函数来建立共享内存和进程之间的关系.最后通过shmctl函数删除共享内存.
#include
#include
#include
#include
#include
#include
#define MAXSIZE 4220
int main( )
{
int c, id, oflag;
char *ptr, *str;
pid_t pid;
oflag = IPC_CREAT | IPC_EXCL;
if((pid = fork()) == 0) {
if((id = shmget(15, MAXSIZE, oflag)) < 0)
{
printf("子进程中id = %d\n", id);
printf("子进程创建共享内存失败!\n");
return -1;
}
printf("子进程中id = %d\n", id);
if((ptr = shmat(id, NULL, 0)) == NULL) {
printf("子进程挂接共享内存失败!\n");
if((shmctl(id, IPC_RMID, NULL)) < 0) {
printf("子进程删除共享内存失败!\n");
}
}
_exit(0);
}else {
wait(NULL);
if((id = shmget(IPC_PRIVATE, MAXSIZE, IPC_CREAT| IPC_EXCL)) < 0) {
printf("父进程中id = %d\n", id);
printf("父进程创建共享内存失败!\n");
return -1;
}
printf("父进程中id = %d\n", id);
if((str = shmat(id, NULL, 0)) == NULL) {
printf("父进程挂接共享内存失败!\n");
return -1;
}
// if((shmctl(id, IPC_RMID, NULL)) < 0) {
// printf("父进程删除共享内存失败!\n");
// return -1;
// }
}
return 0;
}
程序中的wait()函数的作用:如果一个进程正常或异常终止时,内核就向父进程发送SIGCHLD
信号这时:
(1)如果子进程还在运行, 则阻塞.
(2)如果一个子进程已终止,正等待父进程获取其终止状态, 则取得子进程的终止状态立即返回.
(3)如果没有子进程, 则立即出错返回.
IPC_PRIVATE宏定义, 作为参数是保证创建一个新的, 唯一的共享内存对象.