csapp第十章 系统级IO

​ 还记得在第一章节总体介绍计算机系统时,就有一个说法,说操作系统实现了一种抽象,即在linux中,所有的IO设备都是文件

​ shell在每个进程的开始都打开三个文件:标准输入,标准输出,标准错误

​ 元数据:记录文件的相关数据

​ 目录:每个目录都是一个结构

​ 共享文件:

​ 标识符表

​ 每个进程都有一个标识符表

​ 每个表项都指向文件表中的一个表项

​ 文件表
​ 所有进程共享一文件表

​ 每个表项目由文件位置,引用计数和指向V-node表的表项的指针构成

​ v-node表

​ 所有进程共享

​ 这个才是真正的描述唯一的磁盘文件

csapp第九章 虚拟存储器

为了有效管理存储器并少出错,现代操作系统提供了对主存的抽象概念,叫做虚拟存储器

需要知道:

  • 虚拟存储器是硬件异常,硬件地址翻译,主存和磁盘文件,内核软件的完美交互
  • 为每一个进程提供一个大的,一致的和私有的地址空间
  • 将主存作为磁盘地址空间的高速缓存
  • 保护每个进程的地址空间不被其他进程破坏

虚拟存储器遍布在计算机系统所有层次,硬件异常,汇编器,链接器,加载器,共享对象,文件和进程中扮演重要角色

虚拟存储器是危险的:

引用变量,间接引用指针,调用malloc动态分配程序,就会和虚拟存储器交互

如果使用不当,将遇到危险复杂的与存储器有关的错误:段错误,保护错误

物理寻址:

计算机主存被组成为m个连续的字节大小的单元数组,每个字节地址叫做物理地址;

cpu访问存储器最自然方式是使用物理地址,该方式成为物理寻址

虚拟寻址

cpu生成一个虚拟地址,来访问主存

地址翻译

将虚拟地址转为物理地址就叫做地址翻译

地址翻译需要cpu和操作系统之间的合作

​ 主要利用储存在主存中的查询表来动态翻译虚拟地址

​ 查询表则由操作系统进行管理

地址空间

地址空间就是一个非负整数地址的有序集合

如果地址空间中整数连续,则成为线性地址空间

一个地址空间大小由表示最大地址需要的位数来描述

虚拟地址空间就是在一个带虚拟存储器的系统中,cpu从一个有N=2^n个地址的地址空间中生成虚拟地址,这个地址空间成为虚拟地址空间

那当然也有物理地址空间,与系统中物理存储器的M=2^m个字节对应

csapp第八章 异常控制流(2)

进程控制

fork
创建一个子进程,除了返回的PID(进程ID)不同,其余全部相同
两者互不影响,并发执行
fork返回两次,一次返回到父进程,一次返回到子进程
父进程中返回子进程的PID(不为0)
子进程返回0
通过这个来判断父子进程
waitpid
回收子进程
当子进程结束时,保持终止状态知道父进程回收,此时子进程任然占据内存

第一个参数大于零,则等待ID为第一个参数的子进程结束
若为-1,这等待任意进程结束
如果没有子进程,则返回-1
execve
在当前进程中加载一个新的程序,覆盖当前进程的地址空间,但没有创建一个新的进程

signal

当子进程完成后,会发送一个信号给父进程让他来回收自己
内核对不同的信号有唯一的编码
信号传递的整个过程分两个部分
第一个部分为信号发送,由内核发送给进程
当一个进程正在使用信号处理程序处理某一类型的型号时,同种信号不会被接受,直接被丢弃
实际上第二个同种信号不会被丢弃,它被记录到pending组中,而之后的同种信号会被丢弃
原因是pending组实际上是在接收到某一信号后,将对应的位设置为一,所以当存在pending时,我们只能确认有多的信号,而不能确认有几个
上面说的pending是系统默认的一种信号block形式,当然我们可以显式block各种信号
当进程处理完一个信号处理程序后,会查看pending组中待处理的信号,从最小的信号开始处理,知道没有待处理信号,然后会执行下一条程序
安全的信号处理程序
阻塞所有的信号
处理程序和主程序共享全局变量
用volatile声明全局变量
竞争
不能确定子进程和父进程谁先执行导致了竞争的出现
难以稳定浮现错误
具体见CSAPP第8.5.6节例子
sigsuspend函数
格式:int sigsuspend(const sigset_t *mask)
暂时取消所有block,然后挂起该进程,知道收到一个信号mask中是我们不想被收到的信号

csapp第八章 异常控制流

异常控制流

​ 要理解异常控制流,需要先知道控制流.

我们知道,计算机从开机启动开始,会有一个特殊的寄存器叫程序计数器一直指向我们的程序运行位置,我们假设程序计数器的值为一个序列,a0,a1,a2….an,那么每一次ak的值跳转到a k+1的过渡就成为控制转移,然后这种控制转移序列叫做处理器的控制流(control flow),但是作为现代操作系统,仅仅拥有这种控制流是远远不够的,必须具有对系统状态变化做出反应的能力,而这些系统状态突变也就对应着我们的异常控制流(ECF)

异常

熟悉java语言的人应该会知道java的异常处理机制,那么这里的异常和我们操作系统底层的异常有何联系和区别呢?
​ 个人看法:程序设计语言的异常处理机制,应该是封装了的在应用层级别的异常控制流.

​ 这里,异常定义为:控制流中的突变,来响应处理器状态中的某些变化,是异常控制流的一种形式,一部分由硬件实现,一部分由操作系统实现.

​ 在一些情况下,比如发生缺页中断,算术除0,等操作时,处理器会通过一张叫异常表的跳转表,进行一个间接过程调用,到一个专门处理这种事件的操作系统子程序(异常处理程序)中去进行处理.

异常的类别

  1. 中断 interrupt
  2. 陷阱(陷入) trap
  3. 故障 fault
  4. 终止 abort

首先,中断是异步发生,何为异步,是指由于处理器外部的io设备产生,但是交由处理器处理,还得通过总线传输等过程,而同步是指执行一条指令,立刻产生效果(异常),由于中断是为了响应IO请求,故而为异步中断.

对于陷阱,我们知道,操作系统将进程分为内核态和用户态,显然,内核态的进程对于资源的控制(处理器,内存,IO)都要多于在用户态的进程,但是用户态的进程要是需要使用操作系统内核的功能,又或是需要调用一些操作系统提供的服务,那这个时候便需要陷阱,陷阱的作用就相当于在用户程序和内核之间提供了一个接口,这个接口叫做系统调用.

故障显然和上面描述的异常不一样,上面的是程序正确运行多必须的,而故障却是由一些情况产生的,最典型的就是内存中的缺页异常,注意:故障是可以被修复的,还是这个例子,我们需要的页没在内存中,导致故障,处理器将控制权交给异常处理中的缺页处理程序,该过程后返回到故障程序,此时已经不会发生故障,可以完成运行.

终止则是因为发生了致命错误,导致无法恢复,无计可施了只能终止程序运行.

进程

进程的概念可以说是运行中程序的实例,其实没必要理解的如此复杂,仅仅知道他就是运行中的程序即可,进程的概念虽然简单,但是影响却不小,基于进程的概念我们可以解释很多计算机科学中的其他概念

我们在现代操作系统上运行程序时是不是会有这样一种体验,假如我运行qq同时还在音乐,感觉我的qq和音乐程序一直都在运行,这边是操作系统给我们提供的一种假象,在多道程序设计中,cpu以很短的时间在进程间切换,这个时间对于人类来说,根本就察觉不出来,故而有这样一种假象,但其实在任一时刻,cpu只处理一个程序中的指令(忽略多核并行),然后我们可以引入我们并发流的概念

并发流

通过上面的介绍,我们知道了计算机有着很多的逻辑流,而如果一个逻辑流在时间上和另一个逻辑流重叠,则称为并发流,多个流并发执行的现象叫做并发.

并发流思想和处理器核心数无关,不要和并行弄混淆,并行需要多核才可以.如果两个流在时间上重叠了,那么他们就是并发的,即使运行在一个处理器核心上,并行流是并发流的一个真子集,两个流并发的运行在不同的处理器核心上,那我们称为并行流,具体参考操作系统关于对称多处理和集群的概念.

csapp第七章 链接(1)

链接

​ 将各种代码和数据收集和组合成为一个单一文件的过程

链接的好处是:分离编译 我们可以不用把应用程序组织为一个巨大的源文件,而是将它分解成更小,更好管理的模块,独立修改和编译

静态链接

为了构成可执行文件:链接器必须完成:符号解析 重定位两个任务

符号解析:目标文件定义和引用符号。

​ 每个符号可以对应c语言中的函数,全局变量,静态变量。符号解析的作用就是将这些符号引用和符号定义关联起来

重定位:通过之前的学习了解到:我们的代码和数据会存储在以0地址开始的区域,但 是多个可重定位的文件都是以这样的方式,我们怎么样将他们组合呢?

链接器可以通过把每个符号与一个内存关联,从而实现重定位

目标文件

​ 1可重定位目标文件: 包含二进制代码和数据,与其他可重定位目标文件合并成可执行文件

​ 2可执行目标文件: 包含二进制代码和数据,可以直接被复制到内存中执行

​ 3共享目标文件 : 一种特殊的可重定位目标文件 可以在程序加载运行时被动态加载入内存

我们熟悉的编译 汇编 链接过程中,前两个过程会生成可重定向目标文件(包括3)链接器会生成可执行目标文件

解释下目标文件的意思:一个以特定文件格式的形式存放在硬盘上的一个字节序列(目标模块)a.out a.exe 等等

记录下Ubuntu的美化和使用

Ubuntu的配置和美化,话不多说,先上一张图

rutu

配置的话,主要就是几个自己常用软件的安装,包括shadows-qt5,wps,sublime text,intellij idea,pycharm,clion,网易云音乐,tim,wechat大部分都可以官网下载安装,少数几个记录下来

~/.bashrc文件配置

在末尾加上

export JAVA_HOME=/usr/java/jdk-10.0.2
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH

这里主要是配置jdk

/etc/profile文件配置

在末尾加上

CATALINA_HOME=//home/dw/apache-tomcat-7.0.91
export CATALINA_HOME

这里主要配置tomcat

shadowsocks-qt5

1 在ubuntu18.04之前的版本下载ss-qt5的步骤是:

1
2
3
4
5
sudo add-apt-repository ppa:hzwhuang/ss-qt5

sudo apt-get update

sudo apt-get install shadowsocks-qt5

2.但是ppa:hzwhuang/ss-qt5 并没有18.04版本的源,所以再执行update会报错。

​ 这时,只要编辑/etc/apt/sources.list.d/hzwhuang-ubuntu-ss-qt5-bionic.list 文件,将bionic (18.04版本代号)改成xenial(16.04版本代号)。

​ 然后再执行

​ sudo apt-get update

​ 成功之后就是

​ sudo apt-get install shadowsocks-qt5

​ 完成。

至于在google chrome中配置switch omega可以很容易百度到,这里就不在阐述。

intellij idea

​ 主要讲解下破解,ctrl alt t打开终端,vim编辑/etc/hosts 文件 将0.0.0.0 account.jetbrains.com添加入hosts 然后进入 http://idea.lanyus.com/ 获取注册码即可

Apache tomcat

这里我下载的是tomcat 7

这里有几点要注意的地方,至少在我这台机子上是这样 首先要编辑bin目录下的catalina.sh文件,在文件开头加入

CATALINA_HOME=tomcat目录
JAVA_HOME=jdk目录

有可能会在idea出现tomcat权限不够而无法将tomcat集成到项目中这时候就需要

sudo chmod -R 777 tomcat的目录

tim

这里推荐一片文章,是deepin linux移植过来的wine tim

https://www.lulinux.com/archives/1319

1,安装deepin-wine环境:https://gitee.com/wszqkzqk/deepin-wine-for-ubuntu页面下载zip包(或用git方式克隆),解压到本地文件夹,在文件夹中打开终端,输入sudo sh ./install.sh一键安装。

2,安装deepin.com应用容器:http://mirrors.aliyun.com/deepin/pool/non-free/d/中下载想要的容器,点击deb安装即可。以下为推荐容器:

3,Ubuntu 18.04 Gnome桌面显示传统托盘图标:安装TopIconPlus的gnome-shell扩展,命令:sudo apt-get install gnome-shell-extension-top-icons-plus gnome-tweaks,然后用gnome-tweaks开启这个扩展。

关于这个第三步讲解一下,gnome是18.04的桌面环境 如果需要美化则需要gnome-tweak-tool这个工具,可以apt install安装,但是里面的拓展很少,我把我电脑里的拓展截图下来

image

当你安装好tweak tool,再安装上述第三个工具会出现很多拓展,至于每个拓展有什么用,自己去试吧

上述有一个dash to dock的可以这样安装

​ 1 安装git: sudo apt-get install git

​ 2 sudo git clone https://github.com/micheleg/dash-to-dock.git

​ 3 cd dash to dock

​ 4 sudo make

​ 5 make install

因为git下来的是源代码,你需要手动make才可以使用,上述完成之后,alt+f2 输入r 重启桌面 ,打开tweak tool即可

电脑右上角有个显示网速的工具叫系统负载查看器 软件商店里有 安装后自己配置

首选项中全不勾选

indicator items把网速置顶即可

flameshot

Ubuntu上还有个好用的截图工具flameshot 具体这样配置

sudo apt-get install flameshot

进入 设置>设备>键盘,拉到最下面,点击加号

快捷键命令填写:flameshot gui

暂时更新到这里

csapp第六章 存储器层次结构

第六章总的来看比较简单,科普性的介绍了计算机系统里的存储层次结构,如下图所示

存储器层次图

由上至下,存储设备速度越来越慢,但相应的容量越来越大,价格也越来越便宜

最上层的像高速缓冲存储器会使用有双稳态的SRAM,而我们熟悉的主存会采用以电容和访问晶体管构成的DRAM

理解程序的局部性原理:

​ 时间局部性:

​ 在一个具有良好局部性的程序中,被引用过一次的程序位置很可能在不远的将来被多次引用

​ 空间局部性:

​ 在一个具有良好空间局部性的程序中,如果一个内存位置被引用了一次,那么很可能在不远的将来引用附近的一个内存位置

csapp第三章 程序的机器级表示(3)

总结:

​ 第三章总的来说,可以说是简要的说明了汇编语言的一些细节

之前学校有开设汇编语言的课程,只是那时的自己还处在懵逼的时代,感觉这种底层的知识离我很遥远,但是当我渐渐改变,迷迷糊糊的看完csapp第三章后,也能得到一些感慨: 那就随便说说吧!

感慨

先看一个c语言代码

1
2
3
4
5
int sum(int x,int y)
{
int t=x+y;
return t;
}

生成汇编代码

1
2
3
movl (%ebp),%eax  从内存中得到x放入eax寄存器
movl 4(%ebp),%edx 从内存中得到y 放入edx寄存器
addl %edx,%eax 将x和y相加

以IA32来说明:

​ mov 是移动,将%ebp 移动到%eax 后面的l是区分不同的数据类型的 char int double 这些类型,l就是为了区别它们 这个指令将一个地址里面的数移动到另一个地方,也就想当于赋值 。

想想:我们将一个短数据放入长数据位置会怎么样?

一个char放入一个int的空间,会剩下8位空的,那么需要使用movsbl(符号拓展)和movzbl(0拓展)这两个指令 c语言中的有符号无符号之间的运算,便需要借助这些指令。

操作数指示符

c语言具有不同的数据类型,汇编也一样:

​ 立即数类型, 也就是常数,在程序中我们会常常用到常数,在ATT表示下是一个常数前面加上$。

​ 寄存器类型,CPU中,有一个寄存器文件,寄存器类型就是指寄存器文件中某个寄存器中的内容。

​ 存储器引用,存储器指我们的内存区,内存相当于一个大型的字节数组,这里的存储器引用就是指这个数组的索引(数组的下标)

常见指令

书中讲解的比较详细;这里挑一个出来说明

1
2
3
4
5
6
7
int choose(int x,int y)
{
if(x<y)
return y-x;
else
return x-y;
}
1
2
3
4
5
6
7
8
9
10
movl 8(%ebp),%edx   x在%ebp+8处
movl 12(%ebp),%eax y在%ebp+12处
cmpl %eax,%edx
jge .L2
subl %edx,%eax
jmp .L3
.L2:
subl %eax,%edx
movl %edx,%eax
.L3:

第三条指令就是比较指令, 可以发现:每条比较指令下面跟着一条跳转语句,执行完比较操作后,相应的标志位会置位。

csapp第三章 程序的机器级表示(2)

###

本周总结下一个问题以及一些想法:

​ Q:在汇编层面上,只有几个简单的指令:

​ 数据传输:数据从内存到寄存器,寄存器到内存

​ 算逻运算

​ 比较 和 跳转

​ 那么,这么几个简单的指令为什么能支撑起现实这个复杂的世界?

UnderStand:

​ 不管什么语言写的程序,也不管业务逻辑有多复杂,最终都需要转变成最基本的汇编形式,我们如果将现实世界放在计算机世界的顶层,那么从底层到实际现实需要的便是一层一层的抽象,先通过基本的运算,抽象出变量,抽象出来堆和栈,进而出现高级语言,各种框架,我们需要具备的便是这种层层而上的思想。

步入正题:第三章第二次笔记

数据传送:

如图所示,exchange函数由三条指令构成,两个数据传送(movq),一条返回函数被调用点的指令(ret)。

过程开始执行,xp和y分别被存储到寄存器%rdi和%rsi中。第一条movq指令从内存中读出x,并放入%rax中,直接实现了c语言中的x=xp;接着,用寄存器&rax从这个函数返回一个值,因而返回值就是x。下一条指令将y的值写入到寄存器%rdi中的xp指向的内存位置,实现操作 xp=y.该例子说明mov指令从内存中读值到寄存器和从寄存器写入内存。

​ 注意:间接引用指针就是将该指针放到一个寄存器中,然后在内存引用中使用这个寄存器,即movq第二条指令

x这种局部变量,通常保存在寄存器中,而不是内存。

csapp第二章 信息的处理和表示(1)

信息存储

计算机可寻址的内存单元叫字节,机器级程序将内存视为一个字节数组,称为虚拟内存。内存的每个字节由一个数字标识,该数字就是地址,所有可能地址的集合称为虚拟地址空间,这个虚拟地址空间可视为一个展示给机器级别程序的抽象,即是为程序提供的一个看上去统一的字节数组

字数据大小

计算机的字长,表明指针数据的标称大小。因为虚拟地址按照这样的一个字来编码,所以字长可以决定虚拟地址空间的最大大小。即w位的机器,虚拟地址范围是0到2的w次方减1

程序一般是向后兼容,即32位机器编译的程序,可以在32位或者64位的机器上运行 如果是在64位机器上编译的程序,就只能在64位机器上运行,我们说32位/64位程序属猴的是程序是被编译的机器类型,而不是其运行的机器类型

寻址和字节顺序

对于多字节的程序对象,需要知道对象的地址以及在内存中如何排列地址里面的字节。在几乎所有机器上,多字节的对象都被存储成连续的字节序列,对象的地址是这些字节中最小的地址。

排列一个对象的字节有两种规则。大端法和小端法,选用哪种好没有明确的规定,对于大多数程序员,机器的字节顺序完全不可见,但是由于大小端的存在,字节顺序会成为问题,比如

  1. 在不同类型机器之间通过网络传输二进制数据,当小端法机器生成的数据被发送到大端法机器上时,接受程序发现字节为反序的。 为了避免这种问题,网络应用程序编写规定发送方将内部转化位网络标准,接受方机器将网络标准转换位它的内部表示。 这里可以抽象出一个思想,当a——>b无法顺利直达时,可以考虑a–>c–>b 如果a–>c c–>b的花销可以更少,不失为一种很好的选择
  2. 检查机器级程序时,阅读会成为一个问题,自然书写的方式和机器里面的字节顺序相反不利于阅读
  3. 当编写规避正常的类型系统的程序时,如c语言中的强制类型转换,可以允许一种数据类型引用一个对象,但是这个数据类型和创建这个对象时定义的类型不同。即使这种编码技巧对于系统级编程却是必要的。

c中字符串被编码成一个以null(值为0)字符结尾的字符数组