MM 学习笔记 09:进程会话与工作集

MM 学习笔记 09:进程会话与工作集

文件:ntoskrnl/mm/ARM3/procsup.c + ntoskrnl/mm/ARM3/session.c + ntoskrnl/mm/ARM3/wslist.cpp + ntoskrnl/mm/balance.c

日期:2026-07-19


1. 概述

这一组文件管理三个紧密相关的子系统:

文件 行数 职责
procsup.c ~1600 行 进程地址空间管理:PEB/TEB、内核栈、AWE
session.c ~1100 行 Session 生命周期:创建/删除/附加/分离
wslist.cpp ~800 行 工作集列表管理(唯一 C++ 文件)
balance.c ~460 行 内存平衡器:后台线程页面回收

2. procsup.c --- 进程地址空间管理

位置: ntoskrnl/mm/ARM3/procsup.c

2.1 MmCreateProcessAddressSpace

c 复制代码
NTSTATUS
NTAPI
MmCreateProcessAddressSpace(IN ULONG MinWs,
                            IN PEPROCESS Process,
                            OUT PULONG_PTR DirectoryTableBase)

位置: procsup.c:1161

创建新进程的地址空间,分配页目录、超空间和工作集页面:

复制代码
MmCreateProcessAddressSpace
    │
    ├─ 1. 生成进程颜色(用于缓存优化)
    │      └─ Process->NextPageColor = RtlRandom(&MmProcessColorSeed)
    │
    ├─ 2. 初始化超空间锁
    │
    ├─ 3. 分配关键页面(锁定 PFN 数据库)
    │      │
    │      ├─ TableBaseIndex → 页目录(Page Directory)
    │      │   └─ 使用 MiRemoveZeroPageSafe 优先获取零页
    │      │
    │      ├─ HyperIndex → 超空间页(Hyperspace)
    │      │   └─ 用于临时映射物理页
    │      │
    │      └─ WsListIndex → 工作集列表页(Working Set List)
    │          └─ 存储 WSL(Working Set List)
    │
    ├─ 4. 设置基地址
    │      ├─ Process->WorkingSetPage = WsListIndex
    │      └─ DirectoryTableBase[0/1] 赋值
    │
    ├─ 5. 架构相关初始化
    │      └─ MiArchCreateProcessAddressSpace(Process, DirectoryTableBase)
    │
    └─ 6. 插入进程链表
           └─ InsertTailList(&MmProcessList, &Process->MmProcessLinks)

2.2 MmDeleteProcessAddressSpace

c 复制代码
VOID
NTAPI
MmDeleteProcessAddressSpace(IN PEPROCESS Process)

位置: procsup.c:1357

释放进程地址空间:

复制代码
MmDeleteProcessAddressSpace
    │
    ├─ 1. 从进程链表移除
    │      └─ RemoveEntryList(&Process->MmProcessLinks)
    │
    ├─ 2. 从工作集扩展链表移除
    │      └─ RemoveEntryList(&Process->Vm.WorkingSetExpansionLinks)
    │
    ├─ 3. 释放工作集页面
    │      ├─ MiGetPfnEntry(Process->WorkingSetPage)
    │      ├─ MI_SET_PFN_DELETED
    │      └─ MiDecrementShareCount → 两次(PTE 和页面)
    │
    ├─ 4. 释放超空间页面
    │      └─ 同样标记 PFN_DELETED 并减少引用
    │
    ├─ 5. 释放页目录
    │      └─ PageFrameIndex = Process->Pcb.DirectoryTableBase[0] >> PAGE_SHIFT
    │         MI_SET_PFN_DELETED
    │         MiDecrementShareCount × 2(自映射 PDE)
    │
    ├─ 6. 释放 Session 引用
    │      └─ MiReleaseProcessReferenceToSessionDataPage
    │
    └─ 7. 清零目录基地址
           └─ Process->Pcb.DirectoryTableBase[0/1] = 0

2.3 MmCreatePeb / MmCreateTeb

c 复制代码
NTSTATUS
NTAPI
MmCreatePeb(IN PEPROCESS Process, OUT PVOID * Peb)

位置: procsup.c:518

c 复制代码
NTSTATUS
NTAPI
MmCreateTeb(IN PEPROCESS Process, OUT PVOID * Teb)

位置: procsup.c:753

这两个函数类似,都通过 MiCreatePebOrTeb 实现:

复制代码
MiCreatePebOrTeb(Process, Size, &BaseAddress)
    │
    ├─ 1. 分配配额(PsChargeProcessNonPagedPoolQuota)
    │
    ├─ 2. 分配 VAD(MMVAD_LONG)
    │      └─ 标签 'ldaV'
    │
    ├─ 3. 设置 VAD 属性
    │      ├─ CommitCharge = BYTES_TO_PAGES(Size)
    │      ├─ MemCommit = TRUE
    │      ├─ PrivateMemory = TRUE
    │      ├─ Protection = MM_READWRITE
    │      ├─ NoChange = TRUE
    │      └─ VadFlags2.OneSecured = TRUE  (防误删)
    │
    ├─ 4. 计算基地址
    │      ├─ PEB: 随机选择 MM_HIGHEST_VAD_ADDRESS 之上的一个页面
    │      ├─ TEB: 在用户栈附近
    │      └─ 使用 MiInsertVadEx 插入 VAD 树
    │
    └─ 5. 提交页面
           └─ MmCommitPageMemory → 分配物理页

PEB vs TEB 的关键差异:

特性 PEB TEB
大小 sizeof(PEB) sizeof(TEB)
位置 MM_HIGHEST_VAD_ADDRESS 之上的随机地址 最高 VAD 地址范围内
保护 OneSecured + ReadOnly OneSecured
用途 进程信息(堆、模块、LDR) 线程信息(异常处理、TLS)

2.4 MmCreateKernelStack / MmDeleteKernelStack

c 复制代码
NTSTATUS
NTAPI
MmCreateKernelStack(IN BOOLEAN GuiStack,
                    IN ULONG_PTR StackSize,
                    OUT PVOID *StackBase)

位置: procsup.c:276

c 复制代码
VOID
NTAPI
MmDeleteKernelStack(IN PVOID StackBase, IN BOOLEAN GuiStack)

位置: procsup.c:187

内核栈管理:

  • 每个线程有一个内核栈

  • GUI 线程(Win32k)需要更大的栈

  • 栈页面标记为 NoAccess 的守卫页用于检测栈溢出

    MmCreateKernelStack:
    ├─ 分配连续虚拟地址空间(通过 MiAllocateVad)
    ├─ 提交物理页面(部分提交)
    ├─ 设置栈顶的守卫页(MM_GUARDPAGE)
    └─ 返回栈基地址

2.5 AWE 系统调用

AWE(Address Windowing Extensions)允许 32 位应用程序访问超过 4GB 的物理内存:

c 复制代码
NTSTATUS
NTAPI
NtAllocateUserPhysicalPages(IN HANDLE ProcessHandle,
                            IN OUT PULONG_PTR NumberOfPages,
                            OUT PULONG_PTR UserPfnArray)

位置: procsup.c:1434

当前实现是存根(stub)状态,尚未完全实现:

复制代码
NtAllocateUserPhysicalPages
    → DPRINT1("UNIMPLEMENTED\n")
    → return STATUS_NOT_IMPLEMENTED

NtMapUserPhysicalPages
    → DPRINT1("UNIMPLEMENTED\n")
    → return STATUS_NOT_IMPLEMENTED

NtAllocateUserPhysicalPagesEx
    → 同状态

3. session.c --- Session 管理

位置: ntoskrnl/mm/ARM3/session.c

Session 是 Windows 终端服务/多会话支持的核心概念。每个 Session 包含一组进程、一个独立的窗口站和桌面。

3.1 Session 空间地址范围

在 x86 32 位系统中:

复制代码
Session 空间: 0xA0000000 - 0xC0000000 (512MB)
                ↓
页表覆盖整个 Session 区域

3.2 核心全局变量

c 复制代码
PMM_SESSION_SPACE MmSessionSpace;             // 当前 Session 空间指针
PFN_NUMBER MiSessionDataPages;                // Session 数据页数
PFN_NUMBER MiSessionTagPages;                 // Session 标签页数
PFN_NUMBER MiSessionCreateCharge;             // 创建 Session 的配额
KGUARDED_MUTEX MiSessionIdMutex;              // Session ID 位图锁
PRTL_BITMAP MiSessionIdBitmap;                // Session ID 位图
volatile LONG MiSessionLeaderExists;          // Session 领导者存在标志
LIST_ENTRY MiSessionWsList;                   // Session 工作集链表

3.3 MmSessionCreate

c 复制代码
NTSTATUS
NTAPI
MmSessionCreate(OUT PULONG SessionId)

位置: session.c:827

复制代码
MmSessionCreate
    │
    ├─ 1. 检查进程是否已在 Session 中
    │
    ├─ 2. 设置 Session Leader
    │      └─ 原子操作 InterlockedCompareExchange(&MiSessionLeaderExists, 1, 0)
    │
    ├─ 3. 调用 MiSessionCreateInternal 创建 Session
    │      │
    │      ├─ 设置 PSF_SESSION_CREATION_UNDERWAY_BIT 标志
    │      ├─ 分配页表(x86: 512MB 空间的页表)
    │      ├─ 从位图分配 Session ID
    │      ├─ 保留系统 PTE
    │      ├─ 分配数据页(零页)
    │      ├─ 分配标签页
    │      ├─ 设置 PDE 映射
    │      └─ 初始化 Session 空间
    │
    ├─ 4. 初始化 Session 工作集
    │      └─ MiSessionInitializeWorkingSetList()
    │
    └─ 5. 设置完成标志
           └─ MmSessionSpace->u.Flags.Initialized = 1

3.4 MmSessionDelete

c 复制代码
NTSTATUS
NTAPI
MmSessionDelete(IN ULONG SessionId)

位置: session.c:887

3.5 MmAttachSession / MmDetachSession

c 复制代码
VOID
NTAPI
MmAttachSession(IN PEPROCESS Process, IN PVOID SessionObject)

位置: session.c:917

c 复制代码
VOID
NTAPI
MmDetachSession(IN PEPROCESS Process)

位置: session.c:985

复制代码
MmAttachSession:
    ├─ 增加引用计数
    ├─ 附加进程到 Session 空间
    ├─ 设置 Process->Session 指针
    ├─ 插入 Session 进程链表
    └─ 激活 Session 空间(切换 PDE)

MmDetachSession:
    ├─ 减少引用计数
    ├─ 从 Session 进程链表移除
    ├─ 如果引用为 0 → MiDereferenceSessionFinal
    │   ├─ 设置 DeletePending 标志
    │   ├─ 等待所有进程分离
    │   └─ 释放资源
    └─ 清除 Process->Session 指针

3.6 Session ID 位图管理

c 复制代码
void NTAPI MiInitializeSessionIds(VOID)
{
    // 计算需要的数据页数
    MiSessionDataPages = ROUND_TO_PAGES(sizeof(MM_SESSION_SPACE)) >> PAGE_SHIFT;

    // 标签页大小(池标签跟踪)
    MiSessionTagSizePages = 2;
    MiSessionBigPoolPages = 1;
    MiSessionTagPages = MiSessionTagSizePages + MiSessionBigPoolPages;

    // 创建 Session 的总配额
    MiSessionCreateCharge = 1 + MiSessionDataPages + MiSessionTagPages;

    // 初始化位图(初始 64 个 Session ID)
    MiSessionIdBitmap = ExAllocatePoolWithTag(PagedPool,
                                              sizeof(RTL_BITMAP) + BitmapSize,
                                              TAG_MM);
    RtlInitializeBitMap(MiSessionIdBitmap, (PVOID)(MiSessionIdBitmap + 1), Size);
    RtlClearAllBits(MiSessionIdBitmap);
}

3.7 Session 生命周期图

复制代码
┌────────────┐
│  系统启动   │
└─────┬──────┘
      │
      ▼
┌──────────────────┐
│  MiInitialize    │
│  SessionIds      │  ← 初始化 ID 位图
└──────┬───────────┘
       │
       ▼
┌──────────────────┐
│  MmSessionCreate │  ← 创建 Session
│  │               │
│  ├─ 分配 Session │
│  │  ID 和数据页  │
│  ├─ 设置 PDE     │
│  └─ 初始化工作集  │
└──────┬───────────┘
       │
       ▼
┌──────────────────┐
│  MmAttachSession │  ← 进程附加到 Session
│  └─ 增加引用计数 │
└──────┬───────────┘
       │
       ▼
┌──────────────────┐
│  MmDetachSession │  ← 进程分离
│  └─ 减少引用计数 │
└──────┬───────────┘
       │
       ▼ (引用计数 = 0)
┌──────────────────────┐
│  MiDereference       │  ← 最终清理
│  SessionFinal        │
│  ├─ 设置 Delete     │
│  │  Pending         │
│  ├─ 等待分离完成    │
│  └─ 释放所有资源    │
└──────────────────────┘

4. wslist.cpp --- 工作集列表管理

位置: ntoskrnl/mm/ARM3/wslist.cpp

注意:这是 MM 子系统中唯一的 C++ 文件,使用了 C++ 特性(引用传递等)。

4.1 核心数据结构

c 复制代码
PMMWSL MmWorkingSetList;                      // 系统工作集列表
PMMWSL MmSystemCacheWorkingSetList;           // 系统缓存工作集列表
KEVENT MmWorkingSetManagerEvent;              // 工作集管理器事件

工作集列表条目(WSLE):

c 复制代码
typedef struct _MMWSLE {
    union {
        struct {
            ULONG_PTR Valid : 1;              // 有效位
            ULONG_PTR Locked : 1;             // 锁定位
            ULONG_PTR Cache : 1;              // 缓存位
            ULONG_PTR Protection : 5;          // 保护
            ULONG_PTR VirtualPageIndex : 56;   // 虚拟页索引
        } e1;
        struct {
            ULONG_PTR NextFree : 28;           // 下一个空闲条目
            ULONG_PTR PreviousFree : 28;       // 上一个空闲条目
            ULONG_PTR MustBeZero : 8;
        } Free;
        LONGLONG Long;
    } u1;
} MMWSLE, *PMMWSLE;

4.2 MiInitializeWorkingSetList

c 复制代码
NTSTATUS
NTAPI
MiInitializeWorkingSetList(PMMWSL WsList, ULONG_PTR WsListPage)

初始化工作集列表:

  1. 映射工作集列表页面
  2. 设置 ListHead、LastEntry、FirstFree 等字段
  3. 初始化分配位图

4.3 MiInsertInWorkingSetList

c 复制代码
ULONG_PTR
NTAPI
MiInsertInWorkingSetList(PMMWSL WsList, PMMWSLE Wsle)

在 WSL 中插入一个新的工作集条目:

  1. 从空闲链表获取一个槽位
  2. 写入 WSLE 数据
  3. 更新链表指针

4.4 MiRemoveFromWorkingSetList

c 复制代码
VOID
NTAPI
MiRemoveFromWorkingSetList(PMMWSL WsList, ULONG_PTR Index)

从 WSL 中移除一个条目:

  1. 标记为无效
  2. 插入空闲链表
  3. 如果移除的是最后一个条目,缩小列表
c 复制代码
static void FreeWsleIndex(PMMWSL WsList, ULONG Index)
{
    PMMWSLE Wsle = WsList->Wsle;
    ULONG& LastEntry = WsList->LastEntry;
    ULONG& FirstFree = WsList->FirstFree;
    ULONG& LastInitializedWsle = WsList->LastInitializedWsle;

    Wsle[Index].u1.Long = 0;  // 擦除条目

    if (Index == (LastEntry - 1))
    {
        // 释放最后一个条目,缩小列表
        while (Wsle[Index].u1.e1.Valid == 0) Index--;
        LastEntry = Index + 1;

        // 收缩已分配的内存
        while ((LastInitializedWsle - LastEntry) > (PAGE_SIZE / sizeof(MMWSLE)))
        {
            // 释放一页
            PFN_NUMBER Page = PFN_FROM_PTE(PointerPte);
            ntoskrnl::MiPfnLockGuard PfnLock;
            PMMPFN Pfn = MiGetPfnEntry(Page);
            MI_SET_PFN_DELETED(Pfn);
            MiDecrementShareCount(...);
        }
    }
}

4.5 MmWorkingSetManager 工作集管理器

c 复制代码
NTSTATUS
NTAPI
MmWorkingSetManager(ULONG_PTR Parameter)

工作集管理器负责在内存压力下修剪工作集:

复制代码
MmWorkingSetManager
    │
    ├─ 等待事件(MmWorkingSetManagerEvent)
    │
    ├─ 遍历所有进程的工作集
    │
    ├─ 计算需要修剪的页面数
    │
    ├─ 修剪(Trim)
    │   └─ 从 WSL 中移除页面,放回备用列表
    │
    └─ 重复

4.6 TrimWsList 工作集修剪

修剪算法的核心:遍历工作集列表,将最近未访问的页面换出。

复制代码
TrimWsList(WsList, Target)
    │
    ├─ 遍历 WSLE 条目
    │
    ├─ 检查 Accessed 位
    │   ├─ 已访问 → 清除 Accessed 位,跳过
    │   └─ 未访问 → 准备换出
    │
    ├─ 锁定页面(防止正在使用)
    │
    ├─ 为页面分配页文件槽位
    │
    ├─ 写入页面内容到交换文件
    │
    ├─ 从工作集移除
    │   └─ MiRemoveFromWorkingSetList
    │
    └─ 更新统计

5. balance.c --- 内存平衡器

位置: ntoskrnl/mm/balance.c

5.1 核心数据结构

c 复制代码
MM_MEMORY_CONSUMER MiMemoryConsumers[MC_MAXIMUM];  // 消费者数组
static ULONG MiMinimumAvailablePages = 256;          // 最小可用页数
static ULONG MiMinimumPagesPerRun = 256;             // 每轮最少回收页数
static KEVENT MiBalancerEvent;                       // 平衡器事件
static KEVENT MiBalancerDoneEvent;                   // 完成事件
static KTIMER MiBalancerTimer;                       // 定时器

// 消费者类型
#define MC_USER        0   // 用户进程
#define MC_MAXIMUM     1

5.2 内存消费者

c 复制代码
typedef struct _MM_MEMORY_CONSUMER {
    ULONG PagesUsed;             // 已使用页数
    ULONG PagesTarget;           // 目标页数
    NTSTATUS (*Trim)(ULONG Target, ULONG Priority, PULONG NrFreed); // 修剪回调
    ULONG MinimumPages;          // 最小页数
} MM_MEMORY_CONSUMER;

初始化:

c 复制代码
CODE_SEG("INIT")
VOID NTAPI MmInitializeBalancer(ULONG NrAvailablePages, ULONG NrSystemPages)
{
    memset(MiMemoryConsumers, 0, sizeof(MiMemoryConsumers));
    MiMinimumAvailablePages = 256;
    MiMinimumPagesPerRun = 256;
    MiMemoryConsumers[MC_USER].PagesTarget = NrAvailablePages / 2;
}

5.3 MiBalancerThread 平衡器线程主循环

c 复制代码
MiBalancerThread(PVOID Unused)
{
    PVOID WaitObjects[2];

    WaitObjects[0] = &MiBalancerEvent;     // 手动触发
    WaitObjects[1] = &MiBalancerTimer;     // 定时触发

    while (TRUE)
    {
        KeSetEvent(&MiBalancerDoneEvent, IO_NO_INCREMENT, FALSE);

        // 等待事件或定时器
        KeWaitForMultipleObjects(2, WaitObjects, ...);

        // 计算需要回收的页面数
        Target = 256;

        // 遍历消费者
        MiTrimMemoryConsumer(MC_USER, Target);
    }
}

5.4 MmTrimUserMemory 用户内存修剪

c 复制代码
NTSTATUS
MmTrimUserMemory(ULONG Target, ULONG Priority, PULONG NrFreedPages)

两种模式:

  • Priority = 0:仅清除 Accessed 位(不实际换出)

  • Priority = 1 :实际换出页面(调用 MmPageOutPhysicalAddress

    MmTrimUserMemory

    ├─ Priority = 0 (低优先级)
    │ └─ 遍历用户页面的反向映射
    │ └─ Pte->u.Hard.Accessed = 0 (清除访问位)

    └─ Priority = 1 (高优先级)
    └─ 调用 MmPageOutPhysicalAddress 换出页面
    ├─ 获取反向映射条目
    ├─ 锁定地址空间
    ├─ 写入交换文件
    └─ 释放物理页面

5.5 MmRebalanceMemoryConsumers 重新平衡

c 复制代码
VOID
NTAPI
MmRebalanceMemoryConsumers(VOID)
{
    // 如果平衡器不在运行,触发它
    if (InterlockedCompareExchange(&PageOutThreadActive, 1, 0) == 0)
    {
        KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);
    }
}

同步版本:

c 复制代码
VOID
NTAPI
MmRebalanceMemoryConsumersAndWait(VOID)
{
    KeResetEvent(&MiBalancerDoneEvent);
    MmRebalanceMemoryConsumers();
    KeWaitForSingleObject(&MiBalancerDoneEvent, ...);
}

5.6 平衡器流程图

复制代码
┌──────────────────────────┐
│    系统初始化             │
│  MmInitializeBalancer     │
│  └─ 设置目标页数         │
└──────────┬───────────────┘
           │
           ▼
┌──────────────────────────┐
│  创建平衡器线程           │
│  PsCreateSystemThread    │
│  → MiBalancerThread      │
└──────────┬───────────────┘
           │
           ▼
     ┌──────────┐
     │  等待事件  │ ←──────┐
     │  (定时/触发)│        │
     └─────┬────┘          │
           │               │
           ▼               │
     ┌──────────┐          │
     │ 计算目标   │          │
     │ Target=256│          │
     └─────┬────┘          │
           │               │
           ▼               │
     ┌──────────┐          │
     │  MiTrim  │          │
     │ Consumer │          │
     └─────┬────┘          │
           │               │
           ▼               │
     ┌──────────┐          │
     │ 设置完成   │─────────┘
     │ 事件      │
     └──────────┘

6. 三个子系统的关系

复制代码
┌─────────────────────────────────────────────────────┐
│                    balance.c                          │
│              MiBalancerThread                        │
│                    │                                  │
│    MmRebalanceMemoryConsumers                        │
│                    │                                  │
└────────────────────┼─────────────────────────────────┘
                     │ 触发
                     ▼
┌─────────────────────────────────────────────────────┐
│                    wslist.cpp                         │
│              MmWorkingSetManager                     │
│                    │                                  │
│         Trim → MiRemoveFromWorkingSetList            │
│                    │                                  │
└────────────────────┼─────────────────────────────────┘
                     │ 页面回收
                     ▼
┌─────────────────────────────────────────────────────┐
│                    procsup.c                         │
│   MmCreateProcessAddressSpace                       │
│   MmCreatePeb / MmCreateTeb                         │
│   MmCreateKernelStack                               │
│                    │                                  │
│                    ▼                                  │
│   MmDeleteProcessAddressSpace                        │
│                    │                                  │
└────────────────────┼─────────────────────────────────┘
                     │ Session 管理
                     ▼
┌─────────────────────────────────────────────────────┐
│                    session.c                          │
│   MmSessionCreate → MmAttachSession                  │
│   MmSessionDelete → MmDetachSession                  │
│   MiSessionCreateInternal                            │
└─────────────────────────────────────────────────────┘

关键交互路径:

  1. 进程创建MmCreateProcessAddressSpace → 分配页目录/工作集页
  2. Session 创建MmSessionCreateMiSessionCreateInternal → 分配 Session 空间
  3. 内存压力MmRebalanceMemoryConsumersMiBalancerThreadTrim → 页面回收
  4. 工作集修剪MmWorkingSetManagerTrimWsList → 页面换出

参考链接

  • procsup.c --- 进程地址空间管理(file:///d:/reactos/ntoskrnl/mm/ARM3/procsup.c)
  • session.c --- Session 管理(file:///d:/reactos/ntoskrnl/mm/ARM3/session.c)
  • wslist.cpp --- 工作集列表(file:///d:/reactos/ntoskrnl/mm/ARM3/wslist.cpp)
  • balance.c --- 内存平衡器(file:///d:/reactos/ntoskrnl/mm/balance.c)
  • Windows Internals 7th Part 1, Chapter 10 "Memory Management"

相关推荐
Albart5751 小时前
2026年Python入门路线图:从零到实战的30天逐日详解(三)
学习·flask·#python30 天入门·python 实战教程·pytest 单元测试·python 虚拟环境·logging 日志
春生野草1 小时前
C基础--八大排序(个人笔记)
c语言·笔记·排序算法
星恒随风2 小时前
C++ 继承进阶:默认成员函数、多继承、虚继承与组合设计
开发语言·c++·笔记·学习
电子云与长程纠缠2 小时前
UE中使用TGuardValue与TInlineComponentArray数据结构
开发语言·数据结构·学习·ue5·游戏引擎
foo1st3 小时前
MySQL 8.0(Windows)升级笔记
数据库·笔记·mysql
caimouse3 小时前
MM 学习笔记 10:页面文件与系统加载器
笔记·学习·reactos
一只小菜鸡..3 小时前
南京大学 操作系统 (JYY) 学习笔记:Hello, OS World! (程序的机器视角)
java·笔记·学习
lazy H4 小时前
从入门到日常开发,一篇文章掌握 Git 核心操作
git·后端·学习·github
Ameilide13 小时前
51单片机学习 day1
嵌入式硬件·学习·51单片机