目录
1.基本概念与实现
1.1.进程3态
1.1.1.进程调度的方式
1.1.2.几种典型的调度算法
1.2.僵尸进程/孤儿进程
1.3.pread/pwrite
1.4.缓冲区、缓冲区
1.4.1.用户空间的I/O缓冲区
1.4.2.内核缓冲区
1.5.fork
1.6.守护进程
1.6.1.定义
1.6.2.会话/进程组
1.6.3.守护进程创建过程
1.7.信号SIGPIPE(broken pipe)
2.进程通信
2.1.无名管道pipe()
2.2.有名管道fifo
2.3.socket_pair
2.4.消息队列
2.5.共享内存shm
2.5.1.效率对比
2.6.信号量
2.7.信号
2.8.socket
3.线程同步
3.1.互斥锁\自旋锁\读写锁\原子操作\CAS\信号量
3.2.条件变量
3.3.一些问答
3.4. 研讨: 线程数究竟设多少合理
4.五种网络IO模型
4.1.什么是IO
4.2.网络IO模型
4.2.1.阻塞IO
4.2.2.非阻塞IO
4.2.3.信号驱动IO
4.2.4.异步IO/完成端口
4.2.5.IO多路复用
4.3.零拷贝
4.3.1.为什么要有DMA技术?(无DMA有什么弊端)
4.3.2.DMA 技术
4.3.3.传统IO流程(一次读写)
4.3.4.零拷贝sendfile(一次读写)
5.操作系统
5.1.中断
5.1.1.中断触发的方式
5.1.2.硬件中断处理函数会做如下的事情:
5.1.2.中断的触发流程
5.2.文件系统
5.2.1.文件系统的基本组成
5.2.2.虚拟文件系统
5.2.3.文件I/O
5.2.4.数据会丢么?***
5.3.死锁
5.3.1.操作系统的死锁产生必要条件有什么?
5.3.2.死锁避免的方法
5.3.3.死锁发生了,怎么解决:只能强制剥夺
6.计算机网络
6.1.ISO/OSI七层模型
Q1: 应用层协议,哪些协议使用了TCP?哪些协议使用的UDP?
Q2: Ping/Traceroute实现原理
Q3: ARP/RARP 地址解析协议 Address Resolution Protocol
Q4: 在浏览器输入URL回车后,发生了什么?
Q5:Linux接收网络包的流程
7.性能
7.1.top
7.2.性能问题分析及优化
写在最前:进程和线程的区别
进程 |
线程 |
|
CPU资源分配的最小单位 |
CPU调度最小单位 |
|
是否拥有独立的地址空间 |
√ |
× |
切换速度 |
快 |
慢 |
占用资源数 |
多4G |
较少(每线程默认占1MB堆栈) |
安全性 |
相互独立,互不影响 |
一个线程崩溃,可能会导致整个进程崩溃 |
通信代价 |
大 |
小 |
Q: 内核是如何实现进程和线程的?
A: 实际上,对于linux内核而言,是没有线程这个概念的,创建线程/进程都是采用do_fork系统调用,也是创建一个task_struct结构体,只不过创建线程时,指定一些变量是共享的。
1.基本概念与实现
1.1.进程3态
运行 |
当一个进程在处理机CPU上运行时,则称该进程处于运行状态。 |
|
就绪 |
当一个进程获得了除CPU以外的一切所需资源,(一旦得到处理机即可运行),则称此进程处于就绪状态。 |
|
阻塞 |
一个进程正在等待某一事件发生(例如请求I/O而等待I/O完成等)而暂时停止运行,(这时即使把处理机分配给进程也无法运行),故称该进程处于阻塞状态。 |
1.1.1.进程调度的方式
抢占式调度:当一个进程正在CPU上执行时,若有某个更为重要或紧迫的进程(优先级更高)的进程需要使用CPU,则立即暂停正在执行的进程,将CPU分配给这个更重要的进程
非抢占式调度:当一个进程正在CPU上执行时,即使有某个更为重要或者紧迫的进程进入就绪队列,仍然让正在执行的进程继续执行,直到该进程完成或发生某种事件而进入阻塞态时,才把CPU分配给更为重要或紧迫(优先级更高)的进程
1.1.2.几种典型的调度算法
先来先服务调度算法
短作业优先调度算法
优先级调度算法
时间轮旋转调度算法
多级反馈队列调度算法
多个就绪队列(按照“时间片大小”划分优先级):第2级队列的时间片要比第1级队列的时间片长一倍,…第i+1级队列的时间片要比第i级队列的时间片长一倍
- 当一个新的进程进入内存后,首先将它放入第一队列的末尾,按照FCFS原则排队等待调度
- 当轮到该进程执行时候
- case1: 如果它能在一个时间片内完成,便可以撤出队列系统
- case2: 如果它在一个时间片内未完成,再将其放到下一个级别的队列尾部(同理,当第i个队列的进程未执行完,将其放到第i+1队列的尾部)
- case3: 仅当第一级队列为空时,调度程序才调度第二级队列中的进程进行(仅当第1到i-1级队列均为空,才会调度第i级队列中的进程运行)
- case4: 若处理机正在执行第i级队列中的某个进程,此时,又有新的进程进入优先级较高的队列(第1到i-1级的任意一级):则,该进程将抢占正在运行的处理机,即由调度程序把正在运行的进程放回第i级队列末尾,把CPU分配给新到的更高优先级进程
1.2.僵尸进程/孤儿进程
僵尸进程:没有被回收的进程
-
一个进程fork创建子进程,子进程退出后,父进程没有调用wait/waitpid回收子进程资源,这个子进程就叫做僵尸进程
-
尸体会占用系统中的进程号/进程资源,一旦太多将会耗尽系统资源
1. 当子进程退出时,将发送SIGCHLD信号 2. 父进程使用wait/waitpid,回收子进程的资源 |
|
wait |
阻塞 |
waitpid |
提供了非阻塞方式;可以回收指定子进程(-1:所有子进程,>0等待pid子进程;==0,进程组) |
孤儿进程:父进程退出了,子进程还在
-
它会被init进程收养
-
孤儿进程并不存在问题,它只是没有了父进程而已(一般守护进程都这么玩)
1.3.pread/pwrite
带偏移量的、原子的读写文件
调用pread相当于顺序调用lseek和read,但pread和这种调用又有重大区别:
- 调先调用lseek,再调用read/write(原子操作,不能被打断)
- 因为历史上有些系统不支持O_APPEND,才定义了pread和pwrite。 因为lseek与read之间,可能会出现非预期的效果,所以定义pread
- 随机访问的话,pread/pwrite比较方便
1.4.缓冲区、缓冲区
用户数据 — IO缓冲区 — 内核缓冲区 — 磁盘/外设
read把数据从内核缓冲区复制到I/O缓冲区,write把数据从I/O缓冲区复制到内核缓冲区,它们不等价于数据在内核缓冲区和磁盘之间的交换。
说明:4.3.1.传统IO流程(一次读写)
1.4.1.用户空间的I/O缓冲区
I/O缓冲区,其作用是减少read和write的次数,即减少了系统调用,从而减少了系统开销,提高了I/O速度
用户空间IO缓冲的类型:下面的3个缓冲是指的是用户空间的I/O缓冲区,不是内核缓冲区
1. 全缓冲:如果缓冲区写满了就写回内核。
场景:常规文件的写入写出(磁盘)通常是全缓冲的
过程:数据—-I/O缓冲—-内核缓冲—-外设
2. 行缓冲:在I/O缓冲区中遇到换行符或者缓冲区写满时,就自动把数据送到内核缓冲区。
场景:标准输入(0)和标准输出(1)对应终端设备时通常是行缓冲的。
3. 无缓冲:用户层不提供缓冲,数据流直接到内核缓冲区,再到磁盘等外设上
场景:标准错误输出(2)通常是无缓存的,因为它必须尽快输出,且是输出到具有交互式的设备上,如屏幕,不是磁盘
1.4.2.内核缓冲区
写操作
1. 从理论上讲,内核可以在任何时候写磁盘,但并不是所有的write操作都会导致内核的写动作
2. 内核会把要写的数据暂时存在内核缓冲区中,积累到一定数量后再一次写入
3. 有时会导致意外情况,比如断电,内核还来不及把内核缓冲区中的数据写到磁盘上,这些更新的数据就会丢失
1.5.fork
返回值:fork调用1次,返回2次
- 子进程返回值是0
- 父进程返回值是新建子进程的pid
Q1: 子类继承了父类的?
A1: 子进程继承了父进程的几乎所有的属性:
实际UID,GID和有效UID,GID 环境变量 附加GID 调用exec()时的关闭标志 UID设置模式比特位 GID设置模式比特位 进程组号 会话ID 控制终端 当前工作目录 根目录 文件创建掩码UMASK 文件长度限制ULIMIT 预定值, 如优先级和任何其他的进程预定参数, 根据种类不同决定是否可以继承
Q2: 但子进程也有与父进程不同的属性
A2:
进程号:子进程号不同与任何一个活动的进程组号 子进程继承父进程的文件描述符或流时, 具有自己的一个拷贝并且与父进程和其它子进程共享该资源 子进程的用户时间和系统时间被初始化为0 子进程的超时时钟设置为0 子进程不继承父进程的记录锁 pending signals 也不会被继承:阻塞信号集初始化为空集
Q3: COW(写时拷贝机制) copy-on-write
A3:
① 为了节省物理内存,在调用fork生成子进程后,子进程与原进程共享同一块内存区
② 当且仅当某一个进程写操作时,系统才会为写进程复制一份新的物理页面,使父进程和子进程各自拥有自己物理页面
Q4: fork后父进程和子进程的栈变量,堆变量,全局变量,静态变量,常量是私有还是公有的?
A4: 全都是私有的(都是私有的!父子进程不共用这个变量,各自有一份。发生写时,会发生写时拷贝,创建一份)
1.6.守护进程
1.6.1.定义
守护进程是后台运行的,不与任何终端相关联的进程
1.6.2.会话/进程组
进程都有父进程,父进程也有父进程,这就形成了一个以init进程为根的家族树。 |
|
除此之外,进程还有其他层次关系:进程、进程组、会话。 |
|
进程组 |
进程组是一组相关进程的集合 |
会话 |
会话是一组相关进程组的集合 |
一个进程会有如下ID:进程ID(pid)、进程组ID(pgid)、会话ID(sid),默认情况下,新创建的进程会继承父进程的进程组ID和会话ID。 |
|
进程组、会话是为了支持shell作业控制而引入的概念 |
|
当有新的用户登录linux时,登陆进程会为这个用户创建一个会话,用户登录的shell就是会话的受进程,会话的首进程ID会作为整个会话的ID。(会话开始于用户登录,终止于用户退出,在此期间,所有的进程都属于这个会话) 在执行shell命令时,可以使用管道,让多个进程互相配合完成一项工作,这一组进程属于同一个进程组。 |
1.6.3.守护进程创建过程
- fork创建子进程,父进程调用exit退出
- 由于守护进程是脱离控制终端的,这样做,使得父进程先于子进程退出,子进程变为孤儿进程由init进程收养
- 子进程调用setsid()创建新会话
- 调用fork后,子进程全盘拷贝了父进程的会话、进程组、控制终端等,虽然父进程退出了,但是会话、进程组、控制终端等却没有改变。这不是真正意义上的独立开来,而,setsid函数就能够使子进程完全独立开来,即:使当前进程脱离原会话/原进程组/原控制终端的控制
- 再次fork一个子进程,父进程exit退出
-
现在,子进程已经成为无终端的会话组长,但它可以通过fork一个子进程重新申请打开一个控制终端,该新的子进程不是会话受进程,它不能重新打开控制终端。(也就是说,通过再次创建子进程结束当前进程,使进程不再使会话首进程来禁止进程重新打开控制终端)
-
-
在子进程中调用chdir(‘/’)让根目录称为子进程的工作目录
-
在子进程中调用umask重设文件权限掩码为0(相当于把权限放开)
-
在子进程中close不需要的文件描述符
-
用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源。
-
其实在上面的第二步之后,守护进程已经与所属的控制终端失去了联系,因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件已经失去了存在的价值,也应被关闭。(关闭失去价值的输入、输出、报错等对应的文件描述符)
-
1.7.信号SIGPIPE(broken pipe)
管道破裂 |
||
原因 |
向没有打开or意外终止的管道中写数据,写进程就会收到SIGPIPE信号 |
|
第一次写,写进程会收到RST标记,异常终止连接 在收到RST标记后,写进程仍然执意要再向已经关闭的管道中写数据,写进程就会收到SIGPIPE信号!该信号默认情况下,会使写进程终止。 |
||
使用场景 |
在服务器设计时,极有可能出现客户端已经挂掉了,服务端还会继续向客户端套接字写入数据的场景,这会导致产生SIGPIPE信号,导致服务端进程挂掉! 如果服务端不处理该SIGPIPE信号,服务端是很危险的! |
|
服务端增加捕捉SIGPIPE的代码 |
方案1: signal(SIGPIPE, SIG_IGN); // 忽略该信号, 不执行默认的操作 方案2: 注册SIGPIPE信号处理函数,处理该信号 |
2.进程通信
单工:只能向一个方向发送
半双工:可以两个方向,但是同一个时刻,只允许一个方向
全双工:可以两个方向,同一个时刻,允许两个方向
2.1.无名管道pipe()
只能用于具有亲缘关系的进程之间的通信(父子进程/兄弟进程) |
||
特 点 |
半双工(方向只能朝一个方向流动),一个读端,一个写端 |
|
先进先出 |
||
阻塞(管道里没数据,读端阻塞;管道满,写端阻塞) |
||
流管道 |
||
fd[0] 读端 fd[1]写端 |
||
2.2.有名管道fifo
fifo与pipe相比,因为fifo有路径名与之相关联,所以fifo可以在无关进程之间交换数据
使用:mkfifo( "fifo_name", 0666)
2.3.socket_pair
一种管道,全双工,两端都可以读写。与上面用法一样,唯一不同是全双工
2.4.消息队列
消息队列是存放在内核中的,一个消息队列由一个标识符(即队列ID)来标识 |
|
特点 |
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级 |
消息队列独立于发送进程/接收进程。进程终止时,消息队列以及其内容不会删除 |
|
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按照类型读取 |
|
msgget |
创建/打开消息队列,返回消息队列ID |
msgsnd |
添加消息,成功返回0 |
msgrcv |
读取消息,成功返回消息的长度。在读取数据时,参数type可以指定接收某个类型的数据 |
msgclt |
2.5.共享内存shm
指两个/多个进程共享的同一个内存区域 |
|
特点 |
共享内存时最快的一种IPC,因为进程是直接读写内存 |
大小 |
映射区的大小都是以页面大小PAGESIZE为单位的 |
2.5.1.效率对比
补充:(吞吐)内存 / 磁盘 = 10w
1. 管道/消息队列
管道/共享内存存在于内核中,因此,读写数据时,要涉及到用户空间和内核空间的来回拷贝
2. 共享内存
多个进程的虚拟地址空间的一段,映射到一块相同的物理内存。===> 这样,不同进程之间的通信就不需要经过内核
Q1: mmap将共享内存映射到进程的虚拟地址空间时有没有分配物理内存? 什么时候才真正分配物理内存给共享变量?
A1.
mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。这两种方式分配的都是虚拟内存,没有分配物理内存。
在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
2.6.信号量
与前面介绍过的IPC不同,它是一个计数器(用于实现进程间的互斥/同步) |
|
特点 |
信号量用于同步,若在进程间传递数据需要结合共享内存 |
信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作 |
2.7.信号
该信号是signal,信号处理函数
Q: 机制是什么?
A: 实际上是中断,参考下面中断
2.8.socket
网络套接字,四元组
3.线程同步
3.1.互斥锁\自旋锁\读写锁\原子操作\CAS\信号量
3.2.条件变量
与互斥锁一起使用 |
|
使用场景 |
取代采用while循环一直轮询等待的条件到来,减少CPU的开销,用条件标量等待条件的到来 |
当队列中没有数据时,使用条件变量让消费者wait(等待时,使用while循环,防止虚假唤醒) |
|
当生产者生产数据放到队列后,使用notify唤醒在条件变量上等待的消费者 |
3.3.一些问答
Q1: 同一个进程中,哪些东西是线程私有的?
A1:
线程ID:进程用tid来标识线程
寄存器:每个线程有自己的寄存器,该寄存器中保存了线程上下文,线程切换时,保存/恢复现场
线程自己的局部变量
errno:不同的线程有自己的错误码
线程的信号屏蔽码:由于每个线程所感兴趣的信号不同
线程优先级
Q2: errno的多线程问题
A2: 每个系统调用失败后,都会设置errno,在多线程程序中,由于每个线程的errno只属于自己。(每个线程都会分配内存errno,保存自己的错误码)
Q3: 常见错误码
A3:
错误码 |
错误信息 |
可能原因 |
处理 |
EAGAIN |
Try again |
读数据时,没有数据在底层缓冲时 |
while循环读取 |
EPIPE |
broken pipe |
信号SIGPIPE(Broken pipe) |
|
EINTR |
被其他系统调用中断 |
处理很简单,再重新执行一次 |
|
ETIMEOUT |
连接超时 |
||
EADDRINUSE |
地址已被占用 |
设置地址复用标志 |
|
ENOTCONN |
连接建立失败 |
3.4. 研讨: 线程数究竟设多少合理
4.五种网络IO模型
4.1.什么是IO
Q: 天天说IO,什么是IO呢?我们经常使用的read/write,到底干了啥?
A:
个人理解,read/write数据实际上,数据是存放在内核中的缓冲区中的
-
读数据,实际上是用户将内核缓冲区中的数据读到应用层缓冲区
-
写数据,实际上是用户将应用层缓冲区中的数据,写入到内核缓冲区
4.2.网络IO模型
4.2.1.阻塞IO
当调用read函数阻塞读取数据时,如果此时内核缓冲区中没有数据,就阻塞等待,这就是阻塞IO模型。
当内核缓冲区中有数据时,read函数将读到数据,就会将数据拷贝到应用层缓冲区。
4.2.2.非阻塞IO
非阻塞read数据时,当内核缓冲区中没有数据时,程序将不会一直阻塞在read函,read函数会立即返回(EWOULDBLOCK)。
此时程序员可以控制程序去做其他事情。在此期间,程序员要不断的轮询调用read查看是否有数据到来,直到有数据到来,read才读取数据。
缺点:死等待,没有数据CPU一直等待,CPU利用率低
4.2.3.信号驱动IO
原理
- 应用程序需要建立信号处理程序(当数据到来时,从内核中拷贝数据到应用层)
- 当内核中数据准备好的时候,会给应用程序发送一个信号,SIGIO
- 会触发注册的信号处理函数,将数据拷贝走
缺点:忙等待,消耗CPU资源
拉模式:需要应用程序注册回调函数,【主动】将数据从内核空间拷贝到应用空间,是一种【拉模式】
4.2.4.异步IO/完成端口
可以看到,这种模式和“信号驱动IO”最大的区别是:当内核中数据准备好后,是内核主动的将数据拷贝到应用空间(而不是由应用层自己注册的回调函数,将数据拷贝到应用层空间)。这是一种【推模式】。
内核将数据拷贝到应用层空间后,将会通知你,你之后就可以处理接下来的逻辑了。
- 当应用程序调用aio_read时,内核一方面去取数据报内容返回,另一方面将程序控制权还给应用程序
- 应用程序继续执行其他的任务,是一种非阻塞的状态
- 当内核中的数据报就绪时,由内核主动地将数据报拷贝到应用程序中,返回aio_read中定义好的函数处理程序
异步IO与同步IO的核心区别:异步IO无需自己负责读写,OS会自动将数据从内核空间拷贝到用户空间
4.2.5.IO多路复用
IO多路复用有一个文件描述符集合,对这个集合中的每个元素进行循环监听,处理就绪的文件描述符
select/poll/epoll的区别
epoll |
select |
poll |
|||
时间复杂度 |
O(1) |
select/poll:当有I/O时间发生时,不能知道是哪个fd触发了,只能无差别的轮询所有fd。 O(N) |
|||
底层实现 |
红黑树、就绪链表 |
数组 |
链表 |
||
poll与select没有本质上的区别, 但是它没有最大连接数的限制,原因是它是基于链表来存储的。 |
|||||
select缺点①②③ poll缺点②③ |
①最大连接数有上限 ②对fd集合需要轮询扫描③需要将fd集合从内核和用户空间来回拷贝 |
||||
epoll |
不是轮询方式监听fd,只有活跃的fd才会调用callback |
epoll底层原理
epoll_create |
创建一个epoll文件描述符,底层同时创建一棵红黑树、一个就绪链表rdlist |
红黑树存储了所有监控的文件描述符 就绪链表存储就绪文件描述符 |
|
epoll_clt |
删除:将文件描述符从红黑树上摘除 插入:先查看红黑树是否有该fd:如果有,不再插入;如果没有,插入。(epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知) |
epoll_wait |
只是从就绪链表中取出元素,将该元素上的事件复制到用户态区间(使用mmap提高效率) |
epoll水平触发/边缘触发
根本区别:事件触发后,是否从红黑树中摘除
水平触发 |
只要高电平or低电平时,才会触发。即:有数据可读时,就会一直触发 (事件没处理完,不会从激活链表中摘除,会一直触发,直到事件处理完,才移除激活队列) |
边缘触发 |
只有电平发生变化时,才会触发 (事件来到时,只触发一次,就从激活链表中摘除) |
举例 |
一个管道收到了1kb的数据,epoll会立即返回,此时读了512字节数据,然后再次调用epoll。 |
①如果是水平触发的,epoll会立即返回,因为有数据准备好了 |
|
②如果是边缘触发的不会立即返回,因为此时虽然有数据可读,但是已经触发了一次通知,在这次通知到现在还没有新的数据到来,直到有新的数据到来时,epoll才会返回。 |
|
当epoll网络模型时: ①对于读事件,如果用水平触发不用担心数据有没有读完,因为下次epoll返回时,没有读完的socket依然会被返回。但是要注意这种模式下的写事件,因为是水平触发,每次socket可写时,epoll都会返回,当我们写的数据包过大时,一次写不完,要多次才能写完或者每次socket写都写一个很小的数据包时,每次写都会被epoll检测到,因此长期关注socket写事件会无故cpu消耗过大甚至导致cpu跑满,所以在水平触发模式下一般不关注socket可写事件而是通过调用socket write或者send api函数来写socket。 说到这,可以看到水平触发在效率上是没有边缘触发高的,因为每一次socket读或写可能被返回两次甚至多次,所以有时候也会用到边缘触发。但是这种模式下在读数据的时候一定要注意,因为如果一次没有把数据读完,且在socket没有新的数据可读时,epoll就不回返回了,将导致数据没有读取完。 因此,对于边缘触发,必须:①使用非阻塞IO ②要while循环一次性读写完全部数据 |
EPOLL_ONESHOT与边缘触发
解释 EPOLL_ONESHOT |
因为et模式需要循环读取,但是在读取过程中,如果有新的事件到达,很可能触发了其他线程来处理这个socket,那就乱了 |
原理 |
一旦事件触发后,就调用epoll_clt将事件从红黑树上摘除,直到处理完该事件,再调用epoll_clt将该事件添加到红黑树。 这样,就可以保证同一个fd事件在同一时刻只能触发一次! |
epoll可以监听普通文件么?
答: 不行,返回Operation not allow错误(错误码EPERM)。
原因:出现 Operation not allow 的原因
就是:被监听的文件没有提供 poll
接口
开启多个进程,创建多个epoll监听同一个fd,执行epoll_wait,当事件fd触发后,会不会发生惊群?
答:
① 默认情况下,会发生惊群
② 解决方案:为该fd设置SO_REUSEPORT
- 修改 bind 系统调用实现,以便支持多个进程可以绑定到相同的 IP 和端口
- 修改处理新建连接的实现,查找 listener 的时候,能够支持在监听相同 IP 和端口的多个 socket 之间均衡选择(不产生惊群,选择一个提供服务)。且同一个源ip和端口的所有包,必须要发送到同一个listener
SO_REUSEPORT\SO_REUSEADDR区别
1. 功能:
SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用
SO_REUSEPORT是允许多个socket绑定到同一个ip+port上
2. 两者使用场景完全不同
SO_REUSEADDR这个套接字选项通知内核,如果端口忙,但TCP状态位于TIME_WAIT,可以重用端口。这个一般用于当你的程序停止后想立即重启的时候,如果没有设定这个选项,会报错EADDRINUSE,需要等到TIME_WAIT结束才能重新绑定到同一个ip+port上
SO_REUSEPORT用于多核环境下,允许多个线程或者进程绑定和监听同一个ip+port,无论UDP、TCP(以及TCP是什么状态)
(3)对于多播,两者意义相同。
4.3.DMA 技术
4.3.1.为什么要有DMA技术?(无DMA有什么弊端)
在没有 DMA 技术前,I/O 的过程是这样的:
可以看到,整个数据的传输过程,都要需要 CPU 亲自参与搬运数据的过程,而且这个过程,CPU 是不能做其他事情的。
简单的搬运几个字符数据那没问题,但是如果用千兆网卡或者硬盘传输大量数据的时候,都用 CPU 来搬运的话,肯定忙不过来。
4.3.2.介绍
介绍
DMA 技术很容易理解,本质上,DMA 技术就是我们在主板上放一块独立的芯片。在进行内存和 I/O 设备的数据传输的时候,我们不再通过 CPU 来控制数据传输,而直接通过 DMA 控制器(DMA Controller,简称 DMAC)。这块芯片,我们可以认为它其实就是一个协处理器(Co-Processor)。
DMA技术,简单理解就是,在进行 I/O 设备和内存 的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,这样 CPU 就可以去处理别的事务。
注意:这里面的“协”字。DMAC 是在“协助”CPU,完成对应的数据传输工作。在 DMAC 控制数据传输的过程中,DMAC 还是被 CPU 控制,只是数据的拷贝行为不再由 CPU 来完成。
使用场景
DMAC 的价值在如下情况中尤其明显:
- 当我们要传输的数据特别大、速度特别快
- 传输的数据特别小、速度特别慢的时候
比如说,
- 用千兆网卡或者硬盘传输大量数据的时候,如果都用 CPU 来搬运的话,肯定忙不过来,所以可以选择 DMAC
- 当数据传输很慢的时候,DMAC 可以等数据到齐了,再发送信号,给到 CPU 去处理,而不是让 CPU 在那里忙等待。
作用
DMAC 代替了 CPU 负责「内存与磁盘」、「内存与网卡」之间的数据搬运,CPU 作为 DMAC 的控制者。
但是 DMAC 有其局限性,DMAC 仅仅能用于「设备间」交换数据时进行数据拷贝,但是「设备内部」的数据拷贝还需要 CPU 来亲力亲为。例如, CPU 需要负责内核空间与用户空间之间的数据拷贝(内存内部的拷贝),如下图所示:
上图中的 read buffer 也就是 page cache,socket buffer 也就是 Socket 缓冲区。
4.3.3.有DMA技术的数据拷贝
可以看到, CPU 不再参与「将数据从磁盘控制器缓冲区搬运到内核空间」的工作,这部分工作全程由 DMA 完成
4.4.零拷贝
零拷贝的特点是 CPU 不全程负责内存中的数据写入其他组件,CPU 仅仅起到管理的作用。
注意:零拷贝不是不进行拷贝,而是 CPU 不再全程负责数据拷贝时的搬运工作。如果数据本身不在内存中,那么必须先通过某种方式拷贝到内存中(这个过程 CPU 可以仅仅负责管理,DMAC 来负责具体数据拷贝),因为数据只有在内存中,才能被转移,才能被 CPU 直接读取计算。
零拷贝技术的具体实现方式有很多,例如:sendfile、mmap、直接 Direct I/O、splice
前瞻性的技术总结:
- DMA 技术:DMA 负责内存与其他组件之间的数据拷贝,CPU 仅需负责管理,而无需负责全程的数据拷贝
- 使用 page cache 的 zero copy:
- sendfile:一次代替 read/write 系统调用,通过使用 DMA 技术以及传递文件描述符,实现了 zero copy
- mmap:仅代替 read 系统调用,将内核空间地址映射为用户空间地址,write 操作直接作用于内核空间。通过 DMA 技术以及地址映射技术,用户空间与内核空间无须数据拷贝,实现了 zero copy
- 不使用 page cache 的 Direct I/O:读写操作直接在磁盘上进行,不使用 page cache 机制,通常结合用户空间的用户缓存使用。通过 DMA 技术直接与磁盘/网卡进行数据交互,实现了 zero copy
4.4.1.传统IO流程(一次读写)
从流程图中可以看出传统的IO流程包括 4次上下文的切换、4次拷贝数据
如果没有优化,读取磁盘数据,再通过网卡传输的场景性能比较差:
4 次 copy:
- 物理设备 <-> 内存:
- CPU 负责将数据从磁盘搬运到内核空间的 Page Cache 中;
- CPU 负责将数据从内核空间的 Socket 缓冲区搬运到的网络中;
- 内存内部拷贝:
- CPU 负责将数据从内核空间的 Page Cache 搬运到用户空间的缓冲区;
- CPU 负责将数据从用户空间的缓冲区搬运到内核空间的 Socket 缓冲区中;
4 次上下文切换:
- read 系统调用时:用户态切换到内核态;
- read 系统调用完毕:内核态切换回用户态;
- write 系统调用时:用户态切换到内核态;
- write 系统调用完毕:内核态切换回用户态;
我们不免发出抱怨:
- CPU 全程负责内存内部的数据拷贝还可以接受,因为内存的数据拷贝效率还行(不过还是比 CPU 慢很多),但是如果要 CPU 全程负责内存与磁盘、内存与网卡的数据拷贝,这将难以接受,因为磁盘、网卡的 I/O 速度远小于内存;
- 4 次 copy 太多了,4 次上下文切换也太频繁了;
4.4.2.零拷贝
方式1: mmap+write
4次上下文切换、3次拷贝数据(一次CPU拷贝、两次DMA拷贝)
mmap
- 用户进程调用mmap发起IO调用(切换1: 上下文从用户态切换到内核态)
- CPU利用DMA控制器,将数据从硬盘拷贝到内核缓冲区
- (切换2: 上下文从内核状态切回用户态)mmap方法返回
write
- 用户进程调用write发起IO调用(切换3: 上下文从用户态切换到内核态)
- CPU将内核缓冲区的数据拷贝到socket缓冲区
- CPU利用DMA控制器,将数据从socket缓冲区拷贝到网卡(切换4: 上下文从内核态切换到用户态),write方法返回
方式2: sendfile
// out_fd 待写入内容的文件描述符
// in_fd 待读出内容的文件描述符
// offset 文件偏移量
// count 指定在out_fd和in_fd之间传输的字节数
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
2次上下文切换、3次拷贝(两次DMA拷贝、一次CPU拷贝)
- 用户进程发起sendfile系统调用(上下文从用户态切换到内核态)
- DMA将数据从硬盘拷贝到内核缓冲区
- CPU将内核缓冲区中的数据拷贝到socket缓冲区
- DMA异步把数据从socket缓冲器拷贝到网卡
- (上下文从内核态切换到用户态),sendflie函数返回
方式3: sendfile + DMA scatter/gather实现了零拷贝
在linux2.4版本后,对sendflie做了优化升级,引入SG-DMA技术,其实就是对DMA拷贝加入了scatter/gather操作,它可以直接从内核空间缓冲区中将数据读取到网卡,这样的话还可以省去CPU拷贝
2次上下文切换、2次数据拷贝(2次DMA拷贝):这是真正的零拷贝技术,全程没有CPU拷贝,所有的数据都是通过DMA进行传输的
- 用户进程发起sendfile系统调用(上下文从用户态切换到内核态)
- DMA将数据从硬盘拷贝到内核缓冲区
- CPU将内核缓冲区中的文件描述符信息(包括内核缓冲区的内存地址和偏移量)直接发送到socket缓冲区
- DMA根据文件描述符信息直接把数据从内核缓冲区拷贝到网卡
- (上下文从内核态切换到用户态),sendflie函数返回
5.操作系统
5.1.中断
5.1.1.中断触发的方式
- 主动触发(程序中通过函数接口,通知CPU进行中断处理)
- 内部中断(数据溢出、非法地址访问、未识别的操作码…)
- 外部中断(输入/输出设备、硬件设备故障…)
5.1.2.硬件中断处理函数会做如下的事情:
- 需要先「暂时屏蔽中断」,表示已经知道内存中有数据了,告诉网卡下次再收到数据包直接写内存就可以了,不要再通知 CPU 了,这样可以提高效率,避免 CPU 不停的被中断。
- 接着,发起「软中断」,然后恢复刚才屏蔽的中断。
至此,硬件中断处理函数的工作就已经完成。
5.1.2.中断的触发流程
- 某些操作,产生电信号,通过硬件上的中断引脚,被传递到中断控制器,中断控制器会向CPU发送中断请求
- CPU收到中断请求后,判断是否响应中断
- (保护现场)中断触发,将当前正在运行的程序上下文保存到寄存器/堆栈中
- 将当前断点压入堆栈:程序的下一条指令、寄存器的值
- (中断处理)CPU寻找中断服务程序的入口地址,跳转到中断程序运行
- 根据中断号,在中断向量表中找出相应的中断程序的入口地址,跳转到中断程序执行
- (恢复现场)中断处理结束后,CPU会将之前保存在堆栈中的断点和寄存器重新恢复
- 恢复断点的动作:将先前的指针和寄存器的内容从堆栈中弹出
- CPU继续运行之前被打断的程序
5.2.文件系统
5.2.1.文件系统的基本组成
文件系统是操作系统中负责管理持久化数据的子系统,说简单点,就是负责把用户的文件存到磁盘硬件中,因为即使计算机断电了,磁盘里的数据并不会丢失,所以可以持久化的保存文件。
文件系统的基本数据单位是文件,它的目的是对磁盘上的文件进行组织管理,那组织的方式不同,就会形成不同的文件系统。
Linux文件系统会为每个文件分配2个数据结构:目录项(directory entry)、索引节点(index node),它们主要用来记录文件的目录层次结构、元信息
- 索引节点inode
- 记录文件的元信息,比如inode编号、文件大小、访问权限、创建时间、数据在磁盘的位置等等
- 索引节点inode是文件的唯一标识,它们之间一一对应,也同样被存储在硬盘中,所以索引节点同样占用磁盘空间(但是一般情况下,为了加速文件的访问,会将它加载到内存中)
- 目录项entry
- 记录文件的名字、索引节点指针、其他目录项的层级关联关系(多个目录项关联起来,形成目录结构)
- 但它与索引节点不同的是,目录项是内核维护的一个数据结构,不存放于磁盘,而是缓存在内存
注意1:目录也是文件,也是用索引节点唯一标识,和普通文件不同的是,普通文件在磁盘里面保存的是文件数据,而目录文件在磁盘里面保存的是子目录和文件。
注意2:文件的名字,是存放在目录项的,而不是存放在inode的
Q1: 目录和目录项是一个东西么?
A1: 虽然名字很接近,但是它们不是一个东西
- 目录是个文件,持久化存储在磁盘
- 目录项entry是内核一个数据结构,缓存在内存(上面已经说过)
Q2: 磁盘格式化
A2: 会被分成3个存储区域,分别是超级块、索引节点区、数据块区
- 超级块,用来存储文件系统的详细信息,比如块个数、块大小、空闲块等等(当文件系统挂载时,载入内存)
- 索引节点区,用来存储索引节点(当文件被访问时载入内存)
- 数据块区,用来存储文件或目录数据
Q3: 那文件数据是如何存储在磁盘的呢?
A3:
磁盘读写的最小单位是扇区,扇区的大小只有512B,很明显,每次读写都以这么小的单位,那么读写的效率会非常低。
所以,文件系统把多个扇区组成一个逻辑块,每次读写最小单位就是逻辑块(数据块),Linux的逻辑大小为4KB,也就是一次性读写8个扇区,这将大大提高了磁盘的读写速率。
以上就是索引节点inode、目录项entry以及文件数据的关系,下面这个图就很好的展示了它们之间的关系。
5.2.2.虚拟文件系统
文件系统的种类众多,而操作系统希望对用户提供一个统一的接口,于是在用户层与文件系统层引入中间层,这个中间层称为虚拟文件系统VFS。
VFS定义了一组所有文件系统都支持的数据结构和标准接口,这样程序员就不需要了解文件系统的工作原理,只需要了解VFS提供的统一接口即可。
文件系统首先要挂载到某个目录才可以正常使用,比如,linix系统在启动时,会把文件系统挂载到根目录。
文件系统分类,根据存储位置不同,分为3类:
- 磁盘的文件系统
- 直接把数据存储在磁盘中,比如EXT2/3/4、XFS
- 网络的文件系统
- 用来访问其他计算机主机数据的文件系统,比如NFS、SMB等等
- 内存的文件系统
- 这类文件系统的数据不是存储在硬盘的,而是占用内存空间,比如常用的/proc和/sys文件系统都属于这一类,读写这类文件,实际上是读写内核中相关的数据结构
5.2.3.文件I/O
缓冲区与非缓冲区IO
这里所说的「缓冲」特指标准库内部实现的缓冲(用户空间的IO缓冲区),目的:减少系统调用的次数
- 缓冲 I/O:利用的是标准库的缓存实现文件的加速访问,而标准库再通过系统调用访问文件
- 非缓冲 I/O:直接通过系统调用访问文件,不经过标准库缓存
直接与非直接IO
我们都知道磁盘IO是非常慢的,所以Linux内核为了减少磁盘IO次数,在系统调用后,会把用户数据拷贝到内核中缓存起来,这个内核缓冲空间也就是页缓存,只有当缓存满足某些条件时,才发起磁盘IO的请求。
那么,根据是否利用操作系统的内核缓冲区,可以将文件IO分为直接IO与非直接IO
1. 直接IO:不会发生内核缓存和用户程序之间的数据复制,而是直接经过文件系统访问磁盘(O_DIRECT)
2. 非直接IO:读操作时,数据从内核缓存中拷贝给用户程序;写操作时,数据从用户程序拷贝给内核缓存,再由内核决定什么时候写入数据到磁盘
5.2.4.数据会丢么?***
5.3.死锁
5.3.1.操作系统的死锁产生必要条件有什么?
互斥、请求与保持、不可剥夺、环路等待
5.3.2.死锁避免的方法
死锁避免的方法:互斥不能被破坏,另外的3个条件可以被破坏
- 请求与保持:资源一次性分配
- 可剥夺资源:当进程新的资源未得到满足时,释放现在已经占有的资源
- 环路等待:资源有序分配
5.3.3.死锁发生了,怎么解决:只能强制剥夺
- 资源剥夺法:挂起暂时放到外存上某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但是应防止被挂起的进程长时间得不到资源而饥饿
- 撤销/终止进程法:强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源(这种方式的优点是实现简单,但所付出的代价可能会很大。因为有些进程可能已经运行了很长时间,已经接近结束了,一旦被终止可谓功亏一篑,以后还得从头再来)
- 进程回退法:让一个或多个死锁进程回退到足以避免死锁的地步。这就要求系统要记录进程的历史信息,设置还原点
6.计算机网络
6.1.ISO/OSI七层模型
1 |
物理层 |
设备之间比特流的传输,物理媒介 |
||
2 |
数据链路层 |
数据成帧:将IP包封装成帧 透明传输: 差错控制:就检验码、循环冗余检验码CRC MTU:最大传输单元1500,数据链路层的数据帧最大限制 |
||
3 |
网络层 |
提供逻辑IP地址,路由选路;子网划分 ICMP报告传输中的错误(主机不可达/重定向) |
IP/ICMP ARP/RARP |
|
4 |
传输层 |
提供处于网络连接中两台计算机之间的数据传输 |
TCP/UDP |
|
5 |
会话层 |
对应用会话的管理/同步 |
RPC |
|
6 |
表示层 |
数据表现形式:加密/解密,压缩/解压缩 |
JPEG/GIF/ASCII |
|
7 |
应用层 |
用户接口 |
HTTP/FTP/DNS/Telnet/NFS |
Q1: 应用层协议,哪些协议使用了TCP?哪些协议使用的UDP?
A1:
TCP |
HTTP, SSL ,FTP, SMTP, POP3, IMAP |
UDP |
DNS |
Q2: Ping/Traceroute实现原理
A2:
Ping是ICMP应用实例,可以检查网络是否通畅或者查看网络连接速度,便于分析和判定网络故障 |
ping 命令会发送一个 ICMP,接收端在收到此 ICMP 后产生 ICMPecho (ICMP回声应答) ,通过此过程来确定两台网络机器是否连通,时延是多少 ①构建数据包:ICMP、IP、以太帧 ②传输给目标主机 ③解析数据包:以太帧、IP、ICMP ④发送应答报文 |
Traceroute也是ICMP应用实例,用于:路由跟踪,用来跟踪一个分组从源点到终点的整个过程 |
Traceroute是通过ICMP协议中的时间超时差错报告报文来实现的(它从源主机到目的主机发送一连串的IP数据报p1-pn,并且数据报是无法交付的udp数据报。过程见下: 1. 第一个数据报的TTL=1,当这个数据报转发到第一个路由器的时候,路由器收到后TTL减一,减一之后,TTL变成0,并会向源主机返回一个超时差错报告报文 2. 第二个数据报的TTL=2,当这个数据报转发到第二个路由器的时候,TTL变成0,并会向源主机返回一个超时差错报告报文 3. 第K个数据报的行为和步骤1,2一致 4. 直到第N个数据报pn到达目的主机,但是数据报无法交付,此时目的主机会向源主机返回终点不可达差错报告报文 通过这种方式,源主机就可以通过发送过来的“超时差错报文和终点不可达差错报文”来得到经过的路由器以及往返时间等信息,达到路由跟踪的目的。 |
Q3: ARP/RARP 地址解析协议 Address Resolution Protocol
A3:
作用:在以太网环境中,数据的传输所依赖的是MAC地址而非IP地址,而将已知IP地址转换为MAC地址的工作是由ARP协议来完成的
- 主机A,(采用广播的方式)发送ARP请求包询问:目标IP=172.20.1.2,MAC地址是?
- IP地址=172.20.1.2目标主机,收到请求后,会返回数据包,告诉A主机:我是IP=172.20.1.2,我的MAC=xx:xx:xx:xx:xx
Q4: 在浏览器输入URL回车后,发生了什么?
A4:
- URL解析
- 生成HTTP请求消息
- 对
URL
进行解析之后,浏览器确定了 Web 服务器和文件名,接下来就是根据这些信息来生成 HTTP 请求消息了
- 对
- DNS解析(真正的地址查询):网址 ==> IP地址
- 通过浏览器解析 URL 并生成 HTTP 消息后,需要委托操作系统将消息发送给
Web
服务器 - 但在发送之前,还有一项工作需要完成,那就是查询服务器域名对应的 IP 地址,因为委托操作系统发送消息时,必须提供通信对象的 IP 地址
- 比如我们打电话的时候,必须要知道对方的电话号码,但由于电话号码难以记忆,所以通常我们会将对方电话号 + 姓名保存在通讯录里。==> 所以,有一种服务器就专门保存了
Web
服务器域名与IP
的对应关系,它就是DNS
服务器- 域名的层级关系
- DNS 中的域名都是用句点来分隔的,比如
www.server.com
,这里的句点代表了不同层次之间的界限 - 在域名中,越靠右的位置表示其层级越高
- 实际上域名最后还有一个点,比如
www.server.com.
,这个最后的一个点代表根域名
- DNS 中的域名都是用句点来分隔的,比如
- 域名解析的工作流程
- 客户端首先会发出一个 DNS 请求,问 www.server.com 的 IP 是啥,并发给本地 DNS 服务器
- 浏览器缓存:浏览器会先看自身有没有对这个域名的缓存,如果有,就直接返回
- 操作系统缓存:如果没有,就去问操作系统,操作系统也会去看自己的缓存,如果有,就直接返回
- hosts文件:如果没有,再去 hosts 文件看,也没有,才会去问「本地 DNS 服务器」
- 本地DNS服务器,执行域名解析
- 域名的层级关系
- 通过浏览器解析 URL 并生成 HTTP 消息后,需要委托操作系统将消息发送给
- 通过 DNS 获取到 IP 后,就可以把 HTTP 的传输工作交给操作系统中的协议栈
- 出口 ==> 网卡
- 介绍
- 网络包只是存放在内存中的一串二进制数字信息,没有办法直接发送给对方。因此,我们需要将数字信息转换为电信号,才能在网线上传输,也就是说,这才是真正的数据发送过程。
- 负责执行这一操作的是网卡,要控制网卡还需要靠网卡驱动程序。
- 网卡驱动获取网络包之后,会将其复制到网卡内的缓存区中,接着会在其开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列。
-
最后网卡会将包转为电信号,通过网线发送出去
- 介绍
- 送别者 ==> 交换机
- 交换机的设计是将网络包原样转发到目的地。交换机工作在 MAC 层,也称为二层网络设备
- 首先,电信号到达网线接口,交换机里的模块进行接收,接下来交换机里的模块将电信号转换为数字信号
- 然后通过包末尾的
FCS
校验错误,如果没问题则放到缓冲区。这部分操作基本和计算机的网卡相同,但交换机的工作方式和网卡不同。
- 交换机的设计是将网络包原样转发到目的地。交换机工作在 MAC 层,也称为二层网络设备
- 出境大门 ==> 路由器
- 互相扒皮 ==> 服务器与客户端
Q5:Linux接收网络包的流程
A5:
网卡是计算机里的一个硬件,专门负责接收和发送网络包,当网卡接收到一个网络包后,会通过 DMA 技术,将网络包写入到指定的内存地址,也就是写入到 Ring Buffer ,这个是一个环形缓冲区,接着就会告诉操作系统这个网络包已经到达。
那应该怎么告诉操作系统这个网络包已经到达了呢?
最简单的一种方式就是触发中断,也就是每当网卡收到一个网络包,就触发一个中断告诉操作系统。
但是,这存在一个问题,在高性能网络场景下,网络包的数量会非常多,那么就会触发非常多的中断,要知道当 CPU 收到了中断,就会停下手里的事情,而去处理这些网络包,处理完毕后,才会回去继续其他事情,那么频繁地触发中断,则会导致 CPU 一直没完没了的处理中断,而导致其他任务可能无法继续前进,从而影响系统的整体效率。
所以为了解决频繁中断带来的性能开销,Linux 内核在 2.6 版本中引入了 NAPI 机制,它是混合「中断和轮询」的方式来接收网络包,它的核心概念就是不采用中断的方式读取数据,而是①首先采用中断唤醒数据接收的服务程序,②然后 poll
的方法来轮询数据。
因此,当有网络包到达时,①会通过 DMA 技术,将网络包写入到指定的内存地址,②接着网卡向 CPU 发起硬件中断,③当 CPU 收到硬件中断请求后,根据中断表,调用已经注册的中断处理函数
硬件中断处理函数会做如下的事情:
- 需要先「暂时屏蔽中断」,表示已经知道内存中有数据了,告诉网卡下次再收到数据包直接写内存就可以了,不要再通知 CPU 了,这样可以提高效率,避免 CPU 不停的被中断。
- 接着,发起「软中断」,然后恢复刚才屏蔽的中断。
至此,硬件中断处理函数的工作就已经完成。
硬件中断处理函数做的事情很少,主要耗时的工作都交给软中断处理函数了。
软中断的处理:
内核中的 ksoftirqd 线程专门负责软中断的处理,当 ksoftirqd 内核线程收到软中断后,就会来轮询处理数据。
ksoftirqd 线程会从 Ring Buffer 中获取一个数据帧,用 sk_buff 表示,从而可以作为一个网络包交给网络协议栈进行逐层处理。
7.性能
❓如何分析进程的性能手段,如何去优化它?
❓Linux自带的iostat会输出什么?一般会关注哪些列?
7.1.top
Q:top会输出哪些信息
Q:CPU负载是什么意思?
load avg:显示的是一段时间内,正在执行和等待使用CPU的平均进程数
一般情况下,load average == CPU核心个数,才认为CPU被充分利用。当load average > CPU核心个数,系统出现过载
Q:CPU占比具体包含了什么?
usr:CPU在用户态运行时间占比。越高,说明有应用程序比较繁忙
sys:CPU在内核态运行时间占比。越高,说明内核比较繁忙
nice:改变过优先级的进程占用CPU百分比。越高,说明进程可能频繁调整优先级
idle:空闲CPU百分比。user + nice + idle 应该接近 100%。
iowait:等待输入输出的CPU时间百分比。越高,说明磁盘IO是瓶颈
hi(hardware irq):硬件中断
si(software irq):软件中断
st(steal time)实时:只有 Linux 在作为虚拟机运行时 steal 才是有意义的。它表示虚机等待 CPU 资源的时间(虚机分到的是虚拟 CPU,当需要真实的 CPU 时,可能真实的 CPU 正在运行其它虚机的任务,所以需要等待)
Q:问题的原因分别是什么?怎么排查?
- usr CPU 和 Nice CPU 高,说明用户态进程占用了较多的 CPU,所以应该着重排查进程的性能问题
- sys CPU 高,说明内核态占用了较多的 CPU所以应该着重排查内核线程或者系统调用的性能问题
- 若 %iowait 的值过高,表示硬盘存在I/O瓶颈
- 若 %idle 的值高但系统响应慢时,有可能是 CPU 等待分配内存,此时应加大内存容量
- 若 %idle 的值持续低于1,则系统的 CPU 处理能力相对较低,表明系统中最需要解决的资源是 CPU