Linux,全称GNU/Linux,是一种免费使用和自由传播的类UNIX操作系统。它主要由以下的多个模块组成:
进程类似于人类,他们被产生有或多或少的生命,可以产生一个或多个子进程,最终都要死亡。一个微小的差异是进程之间没有性别差异,每个进程都只有一个父亲。从内核的观点看,进程的目的就是担当分配系统资源(cpu时间、内存等)的实体。当一个进程创建时,它几乎与父进程相同,它接受父进程地址空间的一个(逻辑)拷贝,并从进程创建系统调用的下一条指令开始执行与父进程相同的代码。尽管父子进程可以共享含有程序代码(正文)的页,但是它们各自有独立的数据拷贝(栈和堆),因此子进程对一个内存单元的修改对父进程是不可见的(反之亦然)
进程描述符:为了管理进程,内核必须对每个进程所做的事情进行清楚的描述。例如,内核必须知道进程的优先级,它是正在CPU上运行还是因为某些事件而被阻塞,给它分配了什么样的地址空间,允许它访问哪个文件等等。这正是进程描述符的作用。进程描述符都是task_struct类型结构,它的字段包含了与一个进程相关的所有信息。因为进程描述符中存放了那么多信息,所以它是相当复杂的。它不仅包含了很多进程属性的字段,而且一些字段还包括了指向其他数据结构的指针,一次类推,下图表示了Linux的进程描述符。
进程状态:主要分为可运行状态、可中断的等待状态、不可中断的等待状态、暂停状态、跟踪状态、僵死状态和僵死撤销状态。
创建进程:Linux操作系统紧紧依赖进程创建来满足用户的需求。例如,只要用户输入一条命令,shell进程就创建一个新进程,新进程执行shell的另一个拷贝。
撤销进程:进程终止了它们本该执行的代码,从某种角度上来说,这些进程“死”了。当这种情况发生时,必须通知内核以便内核释放进程所拥有的资源,包括内存、打开文件及其他如信号量这些零散的东西。
在Linux系统中,整个系统的性能取决于如何有效地去管理内存。因此,现在所有多任务操作系统都在尽力优化对动态内存的使用,也就是说,尽可能做到当用到时分配,不需要时释放。
虚拟地址:为了充分利用和管理系统内存资源,Linux采用虚拟内存管理技术,利用虚拟内存技术让每个进程都有一定容量互不干涉的虚拟地址空间。进程初始化分配和操作的都是基于这个「虚拟地址」,只有当进程需要实际访问内存资源的时候才会建立虚拟地址和物理地址的映射,调入物理内存页。
物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。在前端总线上传输的内存地址都是物理内存地址,编号从0开始一直到可用物理内存的最高端。
中断:中断是指 CPU 对系统发生某事件时的这样一种响应。CPU 暂停正在执行的程序,在保留现场后自动地转去执行该事件的中断处理程序,执行完后,再返回到原程序的断点处继续执行。
下图 表示中断时 CPU 的活动轨迹。还可进一步把中断分为外中断和内中断。
异常:即这里的内中断。
引入这两者原因:
当CPU启动设备进行输入/输出后,设备便可以独立工作,CPU转去处理与本次输入/输出不相关的事情;当设备完成输入/输出后,通过向CPU发中断报告此次输入/输出的结果,让CPU决定如何处理以后的事情。
如算术溢出、除零、取数时的奇偶错,访存地址时越界或执行了“陷入指令”等,这时硬件改变了CPU当前的执行流程,转到相应的错误处理程序或异常处理程序或执行系统调用。
在LINUX系统中有一个重要的概念:一切都是文件。其实这是UNIX哲学的一个体现,而Linux是重写UNIX而来,所以这个概念也就传承了下来。在UNIX系统中,把一切资源都看作是文件,包括硬件设备。UNIX系统把每个硬件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。这样带来优势也是显而易见的。
上图所示的体系结构显示了用户空间和内核中与文件系统相关的主要组件之间的关系。VFS 是底层文件系统的主要接口。这个组件导出一组接口,然后将它们抽象到各个文件系统,各个文件系统的行为可能差异很大。有两个针对文件系统对象的缓存(inode 和 dentry)。它们缓存最近使用过的文件系统对象。在这里缓冲区缓存和设备驱动的交互、以及VFS提供的系统接口暂不讨论,主要看看实现这个VFS子系统的主要结构。
I/0体系结构
为确保计算机能够正常工作,必须提供数据通路,让信息在连接到计算机的CPU、RAM、和I/O设备之间流动,这些数据通路总称为总线,担当计算机内部主通信通道的作用。
所有计算机都拥有一条系统总线,它连接大部分内部硬件设备,一种典型的系统总线是PCI(Peripheral Component Interconnect)总线。目前使用其他类型的总线也很多,例如:ISA、EISA、MCA、SCSI和USB。典型的情况是,一台计算机包括几种不同类型的总线,它们通过被称作"桥"的硬件设备连接在一起。两条高速总线用于在内存芯片上来回传送数据:前端总线将CPU连接到RAM控制器上,而后端总线将CPU连接到外部硬件的高速缓存上。主机上的桥将系统总线和前端总线连接在一起。
任何I/O设备有且仅能连接一条总线。总线的类型影响I/O设备的内部设计,也影响着内核如何处理设备。
CPU和I/O设备之间的数据通路通常称为I/O总线。80x86微处理器使用16位的地址总线对I/O设备进行寻址,而使用8位、16位或32位数据总线传递数据,每个I/O设备依次连接到I/O总线上,这种连接使用了包含3个元素的硬件组织层次:I/O端口、接口和设备控制器。
设备驱动程序
设备驱动程序是一种内核模块,负责管理硬件设备的底层 I/O 操作。设备驱动程序是使用标准接口编写的,内核可通过调用该标准接口与设备进行交互。设备驱动程序也可以是仅针对软件的,即模拟仅存在于软件中的设备,如 RAM 磁盘、总线以及伪终端。
设备驱动程序包含与设备进行通信时所需的所有特定于设备的代码。此代码包括一组用于系统其余部分的标准接口。就像系统调用接口可使应用程序不受平台特定信息影响一样,此接口可保护内核不受设备特定信息的影响。应用程序和内核其余部分需要非常少的特定于设备的代码(如果有)对此设备进行寻址。这样,设备驱动程序使得系统的可移植性更强,并更易于维护。
设备驱动程序按照处理 I/O 的方式可以分为以下三大类别:
这里以文件读写流程为例。
读流程:
写流程:
这里我们采用类似于压力测试的方法来测试这个主题。
在下面的命令中,我们启动了四个无尽循环。当然也可以通过添加数字或使用 bash 表达式,如 {1...6} 来代替 1 2 3 4 以增加循环次数:
for i in 1 2 3 4; do while : ; do : ; done & done
在命令行上输入后,将在后台启动四个无尽循环:
$ for i in 1 2 3 4; do while : ; do : ; done & done
[1] 205012
[2] 205013
[3] 205014
[4] 205015
在这种情况下,发起了作业 1-4,作业号和进程号会相应显示出来。
CPU负载
$ while true; do uptime; sleep 30; done
在输出中,我们可以看到CPU平均负载是如何增加的,然后在循环结束后又开始下降。
11:25:34 up 5 days, 17:27, 2 users, load average: 0.15, 0.14, 0.08
11:26:04 up 5 days, 17:27, 2 users, load average: 0.09, 0.12, 0.08
11:26:34 up 5 days, 17:28, 2 users, load average: 1.42, 0.43, 0.18
11:27:04 up 5 days, 17:28, 2 users, load average: 2.50, 0.79, 0.31
11:27:34 up 5 days, 17:29, 2 users, load average: 3.09, 1.10, 0.43
11:28:04 up 5 days, 17:29, 2 users, load average: 3.45, 1.38, 0.54
11:28:34 up 5 days, 17:30, 2 users, load average: 3.67, 1.63, 0.66
11:29:04 up 5 days, 17:30, 2 users, load average: 3.80, 1.86, 0.76
11:29:34 up 5 days, 17:31, 2 users, load average: 3.88, 2.06, 0.87
11:30:04 up 5 days, 17:31, 2 users, load average: 3.93, 2.25, 0.97
11:30:34 up 5 days, 17:32, 2 users, load average: 3.64, 2.35, 1.04 <== 循环停止
11:31:04 up 5 days, 17:32, 2 users, load average: 2.20, 2.13, 1.01 11:31:34 up 5 days, 17:33, 2 users, load average: 1.40, 1.94, 0.98
因为所显示的负载分别代表了 1、5 和 15 分钟的平均值,所以这些值需要一段时间才能恢复到系统接近正常的状态。
我们可以宏观地看到这里程序运行时CPU的负载增加。
内存负载
编写以下脚本查看内存负载:
$ cat watch-it
#!/bin/bash
while true
do
free
sleep 30
done
运行之后:
$ ./watch-it
13:09:14 up 5 days, 19:10, 2 users, load average: 0.00, 0.00, 0.00
13:09:44 up 5 days, 19:11, 2 users, load average: 0.68, 0.16, 0.05
13:10:14 up 5 days, 19:11, 2 users, load average: 1.20, 0.34, 0.12
13:10:44 up 5 days, 19:12, 2 users, load average: 1.52, 0.50, 0.18
13:11:14 up 5 days, 19:12, 2 users, load average: 1.71, 0.64, 0.24
13:11:44 up 5 days, 19:13, 2 users, load average: 1.83, 0.77, 0.30
可以看到因为是无限循环,因此内存的负载也是一个逐渐上升的过程。
磁盘I/O负载
我们可以使用 iotop 观察受压的 I/O。注意,运行 iotop 需要 root 权限。
之前:
$ sudo iotop -o
Total DISK READ: 0.00 B/s | Total DISK WRITE: 19.36 K/s
Current DISK READ: 0.00 B/s | Current DISK WRITE: 27.10 K/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
269308 be/4 root 0.00 B/s 0.00 B/s 0.00 % 1.24 % [kworker~fficient]
283 be/3 root 0.00 B/s 19.36 K/s 0.00 % 0.26 % [jbd2/sda1-8]
之后:
Total DISK READ: 0.00 B/s | Total DISK WRITE: 0.00 B/s
Current DISK READ: 0.00 B/s | Current DISK WRITE: 0.00 B/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
269308 be/4 root 0.00 B/s 0.00 B/s 0.00 % 1.32 % [kworker~fficient]
283 be/3 root 0.00 B/s 19.36 K/s 0.00 % 0.28 % [jbd2/sda1-8]
可以看到,虽然这是一个无限循环程序,但是由于它并没有涉及到多少的IO操作,因此,这里I/O相关基本没什么变化。
通过这门课的学习,我认识到了之前很多不足的点,收获颇丰,主要有以下三点:
原文:https://www.cnblogs.com/jizhidexiaobai/p/14771318.html