跳转至

2021

crontab manual

https://www.computerhope.com/unix/ucrontab.htm#command-entries

使用systemd-run 替换

# systemd-run --on-active=30 /bin/touch /tmp/foo
# systemd-run --on-active="12h 30m" --unit someunit.service

docker 存储

docker 有3种方式使用系统存储:

  • bind: 宿主机目录映射方式,启动容器命令添加参数-v dir1:dir2。将宿主机的目录dir1挂载到容器的dir2目录,实际执行的mount -o bind类似命令
  • volume: 由docker管理的存储,先创建后使用,且可以在多个容器中共享,是官方推荐的方式
  • tmpfs: 使用宿主机的内存作为存储,这种使用内存文件系统来挂载到容器目录, 使用参数--mount type=tmpfs,destination=/dir, tmpfs是退出即释放的一种文件系统

docker使用的文件系统

docker使用的文件系统经过很多变化,而且在各发行版下可能不同,但目前主流的是overlay2,执行docker info 查看当前使用的是overlay2

sudo docker info | grep  Storage                                                                                                                                              
 Storage Driver: overlay2

除了overlay2,还有aufs(ubuntu),devicemapper(centos),btrfs和zfs。他们的实现都不同,都能支持分层和支持写时复制(Cow/copy-on-write),而他们实现的方式有区别,所以效率也有区别

  • 分层: 镜像都是分层的,在Dockerfile构建时,每次执行COPY/RUN时,都会增加一层
  • 写时复制: 在容器或者DockerFile执行修改操作时,包括权限修改,会将lower layer 的文件复制到container层再修改

而容器就是在镜像顶层压栈了一个可写层,而且是临时的,当容器销毁时,这层的文件也会删除

overlay的优势

  1. page caching, 可以在多个不同实例之间共享
  2. 不同层之间,相同文件使用硬连接, 节省inode 和 大小

写时复制 copy-up 会导致第一次写时造成延迟,特别是大文件,拷贝起来费时。 但第二次就不会延时, 而且overlay2 有caching, 相比其它文件系统,更减少延时

overlay的问题

  1. 实现不够完全, 例如没有实现uname
  2. 先只读打开一个文件 open(read), 再读写打开相同文件open(write), 两个fd 会对应2个不同文件, 第一个对应的lower的文件,第二个造成写时复制,对应容器里的文件。
  3. 规避方法是先执行touch 操作。 现实的例子是 yum 需要安装yum-plugin-ovl。 但这个只有7.2才支持, 之前的话就需要先touch /var/lib/rpm/*

最佳实践

  1. 使用ssd
  2. 对于写操作比较多的场景,例如数据库,应使用映射文件(bind)或者volume。这样跳过了overlay的复杂操作,直接使用主机的文件系统

overlay的增删改

当运行docker容器时查看挂载

overlay on /var/lib/docker/overlay2/04ea1faa8074e5862f40eecdba968bd9b7f222cb30e5bf6a0b9a9c48be0940f2/merged type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/B74PWZCBMRCWXFH5UL2ZXB5WEU:/var/lib/docker/overlay2/l/WNHICVPVSDNUGSCZW435TPSMOK,upperdir=/var/lib/docker/overlay2/04ea1faa8074e5862f40eecdba968bd9b7f222cb30e5bf6a0b9a9c48be0940f2/diff,workdir=/var/lib/docker/overlay2/04ea1faa8074e5862f40eecdba968bd9b7f222cb30e5bf6a0b9a9c48be0940f2/work)
docker 将镜像的文件挂载为只读, 将容器层挂载为可读可写。 文件系统可以分为2部分 upper(容器层) + lower (镜像层)

  • 当在容器里执行写时, 如果文件不存在, 会依次遍历lower。如果都不存在就会在upper层创建文件
  • 读也相同
  • 删除时会创建一个without 来隐藏, 这是为什么即使删除容器里的文件, 镜像还是会增大。
  • 删除目录情况也差不多

特殊情况

在保存容器后(docker commit),会多一层,里面包含了修改的文件,以及删除后生成的without文件,然后生成镜像

但对于以下特殊目录文件不会提交, 因为这些文件是运行时docker 要根据用户配置进行修改的。

  1. /etc/hostname
  2. /etc/hosts
  3. /etc/resov.conf

例如docker 的link选项,会在容器的hosts 文件里定义对应的容器名->容器ip

手动mount overlayfs的例子

  1. 原本目录,文件都分散在不同目录ABC
    .
    ├── A
    │   ├── aa
    │   └── a.txt
    ├── B
    │   ├── a.txt
    │   └── b.txt
    ├── C
    │   └── c.txt
    └── worker
        └── work [error opening dir]
    
  2. overlay 挂载到/tmp/test目录 sudo mount -t overlay overlay -o lowerdir=A:B,upperdir=C,workdir=worker /tmp/test/
  3. 查看test目录
    /tmp/test/
    ├── aa
    ├── a.txt
    ├── b.txt
    └── c.txt
    
    mount  | grep 'overlay'
    overlay on /tmp/test type overlay (rw,relatime,lowerdir=A:B,upperdir=C,workdir=worker)
    

参考

https://docs.docker.com/storage/storagedriver/

起因

遇到过一个面试题, 以下程序打印几个'A'

#include <stdio.h>
#include <unistd.h>

int main() {
  printf("A");
  fork();
  printf("A");
}
答案是4个,而以下是3个
#include <stdio.h>
#include <unistd.h>

int main() {
  printf("A\n");
  fork();
  printf("A");
}
再把上面的编译后的可执行文件重定向到文件, 则有4个'A'
#./a.out > r.log
#cat r.log
原因就是缓冲

Linux 缓冲介绍

分为无缓冲、行缓冲和全缓冲

演示

发现有些程序可以输出到屏幕,重定向到文件后,执行ctl+c退出后却没有任何内容,例如以下

#include <stdio.h>
#include <unistd.h>

int main(void) {
  while (1) {
    printf("Hello World\n");
    sleep(1);
  }
}
这个程序是死循环所以需要手动结束,且一直打印helloword。但如果将输出重定向到文件,

#./a.out > r.log
当手动退出时,文件依旧是空的

shell 文件操作

读行

while read line ;do
#or while read -r line ;do
echo $line
done < $1
cat $1 | while read line ;do
echo $line
done

使用for时,结果略有不同, for以空格为一行

for line in $(cat $1) ;do
echo $line
done

reference

https://bash.cyberciti.biz/guide/Reads_from_the_file_descriptor_(fd)

huawei change rom

reference

https://forum.xda-developers.com/t/guide-rom-8-1-lineageos-15-1-huawei-mediapad-m3-8-4-btv-w09-btv-dl09.4085995/

setup sftp service

curlfs 和 sshfs 客户端

在debian上,之前一直用curlfs工具,将远程目录mount到本地目录。系统升级之后发现这个包没有了, 原来因为ftp不安全,所以现在推荐sshfs。

reference

https://www.linuxtechi.com/configure-sftp-chroot-debian10/

python arguement types

# accept tupe and list
def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n
    print(sum)

calc([1,2])
calc((1,2,3))

# accept varidict argument
def calc2(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n
    return sum

calc2(0)
calc2(1,2,3,4)

numbers = [1,2,3]
# transform list to varidcit argument
print(calc(*numbers))


# varidict named arguments will be transformed to dict

# only accept named varidict argument
def person2(**kw):
    if 'hello' in kw:
        passs

    print(kw)

# accept compose argument in sequence: normal argument & named varidict argument
def person(name, age, **kw):
    print('name', name, 'age', age, 'other', kw)

person('mike',12)
person('mike',12, city='sh')
person('mike',12, city='sh', birth=1990)
person2(name='mike',age = 12, city='sh', birth=1990)

# compose normal type argument, varidict type argument, and varidict named type argument 
def f2(a, b, c=0, *d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

cs=[3,4]
f2(1,2,*cs,hello='hello')


# use * to sepcify named type argument
def f3(a,b,c,*,d,e):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'e =', e)

# don't accept other named argument
f3(1,2,3,d=4,e=5)

# use tupe and dict can call all kind of function
# so *arg and **kw are always used to forward all argument to another function of any type of argument
args = (1,2,3)
kw = {'c':1,'d':2}
f2(*arg, **kw)

# at last, use base type for default value

bash使用技巧收集

Linux的命令行是非常强大的生产工具,现实中的问题好多可以不写复杂的编程代码而通过命令来解决, 而且不过几行代码而已

shell 内建命令

当执行which的时候,比如which echo 会输出'shell built-in command'而不是返回路径,就说明这个命令是内建命令
shell在执行内建命令时是直接执行,而非内建命令则fork一个子进程来执行

命令行的问题很多与缓冲buff、重定向pipe、delimiter分割符有关

技巧, 用命令代替编程

使用xargs创建多进程并发

xargs可以将输入多进程并发处理,配合find 非常厉害。 配合wget就是一个并发爬虫了

EXAMPLES
       find /tmp -name core -type f -print | xargs /bin/rm -f

       Find files named core in or below the directory /tmp and delete them.  Note that this will work incorrectly if there are any filenames containing newlines or spaces.

       find /tmp -name core -type f -print0 | xargs -0 /bin/rm -f

       Find files named core in or below the directory /tmp and delete them, processing filenames in such a way that file or directory names containing spaces or newlines  are  correctly
       handled.

       find /tmp -depth -name core -type f -delete

       Find  files  named  core in or below the directory /tmp and delete them, but more efficiently than in the previous example (because we avoid the need to use fork(2) and exec(2) to
       launch rm and we don't need the extra xargs process).

       cut -d: -f1 < /etc/passwd | sort | xargs echo

       Generates a compact listing of all the users on the system.

使用awk ORS 分割结果

ls 本来是行分隔的 awk将ls的换行符变为|分割

ls | awk '{ ORS="|"; print; }'
echo $(ls) 则把ls的换行符变为空格

使用declare 声明变量的类型和属性

declare可以指定变量的类型'-i'为整形,'-r'制度(等同shell的readonly),'-g'全局变量 shell默认是字符串,使用'-i'后,数学运算不需要let

#retry=0
declare -i retry=0
while [ $retry -lt 30 ] ; do
ps aux --cols=1024 | grep xxx
if [ $? -eq 0 ] ; then
        exit 0
fi
sleep 1
#let retry=$retry+1
retry=$retry+1
done

go 学习笔记

go为什么要指针和什么时候必须用指针

  1. 传递waitgroup时
  2. 获取命令行参数

全局变量和多变量赋值问题

因为很多函数需要处理error, 当存在全局变量时,可能会覆盖全局变量

var global_var int = 1
func foo(){
  // 此时覆盖了全局变量
  global_var, err := os.OpenFile("file")
}
应改为
var global_var int = 1
func foo(){
  var err error
  global_var, error = os.OpenFile("file")
}

数组和slice的区别

数组和slice使用上如同c++里的std::array和std::vector, 数组的长度固定,slice长度可以2倍扩容
但有传参上有大区别: golang中数组是值传递,相当于将整个数组复制一遍,而slice是引用传递,所以slice用得多,map同理

定义数组和slice:

// 数组
var a [3] int
arr := [5] int {1,2,3,4,5}
var array2 = [...]int {6,7,8}
q := [...] int {1,2,3} // 省略长度
q2 := [...] int {99, -1} // 前99项为0,第100个元素是-1

// slice
s1 := []int{1, 2, 3}
a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}  //a是数组
s2 := a[2:8]         
s3 := make([]int, 10, 20) 

var 和 := 区别, 以及哪些类型需要make

两者很多时候可以相互替换,但是在不同类型上,有区别

对于基本类型string, int, boolean, 数组,var声明即初始化

var str string // 自动初始化为空字符串
var inter int // 自动初始化为0
var bo bool // 自动初始化为false

// 可以直接使用
fmt.Printf("%s %d %t", str, inter, bo) 

而对于slice, map, chann类型而言, 使用var m map[int]string只是声明,需要再用make来获得内存和初始化

var m map[int] string // 此时不能使用m
m = make(map[int] string){1:"a",2:"b"}
fmt.Println(m[1])
而上面的步骤可以简化成
m := make(map[int] string){1:"a", 2:"b"}
或直接
m := map[int] string{1:"a", 2:"b"}

前言

vscode使用clangd作为c++的lsp非常好用,可以支持跳转、补全、clang-tidy、提示,但同时也对配置有要求,我的12Gi内存经常不够用而非常卡
于是摸索了一下,发现有两个问题: 1. clangd会占用很多内存 2. clangd在处理代码文件时会产生pch文件默认放到/tmp目录占用内存

这里会说怎么解决这2个问题,也顺便用到systemd-tmpfiles工具

问题

搜索过程

由于代码比较复杂,随便跳转几个文件就开始感到卡顿,一会就不能动了。切换X界面到终端, 执行free命令,可以看到shared和buff/cache两个部分占用很大内存

~ > free -h
               total        used        free      shared  buff/cache   available
Mem:            11Gi       7.1Gi       721Mi       1.0Gi       3.7Gi       3.1Gi
Swap:             0B          0B          0B

通过man可以知道这几项的意思, 可见share在我的系统指'/tmp', 因为tmpfs挂载在/tmp目录

shared 
Memory used (mostly) by tmpfs (Shmem in /proc/meminfo)

buffers
Memory used by kernel buffers (Buffers in /proc/meminfo)

cache  
Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)

buff/cache
Sum of buffers and cache
而buff/cache指的缓存(cache)和内核(buff)占用, 通过cat /proc/meminfo 查看到buff占用不大,不是内核问题(因为我用的最新内核,所以抱有怀疑态度)

~ > cat /proc/meminfo  | grep "Buffers\|Cached"
Buffers:          308440 kB
Cached:          3297168 kB

所以主要是/tmp目录占用内存过大,以及clangd占用内存过大

解决办法

  1. 对于clangd占用内存过大, 只有添加-background-index=0时,才能避免内存占用过大
  2. clangd在分析代码文件的时候,产生大量pch文件, 这些都会占用内存
~ > ls /tmp/*.pch -lh
-rw-r--r-- 1  100M May 13 11:18 /tmp/preamble-133598.pch
-rw-r--r-- 1  104M May 13 11:17 /tmp/preamble-457bd6.pch
-rw-r--r-- 1  104M May 13 11:17 /tmp/preamble-465a46.pch
-rw-r--r-- 1  104M May 13 11:18 /tmp/preamble-99f5e5.pch
-rw-r--r-- 1  99M May 13 11:17 /tmp/preamble-b56fad.pch

使用systemd-tmpfiles-clean 来清除/tmp目录下的pch文件

systemd-tmpfiles 介绍

systemd-tmpfiles 通过systemd-tmpfiles-clean.timer定时调用systemd-tmpfiles-clean.service, 来清理零时文件,且是默认启动的。
虽然/tmp目录使用的内存挂载tmpfs(也有写发行版使用物理磁盘来挂载),在重启时会清空,但对于服务器这种常年不会重启的系统,就需要这样的机制清理内存/磁盘。

timer默认的配置文件,OnBootSec=15min表示在系统启动15分钟后开始执行,而OnUnitActiveSec=1d表示在执行一次后,1天后再执行

~ > cat /usr/lib/systemd/system/systemd-tmpfiles-clean.timer
#  SPDX-License-Identifier: LGPL-2.1-or-later
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Daily Cleanup of Temporary Directories
Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8)

[Timer]
OnBootSec=15min
OnUnitActiveSec=1d

上面的timer会定时调用systemd-tmpfiles-clean.service这个服务

~ > sudo systemctl status systemd-tmpfiles-clean.service
○ systemd-tmpfiles-clean.service - Cleanup of Temporary Directories
     Loaded: loaded (/usr/lib/systemd/system/systemd-tmpfiles-clean.service; static)
     Active: inactive (dead) since Wed 2021-05-12 16:37:27 HKT; 22h ago
TriggeredBy: ● systemd-tmpfiles-clean.timer
       Docs: man:tmpfiles.d(5)
             man:systemd-tmpfiles(8)
    Process: 36637 ExecStart=systemd-tmpfiles --clean (code=exited, status=0/SUCCESS)

systemd-tmpfiles 配置

上面是timer的设置,这个timer适合服务器,但并不建议修改它们。而是添加规则,再手动调用systemd-tmpfiles --clean 配置规则的方法可以查看man tmpfiles.d 5,但只建议在/etc 和 ~/ 目录添加规则, 前者对所有用户生效,后者只对自己生效

TMPFILES.D(5)                                                                           tmpfiles.d                                                                           TMPFILES.D(5)

NAME
       tmpfiles.d - Configuration for creation, deletion and cleaning of volatile and temporary files

SYNOPSIS
       /etc/tmpfiles.d/*.conf
       /run/tmpfiles.d/*.conf
       /usr/lib/tmpfiles.d/*.conf

       ~/.config/user-tmpfiles.d/*.conf
       $XDG_RUNTIME_DIR/user-tmpfiles.d/*.conf
       ~/.local/share/user-tmpfiles.d/*.conf

直接从/usr/lib/tmpfs.d/ 找合适的配置,拷贝到自己的目录再改

删除/tmp/*.pch文件的效果

~ > free
               total        used        free      shared  buff/cache   available
Mem:        12158548     7808240      854296     1010820     3496012     3019688
Swap:              0           0           0
~ > rm /tmp/*.pch
~ > free
               total        used        free      shared  buff/cache   available
Mem:        12158548     7804356     1169052      699936     3185140     3334460
Swap:              0           0           0

shared项少了许多, 再设置background-index=0后,内存占用大大减小

总结

使用systemd-tmpfs 来清除文件的方式不太适合这种场景, 但其本身是非常功能强大的管理工具