【史上最全】带你全方位了解containerd 的几种插件扩展模式

除了 snapshotter,containerd 的扩展机制你还了解哪些?

本文内容节选自 《containerd 原理剖析与实战》,本书正参加限时优惠内购,限时 69.9 元购买

进入正题之前先看一下 containerd 的整体架构

1. containerd 架构

图片

图 containerd 架构

架构分层介绍: containerd 总体架构分三层:ecosystem (生态层)、containerd (containerd 内部架构)、system (系统层)。

  1. ecosystem (生态层) ecosystem (生态层) 分 Platfrom 和 Client 两层:

    1. Platform: 平台层与 containerd 的设计理念相吻合(嵌入到更大的系统中),作为工业标准的容器运行时通过屏蔽底层差异向上支撑多个平台: 谷歌 GCP、亚马逊 Fargate、微软 Azure、Rancher 等

    2. Client: 客户端是 ecosystem 层连接 containerd 的适配层,containerd 技术上还是经典的 CS 架构,containerd 客户端通过 gRPC 调用 containerd 服务端的 API 进行操作。containerd 暴露的接口有两类: 一类是 CRI 接口,该接口是 Kubernetes 定义的,用于对接不同容器运行时进行的规范与抽象,contaienrd 通过内置的 CRI Plugin 实现了 CRI 的接口,该接口主要是向上对接 Kubernetes 集群,或者 crictl;另一类是通过 containerd 提供的 Client SDK 来访问 containerd 自己定义的接口,该接口向上主要对接的是非 Kubernetes 类的上层 Paas 或更高级的运行时,如 Docker,BuildKit、ctr 等。

  1. containerd(containerd 内部架构) containerd 这一层主要是 containerd 的 Server 实现层,逻辑上分三层:API 层、Core 层、Backend 层。

    1. API:API 层提供北向服务 GRPC 调用接口和 Prometheus 数据采集接口,API 支持 Kuberntes CRI 标准和 containerd client 两种形式。

    2. core:core 层是核心逻辑层,包含服务和元数据。

    3. Backend:Backend 层主要是南向对接操作系统容器运行时,支持通过不同的 Plugin 来扩展,这里比较重要的是 containerd-shim ,containerd 通过 shim 对接不同的容器运行时,如 kata、runc 、runhs、 gVisor、firecracker 等 。

  1. system(系统层) system 层主要是 containerd 支持的底层操作系统及架构,当前支持 Windows 和 Linux, 架构上支持 x86 和 arm。

2. containerd Backend

在 containerd 的 API 层和 Core 层之下,有一层 Backend 层,该层主要对接操作系统容器运行时,该层也是 containerd 对接外部插件的扩展层。Backend 主要包括两大类,proxy plugin,以及 containerd shim。如下图所示。

图片

图 containerd Backend 与扩展

如图 所示, proxy plugin(代理插件)有三种类型: contentdiff 以及 snapshotter。其中,containerd 的 snapshotter 在之前的文章 《一文了解 containerd 中的 snapshot》中已经讲过。

接下来介绍 content、diff 两种 proxy plugin,以及 containerd 的 Runtime 和 shim 扩展机制

3. containerd proxy plugin

containerd 中的微服务都是以插件的形式松耦合的联系在一起,例如 service plugin,grpc plugin,snapshot plugin 等。containerd 除了内置的插件之外,还提供了一种使用外部插件的方式,即代理插件 (proxy plugin)。

在 containerd 中支撑的代理插件类型有 content 和 snapshot,以及 diff (containerd 1.7.1 中新增的类型),在 containerd 配置文件中配置代理插件的方式参见下面的示例:

#/etc/containerd/config.toml
version = 2
[proxy_plugins][proxy_plugins.<plugin name>]type = "snapshot"address = "/var/run/mysnapshotter.sock"

proxy plugin 中可以配置多个代理插件,每个代理插件配置为 [proxy_plugins.<plugin name>] 其中, <plugin name> 表示插件的名称。插件的配置仅有两个参数:

  1. type: 代理插件的类型,containerd 当前版本 (1.7.1) 支持三种,content、diff 和 snapshot

  2. address: 代理插件监听的 socket 地址,containerd 通过该地址与代理插件通过 grpc 进行通信。  代理插件注册后,可以跟内部插件一样使用,可以通过 ctr plugin ls 查看注册好的代理插件。接下来介绍 snapshottercontent、以及 diff 插件的配置。

1. snapshotter 插件的配置及使用

以 nydus 为例,介绍 nydus 代理插件的配置及使用。snapshotter 可以通过 ctr nerdctl 以及 cri 插件来使用,接下来的实例通过 cri 插件来演示。  通过 cri 插件的配置参数 snapshotter = "nydus"

...
[plugins."io.containerd.grpc.v1.cri"][plugins."io.containerd.grpc.v1.cri".containerd]snapshotter = "nydus"disable_snapshot_annotations = false[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]runtime_type = "io.containerd.runc.v2"[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]runtime_type = "io.containerd.kata.v2"privileged_without_host_devices = true
...
[proxy_plugins][proxy_plugins.nydus]type = "snapshot"address = "/var/lib/containerd/io.containerd.snapshotter.v1.nydus/containerd-nydus-grpc.sock"

2. content 插件的配置及使用
 

content 接口用于管理数据以及数据对应的元信息,例如镜像数据 (config、manifest、targz 等原始数据),元数据信息保存在 metadata 中,真正的二进制数据则保留在 /var/lib/containerd/io.containerd.content.v1.content 中。该接口关联的 ctr 命令如下。

ctr content command [command options] [arguments...]

使用 content 的典型场景是拉取镜像时对镜像的保存,具体可参考本文 6.1.3 节中讲述的镜像拉取过程。如下图所示。

图片

图 containerd 拉取镜像到准备容器 rootfs

镜像拉取过程中使用 content 的流程如下:

  1. 镜像拉取后,镜像的 manifest 文件 和 镜像层 targz 文件通过 content API 接口的 write 方法写入到宿主机上,同时更新 content 的元数据信息(metadata)。

  2. 镜像拉取过程中同时涉及 image API 的操作,通过 image API 更新 image 的元数据信息到 metadata 中。

  3. 镜像解压到 snapshot 的过程,则会调用 image 的 API 以及 content API 的 Read 接口,读取镜像的 manifest 文件 和 镜像层 targz 文件,解压到 snapshot 对应的挂载目录中。

不同于 snapshotter ,containerd 中仅支持一种 content 插件,即要么是containerd 内置的 content plugin,要么是自行实现的 content plugin。  自行实现 content plugin 需要实现 ContentServer 的接口,如下所示。

type ContentServer interface {Info(context.Context, *InfoRequest) (*InfoResponse, error)Update(context.Context, *UpdateRequest) (*UpdateResponse, error)List(*ListContentRequest, Content_ListServer) errorDelete(context.Context, *DeleteContentRequest) (*types.Empty, error)Read(*ReadContentRequest, Content_ReadServer) errorStatus(context.Context, *StatusRequest) (*StatusResponse, error)ListStatuses(context.Context, *ListStatusesRequest) (*ListStatusesResponse, error)Write(Content_WriteServer) errorAbort(context.Context, *AbortRequest) (*types.Empty, error)mustEmbedUnimplementedContentServer()
}

接口实现可以参考如下代码

func main() {socket := "/run/containerd/content.sock"// 1. implement content serversvc := NewContentStorer()// 2. registry content serverrpc := grpc.NewServer()content.RegisterContentServer(rpc, svc)l, err := net.Listen("unix", socket)if err != nil {log.Fatalf("listen to address %s failed:%s", socket, err)}if err := rpc.Serve(l); err != nil {log.Fatalf("serve rpc on address %s failed:%s", socket, err)}
}
type Mycontent struct {content.UnimplementedContentServer
}
func (m Mycontent) Info(ctx context.Context, request *content.InfoRequest) (*content.InfoResponse, error) {//TODO implement me
}
... 省略其他接口实现

上述代码将监听 /run/containerd/content.sock 地址,在 containerd 中若想使用该 content plugin,需要禁用内置的 content plugin,配置如下。

...
disabled_plugins = ["io.containerd.content.v1.content"]
...
[proxy_plugins][proxy_plugins.mycontent]type = "content"address = "/run/containerd/content.sock"

【注意】 代理 content 插件用于远程存储的场景,不过使用远程存储更推荐使用 snapshotter 的方式,因为 containerd 代理 content 插件会带来巨大的开销。

3. Diff 插件的配置及使用

diff 接口用于镜像层内容 和 rootfs 之间的转化操作,其中 Diff 函数用于将两个挂载目录(如overlay 中的 upper 和 lower )之间的差异生成符合 OCI 规范的 tar 文件并保存。Apply 函数则相反,将 Diff 生成的 tar 文件解压并挂载到指定目录。如图所示。

图片

图 containerd diff 接口的操作 

该接口关联的 ctr 命令为:

ctr snapshots diff [command options] [flags] <idA> [<idB>]

相比 content 插件,Diff 代理插件就比较灵活了,类似 snapshotter 插件,可以配置多个 Diff 插件,containerd 会依次执行,如下配置, containerd 将会依次执行外置 proxydiff 插件和内置 walking 插件的相关方法。

...[plugins."io.containerd.service.v1.diff-service"]default = ["proxydiff", "walking"]
...
[proxy_plugins][proxy_plugins."proxydiff"]type = "diff"address = "/tmp/proxy.sock"

Diff 插件同样需要实现特定的接口: DiffServer ,如下

···
type DiffServer interface {Apply(context.Context, *ApplyRequest) (*ApplyResponse, error)Diff(context.Context, *DiffRequest) (*DiffResponse, error)mustEmbedUnimplementedDiffServer()
}

具体实现可以参考示例 github.com/zhaojizhuang/containerd-diff-example

4. containerd 中的 Runtime 和 Shim

Contaienrd Backend 中除了三个 proxy plugin 之外,还有一个 containerd 中最重要的扩展插件------Shim

启动 contianerd 中的 task 时,会启动 containerd 中对应的 Shim 来启动容器。如下图所示。

图片

图 containerd Shim 与 OCI Runtime

如图所示,containerd 与底层 OCI Runtime 通过Shim 连接, containerd 中的 Runtime V2 模块(最早支持的 Runtime V1 已经在 1.7.1 版本中移除)负责 shim 的管理。

1. Shim 机制

Shim 机制是 containerd 中设计的用来扩展不同容器运行时的机制,不同运行时的开发者可以通过该机制,将自己的容器运行时集成在 containerd 中。当前 containerd 支持的是 V2 版本的 Runtime Shim。V1 版本的相关 Runtime Shim 已在 1.7.1 版本中废弃。  通过 ctr 、nerdctl 或者 CRI Plugin 通过指定 runtime 字段来启动特定的容器运行时。如下  1) ctr 指定 runtime 启动容器

通过 ctr run --runtime 指定特定的容器运行时来启动容器,如下。

ctr run --runtime io.containerd.runc.v2 xxx

2) nerdctl 指定 runtime 启动容器

通过 nerdctl run --runtime 来指定特定的容器运行时来启动容器,如下。

nerdctl run --runtime io.containerd.kata.v2 xxx

3) CRI Plugin 中 通过 runtime_type 字段指定 runtime

CRI Plugin 使用时通过会结合 RuntimeClass 一起使用, 例如使用 kata 时 CRI Plugin 的配置参数如下。

[plugins."io.containerd.grpc.v1.cri".containerd][plugins."io.containerd.grpc.v1.cri".containerd.runtimes][plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]runtime_type = "io.containerd.kata.v2"

当 containerd 用户通过 runtime 指定时,containerd 在调用时会将 runtime 的名称解析为二进制文件,并在 $PATH 中查找对应的二进制文件。

例如 runtime io.containerd.runc.v2 会被解析成二进制文件 containerd-shim-runc-v2,客户端在创建容器时可以指定使用哪个 shim,如果不指定就使用默认的 shim (containerd 中默认的 runtime 为 io.containerd.runc.v2 )。

2. containerd 支持的 Shim

只要是符合 containerd Shim API 规范的 shim,containerd 都可以支持对接,当前containerd 支持的 Shim 如表7.8所示。  表7.8 containerd 支持的 Shim

类型shim对接的 runtime说明
官方内置io.containerd.runc.v1runc实现的是 v2 版本的 shim,(即一个 shim 进程对接 pod 内多个 container ),也是仅支持 cgroup v1
io.containerd.runc.v2runc实现的是 v2 版本的 shim,支持 cgroup v1 和 cgroupv2,当前 containerd 默认支持的 shim(tutime_type)
其他第三方io.containerd.runhcs.v1hcsWindows 上的容器化方案,对接的是 windos 的 HCS (Host ComputeService); Window 容器当前只有 微软在主推,项目地址 github.com/microsoft/hcsshim引申:Windows 上还有一种基于虚拟化的容器方案,即 hyper-v
io.containerd.kata.v2kataKata runtime,基于虚拟化实现的 Runtime,当前支持 qemu,cloudhypervisor,firecracker,实现的是 v2 版本的 shim
io.containerd.systemd.v1systemd基于 Systemd 实现的 v2 版本的 shim,参考 github.com/cpuguy83/containerd-shim-systemd-v1

5. containerd Shim 规范

关于 Shim 机制,containerd 定义了一套完整的规范,来帮助容器运行时的作者来实现自己的 Shim。接下来介绍 containerd 中的 Shim API。  containerd 与 Shim 交互如图7.16 所示。

图片

图 containerd 调用 shim 的两种方式   

Runtime Shim API 定义了两种调用方式:

  1. 二进制调用方式: 通过 shim start 命令直接启动 shim 二进制,shim 二进制启动后会启动对应的 ttrpc Server。启动命令示例如 containerd-shim-runc-v2 start -namespace xxx -address /run/containerd/containerd.sock -id xxx

  2. ttrpc 调用方式: shim 进程启动后便充当了 ttrpc Server 的角色,之后 containerd 与shim 的交互都走 ttrpc 调用。

6. Shim 工作流程解析

下面通过一个具体的例子说明容器启动时 Shim 与 containerd 交互的流程。

以 ctr 启动 nginx 容器为例。命令如下。

ctr image pull docker.io/library/nginx:latest
ctr run docker.io/library/nginx:latest nginx

注意这里 ctr run 启动容器时,containerd 启动时默认使用的 runtime 为 io.containerd.runc.v2

启动容器时,containerd 与 shim 的交互机制如下图。

图片

图 通过 ctr 启动容器时 containerd 与 Shim 交互的流程   

如上图所示,通过 ctr 创建容器时的相关调用流程如下:

  1. ctr run 命令之后,首先会调用 containerd 的 Create Container 接口,将 container 数据保存在 metadb 中。

  2. Container 创建成功后,返回对应的 Container ID。

  3. Container 创建之后 ctr 会调用 containerd 的 Task Create 接口。

  4. containerd 为容器运行准备 OCI Bundle,其中 Bundle 中的 rootfs 通过调用 snapshotter 来准备。

  5. OCI Bundle 准备好之后,containerd 根据指定或默认的运行时名称解析 shim 二进制文件,例如:io.containerd.runc.v2 -> containerd-shim-runc-v2 ,containerd 通过 start 命令启动 shim 二进制文件,并加上一些额外的参数,用于定义命名空间、OCI bundle 路径、debug 模式,containerd 监听的 unix socket 地址等。在这一步调用中,当前工作目录 (OCI Bundle 路径) 设置为 shim 的工作路径。

  6. 调用 shim start 后,shim 启动 ttrpc server,并监听特定的 unix socket 地址,该 path 在<oci bundle path>/address 文件中的内容即为 该 unix socket 的地址,为 unix:///run/containerd/s/xxxxx

  7. ttrpc Server 正常启动后,shim start 命令正常返回,将 shim ttrpc server 监听的 unix socket 地址通过 stdout 返回给 containerd。

  8. containerd 为每个 shim 准备 ttrpc 的 client,用于和该 shim ttrpc server 进行通信。

  9. containerd 调用 shim 的 TaskServer.Create 接口, shim 负责将请求参数 CreateTaskRequest 中的Mount 信息中的文件系统挂载到 OCI Bundle 中的 rootfs/ 目录。

  10. 对 shim 的 ttrpc 调用执行成功后返回 Task ID。

  11. containerd 返回给 ctr Task 的 ID。

  12. ctr 通过 Start Task 调用 containerd 来启动容器进程

  13. contaienrd 通过 ttrpc 调用 shim 的 TaskServer.Start 方法,这一步是真正启动容器内的进程。

  14. shim 执行 Start 成功后返回给 containerd

  15. 接下来 ctr 调用 containerd 的 task.Wait API

  16. 触发 containerd 调用 shim 的 TaskService.Wait API。该请求会一直阻塞,直到容器退出后才会返回。

  17. shim 进程退出后会将进程退出码返回给 containerd

  18. containerd 返回给 ctr 客户端进程退出状态。  接下来是停止容器的流程。

图片

图 通过 ctr 停止容器时 containerd 与 Shim 交互的流程   

如图所示,展示的是通过 ctr task kill 删除容器时的相关调用流程,即

ctr task kill nginx

下面讲述下 kill 容器过程中的相关调用流程:。

  1. 执行 ctr kill 之后,ctr 调用 containerd 的 Task Kill API

  2. 触发 containerd 通过 ttrpc 调用 shim 的 TaskService.Kill API, Shim 会通过给进程发送 SIGTERM(等同于 shell kill)信号来通知容器进程退出,在容器进程超时未结束时再发送 SIGKILL (等同于 shell kill -9)。

  3. Kill 调用执行成功后返回给 containerd。

  4. containerd 返回成功给 ctr 客户端。

  5. ctr 继续调用 containerd 的 Task Delete API,该调用 containerd 会删除 task 记录,同时会调用 shim 的 相关来清理 shim 资源。

  6. containerd 首先会调用 Shim 的 TaskService.Delete API, shim 会删除容器对应的资源。

  7. Shim 返回 Delete 成功信号给 containerd

  8. containerd 继续调用 Shim 的 TaskService.Shutdown API, 该调用中 Shim 会停止 ttrpc Server 并退出 Shim 进程。

  9. Shim 退出成功

  10. containerd 关闭 shim 对应的 ttrpc Client。

  11. containerd 通过二进制调用方式执行 delete,即执行 containerd-shim-runc-v2 delete xxx 操作。

  12. 二进制调用 delete 会删除 对应的 OCI Bundle。

  13. containerd 返回 容器删除成功信号给 ctr 客户端。

以上内容节选自新书 《containerd 原理剖析与实战》

最后,附上本书的购买链接,新书刚刚上架原价 109,限时优惠内购 69.9 元,感兴趣的朋友可以尽快入手。内购链接扫描下面的二维码进行购买

图片

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

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

相关文章

UE4_常见动画节点学习_Two Bone IK双骨骼IK

学习资料&#xff0c;仅供参考&#xff01; Two Bone IK 控制器将逆运动&#xff08;IK&#xff09;解算器应用于到如角色四肢等3关节链。 变量&#xff08; HandIKWeight &#xff09;被用于在角色的 hand_l 和 hand_r 控制器上驱动 关节目标位置&#xff08;Joint Target Lo…

Navicat连接postgresql时出现‘datlastsysoid does not exist‘报错的问题

连接报错 解决方案 解决方法1&#xff1a;升级navicat 解决方法2&#xff1a;降级pgsql 解决方法3&#xff1a;修改dll 使用3解决 实操演示 1、 打开 Navicat 安装目录&#xff0c;找到libcc.dll文件 2、备份libcc.dll文件&#xff0c;将其复制并粘贴或者修改副本为任何其他名…

【C++杂项】cin的详细用法

cin详细用法 1. cin简介2. cin的常用读取方法2.1 cin>>的用法2.2 cin.get的用法2.3 cin.getline的用法 3. cin清空输入缓冲区4. 其它方法4.1 getline()读取一行 1. cin简介 cin是C中的标准输入流对象&#xff0c;即istream类的对象。cin主要用于从标准输入读取数据&…

DNS服务器的管理与配置

目录 一、相关知识 域名空间 DNS服务器分类 域名解析过程 资源记录 二、安装DNS服务 安装bind软件包 DNS服务的启动与停止 配置主要名称服务器 主配置文件 从例子学起&#xff1a; &#xff08;1&#xff09;建立主配置文件named.conf &#xff08;2&#xff09;…

Windows10安装Docker Desktop(大妈看了都会)

目录 Windows10安装Docker Desktop&#xff08;大妈看了都会&#xff09; 1.前言 1.1 为什么要在Windows10上安装Docker 1.2 Docker Desktop介绍 2.下载Docker Desktop 3.启用Hyper-V以在 Windows 10上创建虚拟机 4.安装Docker Desktop 5.运行Docker Desktop 6.Docker…

阿里云图片处理之 缩放

文档 : https://help.aliyun.com/zh/oss/user-guide/resize-images-4?spma2c4g.11186623.0.0.61cd2759v4jkhX 需求 : 图片过大, 导致加载过慢, 需对图片进行压缩 <image :src"imgUrl ?x-oss-processimage/resize,h_700,m_lfit"></image>Ps : 题外话…

如何下载省,市,区县行政区Shp数据

摘要&#xff1a;一般非专业的GIS应用通常会用到省市等行政区区划边界空间数据做分析&#xff0c;本文简单介绍了如何在互联网上下载省&#xff0c;市&#xff0c;区县的shp格式空间边界数据&#xff0c;并介绍了一个好用的在线数据转换工具&#xff0c;并且开源。 目录 一、下…

深度 | 践行绿色健康可持续发展,这家企业提供了价值范本

文 | 螳螂观察 作者 | 余一 近段时间以来&#xff0c;小米SU7热度一直不减&#xff0c;在展露小米强大品牌号召力的同时&#xff0c;也侧面体现出了当前消费者对于新能源汽车的喜爱。 而消费者选择新能源汽车时&#xff0c;环保因素也起到了至关重要的作用。像前几日&#x…

数据结构-上三角矩阵存储方式[0知识掌握]

目标&#xff1a;看完本文章你将会了解上三角矩阵的存储方式以及矩阵中数据的位置索引号如何求 难点&#xff1a;上三角矩阵的公式推导&#xff0c;上三角任意位置对应的存储位置。 一、准备知识 1.求和公式 前n项和&#xff1a;Sn n(a1an)/2 公差&#xff1a;d后项-前项…

【JavaEE多线程】线程安全、锁机制及线程间通信

目录 线程安全线程安全问题的原因 synchronized 关键字-监视器锁monitor locksynchronized的特性互斥刷新内存可重入 synchronized使用范例 volatilevolatile能保证内存可见性volatile不保证原子性synchronized 也能保证内存可见性 wait 和 notifywait()方法notify()方法notify…

拖拽式工作流有哪几个优势?

在信息技术迅猛发展的今天&#xff0c;如何助力中小型企业在数字化转型的过程中平稳过渡&#xff1f;又是如何让中小型企业摆脱数据孤岛、成本投入高等各种瓶颈和难题&#xff1f;低代码技术平台是近些年较为理想的平台产品&#xff0c;其中拖拽式工作流优势特点突出&#xff0…

地埋电缆故障检测方法有哪些?地埋电缆故障检测费用是多少?

地埋电缆故障检测方法主要涵盖脉冲反射法、桥接法、高压闪络法和声波定位法等多种方法。选择适当的方法取决于故障类型、电缆类型和实际现场条件。至于地埋电缆故障检测费用则受到多个因素的影响&#xff0c;包括故障类型、检测方法的复杂性、检测设备的先进程度以及所处地区的…

从零开始搭建社交圈子系统:充实人脉的最佳路径

线上交友圈&#xff1a;拓展社交网络的新时代 线上交友圈是社交网络的新引擎&#xff0c;提供了更广泛的社交机会&#xff0c;注重共同兴趣的连接&#xff0c;强调多样性的社交形式&#xff0c;更真实地展示自己&#xff0c;让朋友更全面地了解我们的生活状态。虽然虚拟交往存在…

SD-WAN解决电商企业海外业务网络难题

全球化背景下&#xff0c;众多国内企业都涉及到海外贸易业务&#xff0c;尤其是出海电商得到蓬勃发展。企业做出海电商&#xff0c;需要访问国外网页、社交平台&#xff0c;如亚马逊、TikTok、Facebook、YouTube等与客户沟通互动&#xff0c;SD-WAN的发展正好为解决国际网络访问…

14 Php学习:表单

表单 PHP 表单是用于收集用户输入的工具&#xff0c;通常用于网站开发。PHP 可以与 HTML 表单一起使用&#xff0c;用于处理用户提交的数据。通过 PHP 表单&#xff0c;您可以创建各种类型的表单&#xff0c;包括文本输入框、复选框、下拉菜单等&#xff0c;以便用户可以填写和…

主存储器与CPU之间的连接(会画图)

位扩展 字扩展 由于只有A13&#xff0c; A14 连到了译码器上&#xff0c;以、因此该译码器是一个 2/4 译码器&#xff0c;对应的选片有四种。选中第一个选片&#xff0c;就是把译码器“0口置0&#xff0c; 1~3口置1”&#xff0c;因为CS有非号&#xff0c;因此&#xff0c;低电…

【C++】string的使用

目录 1、为什么学习string类&#xff1f; 2、标准库中的string类 2.1 string类 2.2 string类的常见接口声明 2.2.1 string类的常见构造 ​编辑 2.2.2 string类对象的访问及遍历操作 2.2.3 string类对象的容量操作 2.2.4 string类对象的修改操作 ​编辑 1、为什么学习s…

excel中vlookup查找值必须在table_array的第一列,有其他办法吗有XLOOKUP

vlookup查找值必须在table_array的第一列&#xff0c;有其他办法吗&#xff1f;有XLOOKUP。 vlookup 查找如下&#xff0c;查找值必须在table_array的第一列 如果下面&#xff0c;编码和名称交换位置&#xff0c;就不能使用vlookup查找了。 XLOOKUP 查找如下

Linux:进程调度

Linux&#xff1a;进程调度 进程优先级查看优先级调整优先级 Linux 2.6 内核进程调度队列 进程优先级 查看优先级 在Linux中&#xff0c;进程是有优先级的&#xff0c;我们可以通过指令ps -la来查看&#xff1a; 其中PRI表示priority优先级&#xff0c;在Linux中&#xff0c;…

解决 vue install 引发的 failed Error: not found: python2 问题

发生 install 异常时&#xff0c;提示信息如下所示&#xff1a; npm ERR! code 1 npm ERR! path U:\cnblogs\fanfengping-dtops\fanfengping-dtops-front\node_modules\node-sass npm ERR! command failed npm ERR! command U:\Windows\system32\cmd.exe /d /s /c node scripts…