跳转至

Recent Blog

语法规则-名词修饰名词

无论书写还是口语,无论英文还是中文,当用名词修饰名词时,就感觉容易找不到主体(subject) ,但这种用法有很常见,特别是中文。比如电脑/人脑,说明书/漫画书,都是用名词修饰名词,也可以说是造了新(名)词,在英文中有很多名词+名词合成的派生词, 例如bookstore。大家接受这种词因为没有歧义,大家都知道前面的名词是用来修饰后面的词。例如说电脑,大家都知道指“带电的脑”,不会以为指“脑里面的电”。bookstore 则都认为指 store of books(有书的店),不会认为是 book of stores(有店的书)。

但如果句子比较复杂时,就容易有歧义了。

举例中文

当长一点的句子,有词修饰词的情况时,就会可能有歧义,特别是比较陌生时,不一定能靠语句分析出主体,而熟悉的东西可以靠经验储备来辅助

”新的证据表格“: 1. 新修饰的表格,表格有新旧之分,新表格和表格都用于记录证据 2. 新修饰的证据,证据有新旧之分,表格专门用来记录新证据

举例英文

new evidence table 这里有歧义: 新表格还是新证据?

正确的说法 * new table of evidence, 新表格 * new evidence in the table, 新证据

英文中的名词修饰名词

英文中的名词

Noun-adjunct-维基 知乎

多个of 情况

是不推荐的

总结

对于英文而言,名词修饰名词的情况比较少见,除非是常用的或者派生词。用“名词 of 名词”是比较合理的。而当多个of时, 虽然语法上没错,但是容易产生歧义,这时需要调整语句结构来消除歧义。中文同理。

容器的命名空间

容器是利用'cgroup'和'namespace' 实现软件级的虚拟化,产品有lxc和docker。不同与全虚拟化和半虚拟化,这些是硬件辅助的半虚拟化Zen(Zen也有全虚拟化)和全虚拟化kvm
因为容器使用主机内核,不存在特权指令翻译,所以从性能上比较,容器是最高效的,其次是kvm, Zen

cgroup和namespace

这两个是容器技术的根基,包括Docker, lxc。实际上docker开始用的lxc,后来用go重新写了libcontainer来实现控制这两个东西
简单来说,cgroup实现对进程组的资源的限制和监控,而namespace如同c++里面类似,实现了进程组之间的隔离

但除此之外, 虚拟化系统时则还需要rootfs支持

LXC和Docker

lxc代码合入了内核,其原理和Docker非常类似, 不过Docker有比较丰富的全套工具和方便的api

nsenter命令的使用

容器技术都使用到了namespace, 而nsenter可以操作namespace, 进入容器内部,以下都一docker容器为例

使用命令, 获取到容器的首进程pid

docker inspect --format '{{.State.Pid}}' containername
3015

然后可以使用nsenter来进入容器,如同docker exec 一样

$ pwd
/home/user
$ sudo nsenter -t 3015 -m  pwd
/

-m表示挂载命名空间,这样就能访问docker里的进程的文件系统,所以能正确打印出容器进程的当前目录
-t表示目标进程,nsenter可以获取以下命名空间。 跟通过执行ls /proc/self/ns所看到的差不多,这里self是命令本身pid

/proc/pid/ns/mnt    the mount namespace
/proc/pid/ns/uts    the UTS namespace
/proc/pid/ns/ipc    the IPC namespace
/proc/pid/ns/net    the network namespace
/proc/pid/ns/pid    the PID namespace
/proc/pid/ns/user   the user namespace
/proc/pid/ns/cgroup the cgroup namespace
/proc/pid/ns/time   the time namespace
/proc/pid/root      the root directory
/proc/pid/cwd       the working directory respectively
上面的每项都有对应的参数,而'-a'表示进入上面的所有命名空间

c++的basic_string优化

Small-string optimization is basically

union { 
  struct { char*, size_t, size_t; }
  struct { char[23]; bool; } 
} 
- but with bit-tricks instead. When allocated, the local buffer's space is reused for size and capacity. It's not wasted. But gdb just shows both sides of the union, which can be confusing.

c++的信号和异常

信号和异常都是c++程序会遇到的,以前常把信号也当异常来看待,当其实两者是截然不同的东西

信号

信号不仅是对c++程序,而是对所有linux程序,是内核在进程由内核态转用户态时,检查信号槽并调用信号处理函数(用户定义或者默认)。
有些信号可以由程序处理(常见退出信号SIGINT),而有些无法处理(常见退出信号SIGABT, 段错误信号SIGSEGV),而大多信号默认是退出进程。

异常

c++的异常是c所没有的,异常一般在出现逻辑错误运行时错误时要抛出异常,如果没有异常处理函数,则coredump。

触发方式

触发信号用raise,而异常用trow

int raise(int sig);

Linux定时器

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 some-unit.service

docker storage

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)

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