翻译

Photon 也许能成为你最喜爱的容器操作系统

Posted on

Photon OS

Phonton OS 专注于容器,是一个非常出色的平台。 —— Jack Wallen

容器在当下的火热,并不是没有原因的。正如之前讨论的,容器可以使您轻松快捷地将新的服务与应用部署到您的网络上,而且并不耗费太多的系统资源。比起专用硬件和虚拟机,容器都是更加划算的,除此之外,他们更容易更新与重用。

更重要的是,容器喜欢 Linux(反之亦然)。不需要太多时间和麻烦,你就可以启动一台 Linux 服务器,运行Docker,然后部署容器。但是,哪种 Linux 发行版最适合部署容器呢?我们的选择很多。你可以使用标准的 Ubuntu 服务器平台(更容易安装 Docker 并部署容器)或者是更轻量级的发行版 —— 专门用于部署容器。

Photon 就是这样的一个发行版。这个特殊的版本是由 VMware 于 2005 年创建的,它包含了 Docker 的守护进程,并可与容器框架(如 Mesos 和 Kubernetes )一起使用。Photon 经过优化可与 VMware vSphere 协同工作,而且可用于裸机、Microsoft Azure、 Google Compute Engine、 Amazon Elastic Compute Cloud 或者 VirtualBox 等。

Photon 通过只安装 Docker 守护进程所必需的东西来保持它的轻量。而这样做的结果是,这个发行版的大小大约只有 300MB。但这足以让 Linux 的运行一切正常。除此之外,Photon 的主要特点还有:

  • 内核为性能而调整。
  • 内核根据内核自防护项目(KSPP)进行了加固。
  • 所有安装的软件包都根据加固的安全标识来构建。
  • 操作系统在信任验证后启动。
  • Photon 的管理进程可以管理防火墙、网络、软件包,和远程登录在 Photon 机器上的用户。
  • 支持持久卷。
  • Project Lightwave 整合。
  • 及时的安全补丁与更新。

Photon 可以通过 ISO 镜像OVAAmazon Machine ImageGoogle Compute Engine 镜像Azure VHD 安装使用。现在我将向您展示如何使用 ISO 镜像在 VirtualBox 上安装 Photon。整个安装过程大概需要五分钟,在最后您将有一台随时可以部署容器的虚拟机。

创建虚拟机

在部署第一台容器之前,您必须先创建一台虚拟机并安装 Photon。为此,打开 VirtualBox 并点击“新建”按钮。跟着创建虚拟机向导进行配置(根据您的容器将需要的用途,为 Photon 提供必要的资源)。在创建好虚拟机后,您所需要做的第一件事就是更改配置。选择新建的虚拟机(在 VirtualBox 主窗口的左侧面板中),然后单击“设置”。在弹出的窗口中,点击“网络”(在左侧的导航中)。

在“网络”窗口(图1)中,你需要在“连接”的下拉窗口中选择桥接。这可以确保您的 Photon 服务与您的网络相连。完成更改后,单击确定。

change settings

图 1: 更改 Photon 在 VirtualBox 中的网络设置。经许可使用

从左侧的导航选择您的 Photon 虚拟机,点击启动。系统会提示您去加载 ISO 镜像。当您完成之后,Photon 安装程序将会启动并提示您按回车后开始安装。安装过程基于 ncurses(没有 GUI),但它非常简单。

接下来(图2),系统会询问您是要最小化安装,完整安装还是安装 OSTree 服务器。我选择了完整安装。选择您所需要的任意选项,然后按回车继续。

installation type

图 2: 选择您的安装类型。经许可使用

在下一个窗口,选择您要安装 Photon 的磁盘。由于我们将其安装在虚拟机,因此只有一块磁盘会被列出(图3)。选择“自动”按下回车。然后安装程序会让您输入(并验证)管理员密码。在这之后镜像开始安装在您的磁盘上并在不到 5 分钟的时间内结束。

Photon

图 3: 选择安装 Photon 的硬盘。经许可使用

安装完成后,重启虚拟机并使用安装时创建的用户 root 和它的密码登录。一切就绪,你准备好开始工作了。

在开始使用 Docker 之前,您需要更新一下 Photon。Photon 使用 yum 软件包管理器,因此在以 root 用户登录后输入命令 yum update。如果有任何可用更新,则会询问您是否确认(图4)。

Updating

图 4: 更新 Photon。经许可使用

用法

正如我所说的,Photon 提供了部署容器甚至创建 Kubernetes 集群所需要的所有包。但是,在使用之前还要做一些事情。首先要启动 Docker 守护进程。为此,执行以下命令:

systemctl start docker
systemctl enable docker

现在我们需要创建一个标准用户,以便我们可以不用 root 去运行 docker 命令。为此,执行以下命令:

useradd -m USERNAME
passwd USERNAME

其中 “USERNAME” 是我们新增的用户的名称。

接下来,我们需要将这个新用户添加到 “docker” 组,执行命令:

usermod -a -G docker USERNAME

其中 “USERNAME” 是刚刚创建的用户的名称。

注销 root 用户并切换为新增的用户。现在,您已经可以不必使用 sudo 命令或者切换到 root 用户来使用 docker 命令了。从 Docker Hub 中取出一个镜像开始部署容器吧。

一个优秀的容器平台

在专注于容器方面,Photon 毫无疑问是一个出色的平台。请注意,Photon 是一个开源项目,因此没有任何付费支持。如果您对 Photon 有任何的问题,请移步 Photon 项目的 GitHub 下的 Issues,那里可以供您阅读相关问题,或者提交您的问题。如果您对 Photon 感兴趣,您也可以在该项目的官方 GitHub中找到源码。

尝试一下 Photon 吧,看看它是否能够使得 Docker 容器和 Kubernetes 集群的部署更加容易。

欲了解 Linux 的更多信息,可以通过学习 Linux 基金会和 edX 的免费课程,“Linux 入门”


via: https://www.linux.com/learn/intro-to-linux/2017/11/photon-could-be-your-new-favorite-container-os

作者:JACK WALLEN
译者:KeyLD (其实就是我啦~~)
校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

LinuxCN原文传送门

学习笔记

《真象》第四章 保护模式入门

Posted on

回过头来看以前写的代码居然有点看不懂了,还是应该写一下笔记啊……顺便吧第四章复习一下。
《真象》是指《操作系统真象还原》这是第四章笔记,保护模式入门。

  1. 保护模式起于80286,为16位的CPU,兴起于80386,32位的CPU。保护模式下一般都是32位及以上。
  2. CPU有三种模式:实模式,虚拟8086模式,保护模式。严格意义上32位CPU无实模式。
  3. 制定运行模式编译:使用法 [bits] 16/32
  4. 运行模式反转:指运行模式在16位实模式,操作数为32位;或者当前运行模式是32为保护模式,操作数为16位。操作系统兼容两种运行模式,所以支持两种操作,一般临时的反转会在指令前自动加上'66'或者'67'。(不过这实际上是编译器的工作,我们只需要知道就好了
  5. 在保护模式下,必须用ecx表示循环次数,其他都兼容。
  6. 对于段寄存器的入栈,无论指定在哪种模式下运行,都是按照当前模式的默认操作数大小压入的。而对于通用寄存器和内存,入栈是sp移动大小有操作数决定。
  7. 段描述符,8字节大小。保存着段基址和段界限和各种保护信息,用于描述一个段(代码段,栈段等),具体格式见 P151。
  8. 全局描述符表GDT,局部描述符表LDT。通俗的说,描述符表就是一个段描述符数组,所谓选择子就是数组下标。GDT是全局数组,LDT为局部数组。通过选择子在描述符表中找出段描述符,段描述符中又记录中段基址和段界限,即可得到一个段。
  9. GDT Register。全局表述符表保存在内存中,而GDT寄存器保存着这个表的位置与大小。寄存器大小为 6 字节。前2个字节为GDT界限,后4个字节为GDT内存其实地址。赋值格式 lgdt 48位数据。而 LDT Register的格式为 lldt 16位寄存器/16位内存
  10. 选择子结构。前2位用于表示选择子请求者的当前特权级别RPL,2位4个等级。第3位为类型标识,0为GDT,1为LDT。一个选择子初始化为全0。初始选择子会访问GDT的第一个段描述符,为了保护作用,GDT的第一个段空间不可用,当访问第一个段空间的时候会报错。
  11. A20Gate。在实模式下的wrap-around,当地址超过20位的时候就会发生回绕。保护模式下为了兼容实模式,有了A20Gate。A20Gate被禁止并访问20位以上时,会发生地址回绕,否则不会,也就在寻址上进入了保护模式。
  12. 保护模式的开关,CR0寄存器的PE位。将其置为1后,将在真正意义上进入保护模式。一般或1即可。
  13. 由实模式进入保护模式的三个步骤:
    1. 打开A20Gate
    2. gdtr寄存器中加载GDT的地址会界限
    3. 将CR0寄存器的pe位置1
  14. 流水线。将一个目标指令具体化为多个指令,在已知(或预测)指令的情况下,CPU并行处理多个指令,因此某段时间后的每一个时间周期都会执行完一个目标指令。当遇到jmp指令的时候,流水线会清空。
  15. 对流水线的优化有无先后逻辑的指令有乱序执行和缓存等。
  16. 在实模式进入保护模式之后,应优先清空流水线。因为实模式下的指令存储与保护模式不同,实模式会先把段基址左移后在加入缓存,这将与保护模式的指令格式不符,导致错误。
  17. 分支预测。最简单的分支预测算法是 2位预测法,用2位bit的计数器来记录跳转状态,没跳转一次加1,达到3则不再增加,否则减1,直到0则不再减了。优先计数大的,否则随机。
  18. 加载选择子时的保护:
    1. 验证段描述符是否越界。
    2. 检查段的类型。不同段的不同type允许被加载的寄存器不同。详细见P174。
    3. 检查段是否存在。CPU通过段描述符的P位来确认内存段是否存在。
  19. 代码段与数据段的保护。CPU每访问一个地址,都要确认该地址不能超过该内存段范围。确认方式为 (描述符中段界限+1) × (段界限的粒度大小:4k或者1) - 1,化简可得
    • 指令: EIP的偏移地址+指令长度-1 <= 实际界限大小
    • 数据: 偏移地址+数据长度-1 <= 实际界限大小
  20. 栈段并不一定都是向下扩展的。(这里有地方不是很明白,P175
宅技术

Linux下学习王爽老师的汇编语言

Posted on

王爽的汇编语言是基于windows下的dos和masm…………
这个让我很无语,因为我平时根本不会去用windows,而且在Linux下也没有masm……
masm与nasm区别开的m应该就是微软的意思。
更让人无语的是,两个编译器的语法还有很大差别……
因为编译器不同导致语法差别我还是第一次碰到……
不过汇编作为第一门语言,除了指令以外的伪指令有所不同还是可以理解的。

开始正文。

freedos

fochs官网有提供freedos的镜像,所以十分方便。
只要下载这个镜像,再用模拟器去模拟加载就可以了,在解压目录下运行bochs即可自动加载。

bochs镜像 传送门 找第一个就是freedos的镜像。

界面是这样的

之后的操作就完全跟书上的一样了…………
突然发现没办法用masm……这是最僵硬的…………

dosemu

没办法,用了dosemu。
dosemu的一个优点在于默认会在它的d盘,挂载你的用户根目录,这就免去修改镜像或者无从挂载的麻烦。

界面是这样的

比起bochs来速度很快,感觉bochs用来模拟dos有点牛刀小用了。
之后你只要去下载一个masm.exe,再先进入D盘符,再进入你的masm.exe的目录,就可以在dos里进行编译调试了。

最后写了一个 helle world,运行成功!!

真是不容易啊………………

学习笔记

《汇编语言》的一些小结

Posted on

今天汇编终于到了,马买皮,早知道这么慢就去京东上买了。

我为什么要学汇编

关于汇编,以前看《深入理解计算机系统》的时候就大致看了一蛤,当然是远远不够的,而且这本书我从大二看到现在也还没看完……
而我稍微浏览了一下我们这学期的教科书,跟那个煞笔老师一样让我感到很遗憾的是,书上也完全没有汇编。
要是真的学得这么浅的话,就不止是让人遗憾,更是让人觉得失望了。
是的,我已经很自然的认为学习操作系统就需要学并用到汇编,比如MBR还想用什么写,那是直接接触硬件的部分,除了汇编没有其他选择。emmmmm,据说汇编还有其他在debug等追根溯源上作为终极武器有很强的功能,据说啦,据说。不过看了一点之后发现汇编和操作系统在一些部分是很通用的。
另外,在《操作系统真象还原》其实最基础的假设就是读者都已经学了汇编。
顺便吐槽一句,王爽的《汇编语言》居然平台居然是Windows的,醉了。

正文

2017-10-28 00:37:44 星期六

  1. 对于一串01字符串,既可以表示命令,也可以表示数据,CPU区分的方式是通过寄存器分割开来。比如说固定让这个寄存器访问命令,让那个寄存器访问数据。
  2. 8086通过“段+偏移”的寻址方式,而使段只偏移4位的结果有二,一是CPU可以通过不同的“段+偏移”来形成同一个物理地址。二是若仅改变偏移地址,最多寻址64KB个内存单元。
  3. 8086有4个段寄存器 CS,DS,SS,ES。
  4. 8086 CPU的第一条指令固定是 FFFFH:0000H
  5. cs:ip 只能通过 jmp命令更改,单独更改 cs 段寄存器则不需要。
  6. CPU与内存交互主要通过 DS 段存储器,ds存储内存单元的段地址,再通过 [偏移地址] 访问具体内存单元。
  7. 8086 不支持直接将数据送入段存储器的操作。
  8. 1字 = 2字节。字节类型数据在8086中需要用到两个内存单元存储,高地址单元存放高8位,低地址单元存放低8位
  9. 任意时刻,SS : SP只想栈顶元素。8086 CPU只知道栈顶元素在哪,而无法知道我们安排的栈有多大。
  10. 8086 CPU在入栈时,栈顶从高地址向低地址方向增长。入栈顺序是先移动栈顶指针,再存储数据。出栈则反之。
  11. 段。段简单来说就是一大部分连续的内存单元。但是这个内存单元的起始位置必须是16的倍数。原因在于8086的寻址方式。根据在段中存放的内容,可分为“数据段”,“代码段”,“栈段”。

顺便附上我写的第一个汇编程序

assume cs:codesg

codesg segment

    mv ax,2
    add ax,ax
    add ax,ax

    mov ax,4c00h
    int 21h

codesg ends

end

虽然连结果都打印不出来,但还是挺兴奋的。

2017-10-30 11:40:50 星期一

终于可以做书上的实验了!!!
总结一下 DOS 中debug的命令。

  • r : 查看,修改CPU寄存器的内容
  • d : 查看内存的内容
  • e : 修改内存的内容
  • u : 将内存中的内容解释为机器指令和对应的汇编指令
  • t : 执行当前指令,当前指令有cs:ip 所指向,可通过改变这两个寄存器改变指令
  • a : 以汇编形式写入内存
  • p : 执行中断指令

2017-10-30 22:45:57 星期一

  1. 修改了SS寄存器后,会紧接着执行下一条指令
  2. shell——操作系统的外壳。对于一个可执行程序,都是由shell执行,将该程序加载到内存,设置CS:IP 指向程序的入口,此后程序运行,运行结束后,返回到shell。最后shell再去执行其他程序
  3. dos运行程序时,都需要利用PSP(Program Segment Prefix)来和被加载程序进行通信。PSP占据256(100H)个字节。所以程序的物理地址为 SA+10H:0
  4. SA(Segment Adress),段地址。EA(Effective Adress),偏移(有效)地址。
  5. 通常使用loop指令来实现循环功能,cx中存放循环次数。
  6. 在汇编程序中,数据不能以字母开头。比如A000H,就必须写成0A000H。
  7. 在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用“[...]”来表示内存单元。但如果“[]”内是数值常量,就必须在前面加上段前缀。所谓段前缀就是 “段寄存器:” 的形式。
  8. dos 方式下,一般情况下,0:200 ~ 0:2ff 这段空间是安全的。
  9. end 除了通知编译程序以外,还可以通知编译器程序的入口在哪。
  10. 取得程序内存的两种方法:在程序加载时系统自动为其分配,或者在程序运行时动态申请。
  11. 这里再次强调。伪指令不会被编译器所执行,只能作为程序员理清代码使用。比如最开始的assume,不能确保段的功能与assume相对应。
  12. 用'...'的形式指明数据是由字符形式给出的。编译器会将其转化成对应的ASCII码。
  13. 内存单元中的偏移地址表达形式有:(其中,idata表示数值常量)
    1. [ax+bx+idata]
    2. idata[ax][bx]
    3. [ax].idata[bx]
    4. [ax][bx].idata

至此,全书已看完一半了……

第八章 数据处理的两个基本问题

  1. 8086 CPU中,只有bx,si,di,dp四个寄存器才能用于内存寻址。并且,对于两个两个寄存器两两组合的情况,只有bx,bp 与 si,di 分别两两组合形成的4种组合方案。
  2. 内存寻址方式在 本书 P164 中有很好的总结,这里不再赘述。
  3. 指定处理的数据长度的方法有二:
    1. 通过寄存器名指明数据的尺寸。 因为位数不同的寄存器无法相互操作。
    2. 通过 操作符 X ptr 的方式指定。其中 byte ptr 指的是一个字节单位;word ptr指的是一个字单位; dword ptr 指的是一个双字单位。
  4. 在C语言中,我们经常可以看到,如:dec.cp[i]代表的是名为 dec的结构体中首地址为cp的变量中的第i个单元。而我们在汇编中做法存在bx.10h[si]。仔细一想的话,C语言中包含了很多与汇编相通的语法。只不过包了一层外衣。
  5. div指令。语法是div 除数(reg/内存单元),而我们的被除数一般优先放在 ax 中,其次放在 dx中。其商也是优先放在 ax 中,其次放在 dx 中。
  6. 伪指令 db,dw,dd。以下是三个伪指令的缩写,看了英文就会明白它的意思。
    • db: define byte
    • dw: define word
    • dd: define dword
  7. 指令操作符 dup。表示重复定义元素。语法为 db/dw/dd n dup (元素) 其中n为重复次数。在()为初值。比如 db 4 dup (0) 等同于 db 0,0,0,0

第九章 指令转移的原理

  1. 指令操作符 offset 。 offset 是偏移的意思。它的指令含义是获得标号的偏移地址。语法:offset 标号
  2. 指令操作符 nop 。 申请一个什么都没有的字节单位的机器码。
  3. cpu在执行jmp指令的时候无须知道目标地址。jmp指令只考虑与当前指令地址的偏移量。、
  4. jmp 语法小节:
    • jmp short 标号,偏移量在有符号字节单元内的段内转移。范围即[-128,127]
    • jmp near ptr 标号,偏移量在有符号字单元内的段内转移。范围即[-32768,32767]
    • jmp far ptr 标号,段间转移。用标号处的SA,EA来修改CS,IP。
    • jmp 16位 reg,用reg中的值来改变 ip 。
    • jmp word ptr 内存单元,段内转移。内存单元中存的是EA。
    • jmp dword ptr 内存单元,段间转移。内存单元中高位字存SA,低位字存EA。
  5. jcxz 标号。含义为 “jmp if cx equal to zero”。等同于C语句if((cx)==0) jmp short 标号;
  6. 补充,loop本质上也是有jmp指令而来,loop指令跳转的也是短转移。即其jmp范围为[-128,127]

第十章 call与ret指令

  1. ret 指令: 等同于 pop ip
  2. retf 指令: 等同于 pop ip pop cs指令。
  3. call 指令: 指令将进行两步操作,先将当前 ip 或者是 cs和ip 压入栈中,再是转移。其语法有
    1. call 标号:段内转移,等同于
      1. push ip
      2. jmp near ptr 标号
    2. call far ptr 标号:段间转移,等同于
      1. push cs
      2. push ip
      3. jmp far ptr 标号
    3. call 16位reg:段内转移,用寄存器中的数据进行转移,等同于
      1. push ip
      2. jmp 16位寄存器
    4. call word ptr 内存单元:段内转移,用内存数据进行转移,等同于
      1. push ip
      2. jmp word ptr 内存单元地址
    5. call dword ptr 内存单元:段间转移,用内存数据进行转移,等同于
      1. push cs
      2. push ip
      3. jmp dword ptr 内存单元
  4. 仔细观察一下,将call与ret结合起来使用就是一个函数的功能。
  5. 批量参数数据的传递可以通过申请内存并保存首地址的方式来实现。
  6. 指令操作符 mul:两个相乘的数,要么是8位,要么是16位,默认都放在ax寄存器中。另一个数字放在其他16位寄存器或者内存单元中。而其结果优先放在 ax 中,其次放在 dx 中。
  7. 寄存器是有限的,在使用call之后应首先把当前使用的寄存器的值预先放在栈中,在ret之前把栈的数据还原。

第十一章 标志寄存器

标志寄存器是8086的最后一个寄存器。也称为flag寄存器。flag寄存器是按位起作用的。其有效位如下

1514131211109876543210
OFDFIFTFSFZFAFPFCF

从低位到高位其各自英文全称与作用如下

  • CF: Carry Flag 。进位标志。在无符号计算时如果存在进位则为 1 。否则为 0 。
  • PF: Parity Flag 。奇偶标志。指令计算结果为偶数时为 1, 否则为 0 。
  • AF: Auxiliar Carry Flag。辅助进位标志。 意义不明。
  • ZF: Zero Flag。零标志。 指令结果为 0 则为 1。否则为 0。
  • SF: Sigh Flag。符号标志。 指令结果为 负数 则为 1。否则为 0 。
  • TF: Trace Flag。轨迹标志。 当TF为 1 时,执行完当前指令后将引起单步中断。
  • IF: Interrupt Flag。中断标志。 功能与 TF 一致。
  • DF: Direction Flag。方向标志。 DF为0时,每次指令操作后si,di递增,否则递减。
  • OF: Overflow Flag。溢出标志。在有符号计算时如果发生溢出,则为 1 ,否则为 0。

其他:

  1. adc 指令。带进位加法指令。adc ax,bx等同于 (ax)=(ax)+(bx)+CF,一般用于大数操作。
  2. sbb 指令。带借位减法指令。与adc指令类似。
  3. cmp 指令。cmp指令的功能类似于 减法指令。cmp指令虽然不会保存结果,但是会影响flag寄存器。通过flag寄存器的值,来判断两个数之间的大小关系。
  4. 以ax,bx为例,以下为无符号数大小比较之法:
    • $ ZF = 1 \rightarrow (ax) = (bx) $
    • $ ZF = 0 \rightarrow (ax) \neq (bx) $
    • $ CF = 1 \rightarrow (ax) < (bx) $
    • $ CF = 0 \rightarrow (ax) \geq (bx) $
    • $ CF = 0 \&\& ZF = 0 \rightarrow (ax) > (bx) $
    • $ CF = 1 \&\& ZF = 1 \rightarrow (ax) \leq (bx) $
  5. 以ax,bx为例,以下为有符号数大小比较之法:
    • $ ZF = 1 \rightarrow (ax) = (bx) $
    • $ ZF = 0 \rightarrow (ax) \neq (bx) $
    • $ SF ^ OF = 1 \rightarrow (ax) < (bx) $
    • $ SF = 0 \&\& OF = 0 \rightarrow (ax) \geq (bx) $
    • $ SF = 1 \&\& OF = 1 \rightarrow (ax) > (bx) $
  6. pushf和popf。将flag寄存器压入栈中或者从栈中弹出数据存到flag寄存器中。
  7. movsb / movsw 指令。将 ds:si 指向的内存单元的数据(字节/字)送入 es :di中,然后根据flag寄存器中的df位的值,将si,di递增或递减(1位/2位)。
  8. cld 指令将flag寄存器中的df位置变为 0,std指令则为置1 。
  9. 基于flag寄存器的条件转移,见表1。
  10. dos的debug中对于flag寄存器的表示,见表2。

表1

指令含义检测的相关标志位
je等于则转移$ ZF = 1 $
jne不等于则转移$ ZF = 0 $
jb低于则转移$CF =1 $
jnb不低于则转移$ CF = 0 $
ja高于则转移$ CF = 0 \&\& ZF = 0 $
jna不高于则转移$ CF = 1 || ZF = 1 $

表2

标志值为 1 的标记值为 0 的标志
OFOVNV
SFNGPL
ZFZRNZ
PFPEPO
CFCYNC
DFDNUP

第十二章 内中断

  1. 内存段有4种产生方式
    • 除法错误。中断类型码: 0
    • 单步执行。中断类型码: 1
    • 执行 into 指令。中断类型码: 4
    • 执行 int 指令。中断类型码: 其他
学习笔记

《操作系统真象还原》的一些笔记

Posted on

终于有时间开始搞我的OS Demo了。
希望能在元旦之前搞完。
这里不定时更新记录我在《操作系统真象还原》的一些笔记。以此督促和勉励自己。
很早之前就想看这本书了,之前在一个腾讯员工的书单上看到的,虽然貌似没什么名气,但看了一部分觉得还是挺有趣的,是我能非常乐意看下去的类型。那种带着程序员的幽默而不乏真实技术的书籍。

2017-10-24 23:50:14 星期二

  1. 编译器提供库函数,库函数封装了系统调用,这样的代码集合称之为运行库。
  2. 用户进程永远不会因为进入了内核态而变身为操作系统。
  3. cs: ip。 汇编指令,表示当前执行的指令。cs是代码段寄存器,ip是指令指针寄存器,指令指针计算为 $ cs \times 16 + ip $
  4. DRAM,动态随机访问内存。物理内存,也就是内存条就属于DRAM。其动态并不是内容变化的意思,而是保存时间短,需要顶起的刷新。
  5. 地址总线宽度决定可以访问的内存空间容量。书中所说是决定内存空间大小,我觉得应该是容量,或者说是上限,更为合适。yy 总线先分配外设地址,最后才将其余可用地址分配给DRAM。所以说,内存空间存在一个上限。
  6. 在8086中,内存空间中最前面的 1KB 地址为中断向量表。可以通过使用 int 中断号来实现相关的系统调用。
  7. 魔数。约定的含有具体意义但不说明的数字。在Linux中就是通过魔数来辨别文件系统。
  8. 尽管所有语言都会被转化成机器码,但中间存在效率,也许一个意思,Java语句有100条机器码,而汇编只有10条不到。
  9. 编译器的自我进化真的是非常神奇。内容太多,以后有机会可以再读p16。

2017-10-26 00:46:57 星期四

  1. CHS方式中扇区的编号是从1开始的
  2. 判别是否是主引导记录mbr的方式为文件末尾的两个魔数0x55和0xaa
  3. mbr固定会被加载到内存的 0x7c00 地址
  4. \$ 是编译器给当前行安排的地址,\$\$则是本section的起始地址
  5. section 只是用于给程序员在逻辑上规划代码用的,并没有什么实质意义
  6. 编译器有一个重要的工作是给程序中各符号编址。很重要,符号包括数据类型,数据。编译器只负责编址,它只会将数据相对于文件开头的偏移量作为该数据的地址。
  7. vstart。虚拟起始地址。告知编译器在当前section之后的所有数据都编在这个vstart地址之后。用vstart的时机是我预先知道我的程序将被加载在某处。
  8. 数据的内存地址与文件地址应严格区分开来。数据的文件地址不会受到vstart影响。它的地址绝大多数是连续的。目前的个人理解,可能有错误

2017-10-27 00:09:24 星期五

麻辣格鸡的好难

  1. 实模式。是指CPU的寻址方式,寄存器大小,指令用法等,用来反应CPU在该环境下如何工作的概念。
  2. CPU唯一的任务就是执行指令。
  3. CPU大体分为三个部分,控制单元,运算单元,存储单元。
  4. 控制单元大致由指令寄存器,指令译码器,操作控制器组成。功能很好理解,先存储指令,再解码指令,最后进行操作,对其他CPU部分开始控制。
  5. 很多地方都用到了缓存,浏览器的访问就是一个简单例子。
  6. 寄存器之所以快,是由于它有触发器实现。已经完全忘了
  7. CPU的寄存器可分为对程序员不可见与可见。比如一些固定数据的存储,肯定是不能让程序员访问的。
  8. 实模式下,默认用到的寄存器都是16位宽。
  9. flags寄存器是计算机的窗口,展示了CPU内部的各项设置,指标。并不知道怎么用
  10. 八个通用寄存器 AX BX CX DX SI DI BP SP,其中前四个又可各自划分为两个部分。比如对于 AX来说,可以划分为 高位(High) AH ,低位(Low) AL。也可扩展(Extend) 为 EAX
  11. 是历史上第一款 x86 CPU,在8086之前都是不存在段的概念,直接用硬编码访问内存。
  12. 8086通过“段+偏移”来实现20位寻址,16 + 16 - 20,多出的 12 位不用管它,会自动进行取模运算。

2017-10-28 01:00:32 星期六

先学汇编去了 传送门

心情随笔

退役了……

Posted on

2017年10月22号第三届CCPC哈尔滨站,第二次代表学校出去比赛并且第二次打铁……

即便是现在了,我依然是心如刀割……

许颂嘉卡题卡了4个半小时,说句心里话,我无法完全掐灭她的失常发挥间接导致比赛打崩的想法。
但理性的我还是很清楚,这也并不能完全怪她

因为我对数学不是很敏感,没想到素因子,如果我能对了解一点数学就好了,尽管只是一点……
因为我们的团队配合有问题,我一直在怼我的题,要是我能抽出点时间一直帮许颂嘉解决这个问题就好了……
因为比赛前我并没有把许颂嘉的一些问题指出来,她写代码会写的很长很乱我在很早之前就知道了,但是当初都A了再加上她自己手速挺快我也没有说什么,现场赛让我帮她看代码就真的很致命……
因为没有正确发挥陈雨情的作用,老实说,陈雨情虽然菜,特么的经常读错题让我白写,但是在考虑问题方面往往比我们两个都全面。这场也是的……打到一半才让她看……
因为我太菜了,要是我能先比她出一个题,不仅可以鼓舞士气,说不定手速快点还能拿银牌……但是在后期我急了,应该说是很焦躁,导致最后一题很明显的随机化解法我都没有想到,只是给了它三分钟时间,就说了一句,“好难啊”就继续帮许颂嘉想了……
因为我们没有把题目全看完……一些我可以写的题目我没有去看……

现在来看,已经知道难易系数的我重新再来写这套题的话,至少四题的可能性蛮高的。
当然素数筛我需要模板……

然而就结局而言这跟我想得差距真的是太大太大,尤其是当我看到局势几乎已定的时候,当许颂嘉还在边怼题边叫我们想其他题目来挽回局势的时候,那种无力感真的让我整颗心都在滴血。
回想起最初暑假集训开始,我跟她们说我的目标是能拿银牌……我跟她们说失去女队的代价我一定会尽全力补上……许颂嘉也说已经拿到铜了,想拿银,至少不想打铁…………………………………………………………………………

好烦…………好累………………好痛苦……………………

吕倩学姐倒是安慰许颂嘉说牌不重要,重要的还是自己的学到什么。
这句话真好听啊。
话是这么说,但是我没有给我自己一个交代,没有给我队友一个交代……
只有我自己最清楚我自己的学习过程,在别人眼里,我就是这两届ACMer里的最后的百分之四十。

不过或许我可以用其他单挑的方式去证明我自己,比如CF红名??这倒是可以,但我也已经没有太多精力拿去刷题了。甚至周赛都已经不是很想去了……

如果只是关于退役感想的话,已经没有其他想说的了……硬要说的话,其实还有几个

  • 学那么多高级算法有什么用,还不如多打CF,多写几道思维题
  • 团队配合真的真的真的很重要

当然,我不会忘记这份痛楚。

最后先心疼一波金汶,再由衷的祝福薛昊他们能拿到好成绩。
毕竟薛昊的努力我也是一直都看在眼里。

The End

思维

UVALive 7505 Hungry Game of Ants

Posted on

老实说,这题我在现场赛大概写不出来。

感觉还是比较需要脑洞。

题意:
一排不同重量的蚂蚁按重量排成一排玩饥饿游戏,每只蚂蚁最开始选择一个方向,左或者右,大蚂蚁吃小蚂蚁,并且使自己增加小蚂蚁的重量,如果重量相同,左吃右。
问 n 只蚂蚁,第 k 只活到最后的方案数。

思路:

直接切入正解。
首先第k只蚂蚁必须选择左边,不然只能被吃。( $n \neq k $ )
如果想要第 k 只蚂蚁获胜,必须满足以下两个条件

  1. $ weight[ max(p_1), k ] > weight[ 0, max(p_1) ) $
  2. $ weight[ 1, min(p_2) ] <= weight( min(p_2), n ] $

其中,$ max(p_1) $ 是指,满足第一条不等式的最大的 $p_1$
同理,$ max(p_2) $ 是指,满足第二条不等式的最小的 $p_2$

而其中,只要满足第一条不等式,则 $ [1, max(p_1)) $ 的方向任意,所以数量为 $ 2^{max(p_1)-1} $ 。同时这也是 k 只蚂蚁 k 胜出 的方案数。

下面有一个关键地方,就是如何通过 k 只蚂蚁 k 胜出来推出 n 只蚂蚁 k 胜出。
让第 n-1 选择左边,方案数减少一半,但是第 n 只选择任意,方案数增加一倍。故方案数不变。
以此类推。得 $ dp[n] = \sum_{i=w}^{n-1} dp[i]$ 其中 $(w = min(sum[w] \geq sum[n] - sum[w] ) ) $
这个地方只要求一下后缀和就好了。

AC Code

#include <bits/stdc++.h>

#define each(i, n) for (int(i) = 0; (i) < (n); (i)++)
#define reach(i, n) for (int(i) = n - 1; (i) >= 0; (i)--)
#define range(i, st, en) for (int(i) = (st); (i) <= (en); (i)++)
#define rrange(i, st, en) for (int(i) = (en); (i) >= (st); (i)--)

using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int mod = 1e9 + 7;
const int maxn = 1e6 + 5;

ll sum[maxn], dp[maxn], texp[maxn], tot[maxn];
int bit[maxn];

void init()
{
    texp[0] = 1;
    for (int i = 1; i < maxn; i++) {
        sum[i] = sum[i - 1] + i;
        texp[i] = texp[i - 1] * 2 % mod;
        bit[i] = lower_bound(sum, sum + i + 1, (sum[i] + 1) / 2) - sum;
    }
}

int main()
{
    int n, k, T;
    init();
    scanf("%d", &T);
    for (int cas = 1; cas <= T; cas++) {
        scanf("%d%d", &n, &k);
        if (n == 1 && k == 1) {
            puts("2");
            continue;
        }
        ll s = k;
        memset(dp, 0, sizeof(dp));
        for (int i = k - 1; i >= 1; i--) {
            if (s > sum[i]) {
                tot[k] = dp[k] = texp[i + 1];
                break;
            }
            s += i;
        }
        for (int i = k + 1; i <= n; i++) {
            int tmp = bit[i];
            if (tmp - 1 >= k)
                dp[i] = ((tot[i - 1] - tot[tmp - 1]) % mod + mod) % mod;
            else
                dp[i] = tot[i - 1];
            tot[i] = (tot[i - 1] + dp[i]) % mod;
        }
        printf("Case #%d: %lld\n", cas, dp[n]);
    }
    return 0;
}