鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式

运行机制

共享好端端的一词,近些年被玩坏了,共享单车,共享充电宝,共享办公室,共享雨伞… 甚至还有共享女朋友,真是人有多大胆,共享有多大产。但凡事太尽就容易恶心到人,自己也一度被 共享内存 恶心到了,一直不想碰它,拖到了现在才写。

共享内存的原理简单,目的是为了进程间通讯,方法是通过映射到同一块物理内存。它是一种稀缺资源由内核按资源池方式管理,数量有限,默认是 192个,用资源ID唯一标识,用户进程需要时通过系统调用向内核申请共享内存大小,管理器从资源池中分配一个可用资源ID,并向物理内存申请对应的物理页框。

如何使用共享内存就涉及到了内存模块最重要的概念 映射,不清楚的可以翻看系列相关篇。有共享需求的进程在各自的进程空间中划出一个线性区映射到共享内存段,那如何找到这个共享内存段呢 ? 由系统调用提供操作接口,简单说是先通过参数key创建共享资源ID(shmid),再由shmid来连接/删除/控制 共享内存。详见本篇末尾的4个系统调用 Shm***

如何实现?

这是笔者看完内核共享内存模块画出来的图,尽量用一张图表达一个模块的内容,因为百文是在给源码注释的过程中产生的,所以会画出这种比较怪异的图,有代码,也有模型,姑且称之为 代码模型图:

图分 管理 和 映射使用 两部分解读。 为了精简,代码展示只留下骨干,删除了判断,检查的代码。

管理部分
  • 初始化共享内存,共享内存是以资源池的方式管理的,上来就为全局变量g_shmSegs向内核堆空间申请了g_shmInfo.shmmnistruct shmIDSource
    #define SHM_MNI 192 //共享内存总数 默认192// 共享内存模块设置信息struct shminfo {unsigned long shmmax, shmmin, shmmni, shmseg, shmall, __unused[4];};STATIC struct shminfo g_shmInfo = { //描述共享内存范围的全局变量.shmmax = SHM_MAX,//共享内存单个上限 4096页 即 16M.shmmin = SHM_MIN,//共享内存单个下限 1页 即:4K.shmmni = SHM_MNI,//共享内存总数 默认192 .shmseg = SHM_SEG,//每个用户进程可以使用的最多的共享内存段的数目 128.shmall = SHM_ALL,//系统范围内共享内存的总页数,4096页 };//共享内存初始化UINT32 ShmInit(VOID){// ..ret = LOS_MuxInit(&g_sysvShmMux, NULL);//初始化互斥g_shmSegs = LOS_MemAlloc((VOID *)OS_SYS_MEM_ADDR, sizeof(struct shmIDSource) * g_shmInfo.shmmni);//分配shm段数组(VOID)memset_s(g_shmSegs, (sizeof(struct shmIDSource) * g_shmInfo.shmmni),0, (sizeof(struct shmIDSource) * g_shmInfo.shmmni));//数组清零for (i = 0; i < g_shmInfo.shmmni; i++) {g_shmSegs[i].status = SHM_SEG_FREE;//节点初始状态为空闲g_shmSegs[i].ds.shm_perm.seq = i + 1;//struct ipc_perm shm_perm;系统为每一个IPC对象保存一个ipc_perm结构体,结构说明了IPC对象的权限和所有者LOS_ListInit(&g_shmSegs[i].node);//初始化节点}g_shmUsedPageCount = 0;return LOS_OK;}
  • 系列篇多次提过,每个功能模块都至少有一个核心结构体来支撑模块的运行,进程是PCB,任务是TCB,而共享内存就是shmIDSource
    struct shmIDSource {//共享内存描述符struct shmid_ds ds; //是内核为每一个共享内存段维护的数据结构UINT32 status;	//状态 SHM_SEG_FREE ...LOS_DL_LIST node; //节点,挂VmPage#ifdef LOSCFG_SHELLCHAR ownerName[OS_PCB_NAME_LEN];#endif};

首先shmid_ds是真正描述共享内存信息的结构体,记录了本次共享内存由谁创建,大小,用户/组,访问时间等等。

    //每个共享内存段在内核中维护着一个内部结构shmid_dsstruct shmid_ds {struct ipc_perm shm_perm;///< 操作许可,里面包含共享内存的用户ID、组ID等信息size_t shm_segsz;	///< 共享内存段的大小,单位为字节time_t shm_atime;	///< 最后一个进程访问共享内存的时间	time_t shm_dtime; 	///< 最后一个进程离开共享内存的时间time_t shm_ctime; 	///< 创建时间pid_t shm_cpid;		///< 创建共享内存的进程IDpid_t shm_lpid;		///< 最后操作共享内存的进程IDunsigned long shm_nattch;	///< 当前使用该共享内存段的进程数量unsigned long __pad1;	//保留扩展用unsigned long __pad2;};//内核为每一个IPC对象保存一个ipc_perm结构体,该结构说明了IPC对象的权限和所有者struct ipc_perm {key_t __ipc_perm_key;	//调用shmget()时给出的关键字uid_t uid;				//共享内存所有者的有效用户IDgid_t gid;				//共享内存所有者所属组的有效组IDuid_t cuid;				//共享内存创建 者的有效用户IDgid_t cgid;				//共享内存创建者所属组的有效组IDmode_t mode;			//权限 + SHM_DEST / SHM_LOCKED /SHM_HUGETLB 标志位int __ipc_perm_seq;		//序列号long __pad1;			//保留扩展用long __pad2;};  

status 表示这段共享内存的状态,因为是资源池的方式,只有SHM_SEG_FREE的状态才可供分配,进程池和任务池也是这种管理方式。

      #define SHM_SEG_FREE    0x2000	//空闲未使用#define SHM_SEG_USED    0x4000	//已使用#define SHM_SEG_REMOVE  0x8000	//删除

node双向链表上挂的是一个个的物理页框VmPage,这是核心属性,数据将被存在这一个个物理页框中。ShmAllocSeg为具体的分配函数

    STATIC INT32 ShmAllocSeg(key_t key, size_t size, INT32 shmflg){// ... count = LOS_PhysPagesAlloc(size >> PAGE_SHIFT, &seg->node);//分配共享页面,函数内部把node都挂好了.if (count != (size >> PAGE_SHIFT)) {//当未分配到足够的内存时,处理方式是:不稀罕给那么点,舍弃!(VOID)LOS_PhysPagesFree(&seg->node);//释放节点上的物理页框seg->status = SHM_SEG_FREE;//共享段变回空闲状态return -ENOMEM;}ShmSetSharedFlag(seg);//将node的每个页面设置为共享页g_shmUsedPageCount += size >> PAGE_SHIFT;seg->status |= SHM_SEG_USED;	//共享段贴上已在使用的标签seg->ds.shm_perm.mode = (UINT32)shmflg & ACCESSPERMS;seg->ds.shm_perm.key = key;//保存参数key,如此 key 和 共享ID绑定在一块seg->ds.shm_segsz = size;	//共享段的大小seg->ds.shm_perm.cuid = LOS_GetUserID();	//设置用户IDseg->ds.shm_perm.uid = LOS_GetUserID();		//设置用户IDseg->ds.shm_perm.cgid = LOS_GetGroupID();	//设置组IDseg->ds.shm_perm.gid = LOS_GetGroupID();	//设置组IDseg->ds.shm_lpid = 0; //最后一个操作的进程seg->ds.shm_nattch = 0;	//绑定进程的数量					seg->ds.shm_cpid = LOS_GetCurrProcessID();	//获取进程IDseg->ds.shm_atime = 0;	//访问时间seg->ds.shm_dtime = 0;	//detach 分离时间 共享内存使用完之后,需要将它从进程地址空间中分离出来;将共享内存分离并不是删除它,只是使该共享内存对当前的进程不再可用seg->ds.shm_ctime = time(NULL);//创建时间#ifdef LOSCFG_SHELL(VOID)memcpy_s(seg->ownerName, OS_PCB_NAME_LEN, OsCurrProcessGet()->processName, OS_PCB_NAME_LEN);#endifreturn segNum;}
映射使用部分
  • 第一步: 创建共享内存 要实现共享内存,首先得创建一个内存段用于共享,干这事的是ShmGet
    /*!* @brief ShmGet	*	得到一个共享内存标识符或创建一个共享内存对象* @param key	建立新共享内存对象 标识符是IPC对象的内部名。为使多个合作进程能够在同一IPC对象上汇聚,需要提供一个外部命名方案。为此,每个IPC对象都与一个键(key)相关联,这个键作为该对象的外部名,无论何时创建IPC结构(通过msgget、semget、shmget创建),都应给IPC指定一个键, key_t由ftok创建,ftok当然在本工程里找不到,所以要写这么多.* @param shmflg	IPC_CREAT IPC_EXCLIPC_CREAT:	在创建新的IPC时,如果key参数是IPC_PRIVATE或者和当前某种类型的IPC结构无关,则需要指明flag参数的IPC_CREAT标志位,则用来创建一个新的IPC结构。(如果IPC结构已存在,并且指定了IPC_CREAT,则IPC_CREAT什么都不做,函数也不出错)IPC_EXCL:	此参数一般与IPC_CREAT配合使用来创建一个新的IPC结构。如果创建的IPC结构已存在函数就出错返回,返回EEXIST(这与open函数指定O_CREAT和O_EXCL标志原理相同)* @param size	新建的共享内存大小,以字节为单位* @return	** @see*/INT32 ShmGet(key_t key, size_t size, INT32 shmflg){SYSV_SHM_LOCK();if (key == IPC_PRIVATE) {ret = ShmAllocSeg(key, size, shmflg);} else {ret = ShmFindSegByKey(key);//通过key查找资源IDret = ShmAllocSeg(key, size, shmflg);//分配一个共享内存}SYSV_SHM_UNLOCK();return ret;}
  • 第二步: 进程线性区绑定共享内存 shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。,ShmAt的第一个参数其实是ShmGet成功时的返回值 ,ShmatVmmAlloc负责分配一个可用的线性区并和共享内存映射好
    /*!* @brief ShmAt	* 用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。* @param shm_flg 是一组标志位,通常为0。* @param shmaddr 指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。* @param shmid	是shmget()函数返回的共享内存标识符* @return	* 如果shmat成功执行,那么内核将使与该共享存储相关的shmid_ds结构中的shm_nattch计数器值加1shmid 就是个索引,就跟进程和线程的ID一样 g_shmSegs[shmid] shmid > 192个* @see*/VOID *ShmAt(INT32 shmid, const VOID *shmaddr, INT32 shmflg){struct shmIDSource *seg = NULL;LosVmMapRegion *r = NULL;ret = ShmatParamCheck(shmaddr, shmflg);//参数检查SYSV_SHM_LOCK();seg = ShmFindSeg(shmid);//找到段ret = ShmPermCheck(seg, acc_mode);seg->ds.shm_nattch++;//ds上记录有一个进程绑定上来r = ShmatVmmAlloc(seg, shmaddr, shmflg, prot);//在当前进程空间分配一个线性区并映射到共享内存r->shmid = shmid;//把ID给线性区的shmidr->regionFlags |= VM_MAP_REGION_FLAG_SHM;//这是一个共享线性区seg->ds.shm_atime = time(NULL);//访问时间seg->ds.shm_lpid = LOS_GetCurrProcessID();//进程IDSYSV_SHM_UNLOCK();return (VOID *)(UINTPTR)r->range.base;}
  • 第三步: 控制/使用 共享内存,这才是目的,前面的都是前戏
    /*!* @brief ShmCtl	* 此函数可以对shmid指定的共享存储进行多种操作(删除、取信息、加锁、解锁等)* @param buf	是一个结构指针,它指向共享内存模式和访问权限的结构。* @param cmd	command是要采取的操作,它可以取下面的三个值 :IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值IPC_RMID:删除共享内存段* @param shmid	是shmget()函数返回的共享内存标识符* @return	** @see*/INT32 ShmCtl(INT32 shmid, INT32 cmd, struct shmid_ds *buf){SYSV_SHM_LOCK();switch (cmd) {case IPC_STAT:case SHM_STAT://取段结构ret = LOS_ArchCopyToUser(buf, &seg->ds, sizeof(struct shmid_ds));//把内核空间的共享页数据拷贝到用户空间if (cmd == SHM_STAT) {ret = (unsigned int)((unsigned int)seg->ds.shm_perm.seq << 16) | (unsigned int)((unsigned int)shmid & 0xffff); /* 16: use the seq as the upper 16 bits */}break;case IPC_SET://重置共享段ret = ShmPermCheck(seg, SHM_M);//从用户空间拷贝数据到内核空间ret = LOS_ArchCopyFromUser(&shm_perm, &buf->shm_perm, sizeof(struct ipc_perm));seg->ds.shm_perm.uid = shm_perm.uid;seg->ds.shm_perm.gid = shm_perm.gid;seg->ds.shm_perm.mode = (seg->ds.shm_perm.mode & ~ACCESSPERMS) |(shm_perm.mode & ACCESSPERMS);//可访问seg->ds.shm_ctime = time(NULL);#ifdef LOSCFG_SHELL(VOID)memcpy_s(seg->ownerName, OS_PCB_NAME_LEN, OS_PCB_FROM_PID(shm_perm.uid)->processName,OS_PCB_NAME_LEN);#endifbreak;case IPC_RMID://删除共享段ret = ShmPermCheck(seg, SHM_M);seg->status |= SHM_SEG_REMOVE;if (seg->ds.shm_nattch <= 0) {//没有任何进程在使用了ShmFreeSeg(seg);//释放 归还内存}break;case IPC_INFO://把内核空间的共享页数据拷贝到用户空间ret = LOS_ArchCopyToUser(buf, &g_shmInfo, sizeof(struct shminfo));ret = g_shmInfo.shmmni;break;case SHM_INFO:shmInfo.shm_rss = 0;shmInfo.shm_swp = 0;shmInfo.shm_tot = 0;shmInfo.swap_attempts = 0;shmInfo.swap_successes = 0;shmInfo.used_ids = ShmSegUsedCount();//在使用的seg数ret = LOS_ArchCopyToUser(buf, &shmInfo, sizeof(struct shm_info));//把内核空间的共享页数据拷贝到用户空间ret = g_shmInfo.shmmni;break;default:VM_ERR("the cmd(%d) is not supported!", cmd);ret = EINVAL;goto ERROR;}SYSV_SHM_UNLOCK();return ret;}
  • 第四步: 完事了解绑/删除,好聚好散还有下次,在ShmDt中主要干了解除映射LOS_ArchMmuUnmap这件事,没有了映射就不再有关系了,并且会检测到最后一个解除映射的进程时,会彻底释放掉这段共享内存ShmFreeSeg
    /*** @brief 当对共享存储的操作已经结束时,则调用shmdt与该存储段分离如果shmat成功执行,那么内核将使与该共享存储相关的shmid_ds结构中的shm_nattch计数器值减1* @attention 注意:这并不从系统中删除共享存储的标识符以及其相关的数据结构。共享存储的仍然存在,直至某个进程带IPC_RMID命令的调用shmctl特地删除共享存储为止* @param shmaddr * @return INT32 */INT32 ShmDt(const VOID *shmaddr){LosVmSpace *space = OsCurrProcessGet()->vmSpace;//获取进程空间(VOID)LOS_MuxAcquire(&space->regionMux);region = LOS_RegionFind(space, (VADDR_T)(UINTPTR)shmaddr);//找到线性区shmid = region->shmid;//线性区共享IDLOS_RbDelNode(&space->regionRbTree, &region->rbNode);//从红黑树和链表中摘除节点LOS_ArchMmuUnmap(&space->archMmu, region->range.base, region->range.size >> PAGE_SHIFT);//解除线性区的映射(VOID)LOS_MuxRelease(&space->regionMux);/* free it */free(region);//释放线性区所占内存池中的内存SYSV_SHM_LOCK();seg = ShmFindSeg(shmid);//找到seg,线性区和共享段的关系是 1:N 的关系,其他空间的线性区也会绑在共享段上ShmPagesRefDec(seg);//页面引用数 --seg->ds.shm_nattch--;//使用共享内存的进程数少了一个if ((seg->ds.shm_nattch <= 0) && //无任何进程使用共享内存(seg->status & SHM_SEG_REMOVE)) {//状态为删除时需要释放物理页内存了,否则其他进程还要继续使用共享内存ShmFreeSeg(seg);//释放seg 页框链表中的页框内存,再重置seg状态} else {seg->ds.shm_dtime = time(NULL);//记录分离的时间seg->ds.shm_lpid = LOS_GetCurrProcessID();//记录操作进程ID}SYSV_SHM_UNLOCK();

总结

看到这里你应该不会问共享内存的作用和为啥它是最快的进程间通讯方式了,如果还有这两个问题说明还要再看一遍 😛 ,另外细心的话会发现共享内存会有个小缺点,就是同时访问的问题,所以需要使用互斥锁来保证同时只有一个进程在使用,SYSV_SHM_LOCK和 SYSV_SHM_UNLOCK在以上的四个步骤中都有出现。

STATIC LosMux g_sysvShmMux; //互斥锁,共享内存本身并不保证操作的同步性,所以需用互斥锁
/* private macro */
#define SYSV_SHM_LOCK()     (VOID)LOS_MuxLock(&g_sysvShmMux, LOS_WAIT_FOREVER)	//申请永久等待锁
#define SYSV_SHM_UNLOCK()   (VOID)LOS_MuxUnlock(&g_sysvShmMux)	//释放锁

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

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

开发基础知识:gitee.com/MNxiaona/733GH

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

基于ArkTS 开发

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

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

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

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

相关文章

OpenHarmony 实战开发——轻量带屏解决方案之恒玄芯片移植案例

本文章基于恒玄科技BES2600W芯片的欧智通 Multi-modal V200Z-R开发板 &#xff0c;进行轻量带屏开发板的标准移植&#xff0c;开发了智能开关面板样例&#xff0c;同时实现了ace_engine_lite、arkui_ui_lite、aafwk_lite、appexecfwk_lite、HDF等部件基于OpenHarmony LiteOS-M内…

论文AI率:检测原理是什么?该如何降低论文AI率?

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 上一篇介绍了10个检测AI率的在线工具。本篇来说说AI率到底是如何检测出来的&#xff1f;该如何有效降低论文的AI率&#xff1f; 和AI大模型一样&#xff0c;AI检测的核心也是…

vscode打开esp-idf工程,找不到头文件,有波浪线

就像这样 多半是因为原始的工程不是用vscode的插件新建的&#xff0c;因此没有相关的路径。需要在工程文件夹下的.vscode文件夹中的c_cpp_properties.json文件中增加路径&#xff0c;可以参考插件自动新建的工程里面的写法 {"configurations": [{"name":…

线上问题——一次几经反转惊心动魄的问题排查记录

背景简述 线上某业务由于查询大量数据频繁FullGc优化&#xff0c;依赖于用户完课进度&#xff0c;测试时需要造大量的用户和专栏内容发放数据&#xff0c;且需要用户完课触发&#xff0c;因此在FAT造了一个5000人的班级&#xff0c;这个班级只有3个课时&#xff0c;测试通过脚…

Midjourney与Stable Diffusion大比拼:AI绘画技术的未来

在当今快速发展的人工智能技术浪潮中&#xff0c;AI绘画软件成为了艺术和技术交汇的新领域。两大巨头——Midjourney和Stable Diffusion&#xff0c;在这一领域中引领风骚&#xff0c;它们以其独特的功能和强大的生成能力&#xff0c;让创作者能够将想象力化为现实。本文将深入…

进程与线程含义、区别及在操作系统中的意义(简要)

在现代操作系统中&#xff0c;进程和线程是构建多任务环境的基础。它们虽然紧密相关&#xff0c;但各自扮演着不同的角色。本文将深入探讨进程与线程的概念、它们之间的区别&#xff0c;以及为什么操作系统中会存在进程这一概念。 进程&#xff1a;独立运行的实体 它是操作系…

conan2 基础入门(04)-指定编译器(gcc为例)

conan2 基础入门(04)-指定编译器(gcc为例) 文章目录 conan2 基础入门(04)-指定编译器(gcc为例)⭐准备生成profile文件预备文件和Code ⭐使用指令预览正确执行结果可能出现的问题 ⭐具体讲解conancmake ENDsettings.yml ⭐准备 生成profile文件 # 生成默认profile文件&#xf…

国产操作系统下Chrome的命令行使用 _ 统信 _ 麒麟

原文链接&#xff1a;国产操作系统下Chrome的命令行使用 | 统信 | 麒麟 Hello&#xff0c;大家好啊&#xff01;今天我们来聊聊如何在国产操作系统上使用命令行操作Google Chrome。无论是进行自动化测试、网页截图还是网页数据抓取&#xff0c;使用命令行操作Google Chrome都能…

Go编程语言的调试器Delve | Goland远程连接Linux开发调试(go远程开发)

文章目录 Go编程语言的调试器一、什么是Delve二、delve 安装安装报错cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH解决 三、delve命令行使用delve 常见的调试模式常用调试方法todo调试程序代码与动态库加载程序运行…

常用的简单友好的工单系统(免费)- WGCAT

最近在项目中&#xff0c;有工单系统的需求场景&#xff0c;所以想寻找一款轻量简单的运维工单软件&#xff0c;主要用来记录和处理工作中的一些故障、维护&#xff0c;主要用来记录设备的维护状态&#xff0c;包括服务器、主机、交换机那些 WGCAT&#xff0c;是一款简单轻量的…

知从科技战略客户经理张志强受邀出席2024 AutoSec中国汽车网络安全与数据安全峰会

4月11-12日&#xff0c;AutoSec8周年年会暨中国汽车网络安全及数据安全合规峰会在上海成功举办。此次峰会吸引了来自全球各地的头部汽车网络安全企业、OEM厂商、安全专家和学者等齐聚盛会&#xff0c;零距离共话智能网联汽车产业的新发展、新趋势。 知从科技董事长成云霞亲自带…

mapreduce | 自定义Partition分区(案例1)

1.需求 将学生成绩&#xff0c;按照各个成绩降序排序&#xff0c;各个科目成绩单独输出。 # 自定义partition 将下面数据分区处理&#xff1a; 人名 科目 成绩 张三 语文 10 李四 数学 30 王五 语文 20 赵6 英语 40 张三 数据 50 李四 语文 10 张三 英语 70 李四 英语…

MapReduce | 二次排序

1.需求 主播数据--按照观众人数降序排序&#xff0c;如果观众人数相同&#xff0c;按照直播时长降序 # 案例数据 用户id 观众人数 直播时长 团团 300 1000 小黑 200 2000 哦吼 400 7000 卢本伟 100 6000 八戒 250 5000 悟空 100 4000 唐僧 100 3000 # 期望结果 哦吼 4…

字典是如何实现的?Rehash 了解吗?

字典是 Redis 服务器中出现最为频繁的复合型数据结构。除了 hash 结构的数据会用到字典外&#xff0c;整个 Redis 数据库的所有 key 和 value 也组成了一个 全局字典&#xff0c;还有带过期时间的 key 也是一个字典。(存储在 RedisDb 数据结构中) 字典结构是什么样的呢&#xf…

vue3 自定义国际化、elementPlus 国际化

自定义国际化 1. 引入 vue-i18n 插件 pnpm install vue-i18nnext 2. 页面添加语言文件目录&#xff0c;添加自定义的语言文件 3.语言目录里添加 index.ts&#xff0c; 内容如下 import { createI18n } from "vue-i18n";// 自定义语言文件 import zhCN from "…

【C/C++】内存分布

本文第一部分主要介绍了程序内存区域的划分以及数据的存储。第二部分有一段代码和一些题目&#xff0c;全面直观得分析了程序中的数组在内存中的存储。 因为不同的数据有不同的存储需求&#xff0c;各区域满足不同的需求&#xff0c;所以程序内存会有区域的划分。 根据需求的不…

MFC重要的初始化函数InitInstance

MFC应用程序最早处理的类的初始化函数通常是CWinApp类的构造函数。CWinApp类是MFC应用程序的主类&#xff0c;负责整个应用程序的初始化和管理。 在MFC应用程序中&#xff0c;通常会创建一个派生自CWinApp类的应用程序类&#xff0c;例如CMyApp。在应用程序启动时&#xff0c;…

【数据结构(邓俊辉)学习笔记】二叉树01——二叉树表示与实现

文章目录 0.概述1.树1.1 应用1.2 有根树1.3 有序树1.4 路径环路1.5 深度 层。1.6 树的表示 2. 二叉树的概述3 二叉树实现3.1 二叉树节点3.2 二叉树节点操作接口3.3 二叉树的实现 0.概述 介绍下二叉树的表示与实现。 1.树 1.1 应用 后缀表达式。 相对于线性结构O&#xff08…

力扣HOT100 - 121. 买卖股票的最佳时机

解题思路&#xff1a; 每次遍历price&#xff0c;更新最小的cost和最大的profit class Solution {public int maxProfit(int[] prices) {int cost Integer.MAX_VALUE, profit 0;for (int price : prices) {cost Math.min(cost, price);profit Math.max(profit, price - c…

MFC的句柄概念以及句柄类型

在MFC&#xff08;Microsoft Foundation Class&#xff09;桌面应用程序中&#xff0c;窗口是通过句柄&#xff08;Handle&#xff09;来进行管理和操作的。 句柄是一个标识符&#xff0c;用于唯一标识和引用窗口、控件、设备上下文等对象。在MFC桌面应用程序中&#xff0c;常…