学习/探索mongo


ppt地址 ,资料都是整理

macos上安装和演示

#安装
brew tap mongodb/brew
brew install mongodb-community@4.4
#拉起
brew services start mongodb-community@4.4
#停止
brew services stop mongodb-community@4.4
# mongo shell
mongo 127.0.0.1:27017

mongo 和sql对应概念 区分

SQL术语/概念 MongoDB 术语/概念
database database
table collection
row documentBSON document
column field
index index
table joins (表联接) $lookup, embedded documents (嵌入式文档)
primary key 指定任何唯一的列或者列组合作为主键 primary key 在 MongoDB 中, 主键自动设置为 _id 字段
aggregation (如:group by) aggregation pipeline (聚合管道)参考:SQL to Aggregation Mapping Chart
SELECT INTO NEW_TABLE $out 参考: SQL to Aggregation Mapping Chart
MERGE INTO TABLE $merge (从MongoDB 4.2开始可用) 参考:SQL to Aggregation Mapping Chart
transactions transactions

二进制对应关系

  MongoDB MySQL
数据库服务端 mongod mysqld
数据库客户端 mongo mysql
复制日志 oplog binlog
恢复用日志 journal redolog
最新 oplog 时间戳 snapshot 状态
t0 snapshot0 committed
t1 snapshot1 uncommitted
t2 snapshot2 uncommitted
t3 snapshot3 uncommitted

ref

  • https://www.runoob.com/mongodb/mongodb-osx-install.html
  • https://aotu.io/notes/2020/06/07/sql-to-mongo-1/index.html

Read More

threads safety annotations 以及std::priority_queue的一个小用法


我是随便浏览某个时间队列看到的类似的代码

  mutable mutex mu_;
  condition_variable cv_;
  std::thread timer_thread_;
  std::atomic<bool> stop_{false};
  std::priority_queue<RCReference<TimerEntry>,
                      std::vector<RCReference<TimerEntry>>,
                      TimerEntry::TimerEntryCompare>
      timers_ TFRT_GUARDED_BY(mu_);

这个GUARDED_BY让人好奇,简单查证了一番,发现是clang的工具

简单说就是clang编译器带的一个多线程的帮手,线程安全注解,原理是拓展 __attribute__

比如 __attribute__(guarded_by(mutex))

这样指明依赖关系,更能方便定位问题

使用的话编译带上 -Wthread-safety-analysis就可以了

没发现gcc有类似的工具。可惜。

另外这些时间队列的实现用的 std::priority_queue 很有意思,都指定了容器参数(因为不是内建的类型,没有实现operator <)

我看rocksdb的timequeue长这样

  // Inheriting from priority_queue, so we can access the internal container
  class Queue : public std::priority_queue<WorkItem, std::vector<WorkItem>,
                                           std::greater<WorkItem>> {
   public:
    std::vector<WorkItem>& getContainer() { return this->c; }

直接把容器参数暴漏出来。挺新颖的。这个数据结构设计保留了c就是为了这样暴露吧。


ref

  • https://clang.llvm.org/docs/ThreadSafetyAnalysis.html
    • 可以直接把这个宏抄过去 http://clang.llvm.org/docs/ThreadSafetyAnalysis.html#mutex-h
      • https://github.com/tensorflow/runtime/blob/1f60e4778e91d9932ac04647769a178a9646c0a7/include/tfrt/support/thread_annotations.h 直接抄的
  • 原理论文 https://research.google.com/pubs/archive/42958.pdf
  • ppt介绍 https://llvm.org/devmtg/2011-11/Hutchins_ThreadSafety.pdf
  • 用法 1 https://stackoverflow.com/questions/40468897/clang-thread-safety-with-stdcondition-variable
  • 用法 2 https://zhuanlan.zhihu.com/p/47837673
  • std::priority_queue 看成员对象那一小节https://en.cppreference.com/w/cpp/container/priority_queue
  • 定时器实现总结 https://www.ibm.com/developerworks/cn/linux/l-cn-timers/index.html 文章写得很棒

重点关注最小堆(优先队列) 来维护定时器组,以及时间轮

  • https://www.zhihu.com/question/68451392 管理定时器,不一定需要timerqueue 暴力扫也不是不可以 只要timer不多
  • kafka中的时间轮 https://club.perfma.com/article/328984
  • https://www.cnblogs.com/zhongwencool/p/timing_wheel.html 他这个博客做的不错。。。

Read More

六月待读 need review

我发现越攒越多了这东西

https://github.com/YongjunHe/corobase

https://hal.inria.fr/file/index/docid/555588/filename/techreport.pdf

oatpp

https://github.com/oatpp/oatpp#api-controller-and-request-mapping

continuable

https://naios.github.io/continuable/

https://chubaofs.github.io/chubaodb/zh-CN/config.html

https://hammertux.github.io/slab-allocator

coroutine

https://luncliff.github.io/coroutine/articles/combining-coroutines-and-pthread_create/

https://www.jianshu.com/u/bb58761c6c04

分布式

https://cloud.tencent.com/developer/article/1015442

network

https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/#

https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data/

crdt

https://techbeacon.com/app-dev-testing/how-simplify-distributed-app-development-crdts

https://redislabs.com/redis-enterprise/technology/active-active-geo-distribution/

mongo

https://mongoing.com/archives/6102

https://blog.csdn.net/baijiwei/article/details/78070355

https://blog.csdn.net/baijiwei/article/details/78303200

https://zhuanlan.zhihu.com/c_1047791597869199360

redis延迟分析

https://github.com/moooofly/MarkSomethingDown/blob/master/Redis/Redis%20%E8%AE%BF%E9%97%AE%E5%BB%B6%E8%BF%9F%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90.md

brpc

https://zhuanlan.zhihu.com/p/113427004

hbase结构介绍

https://niyanchun.com/hbase-introduction-extend.html

我比较关系高可用,负载均衡(range server分裂,怎么做的),以及强一致性的实现

大多是怎么用的文档

bluestore

https://zhuanlan.zhihu.com/p/45084771

https://zhuanlan.zhihu.com/p/46362124

making tcp fast

应该有中文文档

https://netdevconf.info/1.2/papers/bbr-netdev-1.2.new.new.pdf

network 101

https://hpbn.co/building-blocks-of-udp/#null-protocol-services

brendan gregg博客分享

抓tcp

http://www.brendangregg.com/blog/2018-03-22/tcp-tracepoints.html

bpf

http://www.brendangregg.com/bpf-performance-tools-book.html

nginx 延迟高吞吐 分享ppt

https://www.nginx.com/blog/optimizing-web-servers-for-high-throughput-and-low-latency/

大页内存和tlb

https://www.mnstory.net/2016/06/30/qemu-hugepages/

还是透明大页

https://www.percona.com/blog/2019/03/06/settling-the-myth-of-transparent-hugepages-for-databases/

针对写放大的kv sifrdb

https://nan01ab.github.io/2019/02/LSM-Trees(2).html

jungle

btree + lsm https://www.usenix.org/system/files/hotstorage19-paper-ahn.pdf

https://www.usenix.org/sites/default/files/conference/protected-files/hotstorage19_slides_ahn.pdf

写个快的json parser

https://chadaustin.me/2017/05/writing-a-really-really-fast-json-parser/

https://chadaustin.me/2013/01/sajson-why-the-parse-tree-is-big-enough/

smfrpchttps://smfrpc.github.io/smf/

years watching https://nwat.xyz/blog/2018/01/15/systems-research-im-watching-for-in-2018/

linux性能调优指南

https://lihz1990.gitbooks.io/transoflptg/content/01.%E7%90%86%E8%A7%A3Linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/1.1.Linux%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86.html

深入理解iostat

https://bean-li.github.io/dive-into-iostat/

为什么就没有个一图剩千言的模板图呢?

十分钟教会看top

https://juejin.im/post/5d590126f265da03db0776b6

为什么就没有个一图剩千言的模板图呢??

tcpdump

https://danielmiessler.com/study/tcpdump/

为什么就没有个一图剩千言的模板图呢???

boltdb介绍

https://lrita.github.io/2017/05/21/boltdb-overview-0/

常见db比较

https://cloud.tencent.com/developer/article/1067439

SILT – A Memory-Efficient, High-Performance Key-Value Store

https://nan01ab.github.io/2018/04/SILT.html

再议silt

http://blog.foool.net/2012/06/%E5%86%8D%E8%AE%AEsilt-a-memory-efficient-high-performance-key-value-store/

taurus db https://nan01ab.github.io/2020/06/Taurus.html

evendb

热点优化

https://www.jianshu.com/p/bc6a5ee0d3db

https://nan01ab.github.io/2020/06/KV-Store(2).html

这个人博客不错

https://www.jianshu.com/u/bb58761c6c04

https://sekwonlee.github.io/files/nvmw20_splitfs.pdf

Read More

(cppcon)一些老的编程规范的反思

goto harmful?

goto更像汇编一点。

感觉是老生长谈了,常用的goto场景还算处理错误码退出,还列了论文,高德纳的

goto 在c++:可能那个跳过构造函数,漏掉初始化(编译不过/编译告警),注意(setjmp是不是也这样)

循环中提前退出, goto版本更高效 (why??)

switch里用goto c++里没有对应的应用,类似 duff’s device????? 手动展开循环 别手写,让编译器干这个活

用switch就算用goto了

讨论了其他语言中的使用套路 pattern, 没记录 只记录c++相关的

  • 不能跳过复杂类型初始化?non-vacuous怎么翻译?我大概理解就算没有默认构造的
  • 不能跳出/跳入函数
  • 不能用在constexpr函数
  • 不能跳入try catch,跳出没事儿(都try-catch了还用goto感觉有点分裂)

函数退出集中起来。

(确实,多路返回的代码让人痛苦,我也写过那种。。 不清晰)

上面讨论的for循环return 低效,可以把for中间判断限制一下,提前break,起到goto效果

返回值复杂,比如variant 使用overload trick,variant的switch

几种例外,能省构造等等。各取所需

成员变量private访问权限

封装 不变量 提前设计 即使你用不到 需要c#那种proprity? class封装和struct那种透明的语义就不一样了。还是哪句话,用不到不要过度设计

声明就初始化

可能写成函数声明了,ecpp有一条 某些场景没必要非得初始化 你不用的,不要多付出

还有在函数开头声明,这是c的习惯,可能用不到,白白浪费构造 其他语言也是一样,什么时候用什么时候声明 两部初始化,工厂模式

ref

  1. https://www.bilibili.com/video/BV1pJ411w7kh?p=12
  2. ppt 在这里 https://github.com/CppCon/CppCon2019/blob/master/Presentations/some_programming_myths_revisited/some_programming_myths_revisited__patrice_roy__cppcon_2019.pdf

Read More

(cppcon)当零抽象失败 怎么把编译器问题解决掉?

这个演讲者写了个python to c++的程序 pythran,牛逼阿 这个演讲是pythran生成代码有个bug,抓bug的故事,涉及到llvm

演示了一个简单的add代码,性能差很多,编译器问题(怎么定界的这么快)

首先找最小复现代码片 c的实现是向量化的,但是llvm生成的ir没有向量化

用clang的 -Rpass-missed=loop-vectoreze -Rpass-analysis=loop-vectorize

分析得到没有循环优化

然后看llvm的代码,编译debug版本看打印

PHI node检测有问题

看checker的条件

inttoptr <=> ptrtoint 逻辑有问题?

这里我就跟不太上了,llvm不了解。得看一下llvm相关的东西

作者做了个去掉的patch,验证,结果向量化了

深层问题 SROA 已经提bug修了

回到标题,零成本抽象是牛逼的,但是需要编译器来达成这个优化

编译器有没有保证的最低程度优化?没。所以需要了解这个东西,了解优化程度 作者的建议就是看ir结果,对比,跑omit, analyze,以及了解c 的llvm ir。简单

ref

  1. https://github.com/serge-sans-paille/pythran
  2. https://www.bilibili.com/video/BV1pJ411w7kh?p=154
  3. PPT没找到

todo

看看llvm的资料


Read More

(cppcon)linux下c++现代调试工具手段

这第三页ppt介绍的也不能说modern吧。rr确实没用过

gdb

gdb -> ptrace ->signal

strace 也是用的ptrace

通过 ptrace(PTRACE_CONT) 传出去 断点和单步,传的信号是SIGTRAP,退出是SIGINT

debug register??头一回知道

DWARF info细节

  • PC信息
  • 堆栈信息
  • 类型信心,函数原型,。。。。

readelf –debug-dump

info signals能看到所有信号的触发

调试 符号优化没了,用-g3 (有没有性能影响?)

堆栈,堆栈指针的优化,CFA,注意,可以利用来导出堆栈 (好像安全不让用?)

libthreaddb 库,用来调试

rr

没细说

valgrind, sanitizers

malloc free的实现是有隐藏细节的。导致意外的越界会有问题,这两个工具都是用来抓类似问题的

#cppcheck, coverity

一个coverity公司来做介绍。。这个ppt我见过,以前也有来我们公司的 检查dead code 死循环,越界还算挺有效果的

简单介绍了一下原理? 所有的checker都所定义好的,用调用图来算异常节点?

ref

  1. https://www.bilibili.com/video/BV1pJ411w7kh?p=15
  2. ppt https://github.com/CppCon/CppCon2019/tree/master/Presentations/modern_linux_cpp_debugging_tools__under_the_covers

Read More

(译)The Hunt for the Fastest Zero

一个场景,把长度为n的字符数组用0填满 如果用c的话,大家肯定都用memset ,这个文章的主题是c++,咱们用c++来写,是这样的

void fill1(char *p, size_t n) {
    std::fill(p, p + n, 0);
}

但是,只添加几个字符,就会快29倍,很容易就写出性能比上面代码片更好的代码来,像这样

void fill2(char *p, size_t n) {
    std::fill(p, p + n, '\0');
}

作者用的是O2优化

函数 Bytes/Cycle
fill1 1.0
fill2 29.1

这两种写法有啥区别呢 看汇编

fill1是这样的

fill1(char*, unsigned long):
      add rsi, rdi
      cmp rsi, rdi
      je .L1

.L3:
      mov BYTE PTR [rdi], 0 ;rdi存0
      add rdi, 1            ;rdi ++
      cmp rsi, rdi          ;比较rdi 和size大小
      jne .L3               ;继续循环L3
.L1:
      ret

能看出来这段代码就是按位赋值 根据参考链接2方法论,这段代码主要瓶颈就是每个周期要有一次选择分支和保存值 但是fill2可完全不一样

fill2:

fill2(char*, unsigned long):
        test rsi,rdi
        jne .L8
        ret
.L8:
        mov rdx, rsi
        xor esi, esi
        jmp memset ;尾调用memset

这里就不再分析为啥memset要快了。肯定比手写copy要快,有循环展开,且省掉了很多分支选择

但是为什么第一种写法不会直接调用memset呢 作者一开始以为编译器做了手脚,试了O3优化,结果都优化成memset了

但是真正的原因,在std::fill的实现上

  /*
   *  ...
   *
   *  This function fills a range with copies of the same value.  For char
   *  types filling contiguous areas of memory, this becomes an inline call
   *  to @c memset or @c wmemset.
  */
  template<typename _ForwardIterator, typename _Tp>
  inline void fill(_ForwardIterator __first, _ForwardIterator __last, const _Tp& __value)
  {
    std::__fill_a(std::__niter_base(__first), std::__niter_base(__last), __value);
  }

std::fill根据某些traits做了优化,至于是那种场景呢?看std::__fill_a

  template<typename _ForwardIterator, typename _Tp>
  inline typename
  __gnu_cxx::__enable_if<!__is_scalar<_Tp>::__value, void>::__type
  __fill_a(_ForwardIterator __first, _ForwardIterator __last, const _Tp& __value)
  {
    for (; __first != __last; ++__first)
      *__first = __value;
  }

  // Specialization: for char types we can use memset.
  template<typename _Tp>
  inline typename
  __gnu_cxx::__enable_if<__is_byte<_Tp>::__value, void>::__type
  __fill_a(_Tp* __first, _Tp* __last, const _Tp& __c)
  {
    const _Tp __tmp = __c;
    if (const size_t __len = __last - __first)
      __builtin_memset(__first, static_cast<unsigned char>(__tmp), __len);
  }

根据这个SFINAE规则能看到,当T是is_byte的时候,才会触发调用memset fill1的写法,T的类型是整型常量,所以没触发优化成memset的版本 等同于

std::fill<char *, int>(p, p + n, 0);

显式的指定函数模板参数,不用编译器推导,也能触发优化,像下面这个fill3

void fill3(char * p, size_t n) {
    std::file<char *, char>(p, p + n, 0);
}

按位复制优化成memset是编译器优化器做的。(优化器怎么做的?idiom recognition) gcc O3/ clang O2

对于第二种写法,不传’\0’,也可以使用 static_cast<char>(0)

后面作者给了个标准库 的修改patch value的类型不必非得和指针类型一致就可以了

  template<typename _Tp, typename _Tvalue>
  inline typename
  __gnu_cxx::__enable_if<__is_byte<_Tp>::__value, void>::__type
  __fill_a(_Tp* __first, _Tp* __last, const _Tvalue& __c)
  {
    const _Tvalue __tmp = __c;
    if (const size_t __len = __last - __first)
      __builtin_memset(__first, static_cast<unsigned char>(__tmp), __len);
  }

但是这种改法,对自定义类型就不行

struct conv_counting_int {
    int v_;
    mutable size_t count_ = 0;

    operator char() const {
        count_++;
        return (char)v_;
    }
};

size_t fill5(char *p, size_t n) {
    conv_counting_int zero{0};
    std::fill(p, p + n, zero);
    return zero.count_;
}

返回值是1而不是n,优化反而让结果不对。这种场景,最好让这种自定义类型不合法 比如



  template<typename _Tpointer, typename _Tp>
    inline typename
    __gnu_cxx::__enable_if<__is_byte<_Tpointer>::__value && __is_scalar<_Tp>::__value, void>::__type
    __fill_a( _Tpointer* __first,  _Tpointer* __last, const _Tp& __value) {
      ...

ref

  1. https://travisdowns.github.io/blog/2020/01/20/zero.html
  2. 值得一看 https://travisdowns.github.io/blog/2019/06/11/speed-limits.html
  3. 这人的博客非常牛逼https://travisdowns.github.io 值得都看看

Read More

Linux/Unix系统编程手册 整理笔记

只列重点和延伸

基本概念

  • 文件I/O模型
  • 进程
    • 内存布局,文本,数据,堆,栈
    • _exit 退出 (实现是调用sys_exit退出,c标准库中的exit比这个函数多了一些清空IO 的动作,遇到过一次和log4cplus挂死的问题)
      • 后面的章节会讲这个
    • 进程的用户和组标识符,凭证,限定权限
    • 特权进程,用户ID为0 的进程,内核权限限制对这种进程无效
    • 不同用户的权限有不同的能力,可以赋予进程来执行或者取消特殊的能力 CAP_KILL
    • init进程 所有进程之父 创建并监控其他进程
    • 守护进程
    • 环境列表 export setenv char **environ
    • 资源限制 setrlimit()
  • 内存映射 mmap
    • 共享映射
      • 同一文件
      • fork继承
        • 有标记来确认映射改动是否可见
  • 进程间通信以及同步
    • 信号 ctrl c
    • 管道 , FIFO, pipe
    • socket
    • 文件锁定 (典型使用举例?)
    • 消息队列 (典型使用举例?)
    • 信号量
    • 共享内存
  • 进程组,信号传递 (典型使用举例?)(ctrl c?)
  • 会话,控制终端和控制进程 ctrl c
  • 伪终端,ssh
  • 日期和时间 真实事件和进程时间 time(time有两个,另一个是看更细致的内核动作的)
  • /proc文件系统

系统编程概念

  • 系统调用
    • 调用c wrapper函数,入参复制到寄存器,触发中断,切换到内核态,通过中断向量表找到系统调用
      • 中断改成sysenter了
    • 内核栈保存寄存器的值,校验系统编号,正式调用sysytemcall
      • 可以用strace抓
  • 库函数
    • 版本号
    • errno perror 封装一些错误处理和解析函数
  • 可移植性问题,指的是一些宏开关,BSD POSIX GNU_SOURCE之类的

文件IO: 通用的IO模型

  • open
    • flag 只列有意思的,后面还会讲
      • O_CLOEXEC fcntl
      • O_NONBLOCK 非阻塞io
      • O_ASYNC 信号驱动io, fcntl(file control)
    • err
      • 无法访问,目录问题,文件描述符上限,文件打开数目上限,文件只读,文件为exe
  • read 返回值,错误-1没了0读到多少返回多少
    • 内核维护读到那里,aka偏移量 lseek
  • write 返回值,写入了多少。可能和指定的count不一致(磁盘满/RLIMIT_FSIZE)
    • 偏移量超过文件结尾继续写入 aka文件空洞 eg: coredump
      • 文件空洞占不占用?严格说占用,看空洞边界落在哪里,落在文件块内还是会分配空间的 用0填充
  • ioctl (io control)

  • 原子操作和竞争条件
  • fcntl更改文件状态位
  • 文件描述符与打开文件的关系
    • 内核维护的数据结构
      • 进程及文件描述符表
        • fd
        • flag
      • 系统级打开文件表
        • 文件句柄
        • 文件偏移量,状态,访问模式,inode引用
      • 文件系统inode表
        • 文件类型,访问权限,锁指针,文件属性
      • 多个文件描述符可以对应同一个句柄,共用同一个偏移量
        • 复制文件描述符 dup/dup2 2>&1
      • O_CLOSEXEC 描述符私有
  • 特定偏移 pread pwrite
  • 分散输入和集中输出scatter-gather readv writev
  • 阶段文件truncate ftruncate
  • 大文件IO
  • /dev/fd ?

进程

  • pstree
  • 内存布局
  • 虚拟内存管理
    • 空间局部性和时间局部性
    • 驻留集 resident set和交换区swap area
    • 有效虚拟地址范围发生变化
      • 栈增长到之前没到过的地方
      • malloc brk sbrk提升program break位置
      • 共享内存访问 shmat shmdt
      • mmaSp/munmap
    • 地址空间隔离的有点
      • 进程进程 进程内核间隔离
      • 共享内存
        • 同一份代码副本
        • shmget mmap共享内存
      • 内存保护
      • 每个进程使用的真实内存少,使得容纳的进程多,CPU利用率高
    • 栈和栈帧 不多说
    • 环境 env 不多说
    • setjmp longjmp 别用

内存分配

  • 堆当前边界 program break
    • 调整边界brk/sbrk
    • malloc free实现,老生常谈了
      • 调试malloc
        • mtrace muntrace 搭配MALLOC_TRACE mtrace 分析文件写入
        • mcheck mprobe 一致性检查分析
        • MALLOC_CHECK_ 环境变量,提供上面的功能,动态的,安全原因,设置用户id组id的程序无法设置
        • glibc检测 mallopt mallinfo
    • calloc realloc
    • 对齐分配 memalign posix_memalign
    • 栈上分配alloca
      • 邪门歪道别乱用

页帧分配(Page frame allocation)

页是物理内存或虚拟内存中一组连续的线性地址,Linux内核以页为单位处理内存,页的大小通常是4KB。当一个进程请求一定量的页面时,如果有可用的页面,内核会直接把这些页面分配给这个进程,否则,内核会从其它进程或者页缓存中拿来一部分给这个进程用。内核知道有多少页可用,也知道它们的位置。

伙伴系统(Buddy system)

Linux内核使用名为伙伴系统(Buddy system)的机制维护空闲页,伙伴系统维护空闲页面,并且尝试给发来页面申请的进程分配页面,它还努力保持内存区域是连续的。如果不考虑到零散的小页面可能会导致内存碎片,而且在要分配一个连续的大内存页时将变得很困难,这就可能导致内存使用效率降低和性能下降。下图说明了伙伴系统如何分配内存页。 buddy-system

如果尝试分配内存页失败,就启动回收机制。可以在/proc/buddyinfo文件看到伙伴系统的信息。

页帧回收

如果在进程请求指定数量的内存页时没有可用的内存页,内核就会尝试释放特定的内存页(以前使用过,现在没有使用,并且基于某些原则仍然被标记为活动状态)给新的请求使用。这个过程叫做内存回收kswapd内核线程和try_to_free_page()内核函数负责页面回收。

kswapd通常在task interruptible状态下休眠,当一个区域中的空闲页低于阈值的时候,它就会被伙伴系统唤醒。它基于最近最少使用原则(LRU,Least Recently Used)在活动页中寻找可回收的页面。最近最少使用的页面被首先释放。它使用活动列表和非活动列表来维护候选页面。kswapd扫描活动列表,检查页面的近期使用情况,近期没有使用的页面被放入非活动列表中。使用vmstat -a命令可以查看有分别有多少内存被认为是活动和非活动状态。

kswapd还要遵循另外一个原则。页面主要有两种用途:页面缓存(page cahe)进程地址空间(process address space)。页面缓存是指映射到磁盘文件的页面;进程地址空间的页面(又叫做匿名内存,因为不是任何文件的映射,也没有名字)使用来做堆栈使用的,在回收内存时,kswapd更偏向于回收页面缓存。

Page out和swap out:“page out”和“swap out”很容易混淆。“page out”意思是把一些页面(整个地址空间的一部分)交换到swap;”swap out”意味着把所有的地址空间交换到swap。

如果大部分的页面缓存和进程地址空间来自于内存回收,在某些情况下,可能会影响性能。我们可以通过/proc/sys/vm/swappiness文件来控制这个行为

swap

在发生页面回收时,属于进程地址空间的处于非活动列表的候选页面会发生page out。拥有交换空间本身是很正常的事情。在其它操作系统中,swap无非是保证操作系统可以分配超出物理内存大小的空间,但是Linux使用swap的空间的办法更加高效。如图1-12所示,虚拟内存由物理内存和磁盘子系统或者swap分区组成。在Linux中,如果虚拟内存管理器意识到内存页已经分配了,但是已经很久没有使用,它就把内存页移动到swap空间。

像getty这类守护进程随着开机启动,可是却很少使用到,此时,让它腾出宝贵的物理内存,把内存页移动到swap似乎是很有益的,Linux正是这么做的。所以,即使swap空间使用率到了50%也没必要惊慌。因为swap空间不是用来说明内存出现瓶颈,而是体现了Linux的高效性。

ps -o majflt,minflt -p pid

minor fault 在内核中,缺页中断导致的异常叫做page fault。其中,因为filemap映射导致的缺页,或者swap导致的缺页,叫做major fault;匿名映射导致的page fault叫做minor fault。 作者一般这么区分:需要IO加载的是major fault;minor fault则不需要IO加载


用户和组

  • 密码与密码文件/etc/shadow

  • /etc/group

    跳过了,没啥说的。


进程凭证

讲了一大堆关于用户组,权限之类的东西


时间

  • gettimeofday
    • time 多余的
    • ctime打印用
  • 时区TZ
  • 地区LC_ALL
  • 软件时钟jiffies
  • 进程时间
    • time命令,有两个

系统限制和选项

  • sysconf getconf pathconf

系统和进程信息

  • /proc

    • cat /proc/pid/status

    • 关注env status cwd fd maps mem mounts task

      image-20200620203233057

  • uname


文件IO缓冲

  • 用户空间缓冲区和内核空间缓冲区之间的数据复制,不会直接发起磁盘访问

  • 系统调用越少越好

  • 刷新stdio fflush

  • fsync 刷盘,包括元数据更新 fdatafync可能会减少磁盘操作的次数

    • 写入同步O_SYNC

    image-20200623144236672

  • 绕过缓存 直接IO O_DIRECT

    • 必须对齐

系统编程概念

  • 设备文件 /dev
    • 字符型设备 终端键盘
    • 块设备,磁盘,512倍数
  • 文件系统
    • 引导块,超级块 i节点表 数据块
    • inode 文件元数据
      • 数据块指针
        • lseek,算指针就行了
  • VFS
  • 日志文件系统
  • 挂载mount 太复杂了。我还是现搜现用吧
  • tmpfs

文件属性 没啥说的

  • stat
  • chown
  • utime

  • 扩展属性setattr

目录与链接

  • 软链接硬链接 没啥说的,inode
  • unlink
  • raname
  • nftw遍历目录树? 没发现啥使用场景

监控文件事件

inotify可以和epoll串起来

和内核交互,会耗费内核内存,所以有限制

​ /proc/sys/fs/inotify


信号

signal handler

kill 发送信号

  • raise相当于自己调用kill(getpid(),sig)
  • killpg

信号掩码,没用过

可重入要考虑

终止signal handler

  • _exit, exit不安全,这是个典型问题了 exit会刷stdio,可能会卡死
  • kill
  • abort
  • setjmp longjmp 有点魔法,没见过谁用

系统调用期间遇到的信号 -EINTR, 可能系统调用体检结束失败了。

利用这个特性,可以为阻塞调用设置一个定时器

也可以手动重启调用

  • 对应有个NO_EINTR的宏
#define NO_EINTR(stmt) while((stmt) == -1 && errno = EINTR)

​ 这样循环执行忽略EINTR错误,比较不方便,但我感觉直接屏蔽信号更好一些

  • 用sigaction指定SA_RESTART让内核帮助重试调用,但是这只是部分有效,比如poll这些肯定是无效的

高级特性

  • core文件
    • /proc/sys/kernel/core_pattern
  • TASK_INTERRUPTIBLE -> S , TASK_UNINTERRUPTIBLE -> D
  • 如果用了信号阻塞,信号恢复后怎么传递?序号升序 ,越小优先级越高
  • signal函数用sigaction实现的。

定时器和休眠

settitimer alarm

alarm会让阻塞的系统调用产生EINTR,也就是超时结束了

但这东西可能有竞态问题,还是用select /poll超时特性更好,还能整合到轮训框架内

nanosleep


进程

创建

  • 父子进程文件共享
    • fd操作文件表项,包含当前文件原信息,修改会互相影响
  • 父子进程谁先谁后?
    • 2.6.32之后默认父在前,有点点性能优势,但差异很小
    • 同步信号规避竞争

终止

  • _exit和exit说了好几遍了
    • exit会主动调用atexit/on_exit注册函数,清理stdio,然后在调用_exit所以期间可能会锁死
    • main函数return n等于exit(n)
  • 更多终止细节
    • 关闭打开的文件描述符 目录流各种描述符
    • 释放各种文件锁
    • 分离各种共享内存段
    • 如果不是daemon,会向终端发送SIGHUP
    • system v信号量 semadj +到信号量值里(没懂这块)
    • 关闭各种posix有名信号量sem_close
    • 关闭各种posix消息队列 mq_close
    • 进程组孤儿 SIGHUP + SIGCONT?
    • 溢出各种内存所mlock
    • 取消各种内存映射mmap

fork和stdio缓冲区的问题

书中的例子printf是有’\n’的

输出到终端是行缓冲会直接刷新到终端,输出到文件是块缓冲,不会立即刷到文件,这就复制了两份缓冲区

如果printf没有‘\n’还是会有缓冲区的问题

标准输出是行缓冲,所以遇到“\n”的时候会刷出缓冲区,但对于磁盘这个块设备来说,“\n”并不会引起缓冲区刷出的动作,那是全缓冲

看参考链接 1 2能加深理解

解决方法,

  • 手动flush
  • 手动设置缓冲为0 setvbuf
  • 子进程_exit跳过刷新缓冲区

孤儿进程和僵尸进程

  • 需要父进程wait,不然占用内核空间
  • 主动处理SIGCHLD或者子进程忽略SIGCHLD 直接系统丢弃子进程状态

文件描述符与exec

FD_CLOSEXEC

为什么system实现要阻塞SIGCHLD,忽略SIGINT和SIGQUIT信号?


线程

  • errno一个线程一个

  • pthread api返回值
  • 链接带上lpthread
  • 可重入函数要注意

创建线程

  • pthread_create
    • pthread_self获取

终止线程

  • 线程函数return/exit
  • pthread_exit
    • pthread_join获取返回值
  • pthread_cancel

  • thread_join 类似waitpid
    • 特定的tid(waitpid可以任意pid)
    • 阻塞的(可以自己用condvar搞非阻塞)
    • 对等的(waitpid只能是父等子)
  • pthread_detach
  • pthread_attr_init 属性设置

更多细节

  • 线程栈
  • 线程和信号

线程同步

互斥量mutex -> futex虽然慢也比fcntl信号量这种系统调用要快

注意死锁

condvar 经典问题,为啥用while守着signal


线程安全

可重入

一次性初始化 pthread_once std::call_once

一个线程安全的singleton是什么样的


进程组 会话和作业控制

find / 2> /dev/null | wc -l &

find命令和wc命令同进程组

find命令和wc命令和当前bash同会话,bash是会话首进程

一个终端也就只有一个会话

前台进程和后台进程组,前台进程就一个or没有

进程控制 nohup SIGHUP

作业控制 jobs


进程优先级和调度

nice值和分配策略

cpu亲和

使用的资源与资源限制

防范

  • 特权?

  • chroot jail?
  • 缓冲区溢出
  • 不可信用户输入
  • DDoS

能力

  • 用户ID ?root?

daemon

永远不会成为会话组长,也就永远不会成为控制终端

关掉不用的0 1 2,不要浪费文件描述符


共享库

使用共享库的有用工具

  • ldd
  • readelf/objdump
  • nm

版本与命名规则


进程间通信

通信工具

  • 数据传输工具
    • 字节流 :文件是一个字节序列 读取动作是消耗性质的
      • 管道
      • FIFO
      • 数据报socket
    • 消息队列
      • system V消息队列
      • posix 消息队列
      • 数据报socket交换
    • 伪终端
  • 共享内存 速度快但是需要同步 所有进程都可见
    • system V共享内存
    • posix共享内存
    • 内存映射

同步工具

  • 信号量
  • 文件锁
  • mutex condvar
  • 通信工具也可以用来同步,pipe eventfd

管道pipe 和FIFO

管道

  • 字节流,单向
  • 容量有限,写入阻塞
  • 进程同步方法
  • 管道和缓冲
    • 伪终端替换管道

FIFO aka命名管道

屏蔽SIGPIPE

阻塞IO 非阻塞IO设置?


system v ipc

消息队列/信号量/共享内存

ipc key:整数 IPR_PRIVATE/ftok

ipcs ipcrm 类似ls rm

ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages
0x00001f4f 0          Ruby       666        0            0

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

获取ipc对象列表

/proc/sysvipc

cat /proc/sysvipc/msg
       key      msqid perms      cbytes       qnum lspid lrpid   uid   gid  cuid  cgid      stime      rtime      ctime
      8015          0   666           0          0     0     0  3000  3000  3000  3000          0          0 1598235273

查看限制

ipcs -l

------ Messages Limits --------
max queues system wide = 963
max size of message (bytes) = 8192
default max size of queue (bytes) = 16384

------ Shared Memory Limits --------
max number of segments = 4096
max seg size (kbytes) = 0
max total shared memory (kbytes) = 18014398442373116
min seg size (bytes) = 1

------ Semaphore Limits --------
max number of arrays = 32000
max semaphores per array = 32000
max semaphores system wide = 1024000000
max ops per semop call = 500
semaphore max value = 32767

消息队列

无法结合内核本身的文件描述符系统

标识符引用,键(key)复杂度都省

无连接,涉及到资源管理就会很糟糕

  • 什么时候删除?
  • 应用怎么保证不用的资源被删除?

避免使用system v ipc消息队列

信号量

进程同步 尤其是共享内存

共享内存

所处位置

全是偏移


内存映射

mmap munmap

支持映射文件

  • 省一步写,能快点

虚拟内存操作

mprotect mlock mincore

madvise 内存使用建议


posix ipc

消息队列 信号量 共享内存 用fd管理

信号量和共享内存api和system v的api差不多。都挺复杂


文件加锁

加锁与stdio缓冲问题

flock

fcntl


socket

创建fd(socket) 绑定fd(bind) 监听(listen, backlog含义,系统未accept之前的客户端connect占用的fd最大个数)

接受connect( accept, 返回连接的fd,操作这个fd进行通信)

客户端主动发起connect(失败?)

close没啥说的,得结合时序图才有意思

unix domain socket 本机通信用

tcp/ip

数据链路层隐藏

网络层无连接不可靠

传输层

TCP

  • 数据打包成段
  • 确认重传以及超时
  • 排序
  • 流量控制
    • 拥塞控制:慢启动和拥塞避免算法

UDP 注意分段

网络相关

  • 网络字节序, 大端
  • 主机服务转换函数
    • gethostbyname废弃,inet_ntop getaddrinfo

服务设计

  • 迭代型
  • 并发性
    • 预先准备好线程/进程 服务池
    • 集群
    • IO复用怎么没提?

inetd

高级主题

  • 部分读部分写
  • shutdown关闭一半
  • 深入TCP
    • 报文格式
    • 确认机制
    • 状态机
    • 建立和终止
      • listen被动打开
      • connect主动打开
      • close主动关闭,另一侧也执行close,被动关闭
    • TIME_WAIT
      • 可靠的断开,2MSL

netstat -a –inet

tcpdump抓流量

  • socke选项
    • SO_REUSEADDR

其他IO模型

水平触发和边缘处罚 LT ET

select poll是水平触发,信号驱动IO是边缘触发,epoll都支持

水平触发可以任意时刻查看fd的就绪状态,处理不完继续处理

边缘触发一次就得处理完,采取边缘触发的程序设计规则

  • 接收到IO时间尽可能多的执行IO,如果没这么做可能会失去至此那个的机会,数据丢失程序阻塞
    • 也就是说这种动作可能会饿到fd,比如上一组动作没处理完,这一组又在等待
  • 如果程序采用循环来处理fd尽可能多,而fd优势阻塞的,这样整个IO调用就阻塞住了,所以必须要改成非阻塞模式,有事件就重复执行IO直到失败为止
    • 对于epoll而言是这样的
      • 所有fd非阻塞
      • epoll_ctl管理fd列表
      • 循环
        • epoll_wait拿到就绪fd
        • 不断执行执行IO系统调用(read/write/send/accept/recv)直到EAGAIN到EWOULDBLOCK
  • 针对其他的fd会饿到的风险场景
    • 用一个列表维护一下有过就绪态的fd,把他们的超时时间调小(分给他们的时间片调小)
    • 维护的列表中已经出现过的,操作要稍微调度一下,rr之类的,而不是直接处理epoll_wait返回的列表,如果出现了错误,就移除

self-pipe技术。不多数


终端, 伪终端

CR EOF DISCARD

stty命令


ref
  1. https://www.veaxen.com/fork%E5%AF%B9%E8%A1%8C%E7%BC%93%E5%86%B2%E5%8C%BA%E7%9A%84%E5%BD%B1%E5%93%8D.html

  2. https://coolshell.cn/articles/7965.html

  3. mmap实现cp https://stackoverflow.com/questions/27535033/copying-files-using-memory-map


Read More

(译)终极python调试指南 Ultimate Guide to Python Debugging

必须打日志

不打日志必后悔,python设置日志非常简单

import logging
logging.basicConfig(
    filename='application.log',
    level=logging.WARNING,
    format= '[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s',
    datefmt='%H:%M:%S'
)

logging.error("Some serious error occurred.")
logging.warning('Function you are using is deprecated.')

有了日志logger能加上配置文件扩展(ini/yaml)就更好了(译者注:really?有点浮夸)

version: 1
disable_existing_loggers: true

formatters:
  standard:
    format: "[%(asctime)s] (pathname)s:%(lineno)d %(levelname)s - %(message)s"
    datefmt: '%H:%M:%S'

handlers:
  console:  # handler which will log into stdout
    class: logging.StreamHandler
    level: DEBUG
    formatter: standard  # Use formatter defined above
    stream: ext://sys.stdout
  file:  # handler which will log into file
    class: logging.handlers.RotatingFileHandler
    level: WARNING
    formatter: standard  # Use formatter defined above
    filename: /tmp/warnings.log
    maxBytes: 10485760 # 10MB
    backupCount: 10
    encoding: utf8

root:  # Loggers are organized in hierarchy - this is the root logger config
  level: ERROR
  handlers: [console, file]  # Attaches both handler defined above

loggers:  # Defines descendants of root logger
  mymodule:  # Logger for "mymodule"
    level: INFO
    handlers: [file]  # Will only use "file" handler defined above
    propagate: no  # Will not propagate logs to "root" logger
import yaml
from logging import config

with open("config.yaml", 'rt') as f:
    config_data = yaml.safe_load(f.read())
    config.dictConfig(config_data)

logger不直接支持yaml,但是可以把yaml转成dict,然后就可以用了

日志装饰器

有了日志还不够,发现了有问题的代码片,想看内部细节调用,直接上日志装饰器,这样要比直接在日志内加要方便的多 ,具体的细节就调节配置文件中的日志级别就可以了

from functools import wraps, partial
import logging

def attach_wrapper(obj, func=None):  # Helper function that attaches function as attribute of an object
    if func is None:
        return partial(attach_wrapper, obj)
    setattr(obj, func.__name__, func)
    return func

def log(level, message):  # Actual decorator
    def decorate(func):
        logger = logging.getLogger(func.__module__)  # Setup logger
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler = logging.StreamHandler()
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        log_message = f"{func.__name__} - {message}"

        @wraps(func)
        def wrapper(*args, **kwargs):  # Logs the message and before executing the decorated function
            logger.log(level, log_message)
            return func(*args, **kwargs)

        @attach_wrapper(wrapper)  # Attaches "set_level" to "wrapper" as attribute
        def set_level(new_level):  # Function that allows us to set log level
            nonlocal level
            level = new_level

        @attach_wrapper(wrapper)  # Attaches "set_message" to "wrapper" as attribute
        def set_message(new_message):  # Function that allows us to set message
            nonlocal log_message
            log_message = f"{func.__name__} - {new_message}"

        return wrapper
    return decorate

# Example Usage
@log(logging.WARN, "example-param")
def somefunc(args):
    return args

somefunc("some args")

somefunc.set_level(logging.CRITICAL)  # Change log level by accessing internal decorator function
somefunc.set_message("new-message")  # Change log message by accessing internal decorator function
somefunc("some args")

实现__repr__ 方便打印

老生常谈了,实现__str__也行

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius

    def __repr__(self):
        return f"Rectangle({self.x}, {self.y}, {self.radius})"

...
c = Circle(100, 80, 30)
repr(c)
# Circle(100, 80, 30)

实现__missing__ 避免KeyError异常

如果实现了自己的dict(译者注:用到dict作为自己的内部成员,可以封装一层dict) 可以实现__missing__ 如果访问key key不存在,就会触发missing调用,帮助捕捉bug

调试崩溃的程序

过快崩溃来不及看日志? -i进入细节调用模式 python -i xx.py 如果这个细节还是不够用,可以调用pdb

# crashing_app.py
SOME_VAR = 42

class SomeError(Exception):
    pass

def func():
    raise SomeError("Something went wrong...")

func()
~ $ python3 -i crashing_app.py
Traceback (most recent call last):
  File "crashing_app.py", line 9, in <module>
    func()
  File "crashing_app.py", line 7, in func
    raise SomeError("Something went wrong...")
__main__.SomeError: Something went wrong...
>>> # We are interactive shell
>>> import pdb
>>> pdb.pm()  # start Post-Mortem debugger
> .../crashing_app.py(7)func()
-> raise SomeError("Something went wrong...")
(Pdb) # Now we are in debugger and can poke around and run some commands:
(Pdb) p SOME_VAR  # Print value of variable
42
(Pdb) l  # List surrounding code we are working with
  2
  3   class SomeError(Exception):
  4       pass
  5
  6   def func():
  7  ->     raise SomeError("Something went wrong...")
  8
  9   func()
[EOF]
(Pdb)  # Continue debugging... set breakpoints, step through the code, etc.

抓调用栈

import traceback
import sys

def func():
    try:
        raise SomeError("Something went wrong...")
    except:
        traceback.print_exc(file=sys.stderr)

重新加载模块,不用开启新shell

>>> import func from module
>>> func()
"This is result..."

# Make some changes to "func"
>>> func()
"This is result..."  # Outdated result
>>> from importlib import reload; reload(module)  # Reload "module" after changes made to "func"
>>> func()
"New result...

不必陷入编辑临时文件,执行,再改,再执行的循环

ref

  1. https://martinheinz.dev/blog/24
  2. 日志装饰器这个是装饰器典型用法了,但是却没真正用到代码里,真是惭愧啊
  3. 作者还推荐 https://remysharp.com/2015/10/14/the-art-of-debugging,不过我打不开这个网址,改天看吧


Read More

嵌套lambda 捕获shared_ptr引发的bug

bug代码片抽象成这样

#include <iostream>
#include <cstdlib>
#include <string>
#include <functional>
#include <vector>
#include <algorithm>
#include <thread>
#include <memory>
using namespace std;
int main()
{

    std::vector<shared_ptr<int>> v;
    for(int i=0;i<10; i++)
        v.push_back(make_shared<int>(i));
    std::vector<std::thread> threads;
    std::for_each(v.begin(), v.end(),
                  [&](shared_ptr<int> p) {
                  //改成传const引用就没问题了
                  //[&](const shared_ptr<int>& p) {
                    threads.emplace_back(std::move(std::thread([&]() {
                      std::cout << *p<< '\n';
                    })));
                  });
    
    
    /*for (auto &e : v) {
         threads.emplace_back(std::move(std::thread([&]() {
                      std::cout << *e << '\n';
                    })));
    }*/
    for (auto &t :threads)
        t.join();
}

问题出在内部的lambda是捕获引用,但是捕获的值是栈上的,这个栈上分配的值在该场景下是复用的,结果有问题。

嵌套lambda,一定要注意捕获参数。参考链接1有详细的介绍,总结五个常见的嵌套lambda搭配

  1. 传值,捕获值,没问题,但是会有赋值开销
  2. 传值,捕获引用,有问题,传的值是分配在栈上的,捕获引用可能会变,可能不存在等等
  3. 传引用,捕获引用,没问题,但是不能传右值
  4. 传const引用,捕获引用,没问题,但是传的值失去了左值能修改的特性
  5. 传值,移动捕获值,unique_ptr只能这么捕获。也是一个好的捕获方案,省一次拷贝。

上面的案例就犯了2 的错误。改成const 引用就好了

ref

  1. http://lucentbeing.com/writing/archives/nested-lambdas-and-move-capture-in-cpp-14/

  2. 关于lambda嵌套。很复杂。https://zh.cppreference.com/w/cpp/language/lambda

  3. 一个lambda的分析 https://web.mst.edu/~nmjxv3/articles/lambdas.html


Read More

^