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

运行机制

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

共享内存的原理简单,目的是为了进程间通讯,方法是通过映射到同一块物理内存。它是一种稀缺资源由内核按资源池方式管理,数量有限,默认是 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_SHELL
          CHAR ownerName[OS_PCB_NAME_LEN];
      #endif
      };

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

    //每个共享内存段在内核中维护着一个内部结构shmid_ds
      struct 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;		///< 创建共享内存的进程ID
          pid_t shm_lpid;		///< 最后操作共享内存的进程ID
          unsigned 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;				//共享内存所有者的有效用户ID
          gid_t gid;				//共享内存所有者所属组的有效组ID
          uid_t cuid;				//共享内存创建 者的有效用户ID
          gid_t cgid;				//共享内存创建者所属组的有效组ID
          mode_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();	//设置用户ID
          seg->ds.shm_perm.uid = LOS_GetUserID();		//设置用户ID
          seg->ds.shm_perm.cgid = LOS_GetGroupID();	//设置组ID
          seg->ds.shm_perm.gid = LOS_GetGroupID();	//设置组ID
          seg->ds.shm_lpid = 0; //最后一个操作的进程
          seg->ds.shm_nattch = 0;	//绑定进程的数量					
          seg->ds.shm_cpid = LOS_GetCurrProcessID();	//获取进程ID
          seg->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);
      #endif
          return segNum;
      }
映射使用部分
  • 第一步: 创建共享内存 要实现共享内存,首先得创建一个内存段用于共享,干这事的是ShmGet
    /*!
      * @brief ShmGet	
      *	得到一个共享内存标识符或创建一个共享内存对象
      * @param key	建立新共享内存对象 标识符是IPC对象的内部名。为使多个合作进程能够在同一IPC对象上汇聚,需要提供一个外部命名方案。
              为此,每个IPC对象都与一个键(key)相关联,这个键作为该对象的外部名,无论何时创建IPC结构(通过msgget、semget、shmget创建),
              都应给IPC指定一个键, key_t由ftok创建,ftok当然在本工程里找不到,所以要写这么多.
      * @param shmflg	IPC_CREAT IPC_EXCL
                  IPC_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查找资源ID
              ret = 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计数器值加1
      shmid 就是个索引,就跟进程和线程的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给线性区的shmid
          r->regionFlags |= VM_MAP_REGION_FLAG_SHM;//这是一个共享线性区
          seg->ds.shm_atime = time(NULL);//访问时间
          seg->ds.shm_lpid = LOS_GetCurrProcessID();//进程ID
          SYSV_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);
      #endif
                  break;
              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;//线性区共享ID
          LOS_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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码生成工具1 ——项目简介和基础开发

1 项目简介 需要提前在数据库建好表&#xff0c;然后执行代码生成工具&#xff0c;会生成简单的Java文件&#xff0c;避免重复编写增删改查代码。类似的工具网上有很多&#xff0c;本人开发这个工具属于自娱自乐。这个专栏会记录开发的过程。 2 项目搭建 数据库使用MySQL &…

MySQL中的子查询

子查询,在一个查询语句中又出现了查询语句 子查询可以出现在from和where后面 from 表子查询(结果一般为多行多列)把查询结果继续当一张表对待 where 标量子查询(结果集只有一行一列)查询身高最高的学生,查询到一个最高身高 列子查询(结果集只有一行多列) 对上表进行如下操作 …

韩顺平0基础学Java——第10天

p202-233 类与对象&#xff08;第七章&#xff09; 成员方法 person类中的speak方法&#xff1a; 1.public表示方法是公开的 2.void表示方法没有返回值 3.speak&#xff08;&#xff09;中&#xff0c;speak表示方法名&#xff0c;括号是形参列表。 4.大括号为方法体&am…

SpringCloud2024最新版链路追踪教程micrometer+zipkin

本文基于B站尚硅谷2024版springcloud教学视频&#xff0c;主要用于自己学习记录以及分享技术&#xff0c;侵权私删 自己本机环境信息&#xff1a; jdk&#xff1a;17.0.10springboot&#xff1a;3.2.0springcloud&#xff1a;2023.0.0 micrometer 之前行业内使用的分布式链路…

机器学习案例:加州房产价格(一)

参考链接&#xff1a;https://hands1ml.apachecn.org/2/ 假设你是被一家地产公司雇佣的数据科学家&#xff0c;现在需要做一些工作。 公司所给的数据集是StatLib 的加州房产价格数据集。这个数据集是基于 1990 年加州普查的数据。数据已经有点老&#xff0c;但它有许多优点&…

HCIP的学习(15)

第六章&#xff0c;BGP—边界网关协议 自治系统—AS ​ 定义&#xff1a;由一个单一的机构或组织所管理的一系列IP网络及其设备所构成的集合。 ​ AS的来源&#xff1a; 整个网络规模过大&#xff0c;会导致路由信息收敛速度过慢&#xff0c;设备对相同目标认知不同。AS之间…

HCIP 6(BGP综合实验)

一、实验拓扑 二、实验要求 1.AS1中存在两个环回&#xff0c;一个地址为192.168.1.0/24&#xff0c;该地址不能在任何协议中宣告&#xff1b;AS3中存在两个环回&#xff0c;一个地址为192.168.2.0/24&#xff0c;该地址不能在任何协议中宣告&#xff0c;最终要求这两个环回可以…

批量文本高效编辑神器:轻松拆分每行内容,一键保存更高效!轻松实现批量拆分与保存

文本处理成为我们日常工作中的一项重要任务。然而&#xff0c;面对大量的文本内容&#xff0c;传统的逐行编辑方式往往显得繁琐且效率低下。那么&#xff0c;有没有一种更高效、更便捷的解决方案呢&#xff1f;答案是肯定的——批量文本高效编辑神器&#xff0c;让您的文本处理…

用命令运行Java程序

1、创建一个类 2、在类文件路径下执行命令(编译)&#xff0c;生成.class javac 类名.java 3、运行.class文件 java 类名

机器学习案例:加州房产价格(二)

参考链接&#xff1a;https://hands1ml.apachecn.org/2/ 设计好系统后&#xff0c;要开始在工作区编写代码来解决问题了。 下载数据 首先我们需要先得到数据集。 一般情况下&#xff0c;数据是存储于关系型数据库&#xff08;或其它常见数据库&#xff09;中的多个表、文档、…

WSL——Centos7.9安装

1. 下载cenos镜像包 centos7.9下载地址 下载CentOS7.zip 2. 安装 将下载的zip文件解压至安装目录(这个目录就是安装centos的目录&#xff0c;可以是c盘之外的盘) 双击CentOS.exe 安装完成后&#xff0c;在安装目录下会多出一个ext4.vhdx 3. 启动 使用 wsl --list 可以查…

linux学习:linux视频输出+FRAME BUFFER+jpeg库+lcd上显示

目录 概念 使用 struct fb_fix_screeninfo{ } struct fb_bitfield { } struct fb_var_screeninfo{ } 例子1 例子2 例子3 jpeg库 步骤 概念 framebuffer 是一种很底层的机制&#xff0c;在 Linux 系统中&#xff0c;为了能够屏蔽 各种不同的显示设备的具体细节&#…

使用 scrapyd 部署 scrapy

1.scrapyd 是什么&#xff1f; Scrapyd 是一个用于部署和运行 Scrapy 爬虫项目的服务器应用程序。它使得你可以通过 HTTP 命令来部署、管理和执行多个 Scrapy 爬虫&#xff0c;非常适合持续集成和生产环境中的爬虫部署。 2.安装scrapyd 并使用 2.1 安装 scrapyd F:\scrapydTes…

CSS之高级技巧

目录 CSS高级技巧精灵图&#xff08;精灵技术&#xff09;字体图标iconfontCSS三角CSS用户界面样式vertical-align属性应用溢出的文字省略号显示常见布局技巧 CSS高级技巧 精灵图&#xff08;精灵技术&#xff09; 为什么&#xff1f; 目的&#xff1a;有效减少服务器接受和…

vs code中如何使用git

由于本地代码有了一些储备&#xff0c;所以想通过网址托管形式&#xff0c;之前一直使用了github&#xff0c;但是鉴于一直被墙&#xff0c;无法登录账号&#xff0c;所以选择了国内的gitee来作为托管网站。 gitee的网址&#xff1a;Gitee - 基于 Git 的代码托管和研发协作平台…

【论文阅读笔记】MapReduce: Simplified Data Processing on Large Clusters

文章目录 1 概念2 编程模型3 实现3.1 MapReduce执行流程3.2 master数据结构3.3 容错机制3.3.1 worker故障3.3.2 master故障3.3.3 出现故障时的语义 3.4 存储位置3.5 任务粒度3.6 备用任务 4 扩展技巧4.1 分区函数4.2 顺序保证4.3 Combiner函数4.4 输入和输出的类型4.5 副作用4.…

如何自定义Linux命令

说明&#xff1a;本文介绍如何将自己常用的命令设置为自定义的命令&#xff0c;以下操作在阿里云服务器CentOS上进行。 修改配置文件 修改配置文件前&#xff0c;先敲下面的命令查看当前系统配置的shell版本 echo $SHELL或者 echo $0区别在于&#xff0c;$SHELL查看的是系统…

房屋出租管理系统需求分析及功能介绍

房屋租赁管理系统适用于写字楼、办公楼、厂区、园区、商城、公寓等商办商业不动产的租赁管理及租赁营销&#xff1b;提供资产管理&#xff0c;合同管理&#xff0c;租赁管理&#xff0c; 物业管理&#xff0c;门禁管理等一体化的运营管理平台&#xff0c;提高项目方管理运营效率…

Java继承学习笔记

Java的继承能保证子类拥有父类的方法的同时&#xff0c;还能有自己的方法&#xff0c;然后也是研究了一下super和this的用法&#xff1a; super的用法&#xff1a; &#xff08;1&#xff09;、super关键字有两个用法&#xff1a;super()和super. &#xff0c;super()这个用法…

C++string 类的常用方法

string (构造函数) (1) default 构造长度为零字符的空字符串。 (2) copy 构造 str 的副本。 (3) substring 复制从字符位置 pos 开始并跨越 len 字符的 str 部分&#xff08;如果任一 str 太短或 len 为 string&#xff1a;&#xff1a;npos&#xff0c;则复制 str 的末尾…