1) shmat() 将本地进程虚拟内存映射到共享段。必须为每个共享内存地址执行此转换,并且相对于 shm 访问的数量而言,这可能会带来很大的成本。在多线程应用程序中,不需要额外的转换:所有 VM 地址都被转换为物理地址,就像在不访问共享内存的常规进程中一样。
除了设置共享页面的初始成本(在调用的进程中填充页表)之外,与常规内存访问相比没有任何开销shmat()
- 在大多数 Linux 风格中,每 4KB 共享内存为 1 页(4 或 8 字节) .
无论页面是共享分配还是在同一进程中分配,(对于所有相关比较)都是相同的成本。
2)共享内存段必须由内核以某种方式维护。我不知道“以某种方式”在性能方面意味着什么,但是例如,当所有附加到 shm 的进程都被删除时,shm 段仍然处于启动状态,并且最终可以被新启动的进程重新访问。必须至少有一定程度的开销与内核在 shm 段的生命周期内需要检查的事情有关。
无论是否共享,每一页内存都附有一个“结构页”,其中包含有关该页的一些数据。其中一项是引用计数。当一个页面被分配给一个进程时[无论是通过“shmat”还是其他机制],引用计数都会增加。当它通过某种方式被释放时,引用计数会减少。如果递减计数为零,则页面实际上被释放 - 否则“不会再发生任何事情”。
与分配的任何其他内存相比,开销基本上为零。无论如何,相同的机制用于页面的其他目的-例如,您有一个也被内核使用的页面-并且您的进程死了,内核需要知道在内核释放该页面之前不要释放该页面以及用户进程。
创建“分叉”时也会发生同样的事情。当一个进程被分叉时,父进程的整个页表本质上被复制到子进程中,并且所有页面都是只读的。每当发生写入时,内核都会发生错误,从而导致该页面被复制 - 因此该页面现在有两个副本,并且执行写入的进程可以修改它的页面,而不会影响其他进程。一旦子(或父)进程死亡,当然所有页面仍然由两个进程拥有[例如从未被写入的代码空间,可能还有一堆从未被触及的公共数据等]显然不能释放,直到两个进程都“死”。再一次,引用计数的页面在这里很有用,因为我们只计算每个页面上的引用计数,
共享库也会发生完全相同的事情。如果一个进程使用共享库,它将在该进程结束时被释放。但是,如果两个、三个或 100 个进程使用同一个共享库,那么代码显然必须保留在内存中,直到不再需要该页面为止。
所以,基本上,整个内核中的所有页面都已经被引用计数了。开销很少。