CS 之 书
关于各种代码编辑工具的使用技巧
关于 vscode 的各种使用技巧
问题描述: 在主机上更换系统为Arch OS,然后想要笔记本用Vsc(Visual Studio Code)的ssh远程连接功能连接该主机开发,但是不断尝试始终连接失败
解决方法:
- 失败的原因,可以通过查看output窗口的输出来分析。可以点击view选项栏,点击
output
来拉出output窗口,如果你的output窗口是隐藏的话。 - 注意到启动远程连接的时候,output信息中提示说远程主机的信息对不上,因为ssh连接建立的时候,会获取并记录远程主机的机器信息,并且与网络地址绑定存储在本地的
~/.ssh/known_hosts
文件中,重新与该网络地址(ip+端口)建立连接的时候会效验 远程主机提供的该信息是否与 之前记录的匹配, 如果不匹配则会发出警告,在控制台通过命令行连接的时候还会询问是否确认建立连接。所以笔者推测,是否之前建立连接的时候,因为OS不一样,生成的效验码不一样,然后被笔记本保存下来, 结果 现在新OS产生的效验码与 本地ssh保存的对不上,进而导致后续vsc ssh连接失败? - 笔者删除了本地的
~/.ssh/known_hosts
文件,重新启动vsc,重新与该网络地址建立ssh连接。因为删除了历史记录,相当于第一次建立连接,vsc询问是否continue
,并提示是一个陌生的机器,笔者选择continue
后成功连接上。 - 所以另一种建立连接的成功方法可能是,先删除本地记录的主机信息,然后命令行建立连接记录上更新后的主机信息,可能这样子做之后vsc的ssh远程开发连接也能建立成功
vscode 插件下载失败问题 解决方案
问题的原因与解决方案
1. 关闭代理
ubuntu上使用vscode下载插件的时候出现XHR failed 原因:可能是网络不好.可能是之前开启了网络代理的原因.
解决方法:配置vscode不使用代理 打开Settings界面, 搜Proxy,选择关闭
2.离线下载插件
如果远程连接服务器进行开发,服务器网络受到魔法限制无法访问vscode插件仓库的时候,就需要通过离线方式下载插件。
去官网插件市场找到插件的.vsix文件,使用vscode提供的控制台code
工具载入.vsix文件
vscode无法监听文件变化的解决方法
- 可能是因为项目太大文件太多,监听钩子不够
小林coding OS 篇 阅读笔记
Ref
- https://xiaolincoding.com/os/
三. 操作系统结构
Ref
- https://xiaolincoding.com/os/2_os_structure/linux_vs_windows.html
Note
1. windows内核和linux内核区别
windows闭源, linux开源
2. 什么是内核?
"作为应用连接外设的桥梁" 管理外设的中间层,让上层应用程序不需要直接与外设交互,而是可以通过内核提供的接口间接操作外设
3. 内核有哪些能力?
进程调度、内存管理、硬件管理、提供系统调用
4. 内核如何工作?
划分内存空间为用户空间,内核空间。 程序运行状态可以分为: 用户态、内核态。 用户态: 程序使用用户空间的时候 内核态: 程序使用内核空间的时候
交互机制: 用户态程序调用系统调用的时候,会产生一个中断, cpu中断用户程序的执行转而执行内核中断处理程序(进入内核态),处理完成后再主动触发中断, 让用户程序恢复执行
5. Linux的设计理念
Multitask(多任务),SMP(对称多处理), ELF(可执行文件链接格式),Monolithic Kernel(宏内核)
6. windows内核结构
windows7和windows10用的内核设计是混合型内核
7. 内核架构分类
宏内核、微内核、混合内核
内存管理
一. 为什么要有虚拟内存?
1. 为什么要有虚拟内存?
为应用程序开发提供统一的虚拟内存地址空间, 避免让应用程序处理直接操作物理内存可能存在的地址冲突问题。
虚拟地址: 程序使用的内存地址叫做虚拟内存地址 物理地址: 物理内存的地址叫做物理内存地址
单片机开发中程序是直接操作物理内存 linux中程序是操作虚拟内存
2. 虚拟内存工作的硬件依赖
虚拟地址通过cpu中的MMU转换为物理地址 MMU: 内存管理单元
3. 操作系统如何管理虚拟地址和物理地址之间的关系?
内存分段、内存分页
4. 什么是内存分段? (https://xiaolincoding.com/os/3_memory/vmem.html#%E5%86%85%E5%AD%98%E5%88%86%E6%AE%B5)
虚拟内存地址=段选择子+段内偏移,根据段选择子中的段号在段表项中找到对应段描述符(含有段基址和段界限),段内偏移加上段基址就是物理地址,同时能够判断段内偏移是否越界
优点: 不存在内部内存碎片,分配连续的内存空间 缺点: 存在外部内存碎片,内存交换效率低(比如段可能很大,一次换入/换出耗时较长)
5. 什么是内存分页? 什么是简单分页? 为什么要有多级页表?
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。 简单分页是指只用1级页表来管理虚拟地址和物理地址之间的映射关系。 多级页表是为了节省内存空间而设计的。因为简单分页需要为每个虚拟页分配一个物理页框,如果虚拟地址空间很大,可能会浪费大量内存。多级页表用比较小的1级页表来覆盖所有虚拟页,后面的n级(n>=2)页表只在需要的时候才分配,节省了内存空间。
关键词: 页表、页号、页内偏移、多级页表
6. TLB(Translation Lookaside Buffer) 作用是什么?
TLB 是一个硬件缓存,用于存储最近使用的虚拟地址到物理地址的映射,以加速地址转换过程。
7. 什么是段页式内存管理?
先分段再分页,虚拟地址=段号+页号+页内偏移
8. 为什么linux要把所有段的基地址设为0?
关键词: 逻辑地址、线性地址(虚拟地址)、物理地址、Intel处理器发展史
9. linux的虚拟地址空间是如何分布的?
每个程序的内存空间中,从高到低分别是: 内核地址、栈地址、文件映射地址、堆地址、BSS地址、数据段、代码段
每个程序虚拟内存中的内核地址,关联的都是相同的物理内存。
10. 虚拟内存有什么作用?
- 降低应用程序管理内存复杂性: 每个程序有独立的虚拟内存空间,避免开发者处理物理内存地址冲突问题
- 提供更大的内存地址空间: 支持进程使用大于物理内存的内存空间
- 提供更好的内存访问安全性: 比如通过页表项中的权限位来限制进程对内存的访问
11. 什么是内存交换?
内存交换是指把不常用的内存页换出到磁盘上,以释放内存空间。linux中使用swap分区来实现内存交换。
12. 什么是缺页中断? 缺页中断是内存访问的哪一步触发的?
在程序试图访问一个没有物理内存映射的虚拟内存页时,会触发缺页中断。缺页中断发生在虚拟地址转换为物理地址的过程中。 CPU尝试访问内存,MMU查询页表发现该虚拟页没有对应物理页映射,于是触发一个缺页中断(一个硬件中断),操作系统捕获到这个中断, 为这个中断处理程序分配物理页,并更新页表,使得虚拟页映射到新分配的物理页。之后,CPU可以继续执行原来的指令,访问到正确的物理内存。
二. malloc是如何分配内存的?
1. 内核空间和用户空间比例
32位系统中, 总空间4G, 内核空间1G, 用户空间3G 64位系统中, 内核空间和用户空间都是128T
2. malloc是系统调用吗? 底层是什么?
malloc是c库函数,底层调用brk()或mmap()系统调用来分配内存。
- brk(): 调整堆顶指针的位置,用于分配小块内存
- mmap(): 映射文件或匿名内存到进程的虚拟地址空间,用于分配大块内存
这里内存的大块小块的划分根据不同glibc的实现而不同, 一般阈值是128kB
3. malloc分配的是物理内存吗?
不,是虚拟内存
4. malloc(1)会分配多大内存?
不是1字节,而是预分配更大的空间做内存池待后面使用。
5. 如何查看进程的内存分配情况?
cat /proc/$pid/maps
查看进程的内存映射情况
6. free释放内存,会归还给操作系统吗?
使用brk()分配的内存,free后不会归还,而是缓存待下次使用; 使用mmap()分配的内存,free后会归还。
最终进程结束的时候,不管之前归没归还的资源都会回收
7. 为什么不全部使用mmap来分配内存?
使用brk()分配内存的内存会放到malloc的内存池中缓存使用,后续再分配的时候可能就不需要再走系统调用,减少系统调用次数。另外,之前分配的虚拟内存地址在页表中的物理地址映射可能还存在,复用之前分配的虚拟内存地址的话,还可能减少缺页中断重新映射物理地址的次数
8. 为什么不全部使用brk()分配内存?
brk分配的内存free的时候不会被释放而是被malloc的内存池管理等待复用,比较容易造成内存碎片,如果要分配大块内存的话很可能无法复用内存碎片的空间,而是需要继续增长brk分配的内存,所以对于大块的内存,使用mmap分配比较好,
9. free只传入一个内存地址,如何知道要释放多大内存?
把分配的内存块大小存放在了内存块地址前面的16字节中
10. malloc内存分配器是如何实现的?
https://mp.weixin.qq.com/s/Flt85kKbDEn_XD83mtYxUA?token=1646973705&lang=zh_CN
三. 内存满了,会发生什么?
1. 内核分配的过程是怎么样的?
malloc分配虚拟内存,虚拟内存实际访问的时候如果发现未映射到物理内存,则触发缺页中断,开始分配物理内存。 如果物理内存充足,则直接分配。如果物理内存不足,则唤醒后台内存回收线程kswapd进行后台内存回收(异步),如果回收速度跟不上进程分配内存的速度,则启动直接内存回收(同步), 如果还是没有足够物理内存,则启动OOM,OOM会持续杀死物理内存占用较高的进程,直到释放足够的内存。
2. 哪些内存可以回收?
文件页:
- 干净页: 直接回收
- 脏页: 先写回磁盘再回收 匿名页: 通过Swap机制回收
3. 回收内存会带来什么性能影响? 如何优化?
回收内存会造成一些磁盘I/O操作,降低系统性能,其中文件页回收比匿名页回收往往性能影响更大。
优化方法:
- 设置
/proc/sys/vm/swappiness
选项,调整文件页和匿名页的回收倾向 - 尽早触发kswapd线程开启异步回收:通过设置min_free_kbytes
4. SMP架构是什么?SMP架构有什么问题?
SMP: 对称多处理器架构。多CPU平等共享系统资源。也称为UMA(Uniform Memory Access)架构
缺点: 总线带宽压力随着核数增大。
5. NUMA架构如何解决SMP架构总线带宽压力过大的问题?
CPU分组,每组CPU用一个Node表示。
"每个 Node 有自己独立的资源,包括内存、IO 等"
当Node的内存不足的时候,有多种回收模式可以选择, 通过/proc/sys/vm/zone_reclaim_mode
控制:
- 0(默认): 先去其他Node找空闲内存
- 1: 只回收本地
- 2: 只回收本地, 可写回脏页
- 4:
注意: 不同的模式是可以组合使用的
6. 如何保护一个进程不被OOM杀掉?
设置/proc/[pid]/oom_score_adj
参数,
取值范围:-1000~1000,越小越不容易被杀掉
四. 4G物理内存上,申请8G内存会怎么样?
32位操作系统上: 8G操过了理论上用户态虚拟内存空间上限3G,会申请失败
64位操作系统上: 8G小于物理内存上限128T,可以申请成功。 但是访问虚拟内存的时候,根据是否开启swap机制会有不同结果:
- 不开启swap机制: 使用的时候,会因物理空间不足,触发OOM,导致进程被杀掉
- 开启swap机制: 使用的时候,可以通过swap来回收内存,该书中测试是可以正常访问;书中把分配的虚拟内存增大到64G的时候,使用过程中进程还是被OOM杀掉了,因为系统多次回收内存还是不能够获取足够的内存。
总结:
- 如果分配的虚拟内存空间操作系统的用户态虚拟内存空间上限(32位操作系统3G)会申请失败
- 分配成功后,使用时如果没有开启swap机制,会因为物理空间不足触发OOM,导致进程被杀掉
- 如果开启了swap机制,使用超过物理内存的虚拟内存空间时,会通过swap机制回收内存,如果回收能够获取足够的内存,那么就能够正常使用;如果多次回收内存还是不能够获取足够的内存,那么就会触发OOM,导致进程被杀掉
五. 如何避免预读失效和缓存污染的问题?
普通的LRU缓存淘汰算法存在两个问题: 预读失效: 预先读取的页后续没有被使用,却被缓存了且淘汰了热点页 缓存污染: 某些一次性读取的页面后续不再使用,却占用了缓存空间,导致热点页被淘汰
解决预读失效: 通过改进LRU算法,给予访问的页和预读的页不同的优先级。 linux的LRU实现使用两个链表:active list和inactive list,预读的页先加入inactive list头部,只有访问过的页才能加入active list中; MySQL Innodb使用一个链表,链表划分为两个部分young和old,预读的页先插入old头部,只有访问过的页才加入young中。
解决缓存污染: 提高优先级改变的阈值,避免仅仅因为访问一次就改变优先级。 linux的LRU实现中在预读内存页被访问第二次的时候,才从inactive list移到active list中; MySQL Innodb,不仅预读的内存页要访问到第二次,还要判断两次时间间隔是否超过1s, 超过了才会从old移到young中。
什么是预读? 利用空间局部性原理,为了提高性能,操作系统在缓存某个要访问的页的时候,会预先读取并缓存后续的若干页。
六. 深入理解Linux虚拟内存管理
1. 什么是虚拟内存地址?
一个用于关联到物理地址的数据。
32位虚拟内存地址=页目录项(10)+页表项(10)+页内偏移(12)
每个虚拟内存地址用于定位一个虚拟内存空间中一个特定的字节。
2. 为什么要使用虚拟内存地址访问内存?
- 提供进程隔离: 每个进程都有独立的虚拟内存空间,避免了不同进程直接使用物理地址需要处理的地址冲突的问题。
- 统一开发者视图,简化开发难度: 每个进程都有独立的虚拟内存空间,避免了开发者需要处理物理内存地址冲突的问题,而且使用连续的大块的虚拟内存空间的时候,不需要考虑底层的物理内存空间是否是连续的问题。把内存管理的复杂性交给操作系统的内存管理模块处理。
- 支持内存超售: 虚拟内存允许系统分配比物理内存更多的内存
- 提高安全性: 通过页表项中的权限位来限制进程对内存的访问
3. 进程虚拟内存空间布局
- https://xiaolincoding.com/os/3_memory/linux_mem.html#_3-%E8%BF%9B%E7%A8%8B%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98%E7%A9%BA%E9%97%B4
4. Linux进程虚拟内存空间
七. 深入理解linux物理内存管理
笔者认为最好的回答。
一句话: 大端序高字节低地址, 小端序高字节高地址
在默认 从 左到右地址 增大 的情况下。
大端序是 每个字节 从左到右书写. 小端序是 每个字节 从右到左书写.
比如一个单词 abc,三个字符{a,b,c}分别对应16进制数字:0x61,0x62,0x63 把它作为一个数据,对他来说a、b相对于c是高字节,b、c相对于a来说是低字节
大端序为: 0x61,0x62,0x63 小端序为: 0x63,0x62,0x61
工程开发相关
Dockerfile命令
参考资料
Overview
- 什么是Dockerfile
- 如何编写Dockerfile
- Q&A
一. 什么是Dockerfile
Dockerfile 是 组织 Dockerfile命令的文件, Dockerfile命令用来指导docker build 构建docker 镜像的过程,一般Dockerfile命名为Dockerfile
,也有命名为dockerfile
的。
一般使用方式如:docker build . -t <target-image-name>
其中.
指示开始构建过程的时候以当前路径为初始构建环境路径,-t <target-image-name
指定构建的镜像的名字,比如hello:1.0
,比如test
,镜像名可能包括版本号
二. 如何编写Dockerfile
1. Dockerfile命令语法格式
2. Dockerfile的结构:
一个Dockerfile至少包含一个FROM命令,一般放在最开头,用来指定镜像的基底,如
FROM ubuntu:latest
指定该镜像的构建基于ubuntu:latest
镜像
一般会包含一个CMD命令指定从镜像启动容器的时候执行的程序,如CMD ["echo","Hello World!"]
所以可以编写一个简单的镜像的Dockerfile如下:
FROM ubuntu:latest
CMD ["echo","Hello World!"]
三. Q&A
1. Dockerfile中是否能够使用环境变量? 是否能够使用外部设定的环境变量?
2. Dockerfile中COPY命令使用的参数中是否参数名能够使用环境变量参与组成?
3. Dockerfile中的RUN命令和CMD命令的区别是什么?
4. Dockerfile中
Ref
- ubuntu24上安装: https://www.sysgeek.cn/install-docker-ubuntu/
Q&A
- 问题
docker ps
失败, 原因可能是 用户不在docker用户组中,使用如下命令把用户加入用户组sudo usermod -aG docker $USER
如果使用了该命令还没有解决,可能是docker用户组不存在,cat /etc/group |grep docker
确认 尝试如下操作:
sudo groupadd docker
sudo gpasswd -a $USER docker
newgrp docker
- 通过 registry 服务分发
- 通过打包传输解压镜像文件分发
docker save -o myimage.tar myimage:tag # 从压缩文件恢复镜像 docker load -i myimage.tar
docker compose 使用
Overview
-
参考资料
-
简介
-
常用命令和基础概念
-
docker-compose.yml 与 .env 语法
-
Q&A
一.参考资料
二.简介
docker compose 是一种 命令行工具,能够用来管理多容器的docker应用的配置与执行。
一种获取方式是通过给 docker 加入插件的方式获取,这种方式能够使用docker compose ...
形式命令调用。新版本的docker下载后自带该插件
另一种获取docker compose的方式是下载单独的docker-compose
软件,往往用于老版本。
三.常用命令与基础概念
-
配置文件:
docker compose 使用
docker-compose.yml
文件配置要创建的容器以及网络,通过.env
文件 配置docker-compose.yml
中用到的环境变量这两个文件要在同一个路径下
-
启动与关闭
docker compose up -d #启动
docker compose down #关闭
这两个命令要在配置文件同一路径下输入 -
查看是否已经根据配置启动了容器们
docker compose ps
四.docker-compose.yml 与.env 语法
(一)..env
语法
<env> = <value>
# 使用"#" 作为注释符
# <env> 为一个 字符字面量,可以称环境变量名,在该语句中 与<value>绑定
# <value> 一般为一个数 或者为一个字符串
# 可以在docker-compose.yaml中使用${<env>}的方式引用<value>绑定的值
(二).docker-compose.yml语法
示例:
version: '3'
services:
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASS}
-
声明docker compose 的文件格式版本
version : "3"
-
定义服务
services: <服务名>: image: <镜像名> environment: <容器内环境变量名>:<变量值>
六.Q&A
1. wsl中使用在wsl中下载的linux版本docker,使用docker compose up -d的时候报错http: invalid Host header
原因:未知,怀疑是 docker 本身的问题,
发生问题的版本Docker Compose version v2.17.2
,Docker version 20.10.24, build 297e128
解决方法,参考Docker-compose ERROR [internal] booting buildkit, http: invalid Host header - Stack Overflow
更新docker版本sudo snap refresh docker --channel=latest/edge
, 成功解决!
一.镜像,容器,DockerFile的关系
镜像是对容器配置的描述.DockerFile是对镜像配置的描述
docker能够通过镜像启动容器.
docker通过dockerfile配置结合本地资源和基础镜像 编译出 新镜像.
二.容器与 shell,与虚拟机的区别
容器的功能就像是一个独立的机器,但是实际上它只是独立地拥有一套虚拟的文件系统,一套独立的环境变量:
-
与shell的区别:除了独立环境变量外,容器还虚拟了一套文件系统
-
与虚拟机的区别:虚拟机不仅仅模拟了文件系统,而是模拟了整个操作系统,包括内存管理,处理器调度等.
一.如何拉取镜像
docker pull <image-label>
if with no version tag,use latest in default
二.如何从镜像启动容器,如何停止容器
(1). 启动容器
docker run [-it] [--name <container-name>] [-d] <image-name>
参数说明:
-
-d :后台运行容器中命令,启动容器后回到主进程,容器独立运行
(2). 停止容器
docker stop <container-name>|<container-id>
停止后的容器内容信息不会被删除,可以再次启动:
docker start <container-name>|<container-id>
三.如何进入容器
-
启动时交互式进入容器
docker exec -it <image-name> <bash-path>
默认使用
/bin/bash
这种方式进入容器一旦通过
exit
之类的方式登出,则容器将终止 -
交互式进入运行中的容器
docker exec -it <container-name> <bash-path>
用这种方式可以多次进入一个容器,在容器中开始的主进程的工作结束前容器都不会因为登出而结束
四.如何查看容器信息,镜像信息
a. 查看所有运行中的容器: docker ps
b. 查看所有未清除的容器: docker ps -a
c. 查看某个容器具体信息 : docker inspect <container-name/id>
d. 查看镜像信息 : docker images
e. 查看指定容器的内存、硬盘、网络、cpu等资源的使用情况
# 使用docker stats
f. 查看指定容器占据的硬盘大小
# 把容器commit成镜像
# 然后查看镜像大小
五.删除镜像,删除容器
-
删除镜像:
docker rmi <image-name[:version-tag]>
注意:要成功删除镜像,需要删除使用到该镜像的所有容器先. 其次,删除镜像有时候需要指定具体的tag,如果删除了指定tag的某个镜像, 比如删除了redis:1.0,但是如果该镜像有其他的名字比如redis:1,实际上底层是同一个镜像的话,该镜像的内容并不会从docker的存储区种删除. 只有一个镜像失去了它所有的指向方式(所有标签)的时候才会被实际删除
-
删除容器:
docker rm <container-name>/<container-id>
注意:容器要先stop,之后才能删除
六.使用docker build编译Dockerfile为镜像
docker build -t <image-name> .
这里面的.
表示以当前路径为打包开始的路径,则docker build执行打包镜像操作的时候,使用的相对地址就是就是用.
指定的基地址 来确定绝对地址
编译镜像的过程中往往需要下下载软件,很多软件下载时往往需要科学上网才能在合理时间内下载成功
七. 把容器commit成镜像
docker commit <container-name> <image-name>
提示词
docker有哪些网络模式
反馈
Docker提供了多种网络模式,用于配置容器之间和容器与外部网络之间的通信。以下是Docker中常用的网络模式:
-
桥接模式(Bridge):桥接模式是Docker默认的网络模式。在桥接模式下,Docker会创建一个虚拟的网络桥接接口(bridge),并将容器连接到这个桥接接口上。每个容器都会分配一个IP地址,并可以通过容器名称或IP地址相互通信。
-
主机模式(Host):主机模式下,容器与宿主机共享网络命名空间。容器将直接使用宿主机的网络接口,不进行网络地址转换(NAT),也不会创建额外的网络接口。这种模式下容器的网络性能会更好,但容器之间的端口冲突可能会出现。
-
无网络模式(None):无网络模式下,容器没有网络接口,完全与外部网络隔离。这种模式适用于那些不需要网络连接的容器,比如批处理任务或与外部网络无关的服务。
-
容器模式(Container):容器模式允许多个容器共享一个网络命名空间。这意味着这些容器可以使用相同的网络配置,包括IP地址和端口。这种模式适用于需要共享网络栈的容器,比如微服务架构中的多个容器。
-
自定义网络模式(Custom):除了上述内置的网络模式,Docker还提供了自定义网络模式的功能。使用自定义网络模式,可以创建具有自定义网络配置的网络,包括子网、网关和DNS等。这种模式适用于复杂的网络拓扑和多容器应用程序的部署。
通过选择适当的网络模式,可以实现容器之间的通信和与外部网络的连接。不同的网络模式适用于不同的应用场景,根据实际需求选择合适的网络模式可以提供更好的网络性能、安全性和灵活性。
宿主机和容器通信的几种方式
- 容器可以通过宿主机的IP地址和端口号进行通信
- 容器中,可以使用特殊的DNS名host.docker.internal来访问宿主机。例如,如果你的应用程序在宿主机的 8080 端口上运行,你可以从容器内部使用 http://host.docker.internal:8080 来访问它。
- 容器可以通过连接到宿主机的网络来访问宿主机。例如,使用
--network=host
标志启动容器,容器将连接到宿主机的网络,可以通过宿主机的IP地址和端口号进行通信。
搭建与使用 本地DockerRegistry
参考
Case
#!/bin/bash
# 本脚本用于搭建一个本地的Docker Registry并展示如何使用它。
# 设置变量
REGISTRY_NAME="local-registry"
REGISTRY_PORT="5000"
CONTAINER_INNER_REGISTRY_PORT="5000"
# 指定镜像存储位置
STORAGE_PATH="/path/to/local/registry/storage"
IMAGE_NAME="my-image"
LOCAL_IMAGE_TAG="localhost:${REGISTRY_PORT}/${IMAGE_NAME}"
# 启动 Docker Registry 容器
echo "启动 Docker Registry..."
docker run -d -p "${REGISTRY_PORT}:${CONTAINER_INNER_REGISTRY_PORT}" --restart=always --name "${REGISTRY_NAME}" \
-v "${STORAGE_PATH}:/var/lib/registry" \
registry:2
# 等待几秒确保 Docker Registry 启动
sleep 3
# 检查 Registry 是否成功运行
echo "检查 Docker Registry 状态..."
curl -X GET http://localhost:${REGISTRY_PORT}/v2/_catalog
# 标记本地镜像以便推送到本地 Registry
echo "标记镜像为本地 Registry 格式..."
docker tag "${IMAGE_NAME}" "${LOCAL_IMAGE_TAG}"
# 推送镜像到本地 Registry
echo "推送镜像到本地 Registry..."
docker push "${LOCAL_IMAGE_TAG}"
# 从本地 Registry 拉取镜像
echo "从本地 Registry 拉取镜像..."
docker pull "${LOCAL_IMAGE_TAG}"
# 脚本结束
echo "本地 Docker Registry 配置完成。"
坑点 1: 为一个用户安装了 podman 后,其他用户无法使用
环境:
PRETTY_NAME="Ubuntu 24.04 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04 LTS (Noble Numbat)"
安装方式: apt
在某个用户 u 安装了 podman 之后,切换到用户 gitea 执行如下命令报错:
gitea@VM-8-17-ubuntu:~$ podman --version
ERRO[0000] XDG_RUNTIME_DIR directory "/run/user/1003" is not owned by the current user
检查发现 1003
是 用户 u 的 uid, 而 用户 gitea 的 uid 是 1004
.
解决方法: 暂无
WSL(Windows Subsystem for Linux)
一. 参考资料
二. 简介
WSL(Windows Subsystem for Linux)是Windows的一个功能,允许在Windows计算机上运行Linux环境,而无需使用虚拟机或双引导。WSL旨在为希望同时使用Windows和Linux的开发人员提供无缝高效的体验。
WSL有两个主要版本:WSL 1和WSL 2。WSL 2是默认的发行版类型,它使用虚拟化技术在轻量级实用工具虚拟机(VM)中运行Linux内核。通过WSL 2运行的Linux发行版将共享同一网络命名空间、设备树、CPU/内核/内存/交换空间等,但有自己的PID命名空间、装载命名空间、用户命名空间等[1]。
WSL的优势包括:
-
资源消耗较少:相较于虚拟机,WSL作为系统层的一部分,消耗更少的资源,并且与系统更紧密地集成[2]。
-
高度兼容性:WSL提供了对Linux内核的支持,使得绝大多数在完整Linux系统中可用的功能和工具都可以在WSL中使用,如Bash shell、命令行工具、编程语言解释器等[2]。
-
与Windows应用程序的互操作性:WSL允许用户在同一组文件上使用Windows应用程序和Linux工具,方便开发人员进行跨平台开发[2]。
目前,WSL支持多个Linux发行版,包括Ubuntu、Kali Linux、openSUSE等,用户可以在Microsoft Store中下载并安装自己喜欢的发行版[3]。
总之,WSL为开发人员提供了在Windows环境中运行Linux的便利性和灵活性,使得开发人员可以同时享受Windows和Linux的优势。
三. 使用
-
windows上win+s,搜索"功能",找到"启动或关闭windows功能“, 选择 开启
适用于linux的windows子系统
功能以及虚拟机平台
功能 -
下载或者更新wsl,使用
wsl --install
命令,下载更新wsl, 使用wsl --set-default-version <Version>
命令切换版本到wsl2, -
查看能够下载使用的发行版
wsl --list --online
-
下载并启动发行版:
wsl --install -d <发行版本>
,常用的发行版比如 -
查看当前可用linux子系统以及该系统使用的wsl版本:
wsl -l -v
-
删除某个wsl启动的linux子系统:
wsl --unregister <发行版名称>
-
设计wsl开机自启动:
wsl本身不会开机自启动,但是启动速度快,
尤其是正在不需要开多个子系统以及本身机器资源丰富的情况下,不需要设置开机自启动
-
使用指定用户进入指定wsl子系统:
wsl -d <子系统发行版名称> --user <用户名>
-
wsl中 使用
sudo hwclock -s
命令同步时间。有的命令需要时间同步才能执行成功,比如apt-get update
四. Q&A
A. 如何在wsl中 为什么监听777端口提示"bind: permission denied"?如何解决?
-
wsl2中开启服务监听777端口 提示"bind: permission denied"
在WSL2中,当您尝试监听TCP端口时,出现"bind: permission denied"错误通常是由于权限限制导致的。WSL2默认情况下会限制非特权用户对低于1024的端口进行绑定。
要解决这个问题,您可以尝试以下方法:
-
使用高于1024的端口:尝试将要监听的端口更改为大于1024的端口号,例如7777或8888。
-
使用sudo或管理员权限:在命令前加上sudo或者以管理员身份运行WSL2终端。
-
-
修改WSL2的配置文件(仅限高级用户):您可以修改WSL2的配置文件,以允许非特权用户绑定低于1024的端口。打开/etc/wsl.conf文件(如果不存在则创建),添加以下内容:
[network] generateResolvConf = false bindNonprivilegedPort = true 保存文件并重启WSL2。
请注意,修改WSL2的配置文件需要管理员权限。如果您不熟悉这方面的操作,请谨慎进行,并确保了解所做更改的后果
-
低于1024的端口有什么特别意义?
在计算机网络中,1024以下的端口号被称为"特权端口"(privileged ports)或"系统端口"(well-known ports)。这些端口号在操作系统中具有特殊的意义,并且通常需要具有管理员或root权限才能绑定或使用。 特权端口用于一些常见的网络服务和协议,例如Web服务器(端口号80)、Secure Shell(SSH,端口号22)、域名系统(DNS,端口号53)等。这些端口号在操作系统中已经被预留,以便于标准化网络通信。 默认情况下,WSL2和许多其他操作系统会限制非特权用户对特权端口的绑定和使用,这是出于安全考虑。这样可以防止普通用户意外或故意占用或干扰这些关键服务。 因此,当尝试在WSL2中绑定低于1024的端口时,可能会出现"bind: permission denied"错误,除非您以管理员身份运行WSL2或修改WSL2配置以允许非特权用户绑定这些端口。
B. wsl中突然无法访问网络是为什么? 如何解决?
在wsl使用过程中,笔者发现突然所有外部网络都无法访问了,如下尝试ping github.com无法ping通
❯ ping github.com
ping: connect: Network is unreachable
原因不明,重启wsl后解决
小狗钱钱
Quick Intro
这是一本可爱的书,书里面的一些知识很稚嫩,但是一些观点很有参考意义。 这本书可以用作幼儿财商的培养。
Note
1. 十个愿望
写下十个为什么想要自己有钱的愿望,并且挑出其中重点的三个。而且之后应该每天都看一遍。
这个作法是为了让自己思考得出金钱对自己的意义,并确定最重要的目标。
2. 梦想储蓄罐
任务: 做个"梦想储蓄罐",上面要有跟梦想相关的图片等信息。
目的:人们把这种行为称作‘视觉化’。成功的人之所以成功,就是因为他们一直梦想着自己成功的那一天,不停地想象着自己实现了理想时的情形。当然,人不能停留在梦想里。
学习就是认识新观念和新想法的过程。假如人们始终以同一种思维方式来考虑问题的话,那么始终只会得到同样的结果。因为我对你讲述的许多内容是你以前从未接触过的,所以我建议你,在你还没有做之前,不要轻易下结论。没有想象力的人是很难成就大事的。我们对一件事投入的精力越多,成功的可能性也越大。可是大多数人把精力放在自己并不喜欢的事情上,而不去想象自己希望得到的东西。
3. 保持自信
要求做到两点:
- 考察自己,考察身边的机会,同时保持自信,勇敢行动抓住机会。
- 编写成功日记,记录自己做成功的事情
这一章讲了个小孩成为百万富翁的作用,告诉读者自信对于赚钱的作用。
不要总是把心思留在那些自己不知道、能做和拥有的东西上,而是多把心思考察自己、考察周围。
其实从达瑞把精力集中在他知道、能做和拥有的东西上的那一天起,他的成功就已经拉开了序幕。这一决定使得一个孩子完全有能力挣到比成人更多的钱,因为成人
经常把一生的时间都用来考虑他们不知道、不能做或没有的东西上。
而编写成功日记,记录自己做成功的事情,就是一种增强自己自信的手段。
4. 从自己的兴趣出发
从自己的兴趣触发寻找赚钱机会
如果一件事情是自己乐意做、能做好,而且别人不乐意做、做没那么好且想要做好的,那就是个自己提供服务来赚钱的机会了。
5. 72 小时约定
很简单。当你决定做一件事情的时候,你必须在 72 小时之内完 成,否则你很可能永远不会再做了。
6. 避免借债
书中提出两个观点:
-
合理规划还贷分期。
- 将扣除将扣除生活费后剩下的钱的一半存起来,剩下的一半用于支付消费贷款,而不是生活费以外的钱都用于还贷。
- 最好根本不申请消费贷款。
- 如果每期还贷设置过高的话,可能导致需要额外借贷
笔者点评: 其实不一定要一半一半,设置合适即可,而且具体怎样更合适还要看当时社会背景。 如果存钱到银行的利息远远小于未偿还贷款产生的利息呢?
-
提前储蓄,避免借债(笔者认可)
7. 不要杀死你的鹅
自己的本金是用来产生复利的鹅。
不要轻易消费自己的本金。要注意储蓄和投资。
CNCSMonster's language leaning notes
Rust Quiz
notice
可以注意到中间漏了些题目,比如 7, 这是因为这个题目已经过时了,已经被移除出 Rust Quiz
quick intro
Rust Quiz is a collection of questions designed to test your knowledge of Rust. It covers various topics, including ownership, lifetimes, and concurrency.
Ref
Note
Q1
值得一提的是,这里介绍了一种 Rust 编译器处理语法的细节。
虽然 Rust 中一切皆表达式,但 Rust 遇到 {}
的时候,会把块处理成 stmt
,不会当成后面的二元运算符的左表达式。
比如 {} && true
,Rust 会理解成 {}
和 &&true
两个 stmt,而不是 {} && true
一个 expr 类型的 stmt. 也就是这里 &&
并没有被当成一个二元运算符,而是当成了连续使用的两个 &
一元运算符。
如果想要把一个 block 当作二元运算符的一个算子表达式,可以用括号括起来,比如 ({} && true)
,这样 Rust 就会把 {}
当成一个 expr,把 ({} && true)
整体当成一个 expr 类型的 stmt.
Q2
该题目涉及的知识点和 Q1 类似,Rust 编译器在处理语法的时候会把 {}
当成一个 stmt,而不是一个 expr。所以 {} & S(4)
会被 Rust 编译器理解成两个 stmt: {}
和 &S(4)
,其中 &
没有被理解成 BitAnd 运算符,而是理解成了一元的取引用运算符。
Q3
Rust 中用 const 定义的全局常量,在局部作用域中取&mut 的时候,实际是创建了一个具有该常量值的临时变量,并且&mut 指向该临时变量。 在局部作用域中对 const 定义的全局常量的字段进行修改的时候,会创建一个具有该常量值的临时变量,并不会实际修改该全局常量的值,也就是后面再访问该全局常量时,仍然是原来的值。
Q4
Rust 中使用 b"xx"
语法创建 byte string,也就是类型为 &'static [u8;<n>]
的类型表示,这里面 <n>
代指对应的字符串中字符数量,每个字符看作 ascii 字符。
Rust 中 ..
可以用作表示匹配剩余内容,也可以用于表示一个 RangeFull 类型的 range
所以 b"062"[..][1]
指向的是 [b'0', b'6', b'2']
中的第一个元素 b'6'
,对应 ascll 值为 54.
Q6
Rust 中 assiggnment 语句本身的值是 ()
,也就是 unit 类型。
所以 let x = y = 1;
等价于 let x = (y = 1);
,也就是先执行 y = 1
,然后该语句的值是 ()
,再将 ()
赋值给 x
。
也就是 let x = y = 1;
等价于 y = 1; let x = ();
。
Q8
Rust 中声明宏匹配标点符号的时候要考虑空格,有时缺乏空格的情况会按照优先左结合组成标点符号的规律匹配,所以有时有没有空格的匹配结构一致。比如声明宏中 ==>
和 == >
是等价的,都被匹配 ==
和 >
两个标点符号。
Rust 中过程宏的 api 更加灵活,能够准确地处理空格的使用。比如能够区别 ==>
和 == >
。
Q9
Rust 的声明宏的匹配机制中,不同类型的宏的片段指示符(fragment specifiers)可以分为两种,一种会让被捕获的成员不透明,另一种不会。 透明的片段指示符包括:
ident
lifetime
tt
Q10
考察 Rust 中的 method lookup. Rust 中类型的自有方法和类型实现的 trait 方法同名的情况下:
- 方法的接收器不同时,
&self
比&mut self
优先 - 方法的接收器相同时, 类型的自有方法比 trait 方法优先
本题目为特殊情况,对于 dyn Trait 类型,存在 dyn Trait 的自有方法与该 Trait 提供的方法同名的情况下,当前没有办法调用 到该 dyn Trait 类型的自有方法。
Q11
考察 Rust 中泛型参数的 early bound 和 late bound
对于生命周期泛型参数, 存在 early bound 和 late bound 两种情况。
如果是 late bound 的生命周期泛型参数,则不能够显式地指定生命周期泛型参数。
其中 fn f<'a>()
中 'a
就是 late bound 的生命周期泛型参数;
其中 fn g<'a:'a>()
中 'a
就是 early bound 的生命周期泛型参数
Q12
考察 Drop 的顺序
Q13
考察 Rust 中 0 大小类型(ZST)的数组中每个元素的地址都是一样的。 ZST 在 Rust 中是一种特殊的抽象,属于编译时才存在的概念。 编译后程序不会为 ZST 的值开辟空间。
Q14
Rust 中的 impl 语句实现的位置不重要,整个程序的 scope 中都可以访问。
Q15
考察 Rust 中整数类型推导的规则。
对于 Rust 中的整数类型的变量,如果它调用了 trait 方法。
首先查询该整数类型本身是否实现了该 trait,比如查询 i32
,该题目中查询到没有,然后查询其他整数类型,查询到 u32
满足该 trait,所以推导该变量为 u32
类型。
如果所有整数类型 T 都没有实现该 trait,则会查询整数类型的 &T
类型是否是实现了该 trait,优先从 &i32
开始。
ps, 这个材料中推荐的 stack overflow 的回答看起来很晦涩,笔者直接放弃。笔者觉得自己这个理解更直接。
经过实验,发现:
- 如果直接整数类型 T 对 trait 的实现存在多个,但是不包含 i32 的实现的话,则编译器会推导整数类型为 i32,然后报错 trait 未实现
- 如果有多个直接整数类型 T 对 trait 的实现,且包含 i32 的实现的话,则正常编译,并且编译器推导该变量为 i32 类型。
- 如果只是存在一个整数类型对该 trait 的实现,则编译器推导该变量为该整数类型。
- 用 T 代指整数类型,如果没有直接整数类型实现该 trait, 但是有且仅有一个
&T
整数类型的引用类型实现该 trait, 则编译器推导该变量为 T 类型 - 如果存在多个&T 对该 trait 的实现,且不包含
&i32
的实现,则编译器推导该变量为 i32 类型,然后报错 trait 未实现
Q16
Rust 中没有 --
和 ++
这样的一元自增/自减运算符。
所以 --x
其实相当于 -(-x)
,也就是先对 x
取负,然后再对结果取负。
Q17
考察 Rust 中的运算符,Rust 中没有自增/自减运算符。
所以若干个 -
或者若干个 +
会相当于用右结合的方式运算。
Q18
Rust 语法中的成员搜索规则是先搜索成员方法,找不到成员方法再搜索成员变量。
Q19
Rust 中 let _ = s;
这个语句不会 move 变量 s
, 也就是 s
如果有 Drop::drop 实现的话,该函数不会在这一行后立刻触发。
Q22
Rust 中 1.
整体表示一个浮点数。
Rust 中宏指示符会把合法数字表示当成一个 token,对应 tt
片段指示符。
Q23
考察 Rust 中 method lookup 的规则。
自由方法和实现的 trait 方法同名的情况下,
如果接收器相同,优先使用接收器为 &self
的方法
如果接收器不同, 优先使用自有方法。
Q24
可以把宏的卫生性理解成一种对局部变量的着色。 对于宏中直接使用的外部局部变量的名称,指向的是宏定义处有定义的局部变量。 至于常量,Rust 中把局部常量当作 items 而不是局部变量,宏使用的外部局部常量的名称指向调用宏时外部的局部常量。
Q25
考察 Rust 的 desconstructing 语法,以及 drop 的时机。 drop 的时机:
- 当一个值被构造出来后没有持有者的时候,会直接 drop;
- 当值的持有者超出作用域的时候,drop.
let S = f()
中 S
不是一个变量名, 而是一个 destructuring 语法中用到的模式,指向类型 struct S
, 这里表示一个不会失败的模式解构,并且绑定新的变量。
Q26
考察迭代器的延迟计算
Q27
&dyn Trait 类型作为参数类型或者参数类型为 T,约束 T:Trait 时参数的行为都是一样的,只能够访问到 Trait 中定义的方法 Rust 中 super trait 不等于 c++ 中的继承,而只是说明一个 trait 要实现必须先实现了某些 trait.
Q28
同 Q25 一样,考察 Drop 的时机
Q29
type M = (i32);
等价于 type M = i32;
Rust 中 一个元素的 tuple 需要结尾加上 ,
来表示, 比如 (T,)
,以与 (T)
区分开来。
Rust 中对于无后缀的整数数字,往往优先认为是 i32
类型的整数。
Q30
考察 Rust 中的方法查找规则。 以及考察 Rust 标准库中的 Rc 实现了 Clone trait 这个知识点。
Q31
考察 Rust 中变量的方法查找顺序。
trait Or { fn f(self); } struct T; impl Or for &T { fn f(self) { print!("1"); } } impl Or for &&&&T { fn f(self) { print!("2"); } } fn main() { let t = T; let wt = &T; // &T let wwt = &&T; // &&T, &&&T, &mut &&T , &T let wwwt = &&&T; // &&&T, &&&&T let wwwwt = &&&&T; // &&&&T let wwwwwt = &&&&&T; // &&&&&T,&&&&&&T, &mut &&&&&T, &&&&T t.f(); // 1 wt.f(); // 1 wwt.f(); // 1 wwwt.f(); // 2 wwwwt.f(); // 2 wwwwwt.f(); // 2 }
Q32
考察 Rust 的 match 语法
xx|yy if bb => {} {..}
这种模式,
相当于
xx if bb => {..},
yy if bb => {..}
Q33 (Notice!!)
Rust 中 ||
优先和后面的表达式组成闭包
所以 || .. .method()
用括号和空格标上顺序应该为 (|| ..) . method()
如果想要表达调用为 ..
实现的 method 方法,应该给 ..
加上括号如下:
|| ((..).method())
Q34
Rust 中函数指针的大小是固定的,是 usize
大小,需要用来存储函数的地址。
但是 Rust 中函数名本身作为值使用时,它本身的类型不是对应函数指针类型,而是与其函数定义本身绑定的一个类型,这个类型特定指向该函数的实现,所以不需要存储函数的地址,所以大小为 0.
举例说明
fn main(){ fn f() {} let a : fn() = f; // a的类型是函数指针类型, 大小为usize的大小 let b = f; // b的类型是函数f的类型(fn f()), 大小为0 println!( "{:?} {:?}", std::mem::size_of_val(&a), std::mem::size_of_val(&b) ); // 输出: 8 0 }
Q35
考察 Rust 中声明宏的卫生性规则。 为了避免声明宏中新定义的变量与外部同作用域下变量命名冲突,即使是同名变量,宏中定义的变量会被当成不同名字的变量处理。
不过如果变量命名是通过宏参数传入的,那么宏中定义的变量会与外部同作用域下变量命名相同。
#![allow(unused)] fn main() { macro_rules! x { ($n:expr) => { let a = X($n); }; ($id:ident,$n:expr) => { let $id = X($n); }; } struct X(u64); impl Drop for X { fn drop(&mut self) { print!("{}", self.0); } } /// 输出: 121 #[test] fn t1() { let a = X(1); // x!(2); x!(a, 2); print!("{}", a.0); } /// 输出: 221 #[test] fn t2() { let a = X(1); x!(a, 2); print!("{}", a.0); } }
Q36
考察 Rust 中闭包的本质。 闭包相当于建立了一个具有闭包对应函数签名的自有方法的结构体。 该闭包捕获的外部变量,相当于结构体的字段。 如果闭包捕获的外部变量的类型都是实现了 Copy trait,则该闭包相当的结构体也相当于 实现了 Copy trait
Q37
考察 Rust 中的临时生命周期延长。
当一个临时变量的引用被 let
/const
/static
语句中使用的时候,这个临时变量的生命周期会延长到该语句所在的作用域结束。