当前位置:首页>Linux>打破黑盒:Linux共享内存内核实现全解析

打破黑盒:Linux共享内存内核实现全解析

  • 2026-01-04 06:36:08
打破黑盒:Linux共享内存内核实现全解析

在 Linux 系统的复杂生态中,进程间通信(IPC)就像是城市交通网络,而共享内存则是其中的高速公路,承担着高效传输数据的重任。当我们在处理大数据分析、实时系统或者高性能计算任务时,数据在不同进程间的快速传递是提升效率的关键。共享内存作为 Linux 进程通信机制中最快速的方式,直接让多个进程访问同一块物理内存区域,避免了数据在内核空间和用户空间之间的多次复制,大大提高了数据传输效率,就如同为进程间的数据交流搭建了一条高速通道。

但是,这条 “高速通道” 是如何在内核层面构建的呢?共享内存的内核实现就像一个隐藏在幕后的黑盒,充满了神秘色彩。它涉及到内核如何管理内存资源、如何协调多个进程对同一块内存的访问、如何保证数据的一致性和安全性等一系列关键问题。今天,就让我们一起深入探究,打破这个黑盒,全面解析 Linux 共享内存的内核实现 。

一、Linux 共享内存

1.1什么是共享内存

共享内存是 Linux 进程间通信(IPC,Inter - Process Communication)中一种极为高效的方式,它允许多个进程直接访问同一块物理内存区域 。这就好比多个办公室的工作人员可以直接进入同一个资料室,无需通过繁琐的文件传递流程,就能获取和修改资料,大大提高了信息交互的效率。在传统的进程间通信方式中,如管道(Pipe)和消息队列(Message Queue),数据往往需要在进程之间进行多次拷贝,这不仅耗费时间,还占用大量系统资源。而共享内存则打破了这种繁琐的模式,让多个进程能够直接在同一块内存中进行数据的读写操作 。

从内存管理的角度来看,共享内存的实现依赖于操作系统的虚拟内存机制。操作系统会为每个进程分配独立的虚拟地址空间,但通过巧妙的页表映射,不同进程的虚拟地址可以指向同一块物理内存。例如,进程 A 和进程 B 通过共享内存进行通信,操作系统会将共享内存区域映射到它们各自的虚拟地址空间中。当进程 A 向共享内存写入数据时,进程 B 可以立即看到这些数据的变化,反之亦然,因为它们实际上操作的是同一块物理内存 。这种直接的内存访问方式,避免了数据在进程间的多次拷贝,极大地提高了数据传输效率,使得共享内存在需要频繁进行大数据量传输的场景中表现尤为出色,如数据库系统中的数据缓存、图形处理中的图像数据共享等。

1.2为何共享内存如此高效

共享内存之所以高效,关键在于它避免了数据在内核空间和用户空间之间的频繁复制 。在传统的进程间通信方式中,如管道和消息队列,数据需要在内核缓冲区和用户进程空间之间来回拷贝,这无疑增加了系统的开销和数据传输的时间。而共享内存则允许进程直接访问同一块物理内存,就像访问自己的私有内存一样,极大地减少了数据复制的次数,提高了数据传输的效率。

以一个简单的例子来说明,假设我们有两个进程 A 和 B,A 进程需要将 10MB 的数据传递给 B 进程。如果使用管道进行通信,数据需要先从 A 进程的用户空间拷贝到内核空间的管道缓冲区,然后再从管道缓冲区拷贝到 B 进程的用户空间,这就涉及到两次数据拷贝。而如果使用共享内存,A 进程直接将数据写入共享内存,B 进程从共享内存中读取数据,只需要一次内存访问操作,大大节省了时间和系统资源 。与其他进程间通信方式相比,共享内存的优势不言而喻。在处理大量数据传输和对实时性要求较高的场景中,共享内存能够显著提升系统的性能。

1.3共享内存映射过程

(1)创建共享内存段:当一个进程需要创建共享内存时,它会调用系统调用shmget(在 System V 共享内存机制中),传入一个键值(key)、共享内存的大小(size)以及一些标志位(shmflg)。键值可以通过ftok函数根据一个文件路径和项目 ID 生成,它就像是共享内存的 “钥匙”,不同进程只要使用相同的键值,就可以访问到同一个共享内存段。例如:

#include <sys/ipc.h>#include <sys/shm.h>#include <stdio.h>intmain(){    key_t key = ftok("."'a');// 根据当前目录和字符'a'生成键值    int shmid = shmget(key, 1024, IPC_CREAT | 0666);    if (shmid == -1) {        perror("shmget");        return 1;    }    printf("Shared memory created with shmid: %d\n", shmid);    return 0;}

在这个例子中,shmget函数创建了一个大小为 1024 字节的共享内存段,如果该共享内存段不存在则创建,权限设置为 0666(表示所有者、组和其他用户都有读写权限)。shmget函数返回一个共享内存标识符(shmid),后续对共享内存的操作都需要使用这个标识符。

(2)进程访问共享内存:其他进程如果要访问这个共享内存段,同样需要使用相同的键值调用shmget函数。如果共享内存段已经存在,shmget函数会返回已存在的共享内存段的标识符。

key_t key = ftok("."'a');int shmid = shmget(key, 00);if (shmid == -1) {    perror("shmget");    return 1;}

这里第二个参数设为 0,表示不需要重新指定共享内存大小,因为共享内存段已经存在,我们只是获取其标识符。

(3)映射共享内存到进程地址空间:进程获取到共享内存标识符后,需要将共享内存映射到自己的虚拟地址空间中,才能进行访问。这一步通过调用shmat函数来实现,传入共享内存标识符(shmid)、期望映射的地址(通常设为 NULL,表示由系统自动选择合适的地址)以及一些标志位(shmflg)。

char *shared_mem = (char *)shmat(shmid, NULL0);if (shared_mem == (void *)-1) {    perror("shmat");    return 1;}

shmat函数返回一个指向映射到进程地址空间的共享内存起始地址的指针。此时,进程就可以像访问普通内存一样,通过这个指针来读写共享内存中的数据。例如:

strcpy(shared_mem, "Hello, shared memory!");

这行代码将字符串 “Hello, shared memory!” 写入共享内存。

(4)撤销共享内存映射:当进程不再需要访问共享内存时,需要调用shmdt函数,将共享内存从自己的地址空间中分离,传入的参数是之前shmat函数返回的共享内存地址。

if (shmdt(shared_mem) == -1) {    perror("shmdt");    return 1;}

需要注意的是,shmdt函数只是解除了进程与共享内存的映射关系,并没有删除共享内存。

(5)删除共享内存:当所有进程都不再需要共享内存时,可以调用shmctl函数来删除共享内存,传入共享内存标识符(shmid)、操作命令(IPC_RMID,表示删除共享内存)以及一个指向共享内存状态结构的指针(通常设为 NULL)。

if (shmctl(shmid, IPC_RMID, NULL) == -1) {    perror("shmctl");    return 1;}

这样,共享内存段就被彻底删除,释放了占用的物理内存资源。

通过以上步骤,不同进程就可以通过共享内存实现高效的数据共享,而共享内存的物理内存映射机制,使得这种数据共享方式既快速又灵活。

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

2.1内核数据结构

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

struct shmid_kernel是内核内部使用的共享内存段描述结构,它包含了许多重要的成员:

  1. struct kern_ipc_perm shm_perm:这是一个 IPC 权限控制块,包含了共享内存段的键值(key)、所有者用户 ID(uid)、所有者组 ID(gid)等权限相关信息 。键值用于唯一标识共享内存段,不同进程通过相同的键值来访问同一个共享内存段;所有者信息则决定了哪些进程有权对共享内存进行操作 。例如,只有所有者进程或具有适当权限的进程才能对共享内存进行读写或删除操作 。
  2. struct file *shm_file:指向虚拟文件系统(VFS)中与共享内存相关联的文件对象 。这个文件对象在共享内存的实现中起着桥梁的作用,它将共享内存与文件系统的管理机制联系起来 。通过这个文件对象,内核可以利用文件系统的一些特性来管理共享内存,如文件的打开、关闭、权限检查等操作 。
  3. unsigned long shm_nattch:记录当前有多少个进程连接到该共享内存段 。当一个进程使用shmat函数将共享内存段连接到自己的地址空间时,这个计数器会增加;当进程使用shmdt函数断开与共享内存段的连接时,计数器会减少 。这个计数器对于内核管理共享内存的生命周期非常重要,当计数器为 0 时,内核可以认为该共享内存段不再被使用,从而可以进行回收或其他管理操作 。
  4. size_t shm_segsz:表示共享内存段的大小(以字节为单位) 。在创建共享内存段时,用户通过shmget函数指定的大小就会存储在这个成员中 。内核在管理共享内存时,会根据这个大小来分配和释放物理内存,以及进行内存映射等操作 。

struct shmid_ds则是用户可见的共享内存段元信息结构,它包含以下主要成员:

  1. struct ipc_perm shm_perm:与struct kern_ipc_perm类似,包含权限相关信息,但它是用户可以访问和修改的部分 。用户可以通过shmctl函数的IPC_STAT和IPC_SET命令来获取和修改这些权限信息 。
  2. int shm_segsz:共享内存段的大小,与struct shmid_kernel中的shm_segsz对应 。
  3. __kernel_time_t shm_atime:记录最后一次进程连接到该共享内存段的时间 。
  4. __kernel_time_t shm_dtime:记录最后一次进程断开与该共享内存段连接的时间 。
  5. __kernel_time_t shm_ctime:记录共享内存段的创建时间或最后一次被修改的时间 。
  6. __kernel_ipc_pid_t shm_cpid:创建该共享内存段的进程 ID 。
  7. __kernel_ipc_pid_t shm_lpid:最后一次对共享内存段进行操作(如连接、断开、控制操作等)的进程 ID 。
  8. unsigned short shm_nattch:当前连接到该共享内存段的进程数,与struct shmid_kernel中的shm_nattch对应 。

这些结构体中的成员相互协作,共同完成了共享内存的创建、管理、访问控制以及生命周期管理等功能 。例如,当一个进程创建共享内存段时,内核会分配一个struct shmid_kernel 结构和一块物理内存,并初始化相关成员 。其他进程通过相同的键值访问该共享内存段时,内核会根据 struct shmid_kernel 中的信息进行权限检查、内存映射等操作,并更新 struct shmid_ds 中的相关时间和进程 ID 等信息 。

2.2内存映射与页表同步

在 Linux 中,共享内存的实现依赖于内存映射机制,而 tmpfs 文件系统在其中扮演了重要角色 。tmpfs 是一种基于内存的文件系统,它将文件数据存储在内存中,而不是磁盘上,这使得它非常适合用于实现共享内存 。 当一个进程创建共享内存时,内核会在 tmpfs 文件系统中创建一个对应的文件对象 。这个文件对象实际上并不对应磁盘上的真实文件,而是存在于内存中 。例如,在 POSIX 共享内存中,使用shm_open函数创建共享内存对象时,内核会在 tmpfs 文件系统中创建一个以指定文件名命名的文件对象 。

接下来,进程通过mmap函数将这个文件对象映射到自己的虚拟地址空间 。mmap函数会在进程的虚拟地址空间中分配一段虚拟内存区域,并建立该虚拟内存区域与共享内存对应的物理内存之间的映射关系 。具体来说,内核会在进程的页表中创建相应的页表项,将虚拟页映射到共享内存的物理页上 。

多个进程对同一共享内存进行映射时,它们的页表项会指向相同的物理页 。这是因为共享内存的物理内存是由内核统一管理的,不同进程通过映射操作,将自己的虚拟地址与共享内存的物理地址建立联系 。例如,进程 A 和进程 B 都映射了同一个共享内存段,当进程 A 修改共享内存中的数据时,由于进程 B 的页表项也指向相同的物理页,所以进程 B 可以立即看到这些修改 。

这种内存映射和页表同步机制,使得多个进程能够高效地共享内存,避免了数据的重复拷贝,提高了数据传输的效率 。同时,由于 tmpfs 文件系统的特性,共享内存的读写操作就像对普通文件的读写操作一样,利用了文件系统的缓存和管理机制,进一步优化了性能 。例如,当进程对共享内存进行读写时,tmpfs 文件系统会利用内存缓存,减少对物理内存的直接访问,提高读写速度 。

2.3写时复制(COW)机制

写时复制(Copy - On - Write,COW)机制是 Linux 共享内存实现中的一个重要优化策略,它在共享内存的性能和资源利用方面发挥着关键作用 。写时复制机制的工作原理基于一个简单而有效的思想:在多个进程共享同一块内存时,只有当某个进程试图对共享内存进行写操作时,才会真正复制该内存块,从而创建出一个私有的副本供该进程进行修改 。

在这之前,所有进程共享同一块物理内存 。以父子进程共享内存为例,当父进程创建子进程时,子进程会继承父进程的地址空间,包括共享内存的映射 。此时,父子进程的页表项都指向相同的物理内存页,这些内存页被标记为只读 。当父进程或子进程对共享内存进行读操作时,由于内存页是只读的,不会发生任何问题,多个进程可以安全地共享同一块物理内存 。

但是,当某个进程试图对共享内存进行写操作时,由于内存页是只读的,会触发一个缺页异常(page fault) 。内核的缺页异常处理程序会捕获到这个异常,并检测到这是一个写时复制的情况 。此时,内核会为发起写操作的进程分配一块新的物理内存页,将原来共享的物理内存页的内容复制到新的物理内存页上,然后修改该进程的页表项,使其指向新的物理内存页 。这样,该进程就拥有了一块私有的内存副本,可以在上面进行写操作,而不会影响其他进程对共享内存的访问 。

写时复制机制对共享内存的性能和资源利用有显著的影响 。从性能角度来看,它避免了在进程创建或共享内存初始化时不必要的内存复制操作,从而加快了进程创建和共享内存映射的速度 。例如,在一个需要创建大量子进程并共享内存的场景中,如果没有写时复制机制,每次创建子进程都需要复制大量的内存,这将消耗大量的时间和系统资源 。而通过写时复制机制,只有在真正需要写操作时才进行内存复制,大大提高了系统的性能 。

从资源利用角度来看,写时复制机制减少了内存的浪费 。在许多情况下,多个进程可能只是读取共享内存中的数据,而很少进行写操作 。通过共享同一块物理内存,而不是为每个进程都复制一份内存副本,可以节省大量的内存空间 。例如,在一个多进程的服务器应用中,多个进程可能需要共享一些配置信息或只读的数据,使用写时复制机制可以确保这些进程高效地共享内存,而不会占用过多的内存资源 。

写时复制机制也带来了一些挑战 。由于内存复制操作是在写操作发生时才进行的,这可能会导致写操作的延迟增加 。在一些对实时性要求极高的场景中,这种延迟可能是不可接受的 。写时复制机制需要内核进行额外的管理和维护工作,包括检测写操作、分配新的物理内存页、更新页表等,这也会消耗一定的系统资源 。在实际应用中,需要根据具体的需求和场景来权衡写时复制机制的利弊,以充分发挥共享内存的优势 。

三、Linux 共享内存的分类

在 Linux 系统中,共享内存主要分为 POSIX 共享内存和 SYSTEM V 共享内存两大体系,其中 POSIX 共享内存又包含 POSIX 共享内存对象和 POSIX 内存映射文件两种类型,它们各自有着不同的特点和适用场景。

3.1 POSIX 共享内存对象

POSIX 共享内存对象是一种基于 POSIX 标准的共享内存实现方式。它的特点是使用方便,通过文件系统中的一个特殊目录/dev/shm来管理共享内存对象 ,这个目录是一个 tmpfs 文件系统,本质上是基于内存的文件系统,所以对共享内存对象的操作就像直接操作内存一样高效。

创建 POSIX 共享内存对象主要使用shm_open函数 ,该函数的原型为int shm_open(const char *name, int oflag, mode_t mode) ,其中name是共享内存对象的名称,以斜杠开头,类似于文件路径;oflag是打开标志,比如使用O_CREAT表示创建共享内存对象,如果对象已存在则打开它,O_RDWR表示以读写模式打开;mode是创建共享内存对象时的权限模式,例如0666表示所有者、组和其他用户都有读写权限。例如,创建一个大小为 100 字节的共享内存区对象可以这样实现:

#include <sys/mman.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <string.h>intmain(){    // 创建共享内存对象,名称为"/my_shared_memory",读写模式,权限为0666    int shm_fd = shm_open("/my_shared_memory", O_CREAT | O_RDWR, 0666);    if (shm_fd == -1) {        perror("shm_open");        return 1;    }    // 设置共享内存对象的大小为100字节    if (ftruncate(shm_fd, 100) == -1) {        perror("ftruncate");        close(shm_fd);        return 1;    }    // 将共享内存对象映射到进程地址空间    char *shared_mem = (char *)mmap(0100, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);    if (shared_mem == MAP_FAILED) {        perror("mmap");        close(shm_fd);        return 1;    }    // 向共享内存中写入数据    strcpy(shared_mem, "Hello, POSIX shared memory!");    // 输出共享内存中的数据    printf("Data in shared memory: %s\n", shared_mem);    // 解除映射    if (munmap(shared_mem, 100) == -1) {        perror("munmap");    }    // 关闭共享内存对象的文件描述符    if (close(shm_fd) == -1) {        perror("close");    }    // 删除共享内存对象    if (shm_unlink("/my_shared_memory") == -1) {        perror("shm_unlink");    }    return 0;}

在这个例子中,首先使用shm_open创建了一个名为/my_shared_memory的共享内存对象,然后使用ftruncate设置其大小为 100 字节 ,接着通过mmap将其映射到进程地址空间,之后就可以像操作普通内存一样对共享内存进行读写操作,最后通过munmap解除映射,close关闭文件描述符,shm_unlink删除共享内存对象。

POSIX 共享内存对象的访问速度非常快,因为它是直接在内存中进行操作,无需经过磁盘 I/O。而且它具有一定的持续性,在进程重启后共享内存中的数据不会丢失,但是在内核自举(即系统重启)或者调用shm_unlink函数删除共享内存对象,又或者在/dev/shm目录下手动删除对应的文件时,数据会丢失。

3.2 POSIX 内存映射文件

POSIX 内存映射文件是将磁盘上的文件映射到进程的地址空间,使得进程可以像访问内存一样访问文件内容。它的特性是可以利用文件系统的持久化存储能力,对于需要长期保存数据的场景非常适用。创建 POSIX 内存映射文件,首先需要使用open函数打开一个文件,获取文件描述符,然后调用mmap函数将文件映射到进程地址空间。

mmap函数的原型为void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) ,其中addr指定映射的地址,通常设为 0 表示由系统选择合适的地址;length是映射的长度;prot是映射的保护模式,比如PROT_READ表示可读,PROT_WRITE表示可写;flags是映射的标志,MAP_SHARED表示共享映射,即对映射区域的修改会反映到文件中;fd是文件描述符;offset是被映射对象内容的起点(偏移量) ,通常设置为 0。例如,在当前目录下创建一个名为test.txt的文件,并将其映射到进程地址空间进行读写操作:

#include <sys/mman.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <string.h>intmain(){    // 创建或打开文件test.txt,读写模式,权限为0666    int fd = open("test.txt", O_CREAT | O_RDWR, 0666);    if (fd == -1) {        perror("open");        return 1;    }    // 设置文件大小为100字节    if (ftruncate(fd, 100) == -1) {        perror("ftruncate");        close(fd);        return 1;    }    // 将文件映射到进程地址空间    char *mapped_mem = (char *)mmap(0100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);    if (mapped_mem == MAP_FAILED) {        perror("mmap");        close(fd);        return 1;    }    // 向映射的内存中写入数据    strcpy(mapped_mem, "Hello, memory - mapped file!");    // 输出映射内存中的数据    printf("Data in mapped memory: %s\n", mapped_mem);    // 解除映射    if (munmap(mapped_mem, 100) == -1) {        perror("munmap");    }    // 关闭文件描述符    if (close(fd) == -1) {        perror("close");    }    return 0;}

在这个示例中,先通过open函数创建或打开test.txt文件,接着用ftruncate设置文件大小,再利用mmap进行映射,之后就可以对映射后的内存进行读写操作,最后通过munmap解除映射,close关闭文件描述符。

POSIX 内存映射文件的访问速度相对内存操作会稍慢一些,因为它涉及到文件系统的操作,数据需要在内核空间和用户空间之间进行同步,不过在处理大文件时,其优势就体现出来了,可以避免频繁的磁盘 I/O 操作。它的持续性很好,只要文件不被删除,数据就会一直存在,即使进程重启或者系统重启,数据依然保留在文件中 。

3.3 SYSTEM V 共享内存

System V 共享内存是一种经典的共享内存实现方式,它基于 System V IPC(Inter - Process Communication)机制 。在这种机制下,共享内存段通过一个唯一的键值(key)来标识,这个键值可以由用户通过ftok函数生成 。ftok函数将给定的文件路径名(pathname)和项目 ID(proj_id)根据算法转换为一个键值(key_t) ,只要不同进程使用相同的文件路径和项目 ID,就能生成相同的键值,从而确定和使用同一个共享内存 。

System V 共享内存的生命周期与内核紧密相连,一旦创建,除非显式删除,否则即使所有使用它的进程都已退出,共享内存依然会存在于系统中 。这就好比你租了一个仓库(共享内存),即使你暂时不用了(进程退出),仓库依然在那里(共享内存存在),直到你明确地退租(删除共享内存) 。

在使用 System V 共享内存时,主要涉及以下几个 API 函数:

shmget:用于创建一个新的共享内存段或者获取一个已有的共享内存段 。其函数原型为int shmget(key_t key, size_t size, int shmflg); ,参数key是共享内存的键值,size指定共享内存段的大小(以字节为单位),shmflg是标志位,常用的标志有IPC_CREAT(如果共享内存不存在则创建)和IPC_EXCL(与IPC_CREAT一起使用,确保创建的是新的共享内存,若已存在则出错返回) 。例如:

#include <iostream>#include <string>#include <sys/ipc.h>#include <sys/shm.h>const std::string gpath = "/home/user/sharemem";const int ProcessId = 0;const size_t gsize = 4096;intmain(){    key_t key = ftok(gpath.c_str(), ProcessId);    int shmid = ::shmget(key, gsize, IPC_CREAT | IPC_EXCL);    if (shmid < 0)        std::cout << "共享内存创建失败..." << std::endl;    else        std::cout << "共享内存key值:" << key << ", " << "shmid:" << shmid << std::endl;    return 0;}

shmat:将共享内存段连接到调用进程的地址空间 。函数原型为void *shmat(int shmid, const void *shmaddr, int shmflg); ,参数shmid是shmget返回的共享内存标识符,shmaddr指定共享内存段附加的位置,通常设置为NULL让系统选择适当的地址,shmflg可以包含标志如SHM_RND(地址舍入)和SHM_RDONLY(只读) 。例如:

#include <iostream>#include <string>#include <unistd.h>#include <sys/ipc.h>#include <sys/shm.h>const std::string gpath = "/home/user/sharemem";const int ProcessId = 0;const size_t gsize = 4096;intmain(){    key_t key = ftok(gpath.c_str(), ProcessId);    int shmid = ::shmget(key, gsize, IPC_CREAT | IPC_EXCL);    if (shmid < 0) {        std::cout << "共享内存创建失败..." << std::endl;        return 1;    }    void *shm_ptr = shmat(shmid, NULL0);    if (shm_ptr == (void *)-1) {        std::cout << "共享内存附加失败..." << std::endl;        return 1;    }    std::cout << "共享内存已附加,地址为:" << shm_ptr << std::endl;    // 使用共享内存    shmdt(shm_ptr); // 分离共享内存    shmctl(shmid, IPC_RMID, NULL); // 删除共享内存    return 0;}
  • shmdt:断开与共享内存段的连接 。函数原型为int shmdt(const void *shmaddr); ,参数shmaddr是shmat返回的指向共享内存的指针 。
  • shmctl:对共享内存段执行特定的控制操作,如删除共享内存段(IPC_RMID)、获取共享内存段的状态(IPC_STAT)等 。函数原型为 int shmctl(int shmid, int cmd, struct shmid_ds *buf); ,参数 shmid 是共享内存标识符,cmd指定要执行的操作,buf是一个指向 shmid_ds 结构的指针,用于存储或修改共享内存段的信息 。

四、共享内存操作全流程

4.1 System V 共享内存操作实战

在实际应用中,System V 共享内存的操作涉及多个关键步骤,下面通过一个完整的示例来详细展示其创建、附加、写入、分离和删除的全过程 。假设我们要实现两个进程之间通过共享内存进行数据传输,一个进程作为发送方,另一个进程作为接收方 。

(1)创建共享内存:发送方进程首先使用shmget函数创建共享内存段 。在创建时,需要指定共享内存的键值(key)、大小(size)以及一些标志位(shmflg) 。这里我们使用ftok函数生成键值,根据当前目录和一个项目 ID(假设为 0x1234)生成唯一的键值 。共享内存大小设置为 4096 字节 。标志位使用IPC_CREAT | IPC_EXCL | 0666,表示如果共享内存不存在则创建新的共享内存段,并且设置其权限为所有用户可读可写 。

#include <sys/ipc.h>#include <sys/shm.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#define PATHNAME "."#define PROJ_ID 0x1234#define SHM_SIZE 4096intmain(){    key_t key = ftok(PATHNAME, PROJ_ID);    if (key == -1) {        perror("ftok");        return 1;    }    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);    if (shmid == -1) {        perror("shmget");        return 1;    }    printf("共享内存创建成功,shmid: %d\n", shmid);    // 后续操作}

(2)附加共享内存:发送方进程创建共享内存段后,使用shmat函数将共享内存段附加到自己的地址空间 。这里将shmaddr参数设置为NULL,表示让系统自动选择合适的地址进行附加 。shmflg参数设置为 0,表示默认的读写权限 。

// 继续上面的代码void *shm_ptr = shmat(shmid, NULL0);if (shm_ptr == (void *)-1) {    perror("shmat");    shmctl(shmid, IPC_RMID, NULL); // 创建失败,删除共享内存段    return 1;}printf("共享内存已附加,地址为: %p\n", shm_ptr);

(3)写入数据:发送方进程将数据写入共享内存 。这里我们简单地将一个字符串写入共享内存 。

// 继续上面的代码const char *message = "Hello, System V Shared Memory!";strncpy((char *)shm_ptr, message, strlen(message));

(4)接收方进程操作:接收方进程首先通过shmget函数获取已经创建的共享内存段 。这里使用相同的键值(通过相同的ftok参数生成)来获取共享内存 。

#include <sys/ipc.h>#include <sys/shm.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#define PATHNAME "."#define PROJ_ID 0x1234#define SHM_SIZE 4096intmain(){    key_t key = ftok(PATHNAME, PROJ_ID);    if (key == -1) {        perror("ftok");        return 1;    }    int shmid = shmget(key, SHM_SIZE, 0666);    if (shmid == -1) {        perror("shmget");        return 1;    }    printf("获取共享内存成功,shmid: %d\n", shmid);    void *shm_ptr = shmat(shmid, NULL0);    if (shm_ptr == (void *)-1) {        perror("shmat");        return 1;    }    printf("共享内存已附加,地址为: %p\n", shm_ptr);    char received_message[SHM_SIZE];    strncpy(received_message, (char *)shm_ptr, SHM_SIZE);    printf("接收到的数据: %s\n", received_message);    // 后续操作}

(5)分离和删除共享内存:发送方和接收方进程在使用完共享内存后,都需要使用shmdt函数分离共享内存 。最后,发送方进程(通常是创建共享内存的进程)使用shmctl函数删除共享内存段 。

// 发送方进程继续上面的代码if (shmdt(shm_ptr) == -1) {    perror("shmdt");}if (shmctl(shmid, IPC_RMID, NULL) == -1) {    perror("shmctl");}// 接收方进程继续上面的代码if (shmdt(shm_ptr) == -1) {    perror("shmdt");}

4.2POSIX 共享内存操作进阶

POSIX 共享内存的操作也遵循类似的流程,但在具体实现上有所不同 。下面通过示例展示 POSIX 共享内存的创建 / 打开、设置大小、内存映射和原子操作等步骤 。

(1)创建 / 打开共享内存对象:使用shm_open函数创建或打开一个共享内存对象 。这里我们创建一个名为/my_shared_memory的共享内存对象,以读写模式打开(O_CREAT | O_RDWR),并设置权限为 0666 。

#include <sys/mman.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#define SHM_NAME "/my_shared_memory"#define SHM_SIZE 4096intmain(){    int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);    if (shm_fd == -1) {        perror("shm_open");        return 1;    }    printf("共享内存对象已创建/打开,文件描述符: %d\n", shm_fd);    // 后续操作}

(2)设置共享内存大小:使用ftruncate函数设置共享内存的大小 。这里将共享内存大小设置为 4096 字节 。

// 继续上面的代码if (ftruncate(shm_fd, SHM_SIZE) == -1) {    perror("ftruncate");    close(shm_fd);    return 1;}

(3)内存映射:使用mmap函数将共享内存映射到进程的地址空间 。这里将addr参数设置为NULL,让系统自动选择映射地址 。length参数设置为共享内存的大小,prot参数设置为可读可写(PROT_READ | PROT_WRITE),flags参数设置为共享映射(MAP_SHARED),fd为shm_open返回的文件描述符,offset设置为 0 。

// 继续上面的代码void *shm_ptr = mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);if (shm_ptr == MAP_FAILED) {    perror("mmap");    close(shm_fd);    return 1;}printf("共享内存已映射,地址为: %p\n", shm_ptr);

(4)原子操作:在多进程访问共享内存时,为了保证数据的一致性和避免竞态条件,通常需要使用原子操作 。例如,使用pthread_mutex_t互斥锁来保护对共享内存的访问 。下面是一个简单的示例,展示如何在 POSIX 共享内存中使用互斥锁进行原子操作 。首先,在共享内存中定义一个包含互斥锁和数据的结构体 。

#include <pthread.h>typedef struct {    pthread_mutex_t mutex;    int data;} SharedData;// 继续上面的代码SharedData *shared_data = (SharedData *)shm_ptr;if (pthread_mutex_init(&shared_data->mutex, NULL) != 0) {    perror("pthread_mutex_init");    munmap(shm_ptr, SHM_SIZE);    close(shm_fd);    return 1;}// 假设在另一个进程中进行写操作if (pthread_mutex_lock(&shared_data->mutex) != 0) {    perror("pthread_mutex_lock");    return 1;}shared_data->data = 100// 写操作if (pthread_mutex_unlock(&shared_data->mutex) != 0) {    perror("pthread_mutex_unlock");    return 1;}// 假设在另一个进程中进行读操作if (pthread_mutex_lock(&shared_data->mutex) != 0) {    perror("pthread_mutex_lock");    return 1;}int value = shared_data->data; // 读操作if (pthread_mutex_unlock(&shared_data->mutex) != 0) {    perror("pthread_mutex_unlock");    return 1;}printf("读取到的数据: %d\n", value);

(5)解除映射、关闭和删除:在使用完共享内存后,需要使用munmap函数解除映射,使用close函数关闭共享内存对象的文件描述符,最后使用shm_unlink函数删除共享内存对象 。

// 继续上面的代码if (munmap(shm_ptr, SHM_SIZE) == -1) {    perror("munmap");}if (close(shm_fd) == -1) {    perror("close");}if (shm_unlink(SHM_NAME) == -1) {    perror("shm_unlink");}

通过以上详细的示例,我们深入了解了 System V 和 POSIX 共享内存的实际操作流程 。在实际应用中,需要根据具体的需求和场景选择合适的共享内存实现方式,并注意内存管理、同步机制等问题,以确保系统的高效、稳定运行 。

五、同步机制:共享内存的稳定保障

5.1同步的必要性

当多个进程同时访问共享内存时,由于进程的执行顺序具有不确定性,可能会出现数据竞争和不一致的问题 。例如,在一个多进程的数据库系统中,多个进程可能同时对共享内存中的数据进行读写操作 。假设共享内存中存储着一个账户的余额信息,进程 A 读取了当前余额为 100 元,准备进行取款操作;与此同时,进程 B 也读取了相同的余额信息 。如果没有同步机制,进程 A 和进程 B 都认为当前余额是 100 元,然后分别进行取款操作 。进程 A 取走了 20 元,将余额更新为 80 元;进程 B 也取走了 30 元,同样将余额更新为 70 元 。但实际上,正确的余额应该是 50 元 。这种数据不一致的情况会导致严重的问题,如金融交易错误、系统状态混乱等 。

为了避免这些问题,同步机制就显得尤为重要 。同步机制可以协调多个进程对共享内存的访问,确保在同一时刻只有一个进程能够对共享内存进行写操作,或者在进行读写操作时,保证数据的完整性和一致性 。例如,使用互斥锁可以将对共享内存的访问代码段(临界区)保护起来,当一个进程进入临界区时,其他进程就不能同时进入,从而避免了数据竞争 。同步机制还可以用于实现进程间的协作,如生产者 - 消费者模型中,生产者进程和消费者进程通过同步机制协调对共享内存缓冲区的访问,确保数据的正确生产和消费 。

5.2常见同步方案对比

(1)信号量:信号量是一种通用的同步机制,它通过维护一个计数器来控制对共享资源的访问 。当一个进程想要访问共享内存时,它需要先获取信号量,如果信号量的计数器大于 0,则获取成功,计数器减 1;否则,进程会被阻塞,直到信号量的计数器大于 0 。当进程访问完共享内存后,需要释放信号量,此时信号量的计数器加 1 。例如,在一个多进程的文件服务器中,可能有多个进程需要同时访问共享内存中的文件数据 。可以使用信号量来限制同时访问文件数据的进程数量,假设信号量的初始值为 5,表示最多允许 5 个进程同时访问 。当一个进程想要访问文件数据时,它先获取信号量,如果信号量的计数器大于 0,说明还有可用的访问名额,进程获取成功后,信号量计数器减 1,该进程可以访问文件数据;当进程访问完后,释放信号量,信号量计数器加 1 。如果信号量的计数器为 0,说明已经没有可用的访问名额,其他进程获取信号量时会被阻塞,直到有进程释放信号量 。信号量的优点是可以灵活地控制多个进程对共享资源的访问,适用于复杂的同步场景,如生产者 - 消费者模型中,可以使用信号量来控制缓冲区的空满状态 。缺点是信号量的操作相对复杂,需要仔细处理计数器的增减,否则容易出现死锁等问题 。

(2)互斥锁:互斥锁是一种简单而常用的同步机制,它只有两种状态:锁定和解锁 。当一个进程获取互斥锁后,其他进程试图获取该互斥锁时会被阻塞,直到持有互斥锁的进程释放它 。互斥锁主要用于保护临界区,确保同一时刻只有一个进程能够进入临界区执行代码 。例如,在一个多进程的图形处理程序中,多个进程可能需要同时访问共享内存中的图形数据 。可以使用互斥锁来保护对图形数据的访问,当一个进程想要访问图形数据时,它先获取互斥锁,如果互斥锁处于解锁状态,进程获取成功,将互斥锁锁定,然后可以访问图形数据;当进程访问完后,释放互斥锁,将其解锁 。如果互斥锁已经被其他进程锁定,该进程会被阻塞,直到互斥锁被释放 。互斥锁的优点是简单直观,使用方便,开销较小 。缺点是它只能用于保护单个临界区,对于复杂的同步需求,可能需要使用多个互斥锁,这增加了死锁的风险 。

(3)条件变量:条件变量通常与互斥锁一起使用,用于线程间的协作 。当某个条件不满足时,线程可以通过条件变量进入等待状态,并释放互斥锁;当条件满足时,另一个线程可以通过条件变量通知等待的线程继续执行 。例如,在一个多进程的网络服务器中,可能有多个进程负责处理客户端的请求 。可以使用条件变量和互斥锁来实现请求队列的管理 。当请求队列中没有请求时,处理进程通过条件变量进入等待状态,并释放互斥锁,以便其他进程可以访问请求队列;当有新的请求到来时,负责接收请求的进程将请求放入请求队列,然后通过条件变量通知等待的处理进程 。处理进程被唤醒后,重新获取互斥锁,从请求队列中取出请求进行处理 。条件变量的优点是可以有效地解决复杂的线程协作问题,提高程序的并发性能 。缺点是使用条件变量需要更加小心地处理线程的状态和同步逻辑,否则容易出现逻辑错误 。

在实际应用中,选择合适的同步机制需要综合考虑多种因素 。如果对共享资源的访问比较简单,只需要保证同一时刻只有一个进程能够访问,那么互斥锁可能是一个不错的选择,因为它简单高效 。如果需要控制多个进程对共享资源的访问数量,或者实现复杂的同步逻辑,如生产者 - 消费者模型等,信号量可能更合适 。而对于需要实现线程间复杂协作的场景,条件变量则是更好的选择 。还需要考虑同步机制的性能开销、死锁风险等因素,以确保系统的高效、稳定运行 。例如,在一个对实时性要求极高的系统中,可能需要选择性能开销较小的同步机制,以减少进程的等待时间;在一个复杂的多进程系统中,需要仔细设计同步逻辑,避免死锁的发生 。

5.3无锁编程实践

无锁编程是一种在多线程或多进程环境下避免使用传统锁机制的编程方式,它通过使用原子操作和特殊的数据结构来实现数据的并发访问控制 。无锁编程在共享内存同步中具有重要的应用价值,它可以提高程序的并发性能,减少锁竞争带来的开销 。

以 CAS(Compare - And - Swap)实现无锁队列为例,CAS 是一种原子操作,它将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值 。在无锁队列中,CAS 操作可以用于实现线程安全的入队和出队操作 。下面是一个简单的无锁队列的实现示例(以 C++ 为例):

#include <atomic>#include <iostream>template <typename T>struct Node {    T data;    std::atomic<Node<T>*> next;    Node(const T& value) : data(value), next(nullptr) {}};template <typename T>class LockFreeQueue {private:    std::atomic<Node<T>*> head;    std::atomic<Node<T>*> tail;public:    LockFreeQueue() {        Node<T>* sentinel = new Node<T>(T());        head.store(sentinel);        tail.store(sentinel);    }    ~LockFreeQueue() {        while (Node<T>* node = head.load()) {            head.store(node->next.load());            delete node;        }    }    voidenqueue(const T& value){        Node<T>* newNode = new Node<T>(value);        Node<T>* oldTail;        do {            oldTail = tail.load();        } while (!tail.compare_exchange_weak(oldTail, newNode));        oldTail->next.store(newNode);    }    booldequeue(T& result){        Node<T>* oldHead;        do {            oldHead = head.load();            if (oldHead == tail.load()) {                return false;            }        } while (!head.compare_exchange_weak(oldHead, oldHead->next.load()));        result = oldHead->next.load()->data;        delete oldHead;        return true;    }};

在这个实现中,enqueue函数用于将元素入队 。首先创建一个新的节点newNode,然后通过compare_exchange_weak函数(基于 CAS 操作)尝试将tail指针更新为newNode 。如果更新失败,说明tail指针已经被其他线程修改,重新尝试 。更新成功后,将旧的tail节点的next指针指向newNode 。

dequeue函数用于将元素出队 。首先通过compare_exchange_weak函数尝试将head指针更新为head->next 。如果更新失败,说明head指针已经被其他线程修改,重新尝试 。更新成功后,获取head->next节点的数据,并删除旧的head节点 。

无锁队列相比传统的锁机制队列,在高并发环境下具有更好的性能 。传统的锁机制队列在多线程并发访问时,由于锁的存在,线程需要竞争锁,这会导致大量的线程上下文切换和等待时间,降低了系统的并发性能 。而无锁队列通过使用原子操作,避免了锁的竞争,多个线程可以同时进行入队和出队操作,提高了系统的并发性能 。无锁队列也存在一些挑战 。由于无锁编程使用了复杂的原子操作和循环重试机制,代码的实现和理解难度较大 。无锁队列的正确性验证也更加困难,需要更加仔细地考虑各种并发情况 。在实际应用中,需要根据具体的需求和场景来权衡是否使用无锁编程,以充分发挥其优势 。

六、性能调优与安全实践

6.1性能优化技巧

在使用共享内存时,合理的性能优化能够进一步提升系统的运行效率 。

大页内存(Hugepage)配置是提升内存访问效率的有效方法 。传统的内存分页大小通常为 4KB,而大页内存的大小可以达到 2MB 甚至 1GB 。使用大页内存可以减少页表项的数量,降低内存管理单元(MMU)的地址转换开销,从而提高内存访问速度 。在数据库系统中,大量的数据读写操作对内存访问性能要求极高,使用大页内存可以显著提升数据库的性能 。在 Linux 系统中,可以通过修改/etc/sysctl.conf文件来配置大页内存,例如:

vm.nr_hugepages = 1024  # 设置大页内存的数量为1024个

修改完成后,执行sysctl -p使配置生效 。在程序中使用大页内存时,可以在创建共享内存时指定SHM_HUGETLB标志,例如:

#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>intmain(){    key_t key = ftok("."'a');    int shmid = shmget(key, 2 * 1024 * 1024, IPC_CREAT | SHM_HUGETLB | 0666);    // 后续操作    return 0;}

对于具有非统一内存访问(NUMA)架构的系统,优化内存访问可以使内存访问更加合理 。在 NUMA 架构中,处理器访问本地内存的速度比访问远程内存的速度更快 。因此,将进程和共享内存分配到同一 NUMA 节点上,可以减少内存访问延迟,提高系统性能 。

可以使用numactl命令来绑定进程到指定的 NUMA 节点,例如:

numactl --cpunodebind=0 --membind=0 your_program  # 将程序绑定到NUMA节点0

在代码中,可以使用mbind函数来实现内存绑定,例如:

#include <numa.h>#include <stdio.h>#include <stdlib.h>#include <sys/mman.h>#include <fcntl.h>#include <unistd.h>intmain(){    int numa_nodes[] = {0};  // 绑定到节点0    size_t size = 1024 * 1024;  // 1MB内存    void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -10);    if (ptr == MAP_FAILED) {        perror("mmap");        return 1;    }    if (mbind(ptr, size, MPOL_BIND, numa_nodes, 10) == -1) {        perror("mbind");        return 1;    }    // 后续操作    return 0;}

6.2安全加固方案

权限设置:在共享内存的权限设置方面,需要遵循最小权限原则 。对于 System V 共享内存,可以通过shmctl函数设置共享内存段的权限 。例如,只允许特定的用户组对共享内存进行读写操作 。在创建共享内存时,可以设置shmflg参数来指定权限,如shmget(key, size, IPC_CREAT | 0660)表示创建一个所有者和所属组具有读写权限,其他用户无权限的共享内存段 。这样可以防止未授权的进程访问和修改共享内存中的数据,避免数据泄露和篡改 。

SELinux 策略:SELinux(Security - Enhanced Linux)是一种强制访问控制(MAC)安全模块,它可以为共享内存提供更细粒度的安全控制 。通过配置 SELinux 策略,可以限制哪些进程能够访问共享内存,以及以何种权限访问 。例如,在一个多进程的服务器应用中,通过 SELinux 策略可以确保只有特定的数据库进程能够访问共享内存中的数据,并且只能进行读操作,防止其他进程对数据库共享内存的非法访问和修改 。要启用 SELinux 策略,需要先确保系统安装了 SELinux,然后编辑/etc/selinux/config文件,将SELINUX参数设置为enforcing 。还需要根据具体的应用场景编写和应用相应的 SELinux 策略文件 。

加密共享内存:对于一些对数据安全性要求极高的场景,如金融交易系统、医疗数据处理等,加密共享内存是一种有效的安全措施 。可以使用如 OpenSSL 等加密库对共享内存中的数据进行加密 。在数据写入共享内存前,使用加密算法(如 AES)对数据进行加密,然后将加密后的数据写入共享内存 。当其他进程读取共享内存中的数据时,再使用相应的密钥进行解密 。这样即使共享内存被非法访问,攻击者也无法直接获取到明文数据 。例如,在一个在线支付系统中,将用户的支付信息加密后存储在共享内存中,只有经过授权的支付处理进程能够使用正确的密钥解密数据,确保了支付信息的安全性 。

6.3容器化集成

(1)Docker 中的共享内存设置:在 Docker 容器中,容器启动后默认会有 64MB 的共享内存挂载在/dev/shm目录 。如果应用程序需要更大的共享内存,可以通过--shm-size参数来设置 。例如,docker run -d --name=test --shm-size=500m busybox:1.32 sleep infinity 这条命令创建了一个名为test的容器,并为其分配了 500MB 的共享内存 。这样,在容器内运行的应用程序就可以使用更大的共享内存空间,满足一些对内存需求较大的场景,如大数据处理、图形渲染等 。

(2)Kubernetes 中的共享内存使用:在 Kubernetes 中,可以通过挂载memory类型的emptyDir来实现共享内存的设置 。例如,以下是一个 Pod 的配置文件示例:

apiVersion: v1kind: Podmetadata:  name: test-shmspec:  containers:  - image: busybox:1.32    name: test-container    command: [sh, -c, "sleep infinity"]    volumeMounts:    - mountPath: /dev/shm      name: cache-volume  volumes:  - name: cache-volume    emptyDir:      medium: Memory      sizeLimit: 500Mi

在这个配置中,emptyDir 类型的卷被挂载到容器的 /dev/shm目录 ,并且设置了大小限制为 500Mi 。这使得容器内的应用程序可以使用这个共享内存区域进行数据共享和通信 。在 Kubernetes 中,还可以通过创建ConfigMap来管理共享内存的配置参数,然后在 Pod 配置文件中引用ConfigMap,实现更灵活的共享内存设置 。

七、Linux共享内存使用中的注意事项

7.1内存大小限制

在 Linux 系统中,共享内存的大小并非可以随意设置,它受到多个内核参数的限制。其中,kernel.shmmax定义了单个进程在其虚拟地址空间中可以访问的单个共享内存段的最大值。例如,在一些数据库应用场景中,如果kernel.shmmax设置过小,可能会限制数据库系统全局区(SGA,System Global Area)的大小,因为 SGA 通常由共享内存组成,这可能会导致数据库性能下降 。假设系统默认的kernel.shmmax值为 33554432(即 32MB) ,而数据库的 SGA 需要设置为 64MB,那么就需要调整kernel.shmmax参数。可以通过修改/etc/sysctl.conf文件,添加或修改kernel.shmmax = 67108864(64MB 对应的字节数) ,然后执行sudo sysctl -p使设置生效。

kernel.shmall则定义了系统级别可以使用的所有共享内存页的数量。由于kernel.shmmax是单个内存段可使用的共享内存大小,所以kernel.shmall的大小应大于等于kernel.shmmax对应的内存页数,即ceil(kernel.shmmax/PAGE_SIZE) ,其中PAGE_SIZE可以通过getconf PAGE_SIZE命令获取,一般为 4096 字节(4KB) 。例如,若kernel.shmmax设置为 67108864 字节,PAGE_SIZE为 4096 字节,那么kernel.shmall至少应设置为ceil(67108864/4096) = 16384 。同样,在/etc/sysctl.conf文件中修改kernel.shmall的值,如kernel.shmall = 16384 ,再执行sudo sysctl -p。

kernel.shmmni这个参数定义了在系统层面最大的共享内存段的数量,默认值通常为 4096,一般情况下不需要修改 。但如果在某些大规模的分布式应用中,需要创建大量的共享内存段,就可能需要根据实际需求调整这个参数。比如,当应用程序需要创建 5000 个共享内存段时,就需要在/etc/sysctl.conf文件中修改kernel.shmmni的值为大于 5000 的数,如kernel.shmmni = 5120 ,然后使设置生效。如果不注意这些内存大小限制参数,可能会导致共享内存创建失败,应用程序无法正常运行 ,比如在创建共享内存段时,如果设置的大小超过了kernel.shmmax的限制,shmget函数就会返回 - 1,并设置错误信息为EINVAL(表示无效参数) 。

7.2同步与互斥问题

共享内存虽然提供了高效的数据共享方式,但它本身缺乏自动的同步机制。当多个进程同时访问和修改共享内存时,就容易出现数据竞争(Data Race)问题,导致数据不一致或程序出现不可预测的行为。例如,假设有两个进程 A 和 B 同时访问共享内存中的一个整数变量count,进程 A 读取count的值为 10,然后准备将其加 1,在进程 A 还未完成写入操作时,进程 B 也读取了count的值,此时count的值还是 10,接着进程 B 也对其进行加 1 操作并写入,最后进程 A 完成写入操作 ,这样最终count的值只增加了 1,而不是预期的增加 2,这就是典型的数据竞争问题。

为了避免这种数据竞争,需要结合其他同步机制,如信号量(Semaphore)、互斥锁(Mutex)等。以信号量为例,它是一种计数器,用于控制对共享资源的访问。可以把信号量想象成一个房间的钥匙,只有拥有钥匙的进程才能进入房间(访问共享内存)。在使用信号量时,首先需要创建一个信号量,然后在访问共享内存之前,进程通过sem_wait函数获取信号量(相当于获取钥匙) ,如果信号量的值大于 0,则获取成功,信号量的值减 1;如果信号量的值为 0,则进程会被阻塞,直到信号量的值大于 0。当进程访问完共享内存后,通过sem_post函数释放信号量(归还钥匙),信号量的值加 1。以下是一个简单的使用信号量配合共享内存的示例代码:

#include <stdio.h>#include <stdlib.h>#include <sys/shm.h>#include <sys/sem.h>#include <sys/ipc.h>#include <unistd.h>#define SHM_SIZE 1024#define SEM_KEY 1234// 信号量操作函数:P操作voidsemaphore_P(int semid){    struct sembuf sb = {0-10};    if (semop(semid, &sb, 1) == -1) {        perror("semaphore_P");        exit(1);    }}// 信号量操作函数:V操作voidsemaphore_V(int semid){    struct sembuf sb = {010};    if (semop(semid, &sb, 1) == -1) {        perror("semaphore_V");        exit(1);    }}intmain(){    key_t key = ftok("."'a');    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);    if (shmid == -1) {        perror("shmget");        return 1;    }    int semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);    if (semid == -1) {        perror("semget");        return 1;    }    // 初始化信号量为1    if (semctl(semid, 0, SETVAL, 1) == -1) {        perror("semctl");        return 1;    }    pid_t pid = fork();    if (pid == -1) {        perror("fork");        return 1;    } else if (pid == 0) {        // 子进程        semaphore_P(semid);        char *shared_mem = (char *)shmat(shmid, NULL0);        if (shared_mem == (void *)-1) {            perror("shmat");            return 1;        }        printf("Child process read from shared memory: %s\n", shared_mem);        if (shmdt(shared_mem) == -1) {            perror("shmdt");            return 1;        }        semaphore_V(semid);    } else {        // 父进程        char *shared_mem = (char *)shmat(shmid, NULL0);        if (shared_mem == (void *)-1) {            perror("shmat");            return 1;        }        strcpy(shared_mem, "Hello from parent!");        if (shmdt(shared_mem) == -1) {            perror("shmdt");            return 1;        }        semaphore_V(semid);        wait(NULL);        if (shmctl(shmid, IPC_RMID, NULL) == -1) {            perror("shmctl");            return 1;        }        if (semctl(semid, 0, IPC_RMID) == -1) {            perror("semctl");            return 1;        }    }    return 0;}

在这个示例中,通过信号量来保证同一时间只有一个进程能够访问共享内存,避免了数据竞争。互斥锁的原理与信号量类似,它是一种二元信号量(只有 0 和 1 两种状态),同一时间只允许一个进程访问共享资源 。在使用互斥锁时,需要在进程访问共享内存之前加锁(pthread_mutex_lock),访问完成后解锁(pthread_mutex_unlock) ,从而确保共享内存的访问安全。

7.3内存释放

共享内存的生命周期与进程是相互独立的,它由内核进行管理。当一个进程创建了共享内存段后,即使该进程终止,共享内存段依然存在于系统中,直到所有使用该共享内存段的进程都与之分离,并且显式地删除共享内存段,或者系统重启,共享内存段才会被真正释放。如果不注意共享内存的释放,可能会导致内存泄漏(Memory Leak) ,即共享内存占用的物理内存资源无法被回收,随着时间的推移,可能会耗尽系统的内存资源,影响系统的正常运行。

在 SYSTEM V 共享内存机制中,当进程不再需要访问共享内存时,需要调用shmdt函数将共享内存从进程的地址空间中分离。例如:

char *shared_mem = (char *)shmat(shmid, NULL0);// 使用共享内存...if (shmdt(shared_mem) == -1) {    perror("shmdt");    return 1;}

这里的shmdt函数只是断开了进程与共享内存的映射关系,并没有删除共享内存。要删除共享内存,需要调用shmctl函数,传入IPC_RMID命令 ,当所有进程都将共享内存分离后,共享内存段才会被真正删除。例如:

if (shmctl(shmid, IPC_RMID, NULL) == -1) {    perror("shmctl");    return 1;}

在 POSIX 共享内存中,删除共享内存对象使用shm_unlink函数 ,它会立即从文件系统中删除共享内存对象的名称,但只有当所有进程都解除了对该共享内存对象的映射后,共享内存才会被真正释放。例如:

int shm_fd = shm_open("/my_shared_memory", O_CREAT | O_RDWR, 0666);// 使用共享内存...if (shm_unlink("/my_shared_memory") == -1) {    perror("shm_unlink");    return 1;}

正确地释放共享内存对于系统的稳定性和资源的有效利用至关重要,在编写使用共享内存的程序时,一定要确保在适当的时候进行内存释放操作 。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-02-09 08:59:40 HTTP/2.0 GET : https://f.mffb.com.cn/a/459463.html
  2. 运行时间 : 0.089215s [ 吞吐率:11.21req/s ] 内存消耗:4,784.71kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=b18c40c4da9ba1beca5279936ba5eff5
  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.000619s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000761s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000320s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000261s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000524s ]
  6. SELECT * FROM `set` [ RunTime:0.000256s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000591s ]
  8. SELECT * FROM `article` WHERE `id` = 459463 LIMIT 1 [ RunTime:0.000798s ]
  9. UPDATE `article` SET `lasttime` = 1770598780 WHERE `id` = 459463 [ RunTime:0.002414s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000257s ]
  11. SELECT * FROM `article` WHERE `id` < 459463 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001345s ]
  12. SELECT * FROM `article` WHERE `id` > 459463 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000630s ]
  13. SELECT * FROM `article` WHERE `id` < 459463 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.005501s ]
  14. SELECT * FROM `article` WHERE `id` < 459463 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.003647s ]
  15. SELECT * FROM `article` WHERE `id` < 459463 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.004192s ]
0.091772s