undefined

一、CPU上下文

CPU执行任务需要知道从哪里开始,也就是说,需要系统帮它设置好CPU寄存器和程序计数器。
CPU寄存器是CPU内置的容量小、速度极快的内存。程序计数器用来存储CPU正在执行的指令位置、或者即将执行的下一条指令位置。他们都是CPU在运行任务前,必须的依赖环境,也叫做CPU上下文。
那么,CPU上下文切换就是先把一个任务的CPU上下文(CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就保证任务原来的状态不受影响。

而根据任务的不同,CPU上下文分为:进程上下文切换、线程上下文切换以及中断上下文切换。

1. 进程上下文切换

如果让应用程序随便访问内存太危险了,因此按照CPU指令的重要程度对指令进行了分级,指令分为四个级别:Ring0-Ring3。
Linux只使用了 Ring0 和 Ring3 这两个运行级别。
进程运行在 Ring3 级别时被称为用户态,指令只能访问用户空间,被执行的代码要受到CPU很多检查;运行在 Ring0 级别时被称为内核态,可以执行任何指令,访问任何内存空间。
从用户态到内核态的转变,需要通过系统调用来完成。系统调用的过程会发生CPU上下文切换。
CPU寄存器中原来用户态的指令位置,需要先保存起来,然后更新为内核态指令的新位置,执行内核态代码;相反,从内核态切换到用户态也需要进行CPU上下文切换。因此,一次系统调用过程,发生了两次CPU上下文切换。

进程的上下文包括虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。
而保存上下文和恢复上下文的过程是需要内核在CPU上运行才能完成的。而根据 Tsuna (https://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html) 的测试报告,每次上下文切换都需要几十纳秒到数微妙的CPU时间。因此,如果进程上下文切换次数较多的情况下,很容易导致CPU将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了真正运行进程的时间。

Linux 下每个CPU都有一个就绪队列,将活跃进程(即正在运行和正在等待CPU的进程)按照优先级和等待 CPU 的时间排序,然后选择最需要 CPU 的进程,也就是优先级最高和等待CPU时间最长的进程来运行。

查看更多

undefined

中断

从物理学的角度看,中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚上。然后再由中断控制器向处理器发送相应的信号。处理器一经检测到此信号,便中断自己的当前工作转而处理中断。此后,处理器会通知操作系统已经产生中断,这样,操作系统就可以对这个中断进行适当的处理了。

不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识。因此,来自键盘的中断就有别于来自硬盘的中断,从而使得操作系统能够对中断进行区分,并知道哪个硬件设备产生了哪个中断。这样,操作系统才能给不同的中断提供不同的中断处理程序。

一、硬中断

由与系统相连的外设(比如网卡、硬盘、键盘)自动产生的。主要是用来通知操作系统外设状态发生了变化。比如,当网卡收到数据包的时候,就会发出一个中断。我们通常所说的中断指的是硬中断(hardirq)

二、软中断

为了满足实时系统的需求,中断处理应该越快越好。linux 为了实现这个特点,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,将那些比较耗时的工作放到中断之后来完成,也就是软中断(softirq)来完成

三、软硬中断的区别

查看更多

undefined

内存分配的分段和分页

一、早期的内存分配机制

要运行一个程序,会把这些程序全部装入内存,程序直接运行在内存上,也就是说程序中访问的内存地址是实际的物理地址。当计算机同时运行多个程序时,必须保证这些程序用到的内存总量要小于计算机实际物理内存的大小。

这种简单的内存分配策略存在很多问题:

查看更多

undefined

一.进程与线程,概念,区别,同步,通信

  • 基本概念:

    • 进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
    • 线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
查看更多

undefined

Linux 中的各种栈:进程栈、线程栈、内核栈、中断栈

一、概述

栈是一种 后入先出 的数据结构,栈的作用主要体现在:函数调用和多任务支持

1. 栈的作用 – 函数调用

函数调用有三个基本过程:调用参数的传入、局部变量的空间管理、函数返回

函数的调用要保证高效,因此数据可以放在 CPU通用寄存器或者 RAM 内存中。以传递调用参数为例,可以选择使用 CPU 通用寄存器来存放参数,但是通用寄存器的数目都是有限的,当出现函数嵌套调用时,子函数再次使用原有的通用寄存器必然会导致冲突。因此如果想用它来传递参数,那在调用子函数前,就必须先 保存原有寄存器的值,然后当子函数退出的时候再 恢复原有寄存器的值

函数的调用参数数目一般都相对少,因此通用寄存器是可以满足一定需求的。但是局部变量的数目和占用空间都是比较大的,依赖有限的通用寄存器是不行的,因此我们可以采用某些 RAM 内存区域来存储局部变量。需要在函数嵌套调用时不能发生冲突,又要注意效率。那么栈无疑是很好的解决办法,如下原因:

查看更多

undefined

计算机各种操作耗时

1. CPU 速度

频率:Hz,代表每核每秒钟的时钟周期。2.5GHZ每个时钟周期为0.4ns
MOV操作:大约占1个时钟周期
移位操作:大约1-2个时钟周期
ADD操作:大约占1-3个时钟周期
浮点加法操作:6个
双精度浮点加法操作:12个。
乘法操作:大约占10个时钟周期
除法操作:大约占17-46个时钟周期

2. CPU 缓存存取速度

L1:4ns,1-5时钟周期
L2:10ns,5-20时钟周期,
L3:20ns,40-100时钟周期

3. 互斥锁/解锁:25ns

4. 内存

内存顺序读取吞吐量:8GB/s
内存随机读取速度:100ns以内

5. SSD 硬盘

SSD存取时间 = 访问时间 + 数据传输时间
IOPS:磁盘每秒读写次数,每秒钟处理的IO请求次数。可能在10w 次左右。
吞吐量:磁盘每秒钟读写的数据量。
SSD顺序访问:500M/s
SSD随机访问:100微秒以内。最多每秒 10w 次存取。
传输1M数据理论耗时:访问时间(100微妙) + 数据传输时间(1000ms/500*1M)

6. 磁盘性能

磁盘存取时间 = 寻道时间 + 旋转时间 + 数据传输时间
寻道时间:10ms左右
旋转时间:普通磁盘有5400转/每分钟,平均旋转时间 60 * 1000 / 5400 / 2 为 5ms 左右
IOPS:磁盘每秒读写次数,每秒钟处理的IO请求次数。7200转的磁盘可能在 100qps 左右。
吞吐量:磁盘每秒钟读写的数据量。磁盘大约100M/s。
磁盘顺序访问:100M/s
磁盘随机访问:15ms一次(寻道时间+旋转时间)
传输1M数据理论耗时:寻道时间 + 旋转时间 + 数据传输时间(1000ms/100M*1M)

查看更多

undefined

惊群效应

一、概念

Linux 中,对设备模型进行了规范的标准化,比如设备分为字符设备、块设备、网络设备等,对于开发者而言,要给一个设备实现一个驱动程序就必须按照 linux 提供的规范来实现,其中对于跟用户层交互这里,内核要求开发者实现一个叫 file_operations 的结构,这个结构定义了一系列操作的回调指针,比如 read、write 等用户熟知的操作,当用户调用 read、write 等方法时,最终内核会回调到这个设备的 file_operations.read、file_operations.write 方法,这个方法的具体逻辑由驱动开发者实现,比如 accept 调用,实际上最终是调用了 socket 下面的 file_operations.accept 方法。

因此,如果一个设备要支持 epoll/select 的调用,就必须实现 file_operations.poll 方法,epoll 在处理用户层传入的 fd 时,实际上最终是调用了这个方法,而这个方法做了一系列规范,要求开发者实现以下逻辑:

  • 要求 poll 方法返回用户感兴趣的事件的标志,比如当前 fd 是否可读、是否可写等
  • 如果poll传入一个poll专用的等待队列结构体,那他将会调用这个结构体,这个结构体中会有一个叫poll_table的东西,里面有一个回调函数,poll方法最终会调用这个回调,这个回调是由epoll来设定的,epoll在这个方法中实现的逻辑是:将当前进程挂在这个fd的等待队列上面
查看更多