分布式系统概念整理

Posted on 2019-05-09 16:24:45 default

摘要:

下面的思维导图整理分布式系统所设计的常用概念,并对重要知识点做了简单解析。

分布式系统概念整理

阅读全文

Python内存分配和垃圾回收

Posted on 2017-05-17 03:24:16 python

摘要:

Python内存分配和垃圾回收

阅读全文

内核内存映射示例

Posted on 2016-04-16 06:19:41 os

摘要:

前一篇博客中,我们介绍了Linux内核中内存映射的实现,以及用户态调用mmap系统调用的原理。接下来,我们会通过Linux内核模块的方式,映射两种物理内存:物理地址空间和内核模块申请的内存,演示内核模块中如何与用户态进程之间共享内存。

在开始之前,介绍一下/dev/mem这个字符设备,打开/dev/mem之后,通过mmap系统调用,就可以映射物理地址空间。需要注意的是,这里的物理地址空间,是站在CPU角度看到的地址空间,而不仅仅是RAM空间,还有外设的IO空间,例如BIOS ROM、Video ROM、PCI BUS等。

下面的内核模块代码工作的行为类似于/dev/mem,打开对应的设备文件后,同样可以映射CPU地址空间。完整的代码在文章最后。

阅读全文

mmap实例及原理分析

Posted on 2015-12-01 11:30:27 os

摘要:

在unix/linux平台下读写文件,一般有两种方式。第一种是首先open文件,接着使用read系统调用读取文件的全部或一部分。于是内核将文件的内容从磁盘上读取到内核页高速缓冲,再从内核高速缓冲读取到用户进程的地址空间。这么做需要在内核和用户空间之间做四次数据拷贝。而且当多个进程同时读取一个文件时,则每一个进程在自己的地址空间都有这个文件的副本,这样也造成了物理内存的浪费。如下图所示:

传统方式读写文件

第二种方式是使用内存映射的方式。首先open文件,接着调用mmap系统调用,将文件的内容的全部或一部分直接映射到进程的地址空间,映射完成后,进程可以像访问普通内存一样做memcpy等操作,不必再调用read/write等操作。mmap并不分配物理地址空间,它只是会占用进程的虚拟内存空间。而第一种方式则需要进程预先分配好物理内存,内核才能将页高速缓冲中的文件数据拷贝到用户进程指定的内存空间中。

使用第二种方式,当多个进程同时访问读取一个文件时,每个进程都将文件内容在内核中的页高速缓冲映射到自己的地址空间。当第一个进程访问内核中的页缓冲时,进程的机器指令会触发一个缺页中断。内核将文件的这一页数据读入到页高速缓冲,并更新进程的页表,使页表指向内核缓冲中的这个页。之后有其他进程再次访问同一页时,该页已经在内存中,内核只需要将进程的页表登记并指向内核中的页高速缓冲即可。如下图所示:

mmap方式读写文件

阅读全文

关于多核编程的一点想法

Posted on 2015-06-30 17:18:28 os

Nim语言有很多语言上先进的特性和接近Python的语法,Rust定位成C++的直接竞争者。 但是请认真思考:这两个语言从一出生开始,都没有解决,而且以后也很难解决本世纪软件业的一次重大危机:多核编程危机。 它们的出现就不是冲着解决多核编程问题来的,基因决定了,靠这两门语言解决不了多核编程的问题。

怎么解决多核编程的问题?

屏蔽硬件上的复杂特性,例如缓存、一致性、内存屏障、原子操作,给程序员简单的并发特性,在编程时存在尽量少的心智负担。

GO可以在内存中创建成千上万的协程,并且提供了协程间通信的基础设施,单凭这两点,Nim和Rust都没有做到。

Nim语言目前没有实现自身的汇编器和链接器,Nim代码首先被编译为C代码,再把C代码编译为本地机器码。 Golang自带汇编器和链接器。

Rust官方最初的目标是像Erlang一样可以创建大量的协程,但是这个目标被官方抛弃了,所以Rust里面是并发执行体不是协程,是OS级别的线程。在高并发场景下,1000个OS的线程同时运行效率就变得非常差。或者可以选择异步模型,但是又面临回调地狱,并且要小心同步IO和CPU密集型计算阻塞当前线程。如果使用第三库必须经过改造以适合异步模型。

因为Rust官方明白,实现完整高效的的协程调度,难度很大。这方面Go做的很好,其他静态编译类型的语言都没有超过它。

我们可以说Nim和Rust的定位不同,要解决各自的目标问题。 但是很多人拿Nim和Go对比的时候,根本没有,而且也不敢把这两种语言的特性和Go的核心特性来对比。

多核编程,是目前遇到的问题,而且是难以解决的问题,谁能解决的高效和优雅,谁就能在未来获胜。

许世伟说过,他在C++中实现协程和协程调度,到头来也只是对Golang的拙劣的模仿,我想Rust官方最初的想法也大概如此吧。

而且我相信Ken Thompson和Russ Rox这两位大师的眼界。

知乎上关于Rust高并发框架实现的问题:http://www.zhihu.com/question/30325880


Golang网络库中socket阻塞调度源码剖析

Posted on 2015-05-14 09:41:57 golang

摘要:

本文分析了Golang的socket文件描述符和goroutine阻塞调度的原理。代码中大部分是Go代码,小部分是汇编代码。完整理解本文需要Go语言知识,并且用Golang写过网络程序。更重要的是,需要提前理解goroutine的调度原理。

网络轮询器是Golang中针对每个socket文件描述符建立的轮询机制。 此处的轮询并不是一般意义上的轮询,而是Golang的runtime在调度goroutine或者GC完成之后或者指定时间之内,调用epoll_wait获取所有产生IO事件的socket文件描述符。当然在runtime轮询之前,需要将socket文件描述符和当前goroutine的相关信息加入epoll维护的数据结构中,并挂起当前goroutine,当IO就绪后,通过epoll返回的文件描述符和其中附带的goroutine的信息,重新恢复当前goroutine的执行。

阅读全文

Golang内置函数和过程调用汇编代码分析

Posted on 2015-04-24 08:55:17 golang

摘要:

Golang汇编快速指南这篇博客中,简单介绍了Golang中汇编的简单语法以及特殊之处。下面介绍Golang中的内置函数和相关操作代码的汇编实现,可以作为上篇博客的补充和实践。

汇编中过程调用的参数是通过栈来传递的,在栈上的布局如下:

参数3
参数2
参数1  <-FP
保存PC <-SP
...
...

内置函数: new, make, append

package main

import (
    "fmt"
)

type new_int int

var (
    gobal_1 = "this is global var"
)

func main() {
    auto_1 := "this is auto_1"

    s0 := new(new_int)

    s1 := make([]int, 10)
    s2 := make([]int, 10)

    append(s2, 9999)
    fmt.Println(s1, s2)
    fmt.Println(s0)
}

下面是汇编代码:

"".main t=1 size=1936 value=0 args=0x0 locals=0x128
    // 定义函数main,栈帧大小为296字节,0字节的参数(无参数)
    0x0000 00000 (builtin.go:13)    TEXT    "".main+0(SB),$296-0
    // 将线程本地存储(thread local storage)传送到CX
    0x0000 00000 (builtin.go:13)    MOVQ    (TLS),CX
    // 下面是检查栈帧的大小是否超过目前分配的小
    0x0009 00009 (builtin.go:13)    LEAQ    -168(SP),AX
    0x0011 00017 (builtin.go:13)    CMPQ    AX,16(CX)
    0x0015 00021 (builtin.go:13)    JHI ,30
    // 如果超过调用runtime.morestack_noctxt
    0x0017 00023 (builtin.go:13)    CALL    ,runtime.morestack_noctxt(SB)
    0x001c 00028 (builtin.go:13)    JMP ,0
    // 扩大栈帧
    0x001e 00030 (builtin.go:13)    SUBQ    $296,SP
    0x0025 00037 (builtin.go:13)    FUNCDATA    $0,gclocals·e14c7473fe07b0ccdc0fdfa1a770087b+0(SB)
    0x0025 00037 (builtin.go:13)    FUNCDATA    $1,gclocals·7a70fcb413ec620f2a7a8c3ba5f394c1+0(SB)

    // 局部变量auto_1
    0x0025 00037 (builtin.go:14)    LEAQ    go.string."this is auto_1"+0(SB),BX
    // string在golang中是由数据本身和其长度组成的
    // 将数据的地址移动到BP
    0x002c 00044 (builtin.go:14)    MOVQ    (BX),BP
    // 移动BP到栈指针152字节的位置
    0x002f 00047 (builtin.go:14)    MOVQ    BP,"".auto_1+152(SP)
    // 将字符串长度移动到BP
    0x0037 00055 (builtin.go:14)    MOVQ    8(BX),BP
    // 移动BP到栈指针160字节的位置
    0x003b 00059 (builtin.go:14)    MOVQ    BP,"".auto_1+160(SP)

    // new_int类型的初始化
    // 将类型本身移动到BX
    0x0043 00067 (builtin.go:17)    MOVQ    $type."".new_int+0(SB),BX
    // 将BX移动到栈顶
    0x004a 00074 (builtin.go:17)    MOVQ    BX,(SP)
    0x004e 00078 (builtin.go:17)    PCDATA  $0,$1
    // 调用runtime.newobject
    0x004e 00078 (builtin.go:17)    CALL    ,runtime.newobject(SB)
    // 将返回的结果移动到BX
    0x0053 00083 (builtin.go:17)    MOVQ    8(SP),BX
    // 移动BP到栈指针80字节的位置
    0x0058 00088 (builtin.go:17)    MOVQ    BX,"".s1+80(SP)
    0x005d 00093 (builtin.go:17)    NOP ,

    // 创建slice s1
    // 将类型移动到BX
    0x005d 00093 (builtin.go:18)    MOVQ    $type.[]int+0(SB),BX
    // 将BX(类型)移动到栈顶
    0x0064 00100 (builtin.go:18)    MOVQ    BX,(SP)
    // 将长度参数len移动到相对于栈顶8字节的位置
    0x0068 00104 (builtin.go:18)    MOVQ    $10,8(SP)
    // 将容量参数cap移动到相对于栈顶16字节的位置
    0x0071 00113 (builtin.go:18)    MOVQ    $10,16(SP)
    0x007a 00122 (builtin.go:18)    PCDATA  $0,$2
    // 调用runtime.makeslice
    0x007a 00122 (builtin.go:18)    CALL    ,runtime.makeslice(SB)
    // 创建出的s1的参数分别保存在相对栈顶24, 32, 40字节的位置
    0x007f 00127 (builtin.go:18)    MOVQ    24(SP),DX
    0x0084 00132 (builtin.go:18)    MOVQ    32(SP),CX
    0x0089 00137 (builtin.go:18)    MOVQ    40(SP),BX

    // CX中保存的是slice的len参数
    0x008e 00142 (builtin.go:20)    MOVQ    CX,"".s2_len+64(SP)
    0x0093 00147 (builtin.go:20)    NOP ,

    // 将slice的array, len, cap传送给s2
    0x0093 00147 (builtin.go:21)    MOVQ    DX,"".s2+168(SP)
    0x009b 00155 (builtin.go:21)    MOVQ    CX,"".s2+176(SP)
    0x00a3 00163 (builtin.go:21)    MOVQ    BX,"".s2+184(SP)
    0x00ab 00171 (builtin.go:21)    MOVQ    BX,AX
    // 从BX中减去CX的值,结果保存在BX
    0x00ae 00174 (builtin.go:21)    SUBQ    CX,BX
    0x00b1 00177 (builtin.go:21)    CMPQ    BX,$1
    0x00b5 00181 (builtin.go:21)    JGE ,262
    // 将类型传送到栈顶
    0x00b7 00183 (builtin.go:21)    MOVQ    $type.[]int+0(SB),BX
    0x00be 00190 (builtin.go:21)    MOVQ    BX,(SP)
    // 将旧的slice传送到栈的第二个参数
    0x00c2 00194 (builtin.go:21)    MOVQ    DX,"".autotmp_0001+240(SP)
    0x00ca 00202 (builtin.go:21)    MOVQ    DX,8(SP)
    // 将长度传送到栈上第三个参数(s2的长度)
    0x00cf 00207 (builtin.go:21)    MOVQ    CX,"".autotmp_0001+248(SP)
    0x00d7 00215 (builtin.go:21)    MOVQ    CX,16(SP)
    // 将33行的BX的值重新保存到24(SP),即s2的array
    0x00dc 00220 (builtin.go:21)    MOVQ    AX,"".autotmp_0001+256(SP)
    0x00e4 00228 (builtin.go:21)    MOVQ    AX,24(SP)
    // 将s2的长度置为1
    0x00e9 00233 (builtin.go:21)    MOVQ    $1,32(SP)
    // 调用growslice
    0x00f2 00242 (builtin.go:21)    PCDATA  $0,$2
    0x00f2 00242 (builtin.go:21)    CALL    ,runtime.growslice(SB)
    // 3个返回值(sliceStruct)
    0x00f7 00247 (builtin.go:21)    MOVQ    40(SP),DX
    0x00fc 00252 (builtin.go:21)    MOVQ    48(SP),CX
    0x0101 00257 (builtin.go:21)    MOVQ    56(SP),AX
    // 增加返回的结构中len参数长度,增加1
    0x0106 00262 (builtin.go:21)    MOVQ    CX,SI
    0x0109 00265 (builtin.go:21)    INCQ    ,SI
    // 变址寻址,DX+CX*8
    0x010c 00268 (builtin.go:21)    LEAQ    (DX)(CX*8),BX
    // 将立即数9999传递到(BX)的内存位置
    0x0110 00272 (builtin.go:21)    MOVQ    $9999,(BX)
    0x0117 00279 (builtin.go:21)    NOP ,
    0x0117 00279 (builtin.go:21)    MOVQ    DX,"".autotmp_0001+240(SP)
    0x011f 00287 (builtin.go:21)    MOVQ    SI,"".autotmp_0001+248(SP)
    0x0127 00295 (builtin.go:21)    MOVQ    AX,"".autotmp_0001+256(SP)
    // 将新的slice的参数传递给s2
    0x012f 00303 (builtin.go:21)    MOVQ    DX,"".s2+168(SP)
    0x0137 00311 (builtin.go:21)    MOVQ    SI,"".s2+176(SP)
    0x013f 00319 (builtin.go:21)    MOVQ    AX,"".s2+184(SP)
    0x0147 00327 (builtin.go:21)    NOP ,
阅读全文

Golang汇编快速指南

Posted on 2015-04-23 15:40:07 golang

摘要:

这篇文档是对于Go编译器套件(6g, 8g, etc.)中不常用的汇编语言的快速预览,涵盖面不是很广泛。

Go的汇编语言基于Plan 9的汇编,Plan 9网站的页面上有详细描述。如果你想编写汇编语言,你应该读这篇文档,虽然它是Plan 9相关的。这边文档总结了汇编的语法,并且描述了使用汇编语言和Go程序交互时的特殊之处。

有一点是很重要的是,Go的汇编中没有直接体现出底层的机器。有些汇编细节能直接对应到机器,但有些不是。这是因为编译器套件在常规过程中不需要汇编语言。取而代之的是,编译器产生二进制的不完整的汇编指令集,链接器会完成它。实际上,链接器做了汇编指令的选择,所以当你看到类似于MOV这样的指令,链接器的实际操作可能不是一个移动指令,也许是清除或者载入。或者可能会根据指令的名字对应到真实的机器指令。总体上,机器相关的指令操作趋向于体现出真实的机器指令,但是一些通用的概念类似于移动内存数据、调用子例程、返回等操作就更抽象了。具体的细节和架构相关,我们为这种不精确性道歉。

阅读全文

长志气戒傲气 必须时刻保持冷静

Posted on 2015-02-04 14:38:50 life

左宗棠是晚清时期著名的军事家、政治家。

1874 年,他被任命为钦差大臣,率兵赶赴西北地区平定叛乱。一天,他行走在一条乡间小路上,无意中看见路边悬挂了一面“天下第一棋手”的旗子,有位老者正悠闲地坐在下面布围棋。左宗棠素来擅长下棋,自视棋艺高超难逢对手,如今遇到这么个狂妄的老者,便信心十足地走上前,要与其一决高下。

两人各执一方开始对弈。几手下来,左宗棠感到老者棋艺不凡,招招布满了杀机,处处暗藏着险境。尽管如此,左宗棠总能够化险为夷,绝处逢生,不及百手便大败对手。老者不服气,请求再战,左宗棠照样险胜,五盘下来连战连捷。左宗棠不禁喜形于色:“你自称天下第一,其实棋艺也不过如此! ”老者羞愧难当,当即拆下旗子,灰溜溜地离去。

不久,左宗棠从前线得胜归来,重游故地时发现路边居然又挂起了“天下第一棋手”的旗子,还是那位老者在布棋。他决心再次挑战对手。两人各守一方,又开始鏖战。老者依旧招招暗藏杀机,棋路变幻莫测,可这次不论左宗棠如何竭力挣扎,奋力解围,五盘下来都是中盘告负。左宗棠不服气,约好次日再战,结果依然屡战屡败,输得心服口服。

左宗棠困惑不已:“短短数日,为何您的棋艺进步得如此神速?”老者微笑着摇了摇头:“我的棋艺与前些日子并无变化。只是上次你即将远征边关保家卫国,一旦我胜了你,必将挫伤你杀敌的锐气,所以我便送你几盘险胜的棋局,以助长你必胜的信心。而今你小胜归来,难免有些沾沾自喜,我故意一再击败你,是让你明白骄傲自满还为时尚早,必须时刻保持冷静才能最终扭转局面……”

在身负重任时要长志气,在赢得小胜时要戒傲气。左宗棠这才明白了老者一让一挫的良苦用心,感叹道:“先生不仅棋艺高明,更深谙处世之道,是我终生的老师啊! ”


python的Greenlet模块源码分析

Posted on 2015-01-26 14:58:26 c

摘要:

协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程可以在运行期间的某个点上暂停执行,并在恢复运行时从暂停的点上继续执行。 协程已经被证明是一种非常有用的程序组件,不仅被python、lua、ruby等脚本语言广泛采用,而且被新一代面向多核的编程语言如golang rust-lang等采用作为并发的基本单位。 协程可以被认为是一种用户空间线程,与传统的线程相比,有2个主要的优点:

与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制。 与线程相比,协程的切换由程序控制,发生在用户空间而非内核空间,因此切换的代价非常小。

协程可以认为是一种用户态的线程,与系统提供的线程不同点是,它需要主动让出CPU时间,而不是由系统进行调度,即控制权在程序员手上。既然看成是用户态线程,那必然要求程序员自己进行各个协程的调度,这样就必须提供一种机制供编写协程的人将当前协程挂起,即保存协程运行场景的一些数据,调度器在其他协程挂起时再将此协程运行场景的数据恢复,以便继续运行。这里我们将协程运行场景的数据称为上下文。

阅读全文