进程间通讯有四种方式:信号量、管道、消息队列、共享内存
我们之前已着重介绍过信号量、管道。
现着重介绍一下共享内存。
共享内存
共享内存是最高效的IPC机制,因为它不涉及进程之间任何的数据传输。这种高效率带来的问题是,我们必须用其他辅助手段来同步进程对共享内存的访问,否则就会产生竞态条件。因此,共享内存通过和其他进程间通信方式一起使用。
Linux共享内存的API都定义在sys/shm.h头文件中,包括4个系统调用函数:shmget、shmat、shmdt和shmctl
shmget
shmget系统调用创建一段新的共享内存,或者获取一段已经存在的共享内存。请定义如下:
#include<sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
和semget系统调用一样,key参数是一个键值,用来表示一段全局唯一的共享内存。size参数指定共享内存的大小,单位是字节。如果是创建新的共享内存,则size值必须被指定。如果是获取已经存在的共享内存,则可以把size设置为0。
shmflg参数的使用和含义与semget系统调用的sem_flags参数相同。不过shmget支持两个额外的标志——SHM_HUGETLB和SHM_NORESERVE。它们的含义如下:
SHM_HUGETLB,类似于mmap的MAP_NORESERVE标志,系统将使用“大页面”来共享内存分配空间。
SHM_NORESERVE,类似于mmap的MAP_NORESERVE标志,不为共享内存保留交换分区(swap空间)。这样,当物理内存不足的时候,对该共享内存执行写操作将触发SIGSEGV信号。
这里sem_flags参数:它低端的9个比特是该信号量的权限,其格式和含义都与系统调用open的mode参数相同。此外,它还可以和IPC_CREAT标志做按位“或”运算以创建新的信号量集。此时即使信号量已经存在,semget也不会产生错误。我们还可以联合使用IPC_CREAT和IPC_EXCL标志来确保创建一组新的、唯一的信号量集。在这种情况下,如果信号量集已经存在,则semget返回错误并设置errno为EEXIST。这种创建信号量的行为与用O_CREAT和O_EXCL标志调用open来排他式地打开一个文件相似。
shmget成功时返回一个正整数值,它是共享内存的标识符。shmget失败时返回-1,并设置errno。
shmat & shmdt
共享内存被创建、共享之后,我们不能立即访问它,而是需要先将它关联到进程的地址空间中。使用完共享内存后,我们也需要先将它从进程的地址空间中分离。这两项任务分别由如下的两个系统调用实现:
#include<sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmdt(const void * shm_addr);
其中,shm_id参数是由shmget调用返回的共享内存标识符。shm_addr参数指定将共享内存关联到进程的哪块地址空间,最终的效果还受到shmflg参数的可选标志SHM_RND的影响:
除了SHM_RND标志以外,shmflg参数还支持如下标志:
shmat成功时返回共享内存被关联到的地址,失败则返回(void *)-1 并设值为errno。shmat成功时,将修改内核数据结构shmid_ds的部分字段,如下:
1.将shm_nattach加1
2.将shm_Ipid设置为调用进程的PID
3.将shm_atime设置为当前的时间
shmdt函数将关联到shm_addr处的共享内存从进程空间中分离。它成功时返回0,失败时则返回-1并设置errno。shmdt在成功调用时将修改内核数据结构shmid_ds的部分字段,如下:
1.将shm_nattach减1
2.将shm_Ipid设置为调用进程的PID
3.将shm_dtime设置为当前的时间
shmctl
#include<sys/shm.h>
int shmctl(int shm_id, int command, struct shmid_ds* buf);
其中,shmid_id参数是由shmget调用返回的共享内存标识符。command参数指定要执行的命令,shmctl支持的所有命令如下表所示:
shmctl成功时的返回值取决于command参数,shmctl失败时返回-1,并设置errno。
这里,将共享内存和信号量结合起来进行进程间通讯,这里实现的是在进程中传递数据,并可以从大写字母转换成小写字母:
代码实现
sem.h
#ifndef __SEM_H
#define __SEM_H
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
typedef union semun
{
int val;
}semun;
int sem_init(int key);
void sem_p(int id,int sem);
void sem_v(int id,int sem);
void sem_del(int id);
#endif
sem.c
#include"sem.h"
int sem_init(int key)
{
int semid;
semid=semget((key_t)key,0,0666);
if(semid==-1)
{
semid=semget((key_t)key,1,0666 | IPC_CREAT);
semun mu;
mu.val=1;
semctl(semid,0,SETVAL,mu);
}
return semid;
}
void sem_p(int id,int sem)
{
struct sembuf buff;
buff.sem_num=sem;
buff.sem_op=-1;
buff.sem_flg=SEM_UNDO;
semop(id,&buff,1);
}
void sem_v(int id,int sem)
{
struct sembuf buff;
buff.sem_num=sem;
buff.sem_op=1;
buff.sem_flg=SEM_UNDO;
semop(id,&buff,1);
}
void sem_del(int id)
{
semctl(id,0,IPC_RMID);
}
shma.c
#include"sem.h"
#include<sys/shm.h>
//因为共享内存中可能存在的竟态条件,因此共享内存需要和其他进程间通讯方式一起使用
int main()
{
int shmid=shmget((key_t)1234,512,0666 | IPC_CREAT);
assert(shmid!=-1);
char *ptra=(char *)shmat(shmid,NULL,0); //共享内存被创建后,我们不能立即访问它,而是需要现将它从关联到进程的地址空间中分离
assert(ptra!=(char *)-1);
int semid=sem_init(123);
while(1)
{
sem_p(semid,0);
printf("Put sentence:");
fflush(stdout);
memset(ptra,0,512);
fgets(ptra,128,stdin);
ptra[strlen(ptra)-1]=0;
if((strcmp(ptra,"end")==0) || (strcmp(ptra,"END")==0))
{
break;
}
sem_v(semid,0);
}
shmdt(ptra);
return 0;
}
shmb.c
#include"sem.h"
#include<sys/shm.h>
int main()
{
int shmid=shmget((key_t)1234,512,0666 | IPC_CREAT);
assert(shmid!=-1);
char *ptrb=(char *)shmat(shmid,NULL,0);
assert(ptrb!=(char *)-1);
int semid=sem_init(123);
while(1)
{
sem_p(semid,0);
if((strcmp(ptrb,"end")==0) || (strcmp(ptrb,"END")==0))
{
break;
}
int i=0;
for(;i<strlen(ptrb);i++)
{
if(ptrb[i]>='A' && ptrb[i]<='Z') //将大写字符转换成小写字符
{
ptrb[i]=ptrb[i]+32;
}
printf("%c\n",ptrb[i]); //观看效果
sleep(1);
}
printf("%s\n",ptrb);
sem_v(semid,0);
}
shmdt(ptrb);
return 0;
}