09 linux011系统调用开销分析

Posted 资质平庸的程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了09 linux011系统调用开销分析相关的知识,希望对你有一定的参考价值。

1 系统调用主要过程

(1) int 80h
保护模式下执行int 80h指令的过程比较复杂

[1] CPU 根据中断号 80h 和 IDTR(IDT内存地址) 读取描述符 IDT[80h] (并作有效、特权级等检查);

[2] CPU 根据 IDT[80h] 描述符中的 GDT 选择符(s)和 GDTR(GDT内存地址)读取段描述符 GDT[s](并作有效、特权级等检查);

       IDT
       +==+          logic memory addr
IDTR[1]|..|                         ^
 +  -->+--+   sys_fn             [3]|
80h    |  |-+-----------------------+
       +--+ |                       |
       |..| |                       |
       +==+ |        GDT            |
            |        +==+           |
            V     [2]|..|           |
    GDTR + selcotr-->+--+ base_addr |
                     |  |-----------+
                     +--+
                     |..|
                     +==+

[3] 现场(寄存器)备份

+-----+
| ss  |栈切换
|-----|备份
| esp |
+-----+
|eflag|tf=if=0
+-----+
| cs  |跳转
|-----|备份
| eip |
+-----+
以及
规定由子函数备份的寄存器

[4] 结合 GDT[s] 中的段基址和 IDT[80h] 中的系统调用入口函数偏移地址生成系统调用入口函数的逻辑内存地址,该逻辑内存地址经页转换便得到系统调用入口函数的物理内存地址;

[5] 栈切换并跳转执行(会导致清空CPU流水线,缓存重载入)系统调用入口函数处;

(2) 执行系统调用(可能会因共享资源如缓冲区缺乏进入睡眠而主动切换其他进程运行,进程切换的TSS备份开销和其它进程在时间片期间的执行应该比整个系统调用切换开销大);

(3) 系统调用返回,弹出内核栈中备份的寄存器,切换用户程序栈,返回用户程序系统调用处(会导致清空CPU流水线,缓存重载入)。

2 系统调用开销分析

抛开(2)(假设所需的内核资源充足),系统调用开销主要体现在

[1] 系统调用的执行;
[2] 刷新CPU流水线,缓存重载;
[3] 内存中描述符解析;
[4] 现场备份和恢复。

假设用户自己写代码完成系统调用的功能,且二者时间性能相近,那么系统调用的主要开销为

[1] 刷新CPU流水线,缓存重载——用户态和内核态之间的切换破坏了寄存器(如ss:esp,cs:eip)所指内存的局部性;
[2] 内存中描述符解析;
[3] 现场备份和恢复。

在这些开销中,[1]和[2]是主要开销。

当所需内核资源不足时,系统调用开销可能主要体现在对内核资源的等待上

[1] 等待内核资源(进程切换/调度开销);
[2] 刷新CPU流水线;内存中描述符解析;现场备份和恢复。

进程切换中的大部分工作(如TSS备份与恢复,描述符的解析)由CPU自动完成,依赖CPU的速度;调度则主要依赖于内核软件的调度算法(当然也依赖于执行程序的速度)。

所以,在现实应用中,需要避开哪一种性能开销会影响我们的选择(如等待内核资源开销在任何时间都是绝对不被忍受的,那就不能用系统调用)。

3 附:粗略测试

在以下环境下粗略测试一下系统开销。

$ lscpu
Architecture:        x86_64
CPU op-mode(s):      32-bit, 64-bit
CPU(s):              4
On-line CPU(s) list: 0-3

型号名称:           Intel(R) Xeon(R) CPU E5-2640 v3 @ 2.60GHz
CPU MHz:            2594.040
                     
L1d 缓存:           32K
L1i 缓存:           32K
L2 缓存:            256K
L3 缓存:            20480K

$ uname -srm
Linux 3.10.0-327.el7.x86_64 x86_64

即主频为2.60GHz、4核支持64位运算、拥有4级cache(速度最快的数据和指令cache分别32K)的CPU搭配内核版本为3的Linux OS。

在实际测试之前先对CPU主频进行一个粗略感观。假设

[1] 一个简单的C语句平均对应10条汇编指令;
[2] 指令执行的平均周期为10个CPU周期;
[3] CPU获取一条指令的平均时间为5个CPU周期(CPU 读 L1i/L1d 约1-3个周期;读内存约几个到10个周期)。

那么主频为2.6GHz的单核CPU 1秒钟大约能执行5200000条简单的C语句(若cache局部性好,能执行更多的C语句)。

测试对象是UDS(unix domain socket, unix套接字域——用于本机进程通信)中的connect()的开销,测试方法是将包含UDS connet() 程序的进程置于linux中运行,并随机取100组来分析

st.sec=1585985495 st.nsec=956157019
et.sec=1585985495 et.nsec=956188018

st.sec=1585985495 st.nsec=956213321
et.sec=1585985495 et.nsec=956216422

st.sec=1585985495 st.nsec=957371430
et.sec=1585985495 et.nsec=957380706

st.sec=1585985496 st.nsec=50087302
et.sec=1585985496 et.nsec=50106971

st.sec=1585985496 st.nsec=74541241
et.sec=1585985496 et.nsec=74564802

...

UDS connect() 在以上环境时间开销约为26微秒。该时间包含了connect()功能代码执行的时间以及系统调用相关(如描述符解析、流水线刷新、现场备份与恢复;但应该不包含资源等待)的时间开销。即在系统资源正常情况下,若我们自己实现connect()的功能代码,所能节约的时间小于该值。

该测试结果在不同的硬件环境以及不同的Linux版本上会有差异。

以上是关于09 linux011系统调用开销分析的主要内容,如果未能解决你的问题,请参考以下文章

10 linux011子进程创建和多进程调度开销分析

10 linux011子进程创建和多进程调度开销分析

Linux内核分析作业第二周

linux内核分析作业:操作系统是如何工作的进行:完成一个简单的时间片轮转多道程序内核代码

Linux内核分析 02

Linux内核分析(第二周)