浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第11章 进程间通信(3)_共享内存

Posted on 2017-03-20 14:41  浅墨浓香  阅读(421)  评论(0编辑  收藏  举报

3.3 共享内存

(1)共享内存简介

 

  ①共享内存区域是被多个进程共享的一部分物理内存

  ②多个进程都可把该共享内存映射到自己的虚拟内存空间。所有用户空间的进程若要操作共享内存,都要将其映射到自己虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信。

  ③共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入数据,共享这个内存区域的所有进程就可以立刻看到其中的内容

  ④本身不提供同步机制,可通过信号量进行同步。

  ⑤提升数据处理效率,一种效率最高的IPC机制

(2)共享内存属性结构体

 

(3)共享内存的使用步骤

  ①使用shmget函数创建共享内存

  ②使用shmat函数映射共享内存,将这段创建的共享内存映射到具体的进程虚拟内存空间中。

  ③解除映射

  ④删除共享内存

(4)共享内存的创建、控制、映射和解除映射

  ①创建共享内存

头文件

#include <sys/shm.h>

函数

int shmget(key_t key, size_t size, int shmflag);

参数

key: 用户指定的共享内存键值

size:共享内存大小

shmflag:IPC_CREAT、IPC_EXCL等权限组合

功能

创建共享内存

返回值

成功返回内核中共享内存的标识ID,出错返回-1

errno:

(1)EINVAL(无效内存段大小)  (2)EEXIST(内存段己经存在,无法创建)

(3)EIDRM(内存段己经被删除) (4)ENOENT(内存段不存在)

(5)EACCESS(权限不够)  (6)ENOMEM(没有足够的内存来创建内存段)

  ②共享内存的控制

头文件

#include <sys/shm.h>

函数

int shmctl(int shmid, int cmd, struct shmid_ds buf);

参数

(1)shmid: 共享内存的ID

(2)buf:共享内存属性指针

(3)cmd:

  ①IPC_STAT: 获取共享内存段属性。

  ②IPC_SET:  设置共享内存段属性

  ③IPC_RMID: 删除共享内存段

  ④SHM_LOCK: 锁定共享内存段页面(页面映射到物理内存不和外存进行换入和换出操作)

  ⑤SHM_UNLOCK:解除共享内存段而面的锁定。

功能

控制共享内存

返回值

成功返回内核中共享内存的标识ID,出错返回-1

  ③共享内存映射和解决映射

头文件

#include <sys/shm.h>

函数

void* shmat(int shmid, char* shmaddr, int shmflag); //映射,成功返回共享内存映射到进程虚拟内存空间的地址,失败返回-1

int shmdt(char* shmaddr); //解除映射。成功返回0,失败返回-1。

参数

(1)shmid: 共享内存ID

(2)shmaddr:映射到进程虚拟内存的地址。建议设置为0,由操作系统分配。

(3)shmflag:若shmaddr设置为0,则shmflag也设置为0。

  ①SHM_RND:随机

  ②SHM_BA:  地址为2的平方

  ③SHM_RDONLY: 只读方式链接

备注

(1)errno:

  ①EINVAL(无效的IPC ID值或无效的地址);

  ②ENOMEM(没有足够的内存)

  ③EACCESS(存取权限不够)

(2)子进程不继承父进程创建的共享内存,因为大家是共享的。子进程继承父进程映射的地址。

【编程实验】不同进程操作共享内存(使用管道来同步)

//tell.h

#ifndef __TELL_H__
#define __TELL_H__
//管道初始化 extern void init(); //利用管道进行等待 extern void wait_pipe(); //利用管道进行通知 extern void notify_pipe(); //销毁管道 extern void destroy_pipe(); #endif

//tell.c

#include "tell.h"
#include <stdio.h>
#include <stdlib.h>

static int fd[2];  //保存管道的文件描述符

//管道初始化
void init()
{
    if(pipe(fd) < 0){
        perror("pipe error");
    }
}

//利用管道进行等待
void wait_pipe()
{
    char c;
    //管道读写默认是阻塞性的
    if(read(fd[0], &c, 1) < 0){
        perror("wait pipe error");
    }
}

//利用管道进行通知
void notify_pipe()
{
    char c = 'c';
    if(write(fd[1], &c, 1) != 1){
        perror("notify pipe error");
    }   
}

//销毁管道
void destroy_pipe()
{
    close(fd[0]);
    close(fd[1]);
}

//cal_shm.c

#include <unistd.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include "tell.h"

int main(void)
{
    //创建共享内存
    int shmid;
    if((shmid = shmget(IPC_PRIVATE, 1024, //大小为1024字节 
                      IPC_CREAT | IPC_EXCL | 0777)) < 0){
        perror("shmget error");
        exit(1);
    }

    pid_t  pid;
    init(); //初始化管道
    //创建子进程
    if((pid = fork()) < 0){
        perror("fork error");
        exit(1);
    }else if(pid > 0){  //parent process
        //进行共享内存的映射
        int* pi = (int*)shmat(shmid, 0, 0);
        if(pi == (int*)-1){
            perror("shmat error");
            exit(1);
        }

        //往共享内存中写入数据(通过地址即可操作!)
        *pi = 100;
        *(pi + 1) = 200;
        
        //操作完毕,解除映射
        shmdt(pi);
        
        //通知子进程到读取共享内存中的数据
        notify_pipe();
        
        destroy_pipe();
        wait(0);
        
        //删除共享内存
        shmctl(shmid, IPC_RMID, NULL);
    }else{ //child process
        //子进程阻塞,等待父进程先往共享内存中写入数据
        wait_pipe();
        
        /*子进程从共享内存中读取数据*/

        //子进程进行共享内存映射
        int* pi = (int*)shmat(shmid, 0, 0);
        if(pi == (int*)-1){
            perror("shmat error");
            exit(1);
        }
        
        printf("start: %d end: %d\n", *pi, *(pi + 1));

        shmdt(pi);

        destroy_pipe();
    }
}

【编程实验】共享内存实现ATM(没有互斥、是不安全!)

(1)银行帐户创建在共享内存中(而不是原来的堆)

(2)改多线程为多进程程序。

//account.h

#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__

typedef struct
{
    int      code;    //帐号
    double   balance; //余额
}Account;

//取款
extern double withdraw(Account* a, double amt); //amt == amount
//存款
extern double deposit(Account* a, double amt);
//查看帐户余额
extern double get_balance(Account* a);

#endif  //__ACCOUNT_H__

//account.c

#include "account.h"
#include <string.h>
#include <assert.h>

//取款
double withdraw(Account* a, double amt) //amt == amount
{
    assert(a != NULL);

    if(amt < 0 || amt > a->balance){
        return 0.0;
    }

    double balance = a->balance; //先取余额
    sleep(1); //为模拟进程下可能出现的问题

    balance -= amt;
    a->balance = balance; //更新余额。在读取余额和更新余额之间有
                          //故意留出“时间窗口”。
    
    return amt;    
}

//存款
double deposit(Account* a, double amt)
{
    assert(a != NULL);

    if(amt < 0){
        return 0.0;
    }

    double balance = a->balance; //先取余额

    sleep(1); //为模拟多进程下可能出现的问题

    balance += amt;
    a->balance = balance; //更新余额。
    
    return amt;    
}

//查看帐户余额
double get_balance(Account* a)
{
    assert(a != NULL);
    
    double balance = a->balance;
 
    return balance;
}

//account_test.c

#include "account.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>

int main(void)
{
    //在共享内存中创建银行帐户
    int shmid;
    if((shmid = shmget(IPC_PRIVATE, sizeof(Account), IPC_CREAT | IPC_EXCL | 0777)) < 0){
        perror("shmget error");
        exit(1);
    }

    //进程共享内存映射(a为返回的映射地址)
    Account* a= (Account*)shmat(shmid, 0, 0);
    if(a == (Account*)-1){
        perror("shmat error");
        exit(1);
    }

    //银行帐户初始化
    a->code = 100001;
    a->balance = 10000;
    printf("balance: %f\n", a->balance);
    
    //父子进程都进行取款
    pid_t pid;
    if((pid = fork()) < 0){
        perror("fork error");
        exit(1);
    }else if(pid > 0){ //parent process
        //父进程进行取款操作
        double amt = withdraw(a, 10000);
        printf("pid %d withdraw %f from code %d\n", getpid(), amt, a->code);
       
        //解除映射
        shmdt(a);

        wait(0);
        
        //删除共享内存区
        shmctl(shmid, IPC_RMID, NULL);

    }else{ //child process
        //子进程会继承父进程映射的共享内存地址
        //子进程进行取款操作
        double amt = withdraw(a, 10000);
        printf("pid %d withdraw %f from code %d\n", getpid(), amt, a->code);

        //解除映射
        shmdt(a);
    }

    return 0;
}
/*输出结果:
 balance: 10000.000000
 pid 1939 withdraw 10000.000000 from code 100001  
 pid 1940 withdraw 10000.000000 from code 100001 //不安全!(可用信号量解决)
 */