Linux操作系统学习总结
BIOS阶段
当机器按下电源键通电之后,执行的第一行代码是存储在只读存储器中的BOIS程序。BIOS程序要完成的功能有:
- 检查机器各个硬件组件是否正常
- 检测启动盘
启动盘可以是U盘、磁盘、移动硬盘,它具有一个标识:设备的第一个扇区是引导扇区,里面保存的是引导程序(boot.img)。引导程序会加载引导程序的各个模块进入内存,最终会将启动盘上的内核信息以列表的通过显示器展示给用户,用户选择一个操作系统内核之后,进入内核的启动阶段。
内核启动阶段
内核启动阶段会初始化各个内核模块,这些模块也可以叫做子系统。有任务管理子系统、内存管理子系统、文件管理系统等等。
内核程序在执行过程中创建的第一个进程,是用户态的进程的祖先。第二个进程是内核态所有进程的祖先。
内核态程序:管理硬件资源的程序,硬件资源是有限的,需要统一管理。
用户态程序:使用硬件资源,通常是使用系统调用,运行内核程序,从而操作硬件。
程序执行过程
在Linux上,可执行的程序都是满足elf标准的文件。可执行程序由源代码经过编译得到二进制程序,也就是elf文件。一个进程通过执行fork调用,创建一个新的进程,新的进程是子进程,而执行fork调用的进程是这个新进程的父进程,子进程再通过exec系统调用,将可执行文件加载到内存中,最终交给CPU执行。
elf文件格式
- elf文件头(文件总体描述)
- 程序指令
- 数据段
进程管理子系统
Linux将每个进程都组织成一个task结构,一个task结构包含这些信息。
- 进程id:每个进程都要一个id,是这个进程的唯一标识。
- 进程状态:TASK_RUNNING(进程可执行)、TASK_INTERRPTIBLE(睡眠状态,可以响应中断)、TASK_UNINTERRUPTIBLE(睡眠状态,不可中断,必须在条件满足之后才可以继续执行)、TASK_KILLABLE(睡眠状态,不可中断、可以响应致命信号)、EXIT_ZOMBIE(进程执行结束,父进程尚未获知其终止信息)、EXIT_DEAD(进程终止状态)
- 进程运行统计信息:在用户态、内核态消耗的时间、上下文切换次数等等
- 进程间关系:父子关系、兄弟关系。
- 进程权限信息:对文件或目录的访问权限、对别的进程的访问权限、别的进程对本进程的访问权限等。
- 占用的内存信息
- 占用的文件信息
线程
在Linux中,线程也是一个task结构,CPU最终调度的对象就是task。
任务调度
每个CPU有一个任务队列,CPU在运行的时候就从队列中取一个任务(task)出来执行。任务调度可以有很多中调度方式,比如fifo、lru、cfs等等。常用的是cfs(Completely Fair Scheduling),即完全公平调度。完全公平调度算法依据task运行时间、优先级来保证公平。
调度时机:选择具体的调度算法,解决了如何调度问题,而还有一个问题是何时调度。
- 主动让出CPU:当一个进程执行sleep调用或者进行了阻塞IO操作,都会主动放弃CPU,在内核中就会主动执行schedule()函数来执行调度。
- 被动让出CPU:进程执行时间太长了,该让出CPU给别的进程使用。这种情况,会在以下几个特殊时机触发schedule()函数。
- 对于用户态的进程来讲,从系统调用中返回的那个时刻,是一个被抢占的时机。
- 对于用户态的进程来讲,从中断中返回的那个时刻,也是一个被抢占的时机。
- 对内核态的执行中,被抢占的时机一般发生在 preempt_enable() 中。在内核态的执行中,有的操作是不能被中断的,所以在进行这些操作之前,总是先调用 preempt_disable() 关闭抢占。再次打开的时候,就是一次内核态代码被抢占的机会。
- 在内核态也会遇到中断的情况,当中断返回的时候,返回的仍然是内核态。这个时候也是一个执行抢占的时机。
内存管理子系统
在Linux系统中,程序执行时访问的指令地址、数据地址都是虚拟地址。每个程序都以为自己独占了整个物理内存。实际上,内存的每个字节只有一个唯一的地址,不同程序程序对于同一个地址的访问最终会经过页表翻译为最终的物理地址,也就是虚拟地址翻译为物理地址。这样,每个程序都可以使用同一个虚拟地址,而对于同一个虚拟地址,最后会被翻译为不同的物理地址。
内存管理子系统需要完成以下功能:
- 物理内存的管理
- 虚拟内存的管理
- 虚拟内存到物理内存的映射
物理内存管理:操作系统将内存分成节点,每个CPU先访问离自己最近的内存节点(下图第二种)。
每个节点里面再分区域(zone),用于区分不同的用法。每个区域里面再分页,默认大小为4KB。一个页是每个程序使用内存的最小单位。分配内存的时候,以一个页的整数倍进行分配,这个整数是1、2、4...1024。当一个页长时间不用了,可以暂时写到磁盘,要用的时候在读回内存,这就是换入换出,这样可以增加物理内存的使用效率。
虚拟内存管理:对于每个进程来说,内存分两个部分,一部分由内核程序使用(称为内核空间),剩下的部分全部归自己使用(称为用户空间)。用户进程不能访问内核空间。
用户空间的内存,分段管理,每一段存放不同的数据,elf格式的程序加载到内存之后,各部分对应放到各个段。内核空间里面也会有内核的程序,同样有 Text Segment、Data Segment和BSS Segment,内核程序也是elf格式的。
虚拟内存到物理内存的映射:将虚拟内存地址转换为物理内存地址,需要使用页表。可以简单的将页表理解为一个映射结构,输入虚拟地址,输出物理地址。
文件系统
文件系统有两个形态,一种形态是磁盘上的文件系统,这是在对磁盘进行格式化的时候写入到磁盘的。另一种形态是内核中的文件系统。内核中的文件系统定义了对磁盘上文件系统的操作以及相应的数据结构。
在文件系统中,存储的最小单位为块,每个块默认是4KB大小。一个文件可以拆分为多个块进行存放,因此需要有一个地方来记录文件都存在于哪些块,这是文件索引节点的需要提供的信息。
文件索引节点(inode)也是保存在块里面,每个文件对应一个inode。在inode中,有以下的信息:
- 文件读写权限
- 文件属于的用户、用户组
- 文件大小
- 文件占用多少个块、每个块的地址信息
文件夹(目录):在文件系统的概念中,目录也是一种文件,每个目录也对应一个inode。与普通文件不一样的是,目录中存放的是下一级文件的文件名和对应的inode,而普通文件里面存放的就是数据内容。
Linux内核程序在内存中维护了一套数据结构,用来记录哪些文件被哪些进程打开和使用,每个进程能打开的文件数量是有上限的,因此在开发中,用完文件之后要妥善关闭文件。
输入输出系统
输入输出系统,是对IO设备的进行管理的系统。常见的IO设备分为两种:字符设备、块设备。输入输出系统使用了层层抽象来对IO设备进行控制。
- 使用设备控制器屏蔽设备差异
- 使用设备系统程序屏蔽设备控制器差异
- 用中断控制器统一处理外部事件
- 用文件系统接口屏蔽驱动程序的差异
进程间通信机制
- 管道:单向数据传递。将前一个程序的输出作为后一个程序的输入。管道实际上以一段缓存。
- 消息队列
- 共享内存 + 信号量
- 信号:内核机制,在特殊情况下会发出信号,用户的程序只要准备信号处理函数就可以处理收到的信号。
网络通信
无论是windows还是linux,网络模型是一样的。网络模型与具体的操作系统无关。不同机器之间要通信就要通过网络。一个应用层的信息要发送到另一台机器的应用,需要将消息层层包装,加上各层协议的内容。目标机器收到网络包之后,逐层剔除协议,再根据端口好转发到对应的应用。
Socket是用户态与内核态的连接体,用户态的程序通过Socket接口就可以完成网络通信,无需关心各层协议细节。TCP、IP协议都是在内核中实现的,不同的操作系统实现细节不一样。
虚拟化&容器化
虚拟化就是在操作系统上运行操作系统,运用这种技术,可以将一台大机器虚拟为多个小机器。比较好的一种虚拟方式是半虚拟化(硬件辅助+半虚拟化驱动),启用硬件辅助技术之后,虚拟机的指令就能直接运行在宿主机的CPU上,不用经过翻译。半虚拟化驱动就是指使用特殊的、更高效的IO设备驱动,提高IO效率。
集群的操作系统
一台物理机器,最重要的四种硬件资源是CPU、内存、存储和网络。对于一个集群,比如说数据中心,如何灵活的管理和分配一堆计算资源,是一个难题。
Kubernetes,数据中心的“操作系统”,可以灵活的实现大批的资源管理。其中,对于CPU和内存,可以利用docker容器技术。对于存储,无论是分布式文件系统和分布式块存储,Kubernetes提供了接口,就行提供了驱动程序接口一样。对于网络,同样提供了标准接口只要对接这个统一的接口,Kubernetes 就可以管理容器的网络。。
docker容器技术可以将 CPU 和内存资源,从大的资源池里面隔离出来,并通过镜像技术,在数据中心里面实现计算资源的自由漂移。