十天学Linux内核之第六天---调度和内核同步
心情大好,昨晚我们实验室老大和我们聊了好久,作为已经在实验室待了快两年的大三工科男来说,老师让我们不要成为那种技术狗,代码工,说多了都是泪啊,,不过我们的激情依旧不变,老师帮我们组好了队伍,着手参加明年的全国大赛,说起来我们学校历史上也就又一次拿国一的,去了一次人民大会堂领奖,可以说老大是对我们寄予厚望,以后我会专攻仪器仪表类的题目,激情不灭,梦想不息,不过最近一段时间还是会继续更新Linux内核,总之,继续加油~
Linux2.6版本中的内核引入了一个全新的调度程序,称为O(1)调度程序,进程在被初始化并放到运行队列后,在某个时刻应该获得对CPU的访问,它负责把CPU的控制权传递到不同进程的两个函数schedule()和schedule_tick()中。下图是随着时间推移,CPU是如何在不同进程之间传递的,至于细节这里不多阐释,大家看待就可以理解的啦~
下面开始介绍一下上下文切换,在操作系统中,CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务。上下文切换包括保存当前任务的运行环境,恢复将要运行任务的运行环境。
如何获得上下文切换的次数?
vmstat直接运行即可,在最后几列,有CPU的context switch次数。 这个是系统层面的,加入想看特定进程的情况,可以使用pidstat。
$ vmstat
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
执行pidstat,将输出系统启动后所有活动进程的cpu统计信息:
linux:~ # pidstat
Linux 2.6.32.12-0.7-default (linux) // _x86_64_ :: PID %usr %system %guest %CPU CPU Command
……
:: 0.00 0.00 0.00 0.00 bash
:: 0.00 0.00 0.00 0.00 dd
::: pidstat获取信息时间点
PID: 进程pid
%usr: 进程在用户态运行所占cpu时间比率
%system: 进程在内核态运行所占cpu时间比率
%CPU: 进程运行所占cpu时间比率
CPU: 指示进程在哪个核运行
Command: 拉起进程对应的命令
备注:执行pidstat默认输出信息为系统启动后到执行时间点的统计信息,因而即使当前某进程的cpu占用率很高
上下文切换的性能消耗在哪里呢?
context switch过高,会导致CPU像个搬运工,频繁在寄存器和运行队列直接奔波 ,更多的时间花在了线程切换,而不是真正工作的线程上。直接的消耗包括CPU寄存器需要保存和加载,系统调度器的代码需要执行。间接消耗在于多核cache之间的共享数据。
引起上下文切换的原因有哪些?
对于抢占式操作系统而言, 大体有几种:
- 当前任务的时间片用完之后,系统CPU正常调度下一个任务;
- 当前任务碰到IO阻塞,调度线程将挂起此任务,继续下一个任务;
- 多个任务抢占锁资源,当前任务没有抢到,被调度器挂起,继续下一个任务;
- 用户代码挂起当前任务,让出CPU时间;
- 硬件中断;
如何测试上下文切换的时间消耗?
这里我再网上查找到了一个程序,代码不长,切换一个 差不多就是 20个微秒吧,这个程序望大神指教
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include<pthread.h> int pipes[][];
char buffer[];
int running = ; void inti()
{
int i =;
while(i--)
{
if(pipe(pipes[i])<)
exit();
pipes[i][] = i;
}
} void distroy()
{
int i =;
while(i--)
{
close(pipes[i][]);
close(pipes[i][]);
}
} double self_test()
{
int i =;
struct timeval start, end;
gettimeofday(&start, NULL);
while(i--)
{
if(write(pipes[][],buffer,)==-)
exit();
read(pipes[][],buffer,);
}
gettimeofday(&end, NULL);
return (double)(*(end.tv_sec-start.tv_sec)+ end.tv_usec-start.tv_usec)/;
} void *_test(void *arg)
{
int pos = ((int *)arg)[];
int in = pipes[pos][];
int to = pipes[(pos + )%][];
while(running)
{
read(in,buffer,);
if(write(to,buffer,)==-)
exit();
}
} double threading_test()
{
int i = ;
struct timeval start, end;
pthread_t tid;
while(--i)
{
pthread_create(&tid,NULL,_test,(void *)pipes[i]);
}
i = ;
gettimeofday(&start, NULL);
while(i--)
{
if(write(pipes[][],buffer,)==-)
exit();
read(pipes[][],buffer,);
}
gettimeofday(&end, NULL);
running = ;
if(write(pipes[][],buffer,)==-)
exit();
return (double)(*(end.tv_sec-start.tv_sec)+ end.tv_usec-start.tv_usec)//;
} int main()
{
inti();
printf("%6.6f\n",self_test());
printf("%6.6f\n",threading_test());
distroy();
exit();
}
总而言之,我们可以认为,这最多只能是依赖于底层操作系统的近似计算。 一个近似的解法是记录一个进程结束时的时间戳,另一个进程开始的时间戳及排除等待时间。如果所有进程总共用时为T,那么总的上下文切换时间为: T – (所有进程的等待时间和执行时间)
接下来来述说抢占,抢占是一个进程到另一个进程的切换,那么Linux是如何决定在何时进行切换的呢?下面我们一次来介绍这三种抢占方式。
显式内核抢占:
最容易的就是这个抢占啦,它发生在内核代码调用schedule(可以直接调用或者阻塞调用)时候的内核空间中。当这种方式抢占时,例如在wait_queue等待队列中设备驱动程序在等候时,控制权被简单地传递到调度程序,从而新的进程被选中执行。
隐式用户抢占:
当内核处理完内核空间的进程并准备把控制权传递到用户空间的进程时,它首先查看应该把控制权传递到哪一个用户空间的进程上,这个进程也行不是传递其控制权到内核的那个用户空间进程。系统中中的每一个进程有一个“必须重新调度”,在进程应该被重新调度的任何时候设置它。代码可以在include/linux/sched.h中查看~
static inline void set_tsk_need_resched(struct task_struct *tsk)
{
set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);
} static inline void clear_tsk_need_resched(struct task_struct *tsk)
{
clear_tsk_thread_flag(tsk,TIF_NEED_RESCHED);
}
//set_tsk_need_resched和clear_tsk_need_resched是两个接口,用于设置体系结构特有的TIF_NEED_RESCHED标志
stactic inline int need_resched(void)
{
return unlikely(test_thread_flag(TIF_NEED_RESCHED));
}
//need_resched测试当前线程的标志,看看TIF_NEED_RESCHED是否被设置
隐式内核抢占:
隐式内核抢占有两种可能性:内核代码出自使抢占禁止的代码块,或者处理正在从中断返回到内核代码时,如果控制权正在从一个中断返回到内核空间,该中断调用schedule(),一个新的 进程以刚才描述的同一种方式被选中,如果内核代码出自禁止抢占的代码块,激活抢占 的操作可能引起当前进程被抢占(代码在include/linux/preempt.h中查看到):
#define preempt_enable() \
do { \
preempt_enable_no_resched(); \
preempt_check_resched(); \
} while()
preempt_enable()调用preempt_enable_no_resched(),它把与当前进程相关的preempt_count减1,然后调用preempt_check_resched():
#define preempt_check_resched() \
do { \
if(unlikely(test_thread_flag(TIF_NEED_RESCHED))); \
preempt_schedule(); \
} while()
preempt_check_resched()判断当前进程是否被标记为重新调度,如果是,它调用preempt_schedule()。
当两个或者两个以上的进程请求对共享资源独自访问时候,它们需要具有这样一种条件,即它们是在给代码段中操作的唯一进程,在Linux内核锁的基本形式是自旋锁。自旋锁会因为连续循环等待或者试图两次获得锁这种方式的操作而导致死锁,所以在此之前必须初始化spin_lock_t,这个可以通过调用spin_lock_init()来完成(代码在include/linux/spinlock.h中查看):
#define spin_lock_init(x) \
do { \
(x) -> magic = SPINLOCK_MAGIC; \
(x) -> lock = ; \ //设置自旋锁为"开锁"
(x) -> babble = ; \
(x) -> module = __FILE__; \
(x) -> owner = NULL; \
(x) -> oline = ; \
} while()
自旋锁被初始化后,可以通过调用spin_lock()或者spin_lock_irqsave()来获取,如果你使用spin_lock()那么进程可能在上锁的代码中被中断,为了在代码的临界区执行后释放,必须调用spin_unlock()或者spin_unlock_irqrestroe(),spin_unlock_irqrestroe()把中断寄存器的状态恢复成调用spin_lock_irq()时寄存器所处的状态。
自旋锁的缺点是它们频繁地循环直到等待锁的释放,那么对于等待时间长的代码区,最好是使用Linux kernel的另一个上锁工具:信号量。它的主要优势之一是:持有信号量的进程可以安全的阻塞,它们在SMP和中断中是保险的(代码在include/asm-i386/semaphore.h,include/asm-ppc/semaphore.h中可以查看):
struct semaphore{
atomic_t count;
int sleepers;
wait_queue_head_t wait;
#ifdef WAITQUEUE_DEBUG
long __magic;
#endif
};
struct semaphore{
atomic_t count;
wait_queue_head_t wait;
#ifdef WAITQUEUE_DEBUG
long __magic;
#endif
};
l两种体系结构的实现都提供了指向wait_queue的一个指针和一个计数,有了信号量我们能够让多于一个的进程同时进去代码的临界区,如果计数初始化为1,则表示只有一个进程能够进去代码的临界区,信号量用sema_init()来初始化,分别调用down()和up()来上锁和解锁,down()和up()函数的使用以及一些情况就不多说了看信号量的调用问题,很容易理解的。
小结
好吧我真的自己不懂的越来越多了,觉得一天天的接受不了这么多,内核实在是块难啃的石头,今天这个可是查了好多一整天才写出来的,肯定有很多问题以及没有提到的地方,各路大神路过时候多指点多批评,,对一个菜鸟来说这儿不容易了,我会好好的继续看代码,看到吐的~~~
版权所有,转载请注明转载地址:http://www.cnblogs.com/lihuidashen/p/4248174.html
十天学Linux内核之第六天---调度和内核同步的更多相关文章
- 十天学Linux内核之第七天---电源开和关时都发生了什么
原文:十天学Linux内核之第七天---电源开和关时都发生了什么 说实话感觉自己快写不下去了,其一是有些勉强跟不上来,其二是感觉自己越写越差,刚开始可能是新鲜感以及很多读者的鼓励,现在就是想快点完成自 ...
- 十天学Linux内核之第四天---如何处理输入输出操作
原文:十天学Linux内核之第四天---如何处理输入输出操作 真的是悲喜交加呀,本来这个寒假早上8点都去练车,两个小时之后再来实验室陪伴Linux内核,但是今天教练说没名额考试了,好纠结,不过想想就可 ...
- 十天学Linux内核之第二天---进程
原文:十天学Linux内核之第二天---进程 都说这个主题不错,连我自己都觉得有点过大了,不过我想我还是得坚持下去,努力在有限的时间里学习到Linux内核的奥秘,也希望大家多指点,让我更有进步.今天讲 ...
- 十天学Linux内核之第十天---总结篇(kconfig和Makefile & 讲不出再见)
原文:十天学Linux内核之第十天---总结篇(kconfig和Makefile & 讲不出再见) 非常开心能够和大家一起分享这些,让我受益匪浅,感激之情也溢于言表,,code monkey的 ...
- 十天学Linux内核之第九天---向内核添加代码
原文:十天学Linux内核之第九天---向内核添加代码 睡了个好觉,很晚才起,好久没有这么舒服过了,今天的任务不重,所以压力不大,呵呵,现在的天气真的好冷,不过实验室有空调,我还是喜欢待在这里,有一种 ...
- 十天学Linux内核之第八天---构建Linux内核
原文:十天学Linux内核之第八天---构建Linux内核 今天是腊八节,说好的女票要给我做的腊八粥就这样泡汤了,好伤心,好心酸呀,看来代码写久了真的是惹人烦滴,所以告诫各位技术男敲醒警钟,不要想我看 ...
- 十天学Linux内核之第五天---有关Linux文件系统实现的问题
原文:十天学Linux内核之第五天---有关Linux文件系统实现的问题 有时间睡懒觉了,却还是五点多醒了,不过一直躺倒九点多才算起来,昨晚一直在弄飞凌的嵌入式开发板,有些问题没解决,自己电脑系统的问 ...
- 十天学Linux内核之第三天---内存管理方式
原文:十天学Linux内核之第三天---内存管理方式 昨天分析的进程的代码让自己还在头昏目眩,脑子中这几天都是关于Linux内核的,对于自己出现的一些问题我会继续改正,希望和大家好好分享,共同进步.今 ...
- 十天学Linux内核之第一天---内核探索工具类
原文:十天学Linux内核之第一天---内核探索工具类 寒假闲下来了,可以尽情的做自己喜欢的事情,专心待在实验室里燥起来了,因为大二的时候接触过Linux,只是关于内核方面确实是不好懂,所以十天的时间 ...
随机推荐
- 怎么确定你的CPU是否支持64位虚拟化
http://www.grc.com/securable.htm 第一个64位表示你的电脑最多支持多少位的系统,32或者64. 第二个表示你的硬件是否支持DEP?Yes,支持.No,不支持.OFF,表 ...
- Blend4精选案例图解教程(二):找张图片玩特效
原文:Blend4精选案例图解教程(二):找张图片玩特效 Blend中的特效给了我们在处理资源时更多的想象空间,合理地运用特效往往会得到梦幻般效果,本次教程展示对图片应用特效的常规操作,当然特效不仅限 ...
- FileStream:The process cannot access the file because it is being used by another process
先看下面一段代码(先以共享的方式打开文件读写,然后以只读的方式打开相同文件): FileStream fs = new FileStream(filePath, FileMode.Open, Fil ...
- 转载:Raspberry Pi 树莓派入门
转载说明: 整理转载,原文链接http://www.eeboard.com/bbs/thread-5191-1-1.html 这篇帖子我打算用Q&A的方式来编写,这样大家更容易一目了然的看明整 ...
- bash组织成树数据结构
君子也非独占,善假于物!bash也因此.昨天晚上,今天早上世界杯很精彩.晚上醒来看到不断地居住的电话.早上没有喝的水开始赞赏在英国和意大利的对决.也TM精彩,最后生下了罗马文化.意大利伊特鲁里亚文化获 ...
- 理解和运用javascript中的call及apply
call是为了改变函数上下文context而存在的,换言之,就是改变函数内部this的指向.因为javascript存在[定义时上下文],[运行时上下文]及[上下文]是可以改变的.例如:var fun ...
- 应用层协议系列(两)——HTTPserver之http协议分析
上一篇文章<抄nginx Httpserver设计与实现(一)--多进程和多通道IO现>中实现了一个仿照nginx的支持高并发的server.但仅仅是实现了port监听和数据接收.并没有实 ...
- pragma message任务
pragma message它是用来告诉程序猿,在编译的程序信息.和outputdebugstr则是告诉程序猿.程序在执行时期的信息. 以下就以一个样例来解说pragma message. 配合#if ...
- Apple Watch 1.0 开发介绍 1.1 简介 开发苹果手表
使用Apple Watch,用户可以使用一种不显眼的方式查看信息.不用把iPhone从口袋里拿出来,就可以通过看一下手表快速获得重要信息. 作为Apple Watch的第三方app开发者,应该通过使用 ...
- javascript动画中的“帧”
在写游戏的时候,动画移动的速度需要保持一致,为了在各个软硬件环境中速度的一致,需要考虑帧频的不同. 计算时间系数: 时间系数 = 目标FPS / 实际FPS 计算实际FPS actualFPS = 1 ...