zoukankan      html  css  js  c++  java
  • 第11章 进程间通信(3)_共享内存

    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
    ", *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
    ", 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
    ", 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
    ", 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 //不安全!(可用信号量解决)
     */
  • 相关阅读:
    Windows Phone自学笔记(2)
    对MVC的初步认识
    CMSIS 的相关知识
    关于机器码的一些疑惑
    关于预编译处理的尝试
    IAR提示错误C:\Program Files\IAR Systems\Embedded Workbench 6.4 Kickstart\arm\bin路径下的armjlink.dll文件
    Spring学习笔记(四)
    Spring学习笔记(三)
    Docker容器数据卷(v创建数据卷)
    Spring5学习笔记(一)
  • 原文地址:https://www.cnblogs.com/5iedu/p/6588876.html
Copyright © 2011-2022 走看看