程序是怎样跑起来的

  1. CPU是英文Central Processing Unit(中央处理器)
  2. CPU和内存是由许多晶体管组成的电子部件,通常称为IC (Integrated Circuit,集成电路)。
  3. CPU的内部由寄存器、控制器、运算器和时钟四个部分构成,各部分之间由电流信号相互连通。
  4. 根据种类的不同,一个CPU内部会有20~100个寄存器
  5. 主存通过控制芯片等与CPU相连,主要负责存储指令和数据。主存由可读写的元素构成,每个字节(1字节=8位)都带有一个地址编号。CPU可以通过该地址读取主存中的指令和数据,当然也可以写入数据。主存中存储的指令和数据会随着计算机的关机而自动清除
  6. 程序启动后,根据时钟信号,控制器会从内存中读取指令和数据。通过对这些指令加以解释和运行,运算器就会对数据进行运算,控制器根据该运算结果来控制计算机
  7. 机器语言级别的程序是通过寄存器来处理的
  8. 内存的存储场所通过地址编号来区分,而寄存器的种类则通过名字来区分。
  9. 根据功能的不同,可以将寄存器大致划分为八类。CPU中每个寄存器的功能都是不同的,用于运算的数值放在累加寄存器中存储,表示内存地址的数值则放在基址寄存器和变址寄存器中存储。


    NB907(QVQ78IY0EJ3(53`6J.png
  10. ,CPU是具有各种功能的寄存器的集合体。其中,程序计数器、累加寄存器、标志寄存器、指令寄存器和栈寄存器都只有一个,其他的寄存器一般有多个。
  11. 条件分支和循环中使用的跳转指令,会参照当前执行的运算结果来判断是否跳转。无论当前累加寄存器的运算结果是负数、零还是正数,标志寄存器都会将其保存。(也负责存放溢出和奇偶校验的结果
  12. 标志寄存器的数值会根据运算结果自动设定。条件分支在跳转指令前会进行比较运算。至于是否执行跳转指令,则由CPU在参考标志寄存器的数值后进行判断。运算结果的正、零、负三种状态由标志寄存器的三个位表示。程序中的比较指令,就是在CPU内部做减法运算
  13. 函数的调用需要在完成函数内部的处理后,处理流程再返回到函数调用点(函数调用指令的下一个地址
  14. 函数调用使用的是call指令,而不是跳转指令。在将函数的入口地址设定到程序计数器之前,call指令会把调用函数后要执行的指令地址存储在名为栈的主存内。函数处理完毕后,再通过函数的出口来执行return命令。return命令的功能是把保存在栈中的地址设定到程序计数器中
  15. CPU则会把基址寄存器+变址寄存器的值解释为实际查看的内存地址。
  16. IC的所有引脚,只有直流电压0V或5V两个状态。也就是说,IC的一个引脚,只能表示两个状态。虽然二进制数并不是专门为IC而设计的,但是和IC的特性非常吻合
  17. 二进制数中表示负数值时,一般会把最高位作为符号来使用,因此我们把这个最高位称为符号位。计算机在做减法运算时,实际上内部是在做加法运算。用加法运算来实现减法运算,为了获得补数,我们需要将二进制数的各数位的数值全部取反,然后再将结果加1。通过求解补数的补数,就可知该值的绝对值。
  18. 仔细思考一下补数的机制,大家就会明白像- 32768~32767这样负数比正数多一个的原因了。0包含在正数范围内,所以负数就要比正数多1个。虽然0不是正数,但考虑到符号位,就将其划分到了正数中
  19. 将二进制数作为带符号的数值进行运算时,移位后要在最高位填充移位前符号位的值(0或1)不管是正数还是用补数表示的负数,都只需用符号位的值(0或者1)填充高位即可。这就是符号扩充的方法。
  20. 不管是几位的二进制数,在进行逻辑运算时,都是对相对应的各数位分别进行运算。
  21. 二进制数表示小数,只需将各数位数值和位权相乘,然后再将相乘的结果相加即可实现。


    1K_7%~~W3`Y`N4GDAOZMFY1.png
  22. 计算机之所以会出现运算错误,是因为“有一些十进制数的小数无法转换成二进制数,例如,十进制数0.1,就无法用二进制数正确表示,小数点后面即使有几百位也无法表示。小数点后4位用二进制数表示时的数值范围为0.0000~0.1111。因此,这里只能表示0.5、0.25、0.125、0.0625这四个二进制数小数点后面的位权组合而成(相加总和)的小数


    07CJ4LDXKR052{3AL{$J2MC.png
  23. 十进制数0.1转换成二进制后,会变成0.00011001100…(1100循环)这样的循环小数。这和无法用十进制数来表示1/3是一样的道理。1/3就是0.3333…,同样是循环小数。在遇到循环小数时,计算机就会根据变量数据类型所对应的长度将数值从中间截断或者四舍五入。我们知道,将0.3333…这样的循环小数从中间截断会变成0.333333,这时它的3倍是无法得出1的(结果是0.999999),计算机运算出错的原因也是同样的道理
  24. 像0.12345×103和0.12345×10-1这样使用与实际小数点位置不同的书写方法来表示小数的形式称为浮点数。例如,0.12345×103和0.12345 ×10-1用定点数来表示的话即为123.45和0.012345。浮点数是指用符号、尾数、基数和指数这四部分来表示的小数0
  25. 64位(双精度浮点数)和32位(单精度浮点数)的数据,会被分为三部分来使用。数值的大小用尾数部分和指数部分来表示。例如,小数就是用“尾数部分× 2的指数部分次幂”这样的形式来表示的
    ![M2{ZM3@L9O(2U$6D2UIW3.png](https://upload-images.jianshu.io/upload_images/2111160-45c4d8c458a59508.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![ULN[7}[email protected]
  26. 十进制数的浮点数应该遵循“小数点前面是0,小数点后面第1位不能是0”这样的规则。根据这个规则,0.75就是“0.75×10的0次幂。该遵循“小数点前面是0,小数点后面第1位不能是0”这样的规则。


    Y5~1`6~V_7@)LN17V~[@D1Q.png](https://upload-images.jianshu.io/upload_images/2111160-6b51ed523b0f0a76.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![CNF06IFGA3D(PSKO}`RSPLE.png
  27. 在计算机领域,大写字母K表示的并不是1000,而是2的10次幂的结果1024。1000通常用小写k来表示。后来约定过,内部集成电路内存是这么规定,外部集成电路1K就是1000。
  28. 在程序运行时,操作系统会自动决定变量的物理地址。
  29. 据程序中所指定的变量的数据类型的不同,读写的物理内存大小也会随之发生变化。
      //定义变量
      char a;
      short b;
      long c;
      //给变量赋值
      a =123;
      b =123;
      c =123;
    
    image
  30. 在定义指针时,我们通常会在变量名前加一个星号(*)。我们知道,d、e、f都是用来存储32位(4字节)的地址的变量。
      char *d;                //char类型的指针d的定义
      short *e;               //short类型的指针e的定义
      long *f;                //long类型的指针f的定义
    

    假设d、e、f的值都是100。在这种情况下,使用d时就能够从编号100的地址中读写1个字节的数据,使用e时就是2个字节(100地址和101地址)的数据,使用f时就是4个字节(100地址~103地址)的数据。


    CO%ZDQP65QUF.png
  31. 当随机前来的购票乘客数量和自动售票机的处理速度不相符时,排队能起到很好的缓冲作用。程序中也是如此,为了协调好数据输入和处理时机间的关系,采用类似于排队的机制是很方便的。
  32. 使用二叉查找树的便利之处在于可以使数据的搜索等更有效率。
  33. 磁盘虽然在物理方面只能以扇区为单位进行读写。可以用磁盘替代内存来使用的虚拟内存。
  34. 利用电流来实现存储的内存,同利用磁效应来实现存储的磁盘,还是有差异的。而从存储容量来看,内存是高速高价,而磁盘则是低速廉价。
  35. 即使CPU可以直接读出并运行磁盘中保存的程序,由于磁盘读取速度慢,程序的运行速度还是会降低。
  36. 磁盘缓存指的是把从磁盘中读出的数据存储到内存空间中的方式。这样一来,当接下来需要读取同一数据时,就不用通过实际的磁盘,而是从磁盘缓存中把内容读出。现在,随着硬盘访问速度的大幅改善,磁盘缓存的效果也没有之前那么明显了。Web浏览器就可以把获取的数据暂时保存在磁盘中,然后在需要时再显示磁盘中的数据。也就是说,把低速的网络数据保存到相对高速的磁盘中。
    B(J2TQ28TR.png
  37. 虚拟内存是指把磁盘的一部分作为假想的内存来使用。这与磁盘缓存是假想的磁盘(实际上是内存)相对,虚拟内存是假想的内存(实际上是磁盘)。通过借助虚拟内存,在内存不足时也可以运行程序。例如,在只剩下5MB内存空间的情况下也能运行10MB大小的程序。CPU只能执行加载到内存中的程序。虚拟内存虽说是把磁盘作为内存的一部分来使用,但实际上正在运行的程序部分,在这个时间点上是必须存在在内存中的。也就是说,为了实现虚拟内存,就必须把实际内存(也可称为物理内存)的内容,和磁盘上的虚拟内存的内容进行部分置换(swap),并同时运行程序
  38. 虚拟内存的方法有分页式和分段式。分段式虚拟内存是指,把要运行的程序分割成以处理集合及数据集合等为单位的段落,然后再以分割后的段落为单位在内存和磁盘之间进行数据置换。 Windows采用的是分页式。该方式是指,在不考虑程序构造的情况下,把运行的程序按照一定大小的页(page)进行分割,并以页为单位在内存和磁盘间进行置换。一般情况下,Windows计算机的页的大小是4KB。也就是说,把大程序用4KB的页来进行切分,并以页为单位放入磁盘(虚拟内存)或内存中。
  39. DLL(Dynamic Link Library)文件,顾名思义,是在程序运行时可以动态加载Library(函数和数据的集合)的文件。多个应用可以共有同一个DLL文件。而通过共有同一个DLL文件则可以达到节约内存的效果。
  40. 静态链接导致内存利用效率下降。如果函数MyFunc()是独立的DLL文件而不是应用的执行文件,内存的利用效率也就提高了。


    M7.png
  41. C语言中,在调用函数后,需要执行栈清理处理指令。栈清理处理是指,把不需要的数据从接收和传递函数的参数时使用的内存上的栈区域中清理出去。该命令不是程序记述的,而是在程序编译时由编译器自动附加到程序中的。编译器默认将该处理附加在函数调用方。 由于该处理是在调用函数一方,因此就会导致同一处理被反复进行。这就造成了内存的浪费。栈清理处理,比起在函数调用方进行,在反复被调用的函数一方进行时,程序整体要小一些。
    B%IBXTYTMH7RBHSP2`O.png
  42. 磁盘是通过把其物理表面划分成多个空间来使用的。划分的方式有扇区方式和可变长方式两种,前者是指将磁盘划分为固定长度的空间,后者则是指把磁盘划分为长度可变的空间。
  43. 扇区是对磁盘进行物理读写的最小单位,Windows中使用的磁盘,一般1个扇区是512字节。Windows在逻辑方面(软件方面)对磁盘进行读写的单位是扇区整数倍簇、。磁盘的容量越大,簇的容量也越大
  44. 不同的文件是不能存储在同一个簇中的,否则就会导致只有一方的文件不能被删除。因此,不管是多么小的文件,都会占用1簇的空间。这样一来,所有的文件都会占用1簇的整数倍的磁盘空间。
  45. 用记事本等文本编辑工具做成一个只有1个半角文字的文件,并将其保存到软盘中,然后再来看一下磁盘的属性。这时就会发现,虽然文件的大小只有1字节,但使用空间却变成了512字节。
  46. 以簇为单位进行读写时,1簇中没有填满的区域会保持不被使用的状态。虽然这看起来是有点浪费,不过该机制就是如此规定的。如果减少簇的容量,磁盘访问次数就会增加,就会导致读写文件的时间变长。扇区和簇的大小,是由处理速度和存储容量的平衡来决定的。
  47. 半角英文数字是用1个字节来表示的,汉字等全角字符是用2个字节来表示的。在任何情况下,文件中的字节数据都是连续存储的
  48. 出现频率高的字符用尽量少的位数编码来表示。哈夫曼树则是从叶生枝,然后再生根。哈夫曼算法能够大幅提升压缩比率


    哈夫曼树的编码顺序
  49. 多数情况下,并不要求压缩后的图像文件必须还原到与压缩前同等的质量。与之相比,程序的EXE文件以及每个字符、数值都有具体含义的文本文件则必须要还原到和压缩前同样的内容。我们把能还原到压缩前状态的压缩称为可逆压缩,无法还原到压缩前状态的压缩称为非可逆压缩。GIF格式的文件虽然是可逆压缩,但因为有色数不能超过256色的限制,所以还原后颜色信息会有一些缺失,进而导致了图像模糊
  50. 盒式卡带的情况下,可以将游戏机主机的内存完整置换,所以不需要往内存中复制数据。只有磁盘才必须把数据复制到内存中。
  51. CPU只能解释其自身固有的机器语言。不同的CPU能解释的机器语言的种类也是不同的。例如,CPU有x86、MIPS、SPARC、PowerPC等几种类型,它们各自的机器语言是完全不同的。通过对源代码进行编译,就可以得到本地代码


    F3855B31-2E72-4194-AA2E-13F38223448E.png
  52. x86提供有专门用来同外围设备进行输入输出的I/O地址空间(I/O地址分配)。至于各外围设备会分配到什么样的地址,则要由计算机的机型来定。MS-DOS应用大多都是不经过操作系统而直接控制硬件的,而Windows应用则基本上都由Windows来完成对硬件的控制。
  53. 应用软件则必须根据不同的操作系统类型来专门开发。CPU的类型不同,所对应的机器语言也不同,同样的道理,操作系统的类型不同,应用程序向操作系统传递指令的途径也是不同的。应用程序向操作系统传递指令的途径称为API。在同类型操作系统下,不管硬件如何,API基本上没有差别。
  54. 由于CPU种类不同,机器语言也不相同,因此本地代码当然也是不同的。这种情况下,就需要利用能够生成各CPU专用的本地代码的编译器,来对源代码进行重新编译了。Java也是将Java语法记述的源代码编译后运行。不过,编译后生成的并不是特定CPU使用的本地代码,而是名为字节代码的程序。字节代码的运行环境就称为Java虚拟机。Java虚拟机是一边把Java字节代码逐一转换成本地代码一边运行的。
  55. 在使用用于AT兼容机的Java编译器和Java虚拟机的情况下,编译器会将程序员编写的源代码(sample.java)转换成字节代码(sample.class)。而Java虚拟机则会把字节代码变换成x86系列CPU适用的本地代码,然后由x86系列CPU负责实际的处理。在程序运行时,将编译后的字节代码转换成本地代码,这样的操作方法看上去有些迂回,但由此可以实现同样的字节代码在不同的环境下运行。如果能够结合各种类型的操作系统和硬件作成Java虚拟机,那么,同样字节代码的应用就可以在任何环境下运行了。从操作系统方面来看,Java虚拟机是一个应用,而从Java应用方面来看,Java虚拟机就是运行环境。


    966B4222-404C-4734-8973-163A785B1C13.png
  56. Java虚拟机的问题:
    1. 不同的Java虚拟机之间无法进行完整互换。这是因为,想让所有字节代码在任意Java虚拟机上都能运行是比较困难的。而且,当我们使用只适用于某些特定硬件的功能时,就会出现在其他Java虚拟机上无法运行,或者功能使用受限等情况
    2. 运行速度的问题。Java虚拟机每次运行时都要把字节代码变换成本机代码,这一机制是造成运行速度慢的原因。为此,目前业界也在努力改善这一问题,比如把首次变换后的本地代码保存起来,第2次以后直接利用本地代码,或是对字节代码中处理较为费时的部分进行优化(改善生成的本地代码质量)等。
  57. 程序的运行环境中,存在着名为BIOS(Basic Input/Output System)的系统。BIOS存储在ROM中,是预先内置在计算机主机内部的程序。BIOS除了键盘、磁盘、显卡等基本控制程序外,还有启动“引导程序”的功能。引导程序是存储在启动驱动器起始区域的小程序。操作系统的启动驱动器一般是硬盘,不过有时也可以是CD-ROM或软盘。开机后,BIOS会确认硬件是否正常运行,没有问题的话就会启动引导程序。引导程序的功能是把在硬盘等记录的OS加载到内存中运行。虽然启动应用是OS的功能,但OS并不能自己启动自己,而是通过引导程序来启动。 Bootstrap的原意是指靴子上部的“拔靴带”。BIOS这样小的程序(拔靴带),可以带动(启动)操作系统这样的大程序(靴子),所以由此得名。
  58. 通过对源文件进行编译,得到目标文件。例如,C语言中,将Sample1.c这个源文件编译后,就会得到Sample1.obj这个目标文件。目标文件的内容是本地代码。
  59. 链接器会从库文件中抽取出必要的目标文件并将其结合到EXE文件中。此外,还存在一种程序运行时结合的DLL形式的库文件。
  60. 即使是用不同编程语言编写的代码,转换成本地代码后,也都变成用同一种语言(机器语言)来表示了。


    2AA4B4AE-CACF-4B67-A3CC-CF8C62F94705.png
  61. 每个编写源代码的编程语言都需要其专用的编译器。将C语言编写的源代码转换成本地代码的编译器称为C编译器。编译器首先读入代码的内容,然后再把源代码转换成本地代码。编译器中就好像有一个源代码同本地代码的对应表。但实际上,仅仅靠对应表是无法生成本地代码的。读入的源代码还要经过语法解析、句法解析、语义解析等,才能生成本地代码。
  62. 根据CPU类型的不同,本地代码的类型也不同。因而,编译器不仅和编程语言的种类有关,和CPU的类型也是相关的,Pentium等x86系列CPU用的C编译器,同PowerPC这种CPU用的C编译器就不同。因为编译器本身也是程序的一种,所以也需要运行环境。例如,有Windows用的C编译器、Linux用的C编译器等


    7BC25639-9F08-433F-A73A-4F22F770EC1E.png
  63. 编译器转换源代码后,就会生成本地文件。不过,本地文件是无法直接运行的。为了得到可以运行的EXE文件,编译之后还需要进行“链接”处理。编译后生成的不是EXE文件,而是扩展名为“.obj”的目标文件。Sample1.c编译后,就生成了Sample1.obj目标文件。虽然目标文件的内容是本地代码,但却无法直接运行。当前程序还处于未完成状态。把多个目标文件结合,生成1个EXE文件的处理就是链接。运行连接的程序就称为链接器
  64. C语言中像import32.lib及cw32.lib这样的文件称为库文件。库文件指的是把多个目标文件集成保存到一个文件中的形式。链接器指定库文件后,就会从中把需要的目标文件抽取出来,并同其他目标文件结合生成EXE文件 通过以目标文件的形式或集合多个目标文件的库文件形式来提供函数,就可以不用公开标准函数的源代码内容
  65. Windows中,API的目标文件,并不是存储在通常的库文件中,而是存储在名为DLL文件的特殊库文件中。就如Dynamic这一名称所表示的那样,DLL文件是程序运行时动态结合的文件。函数目标文件是存储在import32.lib中的。实际上,import32.lib中仅仅存储着两个信息,一是目标函数在user32.dll这个DLL文件中,另一个是存储着DLL文件的文件夹信息,目标函数的目标文件的实体实际上并不存在。我们把类似于import32.lib这样的库文件称为导入库。与此相反,存储着目标文件的实体,并直接和EXE文件结合的库文件形式称为静态链接库。


    48A9D57F-37CE-4D5E-A9AA-F813805E8C27.png
  66. EXE文件中给变量及函数分配了虚拟的内存地址。在程序运行时,虚拟的内存地址会转换成实际的内存地址。链接器会在EXE文件的开头,追加转换内存地址所需的必要信息。这个信息称为再配置信息,EXE文件的再配置信息,就成为了变量和函数的相对地址。相对地址表示的是相对于基点地址的偏移量,也就是相对距离。在源代码中,虽然变量及函数是在不同位置分散记述的,但在链接后的EXE文件中,变量及函数就会变成一个连续排列的组。这样一来,各变量的内存地址就可以用相对于变量组起始位置这一基点的偏移量来表示,同样,各函数的内存地址也可以用相对于函数组起始位置这一基点的偏移量来表示,而各组基点的内存地址则是在程序运行时被分配的。
    链接后的EXE文件的构造:
    F862A0DE-E159-4ABD-9F11-5405671A6EB1.png
  67. 当程序加载到内存后,除此之外还会额外生成两个组,那就是栈和堆。栈是用来存储函数内部临时使用的变量(局部变量,以及函数调用时所用的参数的内存区域。堆是用来存储程序运行时的任意数据及对象的内存领域。
  68. EXE文件中并不存在栈及堆的组。栈和堆需要的内存空间是在EXE文件加载到内存后开始运行时得到分配的。内存中的程序,就是由用于变量的内存空间、用于函数的内存空间、用于栈的内存空间、用于堆的内存空间这4部分构成的。
  69. 及堆的相似之处在于,他们的内存空间都是在程序运行时得到申请分配的。栈和堆的大小,可以由程序员任意指定。在高级编程语言中,编译器会自动生成指定栈和堆大小的代码,并将其附加到程序中。在内存的使用方法上,二者存在些许不同。栈中对数据进行存储和舍弃(清理处理)的代码,是由编译器自动生成的,因此不需要程序员的参与。使用栈的数据的内存空间,每当函数被调用时都会得到申请分配,并在函数处理完毕后自动释放。与此相对,堆的内存空间,则要根据程序员编写的程序,来明确进行申请分配或释放(C)
  70. 无论是C语言还是C++,如果没有在程序中明确释放堆的内存空间,那么即使在处理完毕后,该内存空间仍会一直残留。这个现象称为内存泄露。如果内存泄露一直存在的话,就有可能会造成内存不足而导致宕机
  71. 编译器是在运行前对所有源代码进行解释处理的。而解释器则是在运行时对源代码的内容一行一行地进行解释处理的。DLL文件中的函数可以被多个程序共用。因此,借助该功能可以节约内存和磁盘。此外,在对函数的内容进行修正时,还不需要重新链接(静态链接)使用这个函数的程序。
  72. 垃圾回收机制(garbage collection)指的是对处理完毕后不再需要的堆内存空间的数据和对象进行清理,释放它们所使用的内存空间。
  73. 在计算机中尚不存在操作系统的年代,完全没有任何程序,程序员就需要编写出处理相关的所有程序。用机器语言编写程序,然后再使用开关将程序输入。于是,有人开发出了仅具有加载和运行功能的监控程序,这就是操作系统的原型。通过事先启动监控程序,程序员就可以根据需要将各种程序加载到内存中运行。 之后,随着时代的进一步发展,开始有更多的功能被追加到监控程序中,比如,为了方便程序员的硬件控制程序、编程语言处理器(汇编、编译、解析)以及各种实用程序等,结果就形成了和现在相差不大的操作系统。操作系统本身并不是单独的程序,而是多个程序的集合体
    E08124BC-08AD-4901-A7D3-0563DC141E0A.png

    171433A6-402C-4525-8DB3-CA4CD7D2B7F8.png
  74. 在操作系统这个运行环境下,应用并不是直接控制硬件,而是通过操作系统来间接控制硬件的。变量定义中涉及的内存的申请分配,以及time()和printf()这些函数的运行结果,都不是面向硬件而是面向操作系统的。操作系统收到应用发出的指令后,首先会对该指令进行解释,然后会对时钟IC(实时时钟)和显示器用的I/O进行控制
    921BBF2D-4950-419A-81AD-B5D432737ACF.png
  75. 操作系统的硬件控制功能,通常是通过一些小的函数集合体的形式来提供的。这些函数及调用函数的行为统称为系统调用。在Windows操作系统中,提供返回当前日期和时刻,以及在显示器中显示字符串等功能的系统调用的函数名,并不是time()和printf()。系统调用是在time()和printf()函数的内部执行的。这样绕一步是有原因的,C语言等高级编程语言并不依存于特定的操作系统。这是因为人们希望不管是Windows还是Linux,都能使用几乎相同的源代码。因此,高级编程语言的机制就是,使用独自的函数名,然后再在编译时将其转换成相应操作系统的系统调用(也有可能是多个系统调用的组合)。也就是说,用高级编程语言编写的应用在编译后,就转换成了利用系统调用的本地代码。操作系统和高级编程语言能够使硬件抽象化
    8A2D55E3-51E8-4C1C-9534-FC295EF8CB2C.png
  76. 在16位操作系统中处理32位的数据时,因为要处理两次16位的数据,所以会多花一些时间。
  77. 多任务指的是同时运行多个程序的功能。Windows是通过时钟分割技术来实现多任务功能的。时钟分割指的是在短时间间隔内,多个程序切换运行的方式。由于时钟分割的太细,在皮秒范围,超出人感知范围,在用户看来,就是多个程序在同时运行。
  78. Windows中还具有以程序中的函数为单位来进行时钟分割的多线程功能。


    734BCF14-1477-4B4D-A379-D8BD75D6ED1D.png
  79. 网络功能和数据库功能,虽并不是操作系统本身不可欠缺的功能,但因为它们和操作系统很接近,所以被统称为中间件而不是应用。意思是处于操作系统和应用的中间。相对于操作系统一旦安装就不能轻易替换,中间件则可以根据需要进行任意的替换。设备驱动是操作系统的一部分,提供了同硬件进行基本的输入输出的功能,大家购买的新的硬件设备中,通常都会附带着软盘或CD-ROM,里面通常都收录着该硬件的设备驱动。有时DLL文件也会同设备驱动文件一起安装。这些DLL文件中存储着用来利用该新追加硬件的API(函数集)。通过API,可以制作出运用该新硬件的应用
  80. 在高级编程语言的源代码中,即使指令和数据在编写时是分散的,编译后也会在段定义中集合汇总起来。汇编语言编写的源代码,最终也必须要转换成本地代码,负责转换工作的程序称为汇编器,转换这一处理本身称为汇编。在将源代码转换成本地代码这个功能方面,汇编器和编译器是同样的。本地代码变换成C语言源代码的反编译,则要比反汇编困难。这是因为,C语言的源代码同本地代码不是一一对应的,因此完全还原到原始的源代码是不太可能的。
  81. 除了将本地代码进行反汇编这一方法外,通过其他方式也可以获取汇编语言的源代码。大部分C语言编译器,都可以把利用C语言编写的源代码转换成汇编语言的源代码,而不是本地代码。通过在编译器的选项中指定“-S”,就可以生成汇编语言的源代码了
  82. 段定义指的是命令和数据等程序的集合体的意思。一个程序由多个段定义构成。即使在源代码中指令和数据是混杂编写的,经过编译或者汇编后,也会转换成段定义划分整齐的本地代码。
  83. 程序运行时,CPU会从内存中把指令和数据读出,然后再将其存储在CPU内部的寄存器中进行处理。寄存器是CPU中的存储区域。不过,寄存器并不仅仅具有存储指令和数据的功能,也有运算功能
  84. 存储局部变量的不仅仅是栈,还有寄存器。大家可能会认为用高性能的寄存器来代替普通的内存是很奢侈的事情,不过编译器不会这么认为,只要寄存器有空间,编译器就会使用它。因为与内存相比,使用寄存器时访问速度会高很多,这样就可以更快速地进行处理。仅仅对局部变量进行定义是不够的,只有在给局部变量赋值时,才会被分配到寄存器的内存区域。
  85. 与mov指令相比,xor指令的处理速度更快。这里,编译器的最优化功能也会启动。
  86. **C语言源代码中counter =2;这一个指令的部分,在汇编语言源代码,也就是实际运行的程序中,分成了3个指令。如果只是看counter =2;的话,就会以为counter的数值被直接扩大为了原来的2倍。然而,实际上执行的却是“把counter的数值读入eax寄存器”“将eax寄存器的数值变成原来的2倍”“把eax寄存器的数值写入counter”这3个处理。 在多线程处理中,用汇编语言记述的代码每运行1行,处理都有可能切换到其他线程(函数)中
  87. 假设 MyFunc1函数在读出counter的数值100后,还未来得及将它的2倍值200写入counter时,正巧MyFunc2函数读出了counter的数值100,那么结果就会导致counter的数值变成了200。可以调用编程语言的特定操作,通过锁定,在特定范围内的处理完成之前,处理不会被切换到其他函数中。


    D89165AE-F754-4CFE-9518-96C53AB3A07A.png
  88. 所有连接到计算机的外围设备都会分配一个I/O地址编号。DMA指的是,不经过CPU中介处理,外围设备直接同计算机的主内存进行数据传输。像磁盘这样用来处理大量数据的外围设备都具有DMA功能。
  89. 硬件的控制是由操作系统全权负责的。


    3AE04829-65E9-40B5-9B47-76B63032CF7D.png
  90. 编程语言在操作显示器时,操作系统直接控制了作为硬件的显示器。但操作系统本身也是软件,由此可见,操作系统应该向CPU传递了某些指令,从而通过软件控制了硬件。
  91. 计算机主机中,附带了用来连接显示器及键盘等外围设备的连接器。而各连接器的内部,都连接有用来交换计算机主机同外围设备之间电流特性的IC。这些IC,统称为I/O 控制器。CPU和外围设备是无法直接连接的。为了解决这个问题,I/O控制器就很有必要了。显示器、键盘等外围设备都有各自专用的I/O控制器。I/O控制器中有用于临时保存输入输出数据的寄存器。这个寄存器的地址就是端口地址。虽然都是寄存器,但它和CPU内部的寄存器在功能上是不同的。CPU内部的寄存器是用来进行数据运算处理的,而I/O寄存器则主要是用来临时存储数据的。
  92. 在实现I/O控制器功能的IC中,会有多个端口。由于计算机中连接着很多外围设备,所以就会有多个I/O控制器,当然也会有多个端口。一个I/O控制器既可以控制一个外围设备,也可以控制多个外围设备。


    719EE966-99B3-4D75-8455-D3906450BF93.png
  93. 假如同时有多个外围设备进行中断请求的话,CPU也会为难。为此,我们可以在I/O控制器和CPU中间加入名为中断控制器的IC来进行缓冲。中断控制器会把从多个外围设备发出的中断请求有序地传递给CPU。中断处理程序的第一步处理,就是把CPU所有寄存器的数值保存到内存的栈中。在中断处理程序中完成外围设备的输入输出后,把栈中保存的数值还原到CPU寄存器中,然后再继续进行对主程序的处理。


    0FEBAB38-3E73-42B2-A0B7-7EA99C893768.png
  94. 利用DMA,大量数据就可以在短时间内转送到主内存。通过利用DMA,大量数据就可以在短时间内转送到主内存。之所以这么快速,是因为CPU作为中介的时间被节省了。CPU借助DMA通道,来识别是哪一个外围设备使用了DMA
  95. 虽然计算机领域的新技术在不断涌现,但计算机能处理的事情,始终只是对输入的数据进行运算,并把结果输出,这一点是不会发生任何变化的。
  96. AT&T贝尔实验室开发的Unix,最初是用汇编语言编写的,但后来大部分都用C语言进行了重写。借助C语言,Unix的移植性得到了大幅提升。通过对变量进行定义,就可以确保该变量对应的数据类型长度所需要的内存空间,并使用变量名来对内存空间进行读写
  97. 操作系统更像是搭建好的舞台,而应用更像是在舞台上演绎的演员。操作系统提供了各种道具。
  98. 线程是操作系统分配给CPU的最小运行单位。源代码的一个函数就相当于一个线程。

你可能感兴趣的:(程序是怎样跑起来的)