Linux共享内存与信号量
Linux中的共享内存和信号量是两种强大的进程间通信(IPC)机制,它们在多进程编程中发挥着至关重要的作用,共享内存允许多个进程直接访问同一块物理内存,而信号量则用于同步对这些共享资源的访问,本文将详细介绍这两种机制的工作原理、使用方法以及它们之间的配合使用。
一、共享内存
共享内存是一种高效的进程间通信方式,它允许多个进程直接读写同一块内存区域,从而避免了数据在进程间的复制,这种方式特别适用于需要频繁交换大量数据的场景。
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. 分离与删除共享内存
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
:信号量集中的信号量编号。
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, " ")] = '\0'; // Remove newline character printf("Data written in memory: %s ", str); struct sembuf unlock = {0, 1, SEM_UNDO}; // V operation semop(semid, &unlock, 1); if (strcmp(str, "exit") == 0) { break; } } shmdt(str); shmctl(shmid, IPC_RMID, NULL); semctl(semid, 0, IPC_RMID, NULL); return 0; }
// Reader process (reader.c) #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h.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("Read Data : %s ", str); key_t semkey = ftok("semfile", 65); int semid = semget(semkey, 1, 0666|IPC_CREAT); union semun arg; arg.val = 0; // Initialize semaphore to 0 (not available) semctl(semid, 0, SETVAL, arg); printf("Semaphore initialized "); while(1) { struct sembuf lock = {0, -1, SEM_UNDO}; // P operation semop(semid, &lock, 1); if (strlen(str) > 0) { printf("Data read from memory: %s ", str); } else { printf("No data to read "); } struct sembuf unlock = {0, 1, SEM_UNDO}; // V operation semop(semid, &unlock, 1); sleep(1); // Simulate processing time if (strcmp(str, "exit") == 0) { break; } } shmdt(str); shmctl(shmid, IPC_RMID, NULL); semctl(semid, 0, IPC_RMID, NULL); return 0; }
在这个示例中,写进程首先将数据写入共享内存,并通过信号量通知读进程数据已准备好,读进程等待信号量变为可用状态后,读取共享内存中的数据并输出,当写进程写入“exit”字符串时,两个进程都会退出并清理资源,这种机制确保了数据的一致性和进程间的同步。