消息队列、共享内存、信号量的机制:它们在使用之前都要生成 key,然后通过 key 得到唯一的 id,并且都是通过 xxxget 函数。在内核里面,这三种进程间通信机制是使用统一的机制管理起来的,都叫 ipcxxx。为了维护这三种进程间通信进制,在内核里面,我们声明了一个有三项的数组。
通过这段代码,来具体看一看。
struct ipc_namespace {
......
struct ipc_ids ids[3];
......
}
#define IPC_SEM_IDS 0
#define IPC_MSG_IDS 1
#define IPC_SHM_IDS 2
#define sem_ids(ns) ((ns)->ids[IPC_SEM_IDS])
#define msg_ids(ns) ((ns)->ids[IPC_MSG_IDS])
#define shm_ids(ns) ((ns)->ids[IPC_SHM_IDS])
根据代码中的定义,第 0 项用于信号量,第 1 项用于消息队列,第 2 项用于共享内存,分别可以通过 sem_ids、msg_ids、shm_ids 来访问。
这段代码里面有 ns,全称叫 namespace。可能不容易理解,你现在可以将它认为是将一台 Linux 服务器逻辑的隔离为多台 Linux 服务器的机制,它背后的原理是一个相当大的话题,我们需要在容器那一章详细讲述。现在,你就可以简单的认为没有 namespace,整个 Linux 在一个 namespace 下面,那这些 ids 也是整个 Linux 只有一份。
共享内存的创建和映射过程。
- 调用 shmget 创建共享内存。
- 先通过 ipc_findkey 在基数树中查找 key 对应的共享内存对象 shmid_kernel 是否已经被创建过,如果已经被创建,就会被查询出来,例如 producer 创建过,在 consumer 中就会查询出来。
- 如果共享内存没有被创建过,则调用 shm_ops 的 newseg 方法,创建一个共享内存对象 shmid_kernel。例如,在 producer 中就会新建。
- 在 shmem 文件系统里面创建一个文件,共享内存对象 shmid_kernel 指向这个文件,这个文件用 struct file 表示,我们姑且称它为 file1。
- 调用 shmat,将共享内存映射到虚拟地址空间。
- shm_obtain_object_check 先从基数树里面找到 shmid_kernel 对象。
- 创建用于内存映射到文件的 file 和 shm_file_data,这里的 struct file 我们姑且称为 file2。
- 关联内存区域 vm_area_struct 和用于内存映射到文件的 file,也即 file2,调用 file2 的 mmap 函数。
- file2 的 mmap 函数 shm_mmap,会调用 file1 的 mmap 函数 shmem_mmap,设置 shm_file_data 和 vm_area_struct 的 vm_ops。
- 内存映射完毕之后,其实并没有真的分配物理内存,当访问内存的时候,会触发缺页异常 do_page_fault。
- vm_area_struct 的 vm_ops 的 shm_fault 会调用 shm_file_data 的 vm_ops 的 shmem_fault。
- 在 page cache 中找一个空闲页,或者创建一个空闲页。