共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
本质:还是先让不同的进程看到同一份资源。
所以,共享内存是Unix下的多进程之间的通信方法,允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。共享内存本质上是一块物理内存,多个进程通过将同一块物理内存都映射到自己的虚拟地址空间,通过自己的虚拟地址进行访问,实现数据间的共享。
如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。但是需要注意的是,共享内存并未提供锁机制,也就是说,在某一个进程对共享内存的进行读写的时候,不会阻止其它的进程对它的读写。如果要对共享内存的读/写加锁,可以使用信号灯。
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 */
};
Linux的共享内存数据结构是struct shmid_ds
,它定义了共享内存的一些重要属性。这个结构体包括以下字段:
struct ipc_perm shm_perm:
这个字段包含了操作权限,如读、写和执行等。
int shm_segsz:
这个字段表示共享内存段的字节大小。
__kernel_time_t shm_atime:
这个字段表示最后一次附加的时间。
__kernel_time_t shm_dtime:
这个字段表示最后一次分离的时间。
__kernel_time_t shm_ctime:
这个字段表示最后一次更改的时间。
__kernel_ipc_pid_t shm_cpid:
这个字段表示创建者的进程ID。
__kernel_ipc_pid_t shm_lpid:
这个字段表示最后一次操作共享内存的进程ID。
unsigned short shm_nattch:
这个字段表示当前附加到共享内存的进程数。
unsigned short shm_unused:
这个字段是用于兼容的,目前未使用。
这个结构体主要用于在Linux内核中描述POSIX共享内存(即System V共享内存)。在用户空间中,通常使用的是shmget(), shmat(), shmdat()等系统调用与内核的POSIX共享内存交互。
shmget函数
原型: 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,表示连接操作用来只读共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
原型: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资源必须删除,否则不会自动清除, 除非重启,所以system V IPC资源的生命周期随内核。
System V消息队列的原理是基于消息块的数据结构,可以在进程间传递数据。每个消息块都有一个类型和信息两部分构成。两个进程可以通过System V消息队列互相发送和接收消息。发送消息时,进程将数据块添加到消息队列的队尾,接收消息时,进程从消息队列的队头获取数据块。
需要注意的是,System V消息队列的资源必须自行删除,否则不会自动清除,因为System V IPC资源的生命周期是随内核的。同时,消息队列数据结构也可能会存在大量的消息队列,系统也必须为消息队列维护相关的内核数据结构。
System V信号量集(也称为信号量)的原理是基于一种数据操作锁(相当于资源计数器),用于同步和协调多个进程之间的数据交换。它自身不具备数据交换功能。
信号量的工作原理基于两种原子性操作:wait(p)和signal(v)。 当一个进程进行wait(p)操作时,如果信号量的值大于0,则该进程可以继续执行并使用一个资源,将信号量的值减1;如果信号量的值为0,则该进程会被阻塞,直到其他进程释放资源,使信号量的值大于0为止。当一个进程进行signal(v)操作时,如果信号量的值小于等于0,则该进程可以继续执行并使用一个资源,将信号量的值加1;如果信号量的值大于0,则进程不会得到资源,因为已有其他进程在使用资源。
信号量的作用是维护资源的互斥和多进程的同步访问,以确保在一个时间点只有一个进程可以访问共享资源,从而避免了多个进程同时访问共享资源而引起的竞争条件。 同时,信号量也可以用于控制多个进程之间的同步和通信。
由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。
系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
在进程中涉及到互斥资源的程序段叫临界区。
特性方面:IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。
makefile
.PHONY:all
all:server client
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
comm.h
#include
#include
#include
#include
#define PATHNAME "."
#define PROJ_ID 0x6666
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
#endif
comm.c
static int commShm(int size, int flags)
{
key_t _key = ftok(PATHNAME, PROJ_ID);
if(_key < 0)
{
perror("ftok");
return -1;
}
int shmid = 0;
if( (shmid = shmget(_key, size, flags)) < 0)
{
perror("shmget");
return -2;
}
return shmid;
}
int destroyShm(int shmid)
{
if(shmctl(shmid, IPC_RMID, NULL) < 0)
{
perror("shmctl");
return -1;
}
return 0;
}
int createShm(int size)
{
return commShm(size, IPC_CREAT|IPC_EXCL|0666);
}
int getShm(int size)
{
return commShm(size, IPC_CREAT);
}
server.c
#include "comm.h"
int main()
{
int shmid = createShm(4096);
char *addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(i++<26)
{
printf("client# %s\n", addr);
sleep(1);
}
shmdt(addr);
sleep(2);
destroyShm(shmid);
return 0;
}
client.c
#include "comm.h"
int main()
{
int shmid = getShm(4096);
sleep(1);
char *addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while(i<26)
{
addr[i] = 'A'+i;
i++;
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}