Chinaunix首页 | 论坛 | 博客
  • 博客访问: 832473
  • 博文数量: 97
  • 博客积分: 3042
  • 博客等级: 中校
  • 技术积分: 1610
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-21 11:48
文章存档

2015年(1)

2014年(3)

2013年(4)

2012年(43)

2011年(44)

2010年(2)

分类: LINUX

2011-06-17 21:25:39

看完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即可删除该共享内存区.

      1. 当我们要用到共享内存区数据结构shmid_ds种的某些字段时, 我们只需将函数参数设置成IPC_STAT即可.

      2. 有时候需要设置共享内存区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宏定义, 作为参数是保证创建一个新的, 唯一的共享内存对象

阅读(1328) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~