dumpsys meminfo 流程中细节

源码基于:Android U

参考:

dumpsys meminfo 详解(R)

dumpsys meminfo 详解(U)

0. 前言

之所以单独开这一篇博文,主要是前面详解地剖析了 dumpsys meminfo 的整个流程, 这样导致了博文篇幅太长了,查找起来可能抓不到想要的重点。本篇博文对前两篇进行梳理,将其中的关于dumpsys meminfo 的细节进行提炼。

1. 命令入口 MemBinder

frameworks/base/services/core/java/com/android/server/am/AMS.javastatic class MemBinder extends Binder {ActivityManagerService mActivityManagerService;MemBinder(ActivityManagerService activityManagerService) {mActivityManagerService = activityManagerService;}@Overrideprotected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {...}}

入口函数,详细的代码可以查看 dumpsys meminfo 详解(U) 一文。 

2. 根据参数收集应用层 procs

dump 流程首先会调用 collectProcesses() 函数来解析需要dump 的进程 ProcessRecord,并且会将该 ProcessRecord list 以参数形式传入 dumpApplicationMemoryUsage() 函数,这也是 dump 的核心处理函数。

frameworks/base/services/core/java/com/android/server/am/AMS.javaArrayList<ProcessRecord> collectProcesses(PrintWriter pw, int start, boolean allPkgs,String[] args) {synchronized (mProcLock) {return mProcessList.collectProcessesLOSP(start, allPkgs, args);}}

详细的代码可以查看 dumpsys meminfo 详解(U) 一文。

代码还是比较清晰的:

  • 如果命令行指定了pid,那么就收集这些进程;
  • 如果设定了packageName,就收集这些package;
  • 如果没有设定,则收集所有的 LRU process;

注意,前两点有可能收集的 proc 为空,因为设定的参数有可能是假的或者无法匹配。

这个时候终端上还提示No process:

shift:/ # dumpsys meminfo 12345
No process found for: 12345

3. 根据参数收集native层procs

在 dumpApplicationMemoryUsage() 函数中会看到这样一部分代码:

frameworks/base/services/core/java/com//android/server/am/AMS.javaif (collectNative) {mi = null;final Debug.MemoryInfo[] memInfos = new Debug.MemoryInfo[1];mAppProfiler.forAllCpuStats((st) -> {if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) {...}});ArrayList<MemItem> catMems = new ArrayList<MemItem>();catMems.add(new MemItem("Native", "Native",ss[INDEX_NATIVE_PSS], ss[INDEX_NATIVE_SWAP_PSS], ss[INDEX_NATIVE_RSS], -1));...}

这部分代码是继处理应用层 LRU procs 之后,接着dump native 进程 procs 的内存。

其中入口需要 collectNative 为true,来看下初始化的地方:

final boolean collectNative = !opts.isCheckinRequest && numProcs > 1 && !opts.packages;

只要命令行没有配置 --checkin 参数,且没有配置 --package 参数,且LRU 的proc 大于1,则该值为 true。

其实,当dump 多个应用或者所有应用的 meminfo 时,变量 collectNative 被置true;如果只是打印单个应用的 meminfo 时该值为 false,所以最终不会打印。即,collectNative 也是用来区分 dump 所有应用meminfo 还是dump 单个应用的 meminfo。

当 collectNative 为true 时,另外一个很重要的事情就是通过 MemInfoReader.readMemInfo() 获取系统/proc/meminfo 的内存信息。详细的代码可以查看 dumpsys meminfo 详解(U) 一文。

4. --oom 影响内存收集

当收集好 procs 之后,会通过 for 循环进行轮询,通过Debug 提供的内存获取函数,分别统计每个 pid 对应的 smaps 信息。

但,收集内存的函数有两个选择:

  • Debug.getMemoryInfo() 
  • Debug.getPss()
		if (!brief && !opts.oomOnly) {...if (!Debug.getMemoryInfo(st.pid, info)) {return;}} else {long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp);...}

brief 是从 MemBinder 传入,默认为 false;

opts.oomOnly 是命令行是否带有 --oom 参数的flag,如果不带则调用 getMemoryInfo() 函数,如果带有则调用 getPss() 函数。区别在于 oomOnly 如果为 true时,不需要dump category 信息,而当 oomOnly 为false 时,需要 dump category 信息:

Total PSS by category:330,928K: EGL mtrack270,815K: Native240,812K: Dalvik168,739K: .apk mmap154,683K: .art mmap140,548K: .so mmap109,070K: .dex mmap99,964K: GL mtrack92,736K: Dalvik Other44,257K: .jar mmap36,406K: Other mmap33,019K: Stack31,307K: Unknown15,729K: .oat mmap1,670K: Other dev1,013K: Ashmem848K: .ttf mmap0K: Cursor0K: Gfx dev0K: Other mtrack

所以,getMemoryInfo() 中会统计更详细的每个 which_heap 的 pss、rss、swap 等等信息,而 getPss() 函数则只需要统计每个进程的 rss、pss、swap 整体信息即可。

4.1 Debug.getMemoryInfo()

frameworks/base/core/jni/android_os_Debug.cppstatic jboolean android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,jint pid, jobject object)
{bool foundSwapPss;stats_t stats[_NUM_HEAP];memset(&stats, 0, sizeof(stats));if (!load_maps(pid, stats, &foundSwapPss)) {return JNI_FALSE;}struct graphics_memory_pss graphics_mem;if (read_memtrack_memory(pid, &graphics_mem) == 0) {stats[HEAP_GRAPHICS].pss = graphics_mem.graphics;stats[HEAP_GRAPHICS].privateDirty = graphics_mem.graphics;stats[HEAP_GRAPHICS].rss = graphics_mem.graphics;stats[HEAP_GL].pss = graphics_mem.gl;stats[HEAP_GL].privateDirty = graphics_mem.gl;stats[HEAP_GL].rss = graphics_mem.gl;stats[HEAP_OTHER_MEMTRACK].pss = graphics_mem.other;stats[HEAP_OTHER_MEMTRACK].privateDirty = graphics_mem.other;stats[HEAP_OTHER_MEMTRACK].rss = graphics_mem.other;}for (int i=_NUM_CORE_HEAP; i<_NUM_EXCLUSIVE_HEAP; i++) {stats[HEAP_UNKNOWN].pss += stats[i].pss;stats[HEAP_UNKNOWN].swappablePss += stats[i].swappablePss;stats[HEAP_UNKNOWN].rss += stats[i].rss;stats[HEAP_UNKNOWN].privateDirty += stats[i].privateDirty;stats[HEAP_UNKNOWN].sharedDirty += stats[i].sharedDirty;stats[HEAP_UNKNOWN].privateClean += stats[i].privateClean;stats[HEAP_UNKNOWN].sharedClean += stats[i].sharedClean;stats[HEAP_UNKNOWN].swappedOut += stats[i].swappedOut;stats[HEAP_UNKNOWN].swappedOutPss += stats[i].swappedOutPss;}for (int i=0; i<_NUM_CORE_HEAP; i++) {env->SetIntField(object, stat_fields[i].pss_field, stats[i].pss);env->SetIntField(object, stat_fields[i].pssSwappable_field, stats[i].swappablePss);env->SetIntField(object, stat_fields[i].rss_field, stats[i].rss);env->SetIntField(object, stat_fields[i].privateDirty_field, stats[i].privateDirty);env->SetIntField(object, stat_fields[i].sharedDirty_field, stats[i].sharedDirty);env->SetIntField(object, stat_fields[i].privateClean_field, stats[i].privateClean);env->SetIntField(object, stat_fields[i].sharedClean_field, stats[i].sharedClean);env->SetIntField(object, stat_fields[i].swappedOut_field, stats[i].swappedOut);env->SetIntField(object, stat_fields[i].swappedOutPss_field, stats[i].swappedOutPss);}env->SetBooleanField(object, hasSwappedOutPss_field, foundSwapPss);jintArray otherIntArray = (jintArray)env->GetObjectField(object, otherStats_field);jint* otherArray = (jint*)env->GetPrimitiveArrayCritical(otherIntArray, 0);if (otherArray == NULL) {return JNI_FALSE;}int j=0;for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) {otherArray[j++] = stats[i].pss;otherArray[j++] = stats[i].swappablePss;otherArray[j++] = stats[i].rss;otherArray[j++] = stats[i].privateDirty;otherArray[j++] = stats[i].sharedDirty;otherArray[j++] = stats[i].privateClean;otherArray[j++] = stats[i].sharedClean;otherArray[j++] = stats[i].swappedOut;otherArray[j++] = stats[i].swappedOutPss;}env->ReleasePrimitiveArrayCritical(otherIntArray, otherArray, 0);return JNI_TRUE;
}
  • 通过 load_maps() 从 /proc/pid/smaps 中获取进程的内存信息;
  • 通过 read_memtrack_memory() 通过 libmemtrack.so 获取 graphics 的内存信息;
  • 通过 env->SetIntField() 将 UNKNOWNdalvikheap 所属组的信息设置到 java 接口的参数 Meminfo 中;
  • 通过 otherArray 数组对应 java 端 MemoryInfo.otherStats[] 数组;

注意:

除了 HEAP_DALVIK 和 HEAP_NATIVE,其他所有 which_heap 的信息都会累加到 HEAP_UNKNOWN 中。这样,在 AMS 中会统计重要的字段信息,其他都认为是 unknown,如下:

Total PSS by category:330,928K: EGL mtrack270,815K: Native240,812K: Dalvik168,739K: .apk mmap154,683K: .art mmap140,548K: .so mmap109,070K: .dex mmap99,964K: GL mtrack92,736K: Dalvik Other44,257K: .jar mmap36,406K: Other mmap33,019K: Stack31,307K: Unknown15,729K: .oat mmap1,670K: Other dev1,013K: Ashmem848K: .ttf mmap0K: Cursor0K: Gfx dev0K: Other mtrack

4.2 Debug.getPss()

frameworks/base/core/jni/android_os_Debug.cppstatic jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid,jlongArray outUssSwapPssRss, jlongArray outMemtrack)
{jlong pss = 0;jlong rss = 0;jlong swapPss = 0;jlong uss = 0;jlong memtrack = 0;struct graphics_memory_pss graphics_mem;if (read_memtrack_memory(pid, &graphics_mem) == 0) {pss = uss = rss = memtrack = graphics_mem.graphics + graphics_mem.gl + graphics_mem.other;}::android::meminfo::ProcMemInfo proc_mem(pid);::android::meminfo::MemUsage stats;if (proc_mem.SmapsOrRollup(&stats)) {pss += stats.pss;uss += stats.uss;rss += stats.rss;swapPss = stats.swap_pss;pss += swapPss; // Also in swap, those pages would be accounted as Pss without SWAP} else {return 0;}if (outUssSwapPssRss != NULL) {int outLen = env->GetArrayLength(outUssSwapPssRss);if (outLen >= 1) {jlong* outUssSwapPssRssArray = env->GetLongArrayElements(outUssSwapPssRss, 0);if (outUssSwapPssRssArray != NULL) {outUssSwapPssRssArray[0] = uss;if (outLen >= 2) {outUssSwapPssRssArray[1] = swapPss;}if (outLen >= 3) {outUssSwapPssRssArray[2] = rss;}}env->ReleaseLongArrayElements(outUssSwapPssRss, outUssSwapPssRssArray, 0);}}if (outMemtrack != NULL) {int outLen = env->GetArrayLength(outMemtrack);if (outLen >= 1) {jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0);if (outMemtrackArray != NULL) {outMemtrackArray[0] = memtrack;if (outLen >= 2) {outMemtrackArray[1] = graphics_mem.graphics;}if (outLen >= 3) {outMemtrackArray[2] = graphics_mem.gl;}if (outLen >= 4) {outMemtrackArray[3] = graphics_mem.other;}}env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0);}}return pss;
}
  • 通过 read_memtrace_memory() 统计graphics 的内存信息,并都累计到总的 pss、rss、uss、memtrack 中;
  • 通过 proc_mem.SmapsOrRollup() 函数统计 /proc/pid/smaps 中 Pss、Private_Clean、Private_Dirty、Rss、SwapPss 等信息;
  • 该函数返回的是进程的 pss,包括 swapPss。其他数据通过两个数组参数回传到 java端:
    • tmpLong 的三个元素分别是:uss、swap pss、rss;
    • memtrackTmp 的四个元素分别是:memtrack 总和、graphics、gl、other;

注意:

无论 getMemoryInfo() 之后在 AMS 中计算,还是getPss() 在 android_os_Debug.cpp 中计算,total 的pss,都是需要加上 swap out pss;

5. graphics 的内存收集函数

如上,无论是 getMemoryInfo() 还是 getPss() 函数,都会调用 read_memtrace_memory() 函数收集 graphics 的内存。

主要是通过libmemtrack.so,详细可以查看 dumpsys meminfo 详解(U) 一文。

6. -a 或 -s 影响细节打印

opts.dumpDetails 是命令行是否带有 -a -s 参数的 flag,当带有该参数时需要收集进程的所有category 信息。所以,收集函数是调用 getMemoryInfo(),而不是 getPss()。

另外,当该 flag 为true时,dump 流程会调用 thread.dumpMemInfo() 函数,会通过 getRuntime() 获取app 进程 dalvik 的 totalMemory 和 freeMemory,并计算出 dalvikAllocated,得到app 进程虚拟机内存使用情况。 详细可以查看 dumpsys meminfo 详解(U) 一文。

最终结果如下图:

7. which_heap 的概念

whick_heap 的概念在 android_os_Debug.cpp 中提出,用以区分某 pid 应用的内存。在 native 端有个 enum:

frameworks/base/core/jni/android_os_Debug.cppenum {HEAP_UNKNOWN,HEAP_DALVIK,HEAP_NATIVE,HEAP_DALVIK_OTHER,HEAP_STACK,HEAP_CURSOR,HEAP_ASHMEM,HEAP_GL_DEV,HEAP_UNKNOWN_DEV,HEAP_SO,HEAP_JAR,HEAP_APK,HEAP_TTF,HEAP_DEX,HEAP_OAT,HEAP_ART,HEAP_UNKNOWN_MAP,HEAP_GRAPHICS,HEAP_GL,HEAP_OTHER_MEMTRACK,..._NUM_HEAP,_NUM_EXCLUSIVE_HEAP = HEAP_OTHER_MEMTRACK+1,_NUM_CORE_HEAP = HEAP_NATIVE+1
};

当然,该分类对应 Debug.MemoryInfo中有相同值的静态变量,如下:

frameworks/base/core/java/android/os/Debug.javapublic static final int HEAP_UNKNOWN = 0;/** @hide */public static final int HEAP_DALVIK = 1;/** @hide */public static final int HEAP_NATIVE = 2;/** @hide */public static final int OTHER_DALVIK_OTHER = 0;/** @hide */public static final int OTHER_STACK = 1;/** @hide */public static final int OTHER_CURSOR = 2;/** @hide */...

注意:

系统将 smaps 中的内存分成了三大块:dalviknative 以及 unknown,并且系统将 unknown 又细分了17 类。

7.1 dumpsys meminfo 中的category

Total PSS by category:260,780K: Native255,280K: EGL mtrack213,776K: Dalvik186,632K: .apk mmap131,867K: .so mmap110,157K: .dex mmap109,952K: GL mtrack87,703K: Dalvik Other79,525K: .art mmap47,593K: .jar mmap41,380K: Other mmap31,408K: Stack29,258K: Unknown17,319K: .oat mmap1,544K: Other dev887K: Ashmem510K: .ttf mmap0K: Cursor0K: Gfx dev0K: Other mtrack

其中,除了 Native、Dalvik 和 Unknown,其他的是 unknown 细分出来的 17 类。

而,上面的 Unknown 是从大块 Unknown去除其他 17 类之后的内存。

7.2 smaps 中vma 与 HEAP 枚举对照

对照表可以查看 dumpsys meminfo 详解(U) 一文。

8. 细分内存

8.1 total pss

totap pss 分两大块:应用进程的 total pss + native 进程的 total pss。每个进程的 total pss 有如下的公式:

total pss = dalvikPss + nativePss + otherPss + total swap out pss;

即,系统的 total pss 为所有应用进程的 total pss 与所有native 进程的 total pss 之和。

其中每个进程的 total swap out pss 满足:

frameworks/base/core/java/android/os/Debug.javapublic int getTotalSwappedOutPss() {return dalvikSwappedOutPss + nativeSwappedOutPss + otherSwappedOutPss;}

注意:

通过 load_maps() 统计的 pss 和 swap out pss 是分开统计、独立的,在计算 total pss 时需要将两者相加。

8.2 cached pss

cached pss 为应用进程中 oomAdj 大于等于 ProcessList.CACHED_APP_MIN_ADJ 的所有进程 total pss 之和。

8.3 used pss

在dump 的打印中会显示:

Used RAM: 3,686,777K (2,785,097K used pss +   901,680K kernel)

used pss + cached pss = total pss - EGL mtrack - GL mtrack + dmaMapped;

8.4 Total RAM

8.5 Free RAM

8.6  ION

8.7  DMA-BUF

8.8 GPU

8.9 Used RAM

8.10 Lost RAM

8.11 ZRAM

8.12 Tuning

详细可以查看 dumpsys meminfo 详解(U) 一文。

9. Lost RAM 为什么为负值?

 在Android12 之前:

totalPss 组件包含 GPU 内存用量,后者由 Memtrack HAL 的 getMemory() 接口返回。kernelUsed 组件包含 DMA-BUF 总内存用量。不过,对于 Android 设备,GPU 内存源自下列各项:

  • 由 GPU 驱动程序使用物理页面分配器进行的直接分配
  • 映射到 GPU 地址空间的 DMA-BUF

因此,在计算 RAM 损失时,系统会将内存映射到 GPU 地址空间的 DMA-BUFF 减去两次。

在Android12中:

Android 12 实施了一个解决方案,可以计算映射到 GPU 地址空间的 DMA-BUF 的大小,这意味着它在 RAM 损失计算中只计算一次。

该解决方案的详细信息如下:

  • 在使用 PID 0 进行调用时,Memtrack HAL API getMemory() 必须为 MemtrackType::GL 和 MemtrackRecord::FLAG_SMAPS_UNACCOUNTED 报告 GPU 全局专用总内存。
  • 在使用 PID 0 为 MemtrackType(而不是 GL)进行调用时,getMemory() 不得失败,而必须返回 0。
  • 在 Android 12 中添加的 GPU 内存跟踪点/eBPF 解决方案会占用 GPU 总内存。从 GPU 总内存中减去 GPU 专用总内存,即可得出映射到 GPU 地址空间的 DMA-BUF 的大小。然后,通过正确计算 GPU 内存用量,这个值可以用来提高 RAM 损失计算的准确性。
  • GPU 专用内存包含在大多数 Memtrack HAL 实现的 totalPss 中,因此必须先删除其中的重复信息,然后才能将其从 lostRAM 中移除。

虽然如此,但为什么在实际的统计过程中还是会出现负值呢?

根本原因是 dumpsys meminfo 不是当前系统内存的快照,而是一个相当复杂的长时间操作,每阶段的内存统计都有时间差:

  • 首先,查询每个应用进程的 smaps 并进行解析,这就是一个长期的过程;
  • 其次,需要统计 memtrack;
  • 再者,需要统计 native 进程的 smaps 并进行解析;
  • 最后,还需要统计 /proc/meminfo 内存信息;

Lost RAM 出现负值,尤其是在native 端内存使用紧张的时候,负值可能会很大。例如,刚统计完 LRU  procs 的进程信息, camera 启动并且会使用很大内存,此时只能通过lmkd 查杀掉一部分的应用以获取足够的内存,这样就导致 totalpss 的值过大,而计算 Lost RAM 做减法的时候可能存在负值。

 关联博文:

dumpsys meminfo 详解(R)

dumpsys meminfo 详解(U)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/687813.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git bash各分支修改内容不同但合并后不显示冲突问题

在跟着廖雪峰老师的git学习时&#xff0c;按部就班的执行明后&#xff0c;发现 而不是出现原文的结果 解决方法&#xff1a; 切换位feature分支&#xff0c;再合并 git switch feature1 git merge master 此时我们发现&#xff1a; 后面再跟着原文敲就可以了

LED显示屏的部件组成及相关知识

LED显示屏作为现代化信息传播的重要载体&#xff0c;在各种场所得到了广泛应用。其功能强大&#xff0c;效果生动&#xff0c;但其背后的部件组成却是复杂而精密的。本文将介绍LED显示屏的主要部件组成及相关知识&#xff0c;以帮助读者更好地理解LED显示屏的工作原理和构造。 …

华为昇腾310B1平台 [ERROR] Send frame to vdec failed, errorno:507018

目录 1 [ERROR] Send frame to vdec failed, errorno:507018 2 bug解决尝试1 3 bug解决尝试2 4 bug解决尝试3 附录&#xff1a;华为视频解码基本原理 1调用aclvdecCreateChannel接口创建视频码流数据处理的通道 2 调用aclvdecSendFrame接口将视频码流解码成YUV420SP格式…

网络端口占用问题的综合调研与解决方案

原创 Randy 拍码场 问题背景 去年底信息安全团队进行网络权限治理&#xff0c;要求所有应用实例使用静态IP&#xff0c;公网访问策略与静态IP绑定&#xff1b;之后实例重启时偶现“端口被占用”错误。通过分析总结应用日志&#xff0c;共有以下4种错误类型&#xff0c;实质都是…

用docker 搭建 vscode for web

前言: 每当我们换机子或者是电脑内容不够的时候&#xff0c;总想着能用web方式使用某些软件&#xff0c;这样子&#xff0c;你无论何时何地都能愉快的开发了&#xff0c;今天来安排下使用容器技术去搭建vscode。 查找合适的Docker镜像 你可以使用官方的Code Server Docker镜像…

信息化系统建设运维服务方案(投标)Word原件

《信息化系统运维服务方案》&#xff08;原件可获取&#xff09; 1.项目情况 2.服务简述 2.1服务内容 2.2服务方式 2.3服务要求 2.4服务流程 2.5工作流程 2.6业务关系 2.7培训 3.资源提供 3.1项目组成员 3.2服务保障 软件全套精华资料包清单部分文件列表&#xff1a; 工作安排任…

C#上位机1ms级高精度定时任务

precisiontimer 安装扩展包 添加引用 完整代码 using PrecisionTiming;using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; us…

1013: 哈希表(开放定址法处理冲突)

解法&#xff1a; 线性探测是一种解决哈希冲突的方法&#xff0c;当发生哈希冲突时&#xff0c;它会依次往后查找空的槽位&#xff0c;直到找到一个空的槽位或者达到数组的末尾。 下面是处理哈希冲突的线性探测的步骤&#xff1a; 创建一个哈希表&#xff0c;里面包含一定数量的…

MySQL的表级锁

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;面经 ⛺️稳中求进&#xff0c;晒太阳 表级锁 介绍 对于表锁&#xff0c;分为两类&#xff1a; 表共享读锁表独占写锁 语法 1. 加锁&#xff1a;lock tables 表名... read/write 2.…

SpringSecurity + JWT实现登录认证

前置基础请参考&#xff1a;SpringSecurity入门-CSDN博客 配置&#xff1a; pom.xml <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.5</version></p…

Xilinx 千兆以太网TEMAC IP核简介

Xilinx 公司提供了千兆以太网MAC控制器的可参数化LogiCORET™IP解决方案&#xff0c;通过这个IPCore可以实现FPGA与外部网络物理层芯片的互连。基于Xilinx FPGA 的以太网设计&#xff0c;大大降低了工程的设计复杂度&#xff0c;缩短了开发周期&#xff0c;加快了产品的面市速度…

【数据结构】第五讲:栈和队列

个人主页&#xff1a;深情秋刀鱼-CSDN博客 数据结构专栏&#xff1a;数据结构与算法 源码获取&#xff1a;数据结构: 上传我写的关于数据结构的代码 (gitee.com) 目录 一、栈 1.栈的定义 2.栈的实现 a.栈结构的定义 b.初始化 c.扩容 d.入栈 e.出栈 f.打印 g.取栈顶元素…