十、进程间通信-共享内存

一、概述

 

 

 

  • 共享内存用于用于进程间的数据共享,

  • 开辟一块物理内存空间, 各个进程将同一块物理内存空间映射到自己的虚拟地址空间中, 通过虚拟地址进行访问, 进而实现数据共享

  • 共享内存是最快的进程间通信方式, 因为通过虚拟地址空间映射后, 直接通过虚拟地址访问物理内存, 相较于其他方式少了两步数据拷贝的操作.

  • 用于高效率传输大量数据

二、共享内存的用法

1、定义一个唯一key(ftok)

key_t ftok(const char *pathname, int proj_id);

2、构造一个共享内存对象(shmget)

(1)头文件

 #include <sys/ipc.h>
 #include <sys/shm.h>

 (2)函数原型

int shmget(key_t key,int size,int shmflg)

(3)参数

  • key:  键值;

  • size: 共享内存的大小。

  • shmflg:

    •  IPC_CREATE:共享内存不存在则创建 

    • mode:共享内存的权限

(4)返回值 

  • 成功:共享内存ID;

  • 失败:-1

3、共享内存映射(shmat)

int shmat(int shmid,const void *shmaddr,int shmflg)

(1)参数

  • shmid:共享内存ID

  • shmaddr:映射地址,NULL为自动分配

  • shmflg:

    •  SHM_RDONLY:只读方式映射

    •  0:可读可写

(2)返回值

  • 成功:共享内存首地址
  • 失败:-1

4、解除共享内存映射(shmdt)

int shmdt(const void *shmaddr)

   (1)参数

  • shmaddr:映射的地址

(2)返回值 

  • 成功:0
  • 失败:-1

5、删除共享内存(shmctl RMID)

int shmctl(int shmid,int cmd,struct shmid_ds *buf)  //获取或设置共享内存的相关属性

(1)参数:

  • shmid:共享内存ID

  • cmd

    • IPC_STAT:获取共享内存的属性信息
    • IPC_SET:设置共享内存的属性
    •  IPC_RMID:删除共享内存
  •  buf:属性缓冲区

(2)返回值:

  • 成功:由cmd类型决定
  • 失败:-1

三、实例

  使用共享内存,子进程拷贝数据到共享内存,父进程读取共享内存的数据。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include <sys/shm.h>

#define DELAY_TIME 3
union semun{
    int val;
    struct seimd_ds *buf;
};


/*初始化信号量*/
int init_sem(int sem_id,int init_value)
{
    union semun sem_union;
    sem_union.val = init_value;
      /*设置信号量集中的一个单独的信号量的值*/
    if(semctl(sem_id,0,SETVAL,sem_union) == -1) 
    {
        printf("Initialize semaphore failed!\n");
        return -1;
    }
    return 0;
}

/*删除信号量*/
int del_sem(int sem_id)
{
    union semun sem_union;
    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
    {
        perror("Delete semaohore fail!\n");
        return -1;
    }
    return 0;
}

/*P 操作:执行P操作时,信号量-1*/  

int sem_p(int sem_id)
{
    struct sembuf sops;
    sops.sem_num = 0;
    sops.sem_op  = -1;
    sops.sem_flg = SEM_UNDO;/*系统自动释放将会在系统中残留的信号量*/

    if(semop(sem_id,&sops,1) == -1)
    {
        perror("P operation failed\n");
        return -1;
    }
    return 0;
}

/*V 操作:执行P操作时,信号量+1*/
int sem_v(int sem_id)
{
    struct sembuf sops;
    sops.sem_num = 0;   /*单个信号量的编号应该为 0*/
    sops.sem_op  = 1;    /*表示V操作*/
    sops.sem_flg = SEM_UNDO;  /*系统自动释放将会在系统中残留的信号量*/

    if(semop(sem_id,&sops,1) == -1)
    {
        perror("V operation failed\n");
        return -1;
    }
    return 0;
}

void main(void)
{
    pid_t result;
    int sem_id;
    int shm_id;
    char* addr;
   //这里6666的key也可以用ftok去生成
    sem_id = semget((key_t)6666,1,0666|IPC_CREAT);/*创建一个信号量*/
    
    shm_id = shmget((key_t)7777,1024,0666|IPC_CREAT); /*创建一个共享内存对象*/

    init_sem(sem_id,0);  //初始化信号量的值为0,只能先执行V操作+1,然后才能执行P操作-1

    /*调用fork()函数*/
    result = fork();
    if(result == -1)
    {
        perror("Fork\n");
    }
    else if(result == 0)
    {
        printf("Child proess will wait for some seconds...\n");
        sleep(DELAY_TIME);  
        /*映射共享内存*/
        addr = shmat(shm_id,NULL,0);
        if(addr == (void*)-1)
        {
            printf("shmat111 error!");
            exit(-1);
        }
        /*设置共享内存的内容*/
        memcpy(addr,"helloworld",11);
        printf("the child process is running...\r\n");
        sem_v(sem_id); //如果有其他进程因等待信号量而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
    }
    else /*返回值大于0代表父进程*/
    {
        sem_p(sem_id); //如果信号量等于0,则进程挂起,直到信号量大于0,执行V操作
        printf("the father process is running...\r\n");
        /*映射共享内存地址*/
        addr = shmat(shm_id,NULL,0);
          if(addr == (void*)-1)
        {
            printf("shmat222 error!");
            exit(-1);
        }
        printf("shared memory string:%s\r\n",addr);

        /*解除共享内存映射*/
        shmdt(addr);

        /*删除共享内存映射*/
        shmctl(shm_id,IPC_RMID,NULL);

        /*删除信号量*/
        del_sem(sem_id);
    }
    
    exit(0);
}

执行结果:

 

 父进程读出了共享内存地址的数据helloworld,代码中的信号量用于共享资源的互斥。

  

  

posted @ 2022-04-27 18:18  轻轻的吻  阅读(129)  评论(0编辑  收藏  举报