当前位置:首页>Linux>深入理解Linux共享内存:从映射原理到底层实现

深入理解Linux共享内存:从映射原理到底层实现

  • 2026-01-31 19:13:02
深入理解Linux共享内存:从映射原理到底层实现

在Linux进程间通信(IPC)的诸多方案中,共享内存始终以“极致高效”的标签占据特殊地位。相较于管道、消息队列等需要频繁在用户态与内核态之间拷贝数据的机制,共享内存直接让多个进程访问同一块物理内存区域,彻底规避了数据拷贝的性能损耗,成为高并发、大数据量场景下的优选方案。但高效的背后,是内核对内存资源的精密调度与进程地址空间的巧妙映射。

不少开发者在实际应用中,往往只关注API的调用,却对“共享内存如何被内核创建与管理”“进程如何将共享内存映射到自身虚拟地址空间”“写时复制(COW)等机制如何保障内存高效利用”等底层逻辑一知半解,这也导致在面对复杂场景的性能优化或问题排查时难以突破瓶颈。本文将从底层逻辑出发,逐层拆解Linux共享内存的实现机制:从内核数据结构的管理逻辑,到虚拟内存与物理内存的映射原理,再到核心机制的运行细节,带你跳出API的表层认知,真正理解共享内存高效运行的底层密码,为后续的深度应用与性能优化筑牢基础。

一、什么是进程间通信(IPC)

1.1进程的独立性与隔离性

在 Linux 系统中,进程是资源分配和调度的基本单位 ,其设计初衷是相互独立。每个进程都拥有自己独立的地址空间,就像是一个个独立的小世界,彼此之间相互隔离。这意味着一个进程无法直接访问另一个进程的内存空间、文件描述符等资源。例如,当我们在系统中同时运行浏览器和文本编辑器这两个进程时,浏览器进程不能直接读取或修改文本编辑器进程正在处理的文档数据,反之亦然。这种独立性和隔离性为系统的稳定性和安全性提供了保障,一个进程的崩溃或异常不会轻易影响到其他进程的正常运行 。不清楚进程间通信,请参考这篇一文吃透Linux进程间通信,告别“社恐”进程!》。

1.2 IPC 的必要性

然而,在实际的应用场景中,进程之间往往需要相互协作来完成复杂的任务。比如,在一个服务器程序中,可能有多个进程分别负责处理不同的业务逻辑,如一个进程负责接收客户端的请求,另一个进程负责从数据库中读取数据并返回给客户端。为了解决进程之间由于隔离性导致的无法直接交互的问题,就需要一种机制来实现进程间的数据交换、资源共享和工作协调,这就是进程间通信(IPC,Inter - Process Communication)机制存在的必要性 。

1.3常见 IPC 方式简介

Linux 系统提供了多种常见的 IPC 方式,每种方式都有其特点和适用场景:

  1. 管道(Pipe):包括匿名管道和命名管道。匿名管道通常用于具有亲缘关系的进程之间,如父子进程,它是一种半双工的通信方式,数据只能单向流动。例如,在 Shell 脚本中,我们可以使用ls | grep "test"这样的命令,其中|就是匿名管道,ls命令的输出通过管道作为grep命令的输入。命名管道则突破了亲缘关系的限制,可以在任意进程之间通信,它在文件系统中以特殊文件的形式存在 。
  2. 消息队列(Message Queue):进程可以向消息队列中发送消息,也可以从消息队列中读取消息。消息队列中的消息具有一定的格式和优先级,它可以实现异步通信,比如一个进程发送消息后不需要等待另一个进程立即处理 。
  3. 信号量(Semaphore):主要用于控制多个进程对共享资源的访问,它本质上是一个计数器,通过对计数器的操作来实现对资源的同步访问。例如,当多个进程需要访问同一个文件时,可以使用信号量来确保同一时间只有一个进程能够对文件进行写操作,避免数据冲突 。
  4. 共享内存(Shared Memory):多个进程可以共享同一块内存区域,这使得它们可以直接读写这块内存中的数据,无需进行数据复制,因此是一种高效的 IPC 方式,特别适合需要频繁进行大量数据传输和共享的场景 。
  5. 信号(Signal):是一种异步通知机制,用于通知进程发生了某种特定事件,如用户按下Ctrl+C组合键会向当前进程发送SIGINT信号,通知进程终止运行 。

在这些常见的 IPC 方式中,共享内存的优势十分明显。与管道和消息队列相比,共享内存不需要在进程间复制数据,减少了数据传输的开销,大大提高了通信效率;和信号量相比,共享内存更侧重于数据的共享,而信号量主要用于同步控制。在大数据处理、实时通信等对数据传输效率要求极高的场景中,共享内存的高性能特点使其成为首选的 IPC 方式 。

二、Linux 共享内存初相识

2.1 定义与概念

共享内存,简单来说,就是一段被多个进程共同映射到各自地址空间的物理内存区域。在 Linux 系统中,每个进程都有自己独立的虚拟地址空间,正常情况下,它们无法直接访问其他进程的内存 。但共享内存打破了这种限制,通过操作系统的特殊映射机制,让多个进程能够看到并访问同一块物理内存。

举个形象的例子,假如每个进程都是一个独立的房间,进程的地址空间就是房间里摆放物品的空间。而共享内存就像是一个大家都可以进入的公共储物间,不同房间(进程)的人都能直接进去存放和取用物品(数据)。在一个多进程的视频编辑软件中,视频解码进程可以将解码后的视频帧数据直接存入共享内存,然后视频特效处理进程和视频合成进程都能从这个共享内存中读取这些视频帧数据,进行后续的处理 。

对于上图我的理解是:当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。

2.2 为什么共享内存是最快的 IPC 方式

在所有的 IPC 方式中,共享内存之所以速度最快,其核心原因在于数据的读写方式 。以管道通信为例,当一个进程向管道写入数据时,数据需要从用户空间复制到内核空间的管道缓冲区;而另一个进程从管道读取数据时,又要将数据从内核空间的管道缓冲区复制回用户空间 。这个过程涉及两次数据拷贝,会消耗较多的时间和系统资源。

而共享内存则不同,多个进程直接对同一块物理内存进行读写操作,无需在用户空间和内核空间之间进行数据拷贝。这就好比在接力比赛中,传统的 IPC 方式就像是运动员要把接力棒先交给裁判(内核),再由裁判转交给下一个运动员,而共享内存则是运动员之间直接交接接力棒,大大减少了数据传输的中间环节,从而显著提高了通信效率 。在实时图像识别系统中,图像采集进程将采集到的图像数据直接存入共享内存,图像识别进程可以立即从共享内存中读取数据进行分析,几乎没有数据传输的延迟,满足了系统对实时性的高要求 。

2.3共享内存与其他 IPC 方式的比较

  • 通信速度:与管道和消息队列相比,共享内存无需数据拷贝,速度优势明显。消息队列在数据传输时,需要在用户态和内核态之间进行多次数据复制,并且消息队列的管理也会带来一定的开销 。而共享内存直接在物理内存上进行操作,数据传输几乎是瞬间完成的,在大数据量、高频次的数据传输场景下,共享内存的速度优势更加突出 。
  • 数据量:共享内存适合传输大量数据,因为它可以直接访问物理内存,不受消息大小等限制。而管道和消息队列通常会有数据大小的限制,比如管道的缓冲区大小是有限的,消息队列中的每条消息也有最大长度限制 。在处理大型数据库查询结果的共享时,共享内存可以轻松应对大量数据的传输,而消息队列可能需要多次传输或者对数据进行拆分,增加了复杂性 。
  • 同步机制:共享内存本身不提供同步机制,需要用户自己实现同步,比如使用信号量来控制对共享内存的访问,防止数据冲突 。而管道和消息队列在一定程度上提供了同步机制,例如管道的读写操作是同步的,消息队列的发送和接收操作也有相应的同步控制 。在多进程并发访问共享内存时,如果没有正确的同步机制,就可能出现数据不一致的问题,这也是使用共享内存时需要特别注意的地方 。

从这些方面的比较可以看出,共享内存以其高效的数据传输能力,在对通信速度和数据量要求较高的场景中具有独特的优势 。

三、共享内存的映射原理

3.1虚拟地址与物理地址映射基础

在深入了解共享内存的映射原理之前,我们先来回顾一下虚拟地址与物理地址的映射基础 。在现代操作系统中,为了实现进程的独立性和内存的有效管理,每个进程都被分配了一个独立的虚拟地址空间 。以 32 位操作系统为例,虚拟地址空间的大小为 2^32 字节,即 4GB。在这个虚拟地址空间中,进程可以自由地访问内存,就好像它拥有整个物理内存一样 。

但实际上,物理内存的大小往往远小于虚拟地址空间的大小,并且多个进程需要共享物理内存 。为了实现虚拟地址到物理地址的转换,操作系统引入了页表(Page Table)这一关键数据结构 。页表是一个存储在内存中的表格,它记录了虚拟地址与物理地址之间的映射关系 。

具体来说,虚拟地址空间被划分为一个个固定大小的页(Page),在 Linux 系统中,常见的页大小为 4KB 。同样,物理内存也被划分为大小相同的页帧(Page Frame) 。页表中的每一项称为页表项(Page Table Entry,PTE),每个页表项对应一个虚拟页,其中记录了该虚拟页对应的物理页帧的地址,以及一些访问权限标志,如可读、可写、可执行等 。当进程访问一个虚拟地址时,CPU 会首先根据虚拟地址的页号(虚拟地址的高位部分)在页表中查找对应的页表项 。如果找到有效的页表项,就可以从中获取到对应的物理页帧地址,再结合虚拟地址的页内偏移(虚拟地址的低位部分),就能得到最终的物理地址,从而完成内存访问 。如果页表项无效,说明该虚拟地址对应的物理内存尚未分配或不在内存中,这时会触发缺页中断,操作系统会进行相应的处理,如分配物理内存、从磁盘交换空间中读取数据等 。

在内核中,共享内存的管理依赖于一系列的数据结构,其中最重要的是shmid_ds和ipc_perm。shmid_ds结构体用于描述一个共享内存段的相关信息,其定义大致如下(不同 Linux 内核版本可能略有差异):

struct shmid_ds {    struct ipc_perm shm_perm;  /* 共享内存的权限和所有者信息 */    size_t          shm_segsz; /* 共享内存段的大小,以字节为单位 */    time_t          shm_atime; /* 最后一次被attach的时间 */    time_t          shm_dtime; /* 最后一次被detach的时间 */    time_t          shm_ctime; /* 共享内存段的创建或最后一次修改时间 */    pid_t           shm_cpid;  /* 创建该共享内存段的进程ID */    pid_t           shm_lpid;  /* 最后一次对共享内存段执行操作(attach或detach)的进程ID */    shmatt_t        shm_nattch;/* 当前连接到该共享内存段的进程数 */    ...};
  • shm_perm:是一个ipc_perm类型的结构体,用于描述共享内存的权限和所有者信息,包括所有者 ID、组 ID、权限标志等,类似于文件的权限管理方式,通过这些信息来控制哪些进程可以访问该共享内存段以及具有何种访问权限 。
  • shm_segsz:记录了共享内存段的大小,内核根据这个大小来分配和管理物理内存。
  • shm_atime、shm_dtime和shm_ctime:分别记录了共享内存段的最后一次被 attach、detach 以及创建或最后一次修改的时间,这些时间戳对于系统的监控和调试很有帮助,比如可以通过这些时间来判断共享内存的使用活跃度和状态变化。
  • shm_cpid和shm_lpid:分别保存了创建该共享内存段的进程 ID 和最后一次对共享内存段执行操作(attach 或 detach)的进程 ID,这有助于跟踪共享内存的创建和使用历史,在排查问题时可以了解到是哪些进程在操作共享内存。
  • shm_nattch:表示当前连接到该共享内存段的进程数,这是一个非常重要的计数,当这个计数变为 0 时,内核知道没有进程在使用该共享内存段了,就可以考虑释放相关的资源,比如物理内存等 。

ipc_perm结构体定义如下:

struct ipc_perm {    key_t          key;    /* 共享内存的键值,用于唯一标识共享内存段 */    uid_t          uid;    /* 所有者的用户ID */    gid_t          gid;    /* 所有者的组ID */    uid_t          cuid;   /* 创建者的用户ID */    gid_t          cgid;   /* 创建者的组ID */    unsigned short mode;   /* 共享内存的访问权限,类似文件权限 */    unsigned short seq;    /* 序列号,用于标识共享内存对象的版本 */};

通过 ipc_perm 结构体,内核可以有效地管理共享内存的访问权限,确保只有授权的进程能够访问共享内存,并且可以根据所有者和组的权限设置来限制不同进程对共享内存的读写操作。例如,如果一个进程不是共享内存的所有者,且没有被授予相应的读权限,那么当它尝试读取共享内存时,内核会根据ipc_perm中的权限设置拒绝该操作,并返回权限不足的错误。

3.2共享内存的映射机制

共享内存的映射机制是将物理内存中的共享内存段映射到进程的虚拟地址空间,使得进程可以像访问普通内存一样访问共享内存。这个过程涉及到内存管理单元(MMU,Memory Management Unit)和页表(Page Table)的协同工作。

在 Linux 系统中,每个进程都有自己独立的虚拟地址空间,当进程调用shmat函数将共享内存映射到自身地址空间时,内核会进行以下操作:

  1. 查找共享内存段:内核首先根据shmat函数传入的共享内存标识符shmid,在系统维护的共享内存段列表中查找对应的shmid_ds结构体,从而获取共享内存段的相关信息,包括物理内存地址、大小等。
  2. 分配虚拟地址空间:内核为共享内存分配一段虚拟地址空间,通常会选择一个合适的空闲区域。如果用户在调用shmat时指定了shmaddr(一般为NULL,表示由系统自动选择),内核会尝试将共享内存映射到指定地址,但需要确保该地址符合内存对齐等要求,并且不会与进程已有的虚拟地址空间冲突。如果指定地址不可用,内核会重新选择一个合适的地址。
  3. 建立页表映射:确定好虚拟地址后,内核通过 MMU 建立虚拟地址与共享内存物理地址之间的映射关系,这一过程涉及到修改进程的页表。页表是一个数据结构,用于存储虚拟地址到物理地址的映射信息,每个进程都有自己的页表。对于共享内存,内核会在进程的页表中添加相应的表项,将虚拟地址与共享内存的物理页帧号(PFN,Page Frame Number)关联起来。例如,如果共享内存的物理地址为0x10000000,大小为 4KB(一个内存页的大小),内核为其分配的虚拟地址为0x20000000,那么内核会在进程的页表中添加一个表项,使得虚拟地址0x20000000到0x20000fff(4KB 的地址范围)映射到物理地址0x10000000到0x10000fff。
  4. 设置访问权限:根据shmid_ds中shm_perm结构体的权限设置以及shmat函数传入的shmflg标志(如SHM_RDONLY表示只读访问),内核设置页表中对应表项的访问权限位,确保进程对共享内存的访问符合权限要求。例如,如果权限设置为只读,那么页表项中的写权限位会被清除,当进程尝试写入共享内存时,MMU 会检测到权限错误,并触发异常,由内核进行处理,通常会返回权限不足的错误信息给进程。

通过以上步骤,共享内存成功地映射到了进程的虚拟地址空间,进程就可以通过访问虚拟地址来读写共享内存中的数据,就像访问普通内存一样,而无需进行额外的数据拷贝或系统调用,大大提高了数据访问的效率。

3.3内核与用户态的交互

用户态进程通过系统调用(如shmget、shmat、shmdt、shmctl等)与内核交互来实现共享内存的操作。系统调用是用户态进程进入内核态执行的一种机制,它提供了用户态和内核态之间的桥梁。以shmget系统调用为例,当用户态进程调用shmget时,会发生以下过程:

  1. 陷入内核态:用户态进程执行shmget函数,这会触发一个软中断(通常是int 0x80或syscall指令,具体取决于系统架构),使得 CPU 从用户态切换到内核态,开始执行内核中的系统调用处理程序。
  2. 参数传递:用户态进程在调用shmget时传入的参数(如key、size、shmflg等)会通过特定的寄存器或栈传递给内核。内核根据这些参数进行共享内存的创建或获取操作。例如,内核会检查key值是否合法,判断共享内存是否已经存在,如果需要创建新的共享内存段,会根据size参数分配相应大小的物理内存,并创建shmid_ds等相关数据结构来管理该共享内存段。
  3. 内核处理:内核中的共享内存管理模块会根据传入的参数执行相应的操作。如果是创建共享内存,会在系统的共享内存段列表中添加新的shmid_ds结构体,并初始化相关字段,如设置共享内存的大小、权限、创建时间等。如果是获取已存在的共享内存段,会查找对应的shmid_ds结构体,并返回其标识符shmid给用户态进程。
  4. 返回用户态:内核完成共享内存的创建或获取操作后,会将结果(成功时返回shmid,失败时返回错误码)通过寄存器或栈传递回用户态进程,然后通过中断返回指令将CPU从内核态切换回用户态,用户态进程继续执行后续的代码,根据返回结果进行相应的处理,比如检查返回值是否为-1来判断操作是否成功,如果失败则根据错误码进行错误处理 。

同样,对于shmat、shmdt和shmctl等系统调用,用户态进程也是通过类似的方式与内核交互,内核根据不同的系统调用功能进行相应的处理,如shmat负责将共享内存映射到进程地址空间,shmdt负责分离共享内存,shmctl负责执行各种控制操作(如删除共享内存段、获取或设置共享内存属性等)。这种内核与用户态的交互机制,保证了共享内存的安全、高效管理和使用,使得用户态进程能够方便地利用共享内存进行进程间通信。

四、Linux共享内存的实现机制

4.1共享内存在内核中的数据结构

在 Linux 内核中,共享内存的管理依赖于一系列精心设计的数据结构,其中struct shmid_ds和struct shmid_kernel是两个关键的结构体 。

(1)struct shmid_ds主要用于存储用户可见的共享内存元信息 ,其定义如下:

struct shmid_ds {    struct ipc_perm shm_perm; /* 操作权限,包含UID、GID、模式等 */    size_t shm_segsz;         /* 共享内存段的大小(字节) */    time_t shm_atime;         /* 最后一次映射时间 */    time_t shm_dtime;         /* 最后一次解除映射时间 */    time_t shm_ctime;         /* 最后一次修改时间 */    pid_t shm_cpid;           /* 创建者的PID */    pid_t shm_lpid;           /* 最后一次操作者的PID */    unsigned short shm_nattch;/* 当前映射到该共享内存段的进程数 */    // 一些兼容性保留字段    unsigned short shm_unused;     void *shm_unused2;     void *shm_unused3; };
  • shm_perm:这个子结构体用于控制共享内存的访问权限 ,它包含了所有者的用户 ID(UID)、组 ID(GID)以及访问模式(如可读、可写权限) 。例如,通过设置shm_perm的权限位,可以限制只有特定用户或用户组能够访问共享内存,保证了数据的安全性 。
  • shm_segsz:明确指定了共享内存段的大小,以字节为单位 。在创建共享内存时,这个值决定了分配的物理内存空间大小,确保了数据有足够的存储区域 。在一个图像共享的应用中,如果需要共享高分辨率图像数据,就需要根据图像的大小来合理设置shm_segsz,以容纳完整的图像信息 。
  • shm_atime、shm_dtime和shm_ctime:分别记录了共享内存最后一次被映射、解除映射和修改的时间 。这些时间戳对于系统的监控和调试非常有用,比如可以通过对比shm_atime和shm_dtime来了解共享内存的使用频率和时长 。
  • shm_cpid和shm_lpid:记录了共享内存的创建者和最后一次操作它的进程的 PID 。这有助于追踪共享内存的创建和操作历史,在多进程复杂环境中,方便定位问题和管理资源 。
  • shm_nattch:表示当前有多少个进程映射到了该共享内存段 。当一个进程附加(attach)共享内存时,shm_nattch会增加;当进程分离(detach)共享内存时,shm_nattch会减少 。这个字段对于内核判断是否可以安全地销毁共享内存非常重要,只有当shm_nattch为 0 时,即没有任何进程在使用共享内存时,内核才会真正删除共享内存段 。

(2)struct shmid_kernel则是内核私有的管理结构体,包含了一些内核级别的字段 :

struct shmid_kernel {    struct kern_ipc_perm shm_perm;  // IPC权限控制块,包含键值(key)、所有者UID等    struct file *shm_file;          // 关联的shm文件对象,实现物理内存与文件系统的关联    unsigned long shm_nattch;       // 映射计数    size_t shm_segsz;               // 段大小    struct pid *shm_cprid;          // 创建者PID(内核态)    struct pid *shm_lprid;          // 最后操作者PID    // 其他内核级字段};
  • shm_perm(内核版):与struct shmid_ds中的shm_perm类似,但包含了更多内核管理所需的信息,比如共享内存的键值(key) 。键值是共享内存的唯一标识,不同进程通过相同的键值来获取或创建同一个共享内存段 。
  • shm_file:这是一个指向虚拟文件系统(VFS)中与共享内存相关的文件对象的指针 。通过这个指针,内核将共享内存与文件系统关联起来,实现了共享内存的文件系统式管理 。这种关联使得共享内存可以像文件一样被操作,例如可以对共享内存进行映射、解除映射等操作,就如同对文件进行读写操作一样 。在实现共享内存的持久化存储时,就可以借助shm_file将共享内存的数据存储到文件系统中 。
  • shm_nattch(内核版):同样用于记录映射到共享内存的进程数,与struct shmid_ds中的shm_nattch相互配合,确保内核和用户空间对共享内存使用情况的认知一致 。

这些数据结构紧密协作,构成了共享内存在内核中的管理体系 ,为共享内存的创建、映射、访问和销毁等操作提供了坚实的数据基础 。

4.2共享内存的创建与销毁流程

(1)创建过程。在 Linux 系统中,创建共享内存主要通过shmget函数来完成 ,其函数原型为:

intshmget(key_t key, size_t size, int shmflg);
  • key:是一个整数值,用于标识共享内存段 。通常可以使用ftok函数根据文件路径和一个特定的整数生成一个唯一的key值 。不同的进程可以通过相同的key值来获取或创建同一个共享内存段 。比如在一个多进程协作的数据库缓存系统中,所有需要共享缓存数据的进程都使用同一个key来操作共享内存 。
  • size:指定了要创建的共享内存段的大小,以字节为单位 。这个大小必须是系统页面大小(通常是 4KB)的整数倍 。在创建共享内存时,内核会根据size的值分配相应大小的物理内存 。
  • shmflg:是一组标志位,用于控制shmget函数的行为 。常用的取值包括IPC_CREAT和IPC_EXCL 。当shmflg包含IPC_CREAT时,如果共享内存段不存在,则创建一个新的共享内存段;如果已经存在,则返回已存在共享内存段的标识符(shmid) 。IPC_EXCL通常与IPC_CREAT一起使用,如果共享内存段已经存在,则shmget函数会返回错误,这样可以确保创建的是一个全新的、独一无二的共享内存段,避免多个进程同时创建同名的共享内存段而导致冲突 。

当进程调用shmget函数创建共享内存时,内核会执行以下操作:

  • 检查键值:内核首先检查系统中是否已经存在具有相同key值的共享内存段 。如果存在,且shmflg中没有设置IPC_EXCL标志,内核直接返回该共享内存段的标识符shmid 。
  • 分配物理内存:如果系统中不存在具有相同key值的共享内存段,且shmflg中设置了IPC_CREAT标志,内核会为新的共享内存段分配物理内存 。这个过程涉及到内存管理模块,内核会从空闲内存池中查找足够大小的连续物理内存块 。如果没有足够的连续物理内存,内核可能会进行内存碎片整理或从交换空间(swap)中获取内存 。
  • 初始化数据结构:内核创建一个新的struct shmid_kernel和struct shmid_ds结构体实例,用于管理新创建的共享内存段 。在struct shmid_ds中,会初始化共享内存的权限(根据shmflg中的权限位设置)、大小、创建时间等字段 。在struct shmid_kernel中,会初始化权限控制块、关联的文件对象(shm_file)等字段 。特别地,shm_nattch字段会被初始化为 0,表示当前没有进程映射到该共享内存段 。
  • 返回标识符:完成上述操作后,内核返回新创建的共享内存段的标识符shmid,进程可以使用这个标识符来进行后续的共享内存操作,如附加、分离和控制等 。

(2)销毁过程。删除共享内存使用shmctl函数,其函数原型为:

intshmctl(int shmid, int cmd, struct shmid_ds *buf);

当cmd参数为IPC_RMID时,表示要删除共享内存段 。

当进程调用shmctl函数删除共享内存时,内核会进行如下判断和资源回收操作:

  1. 检查引用计数:内核首先检查struct shmid_kernel和struct shmid_ds结构体中的shm_nattch字段,查看当前是否还有进程映射到该共享内存段 。如果shm_nattch大于 0,说明还有进程在使用共享内存,内核不会立即删除共享内存段 。而是将共享内存段标记为待删除状态(在struct shmid_ds的状态字段中设置相应标志),当最后一个进程分离共享内存时,内核才会真正执行删除操作 。
  2. 回收物理内存:当shm_nattch为 0 时,内核知道没有任何进程在使用共享内存了,于是开始回收共享内存占用的物理内存 。内核将这些物理内存标记为空闲,归还给系统的空闲内存池,以便重新分配给其他需要内存的进程或内核模块 。
  3. 释放数据结构:内核释放为共享内存段创建的struct shmid_kernel和struct shmid_ds结构体实例,以及相关的资源,如shm_file关联的文件对象等 。这样,共享内存相关的所有资源都被完全回收,共享内存段被成功删除 。在实际应用中,如果在共享内存使用完毕后不及时删除,会导致系统内存资源的浪费,影响系统性能 。所以,在多进程使用共享内存的程序中,一定要确保在合适的时机调用shmctl函数删除不再使用的共享内存 。

4.3进程如何附加(attach)和分离(detach)共享内存

(1)附加过程。进程将共享内存映射到自己的地址空间是通过shmat函数实现的,其函数原型为:

void *shmat(int shmid, constvoid *shmaddr, int shmflg);
  • shmid:是共享内存的标识符,由shmget函数返回 。
  • shmaddr:指定共享内存段连接到进程地址空间的起始地址 。通常设置为NULL,表示由系统自动选择一个合适的地址来连接共享内存段 。这样可以简化编程,让操作系统根据进程地址空间的使用情况和内存管理策略,选择一个最佳的映射地址 。
  • shmflg:是一组标志位,用于控制共享内存段的连接方式和权限等 。通常设置为 0,表示以读写方式连接共享内存段,且不进行地址舍入操作,这是默认的连接方式 。如果设置为SHM_RDONLY,则表示以只读方式连接共享内存段 。

当进程调用shmat函数附加共享内存时,具体步骤如下:

  1. 查找共享内存信息:内核根据传入的shmid,在共享内存管理数据结构中查找对应的struct shmid_kernel和struct shmid_ds结构体 。通过这些结构体,内核可以获取共享内存的大小、权限、当前映射进程数等信息 。
  2. 分配虚拟地址空间:如果shmaddr为NULL,内核在进程的虚拟地址空间中查找一段合适的空闲区域 。这个空闲区域的大小要能够容纳共享内存段,并且要满足内存对齐的要求 。如果shmaddr不为NULL,内核会检查该地址是否可用且符合要求 。
  3. 建立映射关系:内核更新进程的页表,将共享内存的物理页帧与进程虚拟地址空间中分配的区域建立映射关系 。具体来说,就是在进程的页表中创建新的页表项,将虚拟地址映射到共享内存的物理地址上 。同时,设置页表项的访问权限标志,根据shmflg的值,确定该共享内存区域是可读可写还是只读 。
  4. 更新引用计数:内核将struct shmid_kernel和struct shmid_ds中的shm_nattch字段加 1,表示又有一个进程映射到了共享内存段 。
  5. 返回映射地址:完成上述操作后,内核返回共享内存映射到进程地址空间的起始地址,进程可以通过这个地址直接访问共享内存中的数据 。在一个多进程的科学计算程序中,主进程创建共享内存后,多个子进程通过shmat函数将共享内存映射到自己的地址空间,然后子进程就可以直接读取和处理共享内存中的数据,实现高效的数据共享和协作 。

(2)分离过程。进程将共享内存从自己的地址空间中分离出来使用shmdt函数,其函数原型为:

intshmdt(constvoid *shmaddr);

shmaddr是之前通过shmat函数连接共享内存段时返回的地址指针 。

当进程调用shmdt函数分离共享内存时,内核会进行以下处理:

  1. 解除映射关系:内核根据传入的shmaddr,在进程的页表中查找对应的页表项 。然后删除这些页表项,解除共享内存的物理页帧与进程虚拟地址空间的映射关系 。这样,进程就无法再通过原来的虚拟地址访问共享内存了 。
  2. 更新引用计数:内核将struct shmid_kernel和struct shmid_ds中的shm_nattch字段减 1,表示减少一个进程对共享内存的映射 。
  3. 检查是否删除共享内存:如果shm_nattch减为 0,且共享内存段已经被标记为待删除状态(在调用shmctl函数删除共享内存时设置的标志),内核会执行共享内存段的删除操作,回收物理内存和相关数据结构 。在一个多进程的图像渲染程序中,当某个进程完成对共享内存中图像数据的处理后,调用shmdt函数分离共享内存,释放其在进程地址空间中的映射 。如果此时其他进程也都已经分离了共享内存,内核就会删除共享内存段,回收内存资源,为其他任务腾出空间 。

4.4写时拷贝(Copy - on - Write)机制在共享内存中的应用

写时拷贝(Copy - on - Write,简称 COW)是一种高效的内存管理技术,其核心原理是:多个进程在初始阶段共享相同的物理内存页 ,只有当某个进程尝试对共享内存进行写操作时,系统才会为该进程分配新的物理内存页,并将原共享内存页的数据复制到新的物理内存页中,然后该进程对新的物理内存页进行写操作 ,而其他进程仍然共享原来的物理内存页 。

在共享内存的读写过程中,写时拷贝机制发挥着重要作用,能够有效地节省资源、提高效率 :

  • 节省内存资源:在多个进程共享同一块共享内存时,如果所有进程都只是对共享内存进行读操作,那么它们可以一直共享相同的物理内存页,无需为每个进程单独分配物理内存 。这大大减少了内存的占用,特别是在共享内存数据量较大时,内存节省效果显著 。在一个多进程的监控系统中,多个监控进程需要实时读取共享内存中的监控数据,由于这些进程大多只是读取数据,通过写时拷贝机制,它们可以共享同一块物理内存,避免了大量的内存重复分配 。
  • 提高效率:因为只有在进程进行写操作时才会触发内存复制操作,而大多数情况下,进程在一段时间内可能只是进行读操作 。这样就避免了不必要的内存复制开销,提高了系统的整体性能 。在一个多进程的日志记录系统中,多个日志写入进程可能会共享一段用于缓存日志数据的共享内存 。在初始阶段,它们共享相同的物理内存页 。当某个日志写入进程需要写入新的日志时,才会触发写时拷贝,为该进程分配新的物理内存页进行写入,而其他进程不受影响,仍然可以高效地读取原来的共享内存数据 。

从实现层面来看,写时拷贝机制依赖于页表项中的标志位来实现 。在 Linux 系统中,页表项通常包含一些标志位,如PAGE_COW(写时拷贝标记)和PAGE_READONLY(只读权限) 。当多个进程共享同一块共享内存时,对应的页表项会被设置为只读权限 。当某个进程尝试写操作时,CPU 会检测到页表项的只读标志,触发缺页中断 。

在缺页中断处理程序中,系统会为该进程分配新的物理内存页,将原共享内存页的数据复制到新页中,然后更新页表项,将新页设置为可读写权限,并清除PAGE_COW标志 。这样,该进程就可以在新的物理内存页上进行写操作了 ,而其他进程的页表项保持不变,仍然共享原来的物理内存页 。通过这种方式,写时拷贝机制在保证数据一致性和安全性的前提下,最大限度地提高了内存的利用率和系统的性能 。

五、Linux共享内存相关API 调用

5.1 shmget 函数:创建共享内存段

shmget函数用于创建一个新的共享内存段,或者获取一个已经存在的共享内存段的标识符。它的函数原型如下:

#include <sys/ipc.h>#include <sys/shm.h>intshmget(key_t key, size_t size, int shmflg);
  • key:是一个整数值,用于唯一标识共享内存段。不同的进程可以通过相同的key值来访问同一个共享内存段。我们可以使用ftok函数来生成这个key值,后面会详细介绍ftok函数。
  • size:指定了要创建或获取的共享内存段的大小,单位是字节。需要注意的是,这个大小通常应该是系统内存页大小的整数倍,以提高内存使用效率。
  • shmflg:是一组标志位,用于控制shmget函数的行为。常用的标志位有IPC_CREAT和IPC_EXCL ,其中IPC_CREAT表示如果共享内存段不存在,则创建一个新的共享内存段;如果已经存在,则返回已存在共享内存段的标识符。IPC_EXCL通常与IPC_CREAT一起使用,如果共享内存段已经存在,则shmget函数会返回错误,这样可以确保创建的是一个全新的、独一无二的共享内存段,避免多个进程同时创建同名的共享内存段而导致冲突。此外,shmflg还可以包含一些权限标志,用于设置共享内存段的访问权限,与文件权限设置类似,比如0666表示所有用户都有读写权限。

shmget函数如果执行成功,会返回一个非负整数,这个整数就是共享内存段的标识符(shmid),后续对共享内存的操作都需要使用这个标识符;如果执行失败,返回值为-1,并且会设置全局变量errno来表示具体的错误原因,比如ENOENT表示没有找到对应的共享内存段(当未提供IPC_CREAT标志且请求的共享内存段不存在时),EINVAL表示参数无效(比如指定了不可接受的大小值等情况) 。

下面是一个使用shmget函数创建共享内存段的简单示例代码:

#include <sys/ipc.h>#include <sys/shm.h>#include <stdio.h>#include <stdlib.h>#define SHM_SIZE 1024 // 共享内存大小为1024字节intmain(){    key_t key;    int shmid;    // 使用IPC_PRIVATE创建一个新的共享内存段,仅供当前进程使用    key = IPC_PRIVATE;    // 创建共享内存段,权限设置为所有者可读写    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0600);    if (shmid == -1) {        perror("shmget failed");        exit(1);    }    printf("Shared memory created. shmid = %d\n", shmid);    // 这里可以进行其他操作,比如将共享内存映射到进程地址空间等    // 最后,记得在合适的时候删除共享内存段,这里先省略相关代码    return 0;}

在这个示例中,我们使用IPC_PRIVATE作为key值,创建了一个大小为 1024 字节的共享内存段,并设置了所有者可读写的权限。如果shmget函数执行成功,会打印出创建的共享内存段的标识符shmid。

5.2 ftok 函数:生成共享内存的唯一标识

ftok函数的作用是根据给定的文件路径和一个项目标识符,生成一个唯一的键值(key),这个键值可以用于shmget等函数来创建或访问共享内存段。它的函数原型如下:

#include <sys/ipc.h>key_tftok(constchar *pathname, int proj_id);
  • pathname:是一个已经存在的文件路径名。ftok函数会根据这个文件的索引节点号(inode)来生成键值的一部分。通常,我们可以选择一个系统中存在的且不会被轻易删除或移动的文件,比如当前目录下的某个文件或一个已知的配置文件等。
  • proj_id:是一个字符型的项目标识符,它与文件路径名结合起来生成最终的键值。其取值范围是 0 到 255 之间的整数。

ftok函数如果执行成功,会返回一个有效的键值(key_t类型);如果执行失败,返回值为-1,并且会设置errno来表示错误原因,比如ENOENT表示指定的文件不存在,EINVAL表示pathname参数为空字符串或其他非法值。下面是一个使用ftok函数生成key值的示例代码:

#include <sys/ipc.h>#include <stdio.h>intmain(){    key_t key;    const char *pathname = "/tmp/testfile"// 假设这个文件存在    int proj_id = 0x123// 项目标识符    key = ftok(pathname, proj_id);    if (key == -1) {        perror("ftok failed");        return 1;    }    printf("Generated key = %x\n", key);    return 0;}

在这个示例中,我们使用ftok函数根据/tmp/testfile文件和项目标识符0x123生成了一个键值key,并打印出来。如果ftok函数执行成功,会输出生成的键值;如果失败,会打印错误信息。

5.3 shmat 函数:将共享内存映射到进程地址空间

shmat函数用于将共享内存段连接到调用进程的地址空间,这样进程就可以像访问普通内存一样访问共享内存中的数据。它的函数原型如下:

#include <sys/shm.h>void *shmat(int shmid, constvoid *shmaddr, int shmflg);
  • shmid:是shmget函数返回的共享内存段的标识符。
  • shmaddr:指定共享内存段连接到进程地址空间的起始地址。通常设置为NULL,表示由系统自动选择一个合适的地址来连接共享内存段。如果指定了一个非NULL的地址,系统会尝试将共享内存段映射到该地址,但需要注意地址对齐等问题,一般情况下不建议手动指定地址,除非有特殊需求。
  • shmflg:是一组标志位,用于控制共享内存段的连接方式和权限等。通常设置为0,表示以读写方式连接共享内存段,且不进行地址舍入操作,这是默认的连接方式。如果设置为SHM_RDONLY,则表示以只读方式连接共享内存段。

shmat函数如果执行成功,会返回一个指向共享内存段连接到进程地址空间的起始地址的指针,我们可以通过这个指针来访问共享内存中的数据;如果执行失败,返回值为(void *)-1,并且会设置errno来表示错误原因,比如EACCES表示没有权限访问共享内存段,EINVAL表示shmid无效或其他参数错误。下面是一个使用shmat函数将共享内存映射到进程地址空间的示例代码,接着前面shmget创建共享内存段的示例:

#include <sys/ipc.h>#include <sys/shm.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#define SHM_SIZE 1024intmain(){    key_t key;    int shmid;    char *shm_ptr;    key = IPC_PRIVATE;    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0600);    if (shmid == -1) {        perror("shmget failed");        exit(1);    }    // 将共享内存映射到进程地址空间    shm_ptr = (char *)shmat(shmid, NULL0);    if (shm_ptr == (char *)-1) {        perror("shmat failed");        exit(1);    }    printf("Shared memory attached at address %p\n", shm_ptr);    // 向共享内存中写入数据    strcpy(shm_ptr, "Hello, shared memory!");    // 这里可以进行其他对共享内存的操作    // 最后,记得在合适的时候分离共享内存,这里先省略相关代码    return 0;}

在这个示例中,我们先使用shmget创建了共享内存段,然后使用shmat将其映射到进程地址空间,并获取了指向共享内存起始地址的指针shm_ptr。接着,我们通过这个指针向共享内存中写入了字符串"Hello, shared memory!"。

5.4 shmdt 函数:断开共享内存连接

shmdt函数用于将共享内存段与当前进程的地址空间分离,即停止当前进程对共享内存的访问。它的函数原型如下:

#include <sys/shm.h>intshmdt(constvoid *shmaddr);

shmaddr:是之前通过shmat函数连接共享内存段时返回的地址指针,也就是指向共享内存段在进程地址空间中的起始地址。

shmdt函数如果执行成功,返回值为0;如果执行失败,返回值为-1,并且会设置errno来表示错误原因,比如EINVAL表示shmaddr无效,不是一个有效的共享内存地址。

下面是一个使用shmdt函数断开共享内存连接的示例代码,接着前面的示例:

#include <sys/ipc.h>#include <sys/shm.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#define SHM_SIZE 1024intmain(){    key_t key;    int shmid;    char *shm_ptr;    key = IPC_PRIVATE;    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0600);    if (shmid == -1) {        perror("shmget failed");        exit(1);    }    shm_ptr = (char *)shmat(shmid, NULL0);    if (shm_ptr == (char *)-1) {        perror("shmat failed");        exit(1);    }    strcpy(shm_ptr, "Hello, shared memory!");    // 分离共享内存    if (shmdt(shm_ptr) == -1) {        perror("shmdt failed");        exit(1);    }    printf("Shared memory detached\n");    // 这里还可以进行其他操作,比如删除共享内存段(如果不再需要)    return 0;}

在这个示例中,我们在向共享内存写入数据后,使用shmdt函数将共享内存与进程地址空间分离。如果分离成功,会打印"Shared memory detached";如果失败,会打印错误信息。

5.5 shmctl 函数:共享内存的控制中心

shmctl函数是共享内存的控制函数,用于执行各种控制操作,包括获取共享内存段的状态信息、设置共享内存段的属性、删除共享内存段等。它的函数原型如下:

#include <sys/ipc.h>#include <sys/shm.h>intshmctl(int shmid, int cmd, struct shmid_ds *buf);
  • shmid:是shmget函数返回的共享内存段的标识符。
  • cmd:指定了要执行的控制操作,常用的命令有IPC_RMID(用于删除共享内存段)、IPC_STAT(用于获取共享内存段的当前状态信息,并将这些信息存储到buf所指向的struct shmid_ds结构体中)、IPC_SET(用于设置共享内存段的某些属性,这些属性通常存储在buf所指向的struct shmid_ds结构体中)。
  • buf:是一个指向struct shmid_ds结构体的指针,用于存储或传递与共享内存段相关的信息。当cmd为IPC_RMID时,buf通常设置为NULL;当cmd为IPC_STAT或IPC_SET时,buf需要指向一个有效的struct shmid_ds结构体。

shmctl函数如果执行成功,返回值为0;如果执行失败,返回值为-1,并且会设置errno来表示错误原因,比如EINVAL表示shmid无效或cmd参数不合法,EPERM表示没有权限执行指定的操作。

下面是一个使用shmctl函数删除共享内存段的示例代码,接着前面的示例:

#include <sys/ipc.h>#include <sys/shm.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#define SHM_SIZE 1024intmain(){    key_t key;    int shmid;    char *shm_ptr;    key = IPC_PRIVATE;    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0600);    if (shmid == -1) {        perror("shmget failed");        exit(1);    }    shm_ptr = (char *)shmat(shmid, NULL0);    if (shm_ptr == (char *)-1) {        perror("shmat failed");        exit(1);    }    strcpy(shm_ptr, "Hello, shared memory!");    if (shmdt(shm_ptr) == -1) {        perror("shmdt failed");        exit(1);    }    // 删除共享内存段    if (shmctl(shmid, IPC_RMID, NULL) == -1) {        perror("shmctl(IPC_RMID) failed");        exit(1);    }    printf("Shared memory deleted\n");    return 0;}

在这个示例中,我们在分离共享内存后,使用shmctl函数并传入IPC_RMID命令来删除共享内存段。如果删除成功,会打印"Shared memory deleted";如果失败,会打印错误信息。

六、实战演练:手撕共享内存代码

通过前面的理论知识和函数介绍,相信大家对共享内存已经有了比较深入的理解。现在,我们通过一个完整的代码示例来将这些知识串联起来,实现一个简单的进程间通信示例,其中一个进程向共享内存写入数据,另一个进程从共享内存读取数据。

6.1代码示例

#include <sys/ipc.h>#include <sys/shm.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#define SHM_SIZE 1024 // 共享内存大小为1024字节intmain(){    key_t key;    int shmid;    char *shm_ptr;    // 使用ftok生成key值,假设当前目录下存在testfile文件    key = ftok("testfile"1);    if (key == -1) {        perror("ftok failed");        exit(1);    }    // 创建共享内存段,权限设置为所有者可读写    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0600);    if (shmid == -1) {        perror("shmget failed");        exit(1);    }    // 将共享内存映射到进程地址空间    shm_ptr = (char *)shmat(shmid, NULL0);    if (shm_ptr == (char *)-1) {        perror("shmat failed");        exit(1);    }    // 创建子进程    pid_t pid = fork();    if (pid == -1) {        perror("fork failed");        exit(1);    } else if (pid == 0) {        // 子进程,从共享内存读取数据        printf("Child process reading from shared memory...\n");        sleep(1); // 稍微等待一下,确保父进程已经写入数据        printf("Data read from shared memory: %s\n", shm_ptr);        // 分离共享内存        if (shmdt(shm_ptr) == -1) {            perror("shmdt failed in child");            exit(1);        }    } else {        // 父进程,向共享内存写入数据        printf("Parent process writing to shared memory...\n");        strcpy(shm_ptr, "Hello, from parent process!");        // 等待子进程完成读取        wait(NULL);        // 分离共享内存        if (shmdt(shm_ptr) == -1) {            perror("shmdt failed in parent");            exit(1);        }        // 删除共享内存段        if (shmctl(shmid, IPC_RMID, NULL) == -1) {            perror("shmctl(IPC_RMID) failed");            exit(1);        }        printf("Shared memory deleted by parent.\n");    }    return 0;}
  1. 生成key值:使用ftok函数根据当前目录下的testfile文件和项目标识符1生成key值,这个key值用于唯一标识共享内存段,确保不同进程可以通过相同的key访问同一个共享内存。
  2. 创建共享内存段:调用shmget函数,传入生成的key值、共享内存大小SHM_SIZE以及标志位IPC_CREAT | 0600,创建一个新的共享内存段,并设置所有者具有读写权限。如果共享内存段已经存在,则返回已存在的共享内存段的标识符shmid。
  3. 映射共享内存:通过shmat函数将共享内存段映射到当前进程的地址空间,获取指向共享内存起始地址的指针shm_ptr,这样进程就可以像访问普通内存一样访问共享内存。
  4. 创建子进程:使用fork函数创建一个子进程,子进程和父进程会共享相同的代码段和数据段,但有各自独立的栈空间。
  5. 子进程操作:子进程通过shm_ptr指针从共享内存中读取数据,在读取之前先sleep(1)等待 1 秒,这是为了确保父进程已经完成数据写入。读取数据后,子进程调用shmdt函数将共享内存与自身地址空间分离。
  6. 父进程操作:父进程通过shm_ptr指针向共享内存中写入字符串"Hello, from parent process!"。然后调用wait(NULL)等待子进程结束,确保子进程完成数据读取。之后,父进程依次调用shmdt分离共享内存和shmctl并传入IPC_RMID命令删除共享内存段。

6.2运行与调试

(1)编译代码:将上述代码保存为shared_memory_example.c文件,然后使用gcc编译器进行编译,命令如下:

gcc -o shared_memory_example shared_memory_example.c

这会生成一个可执行文件shared_memory_example。

(2)运行程序:在终端中运行生成的可执行文件,命令如下:

./shared_memory_example

运行结果会显示父进程写入数据的提示和子进程读取数据的提示,以及子进程从共享内存中读取到的数据,最后显示共享内存被父进程删除的提示。

(3)可能遇到的问题及解决方法:

  1. ftok失败:如果ftok函数返回-1,可能是指定的文件不存在,或者文件路径无效。确保ftok函数中指定的文件(如testfile)确实存在于当前目录下,并且具有正确的访问权限。
  2. shmget失败:shmget返回-1可能有多种原因,比如权限不足、共享内存段大小不合法等。检查权限设置是否正确,确保当前用户有足够的权限创建共享内存段。同时,确保共享内存段大小是合理的,符合系统的限制(通常应是内存页大小的整数倍)。
  3. shmat失败:shmat失败可能是因为共享内存标识符shmid无效,或者没有足够的权限映射共享内存。检查shmid是否正确获取,以及共享内存的权限设置是否允许当前进程进行映射操作。
  4. 共享内存数据不一致:在多进程环境下,可能会出现共享内存数据不一致的问题,这通常是由于没有正确处理同步机制导致的。可以使用信号量(Semaphores)或互斥锁(Mutexes)等同步工具来确保多个进程对共享内存的访问是安全的,避免竞态条件(Race Condition)。例如,可以在写入和读取共享内存之前,先获取信号量或互斥锁,完成操作后再释放。在上述代码中,如果不使用sleep函数让子进程等待父进程写入数据,可能会出现子进程读取到未完整写入的数据,通过引入同步机制(如信号量)可以解决这类问题 。

通过对共享内存从 API 调用到内核映射的全流程分析,我们深入了解了这一高效的进程间通信方式。从用户态的 API 调用,如shmget、ftok、shmat、shmdt和shmctl,我们掌握了如何在应用层创建、映射、访问和管理共享内存;而在内核层面,共享内存依赖于shmid_ds和ipc_perm等数据结构进行管理,通过复杂的内存映射机制和内核与用户态的交互,实现了进程间数据的高效共享 。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-02-07 19:59:08 HTTP/2.0 GET : https://f.mffb.com.cn/a/470516.html
  2. 运行时间 : 0.185520s [ 吞吐率:5.39req/s ] 内存消耗:4,711.25kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=4b2cbdab94eac551963b437cf3b0fcd0
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000317s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000524s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.003460s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000348s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000519s ]
  6. SELECT * FROM `set` [ RunTime:0.000193s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000651s ]
  8. SELECT * FROM `article` WHERE `id` = 470516 LIMIT 1 [ RunTime:0.006218s ]
  9. UPDATE `article` SET `lasttime` = 1770465548 WHERE `id` = 470516 [ RunTime:0.006950s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000242s ]
  11. SELECT * FROM `article` WHERE `id` < 470516 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000459s ]
  12. SELECT * FROM `article` WHERE `id` > 470516 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.004787s ]
  13. SELECT * FROM `article` WHERE `id` < 470516 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.030942s ]
  14. SELECT * FROM `article` WHERE `id` < 470516 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.018828s ]
  15. SELECT * FROM `article` WHERE `id` < 470516 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.046714s ]
0.186996s