www.91084.com

GVKun编程网logo

Linux为什么不通过TSS使用硬件上下文切换?

15

在本文中,您将会了解到关于Linux为什么不通过TSS使用硬件上下文切换?的新资讯,并给出一些关于4分钟理解Linux为什么不是一个硬实时的操作系统、c#–使用不安全的代码是否有任何性能损失或线程上下

在本文中,您将会了解到关于Linux为什么不通过TSS使用硬件上下文切换?的新资讯,并给出一些关于4分钟理解Linux为什么不是一个硬实时的操作系统、c# – 使用不安全的代码是否有任何性能损失或线程上下文切换?、Linux - 上下文切换、Linux 64位上下文切换的实用技巧。

本文目录一览:

Linux为什么不通过TSS使用硬件上下文切换?

Linux为什么不通过TSS使用硬件上下文切换?

我阅读了以下声明:

x86体系结构包括一种称为任务状态段(TSS)的特定段类型,用于存储硬件上下文。尽管Linux不使用硬件上下文切换,但是仍然被迫为系统中每个不同的CPU设置一个TSS。

我想知道:

  • Linux为什么不使用硬件支持进行上下文切换?
  • 硬件方法不是比软件方法快得多吗?
  • 是否有任何可以利用硬件上下文切换的操作系统?Windows是否使用它?

最后,一如既往,感谢您的耐心等待和回复。

- - - - - -添加 - - - - - - -

http://wiki.osdev.org/Context_Switching得到了一些解释。

像我一样困惑的人们可以看看它。8 ^)

答案1

小编典典

x86 TSS对于硬件多任务处理非常慢,与软件任务切换相比几乎没有任何好处。(实际上,我认为手动完成击败了TSS很多次)

TSS的使用也很烦人且乏味,并且即使在x86-64上也不便于移植。Linux旨在于多种体系结构上工作,因此他们可能选择使用软件任务切换,因为它可以以与机器无关的方式编写。而且,软件任务切换为可完成的工作提供了更多功能,并且与TSS相比,通常更易于设置。

我相信Windows 3.1使用了TSS,但至少NT> 5内核没有使用。我不知道任何使用TSS的类Unix操作系统。

请注意,TSS 是强制性的。
操作系统要做的事情是创建一个(每个处理器)一个TSS条目,并且每当他们需要切换任务时,它们都只是改变了这个TSS。而且,通过软件任务切换在TSS中使用的唯一字段是ESP0SS0。这用于从环3代码到达环0进行中断。没有TSS,就不会有已知的Ring
0堆栈,这当然会导致GPF并最终导致三重故障。

4分钟理解Linux为什么不是一个硬实时的操作系统

4分钟理解Linux为什么不是一个硬实时的操作系统

下面的一段小视频用最短时间解释了Linux为什么不能硬实时,哪几类区间里面不能抢占:



如有兴趣进一步深入系统的了解操作系统硬实时,可以看看

Linux阅码场的《操作系统*硬实时*深度剖析》在线课程


(完)

本文转载自Linux阅码场, Linux阅码场是一个专注Linux内核和系统编程与调试调优技术的公众号,它的文章云集了国内众多知名企业一线工程师的心得。 无论你工作在哪一个层次,理解底层OS和计算机系统的基本原理、计算机系统的调试与性能分析方法,都将使您如虎添翼!


他们的热门文章如下,篇篇都是经典:

迭代螺旋法 -- 关于Linux学习方法的血泪建议

Linux的任督二脉--进程调度和内存管理

让天堂的归天堂,让尘土的归尘土——谈Linux的总线、设备、驱动模型

CPU是如何访问到内存的? -- MMU最基本原理

文件读写(BIO)波澜壮阔的一生

郭健:Linux进程调度技术的前世今生之“前世”

学习Linux的必备硬件基础一网打尽

KVM最初的2小时——KVM从入门到放弃(修订版)

Docker 最初的2小时(Docker从入门到入门)

阿里杨勇:浅谈 Linux 高负载的系统化分析

更多精华:   Linux阅码场原创精华文章汇总


扫描下方二维码关注"Linux阅码场"吧!

本文分享自微信公众号 - RTThread物联网操作系统(RTThread)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

c# – 使用不安全的代码是否有任何性能损失或线程上下文切换?

c# – 使用不安全的代码是否有任何性能损失或线程上下文切换?

如果我想在一个非常时间敏感的应用程序中使用一些不安全的代码 – 在’swiching’到不安全的代码或线程上下文切换时会有任何延迟吗? C#.net 4

解决方法

原则上:不.重点是您绕过了一些托管运行时检查和限制.

也就是说,理论上可能JIT引擎在极少数情况下可以应用较少的优化,因为可以对不安全块中的代码做出较少的假设.编辑实际上,马修所做的关于固定堆内存的观点就是这个方向的一个主要例子. JIT-ter和GC引擎受到更多限制,可以做出更少的假设

此外,不安全的代码需要以某些权限运行,因此它可能不适合所有deplyoment目标.

Linux - 上下文切换

Linux - 上下文切换

开发高并发应用时, 通常是基于 “降低 用户空间IO等待 &上下文切换  对 CPU 资源的消耗 ” 进行思考设计。

对于 JVM 应用来说, 常见的耗 CPU 的有:

  1. 频繁 GC,访问量高时,有可能造成频繁的 GC,甚至 FGC。当调用量大时,内存分配过快,就会造成 GC 线程不停的执行,进行大量分析计算,导致 CPU 飙高 。
  2. 计算密集:  序列化与反序列化 、 加解密 、 正则表达式 等 大量计算。( Java 正则表达式使用的引擎实现是 NFA 自动机,这种引擎在进行字符匹配会发生回溯(backtracking)  
  3. 线程上下文切换过频: 大量线程都处于不断的阻塞状态(锁等待、IO 等待等)、锁竞争激烈等执行状态的变化过程中。 线程数高的应用 Runable 和 Running 状态的线程不多,这时 CPU 使用率不一定会高 ; BLOCKED  和 WAITING 状态的,这种线程是不会占用 CPU 的 , 这时候又得考虑是否有死锁现象。
  4. 某些线程在做无阻塞的运算,eg. while (true) 中不停的做运算,没有任何阻塞 。 死循环会调用 CPU 寄存器进行计数,这个操作就会占用 CPU。其次,如果线程一直处于死循环状态, 不会让出 CPU,除非操作系统时间片到期,但死循环会不断向系统申请时间片,直到系统没有空闲时间做别的事情。

用户态 & 内核态

操作系统都是采用虚拟存储器, 对 32 位 Linux 操作系统而言,它的寻址空间(虚拟存储空间)为 4G(2 的 32 次方)。

将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间

  1. 将最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF,也就是 3G-4G 部分 ) 给大家共享使用,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。
  2. 而将较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF)称为用户空间, 用来运行用户程序。

操作系统的核心是内核(kernel),可以访问受保护的内存空间,也有访问底层硬件设备 (硬盘、网卡等) 的所有权限 ; 处在内核空间称为内核态

用户进程只能受限的访问内存,且不允许访问外设,占用 cpu 的能力被剥夺,cpu 资源可以被其他程序获取 ; 也就是说不能直接操作内核,保证了内核的安全 。处在用户空间称为用户态通过内存页表  page cache  操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程的地址空间中的数据。

intel cpu 提供Ring0- Ring3三种级别的运行模式,Ring0级别最高,Ring3最低。Linux 使用了Ring3级别运行用户态,Ring0作为内核态,没有使用 Ring1 和 Ring2。Ring3状态不能访问Ring0的地址空间,包括代码和数据。

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语 (Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行的进程(获得 CPU),才可能将其转为阻塞状态, 此时是不占用 CPU 资源

Linux系统中,线程就是能并行运行且与fork创建它们的父进程)共享同一地址空间(内存区域)和其他资源的 轻量级的进程, 每个进程都有自己的内核栈

模式切换

系统调用:  在类 Unix 系统中是指活跃(正在运行在 CPU 上)的进程对于内核所提供的服务的请求。eg.(I/O)和新进程创建, I/O 可以被定义为任何信息流入或流出 CPU 与主内存(RAM)。

当用户运行一个程序,该程序所创建的进程开始是运行在用户态的,如果要执行文件操作、网络数据发送等操作,必须通过writesend等系统调用命令来执行内核中的代码来完成操作;这时,必须切换到Ring0,然后进入 3GB-4GB 中的内核地址空间去执行这些代码完成操作,完成后再切换回Ring3回到用户态。

  • 当进程运行在内核态时: CPU 堆栈指针寄存器指向的是内核堆栈地址,使用的是内核堆栈。 内核栈是属于操作系统空间的一块固定区域,可以用于保存中断现场、保存操作系统子程序间相互调用的参数、返回值等。
  • 当进程运行在用户态时:CPU 堆栈指针寄存器指向的是用户堆栈地址,使用的是用户堆栈。  用户栈是属于用户进程空间的一块区域,用户保存用户进程子程序间的相互调用的参数、返回值等。

当进程由于中断进入内核态时,系统会把一些用户态的堆栈地址保存到内核中, 然后设置 CPU 堆栈指针寄存器的地址为内核栈地址 ; 这样就完成了用户栈向内核栈的切换当返回用户态时,取出内核栈中得信息( 用户栈地址 )恢复到 CPU 栈指针寄存器 ,这样就返回到程序原来执行的地方。

用户态和内核态 在类 Unix 系统中共存意味着当系统调用发生时 CPU 切换到内核态是必要的。这应该叫做模式切换而不是上下文切换,因为没有改变当前的进程。

上下文切换

上下文指的是: 某一时间点 CPU 寄存器和程序计数器的内容。

寄存器是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内存), 寄存器通过对常用值(运算的中间值)的快速访问来提高计算机程序运行的速度。程序计数器是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在 (或下一个将要) 执行的指令的位置,具体依赖于特定的系统。

上下文切换在多任务操作系统中是一个必须的特性。多任务操作系统是指多个进程运行在一个 CPU 中互不打扰,看起来像同时运行一样。这个并行的错觉是由于上下文在高速的切换(每秒几十上百次),当某一进程自愿放弃它的 CPU 时间或者系统分配的时间片用完时,就会发生上下文切换。

上下文切换有时也因硬件中断而触发,硬件中断是指硬件设备(如键盘、鼠标、调试解调器、系统时钟)给内核发送的一个信号,该信号表示一个事件(如按键、鼠标移动、从网络连接接收到数据)发生了。

上下文切换只能发生在内核态中,分为 进程(线程)上下文切换、中断上下文切换。可以认为是:内核在 CPU 上 挂起 正在执行进程(含线程)的 运行,然后继续执行之前挂起的众多进程中的某一个。

  1. 挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内核中的某处;
  2. 在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复;
  3. 跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。

上下文切换通常是计算密集型的,也就是说,它需要相当可观的处理器时间;在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。

查看系统的上下文切换

如果当上下文切换次数超过一万次,或者切换次数出现数量级增长时,很可能已经出现了性能问题。

vmstat主要用来分析系统内存使用情况,也常用来分析 CPU 上下文切换和中断的次数。

# 每隔 5 秒输出 1 组数据
$ vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 190952 156956 2456464    0    0    9    40    1    1 11  2 87  0  0
 0  0      0 190640 156960 2456532    0    0    0    33 4880 9497  4  3 93  0  0
  • cs(context switch) 是每秒上下文切换的次数。

  • in(interrupt) 是每秒中断的次数。

  • r(Running or Runnable) 是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。

  • b(Blocked) 是处于不可中断睡眠状态的进程数。

查看每个进程的详细情况,使用 pidstat

# 每隔 5 秒输出 1 组数据
# -w 参数表示输出进程切换指标,而 -u 参数则表示输出 CPU 使用指标
$ pidstat -w -u 5
Linux 3.10.0-1062.9.1.el7.x86_64 (iZwz9hzc7pd8k5wat4x4wiZ) 	12/10/2020 	_x86_64_	(2 CPU)

03:52:00 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
03:52:01 PM  1000      1496    0.00    0.99    0.00    0.99     0  pidstat
03:52:01 PM  1000      6515    0.00    0.99    0.00    0.99     1  java
03:52:01 PM     0     12805    0.99    0.99    0.00    1.98     0  AliYunDun
03:52:01 PM  1000     21522    0.00    0.99    0.00    0.99     0  java
03:52:01 PM  1000     27459    0.99    0.00    0.00    0.99     0  java

03:52:00 PM   UID       PID   cswch/s nvcswch/s  Command
03:52:01 PM     0         1      0.99      0.00  systemd
03:52:01 PM     0         6      3.96      0.00  ksoftirqd/0
03:52:01 PM     0         9     97.03      0.00  rcu_sched
03:52:01 PM     0        14      1.98      0.00  ksoftirqd/1
03:52:01 PM     0       308      0.99      0.00  crond
03:52:01 PM  1000       391      0.99      0.00  sshd
  • cswch  表示每秒自愿上下文切换的次数。 指进程无法获取所需资源,导致的上下文切换。比如,IO、内存等系统资源不足时,就会发生自愿上下文切换。
  • nvcswch  表示每秒非自愿上下文切换的次数。 指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在抢占 CPU 时,就容易发生非自愿上下文切换。

默认 pidstat 显示进程的指标数据,加上 -t 参数后,才会输出线程的指标 。

​通过 /proc/interrupts来读取中断的使用情况,通过运行下面的命令: 

# -d 参数表示高亮显示变化的区域
$ watch -d cat /proc/interrupts
... ...
           CPU0       CPU1
  0:         82          0   IO-APIC-edge      timer
  1:         10          0   IO-APIC-edge      i8042
  4:        742          0   IO-APIC-edge      serial
  6:          3          0   IO-APIC-edge      floppy
  8:          0          0   IO-APIC-edge      rtc0
  9:          0          0   IO-APIC-fasteoi   acpi
 10:          0          0   IO-APIC-fasteoi   virtio4
 11:         34          0   IO-APIC-fasteoi   uhci_hcd:usb1
 12:         15          0   IO-APIC-edge      i8042
 14:          0          0   IO-APIC-edge      ata_piix
 15:   28445440          0   IO-APIC-edge      ata_piix
 24:          0          0   PCI-MSI-edge      virtio2-config
 25:   17640933   53622308   PCI-MSI-edge      virtio2-req.0

Linux 64位上下文切换

Linux 64位上下文切换

在32位模式的switch_to宏中,在调用__switch_to函数之前执行以下代码:

asm volatile("pushfl\n\t"       /* save    flags */ \
         "pushl %%ebp\n\t"      /* save    EBP   */ \
         "movl %%esp,%[prev_sp]\n\t"    /* save    ESP   */ \
         "movl %[next_sp],%%esp\n\t"    /* restore ESP   */ \
         "movl $1f,%[prev_ip]\n\t"  /* save    EIP   */ \
         "pushl %[next_ip]\n\t" /* restore EIP   */ \
         __switch_canary                    \
         "jmp __switch_to\n"    /* regparm call  */

EIP被压入堆栈(恢复EIP).当__switch_to完成时,会有一个返回到该位置的ret.
这是相应的64位代码:

asm volatile(SAVE_CONTEXT                     \
     "movq %%rsp,%P[threadrsp](%[prev])\n\t" /* save RSP */   \
     "movq %P[threadrsp](%[next]),%%rsp\n\t" /* restore RSP */    \
     "call __switch_to\n\t"

在那里,只保存和恢复rsp.我认为RIP已经在
堆栈的顶部.但是我无法找到指示的地方.
如何实际完成64位上下文切换,特别是对于RIP寄存器?

提前致谢!

解决方法

在32位内核中,thread.ip可能是以下之一:

> switch_to中的1个标签
> ret_from_fork
> ret_from_kernel_thread

通过使用push jmp对模拟呼叫来确保返回正确的位置.

在64位内核中,thread.ip不是这样使用的.在调用之后执行总是继续(以前是32位情况下的1标签).因此,不需要模拟呼叫,它可以正常完成.在__switch_to返回后使用条件跳转调度到ret_from_fork(您已省略此部分):

#define switch_to(prev,next,last) \
        asm volatile(SAVE_CONTEXT                                         \
             "movq %%rsp,%P[threadrsp](%[prev])\n\t" /* save RSP */       \
             "movq %P[threadrsp](%[next]),%%rsp\n\t" /* restore RSP */    \
             "call __switch_to\n\t"                                       \
             "movq "__percpu_arg([current_task])",%%rsi\n\t"              \
             __switch_canary                                              \
             "movq %P[thread_info](%%rsi),%%r8\n\t"                       \
             "movq %%rax,%%rdi\n\t"                                       \
             "testl  %[_tif_fork],%P[ti_flags](%%r8)\n\t"                 \
             "jnz   ret_from_fork\n\t"                                    \
             RESTORE_CONTEXT                                              \

ret_from_kernel_thread包含在ret_from_fork路径中,使用entry_64.S中的另一个条件跳转:

ENTRY(ret_from_fork)
        DEFAULT_FRAME

        LOCK ; btr $TIF_FORK,TI_flags(%r8)

        pushq_cfi $0x0002
        popfq_cfi                               # reset kernel eflags

        call schedule_tail                      # rdi: 'prev' task parameter

        GET_THREAD_INFO(%rcx)

        RESTORE_REST

        testl $3,CS-ARGOFFSET(%rsp)            # from kernel_thread?
        jz   1f

今天关于Linux为什么不通过TSS使用硬件上下文切换?的讲解已经结束,谢谢您的阅读,如果想了解更多关于4分钟理解Linux为什么不是一个硬实时的操作系统、c# – 使用不安全的代码是否有任何性能损失或线程上下文切换?、Linux - 上下文切换、Linux 64位上下文切换的相关知识,请在本站搜索。

本文标签:

上一篇如何在Linux命令行上解析CSV文件?(linux读取csv文件)

下一篇Spring Boot-Hibernate SessionFactory的句柄(springboot sessionid)