linux中的进程是个最主要的概念,进程从执行队列到開始执行有两个開始的地方,
一个就是switch_to宏中的标号1:"1:/t",//仅仅要不是新创建的进程,差点儿都是从上面的那个标号1開始的,而switch_to宏则是除了内核本身,全部的进程要
想执行都要经过的地方
另 一个就是ret_form_fork
这样看来。尽管linux的进程体系以及进程调度很复杂,可是整体看来就是一个沙漏状。
对于系统中的每一个新进程它首次被运行的过程必定是:
sys_fork---->do_fork---->copy_process ---->copy_thread 当中copy_process 设置的新进程的eip ret_from_fork
--->放入就绪队列---->被调度---->运行switch_to
然后就到了jmp
__switch_to了,这是个函数。这个函数的返回地址就区分了被调度的进程是新创建的进程还是已经运行过的进程了。
新创建(没有被运行过)的进程的返回地址是ret_from_fork
已经运行过的进程的返回地址是:switch_to中的标号1:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWdhbmxlbmd6aQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

switch_to宏就是沙漏中间那个 最细的地方,想从一端到还有一端。必定要经过那个地方,在非新创建的进程的情况下,全部进程都是从标号1開始,
看一下switch_to宏:
#define switch_to(prev,next,last) do {                            / 

         unsigned long esi,edi;                                          / 

         asm volatile("pushfl/n/t"                                      / 

                      "pushl %%ebp/n/t"                                 / 

                      "movl %%esp,%0/n/t"        /* save ESP */          / 

                      "movl %5,%%esp/n/t"        /* restore ESP */       /   //注意这里已经切换到了新的内核栈,故原来的栈中的局部变量所有失效。因而想得到其值就必须想办法保存它们,为了效率。这里将prev保存在寄存器中, 以便善后使用 

                      "movl $1f,%1/n/t"          /* save EIP */          /   //这里。仅仅要是以前在这里被切换出去的进程都会将标号1作为再回来时的eip 

                      "pushl %6/n/t"             /* restore EIP */       /   //将新进程的eip压入栈中,由于以下是个jmp,并且jmp到的函数最后有一个return。那么依照return的语义,就能够从栈取出eip加载 eip寄存器了,实际上这个对__switch_to的jmp调用就是一个手动的call调用(须要注意的是,这里return的是下一个进程的eip保存的是本进程的eip留给下次被切换进来的时候像如今这样使用)。非常巧妙 

                      "jmp __switch_to/n"                                /   //__switch_to是个FASTCALL的函数。eax/ebx寄存器传參数 

                      "1:/t"                                             /   //标号1的指令,非常easy。可是就是这个简单成全了总体架构的简单 这里为什么不用call

                      "popl %%ebp/n/t"                                   / 

                      "popfl"                                            / 

                      :"=m" (prev->thread.esp),"=m" (prev->thread.eip),  / 

                       "=a" (last),"=S" (esi),"=D" (edi)                 / 

                      :"m" (next->thread.esp),"m" (next->thread.eip),    / 

                       "2" (prev), "d" (next));                          / 

} while (0) 
switch_to宏里面的第三个參数是什么?
实际上这里的三个參数各自是原先A的 prev和next 而最后一个參数是 A被切换进来的前一个进程为什么要这第三个(被调度前的一个进程)參数呢?留待兴许 
linux 之所以实现上述的单点切换就是为了减少复杂度,事实上非常多操作系统内核都是这么做的。这里的单点并非指switch_to这个单点,而是保存/恢复eip 这个寄存器从而保证全部切换回来的进程都从一个地方開始,可是有点美中不足的就是linux并没有将全部的进程从就绪到開始运行都从标号1開始。这也就引出了以下一个问题:
为什么不用call 取代 push+jmp?
假设用call 的话。call运行的时候首先将标号1处的地址压栈,然后jmp到__switch_to開始运行, __switch_to的函数都要返回到标号1:開始处来运行
而其实并非这种,由于新创建的进程并非从标号1開始运行的。而是从ret_from_fork開始运行的。
所以新进程在运行宏  switch_to到 "jmp
__switch_to/n"的时候,它的返回地址将不会是标号1:而应该是eip,即在copy_thread中设置的eip中的值:ret_from_fork
所以关键点是这里的__switch_to函数
全部进程被调度的时候应该运行的是:

新创建进程返回的地址是:ret_from_fork
已经被调度过的进程的返回地址是:1:
看看新创建进程的返回地址处干了什么
到这里说明。新的线程已产生了. 

ENTRY(ret_from_fork) 

    pushl %eax 

    call schedule_tail 

    GET_THREAD_INFO(%ebp) 

    popl %eax 

    jmp syscall_exit 

syscall_exit: 

... 

work_resched: 

    call schedule 

... 

当他从ret_from_fork退出时。会从堆栈中弹出原来保存的eip,而ip指向kernel_thread_helper, 

至此kernel_thread_helper被调用。他就行执行我们的指定的函数了do_exit(). 

从内核空间返回到用户空间。
这样费事做的原因是什么?
看看 do_fork的实现就知道,事实上新创建的进程是不这么做的,新创建的进程的eip是ret_from_fork而不是标号1,这个原因是什么?新创建进 程的时候要手工指定一个開始的地址,毕竟它要開始就要有个起点,那么起点在哪里好呢?
最好是模拟该进程和别的已有进程一样是又一次開始执行的,这样比較统一又便于管理, 然后将这个開始地址也指为标号1。可是这时标号1在哪里,是标号1在嵌入式汇编宏中导致标号1的地址不好取到吗,假设真的由于这的话,全然能够将标号1分
离出来放到一个地方,然后无论是已经有的进程还是新创建的进程都从这个固定的分离出来标号1的地址处取指令不就能够了吗?内核的设计者不可能还没有我聪明,那样的话会浪费取指令的时间和空间的,
来个间接引用肯定没有嵌入式汇编标号直接。减少时间开销,并且另一个原因。用ret_from_fork全然能够做到和既有 进程的标号1一样的好

我们看看进程切换函数的设计。既有进程的切换都是在schedule里面进入switch_to从而找到标号1的,而在 switch_to之后就剩下一个finish_task_switch和推断又一次调度标志了。
我们看看ret_from_fork: 

ENTRY(ret_from_fork) 

         pushl %eax      //注意刚从switch_to调用的__switch_to中ret回来。正好ret到了ret_from_fork(注意switch_to中jmp 指令前的push),而那个函数返回的就是prev,将其放到了eax中,故这里schedule_tail的參数就是prev,也就是切换出去的进程。

call schedule_tail 

         GET_THREAD_INFO(%ebp) 

         popl %eax 

         jmp syscall_exit 

上 面看到ret_from_fork调用的schedule_tail參数是切换出去的进程,而后者立即调用finish_task_switch,这样就 和schedule中的switch_to之后的逻辑对上了,并且參数也没有什么问题,


那么finish_task_switch之后的逻辑呢,比方推断 又一次调度标志怎么办?
那就看看ret_from_fork中的syscall_exit吧,那里面做了推断,假设须要调度,那就会进入正常的 schedule流程,十分正确。
事实上就是这个finish_task_switch善后惹的祸。只是它的设计也是一个非常巧妙的看点,它主要推断原先的进 程是否还有存在的必要,假设已经dead了,那么就是在这里彻底释放其task_struct的,因此必须保存prev的值(回答了为甚么要三个參数这个问题引出的“要了这第三个參数有什么问题”),由于prev是
schedule的局部变量在prev的内核栈中,在切换到新的内核栈后(schedule函数用到了两个内核栈),prev失效。因此才要保存(这里能够理解为从prev传递到兴许可见的地方)的。
http://blog.csdn.net/dog250/article/details/5303541

Linux内核源代码的学习过程转换完成细节的更多相关文章

  1. Linux内核源代码分析方法

    Linux内核源代码分析方法   一.内核源代码之我见 Linux内核代码的庞大令不少人"望而生畏",也正由于如此,使得人们对Linux的了解仅处于泛泛的层次.假设想透析Linux ...

  2. Linux内核源代码获取教程

    Linux内核源代码获取方法 什么叫Linux 什么叫Linux内核 Linux内核源代码的获取 什么叫Linux? Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UN ...

  3. 在windows下解压缩Linux内核源代码出现重复文件原因

    在windows下解压缩Linux内核源代码出现重复文件原因 2009年06月30日 13:35 来源:ChinaUnix博客 作者:embededgood 编辑:周荣茂     原因一.因为在Lin ...

  4. Linux内核源代码情景分析系列

    http://blog.sina.com.cn/s/blog_6b94d5680101vfqv.html Linux内核源代码情景分析---第五章 文件系统  5.1 概述 构成一个操作系统最重要的就 ...

  5. Linux内核源代码

    说明:只供学习交流 一,目录结构 Linux内核源代码采用树形结构进行组织,非常合理地把功能相关的文件都放在同一个子目录下,使得程序更具有可读性. 二,目录结构 arch目录 arch是archite ...

  6. Linux内核源代码目录树结构

    Linux内核源代码目录树结构. arch:包含和硬件体系结构相关的代码,每种平台占一个相应的目录.和32位PC相关的代码存放在i386目录下,其中比较重要的包括kernel(内核核心部分).mm(内 ...

  7. 《深入分析Linux内核源代码》读书、私藏笔记大放送

    秉承着"不懂操作系统原理的程序员不是合格的程序员"的至理名言,鄙人又是买陈莉君老师的“Linux教学视频”,又是研读其力作<深入分析Linux内核源代码>,先将总结笔记 ...

  8. Ubuntu:编译Linux内核源代码和内核模块

    1. 目的 内核模块需要运行在Linux 3.8.13内核中,因此需要为此内核重新编译内核模块源代码. 2. 步骤 1.在Ubuntu 14.04 64位(内核3.13.0-24-generic)上, ...

  9. linux内核源代码、配置与编译

    内核源代码下载:www.kernel.org Linux内核源代码采用树形结构进行组织,非常合理地把功能相关的文件都放在同一个子目录下,使得程序更具可读性. linux内核代码最好不要在windows ...

随机推荐

  1. 模拟new实例化对象。

    使用new和字面量的的方法是两种主流创建对象的方法,两种最终都能达到同样的实例化的对象,本章主要围绕new关键字来实例化一个对象并且讲一个不使用new但是完全与new实例化对象相同的例子. 在使用ne ...

  2. C语言常用的宏

    01: 防止一个头文件被重复包含 #ifndef COMDEF_H #define COMDEF_H //头文件内容 #endif 02: 重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类 ...

  3. Mysql自增主键ID重新排序方法详解

    Mysql数据库表的自增主键ID号乱了,需要重新排列. 原理:删除原有的自增ID,重新建立新的自增ID. 1,删除原有主键: ALTER TABLE `table_name` DROP `id`; 2 ...

  4. RT3070 USB WIFI 在连接socket编程过程中问题总结

    最近耗时多天,成功的将RT3070驱动.并解决了socket的网络编程,成功的在BA9G10上面实现了USB wif.连上家里的无线路由器,通过ubuntu下面建立的服务端程序,将BA9G10中的数据 ...

  5. delphi 自我删除和线程池(1000行代码,需要仔细研究)

    unit Unit4; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  6. 不断摸索发现用 andy 模拟器很不错,感觉跟真机差不多

    嗯,今天也遇到了模拟的问题.那个慢啊,好几分钟才能开机,加载程序总共差不多十几分钟.当时想如果真做android开发必须换电脑啊.后来不断摸索发现用 andy 模拟器很不错,感觉跟真机差不多. 还是真 ...

  7. C语言数据结构----递归的应用(八皇后问题的具体流程)

    本节主要讲八皇后问题的基本规则和递归回溯算法的实现以及具体的代码实现和代码分析. 转载请注明出处.http://write.blog.csdn.net/postedit/10813257 一.八皇后问 ...

  8. nyoj 130 同样的雪花 【哈希】

    同样的雪花 时间限制:1000 ms  |  内存限制:65535 KB 难度:4 描写叙述 You may have heard that no two snowflakes are alike. ...

  9. MD5 概念与用途

    MD5概念: MD5这是message-digest algorithm 5(信息-摘要算法)缩写.用于加密和解密技术上,它能够说是文件的"数字指纹".不论什么一个文件,不管是可运 ...

  10. linux kickstart 自动安装

    最近很多业务系统都是linux lnmp平台安装,反复的安装让人觉得很苦恼,仔细钻研了下kickstart .这里环境是red hat linux 5.8 32位,系统盘中的软件包里包含有kickst ...