对于服务器的优化,很多人都有自己的经验和见解,但就我观察,有两点常常会被人忽视 – 上下文切换 和 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

进程上下文切换 – 残酷的性能杀手(上)相关推荐

  1. 上下文保存 中断_Linux性能优化(CPU篇)(5)——CPU的上下文切换有几种类型?什么是进程上下文切换?...

    上一篇中讲了stress用来模拟常见压力测试的选项: RobotCode俱乐部:<Linux性能优化实战>之CPU性能篇(四)​zhuanlan.zhihu.com 这一篇的主题是:CPU ...

  2. linux进程上下文切换,Linux 性能分析总结之 CPU 上下文切换(二)

    0x00 前言 上一篇笔记中我讲到了,在寻找 CPU 的性能瓶颈的问题的时候,首先会查看整台机器的平均负载是否高,然后再使用 pidstat 等工具判断到底是哪种情况导致的平均负载升高,主要情况有三种 ...

  3. Linux内核:进程上下文切换

    目录 1.进程上下文的概念 2.上下文切换详细过程 2.1 进程地址空间切换 2.2 处理器状态(硬件上下文)切换 3.ASID机制 4. 普通用户进程.普通用户线程.内核线程切换的差别 5. 进程切 ...

  4. linux进程cpu时间片,Linux性能监控之CPU篇

    这篇文章中,主要介绍CPU的一些基础知识. 首先介绍一下Linux kernel中的调度器(scheduler),调度器负责调度系统中的两种资源,一是线程,二是中断.调度器给不同资源不同的优先级.从高 ...

  5. mysql上下文切换_进程上下文切换开销

    进程是我们开发同学非常熟悉的概念,我们可能也听说过进程上下文切换开销.那么今天让我们来思考一个问题,究竟一次进程上下文切换会吃掉多少CPU时间呢?线程据说比进程轻量,它的上下文切换会比进程切换节约很多 ...

  6. php在linux性能,Linux上性能异常定位以及性能监控

    引言:大多数的服务都是跑在Linux上的,Linux现在也已经到了一个很广泛的应用,但是仍然会有很多问题出现,我们就来讨论下我们性能监控的指标,性能监控无非就是从I/O,内存,CPU,TCP连接数,网 ...

  7. linux下把进程绑定到特定cpu核上运行

    现在大家使用的基本上都是多核cpu,一般是4核的.平时应用程序在运行时都是由操作系统管理的.操作系统对应用进程进行调度,使其在不同的核上轮番运行. 对于普通的应用,操作系统的默认调度机制是没有问题的. ...

  8. 伪共享(false sharing),并发编程无声的性能杀手

    在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...

  9. Linux kernel 3.10内核源码分析--进程上下文切换

    一.疑问 进程调度时,当被选中的next进程不是current进程时,需要进行上下文切换. 进行上下文切换时,有一些问题不太容易理解,比如: 1.进程上下文切换必然发生在内核态吗? 2.上下文切换后原 ...

  10. Linux中断与进程切换,结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程...

    @ 实验环境 OS Linux cj-virtual-machine 5.3.0-51-generic 虚拟机 QEMU 内核版本 5.3.4 调式方法 GDB PS:调试环境安装请看上一篇博客汇编级 ...

最新文章

  1. 机器学习:协方差矩阵
  2. xtrabackup2.4 备份Precona5.6数据库,做增量备份与还原
  3. cf-Sasha and Array
  4. libpcap-mmap分析(五)
  5. Oracle 12c 简单的jdbc使用
  6. System.setProperty()
  7. koa2 mysql增删改查_koa2实现对mysql的增删改查函数封装
  8. rdlc报表 矩形高固定_固定资产条码管理系统特点分析
  9. 如何构建 HBase 集群监控系统?
  10. VS Code Python 将支持 Jupyter Notebook
  11. 图解VS2010打包全过程(转)
  12. JDE 权限,分环境设置权限,PY,PD设置不同权限
  13. LitePal数据存储
  14. potplayer视频的倍速设置
  15. 【雷达目标检测】恒定阈值法和恒虚警(CFAR)法及代码实现
  16. 算法竞赛入门经典第11章 无根树转有根树
  17. 计算机修改用户名密码,怎么修改电脑用户名
  18. CTF MISC系列————8、Misc1-纵横四海
  19. TB,GB,MB,KB,Byte字节,bit位 如何换算?
  20. python获取股票数据,并计算技术指标

热门文章

  1. 【图形学】我理解的伽马校正(Gamma Correction)
  2. linux-通过BCM2835芯片手册进行IO操控的代码编程
  3. 九个完全免费的PPT模板网站
  4. (转)protein 数据库
  5. php蓝牙连接不上,蓝牙音响连接不上手机怎么办 两种方法轻松解决连接问题
  6. 使用腾讯云发送短信 ---- 手把手教你搞定所有步骤
  7. 为什么苏联打下了如此强的数学基础,俄罗斯却至今无法成为AI强国?
  8. android+化学输入法,化学输入法使用说明.pdf
  9. AGX平台MCU升级过程分析2014.10.3
  10. 关于信息学奥赛一本通(C++版)在线评测系统 1153 绝对素数