zoukankan      html  css  js  c++  java
  • 深入理解计算机操作系统(二)

    阅读经典——《深入理解计算机系统》01

    1. 信息是什么
    2. 文件
    3. Hello World程序的生命周期
    4. 开始运行Hello World
    5. 虚拟地址空间
    6. 总结

    <h3 id="what_is_information">信息是什么?</h3>

    信息就是+上下文

    怎么理解呢?其实计算机系统中的所有信息都是一个一个的二进制位,不论是硬盘上的文件、内存中的代码还是网络上传输的数据,毫无例外。它们唯一的区别就是所处不同的上下文,可什么又是上下文呢?做过应用程序开发的应该很熟悉context对象,当你创建一个新的控件的时候,往往要向构造方法中传入上下文对象,我们一般会传入this指针,这个上下文对象就是用来告诉新的控件它所处的位置,或者说它所处的环境。在计算机系统中,二进制数据所处的环境决定了它们表达的含义,同样的一段数据,作为整型、浮点型、字符串型或是作为机器指令时,表达的含义是完全不同的。

    <h3 id="file">文件</h3>

    通常来说,文件分为两种,文本文件二进制文件。起初我是不理解的,难道文本文件不是二进制组成的?文本文件当然也是二进制组成的,只不过比纯粹的二进制文件多了点上下文特征,即编码。以ASCII编码的文本文件来说,每个字节表示一个字符,于是这些二进制数据在这个上下文环境中表现为一个个字符,成了可以阅读的文本,这就是文本文件的特殊之处。

    <h3 id="lifecycle">Hello World程序的生命周期</h3>

    先来看一个程序员再熟悉不过的Hello World程序

    #include <stdio.h>
    
    int main()
    {
        print("hello, world
    ");
    }
    

    这是用高级编程语言C语言写的程序,这个程序需要转换成低级的机器语言才能够被计算机识别并执行。我们可以通过运行一条命令

    unix> gcc -o hello hello.c

    来生成可执行文件。(以上命令是在unix环境下调用的gcc编译器的命令,本书将经常采用unix环境。)但是,gcc编译器实际上做的工作不只如此,下图为从hello.c源程序到生成hello可执行程序的完整过程:

     
    编译系统

    首先经过预处理器预处理,然后经过编译器编译得到汇编程序hello.s,再经过汇编器汇编得到可重定位目标程序hello.o,最后,链接器将目标程序和标准库中的printf.o程序链接成为可执行目标程序hello。每一步的详细过程将在后面的章节中叙述,此处只做简要介绍。

    需要补充的是,gcc来自于赫赫有名的GNU项目,该项目为Linux的开发提供了全面的开发工具,包括GCC编译器、GDB调试器、EMACS编辑器、汇编器、链接器等等。有兴趣的朋友可以搜索一下这方面的知识。

    另外,我们经常用的另一款编译器是微软提供的MSVC,当我们使用Visual Studio时,用的就是它自带的编译器。它和gcc在语法要求等方面有所不同,所以会出现gcc正常编译的代码在MSVC中出错的情况,我就曾遇到过这种错误,希望大家注意。

    <h3 id="excute">开始运行Hello World</h3>

    好啦,有了可执行文件,我们就可以运行它,在命令行中敲如下命令:

    unix> ./hello

    显而易见,运行的结果为打印了一行字符串

    hello, world

    可是在我们发出命令和打印出结果期间都发生了什么呢?这就不得不提计算机系统的硬件结构了。下图是计算机系统的硬件结构图,我用红线标出了当我们在shell中输入hello命令时,计算机中的信息流向。

     
    从键盘读取hello命令

    当我们想要输入命令时,其实CPU中已经有一个正在运行的程序,那就是shell。shell程序一直在等待我们的输入,所以我们随时可以在键盘上输入内容。先看左下角的USB控制器,它负责所有USB接口,所以这里有鼠标、键盘等外设。我们在键盘上输入“./hello”命令时,该命令通过USB控制器向上经过I/O总线传递给I/O桥,也就是我们平常所说的南桥北桥,它是CPU和外界沟通的桥梁。再经过系统总线传递给寄存器,到了寄存器后还不是终点,因为shell程序需要把用户输入的内容作为一个变量使用,而这个变量一定在内存中有个地址,所以它最终会到达内存。

    之后,shell程序解析我们的命令内容,知道了我们希望运行hello这个程序。于是shell程序开始从硬盘加载hello文件到内存中。可是这次,这些数据不会经过CPU,而是直接从硬盘到内存,这种方式称为DMA。DMA(直接存储器访问)有利于减轻CPU的负荷,使CPU可以在数据转移的同时做其它任务。数据转移路线如下图:

     
    hello可执行程序从磁盘加载到内存

    加载完hello文件后,CPU将会开始从hello程序的主函数处执行指令。于是hello中的print语句将要打印的字符串传递给CPU,CPU再将它传递给显示器,这一过程字符串“hello, world”经过的路径如下图所示:

     
    从内存输出到显示器

    终于,我们在屏幕上看到了“hello, world”这一字符串。过程很复杂,但却只是一瞬间的事情,可见计算机运行速度之快!

    <h3 id="virtual_memory">虚拟地址空间</h3>

    hello程序我们分析透彻了吗,似乎没有。很多时候我们还会关心程序运行时内存的变化,当启动一个新进程的时候,操作系统是不是要为这个进程分配内存空间呢?答案是肯定的。

    这就是我们要讲的虚拟地址空间。虚拟地址空间是操作系统中一个非常复杂的概念,操作系统负责创建进程,同时为该进程分配内存。在现代操作系统中,出于进程间互不干扰,以及保护操作系统内核安全的考虑,每个进程享有完全独立的一套完整地址空间。对于32位计算机来说,虚拟地址空间大小为2GB,范围从 0x00000000 至 0x7FFFFFFF;对于64位计算机来说,虚拟地址空间大小为8TB,范围从0x000'00000000 至 0x7FF'FFFFFFFF。这就是说,每个进程都可以随意使用这2GB或8TB的内存空间,但是,由于是虚拟地址空间,这些地址映射到真实物理内存的时候是打乱的,用户无法得知自己进程的数据到底存在物理内存的什么地方。接下来,我们来看看用户进程的这2GB或8TB虚拟地址空间是怎么用的。

     
    进程虚拟地址空间

    上图将虚拟地址空间分为了若干个部分,并用箭头表示该部分的扩展方向。最下端地址为0,向上地址逐渐增长。每个部分作用如下:

    • 只读程序数据区和静态数据区:这一部分用来存放可执行程序代码和代码中的全局变量。
    • :用于动态申请的内存变量,比如malloc函数申请的动态内存空间,可以向上扩展。
    • 共享库内存映射区:位于虚拟内存空间的中部,用于存放C语言库函数的代码和数据。本例中即printf的代码和数据。
    • :位于虚拟地址空间的顶部,用于函数调用、存放局部变量等。当我们调用一个函数时,栈会向下扩展,返回时,向上收缩。
    • 内核虚拟地址空间:这个东西前面没提到过,但是它占据了栈向上直到4GB或256TB的所有空间。这个空间是保留给操作系统内核用的,用户进程无权访问这些地址。可是它到底是干什么用的,要等到后面的章节才能解开谜底。

    总结

    结束了Hello World的旅程,在后面的章节中,我们将一步步深入,探索计算机系统那些不为人知的奥秘。

    文中若有错误或不当之处,恳请各位读者指出。

    关注作者文集《深入理解计算机系统》,第一时间获取最新发布文章。

    参考资料



    作者:金戈大王
    链接:https://www.jianshu.com/p/5c33a0bcf244
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
  • 相关阅读:
    topcoder srm 445 div1
    topcoder srm 440 div1
    topcoder srm 435 div1
    topcoder srm 430 div1
    topcoder srm 400 div1
    topcoder srm 380 div1
    topcoder srm 370 div1
    topcoder srm 425 div1
    WKWebView强大的新特性
    Runtime那些事
  • 原文地址:https://www.cnblogs.com/zzdbullet/p/9354568.html
Copyright © 2011-2022 走看看