上下文切换以下简称CS的定义Context Switch definition 此文中已做了详细的说明这里我又偷懒不详细解释了 只提炼以下几个关键要点* 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.c1234567891011121314151617181920212223242526272829303132333435363738int__pthread_cond_wait (cond, mutex)pthread_cond_t *cond;pthread_mutex_t *mutex;{struct_pthread_cleanup_buffer buffer;struct_condvar_cleanup_buffer cbuffer;interr;intpshared (cond-__data.__mutex (void*) ~0l)? LLL_SHARED : LLL_PRIVATE;/* yunjie: 这里省略了部分代码 */do{/* yunjie: 这里省略了部分代码 *//* Wait until woken by signal or broadcast. */lll_futex_wait (cond-__data.__futex, futex_val, pshared);/* yunjie: 这里省略了部分代码 *//* If a broadcast happened, we are done. */if(cbuffer.bc_seq ! cond-__data.__broadcast_seq)gotobc_out;/* Check whether we are eligible for wakeup. */val cond-__data.__wakeup_seq;}while(val seq || cond-__data.__woken_seq val);/* Another thread woken up. */cond-__data.__woken_seq;bc_out:/* yunjie: 这里省略了部分代码 */return__pthread_mutex_cond_lock (mutex);}代码已经经过精简但我们仍然直接把目光放到19行lll_futex_wait这是一个pthread内部宏用处是调用系统调用sys_futexfutex是一种user mode和kernel mode混合mutex这里不展开讲了这个操作会将当前线程挂起休眠马上我们将会到内核中一探究竟lll_futex_wait宏展开的全貌1234567891011#define lll_futex_wake(futex, nr, private) \do{ \int__ignore; \register__typeof (nr) _nr __asm (edx) (nr); \__asm __volatile (syscall\:a(__ignore) \:0(SYS_futex),D(futex), \S(__lll_private_flag (FUTEX_WAKE,private)), \d(_nr) \:memory,cc,r10,r11,cx); \}while(0)可以看到该宏的行为很简单就是通过内嵌汇编的方式快速调用syscall:SYS_futex所以我们也不用再多费口舌直接看kernel的实现吧linux/kernel/futex.c12345678910111213141516171819202122232425262728293031SYSCALL_DEFINE6(futex, u32 __user *, uaddr,int, op, u32, val,structtimespec __user *, utime, u32 __user *, uaddr2,u32, val3){structtimespec ts;ktime_t t, *tp NULL;u32 val2 0;intcmd op FUTEX_CMD_MASK;if(utime (cmd FUTEX_WAIT || cmd FUTEX_LOCK_PI ||cmd FUTEX_WAIT_BITSET)) {if(copy_from_user(ts, utime,sizeof(ts)) ! 0)return-EFAULT;if(!timespec_valid(ts))return-EINVAL;t timespec_to_ktime(ts);if(cmd FUTEX_WAIT)t ktime_add_safe(ktime_get(), t);tp t;}/** requeue parameter in utime if cmd FUTEX_REQUEUE.* number of waiters to wake in utime if cmd FUTEX_WAKE_OP.*/if(cmd FUTEX_REQUEUE || cmd FUTEX_CMP_REQUEUE ||cmd FUTEX_WAKE_OP)val2 (u32) (unsignedlong) utime;returndo_futex(uaddr, op, val, tp, uaddr2, val2, val3);}linux 2.5内核以后都使用这种SYSCALL_DEFINE的方式来实现内核对应的syscall我这里阅读的是inux-2.6.27.62内核 略过一些条件检测和参数拷贝的代码我们可以看到在函数最后调用了do_futex由于这里内核会进行多个函数地跳转我这里就不一一贴代码污染大家了大致流程 pthread_cond_wait sys_futex do_futex futex_wait 蓝色部分为内核调用流程futex_wait中的部分代码12345678910111213141516171819202122232425262728293031323334353637/* add_wait_queue is the barrier after __set_current_state. */__set_current_state(TASK_INTERRUPTIBLE);add_wait_queue(q.waiters, wait);/** !plist_node_empty() is safe here without any lock.* q.lock_ptr ! 0 is not safe, because of ordering against wakeup.*/if(likely(!plist_node_empty(q.list))) {if(!abs_time)schedule();else{hrtimer_init_on_stack(t.timer, CLOCK_MONOTONIC,HRTIMER_MODE_ABS);hrtimer_init_sleeper(t, current);t.timer.expires *abs_time;hrtimer_start(t.timer, t.timer.expires,HRTIMER_MODE_ABS);if(!hrtimer_active(t.timer))t.task NULL;/** the timer could have already expired, in which* case current would be flagged for rescheduling.* Dont bother calling schedule.*/if(likely(t.task))schedule();hrtimer_cancel(t.timer);/* Flag if a timeout occured */rem (t.task NULL);destroy_hrtimer_on_stack(t.timer);}}以上是futex_wait的一部分代码主要逻辑是将当前进程/线程的状态设为TASK_INTERRUPTIBLE可被信号打断然后将当前进程/线程加入到内核的wait队列等待某种条件发生而暂时不会进行抢占的进程序列之后会调用schedule这是内核用于调度进程的函数在其内部还会调用context_switch在这里就不展开但有一点可以肯定就是当前进程/线程会休眠然后内核会调度器他还有时间片的进程/线程来抢占CPU这样pthread_cond_wait就完成了一次CSpthread_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或是中断返回时内核会检查它如果已被设置内核会在继续执行之前调用调度程序之后我们万能的