Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3120305
  • 博文数量: 396
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4209
  • 用 户 组: 普通用户
  • 注册时间: 2016-07-04 13:04
文章分类

全部博文(396)

文章存档

2022年(1)

2021年(2)

2020年(8)

2019年(24)

2018年(135)

2017年(158)

2016年(68)

我的朋友

分类: 嵌入式

2017-09-07 15:52:31

看完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宏定义作为参数是保证创建一个新的唯一的共享内存对象

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