鸿蒙内核源码分析 (内存管理篇) | 虚拟内存全景图是怎样的

初始化整个内存

OsSysMemInitOsMainmain

从 main() 跟踪可看内存部分初始化是在 OsSysMemInit() 中完成的。

UINT32 OsSysMemInit(VOID)
{STATUS_T ret;OsKSpaceInit();//内核空间初始化ret = OsKHeapInit(OS_KHEAP_BLOCK_SIZE);// 内核动态内存初始化 512K   if (ret != LOS_OK) {VM_ERR("OsKHeapInit fail");return LOS_NOK;}OsVmPageStartup();// page初始化OsInitMappingStartUp();// 映射初始化ret = ShmInit();// 共享内存初始化if (ret < 0) {VM_ERR("ShmInit fail");  return LOS_NOK;}return LOS_OK;
}

鸿蒙虚拟内存整体布局图

[图片上传失败…(image-6e54a-1712499300486)]

// HarmonyOS 内核空间包含以下各段:
extern CHAR __int_stack_start; // 运行系统函数栈的开始地址
extern CHAR __rodata_start;  // ROM开始地址 只读
extern CHAR __rodata_end;  // ROM结束地址
extern CHAR __bss_start;  // bss开始地址
extern CHAR __bss_end;   // bss结束地址
extern CHAR __text_start;  // 代码区开始地址
extern CHAR __text_end;   // 代码区结束地址
extern CHAR __ram_data_start; // RAM开始地址 可读可写
extern CHAR __ram_data_end;  // RAM结束地址
extern UINT32 __heap_start;  // 堆区开始地址
extern UINT32 __heap_end;  // 堆区结束地址

内存一开始一张白纸,这些 extern 就是给它画大界线的,从哪到哪是属于什么段。这些值大小取决实际项目内存条的大小,不同的内存条,地址肯定会不一样,所以必须由外部提供,鸿蒙内核采用了 Linux 的段管理方式。结合上图对比以下的解释自行理解下位置。

BSS 段 (bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS 是英文 Block Started by Symbol 的简称。BSS 段属于静态内存分配。该段用于存储未初始化的全局变量或者是默认初始化为 0 的全局变量,它不占用程序文件的大小,但是占用程序运行时的内存空间。

data 段 该段用于存储初始化的全局变量,初始化为 0 的全局变量出于编译优化的策略还是被保存在 BSS 段。

细心的读者可能发现了,鸿蒙内核几乎所有的全局变量都没有赋初始化值或 NULL,这些变量经过编译后是放在了 BSS 段的,运行时占用内存空间,如此编译出来的 ELF 包就变小了。

.rodata 段,该段也叫常量区,用于存放常量数据,ro 就是 Read Only 之意。

text 段 是用于存放程序代码的,编译时确定,只读。更进一步讲是存放处理器的机器指令,当各个源文件单独编译之后生成目标文件,经连接器链接各个目标文件并解决各个源文件之间函数的引用,与此同时,还得将所有目标文件中的.text 段合在一起。

stack 栈段,是由系统负责申请释放,用于存储参数变量及局部变量以及函数的执行。

heap 段 它由用户申请和释放,申请时至少分配虚存,当真正存储数据时才分配相应的实存,释放时也并非立即释放实存,而是可能被重复利用。

内核空间是怎么初始化的?

LosMux g_vmSpaceListMux;//虚拟空间互斥锁,一般和g_vmSpaceList配套使用
LOS_DL_LIST_HEAD(g_vmSpaceList);//g_vmSpaceList把所有虚拟空间挂在一起,
LosVmSpace g_kVmSpace;    //内核空间地址
LosVmSpace g_vMallocSpace;//虚拟分配空间地址//鸿蒙内核空间有两个(内核进程空间和内核动态分配空间),共用一张L1页表
VOID OsKSpaceInit(VOID)
{OsVmMapInit();// 初始化互斥量OsKernVmSpaceInit(&g_kVmSpace, OsGFirstTableGet());// 初始化内核虚拟空间,OsGFirstTableGet 为L1表基地址OsVMallocSpaceInit(&g_vMallocSpace, OsGFirstTableGet());// 初始化动态分配区虚拟空间,OsGFirstTableGet 为L1表基地址
}//g_kVmSpace g_vMallocSpace 共用一个L1页表
//初始化内核堆空间
STATUS_T OsKHeapInit(size_t size)
{STATUS_T ret;VOID *ptr = NULL;/** roundup to MB aligned in order to set kernel attributes. kernel text/code/data attributes* should page mapping, remaining region should section mapping. so the boundary should be* MB aligned.*///向上舍入到MB对齐是为了设置内核属性。内核文本/代码/数据属性应该是页映射,其余区域应该是段映射,所以边界应该对齐。UINTPTR end = ROUNDUP(g_vmBootMemBase + size, MB);//用M是因为采用section mapping 鸿蒙内核源码分析(内存映射篇)有阐述size = end - g_vmBootMemBase;//ROUNDUP(0x00000200+512,1024) = 1024  ROUNDUP(0x00000201+512,1024) = 2048 此处需细品! ptr = OsVmBootMemAlloc(size);//因刚开机,使用引导分配器分配if (!ptr) {PRINT_ERR("vmm_kheap_init boot_alloc_mem failed! %d\n", size);return -1;}m_aucSysMem0 = m_aucSysMem1 = ptr;//内存池基地址,取名auc还用0和1来标识有何深意,一直没整明白, 哪位大神能告诉下?ret = LOS_MemInit(m_aucSysMem0, size);//初始化内存池if (ret != LOS_OK) {PRINT_ERR("vmm_kheap_init LOS_MemInit failed!\n");g_vmBootMemBase -= size;//分配失败时需归还size, g_vmBootMemBase是很野蛮粗暴的return ret;}LOS_MemExpandEnable(OS_SYS_MEM_ADDR);//地址可扩展return LOS_OK;
}

内核空间用了三个全局变量,其中一个是互斥 LosMux,IPC 部分会详细讲,这里先不展开。 比较有意思的是 LOS_DL_LIST_HEAD,看内核源码过程中经常会为这样的代码点头称赞,会心一笑。点赞!

#define LOS_DL_LIST_HEAD(list) LOS_DL_LIST list = { &(list), &(list) }

Page 是如何初始化的?

page 是映射的最小单位,是物理地址 <—> 虚拟地址映射的数据结构的基础

// page初始化
VOID OsVmPageStartup(VOID)
{struct VmPhysSeg *seg = NULL;LosVmPage *page = NULL;paddr_t pa;UINT32 nPage;INT32 segID;OsVmPhysAreaSizeAdjust(ROUNDUP((g_vmBootMemBase - KERNEL_ASPACE_BASE), PAGE_SIZE));//校正 g_physArea sizenPage = OsVmPhysPageNumGet();//得到 g_physArea 总页数g_vmPageArraySize = nPage * sizeof(LosVmPage);//页表总大小g_vmPageArray = (LosVmPage *)OsVmBootMemAlloc(g_vmPageArraySize);//申请页表存放区域OsVmPhysAreaSizeAdjust(ROUNDUP(g_vmPageArraySize, PAGE_SIZE));// g_physArea 变小OsVmPhysSegAdd();// 段页绑定OsVmPhysInit();// 加入空闲链表和设置置换算法,LRU(最近最久未使用)算法for (segID = 0; segID < g_vmPhysSegNum; segID++) {seg = &g_vmPhysSeg[segID];nPage = seg->size >> PAGE_SHIFT;for (page = seg->pageBase, pa = seg->start; page <= seg->pageBase + nPage;page++, pa += PAGE_SIZE) {OsVmPageInit(page, pa, segID);//page初始化}OsVmPageOrderListInit(seg->pageBase, nPage);// 页面分配的排序}
}

进程是如何申请内存的?

进程的主体是来自进程池,进程池是统一分配的,怎么创建进程池的去翻系列篇里的文章,所以创建一个进程的时候只需要分配虚拟内存 LosVmSpace,这里要分内核模式和用户模式下的申请。

//初始化进程的 用户空间 或 内核空间
//初始化PCB块
STATIC UINT32 OsInitPCB(LosProcessCB *processCB, UINT32 mode, UINT16 priority, UINT16 policy, const CHAR *name)
{UINT32 count;LosVmSpace *space = NULL;LosVmPage *vmPage = NULL;status_t status;BOOL retVal = FALSE;processCB->processMode = mode;//用户态进程还是内核态进程processCB->processStatus = OS_PROCESS_STATUS_INIT;//进程初始状态processCB->parentProcessID = OS_INVALID_VALUE;//爸爸进程,外面指定processCB->threadGroupID = OS_INVALID_VALUE;//所属线程组processCB->priority = priority;//优先级processCB->policy = policy;//调度算法 LOS_SCHED_RRprocessCB->umask = OS_PROCESS_DEFAULT_UMASK;//掩码processCB->timerID = (timer_t)(UINTPTR)MAX_INVALID_TIMER_VID;LOS_ListInit(&processCB->threadSiblingList);//初始化任务/线程链表LOS_ListInit(&processCB->childrenList);        //初始化孩子链表LOS_ListInit(&processCB->exitChildList);    //初始化记录哪些孩子退出了的链表    LOS_ListInit(&(processCB->waitList));        //初始化等待链表for (count = 0; count < OS_PRIORITY_QUEUE_NUM; ++count) { //根据 priority数 创建对应个数的队列LOS_ListInit(&processCB->threadPriQueueList[count]);  }if (OsProcessIsUserMode(processCB)) {// 是否为用户态进程space = LOS_MemAlloc(m_aucSysMem0, sizeof(LosVmSpace));if (space == NULL) {PRINT_ERR("%s %d, alloc space failed\n", __FUNCTION__, __LINE__);return LOS_ENOMEM;}VADDR_T *ttb = LOS_PhysPagesAllocContiguous(1);//分配一个物理页用于存储L1页表 4G虚拟内存分成 (4096*1M)if (ttb == NULL) {//这里直接获取物理页ttbPRINT_ERR("%s %d, alloc ttb or space failed\n", __FUNCTION__, __LINE__);(VOID)LOS_MemFree(m_aucSysMem0, space);return LOS_ENOMEM;}(VOID)memset_s(ttb, PAGE_SIZE, 0, PAGE_SIZE);retVal = OsUserVmSpaceInit(space, ttb);//初始化虚拟空间和本进程 mmuvmPage = OsVmVaddrToPage(ttb);//通过虚拟地址拿到pageif ((retVal == FALSE) || (vmPage == NULL)) {//异常处理PRINT_ERR("create space failed! ret: %d, vmPage: %#x\n", retVal, vmPage);processCB->processStatus = OS_PROCESS_FLAG_UNUSED;//进程未使用,干净(VOID)LOS_MemFree(m_aucSysMem0, space);//释放虚拟空间LOS_PhysPagesFreeContiguous(ttb, 1);//释放物理页,4Kreturn LOS_EAGAIN;}processCB->vmSpace = space;//设为进程虚拟空间LOS_ListAdd(&processCB->vmSpace->archMmu.ptList, &(vmPage->node));//将空间映射页表挂在 空间的mmu L1页表, L1为表头} else {processCB->vmSpace = LOS_GetKVmSpace();//内核共用一个虚拟空间,内核进程 常驻内存}#ifdef LOSCFG_SECURITY_VIDstatus = VidMapListInit(processCB);if (status != LOS_OK) {PRINT_ERR("VidMapListInit failed!\n");return LOS_ENOMEM;}
#endif
#ifdef LOSCFG_SECURITY_CAPABILITYOsInitCapability(processCB);
#endifif (OsSetProcessName(processCB, name) != LOS_OK) {return LOS_ENOMEM;}return LOS_OK;
}
LosVmSpace *LOS_GetKVmSpace(VOID)
{return &g_kVmSpace;
}

从代码可以看出,内核空间固定只有一个 g_kVmSpace,而每个用户进程的虚拟内存空间都是独立的。请细品!

task 是如何申请内存的?

task 的主体是来自进程池,task 池是统一分配的,怎么创建 task 池的去翻系列篇里的文章。这里 task 只需要申请 stack 空间,还是直接上看源码吧,用 OsUserInitProcess 函数看应用程序的 main () 是如何被内核创建任务和运行的。

//所有的用户进程都是使用同一个用户代码段描述符和用户数据段描述符,它们是__USER_CS和__USER_DS,也就是每个进程处于用户态时,它们的CS寄存器和DS寄存器中的值是相同的。当任何进程或者中断异常进入内核后,都是使用相同的内核代码段描述符和内核数据段描述符,它们是__KERNEL_CS和__KERNEL_DS。这里要明确记得,内核数据段实际上就是内核态堆栈段。
LITE_OS_SEC_TEXT_INIT UINT32 OsUserInitProcess(VOID)
{INT32 ret;UINT32 size;TSK_INIT_PARAM_S param = { 0 };VOID *stack = NULL;VOID *userText = NULL;CHAR *userInitTextStart = (CHAR *)&__user_init_entry;//代码区开始位置 ,所有进程CHAR *userInitBssStart = (CHAR *)&__user_init_bss;// 未初始化数据区(BSS)。在运行时改变其值CHAR *userInitEnd = (CHAR *)&__user_init_end;// 结束地址UINT32 initBssSize = userInitEnd - userInitBssStart;UINT32 initSize = userInitEnd - userInitTextStart;LosProcessCB *processCB = OS_PCB_FROM_PID(g_userInitProcess);ret = OsProcessCreateInit(processCB, OS_USER_MODE, "Init", OS_PROCESS_USERINIT_PRIORITY);// 初始化用户进程,它将是所有应用程序的父进程if (ret != LOS_OK) {return ret;}userText = LOS_PhysPagesAllocContiguous(initSize >> PAGE_SHIFT);// 分配连续的物理页if (userText == NULL) {ret = LOS_NOK;goto ERROR;}(VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);// 安全copy 经加载器load的结果 __user_init_load_addr -> userTextret = LOS_VaddrToPaddrMmap(processCB->vmSpace, (VADDR_T)(UINTPTR)userInitTextStart, LOS_PaddrQuery(userText),initSize, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |VM_MAP_REGION_FLAG_PERM_EXECUTE | VM_MAP_REGION_FLAG_PERM_USER);// 虚拟地址与物理地址的映射if (ret < 0) {goto ERROR;}(VOID)memset_s((VOID *)((UINTPTR)userText + userInitBssStart - userInitTextStart), initBssSize, 0, initBssSize);// 除了代码段,其余都清0stack = OsUserInitStackAlloc(g_userInitProcess, &size);// 初始化堆栈区if (stack == NULL) {PRINTK("user init process malloc user stack failed!\n");ret = LOS_NOK;goto ERROR;}param.pfnTaskEntry = (TSK_ENTRY_FUNC)userInitTextStart;// 从代码区开始执行,也就是应用程序main 函数的位置param.userParam.userSP = (UINTPTR)stack + size;// 指向栈底param.userParam.userMapBase = (UINTPTR)stack;// 栈顶param.userParam.userMapSize = size;// 栈大小param.uwResved = OS_TASK_FLAG_PTHREAD_JOIN;// 可结合的(joinable)能够被其他线程收回其资源和杀死ret = OsUserInitProcessStart(g_userInitProcess, &param);// 创建一个任务,来运行main函数if (ret != LOS_OK) {(VOID)OsUnMMap(processCB->vmSpace, param.userParam.userMapBase, param.userParam.userMapSize);goto ERROR;}return LOS_OK;ERROR:(VOID)LOS_PhysPagesFreeContiguous(userText, initSize >> PAGE_SHIFT);//释放物理内存块OsDeInitPCB(processCB);//删除PCB块return ret;
}

所有的用户进程都是通过 init 进程 fork 来的, 可以看到创建进程的同时创建了一个 task, 入口函数就是代码区的第一条指令,也就是应用程序 main 函数。这里再说下 stack 的大小,不同空间下的 task 栈空间是不一样的,鸿蒙内核中有三种栈空间 size,如下

#define LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE SIZE(0x800)//内核进程,运行在内核空间2K
#define OS_USER_TASK_SYSCALL_SATCK_SIZE 0x3000 //用户进程,通过系统调用创建的task运行在内核空间的 12K
#define OS_USER_TASK_STACK_SIZE         0x100000//用户进程运行在用户空间的1M

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

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

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

相关文章

【Redis】MISCONF Redis is configured to save RDB snapshots报错解决方案

【Redis】MISCONF Redis is configured to save RDB snapshots报错解决方案 大家好 我是寸铁&#x1f44a; 总结了一篇【Redis】MISCONF Redis is configured to save RDB snapshots报错解决方案✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天在登录redis时&#xff0c…

容器和K8s常见概念

【容器】 1、Open Container Initiative&#xff08;OCI&#xff09;&#xff1a;制定和推动容器格式和运行时的开放标准。容器运行时需要遵循此标准。主要的产出物包括&#xff1a; OCI Image Specification: 定义容器镜像格式的规范&#xff0c;统一描述容器镜像的内容和结…

mysql 连接查询和子查询

学习了mysql基本查询&#xff0c; 接着学习连接查询和子查询。 4&#xff0c;连接查询 连接是关系数据库模型的主要特点。连接查询是关系数据库中最主要的查询&#xff0c;主要包括内连接、外连接等。通过连接运算符可以实现多个表查询。在关系数据库管理系统中&#xff0c;表建…

X年后,ChatGPT会替代底层程序员吗?

能不能替代&#xff0c;真的很难说&#xff0c;因为机器换掉人&#xff0c;这其实是一个伦理问题。 其实说白了&#xff0c;任何行业在未来都会被AI或多或少的冲击到&#xff0c;因为ChatGPT做为一个可以持续提升智能的AI&#xff0c;在某些方面的智能程度超过人类并不是什么难…

练习 17 Web [极客大挑战 2019]PHP

常见的网站源码备份文件名和后缀&#xff0c;反序列化攻击 unserialize()&#xff1a;wakeup绕过&#xff0c;private类以及属性序列化后的%00修改 开靶机 提到”备份“ 那看看有没有backup.php啥的 如果网站存在备份文件&#xff0c;常见的备份文件后缀名有&#xff1a;“.gi…

ExpiringMap使用详解

一、ExpiringMap概述 ExpiringMap项目地址&#xff1a;https://github.com/jhalterman/expiringmap 1、ExpiringMap简介 针对一些小体量的项目&#xff0c;存储的数据量也不是很大&#xff08;如校验码&#xff09;的情况下&#xff0c;使用 Redis会增加系统的复杂性和维护难度…

【论文阅读笔记】Customized Segment Anything Model for Medical Image Segmentation

1.论文介绍 Customized Segment Anything Model for Medical Image Segmentation 医学图像分割的自定义分割模型 2023年 arXiv Paper Code 2.摘要 本文提出SAMed&#xff0c;医学图像分割的一般解决方案。与以往的方法不同&#xff0c;SAMed基于大规模图像分割模型Segment A…

探索数据结构:特殊的双向队列

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty’s blog 1. 双向队列的定义 **双向队列(double‑ended queue)**是一种特殊的队列…

【并发编程系列】使用 CompletableFuture 实现并发任务处理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

python买铅笔 2024年3月青少年电子学会等级考试 中小学生python编程等级考试一级真题答案解析

目录 python买铅笔 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python买铅笔 2024年3月 python编程等级考试级编程题 一、题目要求 1、编…

Bigtable [OSDI‘06] 论文阅读笔记

原论文&#xff1a;Bigtable: A Distributed Storage System for Structured Data (OSDI’06) 1. Introduction Bigtable 是一种用于管理结构化数据的分布式存储系统&#xff0c;可扩展到非常大的规模&#xff1a;数千台服务器上的数据量可达 PB 级别&#xff0c;同时保证可靠…

《QT实用小工具·十七》密钥生成工具

1、概述 源码放在文章末尾 该项目主要用于生成密钥&#xff0c;下面是demo演示&#xff1a; 项目部分代码如下&#xff1a; #pragma execution_character_set("utf-8")#include "frmmain.h" #include "ui_frmmain.h" #include "qmessag…

js计算器实现

文章目录 1. 演示效果2. 分析思路3. 代码实现 1. 演示效果 2. 分析思路 给每个按钮添加点击事件&#xff0c;使用eval()进行计算。 3. 代码实现 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name&q…

低代码革新:软件开发的未来潜力与创新路径探索

过去的一年&#xff0c;挑战与机遇并存。人们一边忧虑市场经济下行所带来的新的增长难题、裁员危机&#xff0c;一边惊叹于AIGC、量子技术等领域不断涌现新的创新成果。 时代发生了改变&#xff0c;传统“互联网”的模式已走入尾声&#xff0c;新一轮的科技革命与产业变革正在到…

学习 MongoDB:打开强大的数据库技术大门

一、基本概念 MongoDB 是一个基于分布式文件存储的文档数据库&#xff0c;由 C 语言编写。它旨在为 Web 应用提供可扩展的高性能数据存储解决方案。 相信MySQL我们非常的熟悉&#xff0c;那么MySQL的表结构与MongoDB的文档结构进行类比的话可能更好理解MongoDB。 MySQL的数据…

Windows下Docker安装Kafka3+集群

编写 docker-compose.yaml 主要参照&#xff1a;https://www.cnblogs.com/wangguishe/p/17563274.html version: "3"services:kafka1:image: bitnami/kafka:3.4.1container_name: kafka1environment:- KAFKA_HEAP_OPTS-Xmx1024m -Xms1024m- KAFKA_ENABLE_KRAFTyes- K…

类和对象(上)

目录 类的定义&#xff1a; 类的访问限定符及封装&#xff1a; 访问限定符&#xff1a; 封装&#xff1a; 类的作用域&#xff1a; 类的实例化&#xff1a; ​编辑 类对象模型&#xff1a; 类对象的大小与存储方式&#xff1a; this指针&#xff1a; this指针的引出&…

《债务与国家的崛起》西方民主制度的金融起源 - 三余书屋 3ysw.net

债务与国家的崛起&#xff1a;西方民主制度的金融起源 你好&#xff0c;今天我们来聊聊由英国知名经济与金融历史学家詹姆斯麦克唐纳所著的《债务与国家的崛起》这本书。19世纪世界历史上发生了一次巨变&#xff0c;即“大分流”。当时西方通过工业革命实现了科技和经济的飞速…

vue项目入门——index.html和App.vue

vue项目中的index.html文件 在Vue项目中&#xff0c;index.html文件通常作为项目的入口文件&#xff0c;它包含了Vue应用程序的基础结构和配置。 该文件的主要作用是引入Vue框架和其他必要的库&#xff0c;以及定义Vue应用程序的启动配置。 import Vue from vue import App …

以动态库链接库 .dll 探索结构体参数

Dev c C语言实现第一个 dll 动态链接库 创建与调用-CSDN博客 在写dll 插件中发现的函数指针用途和 typedef 的定义指针的用法-CSDN博客 两步之后&#xff0c;尝试加入结构体实现整体数据使用。 注意结构体 Ak 是相同的 代码如下 DLL文件有两个&#xff0c;dll.dll是上面提到…