关于docker的一些深入了解

news2024/12/23 12:18:11

本文将深入介绍一下docker方面的知识,不尽完全,慢慢完善。

进程

进程的概念

在介绍docker的相关知识前,先了解一下相关概念。进程就是系统中正在运行的程序,进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就是创建了一个进程,在这个过程中操作系统对进程资源的分配和释放,可以认为进程就是一个程序的一次执行过程。

Linux下的三个特殊进程

Linux下有三个特殊的进程idle进程(PID=0),init进程(PID=1),和kthreadd(PID=2)

  • idle进程由系统自动创建,运行在内核态。idle进程的pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换,是其他所有进程的祖先进程。
  • kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间,负责所有内核进程的调度和管理。
    它的任务就是管理和调度其他内核线程kernel_thread,会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程 。
  • init进程由idle通过kernel_thread创建,在内核空间完成初始化后,加载init程序。

init进程

在这里我们就主要讲解下init进程,init进程由idle进程创建,完成系统的初始化。在Linux操作系统启动时,首先从 BIOS 开始,接下来进入 boot loader,由 bootloader 载入内核,进行内核初始化。内核初始化的最后一步就是启动 pid 为 1 的 init 进程,这个进程是系统的第一个进程,它负责产生其他所有用户进程。由此我们可以看出,整个系统的用户进程,是一棵由init进程作为根的进程树。(注意是用户进程

init 的一些特点

  • init 以守护进程方式存在,是所有其他进程的祖先。init 进程非常独特,能够完成其他进程无法完成的任务。
    init系统能够定义、管理和控制 init 进程的行为。它负责组织和运行许多独立的或相关的始化工作(因此被称为 init 系统),从而让计算机系统进入某种用户预订的运行模式。仅仅将内核运行起来是毫无实际用途的,必须由 init 系统将系统代入可操作状态。比如启动外壳 shell 后,便有了人机交互,这样就可以让计算机执行一些预订程序完成有实际意义的任务。

  • init进程有一个非常厉害的地方,就是SIGKILL信号对它无效。很显然,如果我们将一棵树的树根砍了,那么这棵树就会分解成很多棵子树,这样的最终结果是导致整个操作系统进程杂乱无章,无法管理。所以为了防止用户误操作init进程是无法kill掉的。
    init(PID 1)进程的发展也是一段非常有趣的过程,从最早的sysvinit,到upstart,再到systemd。我们可以用 pstree -p查看PID 1的 进程是谁。
    init(PID 1)的作用是负责清理那些被抛弃的进程(孤儿和僵尸进程)所留下来的痕迹,有效的回收的系统资源,保证系统长时间稳定的运行,可谓是功不可没。在理解了它的重要性之后,我们今天主要探讨一下在容器中的PID 1是怎么回事。

僵尸进程

僵尸进程指的是:进程退出后,到其父进程还未对其调用wait/waitpid之间的这段时间所处的状态。一般来说,这种状态持续的时间很短,我们一般很难在系统中捕捉到。但是,一些粗心的程序员可能会忘记调用wait/waitpid,或者由于某种原因未执行该调用等等,那么这个时候就会出现长期驻留的僵尸进程了。如果大量的产生僵尸进程,其进程号就会一直被占用,可能导致系统不能产生新的进程。(子进程挂了,如果父进程不给子进程“收尸”(调用 wait/waitpid),那这个子进程小可怜就变成了僵尸进程。)

孤儿进程

父进程先于子进程退出,那么子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)接管,并由init进程对它完成状态收集(wait/waitpid)工作。

容器中的PID 1

对Docker有一定使用经验的童鞋应该知道,容器并不是一个完整的操作系统,它也没有什么内核初始化过程,更没有像init(1)这样的初始化过程。在容器中被标志为PID 1的进程实际上就是一个普普通通的用户进程,也就是我们制作镜像时在Dockerfile中指定的ENTRYPOINT(CMD)的那个进程。而这个进程在宿主机上有一个普普通通的进程ID,而在容器中之所以变成PID 1,是因为linux内核提供的PID namespaces功能,如果宿主机的所有用户进程构成了一个完整的树型结构,那么PID namespaces实际上就是将这个ENTRYPOINT进程(包括它的后代进程)从这棵大树剪下来,很显然,剪下来的这部分东西本身也是一个树型结构,它完全可以自己长成一棵苍天大树(不断地fork),当然子namespaces里面是看不到整棵树的原貌的,但是父级的namespaces确可以看到完整的子树。

pid1 的测试

创建一个测试镜像

[root@k8s-m1 k8s-total]# cat Dockerfile 
From centos:7
CMD ["/bin/sh","-c","sleep 3600"]

[root@k8s-m1 k8s-total]# docker build -t lifecycle:v1 .
Sending build context to Docker daemon  3.223MB
Step 1/2 : From centos:7
 ---> eeb6ee3f44bd
Step 2/2 : CMD ["/bin/sh","-c","sleep 3600"]
 ---> Running in 5c9b2704c6cd
Removing intermediate container 5c9b2704c6cd
 ---> 8d208d2b880b
Successfully built 8d208d2b880b
Successfully tagged lifecycle:v1

[root@k8s-m1 k8s-total]# docker run -idt lifecycle:v1 
3971502e8f0410f779da7ed4a79aab8260754d9b2211b248b1153cd4ef6dd45e

[root@k8s-m1 k8s-total]# docker ps  -l
3971502e8f04   lifecycle:v1                                        "/bin/sh -c 'sleep 3…"   9 seconds ago    Up 8 seconds                          friendly_hodgkin

[root@k8s-m1 k8s-total]# docker exec -it 39 /bin/bash
[root@3971502e8f04 /]# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4364   356 pts/0    Ss+  02:15   0:00 sleep 3600
root         6  1.6  0.0  11828  1892 pts/1    Ss   02:16   0:00 /bin/bash
root        19  0.0  0.0  51732  1704 pts/1    R+   02:16   0:00 ps aux
[root@3971502e8f04 /]# 

从上面我们可以看到pid为1的是一个/bin/sh的进程,容器是单独一个pid namespaces的。通过下图可以更方便理解。由于子namespaces无法看到父级的namespaces,所以容器里第一个进程(也就是cmd)认为自己是pid为1,容器里其余进程都是它的子进程。
在这里插入图片描述

#在容器内部使用kill -9杀pid为1的进程发现是杀不掉的
[root@3971502e8f04 /]# kill -9 1
[root@3971502e8f04 /]# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4364   356 pts/0    Ss+  02:15   0:00 sleep 3600
root         6  0.0  0.0  11828  1892 pts/1    Ss   02:16   0:00 /bin/bash
root        20  0.0  0.0  51732  1704 pts/1    R+   02:19   0:00 ps aux
[root@3971502e8f04 /]# exit

宿主机上测试是否能杀掉容器内部pid为1 的进程

#查看容器内部pid为1的进程在宿主机上的pid号,通过docker top查看
[root@k8s-m1 k8s-total]# docker top 3971502e8f04
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                29298               29279               0                   10:34               pts/0               00:00:00            sleep 3600

#或者ps也可以查看
[root@k8s-m1 k8s-total]# ps aux|grep sleep
root     29298  0.4  0.0   4364   356 pts/0    Ss+  10:34   0:00 sleep 3600
root     29676  0.0  0.0 112812   976 pts/0    S+   10:35   0:00 grep --color=auto sleep

[root@k8s-m1 k8s-total]# kill -9 29298
[root@k8s-m1 k8s-total]# docker ps -a|grep 3971502e8f04
3971502e8f04   lifecycle:v1                                        "/bin/sh -c 'sleep 3…"   20 minutes ago       Exited (137) 7 seconds ago                friendly_hodgkin
#可以看到在宿主机上已经将容器内部pid为1的容器杀掉

容器存活时间

[root@k8s-m1 k8s-total]# docker run -d centos:7 ls
b80e296e4667f106cc5c71a39050841cc8a87917de16c3d582118b9818a8fd7b
[root@k8s-m1 k8s-total]# docker run -d centos:7 sleep 3600
5665a9169f9a1bfbdb0e38f5fb9bdd828fb7c79cd2edbba56be14abcbd4251c8

[root@k8s-m1 k8s-total]# docker ps -a|grep centos
5665a9169f9a   centos:7                                            "sleep 3600"             10 seconds ago   Up 9 seconds                              wonderful_germain
b80e296e4667   centos:7                                            "ls"                     21 seconds ago   Exited (0) 20 seconds ago                 peaceful_ishizaka

[root@k8s-m1 k8s-total]# 

docker run 后面镜像后面的command和arg会覆盖掉镜像的CMD(注意entrypoint一般不能被覆盖,注意二者区别)。上面例子通过命令行添加了cmd覆盖掉centos镜像默认的CMD bash。我们可以看到ls的容器直接退出了,但是sleep 3600的容器会运行3600s后才会退出。这也说明了容器不是虚拟机,容器是个隔离的进程。

而且容器的存活是容器里pid为1的进程运行时长决定的。所以nginx的官方镜像里就是用的exec格式让nginx充当pid为1的角色
CMD [“nginx”, “-g”, “daemon off;”]

JDK无法识别cgroup限制

首先Docker容器本质是宿主机上的一个进程,它与宿主机共享一个/proc目录,也就是说我们在容器内看到的/proc/meminfo,/proc/cpuinfo与直接在宿主机上看到的一致。
如下:

[root@8baf80228c25 /]# head -n3 /proc/meminfo 
MemTotal:        8007180 kB
MemFree:         2976140 kB
MemAvailable:    5661936 kB
[root@8baf80228c25 /]# exit
[root@k8s-m1 k8s-total]# head -n3 /proc/meminfo 
MemTotal:        8007180 kB
MemFree:         2991332 kB
MemAvailable:    5677132 kB
[root@k8s-m1 k8s-total]# 

jvm也是读取/proc目录,会导致无法识别cgroup限制。默认情况下,JVM的Max Heap Size是系统内存的1/4,假如我们系统是8G,那么JVM将的默认Heap≈2G。

Docker通过CGroups完成的是对内存的限制,而/proc目录是已只读形式挂载到容器中的,由于默认情况下Java压根就看不见CGroups的限制的内存大小,而默认使用/proc/meminfo中的信息作为内存信息进行启动,这种不兼容情况会导致,如果容器分配的内存小于JVM的内存,JVM进程申请超过限制的内存会被docker认为oom杀掉。

测试用例(OPENJDK)
在JDK8u212版本之前,JVM在容器里面识别到的是宿主机的内存。如果没有手动调整堆大小的话JVM默认会使用1/4的宿主机内存。这样会远远大于容器规格限制的内存,导致oom之后容器自动重启。这里我用我们生产用的openjdk8做演示,jdk8也是一个长期维护版本。测试机器为8G内存,给容器限制内存为4G,看JDK默认参数下的最大堆为多少。

jdk 212版本之前

[root@k8s-m1 k8s-total]# docker run -m 4GB --rm  openjdk:8u181 java -XshowSettings:vm  -version
VM settings:
    Max. Heap Size (Estimated): 1.70G
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_181"
OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

未能正确识别CGroup限制,使用的是/proc/meminfo 里面的值

jdk 212版本之后

[root@k8s-m1 k8s-total]# docker run -m 4GB --rm  openjdk:8-jdk java -XshowSettings:vm  -version
VM settings:
    Max. Heap Size (Estimated): 910.50M
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_312"
OpenJDK Runtime Environment (build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)
[root@k8s-m1 k8s-total]# 

能正确识别CGroup限制

总结,OpenJDK8老版本无法识别容器限制,我们在选jdk的时候可以选取高版本的OpenJDK8或adoptopenjdk。如果不想指定-Xmx,而让Java进程自动的发现容器限制,那么请选择JDK8u212之后的版本。如果你想要指定-Xmx,那么你选什么版本都可以。

Docker容器优雅终止方案

作为一名系统可靠工程师(SRE),你可能经常需要重启容器,毕竟 Kubernetes 的优势就是快速弹性伸缩和故障恢复,遇到问题先重启容器再说,几秒钟即可恢复,实在不行再重启系统,这就是系统重启工程师的杀手锏。然而现实并没有理论上那么美好,某些容器需要花费 10s 左右才能停止,这是为啥?有以下两种可能性:

  • 容器中的进程收到了信号,但不能进行处理。
  • 容器中应用的关闭时间确实就是这么长。
    对于第二种可能性这个还是主要需要开发对代码进行优化,本文主要解决第一种

如果要构建一个新的 Docker 镜像,肯定希望镜像越小越好,这样它的下载和启动速度都很快,一般我们都会选择一个瘦了身的操作系统(例如 Alpine,Busybox 等)作为基础镜像。
问题就在这里,这些基础镜像的 init 系统 也被抹掉了,这就是问题的根源!

init 系统有以下几个特点:

  • 它是系统的第一个进程,负责产生其他所有用户进程。
  • init 以守护进程方式存在,是所有其他用户进程的先祖。
    它主要负责:
    • 启动守护进程
    • 回收孤儿进程
    • 将操作系统信号转发给子进程

Docker 容器停止过程
对于容器来说,init 系统不是必须的,当你通过命令 docker stop mycontainer 来停止容器时,docker CLI 会将 TERM 信号发送给 mycontainer 的 PID 为 1 的进程。

  • 如果 PID 1 是 init 进程 ,那么 PID 1 会将 TERM 信号直接转发给子进程,然后子进程开始关闭,最后容器终止。
  • 如果没有 init 进程 - 那么容器中的应用进程(Dockerfile 中的 ENTRYPOINT 或 CMD 指定的应用,新版的docker应该做了优化,不管是使用ENTRYPOINT 或 CMD,shell模式在制作镜像时都会转为exec模式)就是 PID 1,不需要转发。应用进程直接负责响应 TERM 信号。这时又分为两种情况:
    • 应用收到不处理 SIGTERM - 如果应用没有监听 SIGTERM 信号,或者应用中没有实现处理 SIGTERM 信号的逻辑,应用就不会停止,容器也不会终止。
    • 应用收到 SIGTERM 信号并处理信号
      第一种会导致容器停止时间很长 运行命令 docker stop mycontainer 之后,Docker 会等待 10s,如果 10s 后容器还没有终止,Docker 就会绕过容器应用直接向内核发送 SIGKILL,内核会强行杀死应用,从而终止容器。

容器进程收不到 SIGTERM 信号?
如果容器中的进程没有收到 SIGTERM 信号,很有可能是因为应用进程不是 PID 1,PID 1 是 shell,而应用进程只是 shell 的子进程。而 shell 不具备 init 系统的功能,也就不会将操作系统的信号转发到子进程上,这也是容器中的应用没有收到 SIGTERM 信号的常见原因。

解决方案 :使用 init 系统

如果容器中的应用默认无法处理 SIGTERM 信号,又不能修改代码,可以在容器中添加一个 init 系统。init 系统有很多种,这里使用tini测试,它是专用于容器的轻量级 init 系统,使用方法也很简单:

  • 安装 tini
  • 将 tini 设为容器的默认应用
  • 将 test.sh 作为 tini 的参数

具体的 Dockerfile 如下:

[root@k8s-m1 tmp]#  cat Dockerfile
FROM alpine:3.18
COPY test.sh .
RUN chmod +x test.sh
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--", "./test.sh"]

[root@k8s-m1 tmp]# docker build -t test:v1 .
Sending build context to Docker daemon  24.06kB
Step 1/5 : FROM alpine:3.18
 ---> c1aabb73d233
Step 2/5 : COPY test.sh .
 ---> 7fd1ff5ff917
Step 3/5 : RUN chmod +x test.sh
 ---> Running in 772972a29e18
Removing intermediate container 772972a29e18
 ---> 8336d2d63696
Step 4/5 : RUN apk add --no-cache tini
 ---> Running in 2461ef80c3f4
fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tini (0.19.0-r1)
Executing busybox-1.36.1-r0.trigger
OK: 7 MiB in 16 packages
Removing intermediate container 2461ef80c3f4
 ---> b54a49cb55aa
Step 5/5 : ENTRYPOINT ["/sbin/tini", "--", "./test.sh"]
 ---> Running in fcd4c55b5ebd
Removing intermediate container fcd4c55b5ebd
 ---> 69039ca9c5a1
Successfully built 69039ca9c5a1
Successfully tagged test:v1

[root@k8s-m2 tmp]# docker  run -d test:v1 
9c6fbeae5422b62ee0cac50cc240d1ef83280045c302c730a373566ca6c13b8b

[root@k8s-m2 tmp]# docker ps -l
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
9c6fbeae5422   test:v1   "/sbin/tini -- ./tes…"   3 seconds ago   Up 2 seconds             vigorous_tereshkova

[root@k8s-m2 tmp]# docker exec -it 9c /bin/sh
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/tini -- ./test.sh
    6 root      0:00 {test.sh} /bin/sh ./test.sh
   21 root      0:00 /bin/sh
   28 root      0:00 ps aux
/ # 

从上面结果可以看到现在 tini 就是 PID 1,它会将收到的系统信号转发给子进程 test.sh。

如果你想直接通过 docker 命令来运行容器,可以直接通过参数 --init 来使用 tini,其实不需要在镜像中安装 tini。如果是 Kubernetes 就不行了,还得老老实实安装 tini。

其实在k8s中的ingress-nginx就使用了dumb-init,一样的效果

    "Cmd": [
        "/nginx-ingress-controller"
    ],
    "ArgsEscaped": true,
    "Image": "sha256:f3745c5705f1617a2b44c28ee7f5637257b9ca281b9df0a2069c6c8d30ebbba8",
    "Volumes": null,
    "WorkingDir": "/etc/nginx",
    "Entrypoint": [
        "/usr/bin/dumb-init",
        "--"
    ]

而如果安装了tini,应用test.sh 中是否还需要类似如下脚本中对 SIGTERM 信号的处理逻辑。也就是trap "exit" TERM

cat test.sh
#!/bin/sh
# catch the TERM signal and then exit
trap "exit" TERM   
while true 
do
	date
	sleep 1 
done

大家可以自行测试,是不影响的。也就是可以不需要在对 SIGTERM 信号再进行处理。

更多关于docker容器和运维相关的知识,请前往博客主页。

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

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

相关文章

关于 Ubuntu 长按 shift 无效, 按 Esc 直接进入 grub 改密码的解决方法

本次长按shift没有反应,直接进入了系统界面,所以改用长按Esc键,步骤如下: 1. 长按esc,进入grub>提示 2.输入grub>normal ,回车 3.上一步回车后,继续敲击Esc ,出现grub界面 …

rdp、ftp协议的密码爆破

远程桌面 rdp协议 3389 文件传输 FTP协议 20 21 攻击方:Kali 测试方:Win7 两台都要在同一网段 密码爆破工具 hydra九头蛇 hydra(九头蛇)是著名黑客组织thc的一款开源的暴力破解密码工具,功能非常强大,ka…

Emacs之远程开发C++配置: emacs + tramp + clangd(一百二十六)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…

大数据学习教程:Linux高级教程(下)

四、大数据集群服务器搭建 1. 新增Linux服务器 1.1、克隆虚拟机 学习环境中,一般使用VMware虚拟机克隆Linux系统,用来进行集群服务器的搭建。 VMware支持两种类型的克隆:完整克隆、链接克隆 完整克隆是和原始虚拟机完全独立的一个复制&…

系统架构设计师_备考第2天(计算机组成与体系结构)

文章目录 考频(3分左右)一、计算机结构二、CPU组成三、冯诺依曼结构和哈弗结构四、层次化存储结构五、Cache 考频(3分左右) 提示:这里可以添加本文要记录的大概内容: 计算机结构(★&#xff0…

魔法上网端口号被占用通过端口号找到进程并且杀掉进程随笔

Windows11系统由于魔法上网被异常关闭导致再次启动的时候报出端口号被占用问题记录以前忘记的通过端口杀掉进程相关操作。 在命令行(winr,输入cmd)中输入: netstat -ano 可以看到本机正在使用的ip地址和端口号如图: 在命令行(wi…

Array.prototype.slice.call()方法详解

slice:用来截取截取字符串方法Array: javascript的一个引用类型,其原型prototype上有一个方法叫slicecall和apply : 用来改变对象中函数内部的this引用,使得函数可以随便换‘妈妈’ 为什么不直接用 arguments.slice(1)呢 不是一样的么? 答案…

springboot第33集:nacos图

./startup.sh -m standalone Nacos是一个内部微服务组件,需要在可信的内部网络中运行,不可暴露在公网环境,防止带来安全风险。Nacos提供简单的鉴权实现,为防止业务错用的弱鉴权体系,不是防止恶意攻击的强鉴权体系。 鉴…

门面模式:简化复杂系统的接口调用

门面模式:简化复杂系统的接口调用 什么是门面模式? 门面模式(Facade Pattern)是一种结构型设计模式,它提供了一个简单的接口,用于访问复杂子系统中的一组接口。门面模式通过封装子系统的复杂性&#xff0…

如何免费制作中小学分班查询系统?

暑假即将结束,新学年即将开始,学校面临着一个重要任务:学生分班。不论是新生入学还是低年级升入高年级,都需要进行分班工作。这对负责分班的老师们来说,增加了相当大的工作量和挑战。 在开学前,如何快速解…

排序八卦炉之插入和希尔

文章目录 1.插入排序1.1代码1.2复杂度 2.希尔排序2.1代码2.2复杂度2.3算法解析 1.插入排序 1.1代码 //插入排序1.0 /* void InsertSort(int* a, int n) {//i&#xff1a; 0 -- 倒数第2个元素for (int i 0; i < n - 1; i){//end记录iint end i;//tmp记录end后一个值int tm…

ios_base::out和ios::out、ios_base::in和ios::in、ios_base::app和ios::app等之间有什么区别吗?

2023年8月2日&#xff0c;周三晚上 今天我看到了这样的两行代码&#xff1a; std::ofstream file("example.txt", std::ios_base::out);std::ofstream file("example.txt", std::ios::out);这让我产生了几个疑问&#xff1a; 为什么有时候用ios_base::o…

物联网潜在的巨大价值在于大数据分析

物联网潜在的巨大价值在于大数据分析 从数据里去挖掘市场或者用户的精准需求。 往小的说&#xff0c;后台可以统计用户家里各各插座一年甚至更久的用电情况&#xff0c;这些数据也可以通过app或者小程序展现给用户。 用户可以很直观看到自己一年的用电情况&#xff0c;哪个家…

2.6 伽马校正 一、Gamma校正

一、Gamma校正 颜色空间 通用&#xff1a;sRGB 电影&#xff1a;DCI-P3 电视&#xff1a;Rec-709、PAL等 印刷&#xff1a;CMYK、Adobe RGB 传递函数 我们知道了颜色的颜色值&#xff0c;要在电子设备上显示&#xff0c;就要把它转换为视频信号&#xff0c;传递函数就是用…

Linux上安装Keepalived,多台Nginx配置Keepalived(保姆级教程)

目录 一、yum安装 第一步&#xff1a;下载 第二步&#xff1a;编辑Keepalived配置文件&#xff08;第一台&#xff09; 第三步&#xff1a;编辑Keepalived配置文件&#xff08;第二台&#xff09; 第四步&#xff1a;我们在本机利用cmd ping一下 一、yum安装 第一步&…

[国产MCU]-BL602开发实例-开发环境搭建

开发环境搭建 文章目录 开发环境搭建1、BL602介绍2、软件准备3、源码编译3.1 编译内置工程3.2 自定义工程、自定义组件添加与编译4、固件下载BL602 是一款Wi-Fi + BLE组合的芯片组,用于低功耗和高性能应用开发。无线子系统包含2.4G无线电,Wi-Fi 802.11b/g/n和BLE 5.0 基带/MA…

【编程范式】聊聊编程的本质

任何算法都会有两个部分&#xff0c; 一个是 Logic 部分&#xff0c;这是用来解决实际问题的。另一个是 Control 部分&#xff0c;这是用来决定用什么策略来解决问题。Logic 部分是真正意义上的解决问题的算法&#xff0c;而 Control 部分只是影响解决这个问题的效率。程序运行…

单元测试之- mock工具mockito

常用的mock工具mockito 在编写单元测试时&#xff0c;需要mock依赖的对象&#xff0c;减少依赖对象对测试的影响&#xff0c;Mocktio是常用的mock工具之一&#xff0c;那么mockito提供了哪些功能呢&#xff1f; Mock对象的创建和配置&#xff1a;Mockito可以通过简单的语法创建…

WebView2对比CefSharp的超强优势

第一次使用了CefSharp组件&#xff0c;集成开发结束后&#xff0c;测试及使用过程中遇到了一些无法处理的bug及严重的性能问题。然后又测试对比了其他多种组件&#xff0c;具体情况可以阅读我的博客​ ​《.NET桌面程序集成Web网页开发的十种解决方案》​​。最终选用了微软新出…

质数(判定质数 分解质因数 筛质数)

这里写目录标题 一、判定质数思路分析代码实现 二、分解质因数思路分析典型题目代码实现 三、质数筛经典题目思路分析1. 朴素筛法2. 埃氏筛法3. 欧拉筛法 一、判定质数 思路分析 由于每个合数的因子是成对出现的&#xff0c;即如果 d d d 是 n n n 的因子&#xff0c;那么 …