共享内存是最高效的IPC机制,因为它不涉及进程之间的任何数据传输。这种高效带来的问题是,我们必须用其他手段来同步进程对共享内存的访问,否则会产生竞态条件。所以,共享内存通常和其他进程间通信方式一起使用。
linux 共享内存有四个系统调用:shmget, shmat, shmdt, shmctl
shmget
创建一段新的共享内存,或者获取一段已经存在的共享内存:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key 是一个键值,用来标识一段全局唯一的共享内存。size 指定内存的大小,单位是字节。如果是创建内存,size必须指定,如果是获取已经存在的内存,size 可以设置为0
shmflg 和 semget的 semflg 参数相同。
shmget成功时返回一个正整数,它是共享内存的标识符。失败时返回-1,并设置errno.
shmat
共享内存被创建/获取之后,并不能立即访问它,而是需要先将它关联到进程的地址空间中。使用shmat函数:
void *shmat(int shmid, const void *shmaddr, int shmflg)
shmid 参数是有shmget函数返回的标识符。shmaddr指定将共享内存关联到进程的哪块地址空间,如果为NULL,则被关联地址由操作系统选择。
成功时,返回共享内存被关联到的地址,失败则返回(void*)-1,并设置errno
shmdt
int shmdt(const void *shmaddr);
将关联的共享内存从进程分离。
成功返回0,失败返回-1,并设置errno
shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shmid 使用shmget返回的共享内存标识符。cmd指定执行的命令,常用的 IPC_RMID,表示将共享内存打上删除的标记,当最后一个使用它的进程调用shmdt将它从进程中分离时,该共享内存就被删除了。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/sem.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 */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void ErrExit(const char* reason)
{
fprintf(stderr, "%s: %d, %s
", reason, errno, strerror(errno));
exit(1);
}
int initsem(int key = 0)
{
int semid = -1;
if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT)))
{
ErrExit("semget");
}
union semun un;
un.val = 2;
if (-1 == semctl(semid, 0, SETVAL, un))
{
ErrExit("semctl");
}
return semid;
}
void destroysem(int semid)
{
if (-1 == semctl(semid, 0, IPC_RMID))
{
ErrExit("semctl del");
}
}
void P(int semid)
{
struct sembuf op;
op.sem_num = 0;
op.sem_op = -1;
op.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &op, 1))
{
ErrExit("semop p");
}
}
void V(int semid)
{
struct sembuf op;
op.sem_num = 0;
op.sem_op = 1;
op.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &op, 1))
{
ErrExit("semop v");
}
}
void waitfor0(int semid)
{
struct sembuf op;
op.sem_num = 0;
op.sem_op = 0;
op.sem_flg = SEM_UNDO;
if (-1 == semop(semid, &op, 1))
{
ErrExit("semop wait");
}
}
int main(int argc, char const *argv[])
{
int semid = initsem();
int shmid = -1;
int pipefd[2];
// 管道用来传递 共享内存id
pipe(pipefd);
pid_t pid = fork();
if (pid > 0)
{
P(semid);
close(pipefd[0]);
printf("parent process...
");
shmid = shmget(0, 1024, 0666 | IPC_CREAT);
if (-1 == shmid)
{
ErrExit("shmget");
}
write(pipefd[1], &shmid, 4);
void* p = shmat(shmid, NULL, 0);
if ((void*)-1 == p)
{
ErrExit("shmat");
}
char buf[60] = "data from parent";
memcpy(p, buf, strlen(buf));
printf("exit parent process...
");
P(semid);
// P(semid);
waitpid(pid, NULL, 0);
destroysem(semid);
if (-1 == shmctl(shmid, IPC_RMID, NULL))
{
ErrExit("shmctl");
}
if (-1 == shmdt(p))
{
ErrExit("shmdt in parent");
}
}
else if (0 == pid)
{
// 等待共享内存集的值变为0
waitfor0(semid);
close(pipefd[1]);
printf("child process...
");
read(pipefd[0], &shmid, 4);
void* p = shmat(shmid, NULL, 0);
if ((void*)-1 == p)
{
ErrExit("shmat");
}
char buf[60] = {0};
memcpy(buf, p, 60);
printf("%s
", buf);
if (-1 == shmdt(p))
{
ErrExit("shmdt in parent");
}
// V(semid);
}
return 0;
}