Linux共享内存信号量是什么?如何实现和使用?

在 Linux 中,共享内存信号量是两种不同的进程间通信(IPC)机制。共享内存允许多个进程直接访问同一块内存区域,而信号量用于控制对共享资源的访问,确保同步和互斥。

Linux共享内存与信号量

Linux中的共享内存和信号量是两种强大的进程间通信(IPC)机制,它们在多进程编程中发挥着至关重要的作用,共享内存允许多个进程直接访问同一块物理内存,而信号量则用于同步对这些共享资源的访问,本文将详细介绍这两种机制的工作原理、使用方法以及它们之间的配合使用。

Linux共享内存信号量是什么?如何实现和使用?

一、共享内存

共享内存是一种高效的进程间通信方式,它允许多个进程直接读写同一块内存区域,从而避免了数据在进程间的复制,这种方式特别适用于需要频繁交换大量数据的场景。

1. 创建与映射共享内存

shmget:用于创建或获取一个共享内存段,其原型如下:

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

key:共享内存的键值,通常由ftok函数生成。

size:共享内存的大小,单位为字节。

shmflg:操作标志,如IPC_CREAT表示如果不存在则创建。

shmat:将共享内存段映射到当前进程的地址空间,其原型如下:

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

shmid:共享内存标识符,由shmget返回。

shmaddr:共享内存的起始地址,通常设为NULL,让系统自动选择。

shmflg:映射标志,如0表示可读写。

2. 访问共享内存

一旦共享内存被映射到进程的地址空间,进程就可以像访问普通内存一样对其进行读写操作。

char *shared_mem = (char*)shmat(shmid, NULL, 0);
strcpy(shared_mem, "Hello from process!");

3. 分离与删除共享内存

Linux共享内存信号量是什么?如何实现和使用?

shmdt:用于将共享内存从当前进程的地址空间分离,其原型如下:

    int shmdt(const void *shmaddr);

shmctl:用于控制共享内存段,如删除,其原型如下:

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

cmd:命令码,如IPC_RMID表示删除共享内存段。

buf:指向共享内存段的结构体指针,通常设为NULL。

二、信号量

信号量是一种用于解决进程同步问题的机制,它可以确保多个进程对共享资源的互斥访问,信号量本质上是一个计数器,通过PV操作(P表示等待,V表示增加)来控制进程的执行顺序。

1. 创建与初始化信号量

semget:用于创建或获取一个信号量集,其原型如下:

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

key:信号量的键值。

nsems:信号量集中的信号量数量。

semflg:操作标志,如IPC_CREAT表示如果不存在则创建。

semctl:用于控制信号量的信息,其原型如下:

    int semctl(int semid, int semnum, int cmd, ...);

semid:信号量集标识符。

semnum:信号量集中的信号量编号。

Linux共享内存信号量是什么?如何实现和使用?

cmd:命令码,如SETVAL用于初始化信号量的值。

2. 信号量的PV操作

semop:用于对信号量进行操作,其原型如下:

    int semop(int semid, struct sembuf *sops, size_t nsops);

semid:信号量集标识符。

sops:指向sembuf结构体的指针,定义了操作的类型和参数。

nsops:操作的数量。

一个典型的sembuf结构体定义如下:

struct sembuf {
    unsigned short sem_num; /* 信号量集中的编号 */
    short sem_op;  /* 操作类型,负值为P操作,正值为V操作 */
    short sem_flg;  /* 操作标志,如IPC_NOWAIT */
};

示例代码实现一个简单的生产者-消费者模型:

#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
union semun {
    int val;    /* value for SETVAL */
    struct semid_ds *buf;    /* buffer for IPC_STAT, IPC_SET */
    unsigned short *array;  /* array for GETALL, SETALL */
};
int main() {
    key_t key = ftok("semfile", 65);
    int semid = semget(key, 1, 0666 | IPC_CREAT);
    union semun arg;
    arg.val = 1; // Binary semaphore, initialized to 1 (available)
    semctl(semid, 0, SETVAL, arg);
    // Producer code
    struct sembuf p_lock = {0, -1, SEM_UNDO}; // P operation
    semop(semid, &p_lock, 1);
    printf("Producer: Entered critical section
");
    // Critical section: produce item
    sleep(2); // Simulate producing an item
    printf("Producer: Exiting critical section
");
    struct sembuf v_lock = {0, 1, SEM_UNDO}; // V operation
    semop(semid, &v_lock, 1);
    return 0;
}

在这个例子中,生产者在进入临界区之前执行P操作(等待信号量),离开临界区后执行V操作(释放信号量),消费者可以以类似的方式实现,只是在进入临界区时先检查信号量的值是否为1(表示有可用资源)。

三、共享内存与信号量的结合使用

共享内存和信号量经常结合使用,以实现高效的进程间通信和同步,一个进程可以将数据写入共享内存,然后使用信号量通知其他进程数据已准备好读取,这样可以避免多个进程同时访问共享内存而导致的数据竞争问题,以下是一个简化的示例,展示如何使用共享内存和信号量实现进程间的数据传输:

// Writer process (writer.c)
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include <fcntl.h>
union semun {
    int val;    /* value for SETVAL */
    struct semid_ds *buf;    /* buffer for IPC_STAT, IPC_SET */
    unsigned short *array;  /* array for GETALL, SETALL */
};
int main() {
    key_t key = ftok("shmfile", 65);
    int shmid = shmget(key, 1024, 0666|IPC_CREAT);
    char *str = (char*) shmat(shmid, (void*)0, 0);
    printf("Write Data : ");
    fgets(str, 1024, stdin);
    printf("Data written in memory: %s
", str);
    key_t semkey = ftok("semfile", 65);
    int semid = semget(semkey, 1, 0666|IPC_CREAT);
    union semun arg;
    arg.val = 1; // Binary semaphore, initialized to 1 (available)
    semctl(semid, 0, SETVAL, arg);
    printf("Semaphore initialized
");
    while(1) {
        struct sembuf lock = {0, -1, SEM_UNDO}; // P operation
        semop(semid, &lock, 1);
        printf("Enter data: ");
        fgets(str, 1024, stdin);
        str[strcspn(str, "
")] = '