《WebKit 技术内幕》学习之十(2): 插件与JavaScript扩展

2 Chromium PPAPI插件

2.1 原理

        插件其实是一种统称,表示一些动态库,这些动态库根据定义的一些标准接口可以跟浏览器进行交互,至于这个标准接口是什么都可以,重要的是大家都遵循它们,NPAPI接口标准只是其中的一种,因为它被广泛使用,所以被提到的次数也最多。本节介绍的PPAPI也是一种浏览器和插件交互的接口标准,该标准是由Google提出,在Chromium项目中获得支持。

        PPAPI的提出是因为NPAPI的可移植性和性能存在比较大的问题,特别是针对跨进程的插件,同时还有插件需要2D和3D绘图、声音等问题时候就更为棘手。早期的阶段就是要解决这些问题,同时为了赢得插件厂商的支持,尽可能地使用原来NPAPI的接口。现在,随着PPAPI的不断发展,接口不断发生改变。后来,PPAPI也被用在Native Client技术中,之后也被逐渐地修改,直到现在的样子,完整的列表可以查看链接http://code.google.com/p/ppapi/w/list。

        那么,为什么PPAPI能够提供较高性能的绘图和声音等解决方案呢?前面我们提到,在现在的NPAPI插件系统中,通常的做法是,当网页需要显示该插件的时候或者需要更新的时候,它会发送一个失效(Invalidate)的通知,让插件来绘制它们。而在PPAPI插件机制中,它引入了一个保留(Retained)模式,其含义是浏览器始终保留一个后端存储空间,用来表示上一次绘制完的区域。这个很有用,因为PPAPI插件通常是跨进程的,所以浏览器可以绘制网页而不需要锁,与此同时插件进程能够在后台绘制新的结果。

        PPAPI插件有两种运行模式,受信(Trusted)插件和非受信(Untrusted)插件。对于受信的PPAPI插件,它可以在Renderer进程中运行,也可以在另外的进程中运行。对于新版本的实现,架构设计都是基于IPC来设计的。对于非受信的PPAPI插件,则可以借助于使用NativeClient技术来安全运行。受信插件是与平台相关的,可以调用平台相关的接口。而对于非受信插件而言,它们可以是与平台无关的代码,可以调用NativeClient提供的有限接口,而不能调用其他接口,这个后面再介绍。

        在Chromium中,NPAPI和PPAPI插件同时得到支持,都可以在“chrome://plugins”来查看,前面已经提到过。有趣的是,对于同一个功能的插件,甚至可能有两个不同的版本,如图10-8所示Flash的NPAPI插件和PPAPI插件实现。

                        图10-8 Chrome浏览器的NPAPI插件和PPAPI插件

        PPAPI插件同样使用“embed”或者“object”元素,这让网页看起来没什么大的区别,所以对于WebKit而言,它根本不会区分背后的是NPAPI插件还是PPAPI插件,差别在于调用的接口不一样而已,这样做的好处显而易见。

2.2 结构和接口

2.2.1 代码结构

        因为PPAPI插件对于WebKit而言是透明的,所以这里不再介绍WebKit中支持该插件的基础设施。Chromium支持跨进程的PPAPI插件机制,所以在代码结构上可以充分看到这一点。Chromium项目中有3个目录用来支持这一机制,详细结构如图10-9所示。

                        图10-9 支持PPAPI插件机制的代码目录结构

        首先是chrome目录,它包含Renderer进程和Browser进程对于PPAPI插件的支持代码,主要是资源的实现类。

        其次是content目录,同样也包含了资源的实现类,但是同时也有支持跨进程机制的代码,这会在后面的工作过程图中有所体现。

        最后是ppapi目录,当然支持PPAPI插件的代码都是在该目录中,包括支持跨进程的基础代码,它们被Renderer进程和Browser进程的支持代码所使用。

        读者可能好奇为什么chrome目录下的相关文件不直接放在content/目录下,这主要取决于Chromium项目的层次化结构。content目录下包含一些公共或者基础的设施,而chrome/目录则是跟浏览器密切相关的。例如对于PPAPI插件机制而言,它将PDF和Flash都放在该目录下,而将文件等放在content目录下。

2.2.2 应用程序编程接口

        同NPAPI的NPN和NPP开头的接口相似,PPAPI也需要双向调用的编程接口,PPAPI提供了浏览器调用插件的接口,同时更是提供了众多插件调用浏览器各种功能的接口,这非常不一样,因为功能更为强大。

        这些接口的标准定义文件都位于上面所述的目录ppapi/api中,它们都使用一种接口定义语言(IDL,Interface Definition Language)来描述。IDL是一种标准,有兴趣的读者可以查阅它的基本语法。其中以ppb_(ppapi browser)开头的接口文件表示这是由浏览器实现,被插件库所调用;以ppp_(ppapi plugin)开头的接口文件表示这是由插件实现,被浏览器所调用;而其他以pp_开头的接口文件表示共享的接口定义,两边都需要使用,主要是一些基础类定义等。

        不同于NPAPI只是提供C接口,PPAPI既提供了C接口,同时又提供了C++接口。C接口主要是函数指针和结构为主,而C++接口则是提供各种作用的类,它们分别位于目录ppapi/c和ppapi/cpp下。因为两个定义的功能是一致的,之后我们都以C++接口为例来解释PPAPI插件机制。

        公共部分的接口包括各个基础数据,如时间、大小、矩形和资源,这些类会作为后面定义接口的参数来传递,对应的接口例如PP_Time、PP_Size、PP_Rect和PP_Resource。这里面非常重要的接口是PP_Resource,它表示各种类型的资源,例如文件资源、音频资源、图像资源、图形资源等。

        由插件实现的接口大致包括以下几个部分:第一部分是插件模块和插件实例,用于初始化和关闭插件的管理插件功能的接口,例如PPP_InitializeModule()、PPP_ShutdownModule()。而插件的实例类,表示一个插件的实例对象,也就是Interface PPP_Instance,这里面包含多个函数,如DidCreate、DidDestroy等,表示当创建插件之后,浏览器调用它们,以便插件能够做一些后续的辅助工作。第二部分是一些事件的通知接口,表示浏览器需要派发一些消息给插件,典型的包括鼠标事件、通用消息传递接口、3D图形上下文丢失事件和鼠标锁定事件等。

        由浏览器实现的接口,主要提供各种能力给插件使用,这其中包括2D和3D图形绘制接口、文件IO、文件系统、鼠标事件、网络、游戏手柄、时间等,这些都是PPAPI机制中定义的可以被浏览器调用的资源及其编程接口,读者会发现这些主要都是为游戏的需求服务的,实际上这些机制就是为了高性能的游戏而设计的。

2.3 工作过程

2.3.1 基础设施

        对于PPAPI插件的跨进程架构,同NPAPI插件的跨进程架构非常类似,可以说基本相同。同样当网页中出现一个“embed”元素的时候,PPAPI插件进程会为它创建一个插件实例,这里不再赘述。

        对于插件模块和实例接口,由插件进程直接调用并根据需要加载和创建它们,非常的简单明了。在PPAPI插件机制中,复杂的是资源的调用,也就是浏览器提供给插件使用的各种资源接口,所以下面重点介绍围绕资源的基础设施。

        图10-10描述了跨进程模式下PPAPI插件机制中资源是如何被插件调用的。如同前面一样,PPAPI的插件是在插件进程中被加载的,当它需要使用插件的时候,通过图中Thunk设施将C接口转成C++接口来调用相应的PluginResource类。该类是所有资源的基类,是一个代理类(只是将请求转发给真正的实现者),负责发送请求给其他进程,拥有接受其他进程发过来的调用结果的能力。发送请求由相应的其他类来帮助,在这里是PluginDispatcher类和HostDispatcher类。它们都会使用IPC::Channel来发送消息,消息会被Browser进程和Renderer进程中的BrowserPpapiHost类和RenderPpapiHost类处理(它们依赖ppapi/host的基类),这些请求会发送给ResourceHost类来处理,以调用真正的实现函数。读者发现这两个进程都有ResourceHost的子类,这是因为某些资源的实现在Renderer进程完成,例如2D和3D图形资源,但是有些类必须在Browser进程中处理,如文件和文件系统等。

                图10-10 跨进程的PPAPI插件机制中支持资源的基础设施

2.3.2 工作过程

        这里以Chromium中PPAPI的一个使用2D绘图的例子来说明PPAPI插件的工作过程,该例子位于目录ppapi/examples/2d下,主要有两个部分,一个是个简单的HTML网页文件(2d.html),另外一个就是实现插件的文件(paint_manager_example.cc)。示例代码10-1是使用插件的网页代码,示例代码主要是“embed”元素,同NPAPI插件的使用方式完全一模一样,下面会逐步讲解PPAPI插件的源代码,以及该插件是如何和该网页一起工作的。

示例代码10-1 使用插件的网页代码

    <html>
    <head>
        <title>2D Example</title>
    </head>
    <body>
        <embed id="plugin" type="application/x-ppapi-example-2d">
    </body>
    </html>

        首先当然是插件的创建过程。在WebKit中,对于PPAPI和NPAPI的支持都是类似的,所以可以回顾10-7中的NPAPI插件被创建的过程,二者同样根据MIME类型来查找PPAPI插件机制,如果Chromium发现查找到的是一个PPAPI插件而不是NPAPI插件,那么在创建WebPluginContainerImpl对象的时候,就会首先创建一个WebPlugin子类的对象。注意,这里不是一个WebPluginImpl对象,而是一个PepperWebPluginImpl对象。之后它就发送消息到插件进程,请求创建一个插件的实例。回到插件进程,它会根据插件的注册信息查找需要的插件并调用它的构造函数来初始化该插件的模块,如示例代码10-2所示的CreateModule方法。之后需要调用该方法返回的对象来创建一个插件实例的对象,如示例代码中的CreateInstance方法,会创建一个插件类自定义的一个示例。

示例代码10-2 插件的实现代码部分节选

    class MyModule : public pp::Module {
        public:
         virtual pp::Instance* CreateInstance(PP_Instance instance) {
           return new MyInstance(instance);
         }
    };
    namespace pp {
         Module* CreateModule() {
           return new MyModule();
         }
    }

        根据示例代码10-2和示例代码10-3,当MyInstance被创建的时候,Chromium会创建PPP_Proxy_Instance对象,该对象接收从Renderer进程传递过来的关于该实例的状态消息,如插件视图改变、销毁等,然后再调用插件的相应接口,前面说过这些接口是在插件中实现并由浏览器调用的。

        其次来了解资源的创建,一个插件实例可能会用到多个资源,如绘图资源、文件资源等,示例代码10-3所示的OnPaint函数使用到了2D绘图资源。由于它使用了PaintManager类,当需要更新视图的时候,该类需要创建一个Graphics2D资源对象。

示例代码10-3 插件的实例类自定义实现

    class MyInstance : public pp::Instance, public pp::PaintManager::Client {
     public:
      MyInstance(PP_Instance instance) {
        …
      }
      virtual bool HandleInputEvent(…) { … }
      virtual void OnPaint(pp::Graphics2D& graphics_2d, …) { … }
     private:
      pp::PaintManager paint_manager_;
    };

        为了详细说明它的调用过程,图10-11和图10-12描述了资源类对象的创建和资源类对象接口的调用过程,分别以插件进程和Renderer进程的交互为例,而插件进程和Browser进程的交互则是类似的情况。

                        图10-11 Chromium创建PPAPI插件的资源对象的过程

        图10-11包括两个步骤,第一个步骤在插件进程中完成,第二个步骤在Renderer进程中完成。当PPAPI插件需要创建一个资源对象的时候,会通过PPAPI的C接口调用Chromium内部的实现,Thunk层将其转换成C++风格的调用。在插件进程中,会有一个工厂类来创建不同类型的资源对象。本例中Graphics2DResource对象在创建的同时会发送一个消息到Renderer进程,这就是第二步骤。Renderer进程同样包含一个能够创建不同类型ResourceHost对象的工厂类,以帮助完成资源对象的创建。

                        图10-12 PPAPI插件调用资源对象接口的过程

                图10-12描述了当资源对象创建完之后,插件需要调用资源对象的接口来完成特定的操作,这一过程可以包含三个步骤,其发生在两个进程中,图中已有完整描述。首先,当然还是插件进程接收到插件的调用请求,并把请求发送给Renderer进程。其次是Renderer进程接收响应,然后执行特定的操作,并将结果值返回或者通知插件进程该动作执行完成。最后是插件进程接收到返回值或者动作执行完的消息,如果需要,它还可以调用插件的函数来通知插件。当然,某些调用不需要从Renderer进程返回结果到插件进程,所以前两步是必需的,但是第三步却是可选的。

2.4 Native Client

2.4.1 基本原理

        NativeClient,也简称为NaCl,是一种沙箱技术,它能够提供给平台无关的不受信本地代码一个安全的运行环境,可以针对那些计算密集型的需求,例如游戏引擎、可视化计算、大数据分析、3D图形渲染等,这些场合只需要访问有限的一些本地接口,不需要通过网络服务来计算,以免占用额外的带宽资源。同时,它能够比较方便地将原来使用传统语言例如C++编写的库直接移植到Web平台中。它同WebGL、WebAudio这样的技术所解决的问题相似,但是途径不同,因为这些技术是规范(或者草案),而NativeClient技术是Google提出的。使用NativeClient能够将很多本地库的能力轻易地提供给网页使用,而不需要复杂的移植过程,给重用带来很大的方便。

        本身PPAPI和NativeClient没有必然联系,两者解决的是不同方面的问题:PPAPI提供插件机制;NativeClient使用PPAPI的插件机制将使用NativeClient技术编译出来的本地库运行同浏览器交互起来。只是目前NativeClient是基于PPAPI接口来实现的,其实之前NativeClient也曾经基于NPAPI接口来实现,所以能够在Firefox、Safari和Opera浏览器中运行(目前显然不能了)。

        因为NativeClient使用PPAPI来提供一个安全的运行环境,本身它也是一个PPAPI插件,图10-13就是Chrome浏览器中一个PPAPI插件——NativeClient。同其他的PPAPI插件不一样,它是一个在Renderer进程中运行的插件,因为这个插件显然是受信的,如图中的“Type: PPAPI(in-process)”。

                                图10-13 NativeClient的PPAPI插件

        所以,如果需要在网页加入代码<embed id="plugin" type="application/x-nacl">表明使用了NaCl技术,对于WebKit而言,它就是调用一个插件(不知是什么类型的插件),对于PPAPI技术而言,它就是调用了内嵌的NaCl PPAPI插件。因为NaCl还需要调用开发者的本地库,所以它还需要跟本地库来交互,下面来介绍PPAPI插件之后的技术。

        NaCl本质上是一个运行环境,该子系统提供了很少的一些受限系统调用接口和资源的抽象,本地库只能调用它们,而不能任意使用系统调用。与沙箱模型的不同在于,NaCl是将一个第三方开发的代码库运行在受限的环境中,而沙箱模型是将一个进程运行在受限环境中。NaCl提供编译工具,将使用C/C++代码编写的代码编译生成它能运行的可执行格式——nexe。本地代码调用的也都是本地接口,同JavaScript的交互都由NaCl机制和PPAPI机制来完成。

        为了直观理解NaCl机制,用图10-14描述了它的架构图,该图参考Chromium官方网站的示意图,为进行了一些删减和修改。

                        图10-14 使用PPAPI技术的NaCl机制

        首先研究一下Renderer进程,如果没有后面的部分,NaCl插件和其他PPAPI插件没有什么特别的差别,同样通过PPAPI跟渲染引擎进行通信。但是这里NaCl插件只是一个桥接工作,它将同浏览器(也可以说渲染引擎)的交互交接到使用NaCl技术的本地可执行库“nexe”。

        然后,Chromium会创建一个新的进程,该进程使用了沙箱技术,只能访问特定的系统接口,这样限定了该进程中的任何库都不能超越它们。在Renderer进程中的NaCl插件使用消息传递机制同sel_ldr进程通信。在消息机制之上是一种称为SPRC(简单的进程远程调用技术),该机制可以实现PPAPI的跨进程调用。不过目前插件同NaCl的实现之间的通信机制SRPC已经不支持,都是通过一个新的接口PostMessage来实现的,该接口意味着都是通过消息机制来进行的。

        最后,sel_ldr提供的环境能够运行nexe,从图10-14中可以看出,nexe是不受信的部分,但是没关系,该机制可以用一个沙箱来将它运行在限定的sel_ldr进程中,nexe没有办法使用NaCl提供的接口之外的系统接口,从而保证了安全。nexe是本地代码库,所以跟平台相关,例如对于32位和64位系统,需要两份不同的代码库,这同时也造成了库的冗余,有没有什么好的办法能够解决这个问题呢?在最近的版本中,Google的工程师开始将LLVM技术引入到NaCl机制中,这就是pNaCl技术。

2.4.2 pNaCI

        nexe需要不同的版本,一个关键的问题在于NaCl提供的编译工具只能将NaCl的实现直接编译成同硬件架构相关的本地代码,所以不同的平台需要生成不同的本地库。pNaCl提供了一套新的工具,该工具能够将C/C++代码编译成LLVM字节码,读者回忆一下图9-27中关于LLVM的基本结构,LLVM能够将C/C++代码转成字节码,该字节码是平台无关的,而且该字节码可以保存起来,当字节码在某个平台上运行的时候,LLVM的后端能够根据字节码生成特定平台的本地代码。

        回到pNaCl上来,使用了LLVM技术很明显地能够带来减少库的冗余性的好处,这样nexe就可以变成pexe(portable exe)。对于如何使用pNaCl,这里不再介绍,官方网站上有非常详细的介绍,感兴趣的读者请查阅下面的网页:http://www.chromium.org/nativeclient/pnacl/developing-pnacl。

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

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

相关文章

FastDFS分布式文件存储

为什么会有分布式文件系统&#xff1f; 分布式文件系统是面对互联网的需求而产生。因为互联网时代要对海量数据进行存储。很显然靠简单的增加硬盘个数已经满足不了我们的要求。因为硬盘传输速度有限但是数据在急剧增长&#xff0c;另外我们还要要做好数据备份、数据安全等。采用…

初识k8s(概述、原理、安装)

文章目录 概述由来主要功能 K8S架构架构图组件说明ClusterMasterNodekubectl 组件处理流程 K8S概念组成PodPod控制器ReplicationController&#xff08;副本控制器&#xff09;ReplicaSet &#xff08;副本集&#xff09;DeploymentStatefulSet &#xff08;有状态副本集&#…

6 时间序列(不同位置的装置如何建模): GRU+Embedding

很多算法比赛经常会遇到不同的物体产生同含义的时间序列信息&#xff0c;比如不同位置的时间序列信息&#xff0c;风力发电、充电桩用电。经常会遇到该如此场景&#xff0c;对所有数据做统一处理喂给模型&#xff0c;模型很难学到区分信息&#xff0c;因此设计如果对不同位置的…

【Linux】常见指令(一)

前言: Linux有许多的指令&#xff0c;通过学习这些指令&#xff0c;可以对目录及文件进行操作。 文章目录 一、基础指令1. ls—列出目录内容2. pwd—显示当前目录3. cd—切换目录重新认识指令4. touch—创建文件等5. mkdir—创建目录6. rmdir指令 && rm 指令7. man—显…

linux源码编译安装llvm

目录 1 建立文件夹llvm 2 下载源码到llvm文件夹 3 解压上述文件 4 将解压后的3个文件夹改名&#xff0c;并移动到llvm-9.0.0.src中&#xff1a; 5 在llvm文件夹内建立build文件夹&#xff0c;并进入该文件夹&#xff1a; 6 执行cmake命令 7 make 8 安装 9 安装成功后…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--机器人、强化学习

专属领域论文订阅 VX 扫吗关注{晓理紫|小李子}&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 如果你感觉对你有帮助可以扫吗关注&#xff0c;每日准时为你推送最新论文 分类: 大语言模型LLM视觉模型VLM扩散模型视觉导航…

Git Docker 学习笔记

注意&#xff1a;该文章摘抄之百度&#xff0c;仅当做学习笔记供小白使用&#xff0c;若侵权请联系删除&#xff01; 目录 列举工作中常用的几个git命令&#xff1f; 提交时发生冲突&#xff0c;你能解释冲突是如何产生的吗&#xff1f;你是如何解决的&#xff1f; git的4个…

安全通信网络

1.网络架构 1&#xff09;应保证网络设备的业务处理能力满足业务高峰期需要。 设备CPU和内存使用率的峰值不大于设备处理能力的70%。 在有监控环境的条件下&#xff0c;应通过监控平台查看主要设备在业务高峰期的资源&#xff08;CPU、内存等&#xff09;使用 情况&#xff…

双数据源同步的思考

双数据源同步的思考 目录概述需求&#xff1a; 设计思路实现思路分析1.简单实现 2.Spring mybatis 技术 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wa…

geemap学习笔记053:纹理特征

前言 纹理特征通常描述了遥感影像中像素之间的空间关系和变化&#xff0c;对于地物分类、目标检测以及图像分割等遥感应用非常有价值。本节将会介绍Earth Engine中提供的一些纹理特征计算方法&#xff0c;包括熵和灰度共生矩阵。 1 导入库并显示数据 import ee import geema…

.NET发展如火如荼,这回.Net 8的野心确实不小

随着三天.NET Conf 2023的会议结束了&#xff0c;.Net 8正式发布了。 .Net 8是官方号称有史以来性能最快的一个版本了。 .Net 8 增加了数以千计的性能、稳定性和安全性改进&#xff0c;以及平台和工具增强功能&#xff0c;有助于提高开发人员的工作效率和创新速度。 反正就是快…

【Leetcode 965.】判断单值二叉树

单值二叉树&#xff1a; 示例一&#xff1a; 示例二&#xff1a; 代码&#xff1a; bool isUnivalTree(struct TreeNode* root) {if(rootNULL)return true;if(root->left&&root->left->val!root->val)return false;if(root->right&&root-&…

【2024系统架构设计】案例分析- 1软件架构设计

目录 一 基础知识 二 历年真题 案例分析前文回顾: 【2024系统架构设计】 系统架构设计师第二版-大数据架构理论设计与实践 【2024系统架构设计】 系统架构设计师第二版-云原生架构设计理论与实践

opencv#30 线性滤波

均值滤波原理 均值滤波步骤 Step1:求和。 Step2:计算平均值。 所谓均值滤波&#xff0c;就是求平均值的意思。我们假设在一个3*3的范围内有一个图像&#xff0c;其中这个图像每一个像素可能含有噪声&#xff0c;也可能不含噪声&#xff0c;我们是不知道的&#xff0c;因此通…

力扣1143. 最长公共子序列(动态规划)

Problem: 1143. 最长公共子序列 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 我们先假设已经将两个字符串转换为两个char类型的数组&#xff08;t1,t2&#xff09;便于比较 1.如果t1[i] t2[j],有三种决策&#xff1a;&#xff08;i1&#xff0c;j1&#xff09;&a…

【江科大】STM32:TIM输入捕获(理论部分)

文章目录 IC&#xff08;Input Capture&#xff09;输入捕获PWM频率 知识点补充1. 滤波器的工作原理&#xff1a;2. 边沿检测器&#xff1a;自动化清零CNT输入捕获的基本结构PWMI基本结构滤波器和分频器的区别误差分析pwm.cmain.cIC.c PWM模式测频率和占空比 IC&#xff08;Inp…

架构篇08:架构设计三原则

文章目录 合适原则简单原则演化原则小结 成为架构师是每个程序员的梦想&#xff0c;但并不意味着把编程做好就能够自然而然地成为一个架构师&#xff0c;优秀程序员和架构师之间还有一个明显的鸿沟需要跨越&#xff0c;这个鸿沟就是“不确定性”。 对于编程来说&#xff0c;本…

神经网络算法与逻辑回归:优势与差异

神经网络算法和逻辑回归都是预测模型中的重要工具&#xff0c;但它们在处理复杂和非线性问题时表现出不同的性能。本文将深入探讨神经网络算法相对于逻辑回归的优势&#xff0c;以及它们在不同场景下的适用性。 一、引言 神经网络算法和逻辑回归都是预测模型中的重要工具&…

Ubuntu用gparted重新分配空间

ubuntu系统使用过程中安装系统时预先留的空间不够使用怎么办&#xff1f; 这么办&#xff01; 首先 使用df -h 查看当前空间使用情况 已经分配的空间重新规划 &#xff1f; 先将已分配的空间中的多余空间分离出来&#xff1b; 假设我想将挂载点/home下的一部分空间分给挂载…

数据结构之使用顺序表写出通讯录

前言 昨天我们踏入了数据结构的深山&#xff0c;并且和顺序表battle了一番&#xff0c;虽说最后赢了&#xff0c;但同时也留下了一个问题&#xff1a;如何从顺序表的增删查改加强到通讯录的的增删查改&#xff0c;别急&#xff0c;今天就带你一探究竟。 一.回顾与思考 我们昨…