【共享内存】

共享内存

  • 1. 概述
  • 2. 案例分析(同步执行)
    • 写进程(shm_write.c)
      • shmget结构体和shmget函数的区别
      • shmat函数的作用
      • fill_buffer函数
      • shmdt函数
    • 读进程(shm_read.c)
    • 运行结果
  • 3. 案例分析(进程同步)
    • 写进程(shm_writes_sem.c)
    • 读进程(shm_read_sem.c)

1. 概述

  共享内存是一种进程间通信(IPC)的机制,允许不同的进程共享同一块内存区域。这样,多个进程可以同时访问和修改共享内存中的数据,从而达到数据共享的目的。
  共享内存通常由一个进程创建,并通过特定的系统调用将其附加到其他进程的地址空间中。进程可以通过读写共享内存中的数据来进行通信和同步。由于共享内存的访问速度非常快,因此它是一种高效的进程间通信机制。
  但是,由于多个进程可以同时访问和修改共享内存中的数据,因此需要特殊的同步机制来避免数据竞争和其他并发问题。常见的同步机制包括信号量、互斥量和条件变量等。

2. 案例分析(同步执行)

  话不多说,直接上代码。

写进程(shm_write.c)

/* Filename: shm_write.c */
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define BUF_SIZE 1024
#define SHM_KEY 0x1234

struct shmseg {
   int cnt;             //计数器
   int complete;        //标志位
   char buf[BUF_SIZE];  //长度为BUF_SIZE 的缓冲区
};
int fill_buffer(char * bufptr, int size);

int main(int argc, char *argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp; //创建共享内存区域
   char *bufptr;
   int spaceavailable;
   //shmget函数用于创建共享内存段
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT); //SHM_KEY 是一个用户定义的常量,它是一个键值,用于标识共享内存段。
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }

   /* Transfer blocks of data from buffer to shared memory */
   bufptr = shmp->buf;
   spaceavailable = BUF_SIZE;
   for (numtimes = 0; numtimes < 5; numtimes++) {
      shmp->cnt = fill_buffer(bufptr, spaceavailable);
      shmp->complete = 0;
      printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
      bufptr = shmp->buf;
      spaceavailable = BUF_SIZE;
      sleep(3);
   }
   printf("Writing Process: Wrote %d times\n", numtimes);
   shmp->complete = 1;

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }

   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}

int fill_buffer(char * bufptr, int size) {
   static char ch = 'A';
   int filled_count;

   //printf("size is %d\n", size);
   memset(bufptr, ch, size - 1);
   bufptr[size-1] = '\0';
   if (ch > 122)
   ch = 65;
   if ( (ch >= 65) && (ch <= 122) ) {
      if ( (ch >= 91) && (ch <= 96) ) {
         ch = 65;
      }
   }
   filled_count = strlen(bufptr);

   //printf("buffer count is: %d\n", filled_count);
   //printf("buffer filled is:%s\n", bufptr);
   ch++;
   return filled_count;
}
//更多请阅读:https://www.yiibai.com/ipc/shared_memory.html

  这是一个使用共享内存实现进程间通信的程序。它创建了一个共享内存区域,然后向其中写入数据。下面是代码的大致流程:

  1. 定义了一个结构体 shmseg,其中包含了一个计数器、一个标志位以及一个长度为 BUF_SIZE缓冲区
  2. 主函数中调用了 shmget 函数来创建共享内存区域,如果创建失败则输出错误信息并退出程序。
  3. 调用 shmat 函数将进程附加到共享内存区域,获取到了一个指向共享内存区域的指针,如果失败则输出错误信息并退出程序。
  4. 主函数中通过循环向共享内存区域写入数据。具体来说,它调用了一个名为 fill_buffer 的函数,该函数填充缓冲区,然后将缓冲区中的数据写入共享内存区域。循环执行 5 次,每次等待 3 秒钟。
  5. 写入完成后,主函数将 complete 标志位置为 1,然后通过调用 shmdt 函数将进程从共享内存区域分离,最后调用 shmctl 函数删除共享内存区域。
  6. 程序运行完毕,主函数输出 "Writing Process: Complete"

总之,这是一个向共享内存区域写入数据的程序。它创建了一个共享内存区域,然后将数据写入其中。该程序可以与另一个程序共同使用,以实现进程间通信。

shmget结构体和shmget函数的区别

  1. shmget 函数用于创建或访问一个共享内存段。它的函数原型为:
int shmget(key_t key, size_t size, int shmflg);

它接受三个参数
  (1)key:用于标识共享内存段的键值,可以通过 ftok 函数生成。
  (2)size:指定共享内存段的大小(以字节为单位)。
  (3)shmflg:指定访问权限和其他标志。
  shmget 函数返回共享内存段的标识符(也称为共享内存段的 ID)。如果共享内存段已经存在,则返回它的标识符;如果共享内存段不存在,则根据指定的 size 创建一个新的共享内存段,并返回它的标识符。
2. struct shmseg 结构体定义了共享内存段中存储的数据的格式。它包含三个成员:
  (1)cnt:表示缓冲区中存储的数据的长度。
  (2)complete:一个标志,用于表示数据是否已经全部写入共享内存段。
  (3)buf:一个字符数组,用于存储实际的数据。
  在程序中,使用 shmget 函数创建了一个共享内存段,然后使用 struct shmseg 结构体来表示该共享内存段中存储的数据。程序使用共享内存段来实现两个进程之间的通信,其中一个进程用于写入数据,另一个进程用于读取数据。
  因此,shmget 函数和 struct shmseg 结构体的作用不同,但它们都是实现共享内存通信的关键组成部分。

shmat函数的作用

  shmat 函数用于将进程与一个共享内存段关联起来,从而使得进程可以访问共享内存段中的数据。它的函数原型为:

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

它接受三个参数:
  (1)shmid:共享内存段的标识符,它是由 shmget 函数返回的。
  (2)shmaddr:指定共享内存段将要被映射到进程的地址。通常传入 NULL,表示让系统自动选择一个合适的地址。
  (3)shmflg:指定映射方式和其他标志。
  shmat 函数返回一个指向共享内存段第一个字节的指针,如果出现错误,则返回 (void *)-1
  在这个程序中,shmat 函数用于将当前进程与共享内存段关联起来,从而使得进程可以访问共享内存段中的数据。具体来说,程序在调用 shmget 函数创建共享内存段之后,通过 shmat 函数将 struct shmseg 结构体映射到共享内存段上,这样就可以在进程中使用该结构体来访问共享内存段中的数据了。

fill_buffer函数

  fill_buffer 函数的作用是将指定的缓冲区填充为重复的字母序列,直到缓冲区填满为止。
  具体来说,fill_buffer 函数的第一个参数 bufptr 是一个指向缓冲区的指针,第二个参数 size 是缓冲区的大小(以字节为单位)。函数首先将一个静态的字符变量 ch 赋值为 'A',然后使用 memset 函数将缓冲区填充为 size-1ch 字符,最后将缓冲区的最后一个字符设置为 '\0',以确保缓冲区可以当作一个 C 语言风格的字符串使用。
  如果 ch 的值超过了字母表的最后一个字符 'z',则将 ch 的值重置为字母表的第一个字符 'A'。此外,fill_buffer 函数还会返回实际填充缓冲区的字符个数(即不包括最后一个 '\0' 字符)。
  在该程序中,fill_buffer 函数用于向共享内存段中写入数据,即将缓冲区填充为字母序列,然后将数据写入到共享内存段中。在写入数据之前,程序将缓冲区的地址赋给指向共享内存段的指针 shmp->buf,这样程序就可以通过 shmp->buf 指针来访问共享内存段中的缓冲区了。具体来说,程序在循环中反复调用 fill_buffer 函数,将缓冲区填充为不同的字母序列,然后将填充后的数据写入到共享内存段中。

shmdt函数

  shmdt()函数是用于将共享内存从进程的地址空间中分离的函数。调用该函数后,进程将不能再访问共享内存中的数据。该函数的函数原型如下:

#include 
int shmdt(const void *shmaddr);

  其中,参数shmaddr是指向共享内存段附加到进程地址空间的指针。
  如果分离成功,shmdt()函数返回0,否则返回-1,并设置errno变量来指示错误原因。
  在使用共享内存完成数据传输之后,需要使用shmdt()函数将共享内存从进程的地址空间中分离。这是一个很重要的步骤,因为共享内存是由多个进程共享的资源,而不是属于某个进程的私有资源,如果不使用shmdt()函数将共享内存分离,可能会导致其他进程无法访问该共享内存,造成资源泄漏等问题。

读进程(shm_read.c)

/* Filename: shm_read.c */
#include
#include
#include
#include
#include
#include
#include

#define BUF_SIZE 1024
#define SHM_KEY 0x1234

struct shmseg {
   int cnt;             //计数器
   int complete;        //标志位
   char buf[BUF_SIZE];  //长度为BUF_SIZE 的缓冲区
};

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }

   /* Transfer blocks of data from shared memory to stdout*/
   while (shmp->complete != 1) {
      printf("segment contains : \n\"%s\"\n", shmp->buf);
      if (shmp->cnt == -1) {
         perror("read");
         return 1;
      }
      printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
      sleep(3);
   }
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}
//更多请阅读:https://www.yiibai.com/ipc/shared_memory.html

  这段代码实现了一个使用共享内存的读进程。这段代码的作用是从共享内存中读取数据并输出到标准输出流,主要用于进程间通信。以下是代码的详细解读:

  1. 包含必要的头文件:stdio.hsys/ipc.hsys/shm.hsys/types.hstring.h、`errno.h、stdlib.h。
  2. 定义常量:BUF_SIZE表示共享内存中的缓冲区大小为1024SHM_KEY表示共享内存的键值为0x1234
  3. 定义了一个结构体shmseg,包括三个成员变量:cnt表示缓冲区中实际数据的大小;complete表示是否读取完毕;buf表示共享内存中的缓冲区。
  4. main()函数首先创建共享内存,如果创建失败则输出错误信息并返回1。
  5. 通过shmat()函数将共享内存附加到进程的地址空间,返回一个指向共享内存的指针,如果附加失败则输出错误信息并返回1。
  6. 在一个循环中,读进程从共享内存中读取数据并输出到标准输出流,如果读取出错则输出错误信息并返回1。
  7. 如果complete成员变量被设置为1,表明读取完毕,此时读进程将共享内存从地址空间中分离,如果分离失败则输出错误信息并返回1。
  8. 最后,读进程输出“Reading Process: Complete”,并返回0表示正常退出。

运行结果

【共享内存】_第1张图片

3. 案例分析(进程同步)

  发现问题: 在以上代码中,使用了一个简单的标志位complete来指示进程是否已经完成对共享内存的读写操作。标志位的值为0表示正在写入,值为1表示写入已完成,读取进程可以根据该标志位的值来判断何时停止读取。如果使用信号量来控制进程对共享内存的访问,则可以更加精细地控制读写进程之间的同步,从而避免竞态条件和数据不一致等问题。

写进程(shm_writes_sem.c)

/* Filename: shm_write.c */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  //添加了信号量的头文件

#define BUF_SIZE 1024
#define SHM_KEY 0x1234

struct shmseg
{
    int cnt;            // 计数器
    int complete;       // 标志位
    char buf[BUF_SIZE]; // 长度为BUF_SIZE 的缓冲区
};
int fill_buffer(char *bufptr, int size);

int main(int argc, char *argv[])
{
    int shmid, numtimes;
    struct shmseg *shmp; // 创建共享内存区域
    char *bufptr;
    int spaceavailable;
    sem_t *sem_read, *sem_write;
    sem_read = sem_open("/sem_read", O_CREAT, 0644, 0);
    sem_write = sem_open("/sem_write", O_CREAT, 0644, 1);

    // shmget函数用于创建共享内存段
    shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644 | IPC_CREAT); // SHM_KEY 是一个用户定义的常量,它是一个键值,用于标识共享内存段。
    if (shmid == -1)
    {
        perror("Shared memory");
        return 1;
    }

    // Attach to the segment to get a pointer to it.
    shmp = shmat(shmid, NULL, 0);
    if (shmp == (void *)-1)
    {
        perror("Shared memory attach");
        return 1;
    }

    /* Transfer blocks of data from buffer to shared memory */
    bufptr = shmp->buf;
    spaceavailable = BUF_SIZE;
    for (numtimes = 0; numtimes < 5; numtimes++)
    {
        // 等待信号量,保证同步
        sem_wait(sem_write);
        shmp->cnt = fill_buffer(bufptr, spaceavailable);
        shmp->complete = 0;
        printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
        bufptr = shmp->buf;
        spaceavailable = BUF_SIZE;
        sleep(3);
        // 释放信号量
        sem_post(sem_read); // 发送读取信号量
    }
    printf("Writing Process: Wrote %d times\n", numtimes);
    shmp->complete = 1;

    if (shmdt(shmp) == -1)
    {
        perror("shmdt");
        return 1;
    }

    if (shmctl(shmid, IPC_RMID, 0) == -1)
    {
        perror("shmctl");
        return 1;
    }
    // 关闭信号量
    sem_close(sem_read);
    sem_close(sem_write);
    sem_unlink("/sem_read");
    sem_unlink("/sem_write");
    printf("Writing Process: Complete\n");
    return 0;
}

int fill_buffer(char *bufptr, int size)
{
    static char ch = 'A';
    int filled_count;

    // printf("size is %d\n", size);
    memset(bufptr, ch, size - 1);
    bufptr[size - 1] = '\0';
    if (ch > 122)
        ch = 65;
    if ((ch >= 65) && (ch <= 122))
    {
        if ((ch >= 91) && (ch <= 96))
        {
            ch = 65;
        }
    }
    filled_count = strlen(bufptr);

    // printf("buffer count is: %d\n", filled_count);
    // printf("buffer filled is:%s\n", bufptr);
    ch++;
    return filled_count;
}
// 更多请阅读:https://www.yiibai.com/ipc/shared_memory.html

读进程(shm_read_sem.c)

/* Filename: shm_read.c */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  //添加了信号量的头文件

#define BUF_SIZE 1024
#define SHM_KEY 0x1234

struct shmseg
{
    int cnt;            // 计数器
    int complete;       // 标志位
    char buf[BUF_SIZE]; // 长度为BUF_SIZE 的缓冲区
};

int main(int argc, char *argv[])
{
    int shmid;
    struct shmseg *shmp; // 创建共享内存区域
    sem_t *sem_read, *sem_write;
    sem_read = sem_open("/sem_read", O_CREAT, 0644, 0);
    sem_write = sem_open("/sem_write", O_CREAT, 0644, 1);

    shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644 | IPC_CREAT);
    if (shmid == -1)
    {
        perror("Shared memory");
        return 1;
    }

    // Attach to the segment to get a pointer to it.
    shmp = shmat(shmid, NULL, 0);
    if (shmp == (void *)-1)
    {
        perror("Shared memory attach");
        return 1;
    }

    /* Transfer blocks of data from shared memory to stdout*/
    while (shmp->complete != 1)
    {
        sem_wait(sem_read); // 等待读取信号量
        printf("segment contains : \n\"%s\"\n", shmp->buf);
        if (shmp->cnt == -1)
        {
            perror("read");
            return 1;
        }
        printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
        sem_post(sem_write); // 发送写入信号量
        sleep(3);
    }
    printf("Reading Process: Reading Done, Detaching Shared Memory\n");

    if (shmdt(shmp) == -1)
    {
        perror("shmdt");
        return 1;
    }

    if (shmctl(shmid, IPC_RMID, 0) == -1)
    {
        perror("shmctl");
        return 1;
    }

    sem_close(sem_read);
    sem_close(sem_write);
    sem_unlink("/sem_read");
    sem_unlink("/sem_write");
    printf("Reading Process: Complete\n");
    return 0;
}
// 更多请阅读:https://www.yiibai.com/ipc/shared_memory.html

  在这个例子中,可以使用两个二进制信号量(semaphore)来同步和保护共享内存的读和写:

  1. 创建两个二进制信号量:
#include 
#include 

sem_t *sem_read, *sem_write;
sem_read = sem_open("/sem_read", O_CREAT, 0644, 0);
sem_write = sem_open("/sem_write", O_CREAT, 0644, 1);

  以上创建了两个二进制信号量,一个用于读取进程sem_read ,一个用于写入进程sem_write 。 初始值分别为0和1,以确保写入进程可以立即开始写入。

  1. 在写入进程中,在开始写入之前获取写信号量,完成写入后释放读信号量。
sem_wait(sem_write); // 等待写入信号量
shmp->cnt = fill_buffer(bufptr, spaceavailable);
shmp->complete = 0;
printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
bufptr = shmp->buf;
spaceavailable = BUF_SIZE;
sem_post(sem_read); // 发送读取信号量

  1. 在读取进程中,在读取之前获取读取信号量,读取完成后释放写信号量。
while (shmp->complete != 1) {
   sem_wait(sem_read); // 等待读取信号量
   printf("segment contains : \n\"%s\"\n", shmp->buf);
   if (shmp->cnt == -1) {
      perror("read");
      return 1;
   }
   printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
   sem_post(sem_write); // 发送写入信号量
   sleep(3);
}

  这确保了在读取完成之前不会有其他进程写入新的数据。

  1. 在完成操作之后删除信号量。
sem_close(sem_read);
sem_close(sem_write);
sem_unlink("/sem_read");
sem_unlink("/sem_write");

致谢:本章学习参考连接(https://www.yiibai.com/ipc/shared_memory.html)

你可能感兴趣的:(c++,Linux,后端开发,c++,linux)