Containerd组件 —— containerd-shim-runc-v2作用
1、概述
通过《浅析开源容器标准——OCI》、《浅析容器运行时》和《浅析Kubernetes CRI》这三篇博文我们了解了容器标准OCI、容器运行时以及Kubernetes CRI,在本文以当前最火的容器运行时containerd为例,讲解下它是如何运行和管理容器进程的。
在讲解containerd是如何运行和管理容器进程前,先通过简单说下Kubelet + CRI + OCI工作流程以回顾下上面三篇博文的内容。
Kubelet在节点上接收到Pod的定义(Pod调度到当前节点上)。
Kubelet通过CRI(Container Runtime Interface)与容器运行时(例如containerd)通信,告知要创建一个新的容器。
容器运行时(containerd)使用OCI(Open Container Initiative)容器运行时标准来创建容器,包括设置容器的运行时环境、文件系统挂载和网络配置。
容器运行时(containerd)创建完成后,将容器的状态反馈给Kubelet,表示容器已经启动并正在运行。
总之,Kubelet作为Kubernetes节点上的代理,负责接收和处理Pod定义,通过CRI与容器运行时(如containerd)通信来创建和管理容器。容器运行时使用OCI标准来创建容器,而Kubelet负责监控容器的状态并协调Pod中多个容器的启动和关联关系。整个流程确保Pod中的容器能够成功启动和运行。
2、简述containerd各组件功能
通过containerd.io 1.6.6版本rpm包的文件列表来简述下containerd各组件功能:
- /usr/lib/systemd/system/containerd.service:systemd标准的Unit文件,被systemd管理:systemctl start|stop containerd.service。
- /usr/bin/containerd:containerd的守护进程文件,在containerd.service Unit文件中通过ExecStart=/usr/bin/containerd调用,以启动containerd守护进程。
- /etc/containerd/config.toml:在启动过程中加载此配置文件,可以在该配置文件中进行丰富多样的配置,以令containerd更贴合我们的实际需要(比如配置私有镜像源等)。
- /usr/bin/containerd-shim:containerd套件,其目的主要是隔离containerd和容器。containerd守护进程收到gRPC调用请求(比如来自Kubelet或Docker的创建容器请求),便会启动/usr/bin/containerd-shim套件。
- /usr/bin/containerd-shim-runc-v2:containerd-shim启动后会去启动/usr/bin/containerd-shim-runc-v2,然后立即退出,此时containerd-shim-runc-v2的父进程就变成了systemd(1),这样containerd-shim-runc-v2就和containerd脱离了关系,即便containerd退出也不会影响到容器(这也是containerd-shim套件的作用)。
- /usr/bin/runc:OCI标准的具体实现就是runc,真正创建和维护容器最终便是由runc来完成的。/usr/bin/containerd-shim-runc-v2会启动runc去create、start容器,然后runc立即退出,容器的父进程就变成了containerd-shim-runc-v2,这也是容器内部可以看到的PID=1的init进程。
- /usr/bin/ctr:容器管理的客户端工具,可以对标docker命令。
- rpm -ql containerd.io
- /etc/containerd
- /etc/containerd/config.toml
- /usr/bin/containerd
- /usr/bin/containerd-shim
- /usr/bin/containerd-shim-runc-v1
- /usr/bin/containerd-shim-runc-v2
- /usr/bin/ctr
- /usr/bin/runc
- /usr/lib/systemd/system/containerd.service
本文主题是containerd是如何运行和管理容器进程的,通过containerd各功能组件的介绍,我们可以清晰的知道containerd是通过containerd-shim-runc-v2组件进行进程管理的,所以下文主要讲解containerd的containerd-shim-runc-v2组件。
3、Containerd通过containerd-shim-runc-v2组件管理进程
通过《浅析容器运行时》这篇博文和本文第二章节对containerd各组件功能介绍我们可知。
- 当客户端(Kubelet或者DockerDaemon等)调用 containerd 来创建一个容器时,containerd 收到请求后,并不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程,让这个进程去操作容器;
- containerd-shim 启动后会去启动一个叫做 /usr/bin/containerd-shim-runc-v2 的进程,然后立即退出,此时 containerd-shim-runc-v2 的父进程就变成了systemd(1),这样 containerd-shim-runc-v2 就和containerd脱离了关系,即便containerd退出也不会影响到容器(这也是containerd-shim套件的作用);
- 之后 /usr/bin/containerd-shim-runc-v2 会运行runc这个二进制文件去create、start容器,runc 启动完容器后本身会直接退出, containerd-shim-runc-v2 则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的进程(containerd-shim-runc-v2进程所有子孙进程)进行清理, 确保不会出现僵尸进程。
注意 1: 为什么需要引入 containerd-shim-runc-v2 ?
我们指定容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作的,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,整个宿主机上所有的容器都得退出了,而引入 containerd-shim 这个垫片就可以来规避这个问题了。
- containerd-shim可以翻译成垫片或者中间件;
- containerd-shim可以认为是托管我们容器父进程的一个工具;
- 每一个容器起起来之后,都会有一个conatinerd-shim存在;
- containerd-shim主要是来控制你的容器的。
4、通过实战观察containerd-shim-runc-v2组件和容器进程关系
以docker作为客户端,观察docker调用 containerd 来创建一个容器时,containerd-shim-runc-v2组件和容器进程关系。
4.1 环境检查
- [root@pdh1 ~]# docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- [root@pdh1 ~]# ps -ef|grep containerd|grep -v grep
- root 1057 1 0 Apr19 ? 00:12:26 /usr/bin/containerd
- root 1371 1 0 Apr19 ? 00:19:27 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
可以看到,当前没有容器在这台机器上跑,只有一个containerd进程,没有containerd-shim-runc-v2进程。
4.2 启动一个busybox容器
启动一个busybox容器,启动命令为:sleep 10000秒。
- [root@pdh1 ~]# docker run -d busybox:1.28 sleep 10000
- fa19597696960f0ce6414eccc7402c25fb66be021707c31ab03034cd01f80d81
- [root@pdh1 ~]# docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- fa1959769696 busybox:1.28 "sleep 10000" 14 seconds ago Up 13 seconds serene_germain
- [root@pdh1 ~]# ps -ef|grep containerd|grep -v grep
- root 1057 1 0 Apr19 ? 00:12:26 /usr/bin/containerd
- root 1371 1 0 Apr19 ? 00:19:28 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
- root 29765 1 0 10:09 ? 00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id fa19597696960f0ce6414eccc7402c25fb66be021707c31ab03034cd01f80d81 -address /run/containerd/containerd.sock
可以看到,宿主机上,多了一个/usr/bin/containerd-shim-runc-v2进程,PID为29765,它的父进程(ppid)是1,而不是containerd进程。由此可以证明,当客户端调用 containerd 来创建一个容器时,containerd 收到请求后,并不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程,让这个进程去操作容器;containerd-shim 启动后会去启动一个叫做 /usr/bin/containerd-shim-runc-v2 的进程,然后立即退出,此时 containerd-shim-runc-v2 的父进程就变成了systemd(1),这样 containerd-shim-runc-v2 就和containerd脱离了关系,即便containerd退出也不会影响到容器。
4.3 查看containerd-shim-runc-v2进程的子进程
继续看一下这个containerd-shim-runc-v2进程,生出了哪些“宝宝”(子进程)。
- [root@pdh1 ~]# ps -ef|grep 29765|grep -v grep
- root 29765 1 0 10:09 ? 00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id fa19597696960f0ce6414eccc7402c25fb66be021707c31ab03034cd01f80d81 -address /run/containerd/containerd.sock
- root 29785 29765 0 10:09 ? 00:00:00 sleep 10000
可以看到,containerd-shim-runc-v2进程fork出sleep 10000进程(PID=29785), 这正是busybox容器的主进程, 是通过docker run 时设置进去的。
当然也可以使用docker top [容器ID]来查看当前容器的父进程。
- [root@pdh1 ~]# docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- fa1959769696 busybox:1.28 "sleep 10000" 6 minutes ago Up 6 minutes serene_germain
- [root@pdh1 ~]# docker top fa1959769696
- UID PID PPID C STIME TTY TIME CMD
- root 29785 29765 0 10:09 ? 00:00:00 sleep 10000
由此可以证明, /usr/bin/containerd-shim-runc-v2 调用runc二进制文件创建、启动容器后,runc 启动完容器后本身会直接退出, containerd-shim-runc-v2 则会成为容器主进程(容器内部PID=1 的进程)的父进程。
4.4 进入到容器中查看容器内进程
- [root@pdh1 ~]# docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- fa1959769696 busybox:1.28 "sleep 10000" 9 minutes ago Up 9 minutes serene_germain
- [root@pdh1 ~]# docker exec -it fa1959769696 ps -ef
- PID USER TIME COMMAND
- 1 root 0:00 sleep 10000
- 23 root 0:00 ps -ef
除了ps 进程外,容器里,只有一个sleep 10000的进程,它在容器里为PID=1,是容器的主进程,起着操作系统的systemd作用。
4.5 在容器里面运行一个长期运行的命令
ps命令跑完结束了,我们在容器里跑一个长期运行的命令:sleep 20000
4.6 换一个终端,继续观察containerd-shim-runc-v2进程的子进程
- [root@pdh1 ~]# docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- fa1959769696 busybox:1.28 "sleep 10000" 13 minutes ago Up 13 minutes serene_germain
- [root@pdh1 ~]# ps -ef|grep containerd|grep -v grep
- root 1057 1 0 Apr19 ? 00:12:26 /usr/bin/containerd
- root 1371 1 0 Apr19 ? 00:19:28 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
- root 29765 1 0 10:09 ? 00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id fa19597696960f0ce6414eccc7402c25fb66be021707c31ab03034cd01f80d81 -address /run/containerd/containerd.sock
- [root@pdh1 ~]# ps -ef|grep 29765|grep -v grep
- root 29765 1 0 10:09 ? 00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id fa19597696960f0ce6414eccc7402c25fb66be021707c31ab03034cd01f80d81 -address /run/containerd/containerd.sock
- root 29785 29765 0 10:09 ? 00:00:00 sleep 10000
- root 32315 29765 0 10:22 pts/0 00:00:00 sleep 20000
可以看到,在宿主机上,那个containerd-shim-runc-v2又多了一个子进程:sleep 20000(PID=32315)。
4.7 再次进入到容器中查看容器内进程
- [root@pdh1 ~]# docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- fa1959769696 busybox:1.28 "sleep 10000" 23 minutes ago Up 23 minutes serene_germain
- [root@pdh1 ~]# docker exec -it fa19 /bin/sh
- / #
在容器内部执行top命令。
- Mem: 8715620K used, 7552384K free, 794432K shrd, 3268K buff, 6090752K cached
- CPU: 2.4% usr 2.4% sys 0.0% nic 95.1% idle 0.0% io 0.0% irq 0.0% sirq
- Load average: 0.03 0.03 0.09 2/499 48
- PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
- 42 0 root S 1232 0.0 1 0.0 /bin/sh
- 48 42 root R 1228 0.0 3 0.0 top
- 1 0 root S 1216 0.0 0 0.0 sleep 10000
- 29 0 root S 1216 0.0 3 0.0 sleep 20000
在容器内部可以看到sleep 10000、sleep 20000这两个命令以及进入容器内部命令/bin/sh进程的父进程都是0,在宿主机上这三个进程都是/usr/bin/containerd-shim-runc-v2进程(PID为29765)进程的子进程。
- [root@pdh1 ~]# ps -ef|grep 29765|grep -v grep
- root 2151 29765 0 10:32 ? 00:00:00 /bin/sh
- root 29765 1 0 10:09 ? 00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id fa19597696960f0ce6414eccc7402c25fb66be021707c31ab03034cd01f80d81 -address /run/containerd/containerd.sock
- root 29785 29765 0 10:09 ? 00:00:00 sleep 10000
- root 32315 29765 0 10:22 pts/0 00:00:00 sleep 20000
使用pstree命令显示/usr/bin/containerd-shim-runc-v2进程(PID为29765)进程号树状信息。
- [root@pdh1 ~]# pstree -p 29765
- containerd-shim(29765)─┬─sh(2151)───top(2327)
- ├─sleep(29785)
- ├─sleep(32315)
- ├─{containerd-shim}(29766)
- ├─{containerd-shim}(29767)
- ├─{containerd-shim}(29768)
- ├─{containerd-shim}(29769)
- ├─{containerd-shim}(29770)
- ├─{containerd-shim}(29771)
- ├─{containerd-shim}(29772)
- ├─{containerd-shim}(29773)
- ├─{containerd-shim}(29774)
- ├─{containerd-shim}(29775)
- ├─{containerd-shim}(29811)
- ├─{containerd-shim}(29818)
- ├─{containerd-shim}(32321)
- ├─{containerd-shim}(2159)
- └─{containerd-shim}(2160)
- [root@pdh1 ~]#
和上面top命令结果吻合,sh、sleep、sleep这三个进程都是containerd-shim-runc-v2的子进程(PID为29765),top这个进程又是sh这个进程衍生出来子进程。
- [root@pdh1 ~]# ps -ef|grep 2151|grep -v grep
- root 2151 29765 0 10:32 ? 00:00:00 /bin/sh
- root 2327 2151 0 10:33 ? 00:00:00 top
由此可以证明,容器本质上就是宿主机上的一个特殊的进程,通过 Namespace 实现资源(网络、文件系统等)隔离,通过 Cgroups 实现资源(CPU、内存)限制,让我们使用起来就感觉像在操作虚拟机一样(但其和虚拟机有本质上的区别,那就是容器和宿主机是共享同一个内核的)。为了将我们的应用进程运行在容器中,容器运行时通过更加简单的接口和命令去帮我们调用 Linux 的系统功能来运行和管理容器进程和容器镜像。
注意 1:安装pstree命令,psmisc时一个进程管理软件包套装,里面拥有很多小工具来管理linux系统进程,pstree只是它的其中的一个。
- yum install -y psmisc
注意 2:通过pstree列出来的containerd-shim进程的子进程containerd-shim(29766)通过ps -ef是查询不到的。
4.8 杀死容器主进程
- [root@pdh1 ~]# ps -ef|grep 29765|grep -v grep
- root 2151 29765 0 10:32 ? 00:00:00 /bin/sh
- root 29765 1 0 10:09 ? 00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id fa19597696960f0ce6414eccc7402c25fb66be021707c31ab03034cd01f80d81 -address /run/containerd/containerd.sock
- root 29785 29765 0 10:09 ? 00:00:00 sleep 10000
- root 32315 29765 0 10:22 pts/0 00:00:00 sleep 20000
- [root@pdh1 ~]# kill -9 29785
- [root@pdh1 ~]# ps -ef|grep 29765|grep -v grep
- [root@pdh1 ~]#
- [root@pdh1 ~]# docker ps -a
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- fa1959769696 busybox:1.28 "sleep 10000" About an hour ago Exited (137) 2 minutes ago serene_germain
- [root@pdh1 ~]# ps -ef|grep containerd|grep -v grep
- root 1050 1 0 11:00 ? 00:00:00 /usr/bin/containerd
- root 1316 1 0 11:00 ? 00:00:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
在宿主机执行kill -9命令杀死容器主进程(容器内部 PID=1 的init进程),可以看到容器中的其他子进程和containerd-shim-runc-v2进程(PID为29765)都会被清理,由此说明,containerd-shim-runc-v2负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程。
4.9 containerd-shim-runc-v2进程和容器个数的关系
- [root@node1 ~]# ps -ef|grep containerd|grep -v grep|wc -l #由4.1可知,统计containerd进程个数需要-2
- 439
- [root@node1 ~]# docker ps -q | wc -l
- 437
在一台运行多个容器的物理机上分别统计运行容器和containerd-shim-runc-v2进程个数的关系,可以得出containerd-shim-runc-v2进程个数等于当前机器上运行容器个数。
5、总结
通过本博文阐述的内容,可以得出以下结论:
- 当客户端(Kubelet或者DockerDaemon等)调用 containerd 来创建一个容器时,containerd 收到请求后,并不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程,让这个进程去操作容器;
- containerd-shim 启动后会去启动一个叫做 /usr/bin/containerd-shim-runc-v2 的进程,然后立即退出,此时 containerd-shim-runc-v2 的父进程就变成了systemd(1),这样 containerd-shim-runc-v2 就和containerd脱离了关系,即便containerd退出也不会影响到容器(这也是containerd-shim套件的作用);
- 之后 /usr/bin/containerd-shim-runc-v2 会运行runc这个二进制文件去create、start容器,runc 启动完容器后本身会直接退出, containerd-shim-runc-v2 则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的进程(containerd-shim-runc-v2进程所有子孙进程)进行清理, 确保不会出现僵尸进程。
- 容器本质上就是宿主机上的一个特殊的进程,通过 Namespace 实现资源(网络、文件系统等)隔离,通过 Cgroups 实现资源(CPU、内存)限制,让我们使用起来就感觉像在操作虚拟机一样(但其和虚拟机有本质上的区别,那就是容器和宿主机是共享同一个内核的)。为了将我们的应用进程运行在容器中,容器运行时通过更加简单的接口和命令去帮我们调用 Linux 的系统功能来运行和管理容器进程和容器镜像。(在容器里看到的所有进程,其实都是在宿主机上的,容器技术只不过中用了“障眼法”,把它们的pid改了,让我们误以为容器是跑一个独立的环境中,这个技术叫PID Namespace。)
- 当前机器containerd-shim-runc-v2进程个数等于当前机器上运行容器个数。
参考:观察containerd-shim-runc-v2进程与容器里的1号进程
Containerd组件 —— containerd-shim-runc-v2作用的更多相关文章
- 关于docker创建容器报错-docker: Error response from daemon: runtime "io.containerd.runc.v2" binary not installed
今天在对一台服务器(docker相关的业务服务器)进行OS补丁时,默认使用的 yum update -y 对所有的安装包进行了升级 升级完成后,让应用方检查确认应用及功能是否一切正常,如果不正常,严重 ...
- 如何实现select组件的选择输入过滤作用
实现select组件的选择输入过滤作用的js代码如下: /** *其中//******之间的部分显示的是在没有选择输入过滤功能的代码上加入的功能代码 ** / (function ( $ ) { $. ...
- 【Pod Terminating原因追踪系列之一】containerd中被漏掉的runc错误信息
前一段时间发现有一些containerd集群出现了Pod卡在Terminating的问题,经过一系列的排查发现是containerd对底层异常处理的问题.最后虽然通过一个短小的PR修复了这个bug,但 ...
- docker,containerd,runc,docker-shim之间的关系
原文:https://blog.csdn.net/u013812710/article/details/79001463 关于containerd关于containerd的一些详解介绍,请参考cont ...
- 基于containerd二进制部署k8s-v1.23.3
文章目录 前言 k8s 组件 环境准备 创建目录 关闭防火墙 关闭selinux 关闭swap 开启内核模块 分发到所有节点 启用systemd自动加载模块服务 配置系统参数 分发到所有节点 加载系统 ...
- Kubernetes容器运行时弃用Docker转型Containerd
文章转载自:https://i4t.com/5435.html Kubernetes社区在2020年7月份发布的版本中已经开始了dockershim的移除计划,在1.20版本中将内置的dockersh ...
- Containerd教程
文档是从B站有关视频上对应找到的,具体视频地址是:https://www.bilibili.com/video/BV1XL4y1F7QB?p=21&spm_id_from=333.880.my ...
- 揭秘!containerd 镜像文件丢失问题,竟是镜像生成惹得祸
导语 作者李志宇,腾讯云后台开发工程师,日常负责集群节点和运行时相关的工作,熟悉 containerd.docker.runc 等运行时组件.近期在为某位客户提供技术支持过程中,遇到了 contain ...
- Containerd 的前世今生和保姆级入门教程
原文链接:https://fuckcloudnative.io/posts/getting-started-with-containerd/ 1. Containerd 的前世今生 很久以前,Dock ...
- 实操|如何将 Containerd 用作 Kubernetes runtime
日前专为开发者提供技术分享的又拍云 OpenTalk 公开课邀请了网易有道资深运维开发工程师张晋涛,直播分享<Containerd 上手实践 >,详细介绍 Containerd 的发展历程 ...
随机推荐
- [网络/Linux]处理安全报告/安全漏洞的一般流程与思路
对近期工作中所经历的4次处理第三方网络安全公司的安全报告及其安全漏洞的经验做一点小结. 1 流程 Stage1 阅读/整理/分类:安全漏洞报告的安全漏洞 (目的:快速了解漏洞规模和分布) Stage2 ...
- 使用Python代码远程连接服务器
目录 一.paramiko模块的介绍 二.基本使用(用户名密码登录) 三.用公钥私钥连接 一.paramiko模块的介绍 模块介绍 使用Python的第三方模块paramiko实现远程连接服务器 功能 ...
- StarRocks 3.0 集群安装手册
本文介绍如何以二进制安装包方式手动部署最新版 StarRocks 3.0集群. 什么是 StarRocks StarRocks 是新一代极速全场景 MPP (Massively Parallel Pr ...
- 【LeetCode动态规划#13】买卖股票含冷冻期(状态众多,比较繁琐)、含手续费
最佳买卖股票时机含冷冻期 力扣题目链接(opens new window) 给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 . 设计一个算法计算出最大利润.在满足以下约束条件下,你可以 ...
- [Pytorch框架] 2.1.1 PyTorch 基础 : 张量
文章目录 PyTorch 基础 : 张量 张量(Tensor) 基本类型 Numpy转换 设备间转换 初始化 常用方法 PyTorch 基础 : 张量 在第一章中我们已经通过官方的入门教程对PyTor ...
- 加速 AI 训练,如何在云上实现灵活的弹性吞吐
AI 已经成为各行各业软件研发的基础,带来了前所未有的效率和创新.今天,我们将分享苏锐在AWS量化投研行业活动的演讲实录,为大家介绍JuiceFS 在 AI 量化投研领域的应用经验,也希望为其他正在云 ...
- 终端命令行前出现(base)
原因 (base) 的出现是因为电脑安装了conda后,每次打开终端都会自动启动conda的base环境 解决 取消自动启动base环境:conda config --set auto_activat ...
- ubuntu18.04.4修改静态ip
ubuntu18.04.4修改静态ip 修改interfaces文件 sudo vim /etc/network/interfaces
- 2022-11-22:小美将要期中考试,有n道题,对于第i道题, 小美有pi的几率做对,获得ai的分值,还有(1-pi)的概率做错,得0分。 小美总分是每道题获得的分数。 小美不甘于此,决定突击复习,
2022-11-22:小美将要期中考试,有n道题,对于第i道题, 小美有pi的几率做对,获得ai的分值,还有(1-pi)的概率做错,得0分. 小美总分是每道题获得的分数. 小美不甘于此,决定突击复习, ...
- 2022-01-24:K 距离间隔重排字符串。 给你一个非空的字符串 s 和一个整数 k,你要将这个字符串中的字母进行重新排列,使得重排后的字符串中相同字母的位置间隔距离至少为 k。 所有输入的字符串
2022-01-24:K 距离间隔重排字符串. 给你一个非空的字符串 s 和一个整数 k,你要将这个字符串中的字母进行重新排列,使得重排后的字符串中相同字母的位置间隔距离至少为 k. 所有输入的字符串 ...