进程上下文切换 – 残酷的性能杀手(上)
对于服务器的优化,很多人都有自己的经验和见解,但就我观察,有两点常常会被人忽视 – 上下文切换 和 Cache Line同步 问题,人们往往都会习惯性地把视线集中在尽力减少内存拷贝,减少IO次数这样的问题上,不可否认它们一样重要,但一个高性能服务器需要更细致地去考察这些问题,这个问题我将分成两篇文章来写:
1)从一些我们常用的用户空间函数,到linux内核代码的跟踪,来看一个上下文切换是如何产生的
2)从实际数据来看它对我们程序的影响
Context Switch简介 -
上下文切换(以下简称CS)的定义,https://blog.csdn.net/zhangmingcai/article/details/84832799,此文中已做了详细的说明,这里我又偷懒不详细解释了:) 只提炼以下几个关键要点:
*) context(这里我觉得叫process context更合适)是指CPU寄存器和程序计数器在任何时间点的内容
*)CS可以描述为kernel执行下面的操作
1. 挂起一个进程,并储存该进程当时在内存中所反映出的状态
2. 从内存中恢复下一个要执行的进程,恢复该进程原来的状态到寄存器,返回到其上次暂停的执行代码然后继续执行
*)CS只能发生在内核态(kernel mode)
*)system call会陷入内核态,是user mode => kernel mode的过程,我们称之为mode switch,但不表明会发生CS(其实mode switch同样也会做很多和CS一样的流程,例如通过寄存器传递user mode 和 kernel mode之间的一些参数)
*)一个硬件中断的产生,也可能导致kernel收到signal后进行CS
什么样的操作可能会引起CS -
首先我们一定是希望减少CS,那什么样的操作会发生CS呢?也许看了上面的介绍你还云里雾里?
首先,linux中一个进程的时间片到期,或是有更高优先级的进程抢占时,是会发生CS的,但这些都是我们应用开发者不可控的。那么我们不妨更多地从应用开发者(user space)的角度来看这个问题,我们的进程可以主动地向内核申请进行CS,而用户空间通常有两种手段能达到这一“目的”:
1)休眠当前进程/线程
2)唤醒其他进程/线程
pthread库中的pthread_cond_wait 和 pthread_cond_signal就是很好的例子(虽然是针对线程,但linux内核并不区分进程和线程,线程只是共享了address space和其他资源罢了),pthread_cond_wait负责将当前线程挂起并进入休眠,直到条件成立的那一刻,而pthread_cond_signal则是唤醒守候条件的线程。我们直接来看它们的代码吧
pthread_cond_wait.c
1 int2 __pthread_cond_wait (cond, mutex)3 pthread_cond_t *cond;4 pthread_mutex_t *mutex;5 {6 struct _pthread_cleanup_buffer buffer;7 struct _condvar_cleanup_buffer cbuffer;8 int err;9 int pshared = (cond->__data.__mutex == (void *) ~0l) 10 ? LLL_SHARED : LLL_PRIVATE; 11 12 /* yunjie: 这里省略了部分代码 */ 13 14 do 15 { 16 /* yunjie: 这里省略了部分代码 */ 17 18 /* Wait until woken by signal or broadcast. */ 19 lll_futex_wait (&cond->__data.__futex, futex_val, pshared); 20 21 /* yunjie: 这里省略了部分代码 */ 22 23 /* If a broadcast happened, we are done. */ 24 if (cbuffer.bc_seq != cond->__data.__broadcast_seq) 25 goto bc_out; 26 27 /* Check whether we are eligible for wakeup. */ 28 val = cond->__data.__wakeup_seq; 29 } 30 while (val == seq || cond->__data.__woken_seq == val); 31 32 /* Another thread woken up. */ 33 ++cond->__data.__woken_seq; 34 35 bc_out: 36 /* yunjie: 这里省略了部分代码 */ 37 return __pthread_mutex_cond_lock (mutex); 38 }
代码已经经过精简,但我们仍然直接把目光放到19行,lll_futex_wait,这是一个pthread内部宏,用处是调用系统调用sys_futex(futex是一种user mode和kernel mode混合mutex,这里不展开讲了),这个操作会将当前线程挂起休眠(马上我们将会到内核中一探究竟)
lll_futex_wait宏展开的全貌
1 #define lll_futex_wake(futex, nr, private) \2 do { \3 int __ignore; \4 register __typeof (nr) _nr __asm ("edx") = (nr); \5 __asm __volatile ("syscall" \6 : "=a" (__ignore) \7 : "0" (SYS_futex), "D" (futex), \8 "S" (__lll_private_flag (FUTEX_WAKE, private)), \9 "d" (_nr) \ 10 : "memory", "cc", "r10", "r11", "cx"); \ 11 } while (0)
可以看到,该宏的行为很简单,就是通过内嵌汇编的方式,快速调用syscall:SYS_futex,所以我们也不用再多费口舌,直接看kernel的实现吧
linux/kernel/futex.c
1 SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val, 2 struct timespec __user *, utime, u32 __user *, uaddr2,3 u32, val3)4 {5 struct timespec ts;6 ktime_t t, *tp = NULL;7 u32 val2 = 0; 8 int cmd = op & FUTEX_CMD_MASK;9 10 if (utime && (cmd == FUTEX_WAIT || cmd == FUTEX_LOCK_PI || 11 cmd == FUTEX_WAIT_BITSET)) { 12 if (copy_from_user(&ts, utime, sizeof(ts)) != 0) 13 return -EFAULT; 14 if (!timespec_valid(&ts)) 15 return -EINVAL; 16 17 t = timespec_to_ktime(ts); 18 if (cmd == FUTEX_WAIT) 19 t = ktime_add_safe(ktime_get(), t); 20 tp = &t; 21 } 22 /* 23 * requeue parameter in 'utime' if cmd == FUTEX_REQUEUE. 24 * number of waiters to wake in 'utime' if cmd == FUTEX_WAKE_OP. 25 */ 26 if (cmd == FUTEX_REQUEUE || cmd == FUTEX_CMP_REQUEUE || 27 cmd == FUTEX_WAKE_OP) 28 val2 = (u32) (unsigned long) utime; 29 30 return do_futex(uaddr, op, val, tp, uaddr2, val2, val3); 31 }
linux 2.5内核以后都使用这种SYSCALL_DEFINE的方式来实现内核对应的syscall(我这里阅读的是inux-2.6.27.62内核), 略过一些条件检测和参数拷贝的代码,我们可以看到在函数最后调用了do_futex,由于这里内核会进行多个函数地跳转,我这里就不一一贴代码污染大家了
大致流程: pthread_cond_wait => sys_futex => do_futex => futex_wait (蓝色部分为内核调用流程)
futex_wait中的部分代码
1 /* add_wait_queue is the barrier after __set_current_state. */ 2 __set_current_state(TASK_INTERRUPTIBLE); 3 add_wait_queue(&q.waiters, &wait); 4 /* 5 * !plist_node_empty() is safe here without any lock. 6 * q.lock_ptr != 0 is not safe, because of ordering against wakeup. 7 */ 8 if (likely(!plist_node_empty(&q.list))) { 9 if (!abs_time) 10 schedule(); 11 else { 12 hrtimer_init_on_stack(&t.timer, CLOCK_MONOTONIC, 13 HRTIMER_MODE_ABS); 14 hrtimer_init_sleeper(&t, current); 15 t.timer.expires = *abs_time; 16 17 hrtimer_start(&t.timer, t.timer.expires, 18 HRTIMER_MODE_ABS); 19 if (!hrtimer_active(&t.timer)) 20 t.task = NULL; 21 22 /* 23 * the timer could have already expired, in which 24 * case current would be flagged for rescheduling. 25 * Don't bother calling schedule. 26 */ 27 if (likely(t.task)) 28 schedule(); 29 30 hrtimer_cancel(&t.timer); 31 32 /* Flag if a timeout occured */ 33 rem = (t.task == NULL); 34 35 destroy_hrtimer_on_stack(&t.timer); 36 } 37 }
以上是futex_wait的一部分代码,主要逻辑是将当前进程/线程的状态设为TASK_INTERRUPTIBLE(可被信号打断),然后将当前进程/线程加入到内核的wait队列(等待某种条件发生而暂时不会进行抢占的进程序列),之后会调用schedule,这是内核用于调度进程的函数,在其内部还会调用context_switch,在这里就不展开,但有一点可以肯定就是当前进程/线程会休眠,然后内核会调度器他还有时间片的进程/线程来抢占CPU,这样pthread_cond_wait就完成了一次CS
pthread_cond_signal的流程基本和pthread_cond_wait一致,这里都不再贴代码耽误时间
大致流程:pthread_cond_signal => SYS_futex => do_futex => futex_wake => wake_futex => __wake_up => __wake_up_common => try_to_wake_up (蓝色部分为内核调用流程)
try_to_wake_up()会设置一个need_resched标志,该标志标明内核是否需要重新执行一次调度,当syscall返回到user space或是中断返回时,内核会检查它,如果已被设置,内核会在继续执行之前调用调度程序,之后我们万能的schedule函数就会在wait_queue(还记得吗,我们调用pthread_cond_wait的线程还在里面呢)中去拿出进程并挑选一个让其抢占CPU,所以,根据我们跟踪的内核代码,pthread_cond_signal也会发生一次CS
本篇结束 -
会造成CS的函数远远不止这些,例如我们平时遇到mutex竞争,或是我们调用sleep时,都会发生,我们总是忽略了它的存在,但它却默默地扼杀着我们的程序性能(相信我,它比你想象中要更严重),在下一篇中我将以chaos库(我编写的一个开源网络库)中的一个多线程组件为例,给大家演示CS所带来的性能下降
本文转自https://www.cnblogs.com/zhiranok/archive/2012/08/13/context_switch_1.html
进程上下文切换 – 残酷的性能杀手(上)相关推荐
- 上下文保存 中断_Linux性能优化(CPU篇)(5)——CPU的上下文切换有几种类型?什么是进程上下文切换?...
上一篇中讲了stress用来模拟常见压力测试的选项: RobotCode俱乐部:<Linux性能优化实战>之CPU性能篇(四)zhuanlan.zhihu.com 这一篇的主题是:CPU ...
- linux进程上下文切换,Linux 性能分析总结之 CPU 上下文切换(二)
0x00 前言 上一篇笔记中我讲到了,在寻找 CPU 的性能瓶颈的问题的时候,首先会查看整台机器的平均负载是否高,然后再使用 pidstat 等工具判断到底是哪种情况导致的平均负载升高,主要情况有三种 ...
- Linux内核:进程上下文切换
目录 1.进程上下文的概念 2.上下文切换详细过程 2.1 进程地址空间切换 2.2 处理器状态(硬件上下文)切换 3.ASID机制 4. 普通用户进程.普通用户线程.内核线程切换的差别 5. 进程切 ...
- linux进程cpu时间片,Linux性能监控之CPU篇
这篇文章中,主要介绍CPU的一些基础知识. 首先介绍一下Linux kernel中的调度器(scheduler),调度器负责调度系统中的两种资源,一是线程,二是中断.调度器给不同资源不同的优先级.从高 ...
- mysql上下文切换_进程上下文切换开销
进程是我们开发同学非常熟悉的概念,我们可能也听说过进程上下文切换开销.那么今天让我们来思考一个问题,究竟一次进程上下文切换会吃掉多少CPU时间呢?线程据说比进程轻量,它的上下文切换会比进程切换节约很多 ...
- php在linux性能,Linux上性能异常定位以及性能监控
引言:大多数的服务都是跑在Linux上的,Linux现在也已经到了一个很广泛的应用,但是仍然会有很多问题出现,我们就来讨论下我们性能监控的指标,性能监控无非就是从I/O,内存,CPU,TCP连接数,网 ...
- linux下把进程绑定到特定cpu核上运行
现在大家使用的基本上都是多核cpu,一般是4核的.平时应用程序在运行时都是由操作系统管理的.操作系统对应用进程进行调度,使其在不同的核上轮番运行. 对于普通的应用,操作系统的默认调度机制是没有问题的. ...
- 伪共享(false sharing),并发编程无声的性能杀手
在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...
- Linux kernel 3.10内核源码分析--进程上下文切换
一.疑问 进程调度时,当被选中的next进程不是current进程时,需要进行上下文切换. 进行上下文切换时,有一些问题不太容易理解,比如: 1.进程上下文切换必然发生在内核态吗? 2.上下文切换后原 ...
- Linux中断与进程切换,结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程...
@ 实验环境 OS Linux cj-virtual-machine 5.3.0-51-generic 虚拟机 QEMU 内核版本 5.3.4 调式方法 GDB PS:调试环境安装请看上一篇博客汇编级 ...
最新文章
- 机器学习:协方差矩阵
- xtrabackup2.4 备份Precona5.6数据库,做增量备份与还原
- cf-Sasha and Array
- libpcap-mmap分析(五)
- Oracle 12c 简单的jdbc使用
- System.setProperty()
- koa2 mysql增删改查_koa2实现对mysql的增删改查函数封装
- rdlc报表 矩形高固定_固定资产条码管理系统特点分析
- 如何构建 HBase 集群监控系统?
- VS Code Python 将支持 Jupyter Notebook
- 图解VS2010打包全过程(转)
- JDE 权限,分环境设置权限,PY,PD设置不同权限
- LitePal数据存储
- potplayer视频的倍速设置
- 【雷达目标检测】恒定阈值法和恒虚警(CFAR)法及代码实现
- 算法竞赛入门经典第11章 无根树转有根树
- 计算机修改用户名密码,怎么修改电脑用户名
- CTF MISC系列————8、Misc1-纵横四海
- TB,GB,MB,KB,Byte字节,bit位 如何换算?
- python获取股票数据,并计算技术指标
热门文章
- 【图形学】我理解的伽马校正(Gamma Correction)
- linux-通过BCM2835芯片手册进行IO操控的代码编程
- 九个完全免费的PPT模板网站
- (转)protein 数据库
- php蓝牙连接不上,蓝牙音响连接不上手机怎么办 两种方法轻松解决连接问题
- 使用腾讯云发送短信 ---- 手把手教你搞定所有步骤
- 为什么苏联打下了如此强的数学基础,俄罗斯却至今无法成为AI强国?
- android+化学输入法,化学输入法使用说明.pdf
- AGX平台MCU升级过程分析2014.10.3
- 关于信息学奥赛一本通(C++版)在线评测系统 1153 绝对素数