本文详解了通过共享内存进行进程间通信的方法,并对消息队列,信号量做了简单介绍。
另一种进程间通信--管道,见前文:[OS-Linux]详解Linux的进程间通信1------管道_RMA515T的博客-CSDN博客
管道通信本质是基于文件,OS没有过多的设计。
system V进程间通信是OS特地设计的通信方式,让不同进程看到同一份资源。system V进程间通信包括了共享内存、消息队列、信号量。共享内存与消息队列以传输数据为目的,信号量则是保证进程的同步与互斥设计的,属于通信的范畴。
目录
一、system V共享内存
二、共享内存原理
1. 共享内存的通信原理
2. 共享内存数据结构
3.共享内存的建立过程
三、共享内存函数
1.shmget函数
2.shmat函数
3.shmdt函数
4. shmctl函数
三、共享内存实例
makefile
server.c
client.c
结果
四、system V消息队列
五、system V信号量
1.进程互斥
总结
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
共享内存并未提供同步机制,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。
共享内存示意图
每个进程都有进程控制块(PCB)和地址空间(Addr Space),通过页表对应,负责虚拟地址与物理地址映射,通过内存管理单元(MMU)管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。
当两个进程通过页表将虚拟地址映射到物理地址时,共享内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但要确保一个进程在写的时候不能被读,所以通过信号量来实现同步与互斥。
共享内存实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
(1)申请共享内存(物理内存已经开辟好了);
(2)共享内存挂接到地址空间(建立映射关系);
(3)去关联共享内存(修改页表,取消映射关系);
(4)释放共享内存(内存归还给系统)。
功能:用来创建共享内存
原型:
int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
功能:将共享内存段连接到进程地址空间
原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid:共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
另外:
shmaddr为NULL,核心自动选择一个地址。
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA整数倍。公式:shmaddr -(shmaddr % SHMLBA)。
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。
功能:将共享内存段与当前进程脱离
原型:
int shmdt(const void *shmaddr);
参数:shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
功能:用于控制共享内存
原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
命令 | 说明 |
---|---|
IPC_STAT | 把shmid_ds结构中的数据设置为共享内存的当前关联值 |
IPC_SET | 在进程有足够的权限的前提下,把共享内存当前关联值设置为shmid_ds数据结构中给出的值 |
IPC_RMID | 删除共享内存段 |
server通过共享内存给client发送数据。、
ftok函数:把一个已经存在的路径名和一个整数标识得转换成一个key_t值,称为IPC键
.PHONY:all
all:client server
client: client.c
gcc -o $@ $^
server: server.c
gcc -o $@ $^
.PHONY:clear
clear:
rm client server
#include
#include
#include
#include
#define SIZE 4096
#define PATHNAME "/home/Zht/Linux/SHM"
#define PROJ_ID 0x66
int main()
{
key_t k = ftok(PATHNAME, PROJ_ID);
if(k < 0){
perror("ftok");
return 1;
}
int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0644); //开辟
if(shmid < 0){
perror("shmget");
return 2;
}
char *mem = shmat(shmid, NULL, 0); //挂接
int i = 0;
while(1)
{
mem[i] = 'A' + i;
i++;
sleep(1);
mem[i] = '\0';
}
shmdt(mem);
//shmctl(shmid, IPC_RMID, NULL); //释放
return 0;
}
#include
#include
#include
#include
#define SIZE 4096
#define PATHNAME "/home/Zht/Linux/SHM"
#define PROJ_ID 0x66
int main()
{
key_t k = ftok(PATHNAME, PROJ_ID);
if(k < 0){
perror("ftok");
return 1;
}
int shmid = shmget(k, SIZE, IPC_CREAT); //开辟
if(shmid < 0){
perror("shmget");
return 2;
}
char *mem = shmat(shmid, NULL, 0); //挂接
int i = 0;
while(1)
{
printf("server meg# %s\n", mem); //读取
sleep(1);
}
shmdt(mem);
//shmctl(shmid, IPC_RMID, NULL); //释放
return 0;
}
运行时
正常接收
终止后挂接为0。
消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。
每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值特性方面。
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。
信号量主要用于同步和互斥的。
由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。
系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
进程中涉及到互斥资源的程序段叫临界区。
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。
进程间的通信主要分为管道和system V,管道有可分为匿名管道和命名管道;system V进程间通信包括了共享内存、消息队列、信号量。两篇文章对他们进行了介绍。