目录

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上运行时,则称该进程处于运行状态。

知识体系之APUE/内核编程-编程知识网

就绪

当一个进程获得了除CPU以外的一切所需资源,一旦得到处理机即可运行,则称此进程处于就绪状态。

阻塞

一个进程正在等待某一事件发生(例如请求I/O而等待I/O完成等)而暂时停止运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。

1.1.1.进程调度的方式

抢占式调度:当一个进程正在CPU上执行时,若有某个更为重要或紧迫的进程(优先级更高)的进程需要使用CPU,则立即暂停正在执行的进程,将CPU分配给这个更重要的进程

非抢占式调度:当一个进程正在CPU上执行时,即使有某个更为重要或者紧迫的进程进入就绪队列,仍然让正在执行的进程继续执行,直到该进程完成或发生某种事件而进入阻塞态时,才把CPU分配给更为重要或紧迫(优先级更高)的进程

1.1.2.几种典型的调度算法

先来先服务调度算法

短作业优先调度算法

优先级调度算法

时间轮旋转调度算法

多级反馈队列调度算法

知识体系之APUE/内核编程-编程知识网

        多个就绪队列(按照“时间片大小”划分优先级):第2级队列的时间片要比第1级队列的时间片长一倍,…第i+1级队列的时间片要比第i级队列的时间片长一倍

  1. 当一个新的进程进入内存后,首先将它放入第一队列的末尾,按照FCFS原则排队等待调度
  2. 当轮到该进程执行时候
    1. case1: 如果它能在一个时间片内完成,便可以撤出队列系统
    2. case2: 如果它在一个时间片内未完成,再将其放到下一个级别的队列尾部(同理,当第i个队列的进程未执行完,将其放到第i+1队列的尾部)
    3. case3: 仅当第一级队列为空时,调度程序才调度第二级队列中的进程进行(仅当第1到i-1级队列均为空,才会调度第i级队列中的进程运行)
    4. case4: 若处理机正在执行第i级队列中的某个进程,此时,又有新的进程进入优先级较高的队列(第1到i-1级的任意一级):则,该进程将抢占正在运行的处理机,即由调度程序把正在运行的进程放回第i级队列末尾,把CPU分配给新到的更高优先级进程

1.2.僵尸进程/孤儿进程

僵尸进程:没有被回收的进程

  1. 一个进程fork创建子进程,子进程退出后,父进程没有调用wait/waitpid回收子进程资源,这个子进程就叫做僵尸进程

  2. 尸体会占用系统中的进程号/进程资源,一旦太多将会耗尽系统资源

1. 当子进程退出时,将发送SIGCHLD信号

2. 父进程使用wait/waitpid,回收子进程的资源

wait

阻塞

waitpid

提供了非阻塞方式;可以回收指定子进程(-1:所有子进程,>0等待pid子进程;==0,进程组)

孤儿进程:父进程退出了,子进程还在

  1. 它会被init进程收养

  2. 孤儿进程并不存在问题,它只是没有了父进程而已(一般守护进程都这么玩)

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次

  1. 子进程返回值是0
  2. 父进程返回值是新建子进程的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.守护进程创建过程

知识体系之APUE/内核编程-编程知识网

  1. fork创建子进程,父进程调用exit退出
    • 由于守护进程是脱离控制终端的,这样做,使得父进程先于子进程退出,子进程变为孤儿进程由init进程收养
  2. 子进程调用setsid()创建新会话
    1. 调用fork后,子进程全盘拷贝了父进程的会话、进程组、控制终端等,虽然父进程退出了,但是会话、进程组、控制终端等却没有改变。这不是真正意义上的独立开来,而,setsid函数就能够使子进程完全独立开来,即:使当前进程脱离原会话/原进程组/原控制终端的控制
  3. 再次fork一个子进程,父进程exit退出
    1. 现在,子进程已经成为无终端的会话组长,但它可以通过fork一个子进程重新申请打开一个控制终端,该新的子进程不是会话受进程,它不能重新打开控制终端。(也就是说,通过再次创建子进程结束当前进程,使进程不再使会话首进程来禁止进程重新打开控制终端)

  4. 在子进程中调用chdir(‘/’)让根目录称为子进程的工作目录

  5. 在子进程中调用umask重设文件权限掩码为0(相当于把权限放开)

  6. 在子进程中close不需要的文件描述符

    1. 用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源。

    2. 其实在上面的第二步之后,守护进程已经与所属的控制终端失去了联系,因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件已经失去了存在的价值,也应被关闭。(关闭失去价值的输入、输出、报错等对应的文件描述符

知识体系之APUE/内核编程-编程知识网

1.7.信号SIGPIPE(broken pipe) 

管道破裂

原因

向没有打开or意外终止的管道中写数据,写进程就会收到SIGPIPE信号

第一次写,写进程会收到RST标记,异常终止连接

在收到RST标记后,写进程仍然执意要再向已经关闭的管道中写数据,写进程就会收到SIGPIPE信号!该信号默认情况下,会使写进程终止。

使用场景

在服务器设计时,极有可能出现客户端已经挂掉了,服务端还会继续向客户端套接字写入数据的场景,这会导致产生SIGPIPE信号,导致服务端进程挂掉!

如果服务端不处理该SIGPIPE信号,服务端是很危险的!

服务端增加捕捉SIGPIPE的代码

方案1: signal(SIGPIPE, SIG_IGN);   // 忽略该信号, 不执行默认的操作

方案2: 注册SIGPIPE信号处理函数,处理该信号

2.进程通信

单工:只能向一个方向发送

半双工:可以两个方向,但是同一个时刻,只允许一个方向

全双工:可以两个方向,同一个时刻,允许两个方向

2.1.无名管道pipe()

只能用于具有亲缘关系的进程之间的通信(父子进程/兄弟进程)

知识体系之APUE/内核编程-编程知识网

半双工(方向只能朝一个方向流动),一个读端,一个写端

先进先出

阻塞(管道里没数据,读端阻塞;管道满,写端阻塞)

流管道

fd[0] 读端   fd[1]写端

知识体系之APUE/内核编程-编程知识网

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. 共享内存

        多个进程的虚拟地址空间的一段,映射到一块相同的物理内存。===> 这样,不同进程之间的通信就不需要经过内核

知识体系之APUE/内核编程-编程知识网

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数据实际上,数据是存放在内核中的缓冲区中的

  1. 读数据,实际上是用户将内核缓冲区中的数据读到应用层缓冲区

  2. 写数据,实际上是用户将应用层缓冲区中的数据,写入到内核缓冲区

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

原理

  1. 应用程序需要建立信号处理程序(当数据到来时,从内核中拷贝数据到应用层)
  2. 当内核中数据准备好的时候,会给应用程序发送一个信号,SIGIO
  3. 会触发注册的信号处理函数,将数据拷贝走

 缺点:忙等待,消耗CPU资源

拉模式:需要应用程序注册回调函数,【主动】将数据从内核空间拷贝到应用空间,是一种【拉模式】

4.2.4.异步IO/完成端口

        可以看到,这种模式和“信号驱动IO”最大的区别是:当内核中数据准备好后,是内核主动的将数据拷贝到应用空间(而不是由应用层自己注册的回调函数,将数据拷贝到应用层空间)。这是一种【推模式】。

        内核将数据拷贝到应用层空间后,将会通知你,你之后就可以处理接下来的逻辑了。

  1. 当应用程序调用aio_read时,内核一方面去取数据报内容返回,另一方面将程序控制权还给应用程序
  2. 应用程序继续执行其他的任务,是一种非阻塞的状态
  3. 当内核中的数据报就绪时,由内核主动地将数据报拷贝到应用程序中,返回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底层原理

知识体系之APUE/内核编程-编程知识网

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

  1. 修改 bind 系统调用实现,以便支持多个进程可以绑定到相同的 IP 和端口
  2. 修改处理新建连接的实现,查找 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 的过程是这样的:

知识体系之APUE/内核编程-编程知识网

可以看到,整个数据的传输过程,都要需要 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 的价值在如下情况中尤其明显:

  1. 当我们要传输的数据特别大、速度特别快
  2. 传输的数据特别小、速度特别慢的时候

比如说,

  1. 用千兆网卡或者硬盘传输大量数据的时候,如果都用 CPU 来搬运的话,肯定忙不过来,所以可以选择 DMAC
  2. 当数据传输很慢的时候,DMAC 可以等数据到齐了,再发送信号,给到 CPU 去处理,而不是让 CPU 在那里忙等待。

作用

DMAC 代替了 CPU 负责「内存与磁盘」、「内存与网卡」之间的数据搬运,CPU 作为 DMAC 的控制者。

但是 DMAC 有其局限性,DMAC 仅仅能用于「设备间」交换数据时进行数据拷贝,但是「设备内部」的数据拷贝还需要 CPU 来亲力亲为。例如, CPU 需要负责内核空间与用户空间之间的数据拷贝(内存内部的拷贝),如下图所示:

知识体系之APUE/内核编程-编程知识网

上图中的 read buffer 也就是 page cache,socket buffer 也就是 Socket 缓冲区。

 4.3.3.有DMA技术的数据拷贝

        可以看到, CPU 不再参与「将数据从磁盘控制器缓冲区搬运到内核空间」的工作,这部分工作全程由 DMA 完成

知识体系之APUE/内核编程-编程知识网

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次拷贝数据

知识体系之APUE/内核编程-编程知识网

如果没有优化,读取磁盘数据,再通过网卡传输的场景性能比较差:

4 次 copy:

  • 物理设备 <-> 内存:
    • CPU 负责将数据从磁盘搬运到内核空间的 Page Cache 中;
    • CPU 负责将数据从内核空间的 Socket 缓冲区搬运到的网络中;
  • 内存内部拷贝:
    • CPU 负责将数据从内核空间的 Page Cache 搬运到用户空间的缓冲区;
    • CPU 负责将数据从用户空间的缓冲区搬运到内核空间的 Socket 缓冲区中;

4 次上下文切换:

  1. read 系统调用时:用户态切换到内核态;
  2. read 系统调用完毕:内核态切换回用户态;
  3. write 系统调用时:用户态切换到内核态;
  4. write 系统调用完毕:内核态切换回用户态;

我们不免发出抱怨:

  1. CPU 全程负责内存内部的数据拷贝还可以接受,因为内存的数据拷贝效率还行(不过还是比 CPU 慢很多),但是如果要 CPU 全程负责内存与磁盘、内存与网卡的数据拷贝,这将难以接受,因为磁盘、网卡的 I/O 速度远小于内存;
  2. 4 次 copy 太多了,4 次上下文切换也太频繁了;

4.4.2.零拷贝

方式1: mmap+write

        4次上下文切换、3次拷贝数据(一次CPU拷贝、两次DMA拷贝)

知识体系之APUE/内核编程-编程知识网

mmap

  1. 用户进程调用mmap发起IO调用(切换1: 上下文从用户态切换到内核态)
  2. CPU利用DMA控制器,将数据从硬盘拷贝到内核缓冲区
  3. (切换2: 上下文从内核状态切回用户态)mmap方法返回

write

  1. 用户进程调用write发起IO调用(切换3: 上下文从用户态切换到内核态)
  2. CPU将内核缓冲区的数据拷贝到socket缓冲区
  3. 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拷贝)

知识体系之APUE/内核编程-编程知识网

  1. 用户进程发起sendfile系统调用(上下文从用户态切换到内核态)
  2. DMA将数据从硬盘拷贝到内核缓冲区
  3. CPU将内核缓冲区中的数据拷贝到socket缓冲区
  4. DMA异步把数据从socket缓冲器拷贝到网卡
  5. (上下文从内核态切换到用户态),sendflie函数返回

方式3: sendfile + DMA scatter/gather实现了零拷贝

知识体系之APUE/内核编程-编程知识网

        在linux2.4版本后,对sendflie做了优化升级,引入SG-DMA技术,其实就是对DMA拷贝加入了scatter/gather操作,它可以直接从内核空间缓冲区中将数据读取到网卡,这样的话还可以省去CPU拷贝

        2次上下文切换、2次数据拷贝(2次DMA拷贝):这是真正的零拷贝技术,全程没有CPU拷贝,所有的数据都是通过DMA进行传输的

  1. 用户进程发起sendfile系统调用(上下文从用户态切换到内核态)
  2. DMA将数据从硬盘拷贝到内核缓冲区
  3. CPU将内核缓冲区中的文件描述符信息(包括内核缓冲区的内存地址和偏移量)直接发送到socket缓冲区
  4. DMA根据文件描述符信息直接把数据从内核缓冲区拷贝到网卡
  5. (上下文从内核态切换到用户态),sendflie函数返回

5.操作系统

5.1.中断

5.1.1.中断触发的方式

  1. 主动触发(程序中通过函数接口,通知CPU进行中断处理)
  2. 内部中断(数据溢出、非法地址访问、未识别的操作码…)
  3. 外部中断(输入/输出设备、硬件设备故障…)

5.1.2.硬件中断处理函数会做如下的事情:

  • 需要先「暂时屏蔽中断」,表示已经知道内存中有数据了,告诉网卡下次再收到数据包直接写内存就可以了,不要再通知 CPU 了,这样可以提高效率,避免 CPU 不停的被中断。
  • 接着,发起「软中断」,然后恢复刚才屏蔽的中断。

至此,硬件中断处理函数的工作就已经完成。

5.1.2.中断的触发流程

  1. 某些操作,产生电信号,通过硬件上的中断引脚,被传递到中断控制器,中断控制器会向CPU发送中断请求
  2. CPU收到中断请求后,判断是否响应中断
  3. (保护现场)中断触发,将当前正在运行的程序上下文保存到寄存器/堆栈
    1. 将当前断点压入堆栈:程序的下一条指令、寄存器的值
  4. (中断处理)CPU寻找中断服务程序的入口地址,跳转到中断程序运行
    1. 根据中断号,在中断向量表中找出相应的中断程序的入口地址,跳转到中断程序执行
  5. (恢复现场)中断处理结束后,CPU会将之前保存在堆栈中的断点和寄存器重新恢复
    1. 恢复断点的动作:将先前的指针和寄存器的内容从堆栈中弹出
  6. CPU继续运行之前被打断的程序

5.2.文件系统

5.2.1.文件系统的基本组成

        文件系统是操作系统中负责管理持久化数据的子系统说简单点,就是负责把用户的文件存到磁盘硬件中,因为即使计算机断电了,磁盘里的数据并不会丢失,所以可以持久化的保存文件。

        文件系统的基本数据单位是文件,它的目的是对磁盘上的文件进行组织管理,那组织的方式不同,就会形成不同的文件系统。

        Linux文件系统会为每个文件分配2个数据结构:目录项(directory entry)、索引节点(index node),它们主要用来记录文件的目录层次结构、元信息 

  • 索引节点inode
    • 记录文件的元信息,比如inode编号、文件大小、访问权限、创建时间、数据在磁盘的位置等等
    • 索引节点inode是文件的唯一标识,它们之间一一对应,也同样被存储在硬盘中,所以索引节点同样占用磁盘空间(但是一般情况下,为了加速文件的访问,会将它加载到内存中)
  • 目录项entry
    • 记录文件的名字索引节点指针、其他目录项的层级关联关系多个目录项关联起来,形成目录结构)
    • 但它与索引节点不同的是,目录项是内核维护的一个数据结构,不存放于磁盘,而是缓存在内存

        注意1:目录也是文件,也是用索引节点唯一标识,和普通文件不同的是,普通文件在磁盘里面保存的是文件数据,而目录文件在磁盘里面保存的是子目录和文件。

        注意2:文件的名字,是存放在目录项的,而不是存放在inode的

Q1: 目录和目录项是一个东西么?

A1: 虽然名字很接近,但是它们不是一个东西

  1. 目录是个文件,持久化存储在磁盘
  2. 目录项entry是内核一个数据结构,缓存在内存(上面已经说过)

Q2: 磁盘格式化

A2: 会被分成3个存储区域,分别是超级块、索引节点区、数据块区

  • 超级块,用来存储文件系统的详细信息,比如块个数、块大小、空闲块等等(当文件系统挂载时,载入内存)
  • 索引节点区,用来存储索引节点(当文件被访问时载入内存)
  • 数据块区,用来存储文件或目录数据

Q3: 那文件数据是如何存储在磁盘的呢?

A3: 

        磁盘读写的最小单位是扇区,扇区的大小只有512B,很明显,每次读写都以这么小的单位,那么读写的效率会非常低。

        所以,文件系统把多个扇区组成一个逻辑块,每次读写最小单位就是逻辑块(数据块),Linux的逻辑大小为4KB,也就是一次性读写8个扇区,这将大大提高了磁盘的读写速率。


        以上就是索引节点inode、目录项entry以及文件数据的关系,下面这个图就很好的展示了它们之间的关系。

知识体系之APUE/内核编程-编程知识网

5.2.2.虚拟文件系统

        文件系统的种类众多,而操作系统希望对用户提供一个统一的接口,于是在用户层与文件系统层引入中间层,这个中间层称为虚拟文件系统VFS

        VFS定义了一组所有文件系统都支持的数据结构和标准接口,这样程序员就不需要了解文件系统的工作原理,只需要了解VFS提供的统一接口即可。

        文件系统首先要挂载到某个目录才可以正常使用,比如,linix系统在启动时,会把文件系统挂载到根目录。

知识体系之APUE/内核编程-编程知识网

文件系统分类,根据存储位置不同,分为3类:

  1. 磁盘的文件系统
    1. 直接把数据存储在磁盘中,比如EXT2/3/4、XFS
  2. 网络的文件系统
    1. 用来访问其他计算机主机数据的文件系统,比如NFS、SMB等等
  3. 内存的文件系统
    1. 这类文件系统的数据不是存储在硬盘的,而是占用内存空间,比如常用的/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协议来完成的

知识体系之APUE/内核编程-编程知识网

  1. 主机A,(采用广播的方式)发送ARP请求包询问:目标IP=172.20.1.2,MAC地址是?
  2. IP地址=172.20.1.2目标主机,收到请求后,会返回数据包,告诉A主机:我是IP=172.20.1.2,我的MAC=xx:xx:xx:xx:xx

Q4: 在浏览器输入URL回车后,发生了什么?

知识体系之APUE/内核编程-编程知识网

 A4: 

  1. URL解析
    1. 知识体系之APUE/内核编程-编程知识网
    2. URL实际上是请求服务器里的文件资源
      1. 地址解析:判断输入的是一个合法的URL还是一个带搜索的关键字,并根据输入的内容进行自动完成、字符编码等操作
      2. 安全检查、访问限制 
  2. 生成HTTP请求消息
    1. 对 URL 进行解析之后,浏览器确定了 Web 服务器和文件名,接下来就是根据这些信息来生成 HTTP 请求消息了
  3. DNS解析(真正的地址查询):网址 ==> IP地址
    1. 通过浏览器解析 URL 并生成 HTTP 消息后,需要委托操作系统将消息发送给 Web 服务器
    2. 但在发送之前,还有一项工作需要完成,那就是查询服务器域名对应的 IP 地址,因为委托操作系统发送消息时,必须提供通信对象的 IP 地址
    3. 比如我们打电话的时候,必须要知道对方的电话号码,但由于电话号码难以记忆,所以通常我们会将对方电话号 + 姓名保存在通讯录里。==> 所以,有一种服务器就专门保存了 Web 服务器域名与 IP 的对应关系,它就是 DNS 服务器
      1. 域名的层级关系
        1. DNS 中的域名都是用句点来分隔的,比如 www.server.com,这里的句点代表了不同层次之间的界限
        2. 在域名中,越靠右的位置表示其层级越高
        3. 实际上域名最后还有一个点,比如 www.server.com.,这个最后的一个点代表根域名
      2. 域名解析的工作流程
        1. 客户端首先会发出一个 DNS 请求,问 www.server.com 的 IP 是啥,并发给本地 DNS 服务器
        2. 浏览器缓存:浏览器会先看自身有没有对这个域名的缓存,如果有,就直接返回
        3. 操作系统缓存:如果没有,就去问操作系统,操作系统也会去看自己的缓存,如果有,就直接返回
        4. hosts文件:如果没有,再去 hosts 文件看,也没有,才会去问「本地 DNS 服务器」
        5. 本地DNS服务器,执行域名解析
  4. 通过 DNS 获取到 IP 后,就可以把 HTTP 的传输工作交给操作系统中的协议栈
    1. 知识体系之APUE/内核编程-编程知识网
    2. 应用程序(浏览器)通过调用 Socket 库,来委托协议栈工作。
      1. 协议栈的上半部分有两块,分别是:负责收发数据的 TCP 和 UDP 协议,这两个传输协议会接受应用层的委托执行收发数据的操作
      2. 协议栈的下面一半是:用 IP 协议控制网络包收发操作,在互联网上传数据时,数据会被切分成一块块的网络包,而将网络包发送给对方的操作就是由 IP 负责的。
      3. 此外, IP 中还包括 ICMP 协议和 ARP 协议
        1. ICMP 用于告知网络包传送过程中产生的错误以及各种控制信息。
        2. ARP 用于根据 IP 地址,采用”广播“的方式,查询相应的以太网 MAC 地址
          1. ARP缓存
  5. 出口 ==> 网卡
    1. 介绍
      1. 网络包只是存放在内存中的一串二进制数字信息,没有办法直接发送给对方。因此,我们需要将数字信息转换为电信号,才能在网线上传输,也就是说,这才是真正的数据发送过程。
      2. 负责执行这一操作的是网卡,要控制网卡还需要靠网卡驱动程序
      3. 网卡驱动获取网络包之后,会将其复制网卡内的缓存区中,接着会在其开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列
    2. 最后网卡会将包转为电信号,通过网线发送出去

  6. 送别者 ==> 交换机
    1. 交换机的设计是将网络包原样转发到目的地。交换机工作在 MAC 层,也称为二层网络设备
      1. 首先,电信号到达网线接口,交换机里的模块进行接收,接下来交换机里的模块将电信号转换为数字信号
      2. 然后通过包末尾的 FCS 校验错误,如果没问题则放到缓冲区。这部分操作基本和计算机的网卡相同,但交换机的工作方式和网卡不同。
  7. 出境大门 ==> 路由器
  8. 互相扒皮 ==> 服务器与客户端
    1. 知识体系之APUE/内核编程-编程知识网

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.性能

  1. ❓如何分析进程的性能手段,如何去优化它?

  2. ❓Linux自带的iostat会输出什么?一般会关注哪些列?

7.1.top

Q:top会输出哪些信息

知识体系之APUE/内核编程-编程知识网

Q:CPU负载是什么意思?

load avg:显示的是一段时间内,正在执行和等待使用CPU的平均进程数

一般情况下,load average == CPU核心个数,才认为CPU被充分利用。当load average > CPU核心个数,系统出现过载

Q:CPU占比具体包含了什么?

知识体系之APUE/内核编程-编程知识网

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

7.2.性能问题分析及优化