admin管理员组

文章数量:1566222

3.1 术语

        介绍了本章用到的一些概念,比如操作系统、内核、进程等。

3.2 背景

        本章描述了通用的操作系统和内核概念,有助于读者对各类操作系统的实现原理有个基本的理解。

3.2.1 内核

        操作系统,其实就是在硬件设备和应用软件之间的传令官和翻译官,它将对磁盘、CPU、内存等硬件设备的操作指令封装成用户可以直接调用的系统接口,而内核就是操作系统基于硬件的第一层软件扩展。

                                        ​​​​​​​        

        内核之上的系统调用和系统库为用户提供了简单的调用接口,比如linux中的 lib,lib64路径下的库文件。在实际生产运维过程中经常会遇到运维工程师误删这两个路径下的库文件导致的故障,甚至会导致系统命令无法执行,系统无法启动。还有强制安装rpm包,导致安装的库文件与系统其他库文件版本不兼容的异常。

        内核的执行

        执行频繁的IO操作,比如磁盘读写、网络交互,主要在内核中进行,主要与硬件设备打交道;而计算密集型的负载通常在用户态运行。但内核很多情况下也会影响,最明显的是CPU调度场景。

3.2.2 内核态和用户态

        内核态和用户态是通过CPU的特权环来控制的,所谓运行态的切换也是CPU安全级别的切换。特权环可以理解为CPU不同的权限级别,分为0,1,2,3 四个级别,实际只用到两个或三个,权限级别从0到3依次降低。等级越高内操控越底层的设备,用户程序运行在用户态,它想往磁盘里写一条数据,需要CPU切换到内核态再执行,这就涉及到CPU的上下文切换。需要先保存用户态的现场,再切换到内核态,在内核态执行完需要的操作后再切换回用户态,并恢复之前保存的现场。

        因为频繁的上下文切换会增加CPU开销,所以对于IO频繁的业务系统不能只关注磁盘性能,往往CPU也会出现瓶颈。

        相对应的,在内存中也区分了用户空间和内核空间,CPU只有在内核态时才有权限对内核空间的内存进行操作。

3.2.3 系统调用

        用户程序执行一些内核态的特权指令是通过系统调用来传递的,可用的系统调用有几百个,常用的有read、write、open、fork等,这些调用尽可能简单,更为复杂的接口作为系统库构建在用户空间中。操作系统通常包含C语言的标准库,其为许多常见的系统调用提供更容易使用的接口(libc、glibc库)。

        下面介绍了几个常用但比较复杂的系统调用:

        ioctl(2):用于设备输入输出操作的系统调用,执行的功能取决于传入的请求码,可以操作的设备包括网络套接字、文件、接口、ARP、路由、流,在linux中,所有的要素都被视为文件,并且分配了唯一的标识fd。

        mmap(2):将可执行文件和库文件以及内存映射文件映射到进程的地址空间。

        futex(2):处理用户空间的锁。

3.2.4 中断

        中断是向处理器发出的信号,即当发生了一些高优先级需要马上处理的事件时,要中断处理器当前的执行来实施处理。要处理中断,处理器需要进去内核态。保存当前执行的线程,运行一个中断服务例程(ISR)来处理该事件。

        有外部硬件产生的异步中断和软件指令产生的同步中断。

        异步中断(硬件设备主动发出中断信号):

        磁盘设备发出磁盘I/O完成的信号

        硬件显示有故障情况

        网络接口发出有数据包到达的信号

        外接设备输入:键盘、鼠标

        同步中断(软件指令主动中断):

        自陷:主动调用内核,例如通过int(中断)指令

        异常:执行错误指令,比如除0

        故障:内存缺页故障(内存中没有需要的数据,要从磁盘加载)

        中断线程ISR

        用于快速处理中断.

        中断屏蔽:内核可以通过设置CPU的中断屏蔽寄存器来暂时屏蔽中断。(时间尽可能要短),一些高优先级的事件可以被实现为不可屏蔽中断(NMI)。

3.2.5 时钟和空闲

        计时器中断:每秒执行次数。功能:更新系统时间,线程调度时间片,维护CPU统计数据,以及执行内核调度例程。

        空闲线程:CPU没有工作可做时,内核会安排一个占位线程。

3.2.6 进程

        进程保存了用户程序执行的环境信息,包含内存地址空间、文件描述符、线程栈和寄存器。PID唯一标识一个进程。一个进程包含一个或多个线程,同一个进程的线程共享进程的地址空间及文件描述符。

        线程包括栈、寄存器以及指令指针(程序计数器),多线程让单一进程可以在多个CPU上并发执行。

        内核启动的第一个进程是 init,在/sbin/init,PID为1,用于启动用户空间。其他的进程都是fork该进程。

        进程的创建

        进程的创建通常使用fork(2)和clone(2)创建一个进程的副本,然后再调用exec(2)来开始执行一个不同的程序。

        fork和clone可以用写时拷贝(copy on write COW)策略提高性能。原理是fork一个新进程时,会添加对原有地址空间的引用而非把所有内容都复制一遍,如果进程要修改被引用的内存空间才会建立一个单独的副本。推迟甚至取消了内存拷贝的需要。

        进程的生命周期

        进程环境

        进程环境分为用户地址空间和内核上下文,内核上下文保存了进程切换时需要保存或换入的进程信息,而用户地址空间是内存中的用户数据和用户栈。

        

3.2.7 栈

        栈是存储临时数据的内存区域,函数被调用时,返回地址保存到栈中,函数需要的一些寄存器的值也可以被保存在栈里面。函数执行完后,恢复所有需要的寄存器,并从栈中获取返回地址。通过检查线程栈中的所有栈帧中保存的返回地址,可以看到当前函数的调用路径(栈遍历)。

        通过向下阅读栈,可以看到函数完整的调用链,自下而上,可以跟踪函数的执行路径。一般tomcat java程序的报错信息都会列出程序执行的栈,从而定位到具体的代码。

3.2.8 虚拟内存

        虚拟内存是主存的抽象,每个进程都有一个完整主存大小的私有地址空间,进程可以随意往自己的虚拟内存空间存取数据,而不用担心与其他进程冲突,因为这并不是真实的内存,操作系统会将虚拟内存映射到真实内存。

        内存管理

        内核会尽力将最活跃的数据放在主存中:换页(更高效)和进程交换。

3.2.9 调度器

        现在常见的UNIX衍生操作系统都是分时系统,划分了一个个时间周期,其实每个cpu同一时刻还是只执行一个线程,但在一个时间段内,多个线程依次执行,看起来也是并行。而多个线程如何依次执行,则是由CPU调度器控制的。

        调度器维护了一套优先级机制和多个优先级队列,调度器可以动态地修改进程的优先级以提升特定工作负载的性能。工作负载分类:CPU密集型、IO密集型。

3.2.10 文件系统

        要理解操作系统中的文件系统,我们需要从最底层的磁盘结构开始理解。自下而上的组件依次是:

        硬盘->驱动->卷管理->块设备接口->文件系统->VFS->应用程序通过系统库调用

        硬盘的物理结构:主要要理解扇区、磁道、柱面这三个概念,磁头根据这三个信息可以定位到我们需要的具体的数据位置。所以我们通过卷管理对硬盘进行分区也是根据这三个概念进行划分的。

        卷管理:从卷管理开始,我们把硬盘转换为一种逻辑的线性结构,我们可以从0号位置开始依次看下一块硬盘上到底放了什么东西。

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        为了更加直观的展示,我们用windows系统进行演示。当我们给windows添加一块新的硬盘时,在磁盘管理中,首先需要做得就是进行初始化。有两种分区形式可供选择:MBR和GPT。Linux默认使用MBR,当然也可以用parted进行转换。

        MBR(Main Boot Record 主引导记录区)位于整个硬盘的0磁道0柱面1扇区。不过,在总共512字节的主引导扇区中,MBR只占用了其中的446个字节,另外的64个字节交给了 DPT(Disk Partition Table硬盘分区表),最后两个字节“55,AA”是分区的结束标志。这个整体构成了硬盘的主引导扇区。

        ​​​​​​​        ​​​​​​​        

        计算机启动过程中,bios会加载系统盘的mbr信息,读取分区表中第一个字节,如果是80,则表示是活动分区(安装了操作系统),然后就执行mbr中的boot loader程序启动操作系统。

        linux可以使用fdisk命令进行分区,实际上也就是更新DPT分区表。完成分区后,就可以将分区进行格式化,也就是给分区装上文件系统。

文件系统:

        文件系统是一种组织目录、文件的方式,方便人们阅读,查询和操作。

        每个分区又被划分为启动区、超级块、inodes和数据块,实际数据存放在数据块中,只有存放操作系统的分区启动区才有相应的启动程序,超级块中则存放着该分区文件系统的相关信息,包括文件系统的类型,inode的数目,数据块的数目,是文件系统的关键信息,在分区中存在多个备份,被意外破坏后可以通过备份恢复。

        在Linux文件管理中,我们知道,一个文件除了自身的数据之外,还有一个附属信息,即文件的元数据(metadata)。这个元数据用于记录文件的许多信息,比如文件大小,拥有人,所属的组,修改日期等等。元数据并不包含在文件的数据中,而是由操作系统维护的。事实上,这个所谓的元数据就包含在inode中。我们可以用$ls -l filename来查看这些元数据。正如我们上面看到的,inode所占据的区域与数据块的区域不同。每个inode有一个唯一的整数编号(inode number)表示。

        在保存元数据,inode是“文件”从抽象到具体的关键。正如上一节中提到的,inode储存由一些指针,这些指针指向存储设备中的一些数据块,文件的内容就储存在这些数据块中。当Linux想要打开一个文件时,只需要找到文件对应的inode,然后沿着指针,将所有的数据块收集起来,就可以在内存中组成一个文件的数据了。

本文标签: 第三章大神之巅操作系统性能