Kaito's Blog

致力成为一枚silver bullet.

0%

在上篇文章:Scrapy源码分析(一)架构概览,我们主要从整体上了解了 Scrapy 的架构和数据流转,并没有深入分析每个模块。从这篇文章开始,我将带你详细剖析 Scrapy 的运行原理。

这篇文章,我们先从最基础的运行入口来讲,来看一下 Scrapy 究竟是如何运行起来的。

scrapy 命令从哪来?

当我们基于 Scrapy 写好一个爬虫后,想要把我们的爬虫运行起来,怎么做?非常简单,只需要执行以下命令就可以了。

1
scrapy crawl <spider_name>

通过这个命令,我们的爬虫就真正开始工作了。那么从命令行到执行爬虫逻辑,这个过程中到底发生了什么?

在开始之前,不知道你有没有和我一样的疑惑,我们执行的 scrapy 命令从何而来?

实际上,当你成功安装好 Scrapy 后,使用如下命令,就能找到这个命令文件,这个文件就是 Scrapy 的运行入口:

1
2
$ which scrapy
/usr/local/bin/scrapy

使用编辑打开这个文件,你会发现,它其实它就是一个 Python 脚本,而且代码非常少。

1
2
3
4
5
6
7
8
import re
import sys

from scrapy.cmdline import execute

if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(execute())

安装好 Scrapy 后,为什么入口点是这里呢?

答案就在于 Scrapy 的安装文件 setup.py 中,我们找到这个文件,就会发现在这个文件里,已经声明好了程序的运行入口处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from os.path import dirname, join
from setuptools import setup, find_packages

setup(
name='Scrapy',
version=version,
url='http://scrapy.org',
...
entry_points={ # 运行入口在这里:scrapy.cmdline:execute
'console_scripts': ['scrapy = scrapy.cmdline:execute']
},
classifiers=[
...
],
install_requires=[
...
],
)

我们需要关注的是 entry_points 配置,它就是调用 Scrapy 开始的地方,也就是cmdline.pyexecute 方法。

也就是说,我们在安装 Scrapy 的过程中,setuptools 这个包管理工具,就会把上述代码生成好并放在可执行路径下,这样当我们调用 scrapy 命令时,就会调用 Scrapy 模块下的 cmdline.pyexecute 方法。

而且在这这里,我们可以学到一个小技巧——如何用 Python 编写一个可执行文件?其实非常简单,模仿上面的思路,只需要以下几步即可完成:

  1. 编写一个带有 main 方法的 Python 模块(首行必须注明 Python 执行路径)
  2. 去掉.py后缀名
  3. 修改权限为可执行(chmod +x 文件名)
  4. 直接用文件名就可以执行这个 Python 文件

例如,我们创建一个文件 mycmd,在这个文件中编写一个 main 方法,这个方法编写我们想要的执行的逻辑,之后执行 chmod +x mycmd 把这个文件权限变成可执行,最后通过 ./mycmd 就可以执行这段代码了,而不再需要通过 python <file.py> 方式就可以执行了,是不是很简单?

运行入口(execute.py)

现在,我们已经知道了 Scrapy 的运行入口是 scrapy/cmdline.pyexecute 方法,那我们就看一下这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def execute(argv=None, settings=None):
if argv is None:
argv = sys.argv

# --- 兼容低版本scrapy.conf.settings的配置 ---
if settings is None and 'scrapy.conf' in sys.modules:
from scrapy import conf
if hasattr(conf, 'settings'):
settings = conf.settings
# -----------------------------------------

# 初始化环境、获取项目配置参数 返回settings对象
if settings is None:
settings = get_project_settings()
# 校验弃用的配置项
check_deprecated_settings(settings)

# --- 兼容低版本scrapy.conf.settings的配置 ---
import warnings
from scrapy.exceptions import ScrapyDeprecationWarning
with warnings.catch_warnings():
warnings.simplefilter("ignore", ScrapyDeprecationWarning)
from scrapy import conf
conf.settings = settings
# ---------------------------------------

# 执行环境是否在项目中 主要检查scrapy.cfg配置文件是否存在
inproject = inside_project()

# 读取commands文件夹 把所有的命令类转换为{cmd_name: cmd_instance}的字典
cmds = _get_commands_dict(settings, inproject)
# 从命令行解析出执行的是哪个命令
cmdname = _pop_command_name(argv)
parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), \
conflict_handler='resolve')
if not cmdname:
_print_commands(settings, inproject)
sys.exit(0)
elif cmdname not in cmds:
_print_unknown_command(settings, cmdname, inproject)
sys.exit(2)

# 根据命令名称找到对应的命令实例
cmd = cmds[cmdname]
parser.usage = "scrapy %s %s" % (cmdname, cmd.syntax())
parser.description = cmd.long_desc()
# 设置项目配置和级别为command
settings.setdict(cmd.default_settings, priority='command')
cmd.settings = settings
# 添加解析规则
cmd.add_options(parser)
# 解析命令参数,并交由Scrapy命令实例处理
opts, args = parser.parse_args(args=argv[1:])
_run_print_help(parser, cmd.process_options, args, opts)

# 初始化CrawlerProcess实例 并给命令实例添加crawler_process属性
cmd.crawler_process = CrawlerProcess(settings)
# 执行命令实例的run方法
_run_print_help(parser, _run_command, cmd, args, opts)
sys.exit(cmd.exitcode)

这块代码就是 Scrapy 执行的运行入口了,我们根据注释就能看到,这里的主要工作包括配置初始化、命令解析、爬虫类加载、运行爬虫这几步。

了解了整个入口的流程,下面我会对每个步骤进行详细的分析。

阅读全文 »

在爬虫开发领域,使用最多的主流语言主要是 Java 和 Python 这两种,如果你经常使用 Python 开发爬虫,那么肯定听说过 Scrapy 这个开源框架,它正是由Python编写的。

Scrapy 在开源爬虫框架中名声非常大,几乎用 Python 写爬虫的人,都用过这个框架。而且业界很多开源的爬虫框架都是模仿和参考 Scrapy 的思想和架构实现的,如果想深入学习爬虫,研读 Scrapy 的源码还是很有必要的。

从这篇文章开始,我就和你分享一下当时我在做爬虫时,阅读 Scrapy 源码的思路和经验总结。

这篇文章我们先来介绍一下 Scrapy 的整体架构,从宏观层面上学习一下 Scrapy 运行的流程。之后的几篇文章,我会带你深入到每个模块,剖析这个框架的实现细节。

介绍

首先,我们先来看一下 Scrapy 的官方是如何介绍它的。从官方网站,我们可以看到 Scrapy 如下定义。

Scrapy 是一个基于 Python 语言编写的开源爬虫框架,它可以帮你快速、简单的方式构建爬虫,并从网站上提取你所需要的数据。

也就是说,使用 Scrapy 能帮你快速简单的编写一个爬虫,用来抓取网站数据。

本篇文章不再介绍 Scrapy 的安装和使用,这个系列主要通过阅读源码讲解 Scrapy 的实现思路,关于如何安装和使用的问题,请参考官方网站官方文档学习。(注:写本篇文章时,Scrapy 版本为1.2,虽然版本有些低,但与最新版的实现思路基本没有很大出入。)

使用 Scrapy 开发一个爬虫非常简单,这里使用 Scrapy 官网上的例子来说明如何编写一个简单爬虫:

简单来讲,编写和运行一个爬虫只需以下几步:

  1. 使用 scrapy startproject 命令创建一个爬虫模板,或自己按模板编写爬虫代码
  2. 定义一个爬虫类,并继承 scrapy.Spider,然后重写 parse 方法
  3. parse 方法里编写网页解析逻辑,以及抓取路径
  4. 使用 scrapy runspider <spider_file.py> 运行这个爬虫

可见,使用 Scrapy 编写简单的几行代码,就能采集到一个网站页面的数据,非常方便。

但是在这背后到底发生了什么?Scrapy 到底是如何帮助我们工作的呢?

架构

要想知道 Scrapy 是如何工作的,首先我们来看一下 Scrapy 的架构图,从宏观角度来了解一下它是如何运行的:

核心模块

从架构图可以看到,Scrapy 主要包含以下五大模块:

  • Scrapy Engine:核心引擎,负责控制和调度各个组件,保证数据流转;
  • Scheduler:负责管理任务、过滤任务、输出任务的调度器,存储、去重任务都在此控制;
  • Downloader:下载器,负责在网络上下载数据,输入待下载的 URL,输出下载结果;
  • Spiders:我们自己编写的爬虫逻辑,定义抓取意图;
  • Item Pipeline:负责输出结构化数据,可自定义格式和输出的位置;

如果你观察地比较仔细的话,可以看到还有两个模块:

  • Downloader middlewares:介于引擎和下载器之间,可以在网页在下载前、后进行逻辑处理;
  • Spider middlewares:介于引擎和爬虫之间,在向爬虫输入下载结果前,和爬虫输出请求 / 数据后进行逻辑处理;

了解了这些核心模块,我们再来看使用 Scrapy 时,它内部的采集流程是如何流转的,也就是说各个模块是如何交互协作,来完成整个抓取任务的。

运行流程

按照上面架构图标识出的序号,我们可以看到,Scrapy 运行时的数据流转大概是这样的:

  1. 引擎自定义爬虫中获取初始化请求(也叫种子 URL);
  2. 引擎把该请求放入调度器中,同时调度器向引擎获取待下载的请求;
  3. 调度器把待下载的请求发给引擎;
  4. 引擎发送请求给下载器,中间会经过一系列下载器中间件
  5. 这个请求通过下载器下载完成后,生成一个响应对象,返回给引擎,这中间会再次经过一系列下载器中间件
  6. 引擎接收到下载器返回的响应后,发送给爬虫,中间会经过一系列爬虫中间件,最后执行爬虫自定义的解析逻辑
  7. 爬虫执行完自定义的解析逻辑后,生成结果对象新的请求对象给引擎,再次经过一系列爬虫中间件
  8. 引擎把爬虫返回的结果对象交由结果处理器处理,把新的请求通过引擎再交给调度器
  9. 重复执行1-8,直到调度器中没有新的请求处理,任务结束;
阅读全文 »

背景

不知道大家在用Markdown语法写博客时有没有遇到这样的问题?想使用Markdown语法插入一张图,大概要经过以下几个步骤:

  • 截图保存图片到本地
  • 打开并登陆注册好的图床网站
  • 上传图片至图床
  • 复制生成好的图片地址
  • 用Markdown语法插入图片

如果插入图片过多,这样来回操作多次,简直要崩溃!经过网上搜索,貌似有2种解决方案:

  • 付费购买此类软件
  • 自己写一个小工具,简化工作

我当然是属于第二种,想一想这个功能也不复杂,参考了有人已经实现出来的代码和思路,但是不忍其代码写的太渣太low,便自己造了这个轮子。

功能

先来看效果图:

主要功能就是:复制本地图片或截图,快速上传图片至七牛云空间,并获取Markdown格式的图片地址。

阅读全文 »

前两篇文章:Mac效率利器(一)系统管理篇Mac效率利器(二)开发篇,主要介绍了系统、开发相关的效率利器。

这篇文章主要介绍一下在工作中同样能提升效率的一些辅助小工具。

Manico

这个工具是我每天必用的一款辅助工具,简单来说,这个工具可以给你安装的软件定义一个快捷键,一般是以option为前缀。以后每次打开软件时,就可以完全脱离鼠标,使用定义好的快捷键快速启动或切换。

这与之前的command + tab相比,有什么不同?

使用command + tab

  • 打开任务切换视图;
  • 继续按一次或n次tab,切换到已打开的软件;

使用Manico后,为每个软件定义好快捷键后:

  • option + 快捷键,直接切换至软件

一步到位,而且,Macico在没有打开软件的情况下,使用快捷键是可以直接打开的,而前者则不行。

这真的是解放了我的双手,再也不用繁琐的按commad + tab n次来切换软件了。

注意:只有购买付费版,才可以自定义快捷键,否则只能使用默认的快捷键。

Spectacle

窗口管理软件,使用快捷键控制软件窗口的布局,最重要的是免费

当然没有Moom那么强大可以随意控制窗口的大小,但作为键盘党,能脱离鼠标快速调整窗口布局才是最刚性的需求。

使用举例:

  • option + command + :窗口填充屏幕左半边
  • option + command + :窗口填充屏幕右半边
  • option + command + :窗口填充屏幕上半边
  • option + command + :窗口填充屏幕下半边
  • option + command + F:窗口全屏
  • option + command + C:窗口居中
  • control + option + command + :窗口移至上个显示器屏幕
  • control + option + command + :窗口移至下个显示器屏幕

Pomotodo

对于有拖延症的同学,在工作上或学习上效率会越来越低,如何管理自己的时间,提升时间利用率?

这就用到了番茄时间法,简单来说就是,以25分钟为一个工作单位(也叫番茄),这25分钟全身心投入一件事中,不被外界干扰,同时会有时钟倒计时,会有紧迫感。

倒计时结束后,需要填写这25分钟做了什么事情(也叫土豆),然后休息5分钟,这5分钟可以冲杯咖啡、喝口水、回复邮件或微信等,5分钟过后,可以继续25分钟(番茄),往复循环。

这个工具软件除了上述功能外,还可以自动形成工作数据统计,例如展示出哪段时间内工作效率最高,某段时间内在哪件事上占用时间最多,帮助你分析你的时间都用在哪里,逐渐提高时间利用率。

阅读全文 »

上一篇文章,主要推荐了Mac上系统管理相关的效率利器:Mac效率利器(一)系统管理篇,今天这篇文章主要推荐下Mac平台与开发相关的效率利器。

iTerm2

如果是服务端开发,自然少不了终端工具,iTerm2算是Mac平台的王牌终端工具,它增强了普通终端,提供了开发中很常用的功能,例如分屏、检索高亮、命令自动补全、选中自动复制、剪贴板历史、终端主题等强大功能。总之,用上了iTerm2,在开发中能达到事半功倍的效果。

会话分屏

command + d(水平分割)、command + shift + d(垂直分割):

检索高亮

command + f,按Tab自动补全,然后按option + 回车自动粘贴:

命令自动补全

快捷键:command + ;

剪贴板历史

快捷键:command + shift + h

主题配置

Dash

在写代码时,肯定少不了查文档,打开官方网站,找到Document,然后搜索关键字,之后打开N个网页,不但网页越开越多,而且每次这个流程大部分都是重复的工作。

Dash很好的把这些工作集成在一起,只需打开Dash,搜索关键字即可,Dash提供了非常多官方文档,例如:JQuery、CSS、HTML、Python、Git、Java,在工具中直接下载即可。没有收录到Dash的文档,自己也可以制作导入。

模糊检索

代码片段

Dash除了支持管理、搜索文档之外,还提供了代码片段的收录存储。

例如,我先定义了一段代码片段,然后定义别名为hello

当我在开发工具中输入hello后,就会出现:

最重要的是,代码片段中可以预留占位符,然后在用的时候再进行填充。这个__name__就是代码片段中预留的占位符,在使用的时候,就可以自定义填充内容。

Dash还与其他很多开发工具和软件友好集成,例如Alfred

阅读全文 »

作为程序员,要想提高工作和开发效率,总是需要一些神兵利器的,正所谓工欲善其事,必先利其器,这样做事才能达到事半功倍的效果!这次就来介绍一下Mac平台下的利器软件。

先放一张到目前为止积累的利器图:

Mac软件

下面分别介绍一下用于系统的管理利器。

Alfred

用过Mac的人都知道,必装神器,称得上Mac平台的利器TOP 1,自从用上了Alfred,键盘党真是爽到欲罢不能,从此解放双手,效率提升N倍。

Alfred提供一个快捷入口,这个快捷入口提供了非常多平时常用的功能,而且还提供强大的工作流控制。

快速打开应用

Alfred快速打开应用

此功能可以快速打开某个应用程序,Mac的Dock可以隐藏起来了。

全局文件搜索、定位、文件内容搜索

Alfred快速查找文件

Alfred快速打开文件夹

支持模糊检索,只要记得文件、文件夹、文件内容任意一项,便可以快速定位到文件位置,并且支持记录下文件后,多个文件进一步操作。

多网站快速搜索

Alfred快速搜索

Alfred快速搜索

之前的操作是,先打开浏览器,再输入网址打开网站,然后在搜索框中输入搜索词,回车,才出现搜索结果。

现在好了,流程大大简化,打开alfred,输出搜索词,选择网站,回车!

计算器

Alfred在线计算器

不用打开任何计算器软件,直接在线计算得出结果,回车自动复制。

剪切板历史、文字片段

Alfred剪切板配置

Alfred剪切板历史

传统的剪切板,每次复制之后,都会覆盖之前的内容,alfred自带剪切板历史功能,并且支持检索。

同时还提供了文字片段功能,可以预先保存常用的内容片段,然后在使用时使用快捷键打开alfred片段功能,输出关键词,回车,直接粘贴出内容,非常方便!

系统控制

Alfred系统控制

alfred提供了系统管理的快捷命令,例如锁屏、睡眠、关机、退出程序、清理垃圾桶等等,全键盘操作控制系统状态。

工作流集成和开发(付费功能)

alfred还提供了工作流功能,此功能是付费功能,不过非常超值。

Alfred工作流

这个功能可以让开发者开发自己的插件,根据自己的工作习惯,开发出符合自身的工作流。

例如,我想在豆瓣上搜一本书,常规做法是,打开浏览器,打开豆瓣,输入书名,点击搜索。

alfred的工作流,就是把这一流程,通过程序一步到位,如下图:

Alfred豆瓣工作流

其他的工作流:

Alfred豆瓣工作流

Alfred github工作流

Alfred知乎工作流

Alfred翻译工作流

alfred工作流插件支持大部分主流语言开发,上手简单。

网上有其他人开发好的插件,直接下载使用即可:http://www.alfredworkflow.com/

CleanMyMac

脱离了windows系统,总算脱离了各种全家桶,Mac的世界很清静,不过平时清理垃圾什么的还是很有必要的。

CleanMyMac

CleanMyMac操作简单,并且支持大型和旧文件扫描、干净卸载软件等功能,非常值得安装。

阅读全文 »

很久没来更新了,最近事情比较多,加上工作有调动比较忙,现在好了,事情进展还算顺利,也正好在这空闲时间之间休了几天假,去了趟香港,玩的还算开心。

出行准备

前几个月工作很忙,心情也很压抑,心里想必须要找个机会出去好好放松一下,工作上的事情定的差不多了,就计划着出行的准备。

原本打算想去成都玩一下,身边的很多人都推荐说值得一去,由于计划的也比较晚,看了下机票确实贵了不少。后来也不知怎么的突然想到了香港这个地方,看了看机票,也不比成都贵多少,心想,等了这么久,要玩还不如玩的痛快些,正好今年过年时,在家办护照时顺便也办了港澳通行证,正好用的上,哈哈。现在想想,真的是很多事情就好像冥冥之中安排好似的,签证费也不会浪费掉。

决定了要去香港,便开始做攻略,其实也是很匆忙,白天上班,晚上回来还要在网上搜攻略。最终盘算一下,决定购买机票+酒店的自由行套餐划算些,共计5天4晚,香港这个地方也不大,也足够玩了。

然后在网上买了电话卡、地铁卡,周末去银行兑换了港币,总比确实比在境内玩要准备的东西多一些。

第一天

万事俱备,由于飞机起飞时间较早,凌晨就要出发,前天晚上预约了专车送机场,师傅服务也很好。由于是L签,到机场后联系送关的人,又等办完登机牌,再去过关、安检,这里真的要吐槽一下,北京机场安检效率是真低,慢的要死,导致差点延误飞机,在起飞前10分钟冲进去的,第一次坐飞机出境就弄的这么紧张,也是醉了。

飞机起飞的感觉很棒,哈哈。落地后坐巴士到酒店,沿途看到海,心情还不错,香港的巴士确实很干净,而且还有专门的行李放置处。

下午在酒店休息,晚上去了中环,吃了茶餐厅“添好运”,人不算多,但是味道确实不错。

然后坐天星小轮,从中环到尖沙咀,看了香港夜景,很美。

尖沙咀的人很多,而且有很多街头歌手。

乘坐天星小轮看夜景

尖沙咀夜景

香港夜景

第二天

第二天早早的收拾完,然后去了迪士尼乐园,因为之前已经做好了攻略,去的这天也不是节假日,人相对来说较少。

香港迪士尼

迪士尼乐园是个梦幻乐园,没有欢乐谷那么惊悚刺激,喜欢的几个项目:

  • 飞跃太空山,一个室内过山车,在完全漆黑的室内,看不到轨道,然后会出现类似太空的场景,就好像在太空中穿梭,非常棒!
  • 米奇的幻想世界,一个4D的视频演出,画面立体感非常强,好多镜头的东西都像是在眼前,而且随着故事情景的变化现实环境也会有相同的变化,就好像身临其境。
  • 迷离大宅,非常棒的视觉之旅,而且有些惊悚刺激的场景,里面很多东西设计的都很精巧。
  • 森林河流之旅,坐船体验森林的冒险之旅,在河中看到了很多很逼真的动物和惊险场景。

玩到下午,已经把经典项目都玩了一遍,然后等着晚上8点半放烟花。

不得不说香港到了雨季几乎是天天下雨,还好出门一直带着雨伞,一天下下停停。

总是在电视上看到的睡公主城堡,终于看到了,大家都在等待烟花演出。

迪士尼睡公主城堡

阅读全文 »

18、从算盘到芯片

历史上,人们为了简化数学运算,发明了很多精巧的工具和机器。

早期人类社会使用如下工具计算:

  • 古希腊和美洲土著:小卵石或谷粒
  • 欧洲:计数板
  • 中东和中国:算盘

由于这些工具无法做乘法和除法,苏格兰数学家发明了对数:A乘以B等于A和B对数之和,即先在对数表查找这2个值,相加后再在对数表逆向寻找其乘积。

随后的400年里,人们致力于建立对数表的工作,也有一些人设计出一些小装置代替对数表:带对数刻度的滑尺

其次手持计算器出现,利用由刻在骨头、牛角、象牙上的数字条组成的乘法辅助器,从而制造出最早的机械计算器。但一个加法器的关键是处理进位能否成功。直到19世纪后半叶,机械计算器才得以出现并使用。

而在18世纪初,人类发明了自动织布机,使用打孔的金属卡片控制织物的图案,这对计算的历史产生了深远的影响。

在18世纪,人类依赖计算器的计算功能,但是此时的对数表使用起来耗费时间,英国数学家和经济学家发明了选择性的对数计算,即差分法,然后其设计出了差分机,其本质就是一个大型机械加法器,但这个差分机没有完成过。随后,这个数学家有了更好的想法,这就是解析机,解析机包含一个存储部件(类似存储器的概念)和一个运算部件(类似算数逻辑单元),乘法可以通过重复加法求解,除法通过重复减法求解。

此解析机可以使用改造后的织布机的卡片来编程,解析机编织出了代数的结构模型。

这个英国数学家是第一个意识到条件跳转在计算机中重要性的人。

但到18世纪中期,差分机最终由一对父子制造出来,人们已经遗忘了之前的英国数学家设计的差分机。

计算史另一个转折点源于美国宪法,其中有一条要求每10年进行一次人口普查,此时的人口普查数据采集工作大约要花费7年以上的时间,这个时间太久了,因此有人开始研究自动系统可能性。此时使用了穿孔卡片记录每个人的特征,但这不是二进制编码的,原因是:对于此时的人来说,将年龄转换为二进制数要求太高了,而且真正的二进制系统是可以将所有孔打穿的,这将会使卡片破裂。

之后,为了对人口数据进行统计,人们发明了制表机

在当时的人口普查中使用这种自动化技术实验取得了重大成就,在实验中使用超过6200万张卡片,是人口普查的2倍,但时间却缩短了三分之一。

这个创造这种机器设备的人1896年创办了制表机公司,租借并出售穿孔卡片设备。到1911年公司合并,公司更名为计算制表记录公司,也叫C-T-R公司。再到1915年,托马斯·J·华盛顿成为这个公司的总裁,在1924年将公司更名为国际商业机器公司,即IBM

在1928年,人口普查使用的卡片逐渐演变为IBM卡片,之后使用了将近50年。

第一台继电式计算机在1935年被制造出来,使用了二进制数,到了1937年,贝尔实验室的一个人在厨房中连接了一个1位加法器,称为K机器,这促使1939年贝尔实验室中复数计算机的诞生。

同一时期,哈佛大学研究生与IBM合作,在1943年创造出一台自动连续可控计算机,也就是闻名世界的Harvard Mark I,这是一台可以打印表格的数字计算机。

但继电器不是最完美的设备,频繁工作会导致断裂,如果其中有污垢也会导致失效。

在20世纪40年代,真空管已被广泛引用于放大电话信号,真空管同样可以连接成与门、或门、与非门、与或门,那么就可以制造出加法器、选择器、解码器、触发器、计数器

但真空管同样存在价格昂贵、耗电量大、产生热量多等缺点,但好处是真空管可以在百万分之一秒内发生转变,要比继电器快1000倍。

到了20世纪40年代初期,新设计的计算机使用真空管取代继电器。

1937年艾伦·M·图灵在破译德国代码生成器生成的代码项目中,发表2篇论文,首次提出可计算性概念测试机器智能的方法,这对计算机的发展产生深远影响。

1945年人们设计出了曾经最大的计算机,但设计的并不理想,到1946年,约翰·冯·诺依曼提出计算机应该使用二进制,并且应当拥有存储器,并且可以在存储器中进行寻址操作取得代码执行并保存数据,也允许条件跳转,这就是著名的存储程序概念,这个阶段被称为冯·诺依曼结构。但这个结构也有问题,因为在执行命令中,从存储器取指令要花费3/4的时间。

考虑到成本效益,人们开发出了磁芯存储器

1948年在贝尔实验室工作的克劳德·香农发表文章,首次将“位”的概念介绍给世界,并开创了“信息论”研究领域。

与此同时,哈佛大学的诺博尔特·韦纳提出了控制论,表示计算机和机器人的机械原理之间的关系。

1948年IBM发布了第一个商用计算机系统,代号701。

而在1947年,贝尔实验室的两个物理学家制造出晶体管,晶体管开创了固态电子器件的时代。但晶体管刚开始是商用与助听器。

1956年,肖克利离开了贝尔实验室,成立了半导体实验室,地点在加利福尼亚帕罗奥图市,其他半导体和计算机公司相继再此建立基业,这个地方就是硅谷

晶体管同样可以连接成振荡器、加法器、选择器、解码器,振荡器可以组成锁存器或RAM阵列

1958年,人们想到了可以在一块硅片制造出多个晶体管、电阻和其他电子元件的方法,由此演化出集成电路。之后便有了摩尔定律:每18个月同一块芯片上集成晶体管数目会翻一倍

影响集成电路性能的最重要因素是传播时间,即输入端发生变化引起输出端发生变化所需要的时间,传播时间单位是纳秒(ns)

  • 1秒 = 1000毫秒(千分之一秒)
  • 1秒 = 1000000微妙(百万分之一秒)
  • 1秒 = 10亿分之一秒

通常情况下,每秒至少震荡一百万个周期,称为1兆赫兹(MHz)

1970年,真正在使用集成电路在一块电路板上制造出完整的计算机处理器的公司是英特尔公司,发布的第一款产品是一个可以存储1024位数据的芯片。

英特尔为了做出通用性的计算机,然后制造出了Intel 4004,它是第一块微处理器

它是一个4位微处理器,其每秒的最大时钟频率为108KHz,最大时钟频率也叫主频,时钟频率决定了执行一条指令所需要的时间,处理器的位数也影响处理器的速度。

到了1972年4月,英特尔发布了8008芯片,时钟频率200KHz、可寻址空间16KB的8位处理器。

再后来到了1974年5月,英特尔和摩托罗拉同时发布了8008微处理器的改进版——8080处理器和6800处理器,这两款芯片改变了整个世界。

19、两种典型的微处理器

1974年英特尔和摩托罗拉分别推出了8080处理器和6800处理器,这两个都是8位的微处理器,运行时钟频率分别为2MHz和1MHz,寻址空间都是64KB。

这两个微处理器都是40个管脚的集成电路,其中包括:电源输入、输入信号、输出信号、控制信号

微处理器只会读取二进制数,计算,然后输出二进制数。

为了方便编程和记忆,可以为每个处理器操作码指派一个助记符,其中加载、保存和内容转移的助记符分别为STA、LDA、MOV,8080微处理器内部还设置了6个8位寄存器,这些寄存器本质上都是锁存器。

处理器中有寄存器的好处是,当计算机程序同时用到多个数据,这些数据存放在寄存器比存放在存储器更方便访问,速度更快。

微处理器的寻址方式分为:

  • 直接寻址
  • 间接寻址
  • 立即数寻址

8080处理器也包含了标志位寄存器,并可以通过运算改变标志位的值,这些标志位可以帮助处理器完成各种运算逻辑和跳转逻辑

微处理器可以寻址的存储器叫做随机存储器(RAM),主要原因是:只要提供了存储器地址,微处理器就可以用非常简便的方式访问存储器的任意存储单元,RAM就像一本书,可以翻到它的任意一页。

在某些情况下,使用不同的寻址方式访问存储器也是有好处的,例如一项工作既不是随机也不是顺序的,解决这种工作的存储方式叫做堆栈(stack),特点是:最先保存的数据最后取出,最后保存的数据最先取出,对应的操作分别为压栈(push)和弹栈(pop)

在编写汇编程序时,使用堆栈和标志位,就可以完成函数的调用和返回

微处理器并不是只连接存储器,完整的计算机系统需要输入/输出设备(I/O)以实现人机交互。微处理器与外设通信,是通过与外设对应的特定地址(接口)对其进行读写操作

外设操作时,是如何引起微处理器的注意呢?也就是说,外设的行为需要马上得到处理器的响应,这个过程叫做中断

以上介绍的均为8080微处理器,下面来看摩托罗拉的6800处理。

6800微处理器与8080非常相似,也有40个管脚进行输入、输出、控制。6800处理器有一个16位的程序计数器PC、一个16位的堆栈指针SP、一个8位的状态寄存器,以及两个8位的累加器A、B。但6800没有设置其他的8位寄存器。

6800同样可以时间加载、保存、跳转,但其操作码对应的助记符完全不同,而且其标志位也与8080不同。

还有一个很重要的区别是:两个微处理器在保存多字节数据时,地址排序不同,分别为从小到大和从大到小

两种微处理器的操作码不同,导致的问题就是处理器的汇编语言不同。

1975年,英特尔8080处理器被应用在第一台个人电脑上

在8080之后,英特尔又推出8085芯片,而其竞争对手也推出了Z-80芯片,Z-80与8080完全兼容,而且增加了很多非常有用的指令。

同样,在1977年,由斯蒂夫·乔布斯和史蒂芬·沃兹内卡创立的苹果计算机公司推出了新一代产品Apple II,但它没有使用8080和6800,而是使用了基于MOS技术更加便宜的6502芯片,它是6800的改进增强版本

1978年6月,英特尔推出8086芯片,它是一个16位的微处理器,寻址空间1MB。但8086与8080操作码不兼容,但它包含了乘法和除法指令

一年后,英特尔又推出了8088芯片,内部结构与8086完全相同,但外部仍以字节为单位访问存储器。

IBM在个人计算机中使用了8088芯片,这种计算机叫做IBM PC

IBM大举进军个人计算机市场,使用的都是英特尔处理器。英特尔相继发布了186芯片、286芯片、386芯片、486芯片,到了1993年,发布Intel奔腾系列微处理器。这些芯片的指令集都是兼容8086操作码的。

苹果公司的Macintosh发布于1984年,采用68000微处理器,它是16位微处理器,是6800的下一代产品。

到了1994年,Macintosh计算机开始使用PowerPC微处理器,此处理器由摩托罗拉、IBM以及苹果联合开发。

PowerPC采用RISC(精简指令集计算机)微处理器体系结构设计。其内部设置了大量的寄存器,运算速度非常快。

微处理器发展到现代,采用多种技术来提高运行速度。其中一种就是流水线技术,还有高速缓存技术

20、ASCII码和字符转换

计算机中存储器唯一可以存储的是比特,因此如果想要在计算机中处理信息,必须按位存储。那么计算机是如何用它来存储文本的?

为了将文本表示数字形式,我们需要构建一种系统为每一个字母赋予一个唯一的编码,包括数字和标点符号。具有这种功能的系统被称为字符编码集,系统内每个独立编码称为字符编码

那么构建这些编码需要多少比特?

就英语而言,所有大小写字母一共需要52个编码,0-9数字需要10个编码,一共62个编码,加上标点符号,数量超过64个,也就是说,一个编码至少需要6个比特。但无论如何字符数应该不超过128个,而且远远不够128个,也就是说编码长度不会超过8位

所以答案就是7位编码时,不需要转义字符,而且可以区分大小写字母。

如何指派这些编码?当然,自己造一台计算机,可以任意指定字符对应的编码。但是这是不通用的,也与其他计算机不兼容。

这样一来,随意编码就不合适了。如果在处理文本时,字母表的编码是按顺序来的,那么工作起来就会很便利,优点是:字母排序和分类将变得简单

于是就产生了一种标准,被称为美国信息交换标准码,简称ASCII码

ASCII码是7位编码,二进制取值范围是00000001111111,对应十六进制是00h7Fh。

在ASCII码中,大写字母与其对应的小写字母ASCII码相差20h。(大写字母=小写字母-20h)

这种规律大大简化了程序代码的编写。

ASCII码中有95个编码称为图形文字,因为其可以显示出来,还有33个控制字符,它们用来执行某一特定功能,因而不用显示出来。

其中,回车符使打印头换行并转移至当前页的最前端,换行符使打印头转移至当前位置下一行。

尽管ASCII码是计算机领域最重要的标准,但其缺点是:只能用于美国。其他国家有太多字符无法表示。

于是就想到一种扩展ASCII字符集的方案,计算机采用8位编码存储字符,这样就可以保存256个字符。在这个字符集中,编码00h7Fh与原ASCII码保持一致。编码80hFFh用来引入其他字符:例如重音字母以及非拉丁字母。

但是这种扩展ASCII码的思想开始被滥用,随之而来就产生了不同版本扩展的ASCII码,导致编码不一致。

由此,业界打算建立一个独一无二的字符编码系统,它可以用于全世界所有的语言文字。

1988年开始,几大著名计算机公司合作研究一种替代ASCII码的编码系统,取名Unicode码

Unicode码采用16位编码,每一个字符需要2个字节,00000h~FFFFh共计65536个不同字符,全世界所有人类语言,都可以使用同一个编码系统,而且具有很高的扩展性。

Unicode码前128字符编码是与ASCII码一致的,也就是兼容ASCII码。

Unicode码优点是可以表示世界所有语言,但缺点是存储空间比ASCII码要大一倍。

阅读全文 »

1、中断

CPU具有一种能力,可以在执行完当前指令之后,检测从内部产生或外部发来的特殊信息,并立即对其进行处理,这种特殊的信息,叫做中断信息

中断即CPU不再接着刚执行完的指令向下执行,而是转去处理特殊信息。

中断分为:

  • 内中断,CPU内部产生的特殊信息
  • 外中断,CPU外部产生的特殊信息

1.1、内中断

中断信息必须包含标识来源的编码,叫做中断类型码。中断类型码为一个字节型数据,可表示256种中断信息来源。

产生中断信息的事件,即中断信息的来源,叫做中断源

内中断分类:

  • 除法错误,类型码0,如div指令产生溢出
  • 单步执行,类型码1,即单步调试
  • into指令,类型码4
  • int指令,指令格式为int n,类型码为n

CPU在收到中断信息后,需要对中断信息进行处理,这可以由我们编程来决定,我们编写的处理中断信息的程序叫做中断处理程序

CPU要想执行中断处理程序,则必须知道这个程序的入口,即CS:IP指向这个入口,CPU用8位中断类型码通过中断向量表找到对应中断程序的入口地址。

中断向量表在内存中保存,对于8086CPU机,其必须存放在0000:0000~0000:03FF处,这是规定好的。

中断向量表中每个中断类型码对应中断程序的段地址和偏移地址,共占2个字节。

CPU通过中断类型码,然后在中断向量表中找到中断处理程序的段地址和偏移地址,设置CS和IP,CPU开始执行中断处理程序。这个操作是CPU自动完成的,这个过程叫做中断过程

具体过程如下:

  • 从中断信息中取得中断类型码N
  • pushf:标志寄存器的值入栈(中断过程中会改变标志寄存器的值,所以先保存之前的值)
  • TF=0,IF=0:设置标志寄存器第8位TF第9位IF的值为0
  • PUSH CSCS内容入栈
  • PUSH IPIP内容入栈
  • (IP)=(N*4),(CS)=(N*4+2):从内存地址为中断类型码*4中断类型码*4+2的两个字中读取中断处理程序入口地址,设置CS和IP
  • 执行中断处理程序

中断处理程序编写步骤

  • 保存用到的寄存器
  • 处理中断
  • 恢复用到的寄存器
  • iret指令返回

iret指令功能:

  • pop IP
  • pop CS
  • popf

引发中断的具体事件:

  • 除法错误:如果在执行div指令时,发生溢出错误,将产生中断类型码为0的中断信息,CPU检测到此信息然后引发中断过程,转去执行0号中断所对应的中断处理程序;
  • 单步中断:CPU执行完一条指令后,如果检测到TF位是1,则产生单步中断,转去执行1号中断处理程序,CPU提供单步中断功能,为单步跟踪程序执行过程提供了实现机制;
  • int指令引发中断:可自定义调用中断处理程序,例如int n将调用n号中断处理程序;

在主板的ROM中存放的程序,叫做BIOS(基本输入输出系统),主要包含以下内容:

  • 硬件系统的检测和初始化程序;
  • 外部中断和内部中断的中断例程;
  • 用于对硬件设备进行I/O操作的中断例程;
  • 其他和硬件系统相关的中断例程;

操作系统DOS也提供了中断例程,DOS中断例程就是操作系统向程序员提供的编程资源。int 21h中断例程就是DOS提供的,即程序返回功能。

在执行完向ss寄存器传送数据指令后,即使发生中断,CPU也不会响应。因为ss:sp联合指向栈顶,应该持续完成,否则将发生错误。

1.2、外中断

CPU能响应外设的输入,并向它们进行输出,这个过程叫做外中断

外设的输入不直接送入内存和CPU,而是送入相关的接口芯片端口中。CPU向外设输出也不是直接送入外设,而是先送入端口,再由芯片送到外设。CPU通过端口和外设进行联系。

外中断源分为2类:

  • 可屏蔽中断:CPU可以不响应的外中断,由标志寄存器IF决定,如果IF=1则响应,否则屏蔽中断;
    • 中断过程将IF设置为0,就是保证在进入中断处理程序后,禁止其他的可屏蔽中断
    • 8086CPU提供修改IF的指令:sti:IF=1, cli:IF=0
  • 不可屏蔽中断:
    • CPU必须响应的外中断
    • 中断类型码固定为2

几乎所有由外设引发的外中断,都是可屏蔽中断。

阅读全文 »

以下所述的汇编指令均以8086CPU为基础所讲述。

1、基础

1.1、机器语言

如果要介绍汇编语言,首先要说一下机器语言,机器语言的定义如下:

机器语言是机器指令的集合,机器指令就是一台机器可以正确执行的命令,计算机的机器指令是一列二进制数字,计算机将之转变为一列高低电平,以驱动计算机硬件进行运算。

每一种CPU都有自己的指令集,早期程序员通过用0、1数字编写的程序打在纸带或卡片上,然后输入计算机完成运算。

1.2、汇编语言

正是由于机器语言的难以辨别和记忆,于是就产生了汇编语言。

程序员通过汇编代码编写程序,通过汇编编译器,最终还是转化为机器语言,最终让计算机执行。

编写汇编代码 –> 汇编编译器(编译) –> 机器码 –> 计算机(执行)

汇编有3类指令:

  • 汇编指令:机器码助记符,是汇编语言的核心,有对应的机器码
  • 伪指令:由编译器执行,没有对应机器码
  • 其他符号:如+、-、*、/,编译器识别,没有对应机器码

1.3、存储器与总线

CPU要想执行指令,必须由存储器(内存)提供指令和数据。

指令和数据在内存或磁盘上没有任何区别,都是二进制信息。

内存的一个存储单元可以存储1个字节,即8个二进制位。

CPU要想进行数据的读写,必须进行下面3类信息交互:

  • 存储单元的地址(地址信息)
  • 读/写的命令(控制信息)
  • 读/写的数据(数据信息)

CPU与存储器分别通过地址总线、控制总线、数据总线进行信息交互。

  • 地址总线:地址总线宽度决定CPU寻址能力
  • 控制总线:控制总线决定CPU对其他器件的控制能力
  • 数据总线:数据总线宽度觉得CPU与其他器件数据传送一次数据传送量

2、存储器

2.1、内存地址空间

假如一个CPU地址总线宽度为10,那么可寻址1024个内存单元,这1024个内存单元就是这个CPU的内存地址空间

2.2、控制外设

在计算机系统中,所有可用程序控制其工作的设备,都必须受到CPU的控制。

但是CPU不能直接控制外设,直接控制这些设备的是插在扩展插槽的接口卡。

这些扩展插槽通过总线与CPU相连,即接口卡通过总线连接CPU,CPU控制接口卡,从而实现CPU对外设的间接控制。

CPU –> 接口卡 –> 外设

2.3、RAM和ROM

存储器分为随机存储器(RAM)和只读存储器(ROM),使用如下:

  • RAM:存放CPU使用的程序和数据
  • 装有BIOS的ROM
  • 接口卡上的RAM

不管是上述的哪种类型,CPU控制它们的时候,都是当做内存对待,即当做由若干存储单元组成的逻辑存储器,这个逻辑存储器就是内存地址空间

3、寄存器

寄存器是CPU中程序员可以读写的部件,通过改变寄存器的内容来实现对CPU的控制。

8086CPU有14个寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW

3.1、通用寄存器

其中AX、BX、CX、DX是通用寄存器,存放一般性数据。1个16位寄存器可以存储16位的数据。

为了向上一代CPU兼容,这4个寄存器可以分为AH、AL,BH、BL,CH、CL,DH、DL

3.2、物理地址

CPU要想访问内存单元,就要给出内存单元的物理地址,内存是一个一维的线性空间,每一个内存单元都有唯一的地址,这个地址就是物理地址

8086CPU是16位结构的,其数据总线是16位宽,但地址总线是20位,1MB寻址能力。

但内部是16位结构,内部一次性处理、传输地址为16位,所以在内部采用2个16位地址合成方式形成一个20位的物理地址进行内存寻址。

物理地址 = 段地址 * 16 + 偏移地址(段地址*16即左移4位)

其本质含义是:CPU在访问内存时,用一个基础地址(段地址*16)和相对于基础地址的偏移地址相加,得到物理地址。

由于偏移地址为16位,16位地址的寻址能力是64KB,所以一个段的长度最大为64K。

CPU可以用不同的段地址和偏移地址形成同一个物理地址。

3.3、段寄存器

8086CPU有4个段寄存器:CS、DS、SS、ES

CS为代码段寄存器,IP为指令指针寄存器。

任意时刻,CPU将CS:IP指向的内容当做当前指令执行。

8086CPU工作过程:

  • 从CS:IP指向的内存单元读指令,指令进指令缓冲器
  • IP指向下一条指令
  • 执行指令,跳到步骤1,重复这个过程

CPU根据什么将内存中的信息看做指令?答案是CPU将CS:IP指向的内存单元的内容看做指令。

使用jmp 段地址:偏移地址jmp 寄存器修改CS:IP或IP的地址。

在编程时,根据需要可以将长度为N的一组代码,存在一组连续的内存单元中,这段内存单元叫做代码段

同样的,可以根据需要将长度为N的一组内存单元,专门存储数据,这段内存单元叫做数据段

8086CPU自动取DS寄存器中的数据为内存单元的段地址。

例如mov [0] cs表示将寄存器cs的值复制到段地址为DS便宜地址为0的内存单元中。

字在内存中存储时,用2个地址连续的内存单元存放,字的低字节放在低地址单元,高字节放在高地址单元。

3.4、栈

栈是一种特殊的访问方式的存储空间,特点是:后进数据先出去。

8086CPU提供入栈和出栈的指令:PUSHPOP

任意时刻,寄存器SS:SP指向栈顶元素。

push指令执行:

  • SP=SP-2
  • 向SS:SP指向的字单元送入数据

pop指令执行:

  • 从SS:SP指向的字单元读数据
  • SP=SP+2

在编程时,要时刻注意栈顶越界的问题。

用栈来可以暂存以后需要恢复的寄存器中的内容。

push和pop实质上是一种内存传送指令。

同样的,编程时我们可以将长度为N的一组连续内存单元当做栈空间使用,叫做栈段

一个栈段的容量最大为64KB。

3.5、标志寄存器

标志寄存器作用:

  • 存储相关指令某些执行结果
  • 为CPU执行相关指令提供行为依据
  • 控制CPU相关工作方式

8086CPU中有个叫flag寄存器,共16位,每一位代表专门的含义:

  • 第6位ZF零标志位:相关指令执行后,结果为0则zf=1,不为0则zf=0
  • 第2位PF奇偶标志位:相关指令执行后,结果所有bit位中1的个数为偶数,pf=1,否则pf=0
  • 第7位SF符号标志位:相关指令执行后,结果是否为负,结果为负sf=1,否则sf=0
  • 第0位CF进位标志位:对于无符号数运算,CF记录运算结果最高有效位向更高位的进位值/借位值
  • 第11位OF溢出标志位:对于有符号数运算,是否发生溢出,溢出则of=1,否则of=0
  • 第10位DF方向标志位:在串处理指令中,控制操作后si、di增减,df=0每次操作后si、di递增,否则递减
阅读全文 »