# Linux系统概念模型
本文将linux整体结构分作,进程,内存,文件系统,设备管理。
进程管理
内核态和用户态,我们可以认为user/kernel mode是分隔用户空间和内核空间的边界,用户空间运行的程序运行在user mode,内核空间的程序运行在kernel mode。操作系统位于内核空间。需要有一种方式能够让应用程序可以将控制权转移给内核(Entering Kernel)。进入到内核态后可以执行系统调用。
假设我现在要执行另一个系统调用write,相应的流程是类似的,write系统调用不能直接调用内核中的write代码,而是由封装好的系统调用函数执行ECALL指令。所以write函数实际上调用的是ECALL指令,指令的参数是代表了write系统调用的数字。之后控制权到了syscall函数,syscall会实际调用write系统调用。
### 进程调度
分为主动调度和被动调度,在进程调度发生进程切换时,会保存进程上下文,使其调度后能够恢复运行。
## 用户空间和内核空间的切换
具体过程是先把数据读取到内核空间中,然后再把数据拷贝到用户空间并从内核态切换到用户态。在内核态会执行相应的Trap函数,根据Trap发生的原因不同来改变执行的程序。
## 虚拟内存(Virtual Memory)
隔离性是我们讨论虚拟内存的主要原因。创造虚拟内存的一个出发点是你可以通过它实现隔离性。
如果你正确的设置了page table,并且通过代码对它进行正确的管理,那么原则上你可以实现强隔离。内核态运行在相应的page table上用户态运行在相应的page table上。每个页表中有多个页表项,每条页表项对应着一块物理地址。
拓展进程的内存空间可以使用brk()系统调用
## 设备管理部分
设备这部分主要包括字符设备,块设备,网络设备。linux中能够进行模块化编程,将驱动程序打包成一个模块放入系统内核中。用户程序在调用某个设备时,会产生Trap然后由中断处理来对Trap进行处理,由设备识别程序来识别对应的设备。在设备io的过程中,并发是一个非常需要仔细考虑的问题。同步与异步,如何保证io设备和系统程序之间的交互……
## 文件系统
EXT2文件系统是EXT文件系统的升级,在Linux中得到了广泛的使用。EXT2文件系统中的每个文件由一个inode描述,且只能由一个inode描述。
### ext2中超级块
### inode
EXT2的空闲盘块分配算法采用了位图法
### n 位图
为便于查找数据块或索引结点的分配信息n 每个位(bit)都对应了一个磁盘块:
n=0,表示对应的磁盘块(或索引结点)空闲
n=1,表示占用。
### log
为了保证文件系统在crash时能够进行恢复,文件系统中通常会引入log机制,在write和read文件时,实际上 并不是直接对文件进行操作,而是对log进行读写,当log进行commit后再对真正的文件进行读写,这样就能保证在文件系统crash时,不会破坏实际的文件,并且log中存有了文件操作,还能恢复。
### 数据块block
数据块中存放文件的内容,包括目录表、扩展属性、符号链接等。
## 例子一 内存分配
shell在执行时,会先调用fork系统调用,此时该进程会进入到内核态,在进入到内核态之前会先保存原先在用户态中的部分寄存器。进入到内核态后,由Trap机制选择进行系统调用,根据系统调用号来找到对应的系统调用,并执行。然后返回用户态,返回的过程会取出保存的寄存器,恢复原先的进程。
fork系统调用的作用是生成一个子进程来执行。在当前的进程上,fork会将父进程的地址空间复制到子进程中。然后执行。
在shell中一般会使用fork然后紧接着使用exec系统调用。如果直接将所有的父进程空间都进行复制,那么会造成子进程在exec后会造成大量的浪费。此处可以使用copy-on-write机制来进行优化。
当一个进程执行时,如果遇见需要向系统申请更多内存的情况,此时会调用brk()系统调用,brk()系统调用的作用是动态拓展该进程的堆空间,但是linux中使用了一种叫做lazy allocation的机制,因为在进程中为了减少因为空间不足而造成的进程出错的情况,进程总是会优先申请多于自身实际需求的空间,当多个进程同时运行在计算机上时,这种机制会造成大量的空间浪费。而lazy allocation就解决了这种情况。lazy allocation会提升进程的计数,但是实际上并不为其分配物理页面,当进程访问到这个页时,会发生page fault,此时由Trap机制进行捕获,然后再对其进行实际处理。
## 例子二 读取文件的过程
在linux中调用write和read系统调用会对文件进行读取,一个文件的地址空间会被划分为不同的区域:如super block,log,inode,bitmap,和data block
除了data block以外,其他四个都是用于帮助文件系统完成工作的metadata。当我使用echo hi进行写入时实际上的过程大致可以看做两个阶段:
创建文件:标记inode将要被使用,实际的向该inode写入,修改其引用计数,修改第一块data block,这一个block是根目录,因为我们在写的过程中向根目录中添加了一个文件,然后修改inode的引用,因为我们添加了一个数据
写入‘hi’:更新bitmap因为我们要找到一个新的没有使用过的块,查询bitmap中的数据然后修改该位为使用,找到对应的block将数据写入,然后修改inode中的引用和size。
## 影响应用程序的因素
### io设备
当程序在运行时,如果发生了调用io的情况,比如read、write此时,最核心的影响交互效率的因素就是io过程。而为了提升这个过程的速度,现在的操作系统一般都引入了如缓冲区,DMA机制……
缓冲区使得程序在io过程不需要一直等待io过程的完成 ,而可以直接从装有已经完成传输的缓存区中读取数据。
DMA可以使传输数据过程中不需要CPU的参与。
### 地址空间的限制
现在的操作系统一般会使用分级页表这样能拓展进程能访问的地址空间,但是增加了访问页表的次数。当一个程序多次访问同一张页表时,可以使用TLB来进行访问,TLB会将上次所访问的页表地址缓存起来,这样程序不需要在一级一级的查找页表。
### 系统崩溃
当系统在运行时,突然发生断电导致崩溃的时候,由于文件数据的存储是一个持久化的过程,而写入文件的过程本身不是原子性的,也就是说,在写入或者读取文件时可能发生由于外部错误而导致的文件系统出错。为了避免这总情况的发生,文件系统中引入了一个叫做log的机制,普通的read、write等操作文件的系统调用并不是直接对文件数据本身进行操作,而是对log进行操作,当整体操作结束后,log会一次性操作真正的数据,这样就保证了操作文件数据变成了一个事务。并且解决了断电可能造成的崩溃。
### 网络
Linux下的各种应用,一般都是基于网络的,因此网络带宽也是影响性能的一个重要因素,低速的、不稳定的网络将导致网络应用程序的访问阻塞,而稳定、高速的网络带宽,可以保证应用程序在网络上畅通无阻地运行。幸运的是,现在的网络一般都是千兆带宽或光纤网络,带宽问题对应用程序性能造成的影响也在逐步降低。