<<程序员的自我修养>>第一章读书读书笔记----从hello world说起

        在读《程序员自我修养》这本书之前,是准备写读书笔记的,一来加深自己对本书的理解,而来可以和看到笔记的网友们互相讨论。当读到第三章的时候,心理暗暗偷懒,不想写博客了,要将自己的所读所理解的整理为一篇文章,确实会花费不少时间和精力,可又想了想了当初的目标和想法,还是决定写下去。

C语言Hello World程序你了解多少?

        第一章作者就抛出了一个问题,从一个简单的C程序的HelloWorld程序(如下所示),你认识到了多少?先不谈这个问题的答案,先思考思考,如果你和我一样并不能尽可能多的联想到背后所涉及到的复杂而丰富的技术原理…….那么就一起读读这本书吧!

#include <stdio.h>
int main()
{
         printf(“HelloWorld!\n”);
         return0;
}

        作者问了如下几个问题,大家一起来思考思考? (我做了一些简化)

        (1)    这个程序为什么要编译之后才能运行?

        (2)    编译器做了些什么事情?

        (3)    最后编译出来的可执行文件里面有什么?

        (4)    C语言运行时库是什么?

        (5)    如果不同的编译器,编译出来的结果一样吗?

        (6)    Hello World是怎么执行的?

        (7)    printf是怎么实现的?为什么可以有不定的参数数量?

扫描二维码关注公众号,回复: 3820591 查看本文章

        (8)    Hello World运行时在内存中是什么样子?

        是不是和我一样,能说出一些,但又似乎对自己的答案不够信服啊?这个在之后的章节和书的阅读中慢慢解开答案(当然了也只是我个人的读后理解,希望讨论)。

计算机五大部件

        无论我们的软件如何运行起来,他们都是建立在有一台计算机硬件的基础之上,最初的计算机结构是由计算机之父冯·诺依曼提出,由输入设备,输出设备,存储器,运算器以及控制器等5大部件组成。


(图片来自于<<我的青春谁做主--计算机电源面面观>>)

        输入设备:典型的输入设备包括键盘,鼠标,麦克风等等

        输出设备:典型的输出设备包括显示器,磁盘,打印机等等

        存储器:在这个结构中,就是现在所说的内存,用来存储运行的程序和数据。

        运算器:在计算机指令中的加减乘除等等运算,都由其来完成

        控制器:在程序逻辑控制和输入/输出设备之间的交互均有控制器负责,控制器和运算器合起来就是我们所说的CPU,即计算机的大脑。

        计算机和二进制是不可分离的,二进制即只用0和1来表示数值,而计算机也只能识别0和1. 为啥计算机只能识别0和1呢?计算机归根到底就是一个可以智能控制的电路板,而在集成电路板中用高电压代表1,低电压代表0,也就是表示了两种不同的状态,而芯片运行的原理就是布尔代数。详细的原理可以查看《为什么计算机能读懂1 和 0 ?》。

        计算计算机能够认识0或者1,那么最初的设计的逻辑指令和数据都以二进制形式表示,于是也就有了以穿孔纸带作为输入、输出的计算机,穿孔表示1,无孔表示0. 那时候能玩计算机的真是上古大神了,可是如果一直用1和0来作为程序和数据输入,效率低下而且可扩展性非常不好。那用自动化思想来说,凡是机器能做的为什么要交给人去做呢?于是慢慢也就有了汇编语言,这不汇编里面的”mov”, “add”, “dec”等等一眼看去就能知道是什么含义了,于是那时候的程序员就可以摆脱写1和0的程序的烦恼了,在写完汇编程序后,用汇编编译器直接翻译为机器识别的二进制码就可以了。再接着一些高级语言Pascal,Fortran,C语言啊等等应运而生,面对不同的需求,高级语言的语法也不同,目标却是一致的,让程序开发者越来越方便。而他们的编译器,就干着一件特别重要的事儿,将高级语言编译成机器可识别的指令和数据。

操作系统

        最初的程序只能一个接一个的执行,后来发展了有多道程序(Multiprogramming)的处理方法,即有一个监控程序发现正在执行的程序暂时不需要CPU的使用(比如等待某个I/O事件),则监控程序调度另一个等待执行的程序进行执行。但这种方法太过简单粗暴,如果正在执行的程序一直占用CPU,那么某一个紧急的等待执行的程序还是没有及时得到调度。接着又有了分时系统(Time-Sharing Sytem),在书中提到在Windows 3.1中,程序调用Yield、GetMesssage和PeekMessage几个系统调用时,操作系统会判断是否有其他程序正在等待CPU,如果有,则可能暂停当前程序,让出CPU。然而在这个分时系统里面,如果有一个程序直接执行while(1)这种霸占CPU的死循环,则整个系统也得不到切换。这个主要是书中的描述,但是我认为这个并不是真正的分时系统,改革进行的不够彻底,真正的分时,应该是让程序能够真正的公平的获取到执行的时间片。当前长使用的调度方式是多任务(Multi-tasking)系统,这个时候调度策略可以根据每个Task的优先级进行调度(优先级策略:轮转法,先来先服务,多级反馈队列等算法,在此暂时就不做过多的研究)。

        说完调度,咱么再谈一谈设备的使用。比如要在一个显示设备上画一根直线,最原先的方式是往I/O端口0x1001写一个命令0x1111,然后从端口0x1002读取一个4字节的显存地址,然后在先存上画直线,可是如果换成了另一个设备,可能又是另一种做法?如果直接这样操作十分的繁琐,如果划一条直线只需要调用LineTo函数该多好啊,然后函数的内部屏蔽了各种设备的区别。

        再来谈谈内存,在程序运行的时候,程序体是要被加载到内存的。在最初的时候面临着以下几个问题:

        地址空间不隔离:程序直接访问内存,这样很容易访问到其他程序的内存地址空间,造成程序执行异常。

        内存使用效率低:比如一个128M的内存,如果A程序需要使用80M,这时候需要执行B程序,而B程序也需要80M空间,出现空间不足时,又因为需要连续的内存空间,则需将A整个程序导出到磁盘,然后再加载B进行执行。如果您还要访问程序A呢,则又需要将B导出到磁盘,再导入A进行执行。这样做内存的使用率也是非常低下的。

        程序运行的地址不确定:由于直接访问物理内存,则在载入程序到内存之前,内存地址是无法确定的。

        于是就引入了虚拟内存的方法去解决这个问题,每一个进程/任务都有自己独立的虚拟地址空间,一般情况下,比如是32位的地址线,不管实际的物理内存有多少,虚拟内存的空间为4G大小。有了虚拟内存,程序只需要访问自己的虚拟地址,然后系统会将其转换为实际的物理地址,那么地址空间不隔离程序运行地址的不正确的问题就解决了。为了解决内存使用效率低的问题,又有了分页的概念,简单的描述就是,将虚拟内存和物理内存分割为相等大小的一块一块的,每一块的概念也就叫做一页。Intel Pentium系列处理器支持4KB或者4MB的大小。实际的程序,也将分为一页一页的和实际的物理内存相对应,比如一个128M的物理内存,A程序使用了80M,这时候需要执行B程序,B程序也需要80M的空间,这时候如果完全加载B程序,则A程序只需要让出32M的空间(如果是4M分页,则换出8个分页到磁盘),如果A访问到被替换到磁盘的分页,则这时候,只需要将这个分页从磁盘替换到内存中。再也不需要,牵一发而动全身了,大大的提高了内存的利用效率。

        以上提到了任务调度,设备使用,内存管理,这么些高级的管理功能,当然需要有一个响亮的称号,叫做操作系统。有了操作系统,他可以去负责任务调度,内存分配,他也可以帮我们在内核态实现设备驱动,用户态提供系统调用,应用程序。大大的方便了广大程序员和电脑使用者。关于操作系统的理论,广而深,有空的时候可以慢慢研究。

线程

        一个进程由至少一个线程或者多个线程构成,线程是一个程序执行流程的最小单元。比如CPU有两个核,一个进程由两个线程,则可以一个CPU核跑一个线程,实现两个线程并发执行。一个进程内的线程共享进程空间,共享资源等等,每个线程由线程ID,当前指令指针,寄存器集合和栈等组成。

         线程的状态大致可分为3个:

         运行(Running): 线程正在执行

         就绪(Ready): 线程可以立刻执行,但CPU正在被使用

         等待(Waiting): 线程正在等待某一个事件(比如IO)发生,无法执行。

         这三个状态的切换如下图所示:


         多线程程序有可能会访问同一个共享资源,而这个资源很有可能只允许互斥访问(即同一时间只允许一个线程访问),或者多个线程之间需要同步执行。(在这里就不再构造资源互斥,和线程同步的场景了,本书的重点并不在这里,相信有实际编程经验的都能够理解。)对于线程间的互斥执行和同步,一般可以使用信号量,互斥量,临界区,读写锁等等去实现。

         如果在程序中采用了volatile变量和锁,就一定要保证多线程的安全吗?不一定,因为volatile变量保证的是相关的指令不会在编译的时候进行优化,互斥量保证了其保护区域内的资源互斥访问,但是CPU有时候会动态的调整指令优化执行顺序,从而让你的多线程程序变的不安全。这种情况一般称为CPU乱序。有一些CPU提供指令(一般称之为barrier)阻止CPU将该指令之前的指令交换到barrier之后,反之亦然。

        结束语: 知识在不断的加深,理解和实践,才会变成自己的能力和经验。希望大家有问题一起探讨。

猜你喜欢

转载自blog.csdn.net/CJF_iceKing/article/details/46831257