0%

  • 由Heroku创始人Adam Wiggins在2012年发布。
  • 是一个开发服务器应用的方法论。开发运行一个服务器程序,应该考虑这些方面。
  • 使用于任意语言和后端服务开发的应用程序。
  • 最佳阅读人员:服务器开发和运维人员。
  • 中心思想是隔离。
阅读全文 »

使用这个脚本docker-show-repo-tag.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh
#
# Simple script that will display docker repository tags.
#
# Usage:
# $ docker-show-repo-tags.sh ubuntu centos
for Repo in $* ; do
curl -s -S "https://registry.hub.docker.com/v2/repositories/library/$Repo/tags/" | \
sed -e 's/,/,\n/g' -e 's/\[/\[\n/g' | \
grep '"name"' | \
awk -F\" '{print $4;}' | \
sort -fu | \
sed -e "s/^/${Repo}:/"
done
阅读全文 »

  1. 命令尽量都写在一行,docker的命令会产生分层,也就是说,每执行一次命令,都会commit一次,然后继续下一次,这样每一次的执行都会记录下来。
  2. 找比较小的基础镜像,当然还是ubuntu最好啊哈哈哈。
    阅读全文 »

1.offset管理

kafka支持将consumer消费的offset存放到集群中,在0.8.2.2版本,默认存放到zookeeper中,在0.10.1.1中就默认存放到broker中的一个__consumer_offsets的topic中。
zookeeper并不是用来做大规模读写管理的,因此放到这里会对系统造成瓶颈。

阅读全文 »

安装MySQLdb

1
pip install mysql-python

创建连接

1
2
3
4
5
6
def db_get_conn(**dbconfig):
conn = MySQLdb.connect(**dbconfig)
cursor = conn.cursor()
return conn, cursor

conn, cursor = db_get_conn(host="127.0.0.1", user="root",passwd=“123", db=“db_haha",charset="utf8")
阅读全文 »

netcat——瑞士军刀

1. 功能

  1. TCP服务端客户端
  2. 网络测试
  3. ssh的SOCKS或HTTP代理
  4. 简单的端口扫描
    阅读全文 »

重定向没什么难理解的,只是有些命令会不熟悉,这里列出了一些常用的命令,以备后查。

标准输入:键盘,0
标准输出:屏幕,1
标准错误:屏幕,2

阅读全文 »

1. 显示和设置系统日期和时间

date [OPTION]… [+FORMAT]
date [-u| –utc| -universal] [MMDDhhmm[[CC]UYY][.ss]]

2. 其实核心的只有1个功能:

输入特定格式的时间,转换成自己想要的时间格式

阅读全文 »

终端复用软件,跟screen一样,但是要强大的多。

1. 典型应用场景

自己电脑是客户端,平常ssh到linux服务器上编译运行调试软件,网络断开或者自己电脑挂掉之后,不想服务器上运行的程序也挂掉,这时候就可以用tmux了。还有就是tmux能方便的多开好几个窗口。tmux的作用是代理终端,以前你直接通过ssh通道来操作服务器的终端,现在通过ssh通道启动了1个tmux程序,由tmux程序代替你控制多个终端,这样你就拥有了一组终端,所以是个服务器开发神器。这个神器差不多长这样:
image.png

阅读全文 »

。。。

首先,我们有1块物理内存,比如4G的DDR3,如果CPU想要找这个内存里边的一块区域存储的东西(0、1),需要知道这块区域在哪里,最简单的就是把物理内存的一个个存储单元都编号,比如位置在最开头的就编号为0x00,接下来一个单元就编号0x01,…,这里每个单元都是1位,也就是只能存1个0或1个1,就像这样:

但是这样每一位都进行编号,整个地址编号就太多了,如果我们约定每8位编一个号,那整个地址就是原先的1/8,就像这样:

为了方便,我们把每8位的一个单元叫做1个字节,0x00指向的就是物理内存中的第1个存储单元,也就是第1个字节,字节是物理内存的最小存储单元。如果想拿到第1个字节第3位中的数字1(标红的位置),那就需要把整个字节都先拿到,再去找里边的数字。

这里给物理内存编的1个号码就是1个物理地址,所有给物理内存编的号码就叫物理地址的地址空间。

CPU如果想拿到某个物理内存中的数字,需要发送一组电信号给物理内存芯片,芯片根据这组电信号查找相应地址,然后返回给CPU,这组电信号编码成数字就是物理地址了,就像这样:

其中CPU到内存控制芯片的这几根线叫地址线,比如常说的某个架构有20根地址线或者32根、36根地址线,就表明CPU可以寻址到的地址空间,比如32根地址线就可以最高寻址到2^32,也就是4G。我们这里画了4根,就可以寻址到2^4,也即是16个字节。

我们需要编写程序控制CPU进行逻辑处理、计算、存储等动作,当需要读写内存中的数据的时候,最简单的当然是用物理地址去指示CPU问内存控制芯片要哪个地址的数据了,比如:

我们想要把0x00中的数据放到寄存器ax中,就把0x00这个地址给CPU,这就可以了,多简单。当然如果真的这么简单,大家都轻松了🙃。事实上,CPU为了能处理多任务,同时在一个任务中区分代码、数据、栈,就需要对内存进行划分,分别存放不同的内容,这样一是为了区分,二是为了保护(比如代码段不允许修改,某些数据只读)。先来想象一下可能的解决办法,我们的目的是想把一段处理逻辑控制在内存0x00~0x07这个段之间,让CPU在运行的时候不能超出这个内存范围去获取代码或数据,超过就要报错。一个简单的方法是设置一个标志位,该标志位表明指令中的地址都需要在0x00~0x07之间,不能超过。在指示0x080x15之间的内存的时候,再设置另一个标志位,起到同样的作用。这样就可以将整个内存分为2个段:0x00\0x07,0x08~0x15。每个段的长度是8个字节。每个段都设置一个标志位的方法有点儿笨,我们用每个段的首地址来表示这个段:0x00表示第1个段,0x08表示第2个段,再用跟段的首地址的偏移量来确认段内某个字节的地址。现在我们确定第1个段中第2个字节的地址就是0x00:1(第1个字节的地址是0)。如下图:

现在我们指令中用到的0x00:1就是逻辑地址,因为它在逻辑上将内存划分为了多个不同的段。我们要做的只是输入0x00:1这个地址,CPU会帮我们把逻辑地址翻译成0x01的物理地址🙂。

除了使用分段的方式划分内存外,还可以将物理内存直接划分为多个不同的页,称为页帧,一个物理页一般是4K大小,这样控制一页内的内存属性相同,多个不同的页可以使用不同的属性,就跟分段类似了,如下图:

这时候会出现个新问题:CPU已经使用逻辑地址来划分段了,如何去实现分页呢?其实肯定也想到了,在逻辑地址和物理地址之间加一个抽象层接口,也就是我们要说的最后一个地址:线性地址,也叫虚拟地址。线性地址的位置在逻辑地址和物理地址之间,比如在讲linux内核的书里边常见有这样的图:

CPU将逻辑地址按照分段逻辑转换成线性地址,再按分页逻辑转换成物理地址。这里的转换都是依靠CPU电路完成的,但是怎样分段,怎样分页是需要内核程序先规定好才行。CPU怎样进行分段和分页的就不细说了,这里讲的真好,可以去看看。
我们来对比一下分段和分页:

分段 分页
目的 将内存按照不同的属性分隔,实现隔离和保护 将内存按照不同的属性分隔,实现隔离和保护
对象 对线性地址进行分隔 对物理页帧进行分隔
方法 使用逻辑地址(段基址:偏移)分隔不同的段 将线性地址分隔成不同的部分,一步步去寻找页对应的物理页帧,进而找到对应的物理地址
程序需要事先做好的准备 准备好GDT,LDT,IDT,也就是个个不同的段 准备好页目录,页表,也就是各个不同的页
大小 段大小可变,程序控制 页大小固定的几个值,4K,4M,2M,CPU规定好的
好了,现在我们的CPU中地址关系如下:
看上去很复杂吧,其实理解了就没那么多东西,我画画的水平有限😅。

有个问题很多人都想知道,我一开始看内存这部分的时候也很着急:linux用户程序里边用的是什么地址?我先说答案:是逻辑地址。但很多书里边讲linux虚拟内存的分布,跟程序里用到的地址是一模一样的啊?其实linux用户程序中的地址是逻辑地址中的偏移,只不过linux把所有段的基址都设为0,这样段内偏移就跟线性地址一样了(因为段基址都是0啊,段偏移加上0还是段偏移)。为什么linux要把所有段基址都设为0呢?其实分段和分页都是对内存的分隔和保护,从用途上来说是重复的,分页更简单些,所有linux就只用了分页来实现内存保护。

还有另一个问题:linux中用户进程看到的内存都是一样的,0~4G,这是如何实现的?答案是基于分页,linux给每个进程都维护一个分页映射规则,每个分页规则对应一种线性地址到物理地址的映射,这样每个进程看到的都是同样的线性地址,但是后边对应的物理地址各不相同。

内存管理是linux内核中最复杂的系统之一,除了逻辑地址到线性地址再到物理地址的映射外,还有很多其他的特性辅助操作系统对物理内存实现管理,比如分段逻辑实现时用到的GDT、LDT、IDT、TSS、6个段寄存器,分页逻辑实现时用到的页目录、页表、页帧、CR3、PAE,CPU和物理内存之间的高速缓存等。骨架已经理清楚了,其他的理解就慢慢往里加吧。


参考:

  1. http://ilinuxkernel.com/?p=1276,讲linux内核的大牛,思路清晰,文笔优美。
  2. 《Linux内核设计与实现》Robert Love,极好的内核入门书。
  3. 《深入Linux内核架构》(德)Wolfgang.Mauerer,深入细节1。
  4. 《Understanding the Linux Kernel 》3rd_Edition,Daniel P. Bovet and Marco Cesati,深入细节2。

看内核之前需要了解一些体系结构相关的知识,本文介绍IA32常用的几个寄存器。
- 通用寄存器:EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP
- 段寄存器:CS,DS,ES,FS,GS,SS
- EFLAGES寄存器
- EIP寄存器
- 控制寄存器:CR0,CR1,CR2,CR3,CR4
- GDTR,LDTR,IDTR寄存器
- TR寄存器

1. 通用寄存器

用于存放:
  1. 算数和逻辑操作的操作数
  2. 地址操作的操作数
  3. 内存指针
这些寄存器通常可以存放任何东西,但有时候会用作特定用途,比如:
  EAX——存放累加操作数和结果,传递参数和结果等
  EBX——指向DS数据段中数据的指针
  ECX——字符串和循环操作的计数器
  EDX——I/O指针
  ESI——指向DS数据段中数据的指针;字符串操作的源指针
  EDI——跟ES配合,指向目的数据的指针;字符串操作的目的指针
  ESP——栈指针(在SS段中)
  EBP——指向栈中的数据(SS段中),一般用作函数嵌套调用时的栈帧基址

2. 段寄存器

当用平坦内存模型(flat memory model)的时候,段寄存器都指向0,不分段。
当用段内存模型(segmented memory model)的时候,段寄存器指向各自的段,且有CPU的保护机制。
保护模式下(段内存模型):
  CS:保存当前指向代码段的段选择子,配合EIP,可以找到下一个要执行的代码。
  DS,ES,FS,GS:保存四个数据段的段选择子,可以让当前任务同时获取四个数据段,比如一个指向当前特权级的数据段,另一个指向更高特权级的数据段,第三个指向动态创建的数据结构,第四个指向跟另一个程序共享的数据段。
  SS:保存当前栈的段选择子。
段寄存器中保存着段选择子,根据该选择子可以选择GDTR(或LDTR)指向的GDT(或LDT)表中的某一项(段描述符),然后根据该项可以找到相应的段(选择的时候CPU可以进行权限检查等)。如果CPU每次都从段寄存器拿到段选择子,再根据GDTR找到相应的项,再找到段就太麻烦了,所以这几个段寄存器都有个隐藏的寄存器,不暴露给开发人员,只是CPU内部使用的,用于缓存段地址,加快处理速度。如下图:
段寄存器

3. EFLAGS

  存储CPU的一些内部状态。
EFLAGS寄存器

4. EIP寄存器

  用于存放下一个要执行的代码的地址,非常重要,通过特定的指令(JMP,Jcc,CALL,RET,IRET)可以改变值,不能被直接读取,只能在call调用的时候从栈中读取,可以直接赋值,然后用RET或IRET跳转(内核用于向高特权级翻转)。

5. 控制寄存器

控制寄存器
CR0——包含控制处理器操作模式和状态的标志
CR1——保留
CR2——包含缺页中断时的线性地址
CR3——包含分页的第一层结构的基址和两个标志位(PCD,PWT)
CR4——包含一些架构扩展,指定特定的处理器兼容。
一些位解释如下:
PG: Paging,是否分页
CD:Cache Disable,是否使用物理缓存
PE:Protection Enable,打开保护模式,该标志位不会启动分页,只会启动基于段的保护(分页和段都是保护,其实是重复的,linux只用了分页保护)。

6. GDTR,LDTR,IDTR

存放GDT,LDT,IDT的基址。

GDT:Global Descriptor Table,全局描述符表,存放全局的段描述符的数组,只有1份,通过该表可以找到所有的段,也只有通过该表才能实现处理器基于段的保护。

LDT:Local Descriptor Table,局部描述符表,存放局部的段描述符的数组,可以有多份,当前的LDT基址存放在LDTR中。另外,每一份LDT都要在GDT中有一个描述符(说明一个LDT本身也是内存中的一个段)。通过LDT可以获取当前任务的相关段。

IDT:Interrupt Descriptor Table,中断描述符,存放处理器的各个中断的地方。

7. TR寄存器

存放当前TSS的基址。
CPU处理多任务的时候,需要在各个任务之间切换,保存当前任务状态,加载下一个任务的状态,然后执行下一个任务,状态保存在多个地方,其中栈的相关信息保存在TSS中。


参考:

  1. 《Intel® 64 and IA-32 Architectures Software Developer’s Manual
    Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D and 4》
  2. 《Understanding the Linux Kernel 3rd Edition》

  真的是挺好的一本书,从最基础的boot一直讲到进程调度、文件系统、内存管理,最最重要的是每一步都能自己动手实践,不止理解了概念,还对过程有了自己的感性认识,这是别的操作系统书里很难有的。
  经过这些,对计算机如何工作理解的更深刻了。CPU从硬盘中将文件读入到内存,数据和代码都存在在内存,因为涉及到保护之类的,这些内容按段分布,代码控制CPU的逻辑流向。中断让CPU能更及时且准确的处理外来任务。
  学习操作系统最大的感受,一个是CPU如何跟各个外设进行交互,另一个就是事无巨细都要一点点去实现,要对整个系统完全掌控才心里有底。
  目前我觉得学习操作系统的顺序应该是了解其中的几个基本概念:文件系统、内存管理、进程调度、中断机制,然后开始从boot了解,一步步串起来,才会在整个过程中不那么艰难,看完其实操作系统也就那么回事儿了。
  这是对里边涉及到的一些概念的印象,还有待充实。
Orange's:一个操作系统的实现


  1. IA32 CPU的数据总体结构:
    CPU的数据结构
  2. GDT的作用是用来提供段式存储机制,这种机制是通过段寄存器和GDT中的描述符共同提供的。
  3. 描述符的种类:
  • 代码段描述符
  • 数据段描述符
  • 系统段描述符
  • 门描述符
  1. IA32的分段机制中,特权级共有4个级别,从高到低分别是0,1,2,3。数字越小表示的特权级越大
  2. 处理器通过识别CPL,DPL,RPL这3中特权级进行特权级检验
    CPL: current privilege level,当前执行的程序或任务的特权级。存储在cs和ss的第0位和第1位上。通常,CPL等于代码所在的段的特权级,当程序转移到不同特权级的代码段时,处理器将改变CPL。一致代码段可以被相同或者更低特权级的代码访问。当处理器访问一个与CPL特权级不同的一致代码段时,CPL不会被改变。
    RPL: requested privilege level.存储在段选择子的第0和第1位。处理器通过检查RPL和CPL来确认一个访问请求是否合法。RPL占主导。
    DPL: descriptor privilege level,表示段或者门的特权级。存储在段或门描述符的DPL字段。当当前代码段试图访问一个段或门时,DPL将会和CPL以及段或门选择子的RPL相比较,根据段或门类型的不同,DPL将会被区别对待:
  • 数据段:DPL规定了可以访问此段的最低特权级。
  • 非一致代码段(不使用调用门的情况下):DPL规定访问此段的特权级。比如,一个非一致代码段的特权级为0,那么只有CPL为0的程序才能访问它。
  • 调用门:DPL规定了当前执行的程序或者任务可以访问此调用门的最低特权级。
  • 一致代码段和通过调用门访问的非一致代码段:DPL规定了访问此段的最高特权级。比如一个一致代码段的DPL是2,那么CPL为0和1的程序将无法访问此段。
  • TSS: DPL规定了可以访问此TSS的最低特权级。
  1. 程序控制转移的发生:jmp, call, ret, sysenter, sysexit, int n, iret,或者中断、异常
  2. jmp、call指令可以实现4种转移:
  • 目标操作数包含目标代码段的段选择子
  • 目标操作数指向一个包含目标代码段选择子的调用门描述符
  • 目标操作数指向一个包含目标代码段选择子的TSS
  • 目标操作数指向一个任务门,这个任务门指向一个包含目标代码段选择子的TSS
  1. 门描述符种类
  • 调用门 (call gates)
  • 中断门(interrupt gates)
  • 陷阱门(trap gates)
  • 任务门(task gates)
  1. 每个任务最多可能在4个特权级间转移,所以每个任务实际上需要4个堆栈,这时候需要TSS(task-state stack)来存储。
  2. 8259A芯片用于处理外部中断,可设置优先级,可屏蔽
  3. 一致代码段说明
  • 一致代码段是给低特权级执行高特权级代码提供的一条通道,没有这条通道,从低到高或者反过来都不能执行
  • 数据段都是非一致的,但是高的可以无条件访问低的数据段
  1. 系统一开始进入的是0,也就是最高特权级,往特权级低的方向移动使用ret,往高处移动使用call,一个任务最多会用到4个特权级,而TSS只是在低特权级到高特权级移动的时候才会用到,所以TSS只会保存前三个。
  2. 分页是CPU提供的功能,使用两个东西,PDE和PTE,一个PDE和相应的PTE组成一套对应关系,说明一种线性地址和物理地址的对应关系。逻辑地址到线性地址用到的是分段机制。
    线性地址提供的相当于一个胶水层,将上层应用(程序使用的逻辑地址)跟下层物理实现(CPU提供的物理地址)分割开来
    分页地址转换
    因为有多套不同的(PDE,PTE),所以形同的线性地址可能就对应不同的物理地址,这也就是常说的操作系统提供的统一内存空间。
  3. IDT的作用是将每一个中断向量和描述符对应起来。
  4. C调用约定:后面的参数先入栈,调用者负责清理堆栈。在处理可变参数时C调用约定表现良好,因为只有调用者知道此次调用包含了几个参数,于是可以方便清理堆栈
  5. 用到的芯片
  • 8259A:处理中断
  • 8253:PIT(programmable interval timer),可编程定时器
  1. 敲击键盘有两方面的含义:动作和内容。动作:按下、保持按住的状态、放开;内容:字母键、数字键、回车和其他键。
  2. tty设备跟普通文件的不同:1.怎样才算“输入结束”,是每次键盘敲击之后都算结束,还是等回车才算结束,或者其他;2. 是否要让文件系统等待输入过程结束。

于渊

  要知道,写一本好书并不是说说就那么简单的,思路清晰、知道重点、保持严谨、不厌其烦,应该是写文章做报告写书的人都应该具备的特点,我虽然没写过,但也知道这本书很好的起到了反面例子。但即使没那么好的书,也比某些单独的文章要好得多。
  这本书不像Orange’s是个玩具操作系统,而是实实在在讲的linux-0.11——可以实际运行、实际使用的内核。从加电引导,到保护模式启动,内核建立中断,到第0、1、2个进程建立,再到文件系统、内存管理、进程调度,跟着源码一步步将系统带入到“怠速”状态,很神奇的过程。从这里边会明白linux进程调度用到的switch_to(n)是怎么加载TSS,怎么从一个进程转移到另一个进程;会知道系统调用的代码执行路线;内核中的文件系统是怎么组织的,以前想不明白的dup()/dup2()函数是怎么个过程,看了源码立马就清晰了;pipe()原来是个内存页,怪不得最大长度是4096字节(页大小);内存的段页管理机制到底是怎样的。源码之前,了无秘密


  1. 加电启动过程

    1
    2
    3
    4
    1. BIOS:处理器加电启动时会执行0xFFFF0处的代码,也就是BIOS所在区域(直接写在ROM中,不需要加载)。BIOS会加载bootsect进入内存0x007c0位置。
    2. bootsect: 规划内核内存使用,将自己复制到合适的位置 (因为占用了0x007c0的位置,这部分是要给后边的程序用的),加载setup、system代码进入内存;跳到seup
    3. setup: 利用BIOS的中断服务程序从设备提取内核运行所需的机器系统数据,覆盖掉bootsect程序,关中断;将位于0x10000的内核程序复制到内存地址0x00000处(这里原来放着BIOS的中断向量表和BIOS数据区,在bootsect运行的时候还需要用到中断,所以bootsect一开始不能直接把内核载入到0x00000处),设置中断描述符表和全局描述符表;打开A20,实现32位寻址;建立保护模式下的中断机制,对8259A进行重编程;跳到head
    4. head:跟kernel编译在一起,执行本段代码的时候,会覆盖掉本段已经用过的代码(自己吃自己);让所有中断描述符指向ignore_int忽略中断;废除已有的GDT,在新位置重建GDT(原来的GDT是setup建立的,现在setup没用了,这段内存会被覆盖,其中的GDT也会被覆盖。);检验A20是否真正打开;检验数学协处理器;将L6和main地址压栈;建立内核的分页机制;打开分页;调用ret执行main
  2. 进程要想与块设备进行沟通,必须经过主机内存中的缓冲区。

  3. 对物理内存的规划
    除了内核代码和数据所占的内存外,其余物理内存主要分为3部分:
    1) 主内存区:进程代码运行的空间,也包括内核管理进程的数据结构
    2) 缓冲区:主机与外设进行数据交互的中转站
    3) 虚拟盘:可选区域,如果选择使用虚拟盘,就可以将外设上的数据先复制到虚拟盘区,然后加以使用,这样可以提高系统执行效率。

  4. 进程切换,用的是switch_to(n),切换之前会保存当前进程的所有状态,包括下一条指令,再次切换回来后会从先前状态继续执行,沿着自己的函数调用链依次进行或返回。

  5. 逻辑地址的形式是CS:DS。实模式下,CS中存放的是段基址,跟DS组成线性地址,线性地址直接映射到物理地址(也就是通过地址线的地址);保护模式下,CS中存放的是段选择子,段选择子会在段描述符表(GDT)中找到相应的段描述符,段描述符中存放的有段基址、段属性、段权限(用于保护)等信息,段描述符中的段基址跟DS组成线性地址,这里如果没有开启分页(只有保护模式才有分页机制),线性地址就直接映射到物理地址;如果开启了分页,线性地址会分成3部分,分别指示处理器找到页目录表、页表、页(页帧),再用线性地址的最后一部分找到页中的某个实际的物理位置。这么麻烦的使用段描述符,是为了保护;这么麻烦的使用分页,一是为了保护,二是为了实现虚拟内存。


新设计团队