# Linux进程间通信IPC之共享内存和信号量

# 信号量

# 1.基本介绍

信号量是一种用于多线程或多进程之间同步的机制。信号量(Semaphore),是在多进程环境下使用的一种设施, 它负责协调各个进程, 以保证它们能够正确、合理地使用公共资源。信号量分为单值多值两种,单值信号量只能被一个进程获得,多值信号量后者可以被若干个进程获得。

需要包含头文件

#include <semaphore.h>
#include <sys/sem.h>

信号量可以用来保证两个或多个关键代码段不会被并发调用。

# 2.系统调用

可以使用sem_opensemget来创建信号量,不过这两个方法略有区别。

  • sem_open创建信号量

sem_open:用于初始化和打开一个有名信号量,它是POSIX标准中定义的函数,适用于命名信号量,可以创建新的信号量,也可以打开已存在的信号量。

sem_t *sem_cur = sem_open(SEM_NAME, O_CREAT, 0666, 1);
  • semget创建信号量

semget 用于获取System V信号量集的标识符。它是System V信号量API,较旧且不如POSIX信号量接口设计得好。可以获取已存在的信号量集的标识符,也可以创建新的信号量集。

int semget (key_t key, int nsems, int flag);

System VPOSIX是两种不同的标准,用于定义操作系统接口。System V``:最早由AT&T引入,包括了消息队列、共享内存和信号量等机制。它是早期Unix系统的一部分。POSIXIEEE制定的可移植操作系统接口标准,旨在定义应用程序与操作系统之间的接口。它覆盖了更广泛的领域,包括文件操作、进程管理、线程同步等。System V在旧版本的Unix系统中广泛使用,而POSIX`是新标准,更适合跨平台开发。

  • 信号量集专属控制函数semctl
int semctl (int semid, int semnum, int cmd, union semun arg);

函数中参数semid是一个信号量集的标识符,semnum指定semid的信号集中的某一个信号量,其类似于在信号量集资源数组中的下标,用来对指定资源进行操作。参数cmd定义函数所要进行的操作,其取值及表达的意义与参数arg的设置有关。

union semun
{
    int val;                    /* for SETVAL */
    struct semid_ds *buf ;     /* for IPC_STAT and IPC_SET */
    unsigned short array;       /* for GETALL and SETALL */
};
  • 使用sem_waitsem_post
#include <semaphore.h>
#include <sys/sem.h>

int main(void) {
    sem_wait(sem_cur);
    ...
    sem_post(sem_cur);
    return 0
}

sem_wait会阻塞进程直到sem_post发信号过来,依次实现同步。

# 共享内存

# 基本介绍

共享内存可以说是Linux下最快速、最有效的进程间通信方式。共享内存是多个进程可以把一段内存映射到自己的进程空间,以此来实现数据的共享及传输,这也是所有进程间通信方式中最快的一种,共享内存是存在于内核级别的一种资源。

查看当前系统IPC中的状态:

ipcs -m

# 2.相关函数

# System V共享内存

对于System V共享内存,主要的几个APIshmgetshmatshmdtshmctl

  • shmget创建或打开共享内存:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget (key_t key, int size, int flag);

shmget函数除了可以用于创建一个新的共享内存外,也可用于打开一个已存在的共享内存。

参数key表示所要创建或打开的共享内存的键值。

size表示共享内存区域的大小,只在创建一个新的共享内存时生效。

参数flag表示调用函数的操作类型,也可用于设置共享内存的访问权限。

  • 附加到进程的地址空间

当一个共享内存创建或打开后,某个进程如果要使用该共享内存则必须将此内存区域附加到它的地址空间,附加操作的系统调用如下:

void *shmat ( int shmid, const void *addr, int flag );
  • 从当前进程的地址空间脱离

当进程对共享内存段的操作完成后,应调用shmdt函数,作用是将指定的共享内存段从当前进程空间中脱离出去。

int shmdt (void *addr);
  • 控制
int shmctl (int shmid, int cmd, struct shmid_ds *buf );

# POSIX共享内存

POSIX接口可以创建命名的共享内存,用以在不同的进程间使用。

  • shm_open创建共享内存
int shm_fd = shm_open(SHM_NAME, O_RDONLY, 0666); shm_fd==-1)

可以通过指定SHM_NAME参数创建命名共享内存,这样就可以在不同的进程中访问同一块共享内存了。

  • mmap函数mmap(Memory Map)函数是一个强大的系统调用,用于将文件或其他对象映射到进程的虚拟内存地址空间。mmap映射的是文件的页缓存,而非磁盘中的文件本身。通过映射虚拟内存地址,可以直接在用户空间读写页缓存,避免将页缓存的数据复制到用户空间缓冲区的过程。使用mmap可以减少内存拷贝次数,提高文件读写操作的效率,但需要注意同步问题,避免数据丢失。

    • addr:指定映射的虚拟内存地址,可以设置为NULL,让内核自动选择合适的地址。

    • length:映射的长度。

    • prot:映射内存的保护模式,如可读、可写等。

    • flags:指定映射的类型,如共享映射、私有映射等。

    • fd:进行映射的文件句柄。

    • offset:文件偏移量,从文件的何处开始映射。

      void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
      
      # image_data = static_cast<uchar*>(mmap(NULL, 
                                          SHM_SIZE, 
                                          PROT_READ,
                                          MAP_PRIVATE,
                                          shm_fd,
                                          0));
      
  • memcpy将数据拷贝到共享内存中

memcpy(image_data, images_map[image_file], SHM_SIZE);
  • munmap函数,用于删除映射到进程地址空间的对象。munmap 用于解除已经通过 mmap 映射到进程地址空间的内存区域。它会删除特定地址区域的对象映射。
munmap(data_pointer, SHM_SIZE) == -1)
  • shm_unlink函数用于移除已命名的共享内存对象。shm_unlink 是用于解除链接共享内存的重要函数,通常与 shm_open 配对使用将删除字符串name指向的共享内存对象的名称。如果在解除链接对象时存在一个或多个对该共享内存对象的引用,那么在 shm_unlink 返回之前,名称将被删除。但是,对共享内存对象内容的删除将被推迟,直到所有对该共享内存对象的打开和映射引用都被删除。

完整的工程示例代码可以参考gitee仓库basic_cplusplus_examples (opens new window)中的write_shm.cpp (opens new window)read_shm.cpp (opens new window)文件。

# reference

1.https://gitee.com/lx_r/basic_cplusplus_examples.git (opens new window)