diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b25c15b..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*~ diff --git a/Makefile b/Makefile deleted file mode 100644 index b71f493..0000000 --- a/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -deploy: - git checkout source - jekyll build - git add -A - git commit -m "update source" - cp -r _site/ /tmp/ - git checkout master - rm -r ./* - cp -r /tmp/_site/* ./ - git add -A - git commit -m "deploy blog" - git push origin master - git checkout source - echo "deploy succeed" - git push origin source - echo "push source" diff --git a/README.md b/README.md deleted file mode 100644 index 89912b5..0000000 --- a/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Using GitHub Jeklly Markdown to Write Blog - -Visit [HomePage](http://minixalpha.github.io) - -[Introduction](https://github.com/minixalpha/minixalpha.github.io/blob/source/_posts/2014-02-15-github-jekyll-markdown.md)(chinese version): -An introduction about how to create a free blog using GitHub and Jeklly. diff --git a/_config.yml b/_config.yml deleted file mode 100644 index e43b67a..0000000 --- a/_config.yml +++ /dev/null @@ -1,9 +0,0 @@ -baseurl: "" -markdown: redcarpet -redcarpet: - extensions: [tables] -safe: false -pygments: true -excerpt_separator: "\n\n\n" -paginate: 5 -sitemapurl: "http://minixalpha.github.io" diff --git a/_includes/archives.ext b/_includes/archives.ext deleted file mode 100644 index d710d40..0000000 --- a/_includes/archives.ext +++ /dev/null @@ -1,20 +0,0 @@ -

归档

- diff --git a/_includes/categories.ext b/_includes/categories.ext deleted file mode 100644 index 6ffbb4c..0000000 --- a/_includes/categories.ext +++ /dev/null @@ -1,14 +0,0 @@ -

分类

- diff --git a/_includes/comments.ext b/_includes/comments.ext deleted file mode 100644 index a31ca3e..0000000 --- a/_includes/comments.ext +++ /dev/null @@ -1,15 +0,0 @@ -
- - - comments powered by Disqus - diff --git a/_includes/google_analytics.ext b/_includes/google_analytics.ext deleted file mode 100644 index 3dd711f..0000000 --- a/_includes/google_analytics.ext +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/_layouts/category_index.html b/_layouts/category_index.html deleted file mode 100644 index 8dc24c0..0000000 --- a/_layouts/category_index.html +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: default -title: 分类 ---- - - diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index 9d7ea51..0000000 --- a/_layouts/default.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - {% include google_analytics.ext %} - - - {{ page.title }} - - - -
-
-
-

- 潇湘夜雨 - - - - - -

-
-
- - - - - -
- -{{ content }} - -
- -
- {% if page.comments %} - {% include comments.ext %} - {% endif %} -
- - - - -
- - diff --git a/_layouts/monthly_archive.html b/_layouts/monthly_archive.html deleted file mode 100644 index 9a42983..0000000 --- a/_layouts/monthly_archive.html +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: default ---- -
- -
- diff --git a/_plugins/gen_archives.rb b/_plugins/gen_archives.rb deleted file mode 100644 index 903024c..0000000 --- a/_plugins/gen_archives.rb +++ /dev/null @@ -1,133 +0,0 @@ -# Jekyll Module to create monthly archive pages -# -# Shigeya Suzuki, November 2013 -# Copyright notice (MIT License) attached at the end of this file -# - -# -# This code is based on the following works: -# https://gist.github.com/ilkka/707909 -# https://gist.github.com/ilkka/707020 -# https://gist.github.com/nlindley/6409459 -# - -# -# Archive will be written as #{archive_path}/#{year}/#{month}/index.html -# archive_path can be configured in 'path' key in 'monthly_archive' of -# site configuration file. 'path' is default null. -# - -module Jekyll - - # Generator class invoked from Jekyll - class MonthlyArchiveGenerator < Generator - def generate(site) - posts_group_by_year_and_month(site).each do |ym, list| - site.pages << MonthlyArchivePage.new(site, archive_base(site), - ym[0], ym[1], list) - end - end - - def posts_group_by_year_and_month(site) - site.posts.each.group_by { |post| [post.date.year, post.date.month] } - end - - def archive_base(site) - site.config['monthly_archive'] && site.config['monthly_archive']['path'] || '' - end - end - - # Actual page instances - class MonthlyArchivePage < Page - - ATTRIBUTES_FOR_LIQUID = %w[ - year, - month, - date, - content - ] - - def initialize(site, dir, year, month, posts) - @site = site - @dir = dir - @year = year - @month = month - @archive_dir_name = '%04d/%02d' % [year, month] - @date = Date.new(@year, @month) - @layout = site.config['monthly_archive'] && site.config['monthly_archive']['layout'] || 'monthly_archive' - self.ext = '.html' - self.basename = 'index' - self.content = <<-EOS -{% for post in page.posts %} -
  • -
    - {{post.title}}
    -
    - -
    -
      -
    1. {{ post.date | date: '%Y-%m-%d' }}
    2. - -
    -
    - -
    - {{ post.excerpt }} - Read More ...
    -
    -
  • -{% endfor %} - EOS - self.data = { - 'layout' => @layout, - 'type' => 'archive', - 'title' => "Monthly archive for #{@year}/#{@month}", - 'posts' => posts - } - end - - def render(layouts, site_payload) - payload = { - 'page' => self.to_liquid, - 'paginator' => pager.to_liquid - }.deep_merge(site_payload) - do_layout(payload, layouts) - end - - def to_liquid(attr = nil) - self.data.deep_merge({ - 'content' => self.content, - 'date' => @date, - 'month' => @month, - 'year' => @year - }) - end - - def destination(dest) - File.join('/', dest, @dir, @archive_dir_name, 'index.html') - end - - end -end - -# The MIT License (MIT) -# -# Copyright (c) 2013 Shigeya Suzuki -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. diff --git a/_plugins/gen_categories.rb b/_plugins/gen_categories.rb deleted file mode 100644 index b30c5e2..0000000 --- a/_plugins/gen_categories.rb +++ /dev/null @@ -1,32 +0,0 @@ -module Jekyll - - class CategoryPage < Page - def initialize(site, base, dir, category) - @site = site - @base = base - @dir = dir - @name = 'index.html' - - self.process(@name) - self.read_yaml(File.join(base, '_layouts'), 'category_index.html') - self.data['category'] = category - - category_title_prefix = site.config['category_title_prefix'] || 'Category: ' - self.data['title'] = "#{category_title_prefix}#{category}" - end - end - - class CategoryPageGenerator < Generator - safe true - - def generate(site) - if site.layouts.key? 'category_index' - dir = site.config['category_dir'] || 'categories' - site.categories.keys.each do |category| - site.pages << CategoryPage.new(site, site.source, File.join(dir, category), category) - end - end - end - end - -end diff --git a/_posts/2010-09-17-write-your-own-os.markdown b/_posts/2010-09-17-write-your-own-os.markdown deleted file mode 100644 index 51f7dfe..0000000 --- a/_posts/2010-09-17-write-your-own-os.markdown +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: default -title: 关于《自己动手写操作系统》的总结 -category: 操作系统 -comments: true ---- - -# 关于《自己动手写操作系统》的总结 - -昨天晚上终于完成了《自己动手写操作系统》的最后一部分功能,看着三个控制台上的进程各自运行着,终于舒了一口气。查看了最早的程序,是8月18号的,到现在整整30天。原本以为用10天可以搞定,没想到会用这么久。这本书读起来非常累,尤其是要照着书上写的东西把所有程序自己敲一遍,然后调试,运行,周而复始。虽然只是实现了一个最最最简陋的操作系统,没有文件系统,没有内存管理,没有命令,没有好多,不能像正常系统一样工作,但对于我这样的初学者,即使是完全照抄,还是费了很大的劲,当然,也收获了很多。所以,要总结一下,为这段日子画一个句号。 - -首先,评论一下这本书。《自己动手写操作系统》的作者于渊,没有查到他更多的信息,只是根据《程序员》杂志主编孟岩在序中的介绍,于渊是一位非常年轻的程序员,有着广泛的技术视野和上佳的文笔。年轻是一种资本,从书中也可以看到作者的活力与激情。当年Linus写出Linux的内核时也不过是大学二年级的学生。这就是年轻的好处,有着不断的激情与梦想,有着无限的精力,除旧布新,开天辟地。年轻就是要不断探索,所以整个学习过程,我深受感染。但是年轻的劣势就在于沉淀的太少,所以书中很多问题,作者并没有交代清楚,整个思路也没有那么清晰,精要。经典的书籍,一定有一种道在里面。当然,这种评论首先出于我自身的理解和悟性,并不是书本来就是这样的。于渊自己也说,这本书是他学习过程的记录和总结,他也是以一个学习者的态度来写书的。所以,这一切都不是问题,万事都有开始,Linus当初为访问学校文件系统而编写磁盘驱动时,恐怕也没有想到他会开辟Linux世界的大好河山。 - -在学完整个学期的操作系统课程后,如果你想自己写一个小的操作系统,一定会发现无从下手,因为传统的操作系统课程过于关注理论,不会告诉你要用什么工具,什么语言,如何写代码这些。当然,这并不是说理论不重要,在你对事物有了一个感性认识后,会发现理论有非常重要的作用,因为理论基础不行,会在很大程度上制约你的理解和进步。比如我现在很头疼的一点就是自己的数学基础太差了。既然要动手写,就有一个开发环境,这本书给我很大的一个收获就是在各种工具的使用方面。所以就从这儿说起。 - -三种操作系统:学习这本书的过程,我使用了三个操作系统,主机用的是windows,然后虚拟了DOS和linux。在开始阶段用Windows写程序,然后共享到DOS下面运行(因为那些程序要在实模式下运行)。中间阶段以及后期把程序所在文件夹共享到三个系统里。在linux下写程序,编译,然后把程序写到虚拟软盘映像,然后在DOS下加载虚拟软盘映像观察效果,如果有问题,在linux或windows下调试。其实整个过程都可以在linux下进行,并且在这本书的第二版里,作者已经把工作环境完全 放在了linux下,并对此作出了说明。linux命令行的工作方式,会很大程序上提高工作效率,这一点我已经深有体会,不仅如此,那些神奇的命令单兵作战或者协同作战会起到你在windows下想做而做不了的事。也许在windows下可以通过各种软件实现那种功能,但是在linux下,那些命令从发明到今天,20多年了,大家还在使用,不论时代怎么改变,人们都有共有的需求,所以那些命令永远不过时,并且越来越强大。我说上面这些,并不是想让你用linux,我觉得不论什么操作系统,它的存在必然有他的理由以及生命力所在,只有适合不适合,没有高下之分,这正如各种编程语言一样。人是不能被说服的,只能自己觉悟。如果不主动探索,永远不知道原来外面有一个大大的世界。 - -三种虚拟机:因为需要不同的操作系统,所以有人发明了虚拟机这种东西,可以虚拟出不同的操作系统,以便人们不用再买一台机器,不用多系统,也能在各种操作系统之间自由切换。这就是操作系统课上老师说的一台物理设备抽象出了多个逻辑设备吧。学习这本书的过程,我使用了三种虚拟机:VMWare,Virtual PC,Bochs.VMware功能强大,也挺消耗资源,我用来虚拟linux,然后直接在上面写程序。Virtual PC小巧,占用资源少,我用来虚拟DOS,Bochs是一款开源软件,在windows和linux下都有对应的版本,因为它是用软件模拟了硬件,所以很慢,但是它的一个很大的优势也在于此,Bochs可以用来方便的调试操作系统,你可以用它看看一台计算机从加电开始,都执行了哪些指令,各种寄存器的值怎么改变的,例如你怀疑自己的内核有问题,那么可以在内核的入口处设置一个断点,虚拟的计算机启动后,到内核的入口用就停住,你可以看看各个值是否正确。并且三种虚拟机应该都有专门的机制和主机共享,VMWare Virtual PC 的共享我用过,bochs不经常用,就不知道了。 - -三种编译工具:nasm,gcc和make。nasm是开源的汇编语言编译器,windows和linux下都有相应的版本,gcc就不用说了,linux下经典的编译工具,对这两种编译工具,整个过程也只使用了它们最基本的功能。make是我第一次接触,下面说一下make的作用。因为要编写一个操作系统,即使它十分简陋,但是还是要有许多文件需要编译,这些文件分布于不同的文件夹,需要的头文件也在不同的文件夹,难道每次都要一个一个地重新编译吗?出于这样的需求,出现了make,使用make,只要第一次用一种格式化的语言写一个make file,定义好make 命令的作用,然后每次更新完文件,只要简单修改一个Makefile,然后输入 make **,所有文件就会自动全部编译完成,并且如果文件没有更新,就不会被重新编译。 - -三种编辑器:edit,notepad,vim。因为受之前学的《汇编语言》的影响,开始的时候,使用的是DOS下的edit编辑程序,后来感觉实在不好用,就干脆直接用记事本了。学习过程到了中后期,改用了vim。用过linux或者unix的人一定不会不知道vi。vim是vi的一个升级版。vi是许多unix,linux系统默认的编辑器。vim和emacs,分别被称为编辑器之神和神的编辑器,它们的地位可想而知。这次学习操作系统的过程,vim的使用给了我很大的感触,这真的是无比强大的编辑器!每次当我想到,如果有这么一个功能多好,然后google vim+想要的功能,居然每次都可以找到。原因就在于你想要的功能,前人都想到了,所以,利器在手,就看你能不能玩转了。而且因为是开源,vim有着很好的扩展性,需要一个功能时,加入对应的插件就可以了,根据需要定制,非常方便。另一个感触是,学习一个工具,一定要用它,这样才能不断进步。之前我也学习过vim,可是只是为学而学,不是为用而学,所以学到的东西很快就忘了。包括整个Linux系统也是这样,一定要去用它,不要为了学而学。并且vim的学习曲线陡峭,开时时候非常不适应,但是过了适应期,你会发现vim是无往不利,真正的编辑器之神! - -N种小工具:学习的整个过程,还用到了N种工具,这里一起说一下。windows下用到了Turbo Debugger,一款古老的调试器,用来调试汇编程序。winimage,用来建立虚拟软盘,同时可以向软盘里写入文件。Unltra Edit,这本来是一个著名的编辑器,被我用来当16进制查看器查看虚拟软盘的16进制值了。。。linux下我最常用的工具是grep。这是个非常有用的小命令,当你想要知道一个变量都出现在了哪一个文件的哪一行时,你难道像在windows里一样一个文件一个文件的点开看吗?有了grep,只要一个命令就可以把他们全部找到了。如果你想对所有文件中的一个变量作出改变时,就用到了另一个命令,sed。这个命令可以批量的替换,删除你指定的一个或者一批文件中的某个变量。非常方便。有一次,我想在每一个#include "global.h",前面加一个#include "tty.h",并且换行时,就用到了这个命令。还有一个最常用的工具是ctags,这个工具配合vim使用,给我阅读代码带来了很大的方便。ctags的使用是这样的,先用ctags -R *,生成一个tags文件,里面记录了你所有代码中用到的变量,函数等等。这样当你想从一个函数的调用处跳到它的定义处时,只要Ctrl+],主可以直接跳过去了,然后Ctrl+O又跳回来,或者你面对那么多文件,忘记了main函数在哪里时,只要vim -t main,就直接进入了main函数,非常好用。 - -工具就像兵器一样,好的工具可以让工作变得非常有效率,关于工具,说的不少了,就说到这里。 - -因为学习的是操作系统,下一个话题就说一下在操作系统方面的收获。 - -在操作系统上,这本书和课上学的操作系统课不一样的地方在于,它首先给了我一个感性的认识,让你觉得操作系统是可以看的到,摸的着的,而不是课堂上抽象出的一些概念。比如进程一章,就真正在操作系统中编写了进程并让它们运行起来。我觉得感性的认识加上理论上的思考才是最有效的学习方式。实践,然后知道不足,去学习理论,然后再实践,然后觉得知识又不够用,然后再学习,这样一个螺旋式上升的过程我觉得应该是有效的。这是在方法上给我的启示。在具体的知识上,主要实现了从CPU加电初始化BIOS,加载引导扇区开始,到加载loader,loader加载kernel,然后kernel设置中断,键盘驱动,初始化进程,然后由进程调度程序调动整个进程运行这一系列的流程。其中还学习了保护模式,GDT,LDT,IDT,8259A等等概念及其运用。但是没有涉及处理机管理,内存管理,文件系统这些东西,在这本书的第二版《Orange's 一个操作系统的实现》,已经有了这部分功能。 - -  说了也不少了,没有想说的了,就作一个下一步的计划吧,下面要重新学习一下C语言和汇编,感觉基础还是不行,然后散着看一点操作系统方面的东西,跟着课学一下理论,好好看看 Operating system concept,理论了解了,可能会看《操作系统:设计与实现》以及Linux的源代码。未来的变数还很大,只能有一个初步的打算了。 diff --git a/_posts/2013-10-24-markdonnote.md b/_posts/2013-10-24-markdonnote.md deleted file mode 100644 index 4ec3a49..0000000 --- a/_posts/2013-10-24-markdonnote.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -layout: default -title: 使用 Markdown -category: 工具 -comments: true ---- - -# 为什么使用 Markdown - -* 看上去不错 -* 既然看上去不错,为什么不试试呢 - ---- - -# 如何使用 Markdonw - -## 1. 标题级别 - -### 效果 - -# 一级标题 -## 二级标题 -### 三级标题 - -### 代码 -``` -# 一级标题 -## 二级标题 -### 三级标题 - -``` - ---- - -## 2. 列表 - -### 效果 - -* 列表1 -* 列表2 -* 列表3 - -### 代码 (注意星号与文字之间的空格) - -``` -* 列表1 -* 列表2 -* 列表3 -``` - - -### 效果 - -1. 有序列表1 -2. 有序列表2 -3. 有序列表3 - - -### 代码 (注意点号与文字之间的空格) -``` -1. 有序列表1 -2. 有序列表2 -3. 有序列表3 -``` - ---- - -## 3. 引用 - -### 行内代码引用 - -#### 效果 - -来试试 `Markdown` 吧 - -#### 代码 - -``` -来试试 `Markdown` 吧 -``` - - -### 代码块引用 - -#### 效果 - -```python -def hello(): - print 'hello, world' -``` - -#### 代码 - - ```python - def hello(): - print 'hello, world' - ``` - - -### 文字引用 - -#### 效果 - -> 春 眠 不 觉 晓, -> 处 处 闻 啼 鸟。 -> 夜 来 风 雨 声, -> 花 落 知 多 少。 - -#### 代码(需要换行可在句末加两个空格) - -``` -> 春 眠 不 觉 晓, -> 处 处 闻 啼 鸟。 -> 夜 来 风 雨 声, -> 花 落 知 多 少。 -``` - - -#### 效果 - -> 春 眠 不 觉 晓, -处 处 闻 啼 鸟。 -夜 来 风 雨 声, -花 落 知 多 少。 - -#### 代码(需要换行可在句末加两个空格) - -``` -> 春 眠 不 觉 晓, -处 处 闻 啼 鸟。 -夜 来 风 雨 声, -花 落 知 多 少。 -``` - - -#### 效果 - 春 眠 不 觉 晓, - 处 处 闻 啼 鸟。 - 夜 来 风 雨 声, - 花 落 知 多 少。 - -#### 代码 (行首四个空格, 末尾无需空格) -``` - 春 眠 不 觉 晓, - 处 处 闻 啼 鸟。 - 夜 来 风 雨 声, - 花 落 知 多 少。 -``` - ---- - -## 4. 文字 - -### 斜体 - -#### 效果 - -这是一个*斜体* - -#### 代码 - - 这是一个*斜体* - -### 粗体 - -#### 效果 - -这是一个**粗体** - -#### 代码 - - 这是一个**粗体** - ---- - -## 5. 链接 - -### 网页链接 - -#### 效果 - -参见Wiki词条[Markdown](http://zh.wikipedia.org/wiki/Markdown) - -#### 代码 - - 参见Wiki词条[Markdown](http://zh.wikipedia.org/wiki/Markdown) - -### 图片 - -#### 效果 - -![Wikipedia_logo](http://upload.wikimedia.org/wikipedia/commons/6/63/Wikipedia-logo.png) - -#### 代码 - - ![Wikipedia_logo](http://upload.wikimedia.org/wikipedia/commons/6/63/Wikipedia-logo.png) - ---- - -## 6. 公式 - -### 效果 - -$$ \sum^{j-1}\_{k=0}{\widehat{\gamma}\_{kj} z_k} $$ - -### 代码 - - $$ \sum^{j-1}\_{k=0}{\widehat{\gamma}\_{kj} z_k} $$ - - -公式可以通过MathJax支持, 右击公式有惊喜。 -另外,注意如果 markdown 的render 是 redcarpet, `LaTex` 中下划线与Markdown中冲突,所以需要做转义处理 - -如果不想做转义处理,可以尝试更改 markdown 的 render, 我现在用的是 redcarpet,可以改成 kramdown. 如果用的是 jekyll 基于 markdown 搭建博客,可以在 _config.yml 中设置 render,参考 [jekyll configuration](http://jekyllrb.com/docs/configuration/). 感谢评论中 Wead Hsu 提出这一点。 - - -另外,有的 render 在解释文章时可能会出问题,换一个 render 可能就解决了,可以自己实验一下。 - - -特别感谢 [Cmd Markdown](http://www.zybuluo.com/mdeditor?url=http://www.zybuluo.com/static/editor/md-help.markdown)教我使用Markdown diff --git a/_posts/2013-10-25-charpointerarray.md b/_posts/2013-10-25-charpointerarray.md deleted file mode 100644 index bf9bc2d..0000000 --- a/_posts/2013-10-25-charpointerarray.md +++ /dev/null @@ -1,365 +0,0 @@ ---- -layout: default -title: 字符指针与字符数组真正的区别 -categories: [技术, C语言, 计算机系统] -comments: true ---- - -# 字符指针与字符数组真正的区别 - ---- - -## 问题缘起 - -先看一个示例 - -### 示例1 - -```c -#include - -int main() { - char *p = "hello"; - char q[] = "hello"; - - printf ("p: %s\n", p); - printf ("q: %s\n", q); - - return 0; -} -``` - -上面的例子会给出这样的输出 - -``` -p: hello -q: hello -``` - -这样看,` char *p ` 和 ` char q[] ` 好像没什么区别, 那么我们再看一个例子 - -### 示例2 - -```c -#include - -int main() { - char *p = "hello"; - char q[] = "hello"; - - p[0] = 's'; - q[0] = 's'; - - return 0; -} -``` - -如果你在Linux下,运行时可能得到这样的结果 - -``` -Segmentation fault (core dumped) -``` - -这时候你看到了区别,出现了段错误, 你一定想明白,到底发生了什么, -经过几个小实验,你可能会发现使用 `char *p` 定义时,p指向的数据是无法改变的。 -然后你Google, 别人可能会告诉你 - -* char 指针指向的数据不能修改 -* char 指针指向的数据没有分配 -* ... - -你听了还是一头雾水,不能修改是什么意思,没有分配?没有分配为什么能输出? - -作为一个好奇心很重的人,你是绝对不能容忍这些问题的困扰的,且听我慢慢道来 - - ---- - -## 深入理解 - -首先,你的程序代码和程序数据都在内存里放着,也就是说 p 指向的 `hello` 和 q 指向的 `hello`, -都是内存中的数据。从两个示例的比较中,你发现,同样是内存中的数据,都可以读,但是有的可以写, -有的不能写。从这可以看出,问题的关键在于,内存是如何组织的。 - -写好的程序代码放在硬盘上。程序代码的运行需要CPU一条指令一条指令地执行。 - ->在硬盘上读东西是慢的,CPU是快的,所以有了内存。 - -因此程序代码如果要运行,需要先载入内存。这时候,问题又出现了,系统中同时有许多程序要运行, -你想要这块内存,我也想要这块内存,那这块内存给谁呢? 何况,写程序的时候,我是不知道哪块内存 -被占用,哪块没有被占用的。总不能每次我想放数据,都检查一下吧。那程序员的负担也太大了。 - ->一件事如果大家都需要,肯定会出现专门做这件事的人。 - -于是,操作系统接管了内存。程序A说,我要12号内存单元。程序B说,我要12号内存单元。 -操作系统表示很为难。不能都给,要不就冲突了,也不能不给,内存还有好大地方呢。 - ->操作系统是聪明的,聪明人是会抽象的。 - -所谓抽象,就是看不到具体的东西了,只能看到上层的东西。当程序A和程序B都请求12号内存单元时, -操作系统把3号内存单元给了A,5号内存单元给了B。但是为了让程序中对内存的访问保持一致性, -并不让程序知道给他们的不是12号内存单元,否则程序中凡是和12号内存单元相关的,都要作修改, -又变成了程序自己维护内存。操作系统为每个程序维护一个映射表。在映射表中, -对于程序A来说,12号内存单元对应3号内存单元,对于程序B来说12号内存单元对应5号内存单元。 -这时候程序看到的12号内存单元和操作系统实际给出的3,5号内存单元,就变成了两种不同的事物。 -12号内存单元只是程序看到的,3,5号是真实的内存单元。我们把前者称为虚拟内存单元,后者指为物理 -内存单元。 - -有了虚拟内存的概念后,程序就无法无天了,全部的内存我都可以用,想访问哪块访问哪块,至于 -实际上真正访问的是内存哪个位置,我可不关心,那是操作系统的事,我只要把一个虚拟内存号告诉 -操作系统就可以了。所以,从程序看来,他拥有整个内存空间。 - -严格来说,`程序`这个词是不准确的, `程序`一般就是指的代码本身。但是代码一旦运行起来, -和这段代码相关的东西就太多了,比如指令,数据,映射表,用到的内存。另一方面, -系统中有多个程序在执行,有时候程序A执行,有时候程序B执行,操作系统从A切换到B时, -肯定要记下来A执行到哪里了,这也和程序相关。所以这时候,我们又抽象出一个概念,叫`进程`。 -这时候,`程序`就表示硬盘上那块代码,`进程`表示正在运行的程序,`进程`不仅包含代码, -还包含一些运行时相关的东西。 - -现在,当你启动一个程序时,操作系统会先创建一个进程,为这个进程建立一个私有的虚拟的内存空间, -把程序代码加载进来。进程代表一个运行中的程序,程序在运行时要使用内存,并且使用内存的方式多种多样, -程序有有些数据放在内存中是不变的,有些是一开始就分配好的,还有一些会根据需要分配。所以, -我们需要对进程的虚拟内存空间进行良好的组织,以便操作系统和程序配合,高效地完成任务。 - -下图是一个Linux进程的虚拟内存空间 - -![vir_mem](/assets/blog-images/vir_mem.png) - -所有的Linux进程的虚拟内存空间都是以这种方式组织的,只不过不同进程因为映射表不同,所以 -同一虚拟地址对应不同的物理地址。如果进程需要共享一块内存区,只需要在映射表中把同一虚拟内存 -地址映射到相同物理地址就可以了,比如上图中的Kernel virutal memory区域,这个区域是操作系统 -内核的代码,所有进程都需要共享,所以操作系统就可以把所有进程的这一区域映射到相同物理地址处。 - -上图的下半部分是Process virtual memory,代码进程使用的虚拟内存空间,可以看出他们被分成了 -几个块,这些块代表了程序使用内存的不同方式。我们先来看一段代码,并结合上图说明一下程序使用 -内存的不同方式。 - -## 示例3 -```c -#include -#include - -int main() { - char *p = "hello"; - char q[] = "world"; - char *r = (char *)malloc(sizeof(char)*6); - - p[0] = 's'; - q[0] = 's'; - r[0] = 's'; - - printf ("p is:%s",p); - printf ("q is:%s",q); - printf ("r is:%s",r); - - return 0; -} -``` - -我们先用gcc将这段代码编程成汇编语言 - - gcc -S tchar.c -o tchar.s - -## 示例3汇编版本(含注释, 只含关键代码) - -```gas -.file "tcharp.c" - .section .rodata -.LC0: - .string "hello" -.LC1: - .string "p is:%s" -.LC2: - .string "q is:%s" -.LC3: - .string "r is:%s" - .text - .globl main - .type main, @function -main: -# char *p = "hello" - movl $.LC0, 28(%esp) - -# char q[] = "world" - movl $1819438967, 38(%esp) - movw $100, 42(%esp) - -# char *r = (char *)malloc(sizeof(char)*6) - movl $6, (%esp) - call malloc - movl %eax, 32(%esp) - -# p[0] = 's' - movl 28(%esp), %eax - movb $115, (%eax) -# q[0] = 's' - movb $115, 38(%esp) -# r[0] = 's' - movl 32(%esp), %eax - movb $115, (%eax) - - movl $.LC1, %eax - movl 28(%esp), %edx # save p - movl %edx, 4(%esp) - movl %eax, (%esp) - call printf - - movl $.LC2, %eax - leal 38(%esp), %edx # save q - movl %edx, 4(%esp) - movl %eax, (%esp) - call printf - - movl $.LC3, %eax - movl 32(%esp), %edx # save r - movl %edx, 4(%esp) - movl %eax, (%esp) - call printf -``` - -从上述汇编代码可以看出p,q,r三种使用内存的方式。从初始化上看, -p指向的"hello",初始化时,直接指向了一个固定的位置,这意味着代码执行的时候, -这个位置已经有数据了。q指向的"world",初始化是由代码完成的,你把"world"经ASCII码转化成数字形式, -对比一下就会发现,那两个数字,1819438967,100,对应的就是"world"。而r的初始化,是调用malloc得到的。 - -从这段汇编代码。我们从直觉上会感觉到这三种使用内存方式的不同,接下来,我们再来看一下Linux运行时存储器映像。 - -![linux_runtime_mem_img](/assets/blog-images/linux_runtime_mem_img.png) - -.text 段放着已经编译的程序机器代码。 -.rodata 段放着只读数据,`printf`函数参数中的字符串,p指向的"hello", -都在这存着。正因为这个段是只读的,所以不能修改,代码 - -```c -p[0] = 's' -``` -执行时就会出现段错误。 -.data 段放着已经初始化的全局变量,.bss 段变着没有初始化的全局变量。 -再往上是 Run-time heap, 我们用malloc分配的内存空间都在这一段。 -接着是User Stack,程序中的局部变量都在这一段,我们q指向的"world"就存储在这里。 -从图中也可以看到,`%esp`指向栈顶,再回头看一下汇编代码,你可能就明白之前相对于(%esp)地址所做的操作意味着什么。 - - -这里特别要区分 **地址与数据** 。 -p,q,r是局部变量,它们的值都是地址,这个地址作为局部变量的值,在User Stack里存储。 -p表示的地址指向数据"hello",这是不可变量,在.rodata段中存储。q表示的地址指向的数据"world",作为局部变量的数据,在User Stack段存储。 -r表示的地址指向的数据,在Run-time heap中存储。 - -为了验证我们的想法,我们做一个实验,把p,q,r三者地址打印出来, 再把三者指向的数据的地址打印出来。 -然后查看内存分配。 - -## 示例4 - -```c -#include -#include - -int main() { - char *p = "hello"; - char q[] = "world"; - char *r = (char *)malloc(sizeof(char)*6); - int n; - - printf ("addr of p:%p\n", &p); - printf ("addr of q:%p\n", &q); - printf ("addr of r:%p\n", &r); - - printf ("addr of p's data:%p\n", p); - printf ("addr of q's data:%p\n", q); - printf ("addr of r's data:%p\n", r); - - scanf ("%d\n", &n); - - return 0; -} -``` - -为了便于观察,我们引入scanf,同时放在后台运行,这样只要我们不输入数据, -进程就不会终止,我们就可以观察它。运行它, - -``` -$ ./tcharp & -[2] 3461 -addr of p:0xbfaa91d8 -addr of q:0xbfaa91e6 -addr of r:0xbfaa91dc -addr of p's data:0x8048670 -addr of q's data:0xbfaa91e6 -addr of r's data:0x87ad008 -``` - -从这里,我们可以看出:p,q,r本身的值,以及q指向的数据,存储的位置离的很近,我们猜测, -所以0xbfXXXXXX这一块应该是User Stack区域,0x8048XXX这一块是.rodata区域, -0x87XXXXX这一块是Run-time heap区域。 - - -接下来,我们使用 `readelf` 命令,得到各个区域的实际位置,进一步明确我们的猜想。 - - $ readelf -a tcharp > tcharp_elf.txt - -从tcharp_elf.txt中截取关键数据, 得到 - -``` - [11] .init PROGBITS 08048318 000318 00002e 00 AX 0 0 4 - [12] .plt PROGBITS 08048350 000350 000060 04 AX 0 0 16 - [13] .text PROGBITS 080483b0 0003b0 00023c 00 AX 0 0 16 - [14] .fini PROGBITS 080485ec 0005ec 00001a 00 AX 0 0 4 - [15] .rodata PROGBITS 08048608 000608 000077 00 A 0 0 4 - [16] .eh_frame_hdr PROGBITS 08048680 000680 000034 00 A 0 0 4 - [17] .eh_frame PROGBITS 080486b4 0006b4 0000c4 00 A 0 0 4 - [18] .ctors PROGBITS 08049f14 000f14 000008 00 WA 0 0 4 - [19] .dtors PROGBITS 08049f1c 000f1c 000008 00 WA 0 0 4 - [20] .jcr PROGBITS 08049f24 000f24 000004 00 WA 0 0 4 - [21] .dynamic DYNAMIC 08049f28 000f28 0000c8 08 WA 6 0 4 - [22] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4 - [23] .got.plt PROGBITS 08049ff4 000ff4 000020 04 WA 0 0 4 - [24] .data PROGBITS 0804a014 001014 000008 00 WA 0 0 4 - [25] .bss NOBITS 0804a01c 00101c 000008 00 WA 0 0 4 - [26] .comment PROGBITS 00000000 00101c 00002a 01 MS 0 0 1 - [27] .shstrtab STRTAB 00000000 001046 0000fc 00 0 0 1 - [28] .symtab SYMTAB 00000000 0015f4 000430 10 29 45 4 - [29] .strtab STRTAB 00000000 001a24 00022c 00 0 0 1 - -``` - -从这里,我们可以验证对 .rodata 段的猜测,p指向的 "hello", 确实是存储在这一段。 - -然后,我们查看其它段的位置 - -``` -$ cat /proc/3461/maps -08048000-08049000 r-xp 00000000 08:0a 4981409 /home/zhaoxk/test/tcharp -08049000-0804a000 r--p 00000000 08:0a 4981409 /home/zhaoxk/test/tcharp -0804a000-0804b000 rw-p 00001000 08:0a 4981409 /home/zhaoxk/test/tcharp -087ad000-087ce000 rw-p 00000000 00:00 0 [heap] -b75fc000-b75fd000 rw-p 00000000 00:00 0 -b75fd000-b77a1000 r-xp 00000000 08:07 412678 /lib/i386-linux-gnu/libc-2.15.so -b77a1000-b77a3000 r--p 001a4000 08:07 412678 /lib/i386-linux-gnu/libc-2.15.so -b77a3000-b77a4000 rw-p 001a6000 08:07 412678 /lib/i386-linux-gnu/libc-2.15.so -b77a4000-b77a7000 rw-p 00000000 00:00 0 -b77c1000-b77c5000 rw-p 00000000 00:00 0 -b77c5000-b77c6000 r-xp 00000000 00:00 0 [vdso] -b77c6000-b77e6000 r-xp 00000000 08:07 403838 /lib/i386-linux-gnu/ld-2.15.so -b77e6000-b77e7000 r--p 0001f000 08:07 403838 /lib/i386-linux-gnu/ld-2.15.so -b77e7000-b77e8000 rw-p 00020000 08:07 403838 /lib/i386-linux-gnu/ld-2.15.so -bfa8b000-bfaac000 rw-p 00000000 00:00 0 [stack] -``` - -看到stack和heap段的位置了吧,再一次印证了我们的想法。 - -好了,我们的探索到这里就结束了。 - ---- - -# 文后的话 - -从上面的过程可以看出,要想真正理解C语言,你需要了解汇编,需要了解操作系统, -而Linux提供了一系列工具,方便你探索整个系统的运行机制。如果你也想了解它, -请开始使用它。 -还是那句话。 - -> 既然看起来不错,为什么不试试呢? - ---- - -转载请注明出处: http://minixalpha.github.io/2013/10/25/charpointerarray.html diff --git a/_posts/2013-10-26-whyds.md b/_posts/2013-10-26-whyds.md deleted file mode 100644 index e6976d7..0000000 --- a/_posts/2013-10-26-whyds.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -layout: default -title: 为什么我们需要数据结构 -category: 思想 -comments: true ---- - -# 为什么我们需要数据结构 - ---- - -最近在学习各种数据结构,于是就在想,为什么我们需要数据结构呢? -为什么要设计这么多数据结构?数据结构到底解决了我们什么样的问题? - -我们提到 [数据结构](http://zh.wikipedia.org/zh-cn/数据结构) 时,一般是指计算机科学中的一个概念, -但是从本质上讲,数据结构应该是指对数据的一种组织方式。既然如此,我们没必要非在计算机科学领域中讨论 -概念本身,把它放在其它领域中,可能更能加强我们的理解。 - -就说图书管吧,假如你是一名很久很久以前的图书馆管理员,那时候根本没什么计算机。数据结构?那是什么? - -你的任务就是看着图书馆里的一堆书。于是,有一天,图书馆来了一堆书,你把他们堆成一堆,放在馆里。 -这时候,有人来借书了,他只能在那一堆书里乱翻,翻来翻去也找不到自己想要的书,因为那是一堆书, -有的书他检查了很多次,有的一次也没检查。 - -> 这时候这堆书是一个集合,不方便遍历。 - -时间长了,抱怨的人很多。 - -作为一个怕麻烦的管理员,你忍受不了别人的抱怨,于是,你把那 **一堆书** 变成了 **一排书**。 - -这下好了,来找书的人,只要从书架左边走到右边,按顺序找就好了。只要书在图书馆里,慢慢找总是可以找到。 -但是,随着图书馆的书越来越多,这样找实在是太慢了,因为每次都要从第一本书找到最后一本书。 - -> 这时候这堆书是一个列表,方便遍历,但是不方便查找。 - -时间长了,抱怨的人很多。 - -作为一个怕麻烦的管理员,你忍受不了别人的抱怨,于是,你把那 **一排书** 变成了 **很多类书**。 - -那么,按什么分类呢?按书的大小么?颜色么?退一步讲,分类的依据是什么? - -分类是为了加快读者查找书的速度,那么读者查找书的时候,是按什么查找呢?是按书名。所以,我们对书名分类。 -按书名分类也有许多种,按书名读音么?按书名笔画吗?按书名字数么?我们很容易想到,按读音分类给读者的压力最小, -也就是查找前的开销最小。否则每次找书之前还要数一下笔画,读者一定又会抱怨。 - -这时候,我们按读音把书分类,书名第一个字是A的在A书架,是B的在B书架。这下读者查找书的速度大大加快了, -因为一下子就能排除那么多类书,而代价仅仅是想一下书名第一个字的读音。不过,我们马上又发现,有的书架上书实在太多了, -那有什么关系?这个问题我们解决过啊,只要再分类就好了,书名第一个字我们用过了,现在用第二个字。 - -读者终于大致可以满意了。 - -> 这时候这些书架构成了一个查找树,方便查找。 - -另外,我们注意到,其实对于管理员来说,他的负担是增加了的,比如新来了一本书,如果图书馆是一堆书, -只要把新书扔在那一堆里就好了,如果是一排书,要把新书放在这排书的最后,而如果是分好类的书架, -管理员就要先找到这本书的位置,再把新书放在那儿,而不能随便放。好在分类后,我们找新书位置不会花多久, -假如分好类后,读者查找书方便了,但是管理员要把新书放在合适的位置,需要花一年时间, -那这个分类的方法肯定不是一个好方法。 - -这告诉我们 - -> 维护数据结构很重要。 - -这时候,我们在不知不觉间居然 - -## 设计了一个数据结构 - -这时候,我们回到开始时的问题,为什么我们需要数据结构?对应上面的故事, -为什么我们要把一堆书变成多类书?简单地说,这样可以使找书的过程变快。 -这正印证了维基百科词条中的那句话。 - -> 数据结构是计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以带来最优效率的算法。 - -回头想想,从一堆书变成多类书的过程,其实就是数据的组织方式发生了变化。我们来抽象一下整个过程。 - -* 我们有一堆数据D。 -* 数据上最常用的操作是O。 -* 我们的目标是让O很快。 -* 我们设计一个数据结构S来组织数据D。 -* 数据结构S需要额外的信息EI来组织数据D。 -* 数据结构S有性质P,性质P可以使操作O很快。 -* 数据结构除了支持操作O外,还要支持两个最基本的操作,Add:添加数据,Del:删除数据。 -* 数据结构要保持性质P,所以Add,Del需要额外操作EO来保持P。 - -那么,关键的地方就在于: - -> 根据操作O,找到性质P,设计数据结构S,使S有性质P,同时使额外信息EI,额外操作EO尽量小。 - -所以,无论是设计数据结构还是学习数据结构,都要弄清楚, - -* 数据结构的关键性质是什么 -* 为什么关键性质可以加快操作 -* 额外信息与额外操作大小如何 - -下面我们探讨一些基本数据结构的特点 - -数组:数组的关键性质在于元素位置可以通过简单计算马上得到,这个关键性质为随机访问提供了很大的 -便利。如果数据大小需要动态扩展,那么使用数组是不方便的,因为动态扩展难以维护数组的关键性质。 - -数组之所以拥有“元素位置可以通过简单计算得到”这个性质,在于数组在内存中是一片连续的区域,这样 -知道数组第一个元素位置,知道每个元素大小,通过一次加法,一次乘法,就可以知道第N个元素的位置。 -如果数组需要动态扩展,如果这片连续区域相邻的地方有可用内存,那么额外操作是分配这块区域给数组, -如果相邻位置没有可用内存,需要另找一片足够大的连续区域,作为新数组的位置,同时还要把已有数据 -复制过去。 - - -链表:链表的关键性质在于元素之间的连续关系使用数据保存起来,这个关键性质为动态扩展提供了很大 -方便,新添加元素只需要分配一个结点,然后存储一下表头结点的位置,就维持了链表的关键性质。链表 -不利于随机访问,因为元素位置无法通过简单计算得到。 - -链表的结点在内存中的位置是不连续的,结点之间的关系用数据来存储,所以不能通过简单计算得到第N -个元素位置,只能从表头开始,一直走到第N个元素,才找到位置,这样的额外操作花费太大,所以如果 -算法需要随机访问,那么使用链表是不合适的。链表唯一可以直接访问的就是表头了,所以链表适合实现 -后进先出队列。 - -如果需要根据结点内容查找一个结点的位置,那么数组和链表都不太合适,因为他们的关键性质中没有包含结点 -内容与位置的关系,所以你根据结点内容很难查找到结点位置。在二分查找中,算法需要一个已经排好序的 -数组,原因就在于,排序之后,元素的内容与元素位置之间建立了关系,另外,二分查找需要有随机访问某一 -位置元素,所以已经排序的数组就非常适合作为二分查找算法的数据结构。 - -但是,数组不利用动态扩展,链表利于动态扩展,但是又不能随机访问。我们仔细想想,二分查找的关键思想 -在于每次比较一个元素后,就可以排除一部分元素。所以,问题的关键不在于能不能随机访问,而在于比较 -一次后,能不能排除一部分元素。我们来看下一个数据结构:二叉查找树。 - -二叉查找树:二叉查找树的关键性质在于:比根结点小的元素都在左子树中,比根结点大的元素都在右子树中。 -它的关键性质体现了内容与位置的关系,并且每次与根结点比较后,我们就排除了一个子树, -所以它方便查找。另一方面,元素之间的关系是用存储的数据来表示的,所以,利于动态扩展。 - -在添加新元素时,二叉查找树需要的额外操作来保持它的关键性质,这个额外操作其实和查找元素的代价是一样的, -它比数组添加元素容易,比链表添加元素困难。 - -故事,有些讲不下去了,感觉还是理解的不够,就到这里吧。今后又了新的感受,再补充。 diff --git a/_posts/2013-11-01-pypkg.md b/_posts/2013-11-01-pypkg.md deleted file mode 100644 index b69d98b..0000000 --- a/_posts/2013-11-01-pypkg.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -layout: default -title: Python包管理不同方式的区别 -category: 技术 -comments: true ---- - -# Python包管理不同方式的区别 - -学习Python已经有一段时间,经常会遇到安装各种包的问题,一会 `setup.py`, -一会 `easy_install`,一会又是`pip`,还有一些概念比如`distutils`, -`setuptools`等等,搞不清楚谁是谁,什么时候应该用什么,今天就把这些概念 -澄清一下。 - -## distutils - -distutils是Python标准库的一部分,其初衷是为开发者提供一种方便的打包方式, -同时为使用者提供方便的安装方式。 - - -例如你创建了一个名为foo的包,包含一个foo.py文件,你想把它打包以便其它人使用。 -这时候你需要写一个setup.py文件: - -```python -from distutils.core import setup -setup(name='foo', - version='1.0', - py_modules=['foo'], - ) -``` - -然后运行命令 - - $python setup.py sdist - -然后你发现当前目录下出现一个名为dist的文件夹,里面有一个foo-1.0.tar.gz的包。 -这个包里有三个文件,foo.py, setup.py, PKG-INFO,前两个文件和我们之前提到的两个文件一样。 -PKG-INFO是关于包的一些信息。然后你就可以把foo-1.0.tar.gz给别人安装了。 - -安装者要使用这个包时,只需要解压这个foo-1.0.tar.gz文件,再运行命令 - - $python setup install - -这个包就会被自动安装到系统合适的位置。 - - -## setuptools - -[setuptools](http://peak.telecommunity.com/DevCenter/setuptools) 是对 distutils 的增强, -尤其是引入了包依赖管理。 - -setuptools可以为Python包创建 [egg](http://peak.telecommunity.com/DevCenter/PythonEggs)文件, -Python 与 egg 文件的关系,相当于java与jar包的关系。 - -setuptools 提供的 [easy_install](http://peak.telecommunity.com/DevCenter/EasyInstall) 脚本可以用来安装 egg包。 -另外, easy_install 可以自动从 [PyPI](https://pypi.python.org/pypi) 上下载相关的包,并完成安装,升级。 - -easy_install 提供了多种安装,升级Python包的方式,例如: - - easy_install SQLObject - easy_install -f http://pythonpaste.org/package_index.html SQLObject - easy_install http://example.com/path/to/MyPackage-1.2.3.tgz - easy_install /my_downloads/OtherPackage-3.2.1-py2.3.egg - easy_install --upgrade PyProtocols - -后来开发者们觉得 setuptools 开发的太慢了,fork出了 Distribute 项目,然后2013年8月, -Distribute 又合并回 setuptools 0.7。 - -## pip - -pip 是安装,管理Python包的工具。它是对 easy_install 的一种增强。 -同样可以从 PyPI 上自动下载,安装包。 - -在pip中, - -* 安装前所有需要的包都要先下载,所以不会出现安装了一部分,另一部分没安装的情况 -* 所有安装的包会被跟踪,所以你可以知道为什么他们被安装,同时可以卸载。 -* 无需使用 egg 文件。 - -使用方式简单: - - pip install pkg_name - pip uninstall pkg_name - -## 总结 - -用 `pip` 吧! - - -## 参考资料 -* [An Introduction to Distutils](http://docs.python.org/2/distutils/introduction.html) -* [可爱的 Python: pydoc 和 distutils 模块](http://www.ibm.com/developerworks/cn/linux/sdk/python/charm-19/) -* [setuptools](http://peak.telecommunity.com/DevCenter/setuptools) -* [easy_install] (http://peak.telecommunity.com/DevCenter/EasyInstall) -* [Python Egg] (http://peak.telecommunity.com/DevCenter/PythonEggs) -* [why pip] (http://www.pip-installer.org/en/latest/other-tools.html#pip-compared-to-easy-install) -* [Differences between distribute, distutils, setuptools and distutils2?](http://stackoverflow.com/questions/6344076/differences-between-distribute-distutils-setuptools-and-distutils2) -* [Python 包工具之间的关系](http://blog.yangyubo.com/2012/07/27/python-packaging/) diff --git a/_posts/2013-11-14-Application_TestFrame.md b/_posts/2013-11-14-Application_TestFrame.md deleted file mode 100644 index da0d421..0000000 --- a/_posts/2013-11-14-Application_TestFrame.md +++ /dev/null @@ -1,276 +0,0 @@ ---- -layout: default -title: web.py 源代码分析之 web.test 主要文件及测试流程 -category: 源代码阅读 -comments: true ---- - -# 目录文件说明 - -## README - -如何运行测试文件,包含全部测试及分模块测试 - -调用 - - $ python test/alltests.py - -运行全部测试 - -调用 - - $ python test/db.py - -运行db模块测试 - -## alltest.py - -运行全部测试入口,调用 `webtest` 模块完成测试 - -```python -# alltest.py - -if __name__ == "__main__": - webtest.main() -``` - -## webtest.py - -我们发现 webtest.py 中并没有 main 函数,而是从 `web.test` 中导入, - -```python -# webtest.py -from web.test import * -``` - -也就是说,如果 `web.test`中有main函数的话,`webtest.main()` -其实是调用 `web.test` 中的main函数。 - -感觉~ 好神奇 - -## web.test - -看web目录下的test.py文件,果然发现了main函数,终于找到入口啦~ - -```python -def main(suite=None): - if not suite: - main_module = __import__('__main__') - # allow command line switches - args = [a for a in sys.argv[1:] if not a.startswith('-')] - suite = module_suite(main_module, args or None) - - result = runTests(suite) - sys.exit(not result.wasSuccessful()) -``` - -把这个main函数改掉,再运行一下: - - $ python test/alltests.py - -果然是运行修改后的函数,所以这里确定是入口。 - -在进入下一步之前,我们需要学习一下Python自动单元测试框架,即`unittest`模块。关于 `unittest` ,可以参考这篇文章: -[Python自动单元测试框架](http://www.ibm.com/developerworks/cn/linux/l-pyunit/) - - -现在,进入 `web.test.main`, -当我们如果我们打印 `main_module`,会得到 - - - -这说明调用时,`import` 得到的 `main` 与命令行调用指定的 -调用的模块一致。 - -之后会调用 `module_suite` 得到需要测试的模块, - -我们看一下 `module_suite` 的代码, - -```python -def module_suite(module, classnames=None): - """Makes a suite from a module.""" - if classnames: - return unittest.TestLoader().loadTestsFromNames(classnames, module) - elif hasattr(module, 'suite'): - return module.suite() - else: - return unittest.TestLoader().loadTestsFromModule(module) -``` - -可以看出,module_suite分三部分,如果定义了 `classnames`, -会测试具体的类,否则,如果 `module` 中含有 `suite` 函数, -就返回此 `module.suite()` 的调用结果。 - -此时我们的 `module` 是之前得到的 - - - -而 `alltests.py` 中刚好就有 `suite` 函数: - -```python -def suite(): - modules = ["doctests", "db", "application", "session"] - return webtest.suite(modules) -``` - -`modules` 是全部模块的列表,随后以此为参数,返回 -调用 `webtest.suite` 的结果。 - -这时候,同样的情况又出现了,`webtest.py` 中没有 `suite` 函数, -但是 `webtest.py` 中含有 - -```python -from web.test import * -``` - -所以 `webtest.suite` 调用的还是 `web.test.suite`, - -从这里我们可以看出, - -> webtest.py 这个模块就是目录 `test` 下的模块与 `web.test` 模块 -> 之间的一个过渡层,`test` 目录下的模块调用 `webtest.XXX`,而实际 -> 的实现代码都是调用 `web.test.XXX`,所以,我们再次回到 `web.test`。 - -看看 `web.test.suite`的实现: - -```python -def suite(module_names): - """Creates a suite from multiple modules.""" - suite = TestSuite() - for mod in load_modules(module_names): - suite.addTest(module_suite(mod)) - return suite -``` - -首先调用 `TestSuite()`,即 `unittest.TestSuite()`,得到一个 -`suite`。然后,向 `suite` 中添加测试用例。 - -参数 `module_names` 就是刚刚的列表modules: - - modules = ["doctests", "db", "application", "session"] - -调用 `load_modules` 把模块名变为模块本身, -例如 "application": - - >>> __import__("application", None, None, "x") - - -然后,再调用 `module_suite`,这时候你会发现,我们递归回来了, - -```python -def module_suite(module, classnames=None): - """Makes a suite from a module.""" - if classnames: - return unittest.TestLoader().loadTestsFromNames(classnames, module) - elif hasattr(module, 'suite'): - return module.suite() - else: - return unittest.TestLoader().loadTestsFromModule(module) -``` - -此时, `module`为: - - - -我们再看看这时候,`application.py`中是否有 `suite`函数。 -经过查看,发现没有,所以此时就会调用 `if`语句第三个分支: - -```python - return unittest.TestLoader().loadTestsFromModule(module) -``` - -[TestLoader](http://docs.python.org/2/library/unittest.html#unittest.TestLoader) 中的 `loadTestsFromModule` 从模块中导入 -测试用例。 这个函数会搜索模块中 `TestCase`类的子类,再创建一个 -子类的实例,以便调用子类中的测试函数。 -例如 `test/application.py`中的 `ApplicationTest` 类: - -```python -class ApplicationTest(webtest.TestCase): - def test_reloader(self): - write('foo.py', data % dict(classname='a', output='a')) - import foo - app = foo.app - - self.assertEquals(app.request('/').data, 'a') - - # test class change - time.sleep(1) - write('foo.py', data % dict(classname='a', output='b')) - self.assertEquals(app.request('/').data, 'b') - - # test urls change - time.sleep(1) - write('foo.py', data % dict(classname='c', output='c')) - self.assertEquals(app.request('/').data, 'c') - - def testUppercaseMethods(self): - urls = ("/", "hello") - app = web.application(urls, locals()) - class hello: - def GET(self): return "hello" - def internal(self): return "secret" - - response = app.request('/', method='internal') - self.assertEquals(response.status, '405 Method Not Allowed') - - ... -``` - -返回的这些函数会经过 `web.test.suite` 中的 `suite.addTest` 加入到 -`suite`中。 这样经过循环,所有模块中的测试用例就加入 `suite`中。 - -之后,我们回到 `web.test.main` 中,调用 - -```python -result = runTests(suite) -``` - -完成最终的测试。 `runTests` 在 `web.test`模块中: - -```python -def runTests(suite): - runner = unittest.TextTestRunner() - return runner.run(suite) -``` - -`runner`功能同样由 `unittest`模块提供。 - -到这里,我们应该明白了测试的整个过程的全部细节。 - -这里,我们对 `alltests.py` 完成的功能做一个总结。 -从总体上看,`alltests.py`对所有模块完成测试,包括: - -``` - modules = ["doctests", "db", "application", "session"] -``` - -我们绕来绕去,其实不过下列过程: - -* 通过 `suite=TestSuite()` 得到 `suite` -* 把所有模块中的 test 函数 通过 `suite.addTest()` 加入 `suite` -* 通过 `runner=unittest.TextTestRunner()` 得到 `runner` -* 运行 `runner.run(suite)` 调用所有测试用例。 - -之所以感觉绕,是因为 层次关系,及`alltests.py` 需要递归调用 `module_suite`: - -* `alltests.py` 调用 `webtest.main` -* `webtest.main` 指向 `web.test.main` -* 由`web.test.main` 进入 `web.test.module_suite`,进入 `if` 第二分支 -* 再进入 `alltest.py`,调用 `suite()` -* 再进入 `web.test.suite` , 对每个模块调用 `web.test.module_suite` -* 再进入 `web.test.module_suite`,进入 `if` 第三分支,得到每个模块中的 测试用例 -* 将所有测试用例加入 `suite()` 中的 `suite` - - -## requirements.txt - -requirements.txt 文件可由 pip 生成: - - $pip freeze -l > requirements.txt - -同时,pip 可以使用 requirements.txt 文件安装依赖包 - - $pip install -r requirements.txt - -这就为打包与安装包提供了方便 - diff --git a/_posts/2013-11-14-Application_intro.md b/_posts/2013-11-14-Application_intro.md deleted file mode 100644 index bf26226..0000000 --- a/_posts/2013-11-14-Application_intro.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -layout: default -title: web.py 源代码分析之 web.test.application -category: 源代码阅读 -comments: true ---- - -## 分模块测试 - -### application.py - -对 application.py 的测试,调用命令: - - python test/application.py - - -程序入口依然会调用 `webtest.main()` - -```python -if __name__ == '__main__': - webtest.main() -``` - -再仔细观察 `webtest.main` 调用的 `web.test` 中的 `main`函数 - -```python -def main(suite=None): - if not suite: - main_module = __import__('__main__') - # allow command line switches - args = [a for a in sys.argv[1:] if not a.startswith('-')] - suite = module_suite(main_module, args or None) - - result = runTests(suite) - sys.exit(not result.wasSuccessful()) -``` - -回顾一下 `module_suite`函数, - -```python -def module_suite(module, classnames=None): - """Makes a suite from a module.""" - if classnames: - return unittest.TestLoader().loadTestsFromNames(classnames, module) - elif hasattr(module, 'suite'): - return module.suite() - else: - return unittest.TestLoader().loadTestsFromModule(module) -``` - -与 `alltests.py` 不同,`application.py`中并没有 `suite`, -所以直接进入第三分支。后面的流程就和之前讲的后半部分一致了。 - -这里,我们主要集中于 `application.py`中的具体内容。 - -* 1. test_reloader - -* 2. test_UppercaseMethods - -* 3. testRedirect - diff --git a/_posts/2013-11-14-Application_testRedirect.md b/_posts/2013-11-14-Application_testRedirect.md deleted file mode 100644 index 06d0dda..0000000 --- a/_posts/2013-11-14-Application_testRedirect.md +++ /dev/null @@ -1,726 +0,0 @@ ---- -layout: default -title: web.py 源代码分析之 web.test.application.testRedirect -category: 源代码阅读 -comments: true ---- - -# 分模块测试 - -## application.py - -对 application.py 的测试,调用命令: - - python test/application.py - - -### testRedirect - -```python -def testRedirect(self): - urls = ( - "/a", "redirect /hello/", - "/b/(.*)", r"redirect /hello/\1", - "/hello/(.*)", "hello" - ) - app = web.application(urls, locals()) - class hello: - def GET(self, name): - name = name or 'world' - return "hello " + name - - response = app.request('/a') - self.assertEquals(response.status, '301 Moved Permanently') - self.assertEquals( - response.headers['Location'], - 'http://0.0.0.0:8080/hello/') - - response = app.request('/a?x=2') - self.assertEquals(response.status, '301 Moved Permanently') - self.assertEquals( - response.headers['Location'], - 'http://0.0.0.0:8080/hello/?x=2') - - response = app.request('/b/foo?x=2') - self.assertEquals(response.status, '301 Moved Permanently') - self.assertEquals( - response.headers['Location'], - 'http://0.0.0.0:8080/hello/foo?x=2') -``` - -看到这段代码首先对 `urls` 挺好奇的,`urls` 一般是一个 `url` 对应 -一个处理它的类,可是 `redirect /hello/` 是什么意思?所以,我们 -有必要看一下 `web.application` 如何对 `urls` 进行处理。 - -我们还从这句开始: - -```python -response = app.request('/a') -``` - -看看 `urls` 是如何被处理的。 - -```python -# web.application.request - -def request(self, localpart='/', method='GET', data=None, - host="0.0.0.0:8080", headers=None, https=False, **kw): - - - path, maybe_query = urllib.splitquery(localpart) - query = maybe_query or "" - - ... - - env = dict(env, HTTP_HOST=host, REQUEST_METHOD=method, PATH_INFO=path, - QUERY_STRING=query, HTTPS=str(https)) - - ... - - response.data = "".join(self.wsgifunc()(env, start_response)) - -``` - -可以看出,请求的 `url` 被分成两部分: `path` 和 `maybe_query`, -然后传入 `env`中。 - -`urllib` 是标准库的一部分,但是在文档中没有对 `splitquery` -有说明,这可能是一个非公开的API。通过 - - >>> help(urllib.splitquery) - -可以得到 - - splitquery('/path?query') --> '/path', 'query' - -看来这个调用是把 `url` 请求分成路径与请求两个部分,这也和 -返回结果的赋值保持一致。 - -另外,最好不要使用这个函数,python 2.7 中提供了 `urlparse` -模块,可以完成同样功能(甚至更多),python 3 中这个模块 -更改为 `urllib.parse`。 - -这里不再多说,只是明白这一句是要做什么就好。我们继续看数据 -封装在 `env` 后发生的故事。 - -我们又来到这里: - -```python - response.data = "".join(self.wsgifunc()(env, start_response)) -``` - -最终对 `env` 的处理在 `wsgi` 函数中。 - -```python -# web.application.wsgifunc.wsgi - - def wsgi(env, start_resp): - # clear threadlocal to avoid inteference of previous requests - self._cleanup() - - self.load(env) - try: - # allow uppercase methods only - if web.ctx.method.upper() != web.ctx.method: - raise web.nomethod() - - result = self.handle_with_processors() - if is_generator(result): - result = peep(result) - else: - result = [result] - except web.HTTPError, e: - result = [e.data] - - - result = web.safestr(iter(result)) - - status, headers = web.ctx.status, web.ctx.headers - - start_resp(status, headers) -``` - -同样,先把 `env` 载入 `web.ctx`, 然后我们通过 `print` 定位到 -这一句改变了 `web.ctx.status` 的值。 - -``` - result = self.handle_with_processors() -``` - -可见这里对 `url` 进行了分析。下面我们深入下去。 - -```python -# web.application.handle_with_processors - -def handle_with_processors(self): - def process(processors): - try: - if processors: - p, processors = processors[0], processors[1:] - return p(lambda: process(processors)) - else: - return self.handle() - except web.HTTPError: - raise - except (KeyboardInterrupt, SystemExit): - raise - except: - print >> web.debug, traceback.format_exc() - raise self.internalerror() - - # processors must be applied in the resvere order. (??) - return process(self.processors) -``` - -这里 `processors` 为空,所以进入 `self.handle()` - -```python -# web.application.handle - -def handle(self): - fn, args = self._match(self.mapping, web.ctx.path) - return self._delegate(fn, self.fvars, args) -``` - -这个 `self.mapping` 是将我们的 `urls` 转化成两两一组后的列表。 -先看看 `_match`函数。 - -```python -# web.application._match - -def _match(self, mapping, value): - for pat, what in mapping: - if isinstance(what, application): - if value.startswith(pat): - f = lambda: self._delegate_sub_application(pat, what) - return f, None - else: - continue - elif isinstance(what, basestring): - what, result = utils.re_subm('^' + pat + '$', what, value) - else: - result = utils.re_compile('^' + pat + '$').match(value) - - if result: # it''s a match - return what, [x for x in result.groups()] - return None, None -``` - -可以看出这是一个循环,根据当前的 `value` ,即 `web.ctx.path` -去查找 `urls` 中定义的对应项。what就是这个项。 -先使用 `isinstance(what, application)` 看是不是使用了子程序。 -再看看what是不是 `basestring` 的实例。直到运行至 -result不为空,这说明找到了一个匹配。然后会返回匹配中 -的所有分组。 - -当前的运行会选择第二个分支。 -即: - -```python -# web.application._match - -elif isinstance(what, basestring): - what, result = utils.re_subm('^' + pat + '$', what, value) -``` - -`utils.re_subm` 对路径中的正则表达式进行处理。`pat` 和 `what` -是 `urls` 中对应的项, `value` 是当前的请求路径。 - -```python -# utils.re_subm - -def re_subm(pat, repl, string): - """ - Like re.sub, but returns the replacement _and_ the match object. - - >>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball') - >>> t - 'foooooolish' - >>> m.groups() - ('oooooo',) - """ - compiled_pat = re_compile(pat) - proxy = _re_subm_proxy() - compiled_pat.sub(proxy.__call__, string) - return compiled_pat.sub(repl, string), proxy.match - -``` - -这里 `re_compile(pat)` 的含义与 `re.complie(pat)` 类似, -返回一个`RegexObject` 对象,只不过加入了 `Cache` 机制,避免多次执行 `re.complie` 调用。 - -下面看这两行代码 - -```python -proxy = _re_subm_proxy() -compiled_pat.sub(proxy.__call__, string) -``` - -其中 `_re_subm_proxy` 定义为: - -```python -class _re_subm_proxy: - def __init__(self): - self.match = None - def __call__(self, match): - self.match = match - return '' -``` - -`compiled_pat` 会与 `string` 一起生成一个 `Match` 对象, -这个对象会存储在一个 `_re_subm_proxy` 对象,即 `proxy`中 -我们可以看到 `return` 中,`proxy` 最后会将其 `match` 返回。 -我在想,为什么使用`search`直接生成一个 `Match` 对象然后返回呢? -查了一下,这似乎与 `代理模式` 相关。但具体为什么还不知道。又查了很久,似乎又与 `弱引用`, 和之前的 Cache 相关, -但是不确定。 - - 问题:为什么要使用代理类 - -最后的 `return` 语句返回两个值,其中 -`compiled_pat.sub (repl, string)` 是把 `string` 与 `pat` -中匹配的部分,用于替换 `repl` 中对应的组号。`proxy.match` 就是 `pat` 和 `string` 匹配得到的 `Mathc`对象。 - -关于 `Python 正则表达式` 可以参考这篇: -[Python 正则表达式指南](http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html) - -到这里,我们可能就明白了一开始的时候的那段: - -```python -# web.application._match - -elif isinstance(what, basestring): - what, result = utils.re_subm('^' + pat + '$', what, value) -``` - -的含义,它的意思是如何 `urls` 里有一对 `(url,class)` -其中 `url` 和 `class` 都是用正则表达式表示的, -这时候实际来了一个请求 `r_url`,它会与 `url`进行 -匹配,根据这个匹配生成相应的 `r_class`。 - -看看刚才的示例就明白了: - - - >>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball') - >>> t - 'foooooolish' - -你可以把 `re_subm` 的三个参数依序当成 `url`, `class`, -以及 `r_url`,最后得到的 `t` 就是 `r_class`。 - -举个例子,假如有一系列页面,分别是 `req1`, `req2`, -`req3`, ... , `reqN`, 需要处理。 - -那么就可以在 `urls` 里加入 - - (r'req(\d+)', r'proc\1') - -这样,如果来了请求 `req2`,通过`re_subm`自然会解析成 `proc2`。 - - >>> t, m = re_subm(r'req(\d+)', r'proc\1', 'req2') - >>> t - 'proc2' - >>> m.group(0) - 'req2' - >>> m.group(1) - '2' - -总之, - -```python -def handle(self): - fn, args = self._match(self.mapping, web.ctx.path) - return self._delegate(fn, self.fvars, args) -``` - -`self._match` 返回的是请求 `request('/a')` 与 - -``` -python - urls = ( - "/a", "redirect /hello/", - "/b/(.*)", r"redirect /hello/\1", - "/hello/(.*)", "hello" - ) -``` - -进行匹配后的结果, `'/a'` 自然与 `urls[0]` -匹配,得到的 `fn` 是 `"redirect /hello/"`, -得到的 `args` 是 `[]`,因为`urls[0]`里面就没分组。 - -好了,现在进入 `self._delegate`,看看解析后的 -`fn` 和 `args` 被如何处理。 - -```python -def _delegate(self, f, fvars, args=[]): - def handle_class(cls): - meth = web.ctx.method - if meth == 'HEAD' and not hasattr(cls, meth): - meth = 'GET' - if not hasattr(cls, meth): - raise web.nomethod(cls) - tocall = getattr(cls(), meth) - return tocall(\*args) - - def is_class(o): return isinstance(o, (types.ClassType, type)) - - if f is None: - raise web.notfound() - elif isinstance(f, application): - return f.handle_with_processors() - elif is_class(f): - return handle_class(f) - elif isinstance(f, basestring): - if f.startswith('redirect '): - url = f.split(' ', 1)[1] - if web.ctx.method == "GET": - x = web.ctx.env.get('QUERY_STRING', '') - if x: - url += '?' + x - raise web.redirect(url) - elif '.' in f: - mod, cls = f.rsplit('.', 1) - mod = __import__(mod, None, None, ['']) - cls = getattr(mod, cls) - else: - cls = fvars[f] - return handle_class(cls) - elif hasattr(f, '__call__'): - return f() - else: - return web.notfound() -``` - -一眼就可以看出有一个 `elif` 分支对 `redirect` 进行了处理。 - -```python -elif isinstance(f, basestring): - if f.startswith('redirect '): - url = f.split(' ', 1)[1] - if web.ctx.method == "GET": - x = web.ctx.env.get('QUERY_STRING', '') - if x: - url += '?' + x - raise web.redirect(url) -``` - -分析出 `redirect /hello/` 中的 `/hello/`,再看看 -有没有查询字串,就是请求里有没有`?xx` 什么的。 -得到最终的 `url` ,转给 `redirect` 函数处理。 -直接找到 `webapi.py`,看到 `redirect` 的定义。 - - -```python -class Redirect(HTTPError): - """A `301 Moved Permanently` redirect.""" - def __init__(self, url, status='301 Moved Permanently', absolute=False): - """ - Returns a `status` redirect to the new URL. - `url` is joined with the base URL so that things like - `redirect("about") will work properly. - """ - newloc = urlparse.urljoin(ctx.path, url) - - if newloc.startswith('/'): - if absolute: - home = ctx.realhome - else: - home = ctx.home - newloc = home + newloc - - headers = { - 'Content-Type': 'text/html', - 'Location': newloc - } - HTTPError.__init__(self, status, headers, "") - -redirect = Redirect -``` - -这时候传过来的 `url` 是 `/hello/`。 注意 `status` -的默认值。 -先使用 `urljoin` 得到一个绝对路径。官网上给出的 -`urlparse.urljoin` 的例子是: - -```python ->>> from urlparse import urljoin ->>> urljoin('http://www.cwi.nl/%7Eguido/Python.html', 'FAQ.html') -'http://www.cwi.nl/%7Eguido/FAQ.html' -``` - -另外一个例子是针对第二个参数是绝对路径的情况的。 - -```python ->>> urljoin('http://www.cwi.nl/%7Eguido/Python.html', -... '//www.python.org/%7Eguido') -'http://www.python.org/%7Eguido' -``` - -第二个例子的用法和我们当前的状况一致。所以得到的 -`newloc` 还是 `/hello/`。 - -然后再查看 `ctx.home` 和 `ctx.realhome` 得到 -新的 `newloc`。 - -在 `application.py` 中, `load` 函数定义了一些 `path`: - -```python -ctx.homedomain = ctx.protocol + '://' + env.get('HTTP_HOST', '[unknown]') -ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', '')) -ctx.home = ctx.homedomain + ctx.homepath -#@@ home is changed when the request is handled to a sub-application. -#@@ but the real home is required for doing absolute redirects. -ctx.realhome = ctx.home -``` - -从这可以看出,如果调用子程序 `ctx.home` 会改变, -但是 `ctx.realhome` 不会变,它用来在 redirect 时 -生成绝对路径。 - -```python -if newloc.startswith('/'): - if absolute: - home = ctx.realhome - else: - home = ctx.home - newloc = home + newloc -``` - -现在可以理解了,如果 `absolute` 为真,那就用 -`ctx.realhome` 和 `newloc` 组成新值, -如果不为真,直接用 `ctx.home`。这可能在使用子程序, -可以转向到以子程序为基础的`url` 中。 - -现在回到 `Redirect`。转到 `HTTPError`。 - -```python -class HTTPError(Exception): - def __init__(self, status, headers={}, data=""): - ctx.status = status - for k, v in headers.items(): - header(k, v) - self.data = data - Exception.__init__(self, status) -``` - -`ctx.status` 被设置,调用 `Exception`。 - -之后我们回到 `web.redirect(status)` 调用处。 - -```python -def _delegate(self, f, fvars, args=[]): - - ... - - elif isinstance(f, basestring): - if f.startswith('redirect '): - url = f.split(' ', 1)[1] - if web.ctx.method == "GET": - x = web.ctx.env.get('QUERY_STRING', '') - if x: - url += '?' + x - raise web.redirect(url) - - - ... - -``` - -可以看出,这里把异常抛出。所以我们回到上一层,看看 -相应的处理。 - -上一层: - -```python -def handle(self): - fn, args = self._match(self.mapping, web.ctx.path) - return self._delegate(fn, self.fvars, args) -``` - -再上一层: - -```python -# web.application.handle_with_processors - -def handle_with_processors(self): - def process(processors): - try: - if processors: - p, processors = processors[0], processors[1:] - return p(lambda: process(processors)) - else: - return self.handle() - except web.HTTPError: - raise - except (KeyboardInterrupt, SystemExit): - raise - except: - print >> web.debug, traceback.format_exc() - raise self.internalerror() - - # processors must be applied in the resvere order. (??) - return process(self.processors) -``` - -再上一层: - -```python -# web.application.wsgifunc.wsgi - - def wsgi(env, start_resp): - # clear threadlocal to avoid inteference of previous requests - self._cleanup() - - self.load(env) - try: - # allow uppercase methods only - if web.ctx.method.upper() != web.ctx.method: - raise web.nomethod() - - result = self.handle_with_processors() - if is_generator(result): - result = peep(result) - else: - result = [result] - except web.HTTPError, e: - result = [e.data] - - - result = web.safestr(iter(result)) - - status, headers = web.ctx.status, web.ctx.headers - - start_resp(status, headers) -``` - -这里终于看到了对异常的处理。这里 `e.data` 为空。我 -们之前并没有设置这个值。 - -接着看后续处理, `safestr` 在 `utils.py` 中定义, -它负责把给定的对象转化成 `utf-8` 编码的字符串。 -下一句设置 `status` 和 `headers` 的值。之前 - -我们已经看到 `web.ctx.status` 被设置了: - -```python -class HTTPError(Exception): - def __init__(self, status, headers={}, data=""): - ctx.status = status - for k, v in headers.items(): - header(k, v) - self.data = data - Exception.__init__(self, status) -``` - -`__init__` 中的 `status` 参数在上一层中设置。 - -```python -class Redirect(HTTPError): - """A `301 Moved Permanently` redirect.""" - def __init__(self, url, status='301 Moved Permanently', absolute=False): - """ - Returns a `status` redirect to the new URL. - `url` is joined with the base URL so that things like - `redirect("about") will work properly. - """ - newloc = urlparse.urljoin(ctx.path, url) - - if newloc.startswith('/'): - if absolute: - home = ctx.realhome - else: - home = ctx.home - newloc = home + newloc - - headers = { - 'Content-Type': 'text/html', - 'Location': newloc - } - HTTPError.__init__(self, status, headers, "") - -redirect = Redirect -``` - -注意这里的 `status` 默认参数。 - -现在再回到 - -```python -def wsgi(env, start_resp): - # clear threadlocal to avoid inteference of previous requests - self._cleanup() - - self.load(env) - - try: - # allow uppercase methods only - if web.ctx.method.upper() != web.ctx.method: - raise web.nomethod() - - - result = self.handle_with_processors() - - if is_generator(result): - result = peep(result) - else: - result = [result] - except web.HTTPError, e: - result = [e.data] - - - result = web.safestr(iter(result)) - - status, headers = web.ctx.status, web.ctx.headers - - start_resp(status, headers) - - def cleanup(): - self._cleanup() - yield '' # force this function to be a generator - - return itertools.chain(result, cleanup()) -``` - -随后调用 `start_resp` ,对 `status`, `headers` -进行处理。我们再回到上一层。 - -```python -#web.application.request - -def request(self, localpart='/', method='GET', data=None, - host="0.0.0.0:8080", headers=None, https=False, **kw): - - ... - - response = web.storage() - def start_response(status, headers): - response.status = status - response.headers = dict(headers) - response.header_items = headers - response.data = "".join(self.wsgifunc()(env, start_response)) - return response -``` - -这时候,`response` 中的 `stauts` 会设置好。 -最后,返回最初的调用。 - -```python -def testRedirect(self): - urls = ( - "/a", "redirect /hello/", - "/b/(.*)", r"redirect /hello/\1", - "/hello/(.*)", "hello" - ) - app = web.application(urls, locals()) - class hello: - def GET(self, name): - name = name or 'world' - return "hello " + name - - response = app.request('/a') - self.assertEquals(response.status, '301 Moved Permanently') - self.assertEquals(response.headers['Location'], 'http://0.0.0.0:8080/hello/') -``` - -`self.assertEquals` 会验证 `response.status` 的值。 - -所以,到这里,`testRedirect` 函数就分析结束了。 diff --git a/_posts/2013-11-14-Application_testUppercaseMethods.md b/_posts/2013-11-14-Application_testUppercaseMethods.md deleted file mode 100644 index ff71e11..0000000 --- a/_posts/2013-11-14-Application_testUppercaseMethods.md +++ /dev/null @@ -1,234 +0,0 @@ ---- -layout: default -title: web.py 源代码分析之 web.test.application.test_UppercaseMethods -category: 源代码阅读 -comments: true ---- - -# 分模块测试 - -## application.py - -对 application.py 的测试,调用命令: - - python test/application.py - - -### test_UppercaseMethods(self) - -这个函数源代码是: - -```python -def testUppercaseMethods(self): - urls = ("/", "hello") - app = web.application(urls, locals()) - class hello: - def GET(self): return "hello" - def internal(self): return "secret" - - response = app.request('/', method='internal') - self.assertEquals(response.status, '405 Method Not Allowed') -``` - -这个函数似乎是在测试 `request` 请求的方法不正确时,`application` -的反应。 - -我们打印一下 `response` 的值,发现内容是这样的: - - - -从这里可以看出允许的方法: - - 'Allow': 'GET, HEAD, POST, PUT, DELETE' - -`response.status` 最终值为: - - 'status': '405 Method Not Allowed', - - -我们再一次深入 `web.application.request`, 看一看具体实现。 - -```python - def request(self, localpart='/', method='GET', data=None, - host="0.0.0.0:8080", headers=None, https=False, **kw): - - ... - - env = dict( - env, - HTTP_HOST=host, - REQUEST_METHOD=method, - PATH_INFO=path, - QUERY_STRING=query, - HTTPS=str(https) - ) - - ... - - response = web.storage() - def start_response(status, headers): - response.status = status - response.headers = dict(headers) - response.header_items = headers - - response.data = "".join(self.wsgifunc()(env, start_response)) - - return response -``` - -我们只保留了关键代码,可以看出,method 首先被设置在了 `env` 中,然后, -通过 - -```python - self.wsgifunc()(env, start_response)) -``` - -`start_response` 被 `wsgifunc()` 返回的函数调用,`response` 的值被设置。 如果阅读了前面推荐的几篇关于 `WSGI` 的文章,对这里的 `self.wsgifunc()` 和 `start_response`应该不会感觉到陌生。 - -好,我们继续深入 `web.application.wsgifunc()` - -```python - def wsgifunc(self, *middleware): - - ... - - def wsgi(env, start_resp): - # clear threadlocal to avoid inteference of previous requests - self._cleanup() - - self.load(env) - try: - # allow uppercase methods only - if web.ctx.method.upper() != web.ctx.method: - raise web.nomethod() - - result = self.handle_with_processors() - if is_generator(result): - result = peep(result) - else: - result = [result] - except web.HTTPError, e: - result = [e.data] - - - result = web.safestr(iter(result)) - - status, headers = web.ctx.status, web.ctx.headers - start_resp(status, headers) - - def cleanup(): - self._cleanup() - yield '' # force this function to be a generator - - return itertools.chain(result, cleanup()) - - for m in middleware: - wsgi = m(wsgi) - - return wsgi -``` - -从上述代码可以看出,返回的函数就是 `wsgi`, `wsgi` 在返回前,可能 -会使用 `middleware` 进行一层一层的包装。不过这里,我们只看 `wsgi` -的代码就可以了。 - -关键的两行在: - -```python -status, headers = web.ctx.status, web.ctx.headers -start_resp(status, headers) -``` - -这里,`web.application.requests.start_response` 函数 被调用,并设置好 -`status` 和 `headers`,二者的值从 `web.ctx` 中取得。所以,下一部,我们 -关心 `web.ctx` 值的来源。因为 `method` 的信息在 `env` 中,所以 -`web.ctx` 的值一定会受 `env` 的影响,所以在 `wsgi` 中要找到 `env`是如何 -被使用的。 - -```python -self.load(env) -try: - # allow uppercase methods only - if web.ctx.method.upper() != web.ctx.method: - raise web.nomethod() -``` - -我们猜想 `self.load(env)` 对 `web.ctx` 进行了设置,然后后面的 `try` -对 `web.ctx` 检查,这里对 `upper`的检查,让我们想到了,原来一开始的 -`testUppercaseMethods` 是在测试 `method` 名字是不是全是大写。。。 - -于是在 `raise web.nomethod()` 前加一个 `print` ,测试一下是不是执行到 -了这里,发现还真是。那么我们再深入 `load` ,看看如何根据 `env` 设置 -`web.ctx`。 - -```python -def load(self, env): - """Initializes ctx using env.""" - ctx = web.ctx - ctx.clear() - ctx.status = '200 OK' - - ... - - ctx.method = env.get('REQUEST_METHOD') - - ... -``` - -`load` 函数会根据 `env` 设置 `web.ctx` ,但是只是简单赋值,没有对 -`method` 方法的检查。于是,我明白了,原来是在 `raise web.nomethod()`中对 -`status` 进行了设置。 - -在 `raise` 之前打印 `web.ctx.status` 的值,果然是 `200 OK`。那我们再进入 -`web.nomethod` 看看吧。 - -这个函数在 `webapi.py` 中,进入文件一看,果然是对许多状态的定义。 -找到 `nomethod` ,发现指向 `NoMethod`,下面看 `NoMethod`: - -```python -class NoMethod(HTTPError): - """A `405 Method Not Allowed` error.""" - def __init__(self, cls=None): - status = '405 Method Not Allowed' - headers = {} - headers['Content-Type'] = 'text/html' - - methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'] - if cls: - methods = [method for method in methods if hasattr(cls, method)] - - headers['Allow'] = ', '.join(methods) - data = None - HTTPError.__init__(self, status, headers, data) -``` - -发现还真是对错误号 `405` 的处理。 设置好status, 最终的处理 -发给了 `HTTPError`,所以我们再进入 `HTTPError`。 - -```python -class HTTPError(Exception): - def __init__(self, status, headers={}, data=""): - ctx.status = status - for k, v in headers.items(): - header(k, v) - self.data = data - Exception.__init__(self, status) -``` - -这里终于发现了对 `ctx.status` 的设置。这就是在这里我们知道了下面这两 -行关键代码的最终来源。 - -```python -status, headers = web.ctx.status, web.ctx.headers -start_resp(status, headers) -``` - -前者设置 `status` 和 `headers`,后者调用 `requests` 函数中的 `start_response` 对 - `response` 的状态进行了设置。最后返回给最一开始 `testUppercaseMethods` 中的 - `response`。 - - diff --git a/_posts/2013-11-14-Application_testreloader.md b/_posts/2013-11-14-Application_testreloader.md deleted file mode 100644 index 675c3b4..0000000 --- a/_posts/2013-11-14-Application_testreloader.md +++ /dev/null @@ -1,307 +0,0 @@ ---- -layout: default -title: web.py 源代码分析之 web.test.application.test_reloader -category: 源代码阅读 -comments: true ---- - -# 分模块测试 - -## application.py - -对 application.py 的测试,调用命令: - - python test/application.py - -### test_reloader(self) - -```python -def test_reloader(self): - write('foo.py', data % dict(classname='a', output='a')) - import foo - app = foo.app - - self.assertEquals(app.request('/').data, 'a') - - # test class change - time.sleep(1) - write('foo.py', data % dict(classname='a', output='b')) - self.assertEquals(app.request('/').data, 'b') - - # test urls change - time.sleep(1) - write('foo.py', data % dict(classname='c', output='c')) - self.assertEquals(app.request('/').data, 'c') -``` - -总的来说,这个过程会生成一个 foo.py 文件 - -```python -import web - -urls = ("/", "a") -app = web.application(urls, globals(), autoreload=True) - -class a: - def GET(self): - return "a" - -``` - -这是一个典型的 web 服务端应用程序,表示对 `/` 发起 `GET` -请求时,会调用 `class a` 中的 `GET` 函数,测试就是看看 -web.application 是否可以正常完成任务,即是否可以正确返回"a" - -下面详细看代码。 - -首先使用 `write` 生成了一个 `foo.py` 程序: - -```python - write('foo.py', data % dict(classname='a', output='a')) -``` - -write 源代码: - -```python -def write(filename, data): - f = open(filename, 'w') - f.write(data) - f.close() -``` - -data 定义: -```python -data = """ -import web - -urls = ("/", "%(classname)s") -app = web.application(urls, globals(), autoreload=True) - -class %(classname)s: - def GET(self): - return "%(output)s" - -""" -``` - -`data` 相当于一个小型 web 程序的模板,类名和返回值由 -一个 `dict` 指定,生成一个字符串,由 `write` 生成文件。 - -下面是类别和返回值为 `a` 时的 `foo.py` - -```python -import web - -urls = ("/", "a") -app = web.application(urls, globals(), autoreload=True) - -class a: - def GET(self): - return "a" -``` - -测试的方式采用 `TestCase` 中的 `assertEquals` 函数,比较 -实际值与预测值。 - -```python -import foo -app = foo.app -self.assertEquals(app.request('/').data, 'a') -``` - -`app.request('/')` 会得到一个Storage类型的值: - - - -其中的 `data` 就是 `foo.py` 中 `GET` 返回的值。 - -我对这个 `app.request('/')` 是比较困惑的。以 `foo.py` 为例, -之前写程序时,一般是有一个这样的程序: - -```python -import web - -urls = ("/", "a") -app = web.application(urls, globals(), autoreload=True) - -class a: - def GET(self): - return "a" - -if __name__ == "__main__": - app.run() -``` - -然后在浏览器中请求 `0.0.0.0:8080/` 。 -而在 `request` 之前,也没看到 `run` 啊,怎么就能 `request` 回 -数据呢,而且通过上述代码运行后,程序会一直运行直到手动关闭, -而 `request` 的方式则是测试完后,整个程序也结束了。 - -所以,下一部,想比较一下 `application.run` 和 `application.request` 的不同。 - -我们只看关键部分,即返回的值是如何被设值的。 - -在 `web.application.request` 中: - -```python -def request(self, localpart='/', method='GET', data=None, - host="0.0.0.0:8080", headers=None, https=False, **kw): - - ... - -response = web.storage() -def start_response(status, headers): - response.status = status - response.headers = dict(headers) - response.header_items = headers -response.data = "".join(self.wsgifunc()(env, start_response)) -return response -``` - -上述代码中 `self.wsgifunc()(env, start_response)` 比较另人困惑, -我还以为是调用函数的新方式呢,然后看了一下 `wsgifunc` 的代码, -它会返回一个函数`wsgi`,`wsgi`以 `(env, start_response)` 为参数。 -在 `wsgi` 内部,又会调用 `handle_with_processors`, `handle_with_processors` 会再调用 `handle` - -```python - def handle(self): - fn, args = self._match(self.mapping, web.ctx.path) - return self._delegate(fn, self.fvars, args) -``` - -测试了一下,`self._match()` 会得到类名, `self._delegate` 会 -得到返回的字符串,即 `GET`的返回值。 - -进入 `self._delegate`, 会最终调用一个关键函数 `handle_class`: - -```python -def handle_class(cls): - meth = web.ctx.method - if meth == 'HEAD' and not hasattr(cls, meth): - meth = 'GET' - if not hasattr(cls, meth): - raise web.nomethod(cls) - tocall = getattr(cls(), meth) - return tocall(\*args) -``` - -参数`cls`值为`foo.a`, `meth` 会得到方法名 `GET`, 然后 -`tocall` 会得到函数 `a.GET`, 至此,我们终于得以调用, -`GET`函数,得到了返回的字符串。 - -从整个过程可以看出,没有启动服务器的代码,只是不断地调用 -函数,最终来到 `GET` 函数。 - - -再看看 `web.application.run`: - -```python -def run(self, *middleware): - return wsgi.runwsgi(self.wsgifunc(\*middleware)) -``` - -接着,我们来到 `wsgi.runwsgi`: - -```python -def runwsgi(func): - """ - Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server, - as appropriate based on context and `sys.argv`. - """ - - if os.environ.has_key('SERVER_SOFTWARE'): # cgi - os.environ['FCGI_FORCE_CGI'] = 'Y' - - if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi - or os.environ.has_key('SERVER_SOFTWARE')): - return runfcgi(func, None) - - if 'fcgi' in sys.argv or 'fastcgi' in sys.argv: - args = sys.argv[1:] - if 'fastcgi' in args: args.remove('fastcgi') - elif 'fcgi' in args: args.remove('fcgi') - if args: - return runfcgi(func, validaddr(args[0])) - else: - return runfcgi(func, None) - - if 'scgi' in sys.argv: - args = sys.argv[1:] - args.remove('scgi') - if args: - return runscgi(func, validaddr(args[0])) - else: - return runscgi(func) - - - server_addr = validip(listget(sys.argv, 1, '')) - if os.environ.has_key('PORT'): # e.g. Heroku - server_addr = ('0.0.0.0', intget(os.environ['PORT'])) - - return httpserver.runsimple(func, server_addr) -``` - -前面是对参数 `sys.argv` 分析,根据参数指定,启动相应服务, -我们的简单web程序没有参数,所以直接来到最后几行。 - -关键在于最后的 `return` - -```python -return httpserver.runsimple(func, server_addr) -``` - -可以看出,这里启动了一个服务器。 - -再看看 `httpserver.runsimple`的代码: - -```python -def runsimple(func, server_address=("0.0.0.0", 8080)): - """ - Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. - The directory `static/` is hosted statically. - - [cp]: http://www.cherrypy.org - """ - global server - func = StaticMiddleware(func) - func = LogMiddleware(func) - - server = WSGIServer(server_address, func) - - if server.ssl_adapter: - print "https://%s:%d/" % server_address - else: - print "http://%s:%d/" % server_address - - try: - server.start() - except (KeyboardInterrupt, SystemExit): - server.stop() - server = None -``` - -从注释中可以看出,这里使用了 `CherryPy`中的 `WSGI server`, -启动了服务器。 - -```python - server.start() -``` - -我们不再继续深入下去。只是直观地了解一下, `application.run` -和 `application.request` 的不同之处。 - -从这里我们看出了相当重要的概念 `WSGI`。 -这里有几篇对 `WSGI` 作介绍的博客: - -* [Wsgi研究](http://blog.kenshinx.me/blog/wsgi-research/) -* [wsgi初探](http://linluxiang.iteye.com/blog/799163) -* [Getting Started with WSGI] (http://lucumr.pocoo.org/2007/5/21/getting-started-with-wsgi/) -* [An Introduction to the Python Web Server Gateway Interface] (http://ivory.idyll.org/articles/wsgi-intro/what-is-wsgi.html) -* [化整為零的次世代網頁開發標準: WSGI](http://blog.ez2learn.com/2010/01/27/introduction-to-wsgi/) - - -这里有对 `WSGI` 的详细介绍: - -* [WSGI Tutorial](http://webpython.codepoint.net/wsgi_tutorial) -* [WSGI org](http://wsgi.readthedocs.org/en/latest/) -* [PEP 333](http://www.python.org/dev/peps/pep-0333/) diff --git a/_posts/2013-11-14-skeleton_arch.md b/_posts/2013-11-14-skeleton_arch.md deleted file mode 100644 index 05ae541..0000000 --- a/_posts/2013-11-14-skeleton_arch.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -layout: default -title: web.py 项目架构分析之 skeleton -category: 源代码阅读 -comments: true ---- - -# web.py 项目之 skeleton - -skeleton 是 web.py 官网上给出的一个最简单的项目结构示例。 - -## 目录树 -``` -. -└── src - ├── code.py - ├── config.py - ├── db.py - ├── sql - │   └── tables.sql - ├── templates - │   ├── base.html - │   ├── item.html - │   └── listing.html - └── view.py - -3 directories, 9 files -``` - -## 结构分析 - -### 控制模块 - -```python -#code.py - -import web -import view, config -from view import render - -urls = ( - '/', 'index' -) - -class index: - def GET(self): - return render.base(view.listing()) - -if __name__ == "__main__": - app = web.application(urls, globals()) - app.internalerror = web.debugerror - app.run() -``` - -`code` 模块作为入口: - -* app 的创建与启动 -* url 与 处理类的映射与处理入口 - -但是,具体的处理并不在这里实现。而是放在了 `view` 模块中。 - - 这一模块是MVC中的C吗? - -### 显示模块 - -```python -#view.py - -import web -import db -import config - -t_globals = dict( - datestr=web.datestr, -) -render = web.template.render('templates/', cache=config.cache, - globals=t_globals) -render._keywords['globals']['render'] = render - -def listing(\**k): - l = db.listing(\**k) - return render.listing(l) -``` - -从这里可以看出 `view` 模块 - -* 与模板关联 -* 从数据库中取数据,然后发给模板 - -我们再来看看模板: - -```html - - -$def with (page, title=None) - -my site\ -$if title: : $title\ - - -

    my site

    -$:page - -``` - -`base.html` 是所有模块的公共部分,每个模块只需要提供 -它的 `page` ,即内容就可以了。 - -```html - - -$def with (items) - -$for item in items: - $:render.item(item) - -``` - - 这一模块是MVC中的V吗 - -### 数据操作 - -数据库操作分三部分 - -* sql/tables.sql 数据库表定义 -* config.py 数据库连接 -* db.py 数据库操作 - -``` -/* tables.sql */ -CREATE TABLE items ( - id serial primary key, - author_id int references users, - body text, - created timestamp default current_timestamp -); -``` - -``` -#config.py - -import web -DB = web.database(dbn='mysql', db='skeleton', user='root', pw='xx') -cache = False -``` - -``` -# db.py -import config - -def listing(\**k): - return config.DB.select('items', **k) -``` - - 这是MVC中的M吗 - -这是 web.py 中最基本的一个项目结构。应该是使用的MVC设计模式。但是 -因为程序本身不大,还体会不到 MVC 的好处。 diff --git a/_posts/2013-11-17-todolist_arch.md b/_posts/2013-11-17-todolist_arch.md deleted file mode 100644 index 5fad648..0000000 --- a/_posts/2013-11-17-todolist_arch.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -layout: default -title: web.py 项目架构分析之 todolist -category: 源代码阅读 -comments: true ---- - -# web.py 项目之 todolist - -## 目录树 - -``` -. -└── src - ├── model.py - ├── schema.sql - ├── templates - │   ├── base.html - │   └── index.html - ├── todo.py - -2 directories, 8 files -``` - -## 项目说明 - -项目来自 [webpy.org](http://webpy.org/src/todo-list/0.3), -主要实现一个基于web的 todolist,就是可以添加删除一些任务。 -非常简单 - -## 结构分析 - -### 控制模块 - -控制模块只有todo.py - -```python - -""" Basic todo list using webpy 0.3 """ -import web -import model - -### Url mappings - -urls = ( - '/', 'Index', - '/del/(\d+)', 'Delete' -) - - -### Templates -render = web.template.render('templates', base='base') - - -class Index: - - form = web.form.Form( - web.form.Textbox('title', web.form.notnull, - description="I need to:"), - web.form.Button('Add todo'), - ) - - def GET(self): - """ Show page """ - todos = model.get_todos() - form = self.form() - return render.index(todos, form) - - def POST(self): - """ Add new entry """ - form = self.form() - if not form.validates(): - todos = model.get_todos() - return render.index(todos, form) - model.new_todo(form.d.title) - raise web.seeother('/') - - - -class Delete: - - def POST(self, id): - """ Delete based on ID """ - id = int(id) - model.del_todo(id) - raise web.seeother('/') - - -app = web.application(urls, globals()) - -if __name__ == '__main__': - app.run() -``` - -这一模块是对页面逻辑的处理,包括: - -* 处理页面请求 Index.GET -* 处理添加请求 Index.POST -* 处理删除请求 Delete.POST - - 这是MVC中的C吗 - - -### 显示模块 - -显示模块是 templates 下的两个文件,base.html, index.html -框架使用模板引擎已经将显示与实现分开了,这个项目没有像 -skeleton 项目那样有专门的一个 view.py 提供具体的与显示相关 -的逻辑。 - -templates/base.html - -```html - -$def with (page) - - - - Todo list - - - -$:page - - - -``` - -templates/index.html - -```html - -$def with (todos, form) - - - - - - -$for todo in todos: - - - - -
    What to do ?
    $todo.title -
    - -
    -
    - -
    -$:form.render() -
    -``` - -注意这里表单的实现,是在控制模块中生成了 form, 传给模板引擎。 -我觉得还是应该分开好。毕竟 form 只与显示相关,和控制无关。 - -### 数据模块 - -数据模板提供了 model.py 处理数据库操作,数据库操作并不直接在 -控制模块中体现,而是由 model.py 提供接口。另外还包括 schema.sql -提供数据库表格式。 - -schema.sql - -``` -CREATE TABLE todo ( - id INT AUTO_INCREMENT, - title TEXT, - primary key (id) -); -``` - -model.py - -```python -import web - -db = web.database(dbn='mysql', db='todo', user='root', pw="XXX") - -def get_todos(): - return db.select('todo', order='id') - -def new_todo(text): - db.insert('todo', title=text) - -def del_todo(id): - db.delete('todo', where="id=$id", vars=locals()) -``` - -总的来说,项目还是太小了,无法体会到当初作 BlogSystem 时遇到的困难应该怎么 -解决,不过MVC的结构已经非常明显了,希望以后可以阅读一些大型网站的结构。 diff --git a/_posts/2013-11-18-blog_arch.md b/_posts/2013-11-18-blog_arch.md deleted file mode 100644 index f727c07..0000000 --- a/_posts/2013-11-18-blog_arch.md +++ /dev/null @@ -1,300 +0,0 @@ ---- -layout: default -title: web.py 项目架构分析之 blog -category: 源代码阅读 -comments: true ---- - -# web.py 项目之 blog - -## 目录树 - -``` -src/ -├── blog.py -├── model.py -├── schema.sql -└── templates - ├── base.html - ├── edit.html - ├── index.html - ├── new.html - └── view.html - -1 directory, 8 files -``` - -## 项目说明 - -项目来自 [webpy.org](http://webpy.org/src/blog/0.3), -主要实现一个基于web的博客系统,实现了基本的增删查改。 - -## 结构分析 - -### 控制模块 - -控制模块包括 blog.py - -```python -""" Basic blog using webpy 0.3 """ -import web -import model - -### Url mappings - -urls = ( - '/', 'Index', - '/view/(\d+)', 'View', - '/new', 'New', - '/delete/(\d+)', 'Delete', - '/edit/(\d+)', 'Edit', -) - - -### Templates -t_globals = { - 'datestr': web.datestr -} -render = web.template.render('templates', base='base', globals=t_globals) - - -class Index: - - def GET(self): - """ Show page """ - posts = model.get_posts() - return render.index(posts) - - -class View: - - def GET(self, id): - """ View single post """ - post = model.get_post(int(id)) - return render.view(post) - - -class New: - - form = web.form.Form( - web.form.Textbox('title', web.form.notnull, - size=30, - description="Post title:"), - web.form.Textarea('content', web.form.notnull, - rows=30, cols=80, - description="Post content:"), - web.form.Button('Post entry'), - ) - - def GET(self): - form = self.form() - return render.new(form) - - def POST(self): - form = self.form() - if not form.validates(): - return render.new(form) - model.new_post(form.d.title, form.d.content) - raise web.seeother('/') - - -class Delete: - - def POST(self, id): - model.del_post(int(id)) - raise web.seeother('/') - - -class Edit: - - def GET(self, id): - post = model.get_post(int(id)) - form = New.form() - form.fill(post) - return render.edit(post, form) - - - def POST(self, id): - form = New.form() - post = model.get_post(int(id)) - if not form.validates(): - return render.edit(post, form) - model.update_post(int(id), form.d.title, form.d.content) - raise web.seeother('/') - - -app = web.application(urls, globals()) - -if __name__ == '__main__': - app.run() -``` - -可以看出,主要的逻辑,即对各个请求的处理,都在这个模块中, -包括表单的生成,数据的获取,以及对模板引擎的调用。这个 -模块主要是在顶层调用,具体的细节不在这里实现。 - - -### 显示模块 - -显示模块没有具体实现逻辑的代码,只有模板引擎用到的一些HTML -文件,这些文件的大体内容已经定下来,只是具体需要填充的内容 -需要在控制模块中进行设定。 - - -templates/base.html - -```html -$def with (page) - - - - My Blog - - - - - - -$:page - - - -``` - -`base.html` 为每个文件提供都基本的模板,其它文件只负责自己的那 -一块内容。 - - -templates/index.html - -```html - -$def with (posts) - -

    Blog posts

    - -
      -$for post in posts: -
    • - $post.title - from $datestr(post.posted_on) - Edit -
    • -
    -``` - -`index.html` 负责文章列表的显示。 - -templates/new.html - -```html - -$def with (form) - - -

    New Blog Post

    -
    -$:form.render() -
    -``` - -`new.html` 负责新建文件页面。 - -templates/edit.html - -```html - -$def with (post, form) - -

    Edit $form.d.title

    - -
    -$:form.render() -
    - - -

    Delete post

    -
    - -
    -``` - -`edit.html` 负责文章的编辑页面。 - -templates/view.html - -```html -$def with (post) - -

    $post.title

    -$datestr(post.posted_on)
    - -$post.content -``` - -`view.html` 负责显示文章页面。 - - -### 数据模块 - -数据模块包含两个文件 `schema.sql` 和 `model.py` - -schema.sql - -``` -CREATE TABLE entries ( - id INT AUTO_INCREMENT, - title TEXT, - content TEXT, - posted_on DATETIME, - primary key (id) -); -``` - -`schema.sql` 包含了数据库中表的格式 - -model.py - -```python - -import web, datetime - -db = web.database(dbn='mysql', db='miniblog', user='root', pw="7102155") - -def get_posts(): - return db.select('entries', order='id DESC') - -def get_post(id): - try: - return db.select('entries', where='id=$id', vars=locals())[0] - except IndexError: - return None - -def new_post(title, text): - db.insert('entries', title=title, content=text, posted_on=datetime.datetime.utcnow()) - -def del_post(id): - db.delete('entries', where="id=$id", vars=locals()) - -def update_post(id, title, text): - db.update('entries', where="id=$id", vars=locals(), - title=title, content=text) -``` - -`model.py` 包含了数据库连接的创建与数据库操作的具体细节。 -此模块中的函数被控制模块调用,从数据库中读取数据。 - - -总的来说,控制模块在最顶层,负责对请求进行处理,调用数据模块, -从数据库中读取,写入数据。再将从数据库中取出的数据送给显示模块 -进行显示。 - -感觉项目再大一点就应该把控制模块再分割了。 diff --git a/_posts/2013-11-21-gmodules_arch.md b/_posts/2013-11-21-gmodules_arch.md deleted file mode 100644 index 9c099e9..0000000 --- a/_posts/2013-11-21-gmodules_arch.md +++ /dev/null @@ -1,309 +0,0 @@ ---- -layout: default -title: web.py 项目架构分析之 googlemodules -category: 源代码阅读 -comments: true ---- - -# web.py 项目之 googlemodules - -## 项目说明 - -项目来自 [webpy.org](http://webpy.org/src/), 这是一个真实的在线上运行的 -项目: [Google Modules](http://www.googlemodules.com/), 可以上传,下载, -一些模块,还有一些评分,打标签等等功能。(不过这网站挺奇怪的。。。) - -## 目录树 - -``` -src/ - ├── application.py - ├── forum.py - ├── config_example.py - ├── INSTALL - ├── LICENCE - ├── app - │   ├── controllers - │   ├── helpers - │   ├── models - │   └── views - ├── app_forum - │   ├── controllers - │   ├── models - │   └── views - ├── data - ├── public - │   ├── css - │   ├── img - │   │   └── star - │   ├── js - │   └── rss - ├── scripts - └── sql - -18 directories -``` - -终于遇到个稍微大一点的项目了,要好好看看。 - -从目录上看,整个项目分成两个部分,app 和 app_forum,每个部分都使用了 -典型的MVC结构,将app分成 controllers, models, views 三大部分。 - -另外,网站使用的 css, js 文件,图片,也都统一放在了public目录下。 - -INSTALL 文件描述了如何安装部署项目, 包括在哪里下载项目,哪里下载web.py,如何 -配置 lighttpd, 如何配置项目。 - -config_example.py 文件给了一个配置文件模板,按自己的需要修改其中内容,最后 -把文件名改为 config.py 就可以了,其中包括对数据的配置,调试,缓存的开启等等。 - -LICENCE 文件描述了项目使用的开源协议: GPLv3。 - -项目使用的脚本放在scripts目录下,创建数据库使用的文件放在了sql目录下。 - -## 代码统计 - -先看看代码统计 - -![googlemodules_code_stat.jpg](/assets/blog-images/googlemodules_code_stat.jpg) - -## application 模块 - -### application.py - -```python - -#!/usr/bin/env python -# Author: Alex Ksikes - -# TODO: -# - setup SPF for sendmail and -# - emailerrors should be sent from same domain -# - clean up schema.sql -# - because of a bug in webpy unicode search fails (see models/sql_search.py) - -import web -import config -import app.controllers - -from app.helpers import custom_error - -import forum - -urls = ( - # front page - '/', 'app.controllers.base.index', - '/page/([0-9]+)/', 'app.controllers.base.list', - - # view, add a comment, vote - '/module/([0-9]+)/', 'app.controllers.module.show', - '/module/([0-9]+)/comment/', 'app.controllers.module.comment', - '/module/([0-9]+)/vote/', 'app.controllers.module.vote', - - # submit a module - '/submit/', 'app.controllers.submit.submit', - - # view author page - '/author/(.*?)/', 'app.controllers.author.show', - - # search browse by tag name - '/search/', 'app.controllers.search.search', - '/tag/(.*?)/', 'app.controllers.search.list_by_tag', - - # view tag clouds - '/tags/', 'app.controllers.cloud.tag_cloud', - '/authors/', 'app.controllers.cloud.author_cloud', - - # table modules - '/modules/(?:by-(.*?)/)?([0-9]+)?/?', 'app.controllers.all_modules.list_by', - - # static pages - '/feedback/', 'app.controllers.feedback.send', - '/about/', 'app.controllers.base.about', - '/help/', 'app.controllers.base.help', - - # let lighttpd handle in production - '/(?:css|img|js|rss)/.+', 'app.controllers.public.public', - - # canonicalize /urls to /urls/ - '/(.*[^/])', 'app.controllers.public.redirect', - - # mini forum app - '/forum', forum.app, - - '/hello/(.*)', 'hello', - - # site admin app -# '/admin', admin.app, -) - -app = web.application(urls, globals()) -custom_error.add(app) - -if __name__ == "__main__": - app.run() -``` - -可以看出,这是 application 部分的入口,这个模块仅仅是定义了各个请求的处理方式, -并完成程序的启动,所有的实现均不在这里出现,而是通过 `import` 导入,特别需要 -注意 `urls` 最后定义的 `/forum` 和 `/admin` 使用了子程序,而不是通过之前的字符串 -实现映射。还需要注意对静态文件,即css,js,img,rss文件的单独处理。 - -所有这些都与之前分析过的那些小项目不同,回想起我之前写的 -[BlogSystem](https://github.com/minixalpha/BlogSystem), 所有的处理实现都放在 -同一个文件中,导致最后一个文件居然 700多行,真是让人潸然泪下。。。 -而且之前也不知道使用子程序,所有处理都堆在一起。看来读完这份源代码,真应该重构一 -下了。 - -### app 模块 - -``` -app/ - ├── models # 数据模块,MVC中的 M - ├── views # 显示模块,MVC中的 V - ├── controllers # 控制模块,MVC中的 C - └── helpers # 辅助模块,实现辅助功能 - -4 directories -``` - -### controllers 模块 - -``` -controllers/ - ├── base.py # 对基本页面,如网站主页,关于网页,帮助等的处理 - ├── all_modules.py # 显示全部模块 - ├── module.py # 对模块页面的处理 - ├── search.py # 对搜索模块功能处理 - ├── submit.py # 对提交模块的处理 - ├── author.py # 查看模块作者信息 - ├── cloud.py # 对标签云页面进行处理 - ├── feedback.py # 处理反馈信息 - └── public.py # 对静态文件的处理 -``` - -这个模块主要是对请求处理的实现,在 `urls` 里定义的那些映射关系, -很多被映射到这里。 - -实现过程中,调用 models 模块对数据操作,再送入 views 模块通过模板引擎显示数据内容。 - -### models 模块 - -``` -models/ - ├── comments.py # 对评论数据的处理 - ├── modules.py # 对模块数据的处理 - ├── rss.py # 对 rss 订阅的处理 - ├── sql_search.py # 对搜索的数据处理 - ├── submission.py # 对用户提交内容的处理 - ├── tags.py # 对标签内容的数据处理 - └── votes.py # 对用户投票的数据处理 -``` - -这个模块直接调用 web.py 的db模块对数据库进行操作,对数据库的连接在 config.py 中 -已经完成。这里完成数据的获取,处理,返回。可以看出,对不同种类的数据又分成了 -很多小的模块。 - - -### views 模块 - -``` -views/ - ├── about.html - ├── all_modules.html - ├── faq.html - ├── feedback.html - ├── help.html - ├── internal_error.html - ├── layout.html - ├── list_modules_by_author.html - ├── list_modules.html - ├── not_found.html - ├── rss.html - ├── show_module.html - ├── submit_module.html - ├── submitted_form.html - ├── tag_cloud_author.html - └── tag_cloud.html -``` - -这个模块是各个页面的模板文件所在,通过模板引擎,这些模板的内容 -被填充,最终返回给用户。 - - -### helpers 模块 - -``` -helpers/ - ├── custom_error.py # 错误处理,如未找到页面等等 - ├── formatting.py # 格式处理相关的工具函数 - ├── image.py # 图片处理相关函数 - ├── misc.py # 用于生成一些随机数据 - ├── paging.py # 用于多条信息分成多个页面 - ├── render.py # 对基本布局的包装 - ├── strip_html.py # HTML 文件的处理 - ├── tag_cloud.py # 对标签云的处理 - └── utils.py # 常用的小工具函数 -``` - -这一模块把需要用到的一些工具类函数分成多个类组织在一起, -这样可以避免之前的 MVC 模块太臃肿。 - - -## forum 模块 - -在 `application.py` 中定义了对论坛请求的处理,这是在子程序中进行处理的。 - -``` - # mini forum app - '/forum', forum.app, -``` - -对 `/forum` 的请求会发到子程序 `forum.py`, 这是一个类似 `application.py` -的程序: - -```python -#!/usr/bin/env python -# Author: Alex Ksikes - -import web -import config -import app_forum.controllers - -from app.helpers import custom_error - -urls = ( - '/', 'app_forum.controllers.base.index', - '/new/', 'app_forum.controllers.base.new', - '/page/([0-9]+)/', 'app_forum.controllers.base.list', - '/thread/([0-9]+)/', 'app_forum.controllers.base.show', - '/thread/([0-9]+)/reply/', 'app_forum.controllers.base.reply', -) - -app = web.application(urls, globals()) -custom_error.add(app) - -if __name__ == "__main__": - app.run() -``` - -可以看出子程序的作用。在 application 中没有定义 `/forum/new/` ,但是对 -`/forum` 的请求会发给这里的 `forum.py`, 这个程序定义了对 `/new` 的处理, -它们连起来,构成了对 `/forum/new/` 处理的映射。 - - -forum 模块的结构与 application 模块是相似的: - -``` -app_forum/ - ├── controllers - │   ├── base.py # 对基本页面,如论坛主页的处理 - ├── models - │   └── threads.py # 对论坛数据的处理 - └── views # 论坛页面模板 - ├── list_threads.html - ├── show_thread.html - └── submitted_form.html -``` - -可以看出,只有很少的文件, 只搭了个框架,具体的内容我们就不分析了。 diff --git a/_posts/2014-01-13-write_high_quality_program.md b/_posts/2014-01-13-write_high_quality_program.md deleted file mode 100644 index 07aec01..0000000 --- a/_posts/2014-01-13-write_high_quality_program.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -layout: default -title: 如何编写高质量的程序 -category: 思想 -comments: true ---- - - -# 如何编写高质量的程序 - -学习任何编程语言都会有一个基本的过程,开始的时候学习基本的语法,然后学习各种库,框架,开始做各种项目。在做项目的过程中,随着代码量的增加,我们会渐渐感到失去对程序的掌控能力,bug开始增加,牵一发而动全身,顾此失彼。这充分说明了编写高质量程序的重要性,这里的“高质量”主要指程序的正确性,可读性,可维护性。 - -## 什么是高质量的程序 -### 正确性 - -程序正确性的重要程度无需多言,尤其在一些特殊领域,例如芯片制造业,航天业,武器制造业,对程序正确性往往有着极其严格的要求,因为一旦程序出错,代价往往是巨大的。在这些领域,需要使用形式化方法(formal methods)来自动验证程序的正确性,也就是说你需要证明程序的正确性,而不仅仅保证程序在大多数情况下是正确的。在其它领域,对正确性没有这么高要求,形式化方法也不适用,但是我们还是需要使用其它手段,例如测试,code review等等来保证软件的正确性。 - -### 可读性 - -可读性可以帮助程序作者理清思路,思路清晰后,程序不容易出错。另外,其它程序员在维护你的代码时,更容易理解你的意思,方便修改bug,方便扩展。 - -不要浪费自己的时间,更不要浪费别人的时间。 - -### 可维护性 - -这里的可维护性主要指程序应对变化的能力。程序在完成基本功能后,可能会发生各种改变:用户需求变了,性能达不到要求需要重新实现算法,等等。一旦程序的一个点发生改变,其它点如果也需要同时手动改变,那么程序会变的不可控制,出bug的机会会增加。想像一下,我们的程序是一个盒子,在添加新功能时,如果只需要把新模块插到一个地方,新模块就可以被系统使用,这样的程序可维护性是很高的。但是如果添加新功能时,需要把原来的程序盒子拆开,其它模块也需要相应修改,才能加入新模块,这样的程序可维护性就很差。 - - -## 提高程序质量的重要措施 - -### 测试 - -为什么强调先编写测试用例,再实现程序?先编写测试用例的意义在于,让编写程序的人对程序本身有更好的理解。因为你首先得明白什么样的程序是正确的,然后才能写出正确的程序。测试用例其实是对程序正确性的一种描述。 - -为什么强调自动化测试,而不是手动测试?因为自动化测试可以增加测试的便捷度,而人们通常会更多地使用那些便捷度高的东西。我在做个人项目的时候就发现,在编写了自动测试的脚本后,我每改动一点程序,就会自动运行一下脚本,在此之前,我明知道测试很重要,但是还是不会测试的如此频繁。这样的好处是可以方便定位bug,否则在系统经过了大量改动之后,出了bug都不知道可能在哪里。 - -在对程序进行重构时,很重要的一点就在于,一定要先写好测试用例,然后每改动一点,就自动测试一下,保证程序始终保持在可控状态。 - -### 良好的编程风格 - -良好的编程风格,可以增强程序的可读性,一个结构清晰的程序,你会更容易从中发现错误。另一方面,当程序发生变化时,很可能引入新的bug,良好的编程风格可以减少这种bug的出现。下面是与编程风格相关的一些措施。 - -* 风格指南 - -找一份你使用的编程语言的风格指南,例如Google的编程语言风格指南系列,Python的PEP8,并一直遵守这份指南的内容,如果有自动化工具帮助你保持这种风格,那再好不过。 - -* 最佳实践 - -寻找你所使用语言的最佳实践,他们可读性强,经过了大量实践的考验,被广泛接受,所以尽可能多地使用他们。 - -* 起一个好名字 - -变量,函数名,类名,都需要一个好名字。程序本身是对解决方案的一种描述,一个好的名字会增强这种描述性,也会让你的思维集中于解决方案,同时让其它人更容易理解你的解决方案。 - -* 不要直接使用常量 - -在程序中直接使用的常量,一般被称为 Magic Numbers, 一方面它不利于其它程序员对程序的理解,因为没有人知道这个常量代表什么。另一方面,多个常量之间可能是有关系的,直接使用常量根本反应不出这种关系。 - -* 同一变量名不要有多种含义 - -首先这种做法降低了可读性,一个变量前面一个含义,后面一个含义,这会给阅读程序的人带来困扰。 - -* 尽可能保证变量作用域小 - -尽量减少变量定义的点与变量最后一次使用的点之间的跨度,这样可以使变量与其相关代码变得紧凑,提高可读性,不用在使用变量时再去很多的地方查看其它引用。 - -* 保证函数短小精悍 - -过长的函数会让读者陷入细节的泥潭,还需要前后来回看才能明白前面一大段和后面一大段代码的关系。将函数分解,然后给函数起一个好名字,读者马上就能明白这段代码在做什么。 - - -### 提高应变能力 - -程序应对变化的能力强,可扩展性就强,也更容易在变化时保证正确性,这样的程序可维护性强。下面是一些提高程序应变能力的措施。 - -* 不要使用常量 - -不要使用常量的另一个原因在于常量可能变化,如果程序中多次引入了这个常量,那么一旦这个常量要发生变化,就需要同时改动许多地方,这时候,如果有些地方没有改,就会使程序不一致,可能引入bug。 - -* 同一变量名不要有多种含义 - -同一变量名不要有多种含义另一个原因在于,多种含义之间可能会相互影响,第一次写程序时你可能记得这些影响,但是以后对程序进行改动的时候,你可能就忘记了。例如函数内一段代码执行后,索引`i` 的值等于一个长度,但是这段代码后,你没有将`i`赋值给另一个变量`len`,而是直接使用它。等过一段时间后,你或者其它人修改这段程序时,很可能忘了这段代码执行后`i`的值需要等于一个长度,因为这是一种隐式的约定,所以很容易被忽视。 - -* 尽可能保证变量作用域小 - -保证变量作用域小也有利于重构。当一个函数变得很长时,你可能需要将它分解成多个函数,这时候,如果变量跨度小,就可以很方便地提取函数,不用来回查找与此函数相关的变量的引用。 - -* 减少代码重复 - -如果有一段代码在很多地方重复,这就告诉你,需要把他们提取成一个函数。因为代码的重复意味着这是一块独立的逻辑,独立的逻辑可以抽象成一个函数。另一方面,一旦这段逻辑需要发生变化,只需要修改这个函数就可以了,不需要把所有地方都手动修改一遍。 - -* 数据驱动 - -数据驱动的意思是用数据表示来代替程序逻辑。例如,我们需要一个程序,判断某个月有几天,在实现时,最好用一个数组表示各个月的天数,需要哪个月直接查询就好,而不要使用大量的`if`语句来作逻辑判断。这只是一个小例子,它提醒我们,如果程序中含有大量判断语句,就应该想一想,能不能用数据来驱动逻辑,这样需要修改的时候,我们直接修改数据就好,而不用修改程序逻辑。 - -我曾经接手过一个项目,这个项目其实是一个工具集,根据用户的选择,调用不同的工具。原始的代码里,就使用了大量if语句,并且每个工具其实调用方式和代码都很相似。这样,我每次添加新工具时,就需要找到多个`if`语句块,作相应修改。如果用数据驱动的话,我们完全可以去掉这些`if`语句,在用户的选择与工具之间建立对应关系,这样每当新添加工具时,只需要把工具加到系统里,系统会根据这个表直接找到这个工具。这其实和之前举的盒子的例子很相似,添加新工具时,只需要把工具插到盒子上的槽上,根本不用打开盒子。这就大大提高了程序的可扩展性。 - -### 控制复杂度 - -要保证软件的高质量,很重要的一方面在于控制复杂度。控制复杂度的一个很重要的手段在于分解复杂的事物。我们之所以觉得一个事物复杂,是因为同一时间需要关心的事情太多,把复杂事物分解后,每次我们只需要关心很少的事情,这样就控制住了复杂度。 - -* 不要使函数或类过大 - -如果一个函数或类过大,他们会变得过分复杂,你同一时间需要关心许多细节。将函数或类变小之后,你的思维在一段时间内可以集中在同一个抽象层次,而不必过于深入其细节,这样更容易发现程序中的缺陷,因为你每次只需要关心很少的事情。在最高层,你只需要关心模块之间的关系,关心算法的流程,不必关心模块内部的事情。在最低层,你只需要关心一个模块内部的事情,而不必关心其它事情。 - -* 不要使函数参数过多 - -函数参数过多可能说明这个函数负责了太多的事情,你需要将这个函数分解。另一方面,你需要从逻辑上考虑,这些参数是不是一个整体,如果是一个整体,那么直接传过来一个结构体,或者传过来一个对象,是不是更合适? - -* 不要使抽象层次过多 - -如果一个函数或类被分解为过多的抽象层次,在模块内部,你确实只需要关心很小的事情,但是这时候,由于模块过多,抽象层次过深,他们之间的关系又使复杂度增长起来。 - -### 使用自动化工具 - -自动化工具迫使我们养成良好的编程习惯,而且不容易出错。再次强调: - - 工具越是使用方便,你越会频繁使用它。 - -所以,尽可能地让你的工具使用便捷。 -例如,使用一些静态检测工具在编辑时自动帮助你检测程序的不良风格;使用重构工具帮助你重构;使用自动化测试工具在保存时自动运行测试等等。 - - -## 注意事项 - -没有什么事情是一成不变的,所有的法则都需要考虑具体的情况。如果你要用一个法则,需要真正明白自己为什么要用,需要去权衡,而不要为了能用上这个法则而生搬硬套。 - -好好问问自己: - -* 变化真的存在么? -* 真的需要抽象么? -* 真的需要面向对象么? -* 真的xxx么? - -## 参考资料 - -这篇文章是我这段时间阅读过一些书后的想法,书目有 - -* 代码大全(Code Complete) -* 重构——改善既有代码的设计(Refactoring Improving the Design of Existing Code) -* 程序设计实践(The Proactice of Programming) - -在阅读这些书的同时,我还在维护其它人的代码,做自己的个人项目。在阅读的过程中,我会不断地想到我做的项目哪里有问题,可以用书中提到的方法去修改,因此印象深刻。这些书单纯读也非常有好处,但是如果可以结合到自己的项目中,会有更大裨益。因为只有产生了强烈的共鸣,才能保证真正理解了一个东西。 - -上面提到的一些措施,都是我遇到过的,所以印象比较深刻,这几本书中还有大量提高程序质量的方法,我这里只是一个引子,希望给有心人打开一扇窗户。 diff --git a/_posts/2014-01-24-wsgiref-src-intro.md b/_posts/2014-01-24-wsgiref-src-intro.md deleted file mode 100644 index e6a7b4a..0000000 --- a/_posts/2014-01-24-wsgiref-src-intro.md +++ /dev/null @@ -1,484 +0,0 @@ ---- -layout: default -title: wsgiref 源代码分析 -category: 源代码阅读 -comments: true ---- - -# wsgiref 源代码分析 - -## wsgiref - -[wsgiref](http://docs.python.org/2/library/wsgiref.html) 是Python标准库给出的 [WSGI](http://www.python.org/dev/peps/pep-0333/) 的参考实现。 - -WSGI是Python Web 开发中为服务器程序和应用程序设定的标准,满足这一标准的服务器程序和应用程序可以配合使用。我在上一篇博文《[WSGI简介](http://blog.csdn.net/on_1y/article/details/18803563)》中对此有详细的介绍。在阅读wsgiref源代码之前,一定要对WSGI有一定了解。 - -WSGI 在 [PEP 333](http://www.python.org/dev/peps/pep-0333/) 中描述,但是只靠阅读PEP 333 可能在理解上还是不够深入,所以阅读官方给出的参考实现是很有必要的。阅读完这份源代码后,不仅有利于对WSGI的理解。而且会让你对服务端程序如何对客户端请求有一个直观的理解,从相对底层的socket监听请求,到上层对HTTP请求的处理。 - -当然,这只是对WSGI的参考实现,目的是为了描述清楚WSGI,而不是真正用在产品中。如果想对Python Web开发中服务器端的实现有更广泛,更深入的理解,需要进一步阅读Python常用框架的源代码。 - -## wsgiref 源代码分析 - -wsgiref 源代码可以在 [pypi wsgiref 0.1.2](https://pypi.python.org/pypi/wsgiref) 下载。另外,我在阅读的过程中作了大量注释,包含模块介绍,调用层次关系,demo的运行结果,等 等,还包含了阅读过程中制作的思维导图。GitHub地址[注释版wsgiref](https://github.com/minixalpha/SourceLearning/tree/master/wsgiref-0.1.2)。 - -### 结构 - -![wsgiref](/assets/blog-images/wsgiref.bmp) - -上图描述了wsgiref的所有模块及模块间的调用关系,可以看出,wsgiref有以下模块: - -* simple_server -这一模块实现了一个简单的 HTTP 服务器,并给出了一个简单的 demo,运行: - - python simple_server.py - -会启动这个demo,运行一次请求,并把这次请求中涉及到的环境变量在浏览器中显示出来。 - -* handlers -simple_server模块将HTTP服务器分成了 Server 部分和Handler部分,前者负责接收请求,后者负责具体的处理, -其中Handler部分主要在handlers中实现。 -* headers -这一模块主要是为HTTP协议中header部分建立数据结构。 -* util -这一模块包含了一些工具函数,主要用于对环境变量,URL的处理。 -* validate -这一模块提供了一个验证工具,可以用于验证你的实现是否符合WSGI标准。 - -### simple_server - - -![simple_server](/assets/blog-images/simple_server.bmp) - -可以看出,simple_server 模块主要有两部分内容 - -* 应用程序 -函数demo_app是应用程序部分 -* 服务器程序 -服务器程序主要分成Server 和 Handler两部分,另外还有一个函数 make_server 用来生成一个服务器实例 - -我们先看应用程序部分。 - -注意:以 `M:`开始的都是我自己添加的注释,不包含在最初的源代码中。 - -```python -def demo_app(environ,start_response): - # M: StringIO reads and writes a string buffer (also known as memory files). - - from StringIO import StringIO - stdout = StringIO() - print >> stdout, "Hello world!" - print >> stdout - - h = environ.items() - h.sort() - for k,v in h: - print >> stdout, k,'=',`v` - - start_response("200 OK", [('Content-Type','text/plain')]) - - return [stdout.getvalue()] -``` - -这里可以看出,这个 `demo_app` 是如何展示WSGI中对应用程序的要求的: - -* demo_app 有两个参数 -* 第一个参数 environ是一个字典 -* 第二个参数 start_response是一个可调用函数 -* demo_app 返回一个可迭代对象 -* demo_app 需要调用 start_response - -另外,可以看出,返回的内容就是环境变量当前的内容,这一点可以运行 - - python simple_server.py - -在浏览器中看到的内容,就是上述源代码的for循环中输出的。 - -这里,使用了 `StringIO` ,可以看出,所有输出的内容都先存储在其实例中,最后返回的时候一起在可迭代对象中返回。 - -接下来,我们看服务器程序。 - -先从 make_server 看起,它是用来生成一个server实例的: - -```python -def make_server( - host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler -): - """Create a new WSGI server listening on `host` and `port` for `app`""" - - # M: -> HTTPServer.__init__ - # -> TCPServer.__init__ - # -> TCPServer.server_bind - # -> TCPServer.socket.bind - # -> TCPServer.server_activate - # -> TCPServer.socket.listen - server = server_class((host, port), handler_class) - - # M: conresponding to WSGIRequestHandler.handle() - # -> handler.run(self.server.get_app()) - server.set_app(app) - - return server -``` - -虽然代码只有三行,但是可以看出生成一个 server 都需要些什么: - -* (host, port) -主机名和端口号 -* handler_class -用于处理请求的handler类 -* app -服务器程序在处理时,一定会调用我们之前写好的应用程序,这样他们才能配合起来为客户端面服务,所以,你看到了那个 `set_app` 调用。 - -另外,在注释部分,你可以看到那代码背后都发生了什么。 - -生成 server 实例时,默认的 server_class 是 WSGIServer,它是HTTPServer的子类,后者又是TCPServer的子类,所以初始化 server 时,会沿着类的继承关系执行下去,最终,生成 server 实例的过程,其实是最底层的 TCPServer 在初始化时,完成了对socket的bind和listen。 - -后面的 set_app 设置了 app,它会在 handler_class (默认为WSGIRequestHandler)的handle函数中被取出来,然后交给 handler 的 run 函数运行。 - -好,现在我们开始介绍Server部分的主要内容,即WSGIServer和WSGIRequestHandler,首先,我们看一下二者的继承体系。 - -* WSGIServer - -``` -# M: -# +------------+ -# | BaseServer | -# +------------+ -# | -# V -# +------------+ -# | TCPServer | -# +------------+ -# | -# V -# +------------+ -# | HTTPServer | -# +------------+ -# | -# V -# +------------+ -# | WSGIServer | -# +------------+ -``` - -可以看出 WSGIServer 来自 HTTPServer,后者来自 Python 标准库中的BaseHTTPServer模块,更上层的TCPServer和BaseServer来自 Python 标准库中的 SocketServer 模块。 - -* WSGIRequestHandler - -``` -# M: -# +--------------------+ -# | BaseRequestHandler | -# +--------------------+ -# | -# V -# +-----------------------+ -# | StreamRequestHandler | -# +-----------------------+ -# | -# V -# +------------------------+ -# | BaseHTTPRequestHandler | -# +------------------------+ -# | -# V -# +--------------------+ -# | WSGIRequestHandler | -# +--------------------+ -``` - -可以看出 WSGIRequestHandler 来自 BaseHTTPRequestHandler,后者来自 Python 标准库中的BaseHTTPServer模块,更上层的StreamRequestHandler和BaseRequestHandler来自 Python 标准库中的 SocketServer 模块。 - -这时候,三个模块之间的层次关系我们可以理清楚了。 - -``` -# M: -# +-----------------------------------------------+ -# | simple_server: WSGIServer, WSGIRequestHandler | -# | | -# +-----------------------------------------------+ -# | -# V -# +----------------------------------------------------+ -# | BaseHTTPServer: HTTPServer, BaseHTTPRequestHandler | -# +----------------------------------------------------+ -# | -# V -# +----------------------------------------------------+ -# | SocketServer: TCPServer,BaseSErver; | -# | StreamRequestHandler,BaseRequestHandler | -# +----------------------------------------------------+ -# -``` - -另外,这一模块中还有一个类,叫ServerHandler,它继承自 handlers 模块中的 SimpleHandler,我们再看看它的继承体系: - -``` -# M: -# +-------------+ -# | BaseHandler | -# +-------------+ -# | -# V -# +----------------+ -# | SimpleHandler | -# +----------------+ -# | -# V -# +---------------+ -# | ServerHandler | -# +---------------+ -# -``` - -好了,现在这个模块中的继承结构都弄清楚了,现在我们看看他们如何配合起来完成对客户端请求的处理。 - -首先,回顾simple_server涉及的模块: - -* WSGIServer -* WSGIRequestHandler -* ServerHandler -* demo_app -* make_server - -然后,把大脑清空,想一下要对客户端请求进行处理需要做什么 - -* 启动服务器,监听客户端请求 -* 请求来临,处理用户请求 - -我们看看这几个模块是如何配合完成这两个功能的 - -先看看 simple_server 模块的 `main` 部分,即执行 - -``` -python simple_server.py -``` - -时执行的内容: - -```python - httpd = make_server('', 8000, demo_app) - sa = httpd.socket.getsockname() - print "Serving HTTP on", sa[0], "port", sa[1], "..." - - # M: webbrowser provides a high-level interface to allow displaying Web-based documents - # to users. Under most circumstances - import webbrowser - webbrowser.open('http://localhost:8000/xyz?abc') - - httpd.handle_request() # serve one request, then exit -``` - -可以看出,主要完成的功能是: - -* 启动服务器 -* 模块用户请求 -* 处理用户请求 - -那么,我们主要关心的就是 `make_server` 和 `handle_request` 背后都发生了什么。 - -* make_server - -![make_server](/assets/blog-images/make_server.png) - -上图可以看出函数之间的调用关系,也可以看出 make_server 到 使用 socket 监听用户请求的过程。 - -* handle_request - -handle_request 的过程真正将各个模块联系起来了。 - -![handle_request](/assets/blog-images/handle_request.png) - -上图很清楚地说明了 由handle_request到demo_app的执行过程,把这个模块的各个部分联系起来。相信无需多言了。 - -### handlers - - -![handlers](/assets/blog-images/handlers.bmp) - -从图中可以看出handler模块中的四部分,它们其实是四个类,从基类到子类依次为: - -* BaseHandler -* SimpleHandler -* BaseCGIHandler -* CGIHandler - -最主要的实现在 BaseHandler中,其它几个类都是在基类基础上做了简单的实现。BaseHandler是不能直接使用的,因为有几个关键的地方没有实现,SimpleHandler是一个可以使用的简单实现。simple_server中的 ServerHandler类就是这个模块中SimpleHandler的子类。 - -在 BaseHandler中,最重要的是 `run` 函数: - -```python -def run(self, application): - """Invoke the application""" - # Note to self: don't move the close()! Asynchronous servers shouldn't - # call close() from finish_response(), so if you close() anywhere but - # the double-error branch here, you'll break asynchronous servers by - # prematurely closing. Async servers must return from 'run()' without - # closing if there might still be output to iterate over. - try: - self.setup_environ() - self.result = application(self.environ, self.start_response) - self.finish_response() - except: - try: - self.handle_error() - except: - # If we get an error handling an error, just give up already! - self.close() - raise # ...and let the actual server figure it out. - -``` - -它先设置好环境变量,再调用我们的 `demo_app`, 并通过 `finish_response` 将调用结果传送给客户端。如果处理过程遇到错误,转入 `handle_error` 处理。 - -此外,BaseHandler中还包含了 WSGI 中多次提到的 start_response,start_response 在 `demo_app` 中被调用,我们看看它的实现: - -```python - def start_response(self, status, headers,exc_info=None): - """'start_response()' callable as specified by PEP 333""" - - # M: - # exc_info: - # The exc_info argument, if supplied, must be a Python sys.exc_info() - # tuple. This argument should be supplied by the application only if - # start_response is being called by an error handler. - - # exc_info is the most recent exception catch in except clause - - # in error_output: - # start_response( - # self.error_status,self.error_headers[:],sys.exc_info()) - - # headers_sent: - # when send_headers is invoked, headers_sent = True - # when close is invoked, headers_sent = False - - if exc_info: - try: - if self.headers_sent: - # Re-raise original exception if headers sent - raise exc_info[0], exc_info[1], exc_info[2] - finally: - exc_info = None # avoid dangling circular ref - elif self.headers is not None: - raise AssertionError("Headers already set!") - - assert type(status) is StringType,"Status must be a string" - assert len(status)>=4,"Status must be at least 4 characters" - assert int(status[:3]),"Status message must begin w/3-digit code" - assert status[3]==" ", "Status message must have a space after code" - if __debug__: - for name,val in headers: - assert type(name) is StringType,"Header names must be strings" - assert type(val) is StringType,"Header values must be strings" - assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed" - - # M: set status and headers - - self.status = status - - # M: - # headers_class is Headers in module headers - self.headers = self.headers_class(headers) - - return self.write -``` - -可以看出,它先对参数进行了检查,然后再将headers 存储在成员变量中,这两点 WSGI标准中都有明确说明,需要检查参数,并且这一步只能将 headers存储起来,不能直接发送给客户端。也就是说,这个 `start_response` 还没有真正 response。 - -其实刚刚介绍 `run` 函数的时候已经提到了,真正的 response 在 `finish_response` 函数中: - -```python - def finish_response(self): - """Send any iterable data, then close self and the iterable - - Subclasses intended for use in asynchronous servers will - want to redefine this method, such that it sets up callbacks - in the event loop to iterate over the data, and to call - 'self.close()' once the response is finished. - """ - - # M: - # result_is_file: - # True if 'self.result' is an instance of 'self.wsgi_file_wrapper' - # finish_content: - # Ensure headers and content have both been sent - # close: - # Close the iterable (if needed) and reset all instance vars - if not self.result_is_file() or not self.sendfile(): - for data in self.result: - self.write(data) # send data by self.write - self.finish_content() - self.close() -``` - - -另外一个需要注意的地方是错误处理,在 `run` 函数中通过 `try/except` 捕获错误,错误处理使用了 `handle_error` 函数,WSGI中提到,`start_response` 函数的第三个参数 `exc_info` 会在错误处理的时候使用,我们来看看它是如何被使用的: - -```python - def handle_error(self): - """Log current error, and send error output to client if possible""" - self.log_exception(sys.exc_info()) - if not self.headers_sent: - self.result = self.error_output(self.environ, self.start_response) - self.finish_response() - # XXX else: attempt advanced recovery techniques for HTML or text? - - def error_output(self, environ, start_response): - """WSGI mini-app to create error output - - By default, this just uses the 'error_status', 'error_headers', - and 'error_body' attributes to generate an output page. It can - be overridden in a subclass to dynamically generate diagnostics, - choose an appropriate message for the user's preferred language, etc. - - Note, however, that it's not recommended from a security perspective to - spit out diagnostics to any old user; ideally, you should have to do - something special to enable diagnostic output, which is why we don't - include any here! - """ - - # M: - # sys.exc_info(): - # Return information about the most recent exception caught by an except - # clause in the current stack frame or in an older stack frame. - - start_response(self.error_status,self.error_headers[:],sys.exc_info()) - return [self.error_body] -``` - -看到了吧,`handle_error` 又调用了 `error_output` ,后者调用 `start_response`,并将其第三个参数设置为 `sys.exc_info()` ,这一点在 WSGI 中也有说明。 - -好了,这一部分我们就介绍到这里,不再深入过多的细节,毕竟源代码还是要自己亲自阅读的。剩下的三个部分不是核心问题,就是一些数据结构和工具函数,我们简单说一下。 - -### headers - -headers 模块图 - -这个模块是对HTTP 响应部分的头部设立的数据结构,实现了一个类似Python 中 dict的数据结构。可以看出,它实现了一些函数来支持一些运算符,例如 `__len__`, `__setitem__`, `__getitem__`, `__delitem__`, `__str__`, 另外,还实现了 dict 操作中的 `get`, `keys`, `values`函数。 - - -### util - - -![util](/assets/blog-images/util.bmp) - -这个模块主要就是一些有用的函数,用于处理URL, 环境变量。 - -### validate - - -![validate](/assets/blog-images/validate.bmp) - -这个模块主要是检查你对WSGI的实现,是否满足标准,包含三个部分: - -* validator -* Wrapper -* Check - -validator 调用后面两个部分来完成验证工作,可以看出Check部分对WSGI中规定的各个部分进行了检查。 - - -好了,就介绍到这里,这只是一个总结和导读,再次强调: - - 源代码还是要自己亲自阅读的 diff --git a/_posts/2014-01-26-wsgi-intro.md b/_posts/2014-01-26-wsgi-intro.md deleted file mode 100644 index 690196f..0000000 --- a/_posts/2014-01-26-wsgi-intro.md +++ /dev/null @@ -1,514 +0,0 @@ ---- -layout: default -title: WSGI 简介 -category: 技术 -comments: true ---- - -#WSGI 简介 - -## 背景 -Python Web 开发中,服务端程序可以分为两个部分,一是服务器程序,二是应用程序。前者负责把客户端请求接收,整理,后者负责具体的逻辑处理。为了方便应用程序的开发,我们把常用的功能封装起来,成为各种Web开发框架,例如 Django, Flask, Tornado。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。 - -这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。 - -Python Web开发中,这个标准就是 *The Web Server Gateway Interface*, 即 **WSGI**. 这个标准在[PEP 333](http://www.python.org/dev/peps/pep-0333/)中描述,后来,为了支持 Python 3.x, 并且修正一些问题,新的版本在[PEP 3333](http://www.python.org/dev/peps/pep-3333/)中描述。 - -## WSGI 是什么 -WSGI 是服务器程序与应用程序的一个约定,它规定了双方各自需要实现什么接口,提供什么功能,以便二者能够配合使用。 - -WSGI 不能规定的太复杂,否则对已有的服务器来说,实现起来会困难,不利于WSGI的普及。同时WSGI也不能规定的太多,例如cookie处理就没有在WSGI中规定,这是为了给框架最大的灵活性。要知道WSGI最终的目的是为了方便服务器与应用程序配合使用,而不是成为一个Web框架的标准。 - -另一方面,WSGI需要使得middleware(是中间件么?)易于实现。middleware处于服务器程序与应用程序之间,对服务器程序来说,它相当于应用程序,对应用程序来说,它相当于服务器程序。这样,对用户请求的处理,可以变成多个 middleware 叠加在一起,每个middleware实现不同的功能。请求从服务器来的时候,依次通过middleware,响应从应用程序返回的时候,反向通过层层middleware。我们可以方便地添加,替换middleware,以便对用户请求作出不同的处理。 - - -## WSGI 内容概要 -WSGI主要是对应用程序与服务器端的一些规定,所以,它的主要内容就分为两个部分。 - -## 应用程序 - -WSGI规定: - - 1. 应用程序需要是一个可调用的对象 - -在Python中: - -* 可以是函数 -* 可以是一个实例,它的类实现了`__call__`方法 -* 可以是一个类,这时候,用这个类生成实例的过程就相当于调用这个类 - -同时,WSGI规定: - - 2. 可调用对象接收两个参数 - -这样,如果这个对象是函数的话,它看起来要是这个样子: - -```python -# callable function -def application(environ, start_response): - pass -``` - -如果这个对象是一个类的话,它看起来是这个样子: - -```python -# callable class -class Application: - def __init__(self, environ, start_response): - pass -``` - -如果这个对象是一个类的实例,那么,这个类看起来是这个样子: - -```python -# callable object -class ApplicationObj: - def __call__(self, environ, start_response): - pass -``` - -最后,WSGI还规定: - - 3.可调用对象要返回一个值,这个值是可迭代的。 - -这样的话,前面的三个例子就变成: - -```python -HELLO_WORLD = b"Hello world!\n" - - -# callable function -def application(environ, start_response): - return [HELLO_WORLD] - - -# callable class -class Application: - def __init__(self, environ, start_response): - pass - - def __iter__(self): - yield HELLO_WORLD - - -# callable object -class ApplicationObj: - def __call__(self, environ, start_response): - return [HELLO_WORLD] -``` - -你可能会说,不是啊,我们平时写的web程序不是这样啊。 -比如如果使用web.py框架的话,一个典型的应用可能是这样的: - -```python -class hello: - def GET(self): - return 'Hello, world!' -``` - -这是由于框架已经把WSGI中规定的一些东西封装起来了,我们平时用框架时,看不到这些东西,只需要直接实现我们的逻辑,再返回一个值就好了。其它的东西框架帮我们做好了。这也是框架的价值所在,把常用的东西封装起来,让使用者只需要关注最重要的东西。 - -当然,**WSGI关于应用程序的规定不只这些**,但是现在,我们只需要知道这些就足够了。下面,再介绍服务器程序。 - -## 服务器程序 - -服务器程序会在每次客户端的请求传来时,调用我们写好的应用程序,并将处理好的结果返回给客户端。 - -WSGI规定: - - 4.服务器程序需要调用应用程序 - -服务器程序看起来大概是这个样子的: - -```python -def run(application): - environ = {} - - def start_response(status, response_headers, exc_info=None): - pass - - result = application(environ, start_response) - - def write(data): - pass - - for data in result: - write(data) -``` - -这里可以看出服务器程序是如何与应用程序配合完成用户请求的。 - -WSGI规定了应用程序需要一个可调用对象,有两个参数,返回一个可迭代对象。在服务器 -程序中,针对这几个规定,做了以下几件事: - -* 把应用程序需要的两个参数设置好 -* 调用应用程序 -* 迭代访问应用程序的返回结果,并将其传回客户端 - -你可以从中发现,应用程序需要的两个参数,一个是一个dict对象,一个是函数。它们到底有什么用呢?这都不是我们现在应该关心的,现在只需要知道,服务器程序大概做了什么事情就好了,后面,我们会深入讨论这些细节。 - -## middleware - -另外,有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, -不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。 - -middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。 - -其实无论是服务器程序,middleware 还是应用程序,都在服务端,为客户端提供服务,之所以把他们抽象成不同层,就是为了控制复杂度,使得每一次都不太复杂,各司其职。 - -下面,我们看看middleware大概是什么样子的。 - -```python -# URL Routing middleware -def urlrouting(url_app_mapping): - def midware_app(environ, start_response): - url = environ['PATH_INFO'] - app = url_app_mapping[url] - - result = app(environ, start_response) - - return result - - return midware_app -``` - -函数 `midware_app`就是一个简单的middleware:对服务器而言,它是一个应用程序,是一个可调用对象, 有两个参数,返回一个可调用对象。对应用程序而言,它是一个服务器,为应用程序提供了参数,并且调用了应用程序。 - -另外,这里的`urlrouting`函数,相当于一个函数生成器,你给它不同的 url-app 映射关系,它会生成相应的具有 url routing功能的 middleware。 - - -如果你仅仅想简单了解一下WSGI是什么,相信到这里,你差不多明白了,下面会介绍WSGI的细节,这些细节来自 [PEP3333](http://www.python.org/dev/peps/pep-3333/), 如果没有兴趣,到这里 **可以停止**了。 - -## WSGI详解 - -注意:以 点 开始的解释是WSGI规定 **必须满足** 的。 - -### 应用程序 - -* 应用程序是可调用对象 -* 可调用对象有两个位置参数 -所谓位置参数就是调用的时候,依靠位置来确定参数的语义,而不是参数名,也就是说服务 -器调用应用程序时,应该是这样: - -```python -application(env, start_response) -``` - -而不是这样: - -```python -application(start_response=start_response, environ=env) -``` - -所以,参数名其实是可以随便起的,只不过为了表义清楚,我们起了`environ` 和 `start_response`。 - -* 第一个参数environ是Python内置的dict对象,应用程序可以对这个参数任意修改。 -* environ参数必须包含 WSGI 需要的一些变量(详见后文) -也可以包含一些扩展参数,命名规范见后文 -* start_response参数是一个可调用对象。接受两个位置参数,一个可选参数。 -例如: -```python -start_response(status, response_headers, exc_info=None) -``` -status参数是状态码,例如 `200 OK` 。 - -response_headers参数是一个列表,列表项的形式为(header_name, header_value)。 - -exc_info参数在错误处理的时候使用。 - -status和response_headers的具体内容可以参考 [HTTP 协议 Response部分](http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6)。 - -* start_response必须返回一个可调用对象: `write(body_data)` -* 应用程序必须返回一个可迭代对象。 -* 应用程序不应假设返回的可迭代对象被遍历至终止,因为遍历过程可能出现错误。 -* 应用程序必须在第一次返回可迭代数据之前调用 start_response 方法。 -这是因为可迭代数据是 返回数据的 `body` 部分,在它返回之前,需要使用 `start_response` -返回 response_headers 数据。 - -### 服务器程序 - -* 服务器必须将可迭代对象的内容传递给客户端,可迭代对象会产生bytestrings,必须完全完成每个bytestring后才能请求下一个。 -* 假设result 为应用程序的返回的可迭代对象。如果len(result) 调用成功,那么result必须是可累积的。 -* 如果result有`close`方法,那么每次完成对请求的处理时,必须调用它,无论这次请求正常完成,还是遇到了错误。 -* 服务器程序禁止使用可迭代对象的其它属性,除非这个可迭代对象是一个特殊类的实例,这个类会被 `wsgi.file_wrapper` 定义。 - - -根据上述内容,我们的服务器程序看起来会是这个样子: - -```python -def run(application): - environ = {} - - # set environ - def write(data): - pass - - def start_response(status, response_headers, exc_info=None): - return write - - try: - result = application(environ, start_response) - finally: - if hasattr(result, 'close'): - result.close() - - if hasattr(result, '__len__'): - # result must be accumulated - pass - - - for data in result: - write(data) -``` - -应用程序看起来是这个样子: - -```python -HELLO_WORLD = b"Hello world!\n" - - -# callable function -def application(environ, start_response): - status = '200 OK' - response_headers = [('Content-type', 'text/plain')] - start_response(status, response_headers) - - return [HELLO_WORLD] - -``` - - -下面我们再详细介绍之前提到的一些数据结构 - -### environ 变量 - -environ 变量需要包含 CGI 环境变量,它们在[The Common Gateway Interface Specification](http://tools.ietf.org/html/draft-robinson-www-interface-00) 中定义,下面列出的变量**必须**包含在 enciron变量中: - -* REQUEST_METHOD -HTTP 请求方法,例如 "GET", "POST" -* SCRIPT_NAME -URL 路径的起始部分对应的应用程序对象,如果应用程序对象对应服务器的根,那么这个值可以为空字符串 -* PATH_INFO -URL 路径除了起始部分后的剩余部分,用于找到相应的应用程序对象,如果请求的路径就是根路径,这个值为空字符串 -* QUERY_STRING -URL路径中 `?` 后面的部分 -* CONTENT_TYPE -HTTP 请求中的 `Content-Type` 部分 -* CONTENT_LENGTH -HTTP 请求中的`Content-Lengh` 部分 -* SERVER_NAME, SERVER_PORT -与 SCRIPT_NAME,PATH_INFO 共同构成完整的 URL,它们永远不会为空。但是,如果 HTTP_HOST 存在的话,当构建 URL 时, HTTP_HOST优先于SERVER_NAME。 -* SERVER_PROTOCOL -客户端使用的协议,例如 "HTTP/1.0", "HTTP/1.1", 它决定了如何处理 HTTP 请求的头部。这个名字其实应该叫 `REQUEST_PROTOCOL`,因为它表示的是客户端请求的协议,而不是服务端响应的协议。但是为了和CGI兼容,我们只好叫这个名字了。 -*HTTP_ Variables -这个是一个系列的变量名,都以`HTTP`开头,对应客户端支持的HTTP请求的头部信息。 - -WSGI 有一个参考实现,叫 wsgiref,里面有一个示例,我们这里引用这个示例的结果,展现一下这些变量,以便有一个直观的体会,这个示例访问的 URL 为 `http://localhost:8000/xyz?abc` - -上面提到的变量值为: - -```python -REQUEST_METHOD = 'GET' -SCRIPT_NAME = '' -PATH_INFO = '/xyz' -QUERY_STRING = 'abc' -CONTENT_TYPE = 'text/plain' -CONTENT_LENGTH = '' -SERVER_NAME = 'minix-ubuntu-desktop' -SERVER_PORT = '8000' -SERVER_PROTOCOL = 'HTTP/1.1' - -HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -HTTP_ACCEPT_ENCODING = 'gzip,deflate,sdch' -HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2' -HTTP_CONNECTION = 'keep-alive' -HTTP_HOST = 'localhost:8000' -HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36' -``` - -另外,服务器还应该(非必须)提供尽可能多的CGI变量,如果支持SSL的话,还应该提供[Apache SSL 环境变量](http://www.modssl.org/docs/2.8/ssl_reference.html#ToC25)。 - -服务器程序应该在文档中对它提供的变量进行说明,应用程序应该检查它需要的变量是否存在。 - -除了 CGI 定义的变量外,服务器程序还可以包含和操作系统相关的环境变量,但这并非必须。 - -但是,下面列出的这些 WSGI 相关的变量必须要包含: - -* wsgi.version -值的形式为 (1, 0) 表示 WSGI 版本 1.0 -* wsgi.url_scheme -表示 url 的模式,例如 "https" 还是 "http" -* wsgi.input -输入流,HTTP请求的 body 部分可以从这里读取 -* wsgi.erros -输出流,如果出现错误,可以写往这里 -* wsgi.multithread -如果应用程序对象可以被同一进程中的另一线程同时调用,这个值为True -* wsgi.multiprocess -如果应用程序对象可以同时被另一个进程调用,这个值为True -* wsgi.run_once -如果服务器希望应用程序对象在包含它的进程中只被调用一次,那么这个值为True - -这些值在 wsgiref示例中的值为: - -```python -wsgi.errors = ', mode 'w' at 0xb735f0d0> -wsgi.file_wrapper = -wsgi.input = -wsgi.multiprocess = False -wsgi.multithread = True -wsgi.run_once = False -wsgi.url_scheme = 'http' -wsgi.version = (1, 0) -``` - -另外,environ中还可以包含服务器自己定义的一些变量,这些变量应该只包含 -* 小写字母 -* 数字 -* 点 -* 下划线 -* 独立的前缀 - -例如,mod_python定义的变量名应该为mod_python.var_name的形式。 - -## 输入流及错误流(Input and Error Streams) - -服务器程序提供的输入流及错误流必须包含以下方法: - -* read(size) -* readline() -* readlines(hint) -* __iter__() -* flush() -* write() -* writelines(seq) - -应用程序使用输入流对象及错误流对象时,只能使用这些方法,禁止使用其它方法,特别是, -禁止应用程序关闭这些流。 - -## start_response() -start_response是HTTP响应的开始,它的形式为: - -```python -start_response(status, response_headers, exc_info=None) -``` - -返回一个可调用对象,这个可调用对象形式为: - -```python -write(body_data) -``` - -status 表示 HTTP 状态码,例如 "200 OK", "404 Not Found",它们在 [RFC 2616](http://www.faqs.org/rfcs/rfc2616.html)中定义,status禁止包含控制字符。 - -response_headers 是一个列表,列表项是一个二元组: (header_name, heaer_value) , -每个 header_name 都必须是 [RFC 2616](http://www.faqs.org/rfcs/rfc2616.html) 4.2 节中定义的HTTP 头部名。header_value 禁止包含控制字符。 - -另外,服务器程序必须保证正确的headers 被返回给客户端,如果应用程序没有返回headers,服务器必须添加它。 - -应用程序和middleware禁止使用 HTTP/1.1 中的 "hop-by-hop"特性,以及其它可能影响客户端与服务器永久连接的特性。 - -start_response 被调用时,服务器应该检查 headers 中的错误,另外,禁止 start_response直接将 response_headers传递给客户端,它必须把它们存储起来,一直到应用程序第一次迭代返回一个非空数据后,才能将response_headers传递给客户端。这其实是在说,HTTP响应body部分必须有数据,不能只返回一个header。 - -start_response的第三个参数是一个可选参数,exc_info,它必须和Python的 sys.exc_info()返回的数据有相同类型。当处理请求的过程遇到错误时,这个参数会被设置,同时调用 start_response。如果提供了exc_info,但是HTTP headers 还没有输出,那么 start_response需要将当前存储的 HTTP response headers替换成一个新值。但是,如果提供了exc_info,同时 HTTP headers已经输出了,那么 start_response 必须 raise 一个 error。禁止应用程序处理 -start_response raise出的 exceptions,应该交给服务器程序处理。 - -当且仅当提供 exc_info参数时,start_response才可以被调用多于一次。换句话说,要是没提供这个参数,start_response在当前应用程序中调用后,禁止再调用。 - -为了避免循环引用,start_response实现时需要保证 exc_info在函数调用后不再包含引用。 -也就是说start_response用完 exc_info后,需要保证执行一句 - -```python -exc_info = None -``` - -这可以通过 try/finally实现。 - -## 处理 Content-Length Header - -如果应用程序支持 Content-Length,那么服务器程序传递的数据大小不应该超过 Content-Length,当发送了足够的数据后,应该停止迭代,或者 raise 一个 error。当然,如果应用程序返回的数据大小没有它指定的Content-Length那么多,那么服务器程序应该关闭连接,使用Log记录,或者报告错误。 - -如果应用程序不支持Content-Length,那么服务器程序应该选择一种方法处理这种情况。最简单的方法就是当响应完成后,关闭与客户端的连接。 - -### 缓冲与流(Buffering and Streaming) - -一般情况下,应用程序会把需要返回的数据放在缓冲区里,然后一次性发送出去。之前说的应用程序会返回一个可迭代对象,多数情况下,这个可迭代对象,都只有一个元素,这个元素包含了HTML内容。但是在有些情况下,数据太大了,无法一次性在内存中存储这些数据,所以就需要做成一个可迭代对象,每次迭代只发送一块数据。 - -禁止服务器程序延迟任何一块数据的传送,要么把一块数据完全传递给客户端,要么保证在产生下一块数据时,继续传递这一块数据。 - -## middleware 处理数据 -如果 middleware调用的应用程序产生了数据,那么middleware至少要产生一个数据,即使它想等数据积累到一定程度再返回,它也需要产生一个空的bytestring。 -注意,这也意味着只要middleware调用的应用程序产生了一个可迭代对象,middleware也必须返回一个可迭代对象。 -同时,禁止middleware使用可调用对象write传递数据,write是middleware调用的应用程序使用的。 - -### write 可调用对象 - -一些已经存在的应用程序框架使用了write函数或方法传递数据,并且没有使用缓冲区。不幸的是,根据WSGI中的要求,应用程序需要返回可迭代对象,这样就无法实现这些API,为了允许这些API 继续使用,WSGI要求 start_response 返回一个 write 可调用对象,这样应用程序就能使用这个 write 了。 - -但是,如果能避免使用这个 write,最好避免使用,这是为兼容以前的应用程序而设计的。这个write的参数是HTTP response body的一部分,这意味着在write()返回前,必须保证传给它的数据已经完全被传送了,或者已经放在缓冲区了。 - -应用程序必须返回一个可迭代对象,即使它使用write产生HTTP response body。 - -这里可以发现,有两中传递数据的方式,一种是直接使用write传递,一种是应用程序返回可迭代对象后,再将这个可迭代对象传递,如果同时使用这两种方式,前者的数据必须在后者之前传递。 - -### Unicode - -HTTP 不支持 Unicode, 所有编码/解码都必须由应用程序完成,所有传递给或者来自server的字符串都必须是 `str` 或者 `bytes`类型,而不是`unicode`。 - -注意传递给start_response的数据,其编码都必须遵循 [RFC 2616](http://www.faqs.org/rfcs/rfc2616.html), 即使用 ISO-8859-1 或者 [RFC 2047](http://www.faqs.org/rfcs/rfc2047.html) MIME 编码。 - -WSGI 中据说的 `bytestrings` , 在Python3中指 `bytes`,在以前的Python版本中,指 -`str`。 - -### 错误处理(Error Handling) - -应用程序应该捕获它们自己的错误,internal erros, 并且将相关错误信息返回给浏览器。 -WSGI 提供了一种错误处理的方式,这就是之前提到的 exc_info参数。下面是 PEP 3333中提供的一段示例: - -```python -try: - # regular application code here - status = "200 Froody" - response_headers = [("content-type", "text/plain")] - start_response(status, response_headers) - return ["normal body goes here"] -except: - # XXX should trap runtime issues like MemoryError, KeyboardInterrupt - # in a separate handler before this bare 'except:'... - status = "500 Oops" - response_headers = [("content-type", "text/plain")] - start_response(status, response_headers, sys.exc_info()) - return ["error body goes here"] -``` - -当出现异常时,start_response的exc_info参数被设置成 sys.exc_info(),这个函数会返回当前的异常。 - - -### HTTP 1.1 Expect/Continue - -如果服务器程序要实现 HTTP 1.1,那么它必须提供对 HTTP 1.1 `expect/continue`机制的支持。 - -## 其它内容 - -在 PEP 3333 中,还包含了其它内容,例如: - -* HTTP 特性 -* 线程支持 -* 实现时需要注意的地方:包括,扩展API,应用程序配置,URL重建等 - -这里就不作过多介绍了。 - -## 扩展阅读 -这篇文章主要是我阅读 PEP 3333 后的理解和记录,有些地方可能没有理解正确或者没有写全,下面提供一些资源供扩展阅读。 - -* [PEP 3333](http://www.python.org/dev/peps/pep-3333/) -不解释 -* [WSGI org](http://wsgi.readthedocs.org/en/latest/) -看起来好像官方网站的样子,覆盖了关于WSGI的方方面面,包含学习资源,支持WSGI的框架列表,服务器列表,应用程序列表,middleware和库等等。 -* [wsgiref](https://pypi.python.org/pypi/wsgiref) -WSGI的参考实现,阅读源代码后有利于对WSGI的理解。我在GitHub上有自己阅读后的注释版本,并且作了一些图,有需要可以看这里:[wsgiref 源代码阅读](https://github.com/minixalpha/SourceLearning/tree/master/wsgiref-0.1.2) - -另外,还有一些文章介绍了一些基本概念和一些有用的实例,非常不错。 - -* [Wsgi研究](http://blog.kenshinx.me/blog/wsgi-research/) -* [wsgi初探](http://linluxiang.iteye.com/blog/799163) diff --git a/_posts/2014-02-15-github-jekyll-markdown.md b/_posts/2014-02-15-github-jekyll-markdown.md deleted file mode 100644 index 027a499..0000000 --- a/_posts/2014-02-15-github-jekyll-markdown.md +++ /dev/null @@ -1,442 +0,0 @@ ---- -layout: default -title: 使用 GitHub, Jekyll, Markdown 打造自己的博客 -category: 工具 -comments: true ---- - -# 使用 GitHub, Jekyll 打造自己的独立博客 - -[GitHub](https://github.com/)是一个代码托管网站,现在很多开源项目都放在GitHub上。 -利用GitHub,可以让全球各地的程序员们一起协作开发。GitHub 提供了一种功能,叫 -[GitHub Pages](https://help.github.com/categories/20/articles), 利用这个功能,我 -们可以为项目建立网站,当然,这也意味着我们可以通过 GitHub Pages 建立自己的网站。 - -[Jekyll](http://jekyllrb.com/)是一个简单的,针对博客设计的静态网站生成器。使用 -GitHub 和 Jekyll,我们可以打造自己的独立博客,你可以自由地定制网站的风格,并且这 -一切都是免费的。 - -这是我在GitHub上自己建立的[博客](http://minixalpha.github.io/) -及[源代码](https://github.com/minixalpha/minixalpha.github.io) -(两个分支),在下文的讲解中,你可以随时查看博客的源代码,以便有直观的认识。 - -网站截图: - -![blog_snapshot](/assets/blog-images/blog_snapshot.png) - - -## 入门指引 - -GitHub Pages 的 [主页](http://pages.github.com/) 提供了一个简单的入门指引,阅读并 -操作一下,会有一个直观简单的认识。 - -阮一峰的文章《[搭建一个免费的,无限流量的Blog----github Pages和Jekyll入门] -(http://www.ruanyifeng.com/blog/2012/08/blogging_with_jekyll.html)》是使用 -GitHub 和 Jekyll 搭建独立博客非常好的入门文章,**强烈建议先阅读并操作一遍**。 - -## 建立自己的博客 - -在学习完阮一峰同学的文章后,你就已经有能力搭建自己的独立博客了,但是这个博客 -只有最基本的功能,并且也不好看。这时候,你面临几个选择: - - 1. 完全自己定制博客 - 2. 找一份框架,修改后使用 - 3. 从GitHub上fork别人的博客代码,在其中添加自己的文章 - -如果选择 2, 那么 [jekyll-bootstrap](http://jekyllbootstrap.com/)是一个选择。 -如果选择 3, 那么自己Google一下 `github.io 博客` 能找到不少博客,去fork,然后修改一下就好。 -如果选择 1, 那么可以好好看看后文的内容。 - -## GitHub + Jekyll 工作机制 - -* 机制一 - -简单地说,你在 GitHub 上有一个账号,名为`username`(任意), -有一个项目,名为 `username.github.io`(固定格式,username与账号名一致), -项目分支名为 `master`(固定),这个分支有着类似下面的 -目录结构: - -``` -. -├── index.html -├── _config.yml -├── assets -│   ├── blog-images -│   ├── css -│   ├── fonts -│   ├── images -│   └── javascripts -├── _includes -├── _layouts -├── _plugins -├── _posts -└── _site -``` - -这样,当你访问 `http://username.github.io/`时,GitHub 会使用 Jekyll 解析 -用户 `username`名下的`username.github.io`项目中,分支为`master` -的源代码,为你构建一个静态网站,并将生成的 `index.html` 展示给你。 - -关于这个目录更多的内容,我们还不需要关心,如果你好奇心比较重,可以先看 -后文`源代码`一节。 - -看完上面的解释,你可能会有一些疑问,因为按照上面的说法,一个用户只能有一个 -网站,那我有很多项目,每个项目都需要一个项目网站,该怎么办呢?另外,在阮一峰 -同学的文章中,特别提到,分支名应该为 `gh-pages`,这又是怎么回事呢? - -原来,GitHub认为,一个GitHub账号对应一个用户或者一个组织,GitHub会 -给这个用户分配一个域名:`username.github.io`,当用户访问这个域名时, -GitHub会去解析`username`用户下,`username.github.io`项目的`master`分支, -这与我们之前的描述一致。 -另外,GitHub还为每个项目提供了域名,例如,你有一个项目名为`blog`, -GitHub为这个项目提供的域名为`username.github.io/blog`, -当你访问这个域名时,GitHub会去解析`username`用户下,`blog`项目的`gh-pages` -分支。 - -所以,要搭建自己的博客,你可以选择建立名为 `username.github.io`的项目, -在`master`分支下存放网站源代码,也可以选择建立名为 `blog` 的项目,在 -`gh-pages`分支下存放网站源代码。 - -GitHub 的 Help 文档中的 [User, Organization and Project Pages] -(https://help.github.com/articles/user-organization-and-project-pages)对此有 -详细的描述。 - -* 机制二 - -Jekyll 提供了插件功能,在网站源代码目录下,有一个名为 `_plugins`的目录, -你可以将一些插件放进去,这样,Jekyll在解析网站源代码时,就会运行你的插件, -这样插件是 Ruby 写成的。可以为Jekyll添加功能,例如,Jekyll默认是不提供分类 -页面的,你可以写一个插件,根据文章内容生成分类页面。如果没有插件,你只能每 -次写文章,添加分类时,为每个分类手动写 HTML 页面。 - -在本地运行 Jekyll 时,这些插件会自动被调用,但是GitHub在解析网站源代码时, -出于安全考虑,会开启安全模式,禁用这些插件。我们既想用这些插件,又想用 -GitHub,怎么办呢怎么办呢? - -GitHub还为我们提供了更一种解析网站的方式,那就是直接上传最终的静态网页, -这样,我们可以在本地使用 Jeklly 把网站解析出来,然后再上传到 GitHub上, -这就使得我们既使用了插件,又使用了 GitHub。在上文的目录结构中,有一个 -名为 `_site` 的目录,这个就是Jeklly在本地解析时最终生成的静态网站,我们 -把其中的内容上传到 GitHub 的项目中就可以了。例如,我在GitHub上的网站, -既解析后的 `_site` 目录,大概是这样的: - -``` -. - -├── index.html -├── 2013 -├── 2014 -├── assets -├── categories -├── page2 -├── page3 -├── page4 -├── 工具 -├── 思想 -├── 技术 -└── 源代码阅读 -``` - -其中的 `categories`,`2013`, `2014` 目录就是分类插件和归档插件帮助我生成的, -我只要把这个目录下的内容上传到 GitHub 相应的项目分支中就可以了。这样,你 -访问 `username.github.io`时,GitHub就不解析了,直接把`index.html`返回给你了。 - -## 工作流 - -关于 git 和 jekyll 的安装与基本使用,这里就不多说了。 - -* 工作流一 - -如果你不使用插件,那么只需要维护一个分支就好: - - - username/username.github.io 的 master 分支 - - username/blog 的 gh-pages 分支 - -其中 `username` 是你的 GitHub 帐号。 - -你需要在本地维护一份网站源代码,添加新文章后,使用 jekyll 在本地测试一下, -没有问题后,commit 到 GitHub 上的相应分支中就可以了。 - -* 工作流二 - -如果你需要使用插件,那么需要维护两个分支,一个是网站的源代码分支,另一个 -是 Jeklly 解析源代码后生成的静态网站。 - -例如,我的源代码分支名为 `master`,静态网站分支名为 `gh-pages`。平时写博客时, -首先在 master 分支下,添加新文章,然后本地使用 jekyll build 将添加文章后的网站 -解析一次,这时 `_site` 目录下就有新网站的静态代码了。然后把这个目录下的所有内容 -复制到 `gh-pages` 分支下。这个过程,可以写一个 Makefile,每次添加文章后 make 一下, -就自动将文章发布到 GitHub 上。 - - -Makefile 内容如下: - -``` - -deploy: - git checkout source - jekyll build - git add -A - git commit -m "update source" - cp -r _site/ /tmp/ - git checkout master - rm -r ./* - cp -r /tmp/_site/* ./ - git add -A - git commit -m "deploy blog" - git push origin master - git checkout source - echo "deploy succeed" - git push origin source - echo "push source" -``` - -下面的内容涉及源代码,如果需要进一步学习,或者有问题,可以在 -[Jeklly 官网](http://jekyllrb.com/)上找到更详细的解释,或者在评论中留言。 - -## 源代码 - -再来看一下这个目录结构: - -``` -. -├── _config.yml -├── index.html -├── assets -│   ├── blog-images -│   ├── css -│   ├── fonts -│   ├── images -│   └── javascripts -├── _includes -├── _layouts -├── _plugins -├── _posts -└── _site -``` - -* _config.yml - -这是针对 Jekyll 的[配置文件](http://jekyllrb.com/docs/configuration/), -决定了 Jekyll 如何解析网站的源代码,下面是一个示例: - -``` -baseurl: /StrayBirds -markdown: redcarpet -safe: false -pygments: true -excerpt_separator: "\n\n\n" -paginate: 5 -``` - -我的网站建立在 `StrayBirds` 项目中,所以 `baseurl` 设置成 `StrayBirds`, -我的文章采用 Markdown 格式写成,可以指定 Markdown 的解析器 `redcarpet`。 -另外,安全模式需要关闭,以便 Jekyll 解析时会运行插件。 -`pygments` 可以使得Jekyll解析文章中源代码时加入特殊标记,例如指定代码类型, -这可以被很多 javascript 代码高度库使用。 -`excerpt_separator` 指定了一个摘要分割符号,这样 Jekyll 可以在解析文章时, -将文章的提要提取出来。 -paginate 指定了一页有几篇文章,页数太多时,我们可以将文章列表分页,我们在 -后文还会提到。 - -* _layouts - -这个目录存放着一些网页模板文件,为网站所有网页提供一个基本模板,这样 -每个网页只需要关心自己的内容就好,其它的都由模板决定。例如,这个目录下的 -default.html 文件: - -```html -{% raw %} - - - - - {{ page.title }} - - -
    -
    - - - -
    -{{ content }} -
    - -
    -
    - - -{% endraw %} - -``` - -可以看出,这个文件就是所有页面共有的东西,每个页面的具体内容会被填充在 -`{{ content }}` 中,注意这个 content 两边的标记,这是一种叫 -[liquid](https://github.com/Shopify/liquid) 的标记语言。 -另外,还有那个 `{{ page.title }}` ,其中 `page` 表示引用 `default.html`的 -那个页面,这个页面的 `title` 值会在 `page` 相应页面中被设置,例如 -下面的 `index.html` 文件,开头部分就设置了 `title` 值。 - -* index.html - -这是网站的首页,访问 `http://username.github.io` 时,会指向 -`http://username.github.io/index.html`,我们看一下基本内容: - -```html ---- -layout: default -title: 首页 ---- - -{% raw %} -
      - {% for post in site.posts %} - {{ post.title }}
      - {{ post.date | date: "%F" }}
      - {{ post.category }}
      - {{ post.excerpt }} - {% endfor %} -{% endraw %} -
    -``` - -注意,文件开头的描述,我们称之为 [front-matter](http://jekyllrb.com/docs/frontmatter/), -是对当前文件的一种描述,这里 -设置的变量可以在解析时被引用,例如这里的 `layout`就会告诉 Jekyll, 生成 `index.html` -文件时,去 `_layouts` 目录下找 `default.html` 文件,然后把当前文件解析后,添加到 -`default.html` 的 `content` 部分,组成最终的 `index.html` 文件。还有`title` 设置好的 -值,会在 `default.html` 中通过 `page.title` 被引用。 - -文件的主体部分遍历了站点的所有文章,并将他们显示出来,这些语法都是 `liquid` 语法, -其中的变量,例如 `site`, 由 Jekyll 设置我们只需要引用就可以了。而 `post` 中的变量, -如 `post.title`, `post.category` 是由 `post` 文件中的 front-matter 决定,后面马上就会看到。 - -* _posts - -这个目录存放我们的所有博客文章,他们的名字有统一的格式: - -``` -YEAR-MONTH-DAY-title.MARKUP -``` - -例如,2014-02-15-github-jeklly.md,这个文件名会被解析,前面的 `index.html` 中, -`post.date` 的值就由这里文件名中的日期而来。下面,我们看看一篇文章的内容示例: - - -``` ---- -layout: default -title: 使用 Markdown -category: 工具 -comments: true ---- - -# 为什么使用 Markdown - -* 看上去不错 -* 既然看上去不错,为什么不试试呢 - - -# 如何使用 Markdown -``` - -可以看出,文章的 front-matter 部分设置了多项值,以后可以通过类似 `post.title`, -`post.category` 的方式引用这些些,另外,`layout`部分的值和之前解释的一样, -文件的内容会被填充到 `_layouts/default.html` 文件的 `content` 变量中。 - -另外,文章中 `为什么不试试呢`之后的有三个不可见的 `\n`,它决定了这三个 `\n` -之前的内容会被放在 `post.excerpt` 变量中,供其它文件使用。 - - -* _includes - -这个文件中,存放着一些模块文件,例如 `categories.ext`,其它文件可以通过 - -``` -{% raw %} -{% include categories.ext %} -{% endraw %} -``` - -来引用这个文件的内容,方便代码模块化和重用。我的博客 -[主页](http://minixalpha.github.io/)上的 -分类,归档,这些模块的代码都是通过这种方式引用的。 - -* _plugins - -这个文件中存放一些Ruby插件, 例如 `gen_categories.rb`,这些文件会在 Jekyll -解析网站源代码时被执行。下一节讲述的就是插件。 - -* _site - -Jekyll 解析整个网站源代码后,会将最终的静态网站源代码放在这里 - - -## 插件 - -插件使用 Ruby 写成,放在 _plugins 目录下,有些 Jekyll 没有的功能,又不能 -手动添加,因为页面的内容会随着文章日期类别的不同而不同,例如分类功能和归档功能, -这时,就需要使用插件自动生成一些页面和目录。 - -* 分类 -我的分类插件使用的是 [jekyll-category-archive-plugin] -(https://github.com/shigeya/jekyll-category-archive-plugin/tree/master/_plugins), -它会根据网站文章的分类信息,为每个类别生成一个页面。 - -使用方法是,把 _plugins/category_archive_plugin.rb 放在 _plugins 目录下, -把 _layouts/category_archive.html 放在 _layouts 目录下, -这样,这个插件会在Jekyll解析网站时,生成相应categories目录,目录下是各个分类, -每个分类下都有一个 `index.html` 文件,这个文件是根据模板文件 category_archive.html -生成的,例如: - -``` -_site/categories/ -├── 工具 -│   └── index.html -├── 思想 -│   └── index.html -├── 技术 -│   └── index.html -└── 源代码阅读 - └── index.html -``` - -然后,你就可以通过 `http://username.github.io/blog/categories/工具/` 访问 -`工具`类下的 `index.html` 文件。 - -* 归档 -我的归档插件使用的是 [jekyll-monthly-archive-plugin] -(https://github.com/shigeya/jekyll-monthly-archive-plugin),它会根据网站 -_posts目录下的文章日期,为每个月生成一个页面。 - -使用方法同上。注意,这个插件在 jekyll-1.4.2 中可能会出错,在 jekyll-1.2.0 -中没有错误。 - -## 组件 - -* 分页 - -当文章很多时,就需要使用分页功能,在 Jekyll 官网上提供了一种 -[实现](http://jekyllrb.com/docs/pagination/),把相应代码放在 -主页上,然后在 `_config.yml` 中设置 `paginate` 值就行了。 - -* 评论 - -评论功能需要使用外挂,我使用的是 [DISQUS](http://disqus.com/), 注册 -之后,将评论区的一段代码放在你需要使用评论功能的页面上, -然后,通过在页面的 front-matter 部分使用 - -``` -comments: true -``` - -启用评论。 - -评论区截图: - -![blog_comments_snapshot](/assets/blog-images/blog_comments_snapshot.png) - - -基本的内容就介绍到这里,任何问题,欢迎留言。 diff --git a/_posts/2014-02-20-read-mactalk.md b/_posts/2014-02-20-read-mactalk.md deleted file mode 100644 index 5e94a73..0000000 --- a/_posts/2014-02-20-read-mactalk.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -layout: default -title: 读《MacTalk 人生元编程》 -category: 读书 -comments: true ---- - -# 读 《MacTalk 人生元编程》 - -今天看了一本书,叫《MacTalk 人生元编程》,讲述了Mac,编程,科技与人文还有职场等内容,书是在作者的微信公众号基础上写成的,挺有趣的,感兴趣的可以买来看看。 - -我记得小时候不管是读书,看电影,还是春游,老师都要求写读后感,当时的感觉已经忘了,不过小孩子谁愿意写这种玩意?而现在看着书就不断地会有感慨,这就叫有感而发吧。 - -先说读书 - -从书中可以明显看出,作者虽然是一个70后的老程序员,但是是非常喜欢读书的。我是非常喜欢热爱读书的人的,阅读量大的人明显与其它人拉开一个档次,无论是知识面还是对事物的看法。我这么多年没什么悔恨的事,唯一有一点悔恨的事就是年少的时候没有意识到读书的重要性。 - -读一本书给我一种什么感觉呢? - - - -想像一下,你在一个黑暗的屋子里,你以为这个屋子就是整个世界了。突然有一天,你摸到一把钥匙,你拿着这把钥匙打开了一扇窗户,然后,有光照进来。你突然发现,原来外面还有一个世界。顺着这道光,你找到了更多的钥匙,打开了更多的窗户。世界以一种更完整的姿态展现在你的眼前。 - -当然,这时候,你对世界的看法也已经变了,你开始怀疑,有光的地方就是整个世界吗,也许光后面,还有一个世界呢。 - -这就是读书给我的感受。 - -再说回忆 - -作者回忆了自己当前年轻的时候,在洪恩软件工作的点点滴滴。洪恩软件大家可能不太熟悉,不过作者在书中也提到,自己后来因为种种原因离开了洪恩软件,而坚持留下来的人,后来组建了网络游戏公司,这就是后来的完美时空。 - -提到洪恩软件,一下子勾起了我的回忆。 - -大概是小学五六年级的时候,家里买了一台电脑,还附带了一套学习电脑的软件,我一直想不起来出这软件的公司叫什么了,现在终于想起来了,这套软件就是洪恩出的,叫《开天辟地》。跟着这套软件,我开始学怎么用鼠标,怎么用键盘,怎么用word, excel, powerpoint,当然当时还介绍了一点VB的知识,可惜当时我并没有对编程展现出特别的兴趣。后来这个电脑完全就是我打游戏的工具了。 - -现在想起来,一个非常遗憾的地方就是家里一直都没有联网,一直到2008年因为上大学才学会上网。 - -当时我爸说,等你高中毕业了,就给你联网!后来我高中毕业了,家里把电脑卖了。。。。 - -所以虽然家里很早就有了电脑,其实我对计算机一直都是不了解的,电脑中了病毒,都得花50块钱请人来重新安装系统。记得我高考报志愿的时候,是这么想的,像计算机这种专业,就是学学word,excel嘛,这是以后人人都会的,学这玩意干啥?不过不知道为什么,我当时还是把信息安全和计算机专业放在了六个专业选择的最后两个,大概是因为分数合适,或者其它专业实在不合适吧。总之,这意思就是说,实在不行了,咱就学计算机好了。没想到最后真录取到了信息安全专业,想一想,幸亏当年考的分数低啊,幸亏最后写了这两个专业啊。 - -有人问我什么是缘份,这就叫缘份。(其实没人问,我是凑字来着) - -后来呢? - -后来我就上大学了,开始学会上网了。这已经是2008年了,想一想,自己真够后知后觉的。 - -前几天在知乎上看到一个问题,“你在大学期间学过的最有实用价值的课程是什么,收获了什么?”。我回想了一下自己从小学到大学的经历,想一想,真的没有什么课程让我感觉到收获很大。我曾经不只一次吐槽过我们大学的教育。虽然很多人可能觉得整个中国的教育都不怎么样,还有什么可比的。但是,通过我的观察,在中国,名校和普通学校的教育,差距还是非常大的。单说一点,名校的同学用的是世界一流的原版教材,我们用的是什么?说句难听的,上一个普通大学,只要学校老师不误导你,你就回家烧高香吧。 - -好在,时代不同了,我们有了互联网。 - -自从上大学以来,我看的书,学的东西,受到的启发,几乎全部来自互联网。今天说的这本《MacTalk 人生元编程》,也是在微博上看到大家推荐,才读的。这本书是这个月刚上市的,如果没有互联网,我怎么会知道会有这样一本书上市了。我现在的习惯就是,在网上看到有一本好书,就马上上豆瓣,搜出来,加到“想读”列表里,然后慢慢读它们。 - -另一个需要提到的事,就是电子商务了。这本 《MacTalk 人生元编程》我上午下了单,下午就送到了。方便和快捷意味着高频率地使用。如果不是买书这么方便,即使我知道了这本书,那也得找一个周末,还得是一个没事的周末,风和日丽的周末,跑到书店里去买,并且书店还不一定有。 - -上大学的时候,那时候经常在校内网上看到一些好的文章,给了我非常多的启发。无论是生活方式,还是对待事物的看法,还是价值观,都深深地影响着我。后来校内网改名人人网了,再后来就到了现在,人人网上几乎没有什么好看的文章了。上了研究生后,知乎和微博出现在我生活里,虽然说有些后知后觉,但是从这里我还是收获了不少东西。去年,我专门花时间,在知乎的各个领域里看精华内容,收获不少。知乎和其它网站不同的地方在于,这里有很多专业的人聚集,提供了高质量的内容,从事实方面和方法论方面,都给我很多启发。虽然现在一些贡献高质量答案的人因为各种原因离开知乎了,但是它对于我的影响还是不小的。微博对我来说,有点像新闻源了,许多最新的消息都是最先从微博上看到的。另外就是看大家在微博上对一些问题的讨论。 - -无论是知乎还是微博,一个相似的地方就是可以很大地拉近我们和那些优秀的人的距离,虽然他们可能感受不到我的存在,但是看着他们讲述问题,讨论问题,我也会得到一些东西。这些东西可能只凭借我自己很难想到,它们不能给我具体的知识,但是却给了我一些启发,就像那些书一样,为我推开了一扇窗户。 - -我可以很清楚地感受到,互联网深深地改变着我。所以,尽管这个世界上还有很多愚蠢的人和事,但是我仍然觉得这是一个好的时代,也很庆幸自己生活在这样一个时代。 - -并且,也希望有一天,能给这个时代带来些美好。 diff --git a/_posts/2014-02-21-no-useless-insistance.markdown b/_posts/2014-02-21-no-useless-insistance.markdown deleted file mode 100644 index 3225a1c..0000000 --- a/_posts/2014-02-21-no-useless-insistance.markdown +++ /dev/null @@ -1,38 +0,0 @@ ---- -layout: default -title: 不要做无谓的坚持和幻想的努力 -category: 生活 -comments: true ---- - -# 不要做无谓的坚持和幻想的努力 - -有时候,你觉得自己一直在坚持和努力,可是从没问问自己这样的坚持和努力有用吗? - -其实你就是一只温水里的青蛙,还觉得自己一直都在坚持游泳呢。 - -最近我有了一个非常明确和重要的启发,就是不要做无谓的坚持和幻想的努力。 - -我在一个背单词的网站上背了471天单词,不长,不短,再坚持下去我也完全没有问题。但是,这真的就叫坚持,就叫努力吗? - - -前一段时间,我果断停掉了这种背单词的方式。我看了一本学英语的书,受到了一些启发。然后测试了一下自己的词汇量,只有不到四千,以英语为母语的国家三岁小孩的水平。回想一下,从小学到研究生,英语没停过,这也算坚持学英语了吧。可是到现在,听,听不懂,写,写不顺,看,看不明白,这样的坚持有用吗?这算坚持吗? - -做事情不是这么做的,你要想完成一件事情,需要的不是这些低效,无用的坚持,毕竟,*我们的目标是把事情做成,而不是做一个努力和坚持的姿态给自己看,这是麻醉自己,纵容自己*。 - -要完成一件事情,我们需要什么呢? - -需要强烈的目标意识,知道自己想要的到底是什么,要是不知道,就*努力*去想,去找,去问,问自己,问别人,问为什么。 - -需要*努力*寻找好的方法,而不是用一个低效,不费力的方法苦苦坚持。 - -需要建立一个让自己*容易坚持*的计划,而不是逆天而行,天天很痛苦,又没有效率,还陶醉在自己无谓的坚持中。 - -*最最重要*的是,需要有*及时的反馈*,确切地明白,自己按目前的计划执行,是进步了,退步了,还是原地踏步,根据自己的状态,及时调整计划,调整方法,然后再反馈。要学会为自己调整算法,调整参数,为自己做性能调优。 - -上面说的是整体的方法,下面说其中一个极为重要的点,是关于设定目标的。例如我想让自己变得强壮起来,每天做20个俯卧撑,坚持三年,最后能达到效果吗?根本不能,因为我做这件事几乎花不了什么力气。我必须把目标设定到这样一个状态:*我可以做到,但是,又不是那么容易做到,需要费很大的力气才能做到,需要让自己痛苦一些才能做到,这样坚持一段时间,如果这段时间后,我可以很轻松做到了,那么就把目标再提高一些,再恢复那种让自己痛苦的状态*。这就是传说中的*刻意练习*的本质,无论做什么事,想要提高都会经历这样一个过程。你可以在搜索引擎上搜索一下“刻意练习”,看看这意味着什么,这里只推荐这一篇《[怎样练习一万小时](http://www.geekonomics10000.com/519)》。 - -道理虽然是这样,但是“刻意练习”的过程其实是充满困难的。因为没有经验的你并不知道应该把目标设定成什么样,设定高了,怎么也做不到,设定低了,怎么都不会进步,这都会严重地打击你。即使目标设定合适,那种一直处于痛苦和不适的状态,也会让你感觉非常难受,能不断地度过这样的状态,才叫有效的坚持。 - -坚持和努力并不令人尊敬,令人尊敬的是把事情做成了,坚持和努力只不过是一种手段,千万不要把他们本身当作最终的目标。 - diff --git a/_posts/2014-02-22-gcc-intro.markdown b/_posts/2014-02-22-gcc-intro.markdown deleted file mode 100644 index 92116a8..0000000 --- a/_posts/2014-02-22-gcc-intro.markdown +++ /dev/null @@ -1,142 +0,0 @@ ---- -layout: default -title: GCC 的基本使用 -categories: [工具, C语言] -comments: true ---- - -# GCC 的基本使用 - -[GCC](http://gcc.gnu.org/) 是一款非常著名的编译器,支持诸如 C, C++, Java在内的多种程序设计语言,并且提供了许多选项,以支持用户不同程度的需求,例如查看编译中间结果,指定语言标准,指定优化程度等等 ,下面的内容就介绍这些最重要的功能。 - - -首先查看一下 GCC 的版本,有时候可能版本不同,编译的功能和结果就不同。 -[这里](http://gcc.gnu.org/onlinedocs/) 有不同版本的手册。 - -## 查看版本 - $ gcc --version - gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 - Copyright (C) 2011 Free Software Foundation, Inc. - This is free software; see the source for copying conditions. There is NO - warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - - -## 编译过程 -### 一次编译 -如果想直接得到可执行文件,最简单的是这样: - - gcc test.c -o test - -### 分段编译 -有时候,我们可能需要看到编译的中间结果,比如查看汇编代码,没有经过链接的二进制文件等等,这时候可以这样: - -* 源文件->预处理->中间文件 - - gcc -E test.c -o test.i - -* 中间文件->编译->汇编文件 - - gcc -S test.i -o test.s - -* 汇编文件->转换->二进制代码 - - gcc -c test.s -o test.o - -* 二进制代码->链接->可执行文件 - - gcc test.o -o test - -## 设定C语言标准 -不同的标准对于语言特性的支持是不一样的,所以一定要关注标准,别写了半天程序连自己是在什么标准下写的都不知道。 - -关于C语言的标准,首先要提到的就是 ANSI C, 1989年批准,即C89,1990年批准成为ISO标准,即C90或ISO/IEC 9899:1990。C89和C90其实是一样的。可以使用 `-ansi` 或者 `-std=c89` 或者 `-std=c90` 或者`-std=iso9899:1990` 来指定,例如: - - $ gcc -ansi test.c -o test - $ gcc -std=c89 test.c -o test - $ gcc -std=iso9899:1990 test.c -o test - -在1999年,新版本的C语言标准发布,即 ISO/IEC 9899:1999,江湖人称C99。C99引入了一些新特性,例如:inline 函数,long long int 类型,变长数组,单行注释等等 。GCC对此标准的支持并不完整,但许多新特性已经可以支持。可以使用`-std=c99` 或者 `-std=iso9899:1999` 来指定。 - -下一个版本的C语言标准是C1X,正在制定中,GCC只支持其中一部分特性,可以使用`-std=c1x`来指定。 - -另外,GCC对各个C语言标准都提供了一些扩展,可以使用 `-std=gnu89` 或者 `-std=gnu99` 或者 `-std=gnu1x`来指定。 - -如果没有指定标准,默认情况下 GCC 使用 `-std=gnu90`。 - -更多关于GCC支持的语言标准的描述,可以在这里找到:[Language Standards Supported by GCC](http://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Standards.html) -。 - -关于各个标准的简介可以在 Wikipedia 的 C (programming language) 的 [History](http://en.wikipedia.org/wiki/C_(programming_language)#History) 中找到,另外强烈建议把这个词条完整地阅读一遍,可以对C语言有一个全方位的概览。 - -ISO对于各个标准的介绍可以在 [iso9899](http://www.iso-9899.info/wiki/The_Standard#C89_.2F_C90_.2F_C95)中找到。 - -## 警告 - -GCC 的警告信息有时候会给我们一些非常重要的提示,防止我们写出错误的程序,最常用的选项是 `-Wall`,意思是打开全部的警告信息。如果没有错误,仅仅是得到了警告,程序不会停止编译,因为警告不一定是错误的。但是如果你对程序要求比较高,想让编译器在出现警告时停止编译,那么可以在`-Wall`基础上,使用 `-Werror` 选项,这样GCC会把警告当成错误来处理。 - -例如,我们有这样一个程序: - -```c -#include - -int main(void) { - int foo = 2; - - if (foo = 1) { - printf("hello, world\n"); - } - return 0; -} -``` - -注意 `=` 和 `==` 的区别,我们试试警告选项: - - $ gcc -Wall -ansi test.c -o test - test.c: In function ‘main’: - test.c:6:5: warning: suggest parentheses around assignment used as truth value [-Wparentheses] - -可以看出,GCC对我们提出了警告。我们再加上 `-Werror` 试试: - - $ gcc -Wall -Werror -ansi test.c -o test - test.c: In function ‘main’: - test.c:6:5: error: suggest parentheses around assignment used as truth value [- Werror=parentheses] - cc1: all warnings being treated as errors - -这一次,GCC把上一次的警告当成错误来处理了。 - -关于 GCC Warning 的更多信息可以在 GCC文档的 [Warnign Options](http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html)中找到。 - -## 调试信息 - -有时候我们的程序需要使用 GDB 来调试,就需要在编译时加入调试信息,可以通过 `-g` 选项来加入: - - $ gcc -g -ansi test.c -o test - -然后通过 GDB 来调试: - - $ gdb test - GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04 - Copyright (C) 2012 Free Software Foundation, Inc. - License GPLv3+: GNU GPL version 3 or later - This is free software: you are free to change and redistribute it. - There is NO WARRANTY, to the extent permitted by law. Type "show copying" - and "show warranty" for details. - This GDB was configured as "i686-linux-gnu". - For bug reporting instructions, please see: - ... - Reading symbols from /home/minix/Project/C/language/test...done. - -可以看到最后一句,gdb 从 test中读取到了 symbols。 - -更多关于GCC调试的描述可以在 GCC 文档的 [Debugging Options](http://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html)中找到。 - -## 优化 - -GCC 可以对程序提供不同程度的优化,`-O0` 不提供任何优化,这也是默认的情况,从 `-O1` 到 `-O3`提供越来越多的优化,例如: - - $ gcc -O1 test.c -o test - $ gcc -O3 test.c -o test - -更多关于优化的信息可以在 GCC 文档的 [Optimize Options](http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html)中找到。 - -好了,就讲到这里。 diff --git a/_posts/2014-02-22-learn-english-1.markdown b/_posts/2014-02-22-learn-english-1.markdown deleted file mode 100644 index 5db4f9b..0000000 --- a/_posts/2014-02-22-learn-english-1.markdown +++ /dev/null @@ -1,66 +0,0 @@ ---- -layout: default -title: 学习英语第一周 -category: 英语 -comments: true ---- - -# 学习英语第一周 - -这是英语学习计划的第一周,完成了预定的目标,包括阅读英文原著和学习音标。 - -## 阅读英文原著 -出于对武侠小说的热爱,选择了 The Three Musketeers 作为第一本英文原著,下面是过去一周的时间表。 - -| Book | Time | Total Time | Progress | -|:----:| :----:| :----:| :----:| -|The Three Musketeers| 2014.02.16 15:00 ~ 17:30 18:30 ~ 21:00 21:30 ~ 22:30 | 6h |43/625| -|The Three Musketeers| 2014.02.17 19:00 ~ 23:00 |4h |83/625| -|The Three Musketeers| 2014.02.18 19:00 ~ 20:00 21:00 ~ 24:00 |4h| 123/625 | -|The Three Musketeers| 2014.02.19 18:50 ~ 22:50| 4h| 163/625| -|The Three Musketeers| 2014.02.20 18:30 ~ 20:00 21:30 ~ 23:00 |3h| 203/625 | -|The Three Musketeers| 2014.02.21 18:30 ~ 21:00| 3h |243/625| -|The Three Musketeers| 2014.02.22 15:30 - 17:30 18:30-21:30 |5h| 283/625| - -按照计划,每天40页,每两页只可以查一个单词,已经按时完成了。可以看出,除了最后一天晚节不保(也可能是我记错时间了),整体上看,仅仅是在七天内,阅读的速度就有一定改变。第一天足足用了六个小时,现在还记得当时的感觉,几乎看不懂,脑子里只有一个大概的模糊的印象,知道故事大体上发生了什么。后面几天渐入佳境,虽然有的地方还是看不明白,比如生词太多,词组不知道什么意思,句子太长,但是总体来看,还是在进步的。 - - - -这个学习方法让我明白了一个道理,要想养成一个习惯,就用一周时间去培养,并且需要高强度的练习,一周后就适应了,不用那么高强度,自然可以坚持下来。目前我的感觉是没有问题,可以坚持下来,并且已经对阅读英文原著有了兴趣,已经入手了哈里波特系列,读完这本书就开始阅读。 - -另外,这种阅读原著的方式,初步感觉对学习英语是有很大好处的,因为一些单词,句子会经常重复出现,所以很容易记住,之前背单词都是脱离语境,即使记住了这个单词,遇到具体的语境就会感觉这个单词见过,但是想不起来什么意思了。 - -## 学习音标 -音标是跟着《赖世雄美语音标》学的,每天早上起来学40分钟到一个小时,下面是时间表: - -| 内容 | 时间 | -|:--:|:--:| -|英文字母|2014.02.12 08:00 ~ 08:30 | -| 元音两个 | 2014.02.13 08:00 ~ 08:30 | -| 元音两个 | 2014.02.14 08:00 ~ 09:20 | -| 元音两个 | 2014.02.15 08:00 ~ 09:20 | -| 元音两个 | 2014.02.16 14:00 ~ 14:50 | -| 元音两个 | 2014.02.17 08:00 ~ 09:00 | -| 元音两个 | 2014.02.18 08:00 ~ 09:00 | -| 元音两个 | 2014.02.19 08:00 ~ 09:00 | -| 元音三个 | 2014.02.20 09:00 ~ 09:40 | -| 元音四个 | 2014.02.21 09:00 ~ 09:40 | -| 元音总复习 | 2014.02.22 09:00 ~ 10:00 | - -上面的统计数目可能不太对,没有刻意记每天学了多少,我就不具体算了,不过元音是学完了。 - -总体来说,收获是极大的,这确实是一个非常好的学习材料,这是一份广播式的录音,会有讲解,而不是冷冰冰干巴巴的音标句子录音,声音后面是一个有感情的人,时不时还开开玩笑,而不是像机器一样进行发音。另外,专门针对中国人发音容易发错的地方进行反复地讲解,印象非常深刻。 - -这一周就总结到这里。 - -下一周的目标是: - -* 学完辅音,每天大概要学习4个。 -* 每天读20页The Three Musketeers。 - -年度目标是: - - * 英语听说,按《把你的英语用起来》中初中高级的材料学习一遍。 - * 英文阅读,读10本英文原著。 - -下周见。 diff --git a/_posts/2014-02-28-terminal-shell.markdown b/_posts/2014-02-28-terminal-shell.markdown deleted file mode 100644 index 718dcb3..0000000 --- a/_posts/2014-02-28-terminal-shell.markdown +++ /dev/null @@ -1,64 +0,0 @@ ---- -layout: default -title: 控制台,终端,tty,shell等概念的区别 -category: 工具 -comments: true ---- - -# 控制台,终端,tty,shell等概念的区别 - -使用linux已经有一段时间,却一直弄不明白这几个概念之间的区别。虽然一直在用,但是很多概念都感觉模糊不清,这样不上不下的状态实在令人不爽。下面就澄清一下这些概念。 - -这些概念本身有着非常浓厚的历史气息,随着时代的发展,他们的含义也在发生改变,它们有些已经失去了最初的含义,但是它们的名字却被保留了下来。 - - -## 控制台(Console) -控制台([Console](http://en.wikipedia.org/wiki/System_console))是物理设备,用于输入输出,它直接连接在计算机上,是计算机系统的一部分。计算机输出的信息会显示在控制台上,例如BIOS的输出,内核的输出。 - -## 终端(Terminal) -终端([Terminal](http://en.wikipedia.org/wiki/Computer_terminal))也是一台物理设备,只用于输入输出,本身没有强大的计算能力。一台计算机只有一个控制台,在计算资源紧张的时代,人们想共享一台计算机,可以通过终端连接到计算机上,将指令输入终端,终端传送给计算机,计算机完成指令后,将输出传送给终端,终端将结果显示给用户。 - -## 虚拟控制台(Virtual Console),虚拟终端(Virtual Terminal) -虚拟控制台([Virtual Console](http://en.wikipedia.org/wiki/Virtual_console))和虚拟终端是一样的。我们只有一台终端(物理设备),这是我们与计算机之间的用户接口。假如有一天,我们想拥有多个用户接口,那么,一方面我们可以增加终端数目(物理设备),另一方面,还可以在同一台终端(物理设备)上虚拟出多个终端,它们之间互相不影响,至少看起来互相不影响。这些终端就是虚拟终端。 - -在Ubuntu中,我们按下Ctrl+Alt+Fx时,会进入第x个虚拟终端,一共有七个虚拟终端,其中第七个虚拟终端,就是我们默认使用的图形用户界面。 - -## 终端模拟器(Terminal Emulator) -我们知道,终端是一种物理设备,而终端模拟器([Terminal Emulator](http://en.wikipedia.org/wiki/Terminal_emulator)),是一个程序,这些程序用来模拟物理终端。图形用户界面中的终端模拟器一般称为终端窗口(Terminal Window),我们在Ubuntu下打开的gnome-terminal就属于此类。 - -## tty -tty的全称是[TeleTYpewriter](http://en.wikipedia.org/wiki/Teletypewriter),这就是早期的终端(物理设备),它们用于向计算机发送数据,并将计算机的返回结果打印出来。显示器出现后,终端不再将结果打印出来,而是显示在显示器上。但是tty的名字还是保留了下来。 - -在Ubuntu中,我们按下Ctrl+Alt+F1时,会进入第1个虚拟终端,你可以看到屏幕上方显示的tty1。 - -## shell -[shell](http://en.wikipedia.org/wiki/Shell_(computing)) 和之前说的几个概念截然不同,之前的几个概念都是与计算机的输入输出相关的,而shell是和内核相关的。内核为上层的应用提供了很多服务,shell在内核的上层,在应用程序的下层。例如,你写了一个 hello world 程序,你并不用显式地创建一个进程来运行你的程序,你把写好的程序交给shell就行了,由shell负责为你的程序创建进程。 - -我们在终端模拟器中输入命令时,终端模拟器本身并不解释执行这些命令,它只负责输入输出,真正解释执行这些命令的,是shell。 - -我们平时使用的sh, bash, csh是shell的不同实现。 - -* sh -sh这个概念本身就有岐义,它可以指shell程序的名字,也代表了shell的实现。 - - [Thompson shell](http://en.wikipedia.org/wiki/Thompson_shell)是第一个Unix shell,由 Ken Thompso于1971年在Unix第一版本中引入,shell的程序名即为sh。[Bourne shell](http://en.wikipedia.org/wiki/Bourne_shell)作为Thompson shell的替代,由 Stephen Bourne于1977年在Unix第七版中引入,它的程序名也是sh。Bourne shell不仅仅是一个命令解释器,更作为一种编程语言,提供了Thompson shell不具备的程序控制功能,并随着 Brian W. Kernighan 和 Rob Pike 的 *The UNIX Programming Environment*的出版而名声大噪。 - -* csh -[csh](http://en.wikipedia.org/wiki/C_shell)全称为 C Shell,由 Bill Joy在70年代晚期完成,那时候他还是加州伯克利大学的研究生。tcsh是csh的升级版。与sh不同,csh的shell脚本,语法接近于C语言。 - -* bash -[bash](http://en.wikipedia.org/wiki/Bash_(Unix_shell))是由 Brian Fox为GNU项目开发的自由软件,作为Bourne shell的替代品,于1989年发布。是Linux和Mac OS X的默认shell。bash的命令语法是Bourne shell命令语法的超集,从ksh和csh借鉴了一些思想。 - - -好了,就写到这里,上面的内容是我参考维基百科后写下的,**不保证完全正确**, -下面还提供了一些资料,如果有兴趣可以阅读一下。 - -## 扩展阅读 - -1. [What is the exact difference between a 'terminal', a 'shell', a 'tty' and a 'console'?](http://unix.stackexchange.com/questions/4126/what-is-the-exact-difference-between-a-terminal-a-shell-a-tty-and-a-con) - -2. [shell,bash,zsh,console,terminal到底是什么意思,它们之间又是什么关系?](http://www.linuxsir.org/bbs/thread362001.html?pageon=1#2059206) - -3. [shell、控制台、终端的区别](http://blog.csdn.net/caomiao2006/article/details/8791775) - -4. [Why is a virtual terminal “virtual”, and what/why/where is the “real” terminal?](http://askubuntu.com/questions/14284/why-is-a-virtual-terminal-virtual-and-what-why-where-is-the-real-terminal) diff --git a/_posts/2014-03-02-learn-english-2.markdown b/_posts/2014-03-02-learn-english-2.markdown deleted file mode 100644 index 82de25a..0000000 --- a/_posts/2014-03-02-learn-english-2.markdown +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: default -title: 学习英语第二周 -category: 英语 -comments: true ---- - - -# 学习英语第二周 - -## 阅读英文原著 -这周按照预定计划,把每天的阅读量降低了一半,每天20页左右。下面是时间表。 - -| Book | Time | Total Time | Progress | -|:----:| :----:| :----:| :----:| -|The Three Musketeers| 2014.02.23 20:20 - 22:20 | 2h |308/625| -|The Three Musketeers| 2014.02.24 19:00 - 21:00 |2h |330/625| -|The Three Musketeers| 2014.02.25 18:40 - 20:10 |1.5h| 355/625 | -|The Three Musketeers| 2014.02.26 18:40 - 20:10| 1.5h| 375/625| -|The Three Musketeers| 2014.02.27 13:00-14:00 18:40 - 19:40 |2h| 399/625 | -|The Three Musketeers| 2014.02.28 13:00-13:45 19:00-19:30 19:50-20:15| 1.6h |425/625| -|The Three Musketeers| 2014.03.01 14:00-16:00 |2h| 444/625| -|The Three Musketeers| 2014.03.02 13:00-15:00 |2h| 465/625| - -这周阅读起来没什么压力了,下周还按这个节奏,努力一下没准就能读完了。 - -## 学习音标 -这周的任务是学习辅音,到这周结束,已经完成了任务。下面是时间表。 - - - -| 内容 | 时间 | -|:--:|:--:| -|元音复习 |2014.02.23 08:40-09:00 | -| 辅音四个 | 2014.02.24 08:30-09:10 | -| 辅音四个 复习辅音四个 | 2014.02.25 08:40-09:40 | -| 辅音四个 复习辅音四个 | 2014.02.26 08:40-09:40 | -| 辅音四个 复习辅音四个 | 2014.02.27 08:40-09:30 | -| 辅音四个 复习辅音四个 | 2014.02.28 08:40-09:30 | -| 辅音四个 复习辅音四个 | 2014.03.01 08:40-09:40 | -|辅音四个 复习辅音,字母 | 2014.03.02 09:40-10:40 | - -到这周结束,已经把原音和辅音学完了,总的来说,进步还是挺大的,会注意到以前注意不到的发音。 - -这一周就总结到这里。 - -下一周的目标是: - -* 学完连读,把元音复习一遍 -* 每天读20页The Three Musketeers。 - -年度目标是: - - * 英语听说,按《把你的英语用起来》中初中高级的材料学习一遍。 - * 英文阅读,读10本英文原著。 - -下周见。 diff --git a/_posts/2014-03-06-think-int-in-c.markdown b/_posts/2014-03-06-think-int-in-c.markdown deleted file mode 100644 index 572c202..0000000 --- a/_posts/2014-03-06-think-int-in-c.markdown +++ /dev/null @@ -1,159 +0,0 @@ ---- -layout: default -title: C语言中的int类型的范围是由什么决定的 -categories: [计算机系统, C语言] -comments: true ---- - -# C语言中的int类型的范围是由什么决定的 - -在 K&R 经典教材 *The C Programming Language* 的2.2节中,对 int 类型是这样描述的 - -> an integer, typically reflecting the natural size of integers on the host machine - -意思是反映了机器整数类型的 *natural size*,可是, - -> 这个 natural size 又是什么意思呢? - -书中后来在谈到 short, int, long 的关系时,又说,这些类型由编译器根据机器自由选择合适的大小,但是 short 和 int 至少 16 位,long 至少 32 位。 - -这里的问题是 - -> 编译器是根据什么决定类型大小呢? - -后面书中又提到,这些类型啊,在``中都有,我就在ubuntu下查看了 `/usr/include/limits.h`,里面确实提到 - -```c -/* Minimum and maximum values a `signed int' can hold. */ -# define INT_MIN (-INT_MAX - 1) -# define INT_MAX 2147483647 -``` - -但是,这也是一种定义,还是没有说出为什么,我现在想知道的是 - -> 为什么 - - - -于是,我想起了那些年扫过的 《深入理解计算机系统》,英文名叫 *Computer Systems: A Programmer's Perspective*,速查之! - -在2.1节的开头提到,字节(byte)是最小可寻址单位,大多数计算机使用8位的块。 -啊,8位,那位又是什么呢?嗯,位是一种存储结构,一个位只能存储0或者1。 - -后面2.1.2节中提到 - -> 每台计算机都有一个字长(word size),指明了整数和指针数据的 nominal size。 - -指针是什么,指针就是内存中的地址啊,假如字长为w位,那么地址的数目就是2^w个啊,那一个地址代表多大内存呢? - -前面说了,字节(byte)是最小可寻址单位,所以一个地址代表一个字节。当字长是w位时,地址数目2^w个,共有2^w个字节的内存空间。 - -如果计算机字长为32,即传说中的32位计算机,那么它可以表示的内存空间就是 2^32 个字节,这就是传说中的4G啊! - -现在我们是由字长32位,也就是整数的大小32位,推出了内存空间4G。我现在在想: - -> 是不是一开始是决定内存空间是4G,所以才定下了字长32位的规矩,由此,机器的natural size是32位, 所以,编译器才将C语言中int类型才是32位呢? - -可是我没有证据啊! - -没有证据就尝试推理一下吧。 - -我们知道32位机器是由16位机器扩展来的,那为什么要扩展机器字长呢?这个问题原因之一,我们刚才已经解释过了,如果不扩展,那么机器最大寻址空间就比较小,即使我给你一个大内存,你也用不上啊。这可能这也今天我们从32位转到64位的原因吧。 - -所以,现在我们明白了,由于我们想要更大的内存地址空间,所以就将字长从16位提升为32位,而字长代表着指针和整数类型的大小,所以最终整数类型就是32位了。 - -不过这里还有不少问题。 - -字长这东西只是个抽象的概念,方便我们描述机器的一些属性,暂时不谈。 - -先说指针。对于机器来说,哪里有什么指针的概念,指针是C语言中的东西,编译成汇编后就没指针这个概念了。但是,指针表示的是内存的地址,而内存的地址又和机器中的什么部件相关呢? - -再说整数。到汇编这一层,整数的概念还存在吗?整数的概念应该是和汇编中的算术指令相关,那么算术指令又和机器中的什么部件相关呢? - -最后,指针是表示内存地址啊,我们有了更大内存,那么内存地址需要更长的位来表示是可以理解的,可是,这关你整数什么事啊?我内存地址32位,整数16位不行吗? - -其实,总的问题就是 - -> 字长都与机器的什么部件相关 - -要解释这个问题,我们发现自己不由自主地来到了《深入理解计算机系统》的第四章“处理器体系结构”。 - -这一章以一种叫Y86的处理器介绍了处理器体系结构的方方面面。首先介绍了寄存器,寄存器是一种存储部件,存储什么?存储信息,存储信息用来做什么呢?用来计算。我们在C语言中使用一个简单的加法计算,在处理器这一层,就需要使用寄存器来帮助我们计算。我们把一个简单的C语言编译成汇编看看。 - -```c -/* test_add.c */ -#include - -int main(void) { - int a = 1; - int b = 2; - int c = a + b; - - return 0; -} - -``` - -使用 GCC 编译一下 - -``` - gcc -S test_add.c -o test_add.s -``` - - -然后查看一下主要代码。 - -```gas - movl $1, -12(%ebp) - movl $2, -8(%ebp) - - movl -8(%ebp), %eax - movl -12(%ebp), %edx - - addl %edx, %eax - movl %eax, -4(%ebp) -``` - -其中的 ebp eax edx 就是寄存器。 - -可以看出,数据先放到栈里,再从栈里放到寄存器里,然后再进行加法运算,最后再从寄存器里把结果放回栈里。 - -下面的图是书中给出的一个处理器的抽象视图: - -![SEQ_view](/assets/blog-images/SEQ.png) - -栈是什么?栈是一种抽象概念,这里的栈就是指内存。 - -书里说了,在32位计算机中,这些寄存器的大小就是32位。可见, - -> 字长与寄存器大小一样 - -除此之外,我们可以看到,需要计算的时候,movl 指令将数据从内存中放到寄存器里,由于内存和寄存器是不同的部件,所以需要一个部件来传递数据,这种部件叫做数据总线。 - -寄存器的大小与字长相同,那么这种数据总线每次能传送的数据也应该与字长相同,所以: - -> 字长与数据总线宽度一样 - -另外,再想像一下,你想要从内存中取数据出来,总要告诉内存你取的是哪个地址的数据吧,所以,“地址”这个数据也是要从某个地方传送到内存的。只要传递,就需要有部件支持,这个部件叫做地址总线,地址总线传递地址,地址大小与字长一样,那么,我们可以知道: - -> 字长与地址总线宽度一样 - -好了,到了这里,我们的分析就差不多了,总结一下: - -我们由C语言中int类型的大小,得到了字长这个概念,又从字长这个概念寻找了与其相关的一些机器部件的属性。到现在为此,与字长相关的有: - -* int 类型 -* 指针(即内存地址) -* 寄存器 -* 数据总线 -* 地址总线 - -在 Wikipedia 的 [Word(computer_architecture)](http://en.wikipedia.org/wiki/Word_(computer_architecture)#Table_of_word_sizes)词条中,我们可以看到自1837年以来,一系列计算机体系结构中与字长相关的一些属性的变化。 - -我们再想想,为什么要将这么多种部件都设置成相同长度?我想,可能是因为计算机内部实在太复杂了,各个部件之间需要紧密地配合,共同完成复杂的任务。尤其是数据,需要在各个部件之间传递,如果这些部件之间大小不统一,就会增加机器的复杂度,由于,我们将这些部件大小尽可能统一,进而提出字长这种概念来描述计算机的重要性质。 - -到这里,我们再想一下,字长这个概念和这么多部件相关,那么确定字长多大应该不仅仅与内存大小有关系。比如字长代表寄存器的大小,寄存器与机器的运算直接相关,字长变大后,每次能参与计算的值也相应变大,以前我们计算两个很大的数的和时,可能需要动用好几个寄存器,现在咱字长大了,寄存器也大了,只需要两个寄存器就可以了。 - -由此可见,字长的确定是一个综合的考量,代表着计算机计算,存储能力的全面提升。 - -文章结束了,思考永不停止。 diff --git a/_posts/2014-03-06-think-of-data-race-free.markdown b/_posts/2014-03-06-think-of-data-race-free.markdown deleted file mode 100644 index d374b91..0000000 --- a/_posts/2014-03-06-think-of-data-race-free.markdown +++ /dev/null @@ -1,61 +0,0 @@ ---- -layout: default -title: 对Data Race Free 的理解 -categories: [科研] -comments: true ---- - - -# 对Data Race Free 的理解 - -## Data Race Free 的动机 -Data Race Free 是对多线程程序 **同步程度** 的一种描述,假如你的多线程程序的同步程度满足 DRF 的要求,那么,你的程序会有这样一个好处: - - 程序在弱一致性模型下执行,执行的结果与在SC模型下执行一样 - -这意味着,程序员在写程序时,可以按SC模型来推断程序的执行。而程序在底层运行时,可以享受弱一致性模型带来的种种优化措施。 - -## Data Race Free 具体内容 - -DRF 要求多线程程序中不能有冲突的操作。 - -什么是冲突的操作呢? - -冲突的操作是指:两个操作来自不同线程,操作同一地址,至少有一个是写操作。 - -如何让冲突的操作不冲突呢? - -需要使用同步操作将冲突的操作隔离开。 - -为什么要用同步操作将冲突的操作隔离开呢? - - - - -因为如果不隔离开,程序在弱一致性模型下执行的结果就和在SC模型下执行的结果不一样了。这意味着如果你用SC模型推断程序执行结果,而程序又运行在弱一致性模型下,那么程序的真正结果可能和你推断的不一样。 - -那么,又为什么:如果不隔离开,程序在弱一致性模型下执行结果就和SC模型下不一样了呢? - -这个问题其实在问:为什么隔离会使得程序在弱一致性模型下执行结果与SC模型下执行结果一致? - -这个问题用一句话来回答是:隔离使得我们可以找到所有操作的一种全序,而这种全序正是SC所需要的。 - - - -从上图可以看出,同步操作将相互冲突的操作隔离开,这种隔离为原本无序的多线程程序添加了一些顺序: - -* 同步操作之间有顺序了 -* 同步操作与其之前的所有操作之间有顺序了 -* 同步操作与其之后的所有操作之间有顺序了 - -这些顺序保证了程序在弱一致性模型下与在SC模型下执行结果一样。 - -另外,我们还发现,有些操作之间并没有顺序保证,这正是DRF的优势所在,这些无须顺序保证的操作可以在弱一致性模型下得到优化,同时他们的无序又不会使得执行结果与SC下有任何不同。 - -如果我们想找一个所有操作之间的全序,只需要在这些无须保证顺序的操作中随便选择一个顺序,另外,还需要保证那些因为同步而添加的顺序关系。如此构成一个全序。这个全序正是SC模型所需要的。 - -由此,我们也明白了DRF的精髓: - - 只保证必要的顺序,不保证不必要的顺序。 - -所谓必要是指,保证这些顺序就可以使得程序在弱一致性模型下的执行结果与SC模型下的执行结果一致,不保证就不行。 diff --git a/_posts/2014-03-07-history-of-data-race-free.markdown b/_posts/2014-03-07-history-of-data-race-free.markdown deleted file mode 100644 index f6184ed..0000000 --- a/_posts/2014-03-07-history-of-data-race-free.markdown +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: default -title: Data Race Free 的前世今生 -categories: [科研] -comments: true ---- - - -# Data Race Free 的前世今生 - -Data Race Free 是多线程程序是非常重要的概念,因为Java 和 C++的内存模型都是基于 Data Race Free 的,这篇文章将介绍这个概念的由来,另一篇文章《Data Race Free的理解》介绍它的主要思想。 - -事情要追溯到遥远的1979年, Lamport 在他的著名论文 *How to make a multiprocessor computer that correctly executes multiprocess programs* 中提出了今后在内存模型领域被广泛使用的概念 :*sequential consistency*,即顺序一致性。这篇文章告诉我们,你要做一台多处理器的计算机,需要满足什么条件,才能保证程序的正确性。当然,这里的程序跑在不同处理器上,共享同一块内存。虽然现在不说多处理器了,都说多核,多线程,但是问题的本质是没有变的。就是多个执行单元一起完成一个任务,并且通过共享存储单元的方式通信,在这种情况下,底层的系统需要提供什么样的支持,才能保证计算的结果和程序员的预期是一样的。 - - -7年后的1986年,Dubois, Scheurich , Briggs 三人在论文 *Memory access buffering in multiprocessors* 中对 Lamport 的工作进行了扩展,他们提出了一种框架,用于分析共享内存的多处理器系统中的一致性问题。文中引入了三个 states,以此为基础提出了strong ordering的概念,并说明了他与Lamport提出的sequential consistency是一致的。但是,strong ordering对内存操作的限制太强了,对系统性能是一个阻碍,所以,他们又提出了 weak ordering的概念,以此提高系统性能。 满足weak ordering的系统并不是 sequential consistency的,程序员们需要自己去声明同步变量,以保证程序的正确性。 - - - - -又过了4年,到了1990年,Adve大妈出手了,大妈现在是内存模型这个领域的权威,Java和C++内存模型的确立都有大妈的功劳,而Java和C++内存模型中相当重要的Data Race Free 概念就是Adve大妈在这一年提出的。 - -在这篇名为 *Weak ordering—a new definition*的文章中,Adve对Dubois等人提出的Weak Ordering进行了新的定义,并做出了一些修改以便进一步提高系统的性能。 -新的定义出于这样一种想法,程序员习惯使用 sequential consistency来推断程序的运行结果,而底层的系统要想取得更高的性能,又不能使用sequential consistency内存模型来运行程序。那么 - -> 如何使得程序员可以使用sequential consistency推断程序结果,底层的实现又可以进行种种优化呢? - -解决方案是:对程序本身进行足够的同步。 - -这种内存模型保证:如果你的程序进行了足够的同步,那么在我的weak oerding内存模型上运行,我可以保证结果和你在sequential consistency模型下运行的结果一样。 - -这样一来,程序员保证程序正确同步,就可以使用sequential consistency推断程序结果,而底层又可以灵活地进行各种优化,提高系统性能。 - -这里有一个关键问题: - -> 什么叫“足够的同步” - -Adve提出了Data Race Free的概念,也就是说,你的程序要是满足Data Race Free的条件了,你的同步就足够了,“足够”的意思就是说,这程序在weak ordering上跑和在sequential consistency上跑,结果是一样一样的~ - -Adve对weak ordering给出的新定义是: - -> Hardware is weakly ordered with respect to a synchronization model if -and only if it appears sequentially consistent to all software that obey the synchronization model. - -这里的synchronization model的**一种实现方式**,就是 Data Race Free。 - -Data Race Free 后来成为了 Java 和 C++ 内存模型的基础。 - - -Java 的内存模型最早出现在1995年,但是自1997年起,这一内存模型被发现了许多严重的错误和缺陷,它阻碍了很多优化措施,对程序的安全性也没有足够的保证。2001年[JSR 133](https://www.jcp.org/en/jsr/detail?id=133)被确立下来,由William Pugh领导,专家组的成员包括了Adve,Doug Lea, William Pugh等。2004年,JSR 133最终版本发布。2005年,Manson Jeremy, William Pugh, 和 Sarita V. Adve 一同发表了论文 *The Java memory model*,描述了最新的Java内存模型,这一内存模型在Java 5.0中引入,一直沿用至今。 - -Java 内存模型的关键是:如果多线程程序满足Data Race Free,那么内存模型保证程序执行结果和sequentially consistent模型下一样。另外,Java 内存模型的复杂之处还在于,为了保证程序的安全性,即使多线程程序不满足Data Race Free,我们也要对它进行一定程度的限制,这种限制必须恰到好处,太强会阻碍合理的优化,太弱保证不了程序的安全性。 - - -三年后的2008年,Hans-J. Boehm 和 Sarita V. Adve 一同发表了文章 *Foundations of the C++ concurrency memory model*,描述了C++内存模型的基础,这一内存模型为C++ 11标准中的线程提供了明确的语义。 - -C++内存模型与的关键在于:如果多线程程序满足Data Race Free,那么内存模型保证程序执行结果和sequentially consistent模型下一样。与Java内存模型不同,对于那些不满足 Data Race Free的多线程程序,C++内存模型不对其结果提供任何保证。另外,C++内存模型提供了一些特性用以实现不同的内存模型。 diff --git a/_posts/2014-03-09-learn-english-3.markdown b/_posts/2014-03-09-learn-english-3.markdown deleted file mode 100644 index 00ec8ec..0000000 --- a/_posts/2014-03-09-learn-english-3.markdown +++ /dev/null @@ -1,56 +0,0 @@ ---- -layout: default -title: 学习英语第三周 -category: 英语 -comments: true ---- - -# 学习英语第三周 - -## 阅读英文原著 -这周按照预定计划,把每天的阅读量降低了一半,每天20页左右。下面是时间表。 - -| Book | Time | Total Time | Progress | -|:----:| :----:| :----:| :----:| -|The Three Musketeers| 2014.03.03 16:00-17:30 18:30-19:00 | 2h |487/625| -|The Three Musketeers| 2014.03.04 13:00-14:00 18:40-19:40 | 2h |510/625| -|The Three Musketeers| 2014.03.05 12:30-13:10 19:00-20:00 | 1.6h |530/625| -|The Three Musketeers| 2014.03.06 13:00-13:40 18:50-20:10 | 2h |564/625| -|The Three Musketeers| 2014.03.07 13:00-13:45 19:00-20:00 21:00-23:15 | 4h |625/625| -|Harry Potter and the Philosopher's Stone| 2014.03.08 16:30-18:00 | 1.5h |30/309 | -|Harry Potter and the Philosopher's Stone| 2014.03.09 12:30-14:30 | 2h |60/309 | - -这周把 *The Three Musketeers* 阅读完了,第一本英文小说,从一开始看40页需要六个小时,到最后基本可以流畅地看下来,感觉还是不错的。 - -下面是关键数据: - -* 20天(2月16日至3月7日) -* 625页 -* 228402单词 -* 320个新单词 - - - - -## 学习音标 -这周的任务是第一次总复习 - -| 内容 | 时间 | -|:--:|:--:| -|连音学习 |2014.03.03 19:30-20:00 | -|连音复习,字母复习 |2014.03.04 08:50-09:30 | -|复习元音 |2014.03.05 08:30-09:20 | -|复习元音四个 |2014.03.06 09:00-09:30 | -|复习元音四个 |2014.03.07 10:00-10:30 | -|复习元音四个 |2014.03.08 12:00-12:30 | -|复习元音四个 |2014.03.09 12:00-12:30 | - - -下一周准备开始语法学习,但是阅读还是要保证的,开始阅读 *Harry Potter*。 - -*Harry Potter and the Philosopher's Stone* 共309页,76,944字,按我现在的速度,一天平均7000字左右,十天应该可以读完, 一天读30页,这两天先试运行,测试一下时间,下周一正式启动。 -经过测试,30页需要一个半小时。 - -英语语法,开始研习《语法俱乐部》,按一个月的时间,每天应该学大概15页左右,但是页数并不是目标,一定要理解,要是时间消耗太多,就减少每天学习的量,保持理解一个点。另外,经过测试,完成一章大概需要一个小时的时间,40分钟学习,20分钟做练习。 - -音标方面,下周应该完成第二遍的学习了。每天应该学习八个,时间一个小时,这意味着下周要早起了。 diff --git a/_posts/2014-03-12-jmm-papers.markdown b/_posts/2014-03-12-jmm-papers.markdown deleted file mode 100644 index bc96888..0000000 --- a/_posts/2014-03-12-jmm-papers.markdown +++ /dev/null @@ -1,305 +0,0 @@ ---- -layout: default -title: Java 内存模型论文阅读 -categories: [科研] -comments: true ---- - - -# Java 内存模型论文阅读 - -## 引言 -Java 的内存模型最早出现在1995年,但是自1997年起,这一内存模型被发现了许多严重的错误和缺陷,它阻碍了很多优化措施,对程序的安全性也没有足够的保证。2001年[JSR 133](https://www.jcp.org/en/jsr/detail?id=133)被确立下来,由William Pugh领导,专家组的成员包括了Adve,Doug Lea, William Pugh等。2004年,JSR 133最终版本发布。2005年,Manson Jeremy, William Pugh, 和 Sarita V. Adve 一同发表了论文 *The Java memory model*,描述了最新的Java内存模型,这一内存模型在Java 5.0中引入,一直沿用至今。此后,科学家们对Java内存模型进行了进一步的研究的探索,但大的改动并没有出现。 - -## 论文介绍 - -### Pugh2000 - -* 论文名 - Pugh W. The Java memory model is fatally flawed[J]. Concurrency - Practice and Experience, 2000, 12(6): 445-455. - -* 主要内容 - 介绍了现有Java内存模型的不足之处: - * 难以理解,不同的人有不同解读 - * 禁止了很多编译器优化,大多数JVM实现都违反Java内存模型 - * 一些常用编程范式违反Java内存模型 - * 没有考虑到在共享内存,弱一致性内存模型下实现Java所带来的一些问题 - -* 参考文献 - * Gontmakher 1997: 证明了Java内存模型需要Coherence - -### **Manson 2005** - -* 论文名 - Manson J, Pugh W, Adve S V. The Java memory model[M]. ACM, 2005. - -* 主要内容 - JSR 133的成果,介绍了新的Java内存模型,由Java 5.0引入,沿用至今。 - 基本思想包括: - * 对满足 Data Race Free 的程序保证顺序一致性(sequential consistency) - * 对没有正确同步的程序,使用causality的概念加以限制,以保证程序的安全性 - * 新的内存模型足够强,以保证安全性,又足够弱,以保证编译器可以使用足够的优化 - - - - -* 参考文献 - - * Lamport 1979: 提出 Sequential Consistency 概念 - * Relaxed models in academia and commercial hardware - - Adve 1990 - - Adve 1993 - - Dubois 1986 - - Gharachorloo 1990 - - IBM 1983(System/370 Principles of Operation) - - May 1994(The PowerPC Architecture) - - Sites 1995(Alpha AXP Architecture Reference Manual) - - Weaver 1994(The SPARC Architecture Manual) - * SC for DRF - - Adve 1990 - - Adve 1993 - - Gharachorloo 1990 - * Flaws in original Java Memory Model - - Pugh 1999 - * Original Java Memory Model Research - - Gosling 1996 - - Kotrajaras 2001 - - Saraswat 2004 - -### **Polyakov 2006** - -* 论文名 - Polyakov S, Schuster A. -*Verification of the Java causality requirements* -[M]//Hardware and Software, Verification and Testing. Springer Berlin Heidelberg, 2006: 224-246. - -* 主要内容 - * 证明验证causality是NP-complete的 - * 跟踪每个线程实际运行时 read 操作的顺序可以简化验证 - * 对可简化的验证提出了多项式算法 - * 不能简化的提出非多项式算法(仅用于短的测试序列) - * 使用了Post-mortem的方法,实际运行一个多线程程序,在JVM或者定制过的JVM或者模拟器上运行程序,拿到trace,分析trace,以验证内存是否有问题 - * 使用frontier graph验证 - -* 参考文献 - * Boehm 2005: 通过库实现多线程不能保证程序正确性 - * causal acyclicity 形式化定义(reach condition): - - Sufficient System Requirements for Supporting the PLpc Memory Model. 1993 - - Specifying System Requirements for Memory Consistency Models. 1993 - * Gibbons 1993:使用frontier graph验证SC - - -### Aspinall 2007-1 - -* 论文名 -Aspinall D, Ševčík J. -*Formalising Java’s data race free guarantee* -[M]//Theorem Proving in Higher Order Logics. Springer Berlin Heidelberg, 2007: 22-37. - -* 主要内容 - * 给出精确的DRF定义和证明 - * 发现要保证DRF,JMM中并非所有条件都要满足 - * 形式化定义为测试具体实例提供了基础 - * 证明了JMM中给定的条件可以保证DRF -* 参考文献 - - -### Huisman 2007 - -* 论文名 -Huisman M, Petri G. -*The Java memory model: a formal explanation* -[J]. VAMP, 2007, 7: 81-96. - -* 主要内容 - * 使用Coq中形式化描述JMM - * 证明DRF的条件 - -* 参考文献 - -### Cenciarelli 2007 - -*论文名 -Cenciarelli P, Knapp A, Sibilio E. -*The Java memory model: Operationally, denotationally, axiomatically* -[M]//Programming Languages and Systems. Springer Berlin Heidelberg, 2007: 331-346. - -* 主要内容 - - * 构建新的语义框架,由 operational step 构成 denotional model,并被 axioms 限制 - * 使用Configuration Theory 描述 Java 操作规则 - * 为 Java 提供一个基于事件的语义 - -### Aspinall 2007-2 - -* 论文名 -Aspinall D, Sevcik J. -*Java memory model examples: Good, bad and ugly* -[J]. VAMP07 Proceedings 2007. - -* 主要内容 - * Good Example: JMM 允许的行为,展示了非SC的行为和一些优化 - * Bad Example:JMM禁止的行为 - * Ugly Example:JMM禁止,但却出现的行为 - * 通过这些例子展示 Aspinall 2007-1 中提出的形式化定义优于官方定义 - -### Botinan 2007 - -* 论文名 -Botincan M, Glavan P, Runje D. -*Distributed Algorithms: A Case Study of the Java Memory Model[J].* -Proceedings of the ASM, 2007. - -* 主要内容 - * 对 JMM 提供数学化的精确定义 - * 为其在ASM context下提供解释 - -### Sevcik 2008 - -* 论文名 -Ševčík J, Aspinall D. -*On validity of program transformations in the Java memory model[M]* -//ECOOP 2008–Object-Oriented Programming. Springer Berlin Heidelberg, 2008: 27-51. - -* 主要内容 - * 分析了一些常见但在JMM中禁止的优化措施,揭示了 Hotspot JVM 中违背JMM的情况 - * 对data race程序的要求比设计者想的要强 - -### **arnabde 2008** - -* 论文名 -De A, Roychoudhury A, D'Souza D. -*Java memory model aware software validation[C]* -//Proceedings of the 8th ACM SIGPLAN-SIGSOFT workshop on Program analysis for software tools and engineering. ACM, 2008: 8-14. - -* 主要内容 - -提出一种近似JMM的内存模型OpMM,它可以与模型检测工具JPF结合,寻找软件中的bug. - -### **Chen Chen 2009** - -* 论文名 -Chen C, Chen W, Sreedhar V, et al. -*Formalizing Causality as a Desideratum for Memory Models and transformations of Parallel Programs[J].* -2009. - -* 主要内容 - * 提出causally ordered,用以构造 causality graph 框架,以找环的方式分析内存模型是否违反 causality - * 识别出代码转换中保持/违反 causality的措施 - * 提出CMM内存模型,是保证不违反causality的最弱的内存模型 - -* 参考文献 - - * JMM 社区提出了20 causality test cases,用于编译器和虚拟机验证 - - -### Botincan 2010 - -* 论文名 -Botinčan M, Glavan P, Runje D. Verification of causality requirements in Java memory model is undecidable[M]//Parallel Processing and Applied Mathematics. Springer Berlin Heidelberg, 2010: 62-67. - -* 主要内容 - 证明验证任意有限次执行的多线程程序是否满足causality requirments是undecidable的. - -* 参考文献 - * Polyakov 2006:在无同步操作,final作用域上,通过验证有限次数执行的多线程程序,来验证JMM的causality requirements.证明了在给定的一些假设上,此问题是NP-complete的. - -### **Torlak 2010** - -* 论文名 -Torlak E, Vaziri M, Dolby J. -*MemSAT: checking axiomatic specifications of memory models[J].* -ACM Sigplan Notices, 2010, 45(6): 341-350. - -* 主要内容 - * 基于SAT solver的工具MEMSAT,用于调试推导内存模型,如果给出内存模型的公理化描述,包含断言的多线程程序,工具可以输出一个trace,保证内存模型及多线程程序的断言都得到满足. - * 在Manson 的 JMM上,以及Sevcik的修复版本上测试过. - * 第一个对 JMM 的公理化描述进行自动调试推理的工具 - -* 参考文献 - * litmus tests 用于对内存模型的形式化说明进行补充,方便人们理解内存模型,验证litmus tests的工作包括: - - model checking - arnabde 2008: Java memory model aware software validation - yang 2001: Analyzing the CRF Java memory model - - constrain solving - Burckhardt 2007: CheckFence: checking consistency of concurrent data types on relaxed memory models - Gopalakrishnan 2004: QB or Not QB: An efficient execution verification tool for memory orderings - Yang 2003: Analyzing the Intel Itanium memory ordering rules using logic programming and SAT. - - custome search - Sarkar 2009:The semantics of x86–CC multiproces sor machine code -这些工作已经成功对 Intel Itanium x86-CC MM, **JMM** 进行验证,但Aspinall 2007指出了JMM中commiting semantics带来的巨大状态空间使得model checking难以适用. - - * Yang 2004:Nemos:a framework for axiomatic and executable specifications of memory consistency models指出公理语义在描述内存模型上优于操作语义, - - * Sevcik 2008: 对Manson的JMM进行修复. - - * JMM 发展历史,相关工作 - * J. Manson JMM simulator - -### Lochbihler 2012 - -* 论文名 -Lochbihler A. -*Java and the Java memory model—A unified, machine-checked formalisation[M]* -//Programming Languages and Systems. Springer Berlin Heidelberg, 2012: 497-517. - -* 主要内容 -对 JMM 进行形式化,并通过机器检查,与Java源代码及字节码的操作语义联系在一起. -证明了语义保证了DRF - -* 参考文献 - - * Related Work 介绍的挺全 - - -### Jin 2012 - -* 论文名 -Jin H, Yavuz-Kahveci T, Sanders B A. Java memory model-aware model checking[M]. Springer Berlin Heidelberg, 2012. - -* 主要内容 - * 扩展JPF,产生包含data race的执行 - * 提供工具 Java PathRelaxer(JPR),用于推导包含data race的程序,验证它的性质 - - -* 参考文献 - - * Manson 2002: The Java memory model simulator - - -### Demange 2013 - -* 论文名 -Demange D, Laporte V, Zhao L, et al. -*Plan B: A buffered memory model for Java[C]* -//ACM SIGPLAN Notices. ACM, 2013, 48(1): 329-342. - -* 主要内容 - -* 提出了一种新的Java 内存模型BMM,给出公理化定义,刻画了内存事件的次序 -* 给出BMM'的形式化定义,对Java程序的语义来说,这是一个可操作的定义,容易用于x86体系结构. -* 证明BMM和BMM'是一样的 -* 给出BMM性能测试结果 -* 相关工作介绍了JMM发展,验证等等. - -* 参考文献 - - * Sevcik 2011: Relaxed-memory Concurrency and Verified Compilation - - * 在当前情况下,证明一个编译器是否符合JMM中的定义仍然是一个open的问题. - - * Sevcik2008: 现存的JVM是不符合JMM的 - -### **LOCHBIHLER 2013** - -* 论文名 -Lochbihler A. -Making the Java memory model safe[J]. -ACM Transactions on Programming Languages and Systems (TOPLAS), 2013, 35(4): 12. - -* 主要内容 - * 基于之前的JinJiaThread语义,为公理语义的JMM提出了一个 *unified formalization*,将Java与JMM结合在一起 - * 澄清了现有的JMM标准,修复了一些不合适的地方 - * 证明了DRF需要满足的条件 - -* 参考文献 diff --git a/_posts/2014-03-14-hd-mm.markdown b/_posts/2014-03-14-hd-mm.markdown deleted file mode 100644 index a7fc575..0000000 --- a/_posts/2014-03-14-hd-mm.markdown +++ /dev/null @@ -1,88 +0,0 @@ ---- -layout: default -title: 硬件内存模型论文阅读 -categories: [科研] -comments: true ---- - -# 硬件内存模型论文阅读 - -## 引言 -内存模型定义了内存操作的语义,提出内存模型的目的在于指明内存操作之间的次序有何限制. - -内存模型并不直接指定内存操作的次序,它只提供一种行为描述,只要**你的执行结果** 与 **按内存模型规定的次序执行结果** 一致就行,并不要求具体实现一定按这个顺序. - - -## 论文 - -### Lamport 1979 - -* 论文名 -Lamport L. How to make a multiprocessor computer that correctly executes multiprocess programs[J]. Computers, IEEE Transactions on, 1979, 100(9): 690-691. - -* 主要内容 - * 提出 sequencial consistency 的概念 - * SC 的两个条件 - * 程序的任意一次执行,其结果都和所有处理器操作以某种次序依次执行的结果一致 - * 在上述次序中,同一处理器的操作之间的次序与它们在程序中的次序一致 - * 多处理器计算机实现SC的两个条件 - * 每个处理器都按照程序给出的顺序 issue 内存请求 - * 所有处理器被 issue 到同一 memory module的操作都在一个FIFO队列中接受服务 - - - -* 参考文献 - -### **SPARC-V9 1994** - -* 论文名 - -The SPARC architecture manual[M]. Englewood Cliffs, NJ 07632: PTR Prentice Hall, 1994. - -* 主要内容 - - * 定义了三种内存模型:TSO,PSO,RMO - * 介绍了 Sparc Processor Model:描述 了issue, reorder, execution - -* 参考文献 - - -### Gharachorloo 1990 - -* 论文名 - -Gharachorloo K, Lenoski D, Laudon J, et al. -*Memory consistency and event ordering in scalable shared-memory multiprocessors[M].* -ACM, 1990. - -* 主要内容 - - * 提出release consistency内存模型 - * RC 对weak consistency进行扩展,支持更多的buffering和pipelining - * RC与SC对进行足够同步的并行程序而言是等价的 - * 介绍了 performed , globally performed - * 采用event ordering 的方式描述内存模型 - * 回顾之前的内存模型: sequential consistency, processor consistency, weak consistency. - * 与 weak consistency 相比, 将同步操作分成 acquire 和 release, 对次序进行进一步的放松 - -* 参考文献 - * Memory access buffering in multiprocessors. 1986: 使用 event ordering 描述内存模型, 提出 weak consistency(weak ordering). - * Efficient and correct execution of parallel programs that share memory. 1988: conflicting accesses. - - -### Sewell 2010 - -* 论文名 -Sewell P, Sarkar S, Owens S, et al. -*x86-TSO: a rigorous and usable programmer's model for x86 multiprocessors[J].* -Communications of the ACM, 2010, 53(7): 89-97. - -* 主要内容 - * 动机: x86系列处理器文档中对内存模型的描述不精确,有些甚至是错误的,与实际的实现不一样,这妨碍了程序员编写正确的程序,也不利于对内存模型进行验证. - * 回顾了各种x86系列处理器内存模型描述中的错误 - * 给出了对x86系列处理器内存模型数据化的精确描述以解决上述错误 - - - -* 参考文献 - diff --git a/_posts/2014-03-14-mm-verify.markdown b/_posts/2014-03-14-mm-verify.markdown deleted file mode 100644 index 06327d4..0000000 --- a/_posts/2014-03-14-mm-verify.markdown +++ /dev/null @@ -1,131 +0,0 @@ ---- -layout: default -title: 内存模型的验证论文阅读 -categories: [科研] -comments: true ---- - - -# 内存模型的验证论文阅读 - -## 动态验证 - -### Hangal2004 - -* 论文名 -Hangal S, Vahia D, Manovit C, et al. TSOtool: A program for verifying memory systems using the memory consistency model[C]//ACM SIGARCH Computer Architecture News. IEEE Computer Society, 2004, 32(2): 114. - -* 主要内容 - * 实现工具TSOtool,用于检测多处理器的共享内存系统 - * 内存模型TSO - * 随机生成包含data race的程序 - * 提出多项式时间算法,incomplete - * 检测出商业系统中的设计错误 - * 根据内存模型定义一种次序关系,通过程序及结果构造有向图, - 在图中找环 - * 时间复杂度:最坏O(n^5) - -* 参考文献 - * 验证SC的复杂度, 将问题定义为VSC, VSC-read(map read to write), VSC-conflict(VSC-read + total order of write): Gibbons P B, Korach E. Testing shared memories[J]. SIAM Journal on Computing, 1997, 26(4): 1208-1244. - - - -### Manovit 2005 - -* 论文名 -Manovit C, Hangal S. Efficient algorithms for verifying memory consistency[C]//Proceedings of the seventeenth annual ACM symposium on Parallelism in algorithms and architectures. ACM, 2005: 245-252. - -* 主要内容  - * 将vector clock引入TSOtool工具 - * 为VSC-conflict问题提出一个多项式算法 - * 为VSC-read提出多项式算法,快速,有效,但不完备 - * 时间复杂度:O(k*n^3) - -* 参考文献 - -### Manovit 2006 - -* 论文名 -Manovit C, Hangal S. Completely verifying memory consistency of test program executions[C]//High-Performance Computer Architecture, 2006. The Twelfth International Symposium on. IEEE, 2006: 166-175. - -* 主要内容 - * 在TSOtool中引入回溯,使验证TSO内存模型的算法完备 - * 时间复杂度: O(p*n^3) - -* 参考文献 - - -### Roy 2006 - -* 论文名 -Roy A, Zeisset S, Fleckenstein C J, et al. Fast and generalized polynomial time memory consistency verification[C]//Computer Aided Verification. Springer Berlin Heidelberg, 2006: 503-516. - -* 主要内容 - * 提出新的算法,将最坏时间复杂度降低至O(n^4),适用于任何占用O(n^2)空间的内存一致性模型 - -* 参考文献 - -### Chen 2008 - -* 论文名 -Chen Y, Lv Y, Hu W, et al. Fast complete memory consistency verification[C]//High Performance Computer Architecture, 2009. HPCA 2009. IEEE 15th International Symposium on. IEEE, 2009: 381-392. - -* 主要内容  - * 实现工具LCHECK - * 引入time order的概念 - * 对可靠完备性验证,时间复杂度O(C^p*p^2*n^2) - * 对可靠但不完备性验证,时间复杂度(p^3*n) - -* 参考文献 - -## 静态验证 - -### Park 1995 - -* 论文名 -Park S, Dill D L. An executable specification, analyzer and verifier for RMO (relaxed memory order)[C]//Proceedings of the seventh annual ACM symposium on Parallel algorithms and architectures. ACM, 1995: 34-41. - -* 主要内容 - * 使用murphi语言描述SPARC的RMO内存模型 - * murphi verifier 可以生成小断汇编语言多处理器程序的所有结果 - -* 参考文献 - -### Condon 1999 - -* 论文名 -Condon A E, Hill M D, Plakal M, et al. Using lamport clocks to reason about relaxed memory models[C]//High-Performance Computer Architecture, 1999. Proceedings. Fifth International Symposium On. IEEE, 1999: 270-278. - -* 主要内容 - * 对TSO和Alapha内存模型,给出定义,实现,以及对实现的证明 - -* 参考文献 - - -### Chatterjee2002 - -* 论文名  -Chatterjee P, Sivaraj H, Gopalakrishnan G. Shared memory consistency protocol verification against weak memory models: Refinement via model-checking[C]//Computer Aided Verification. Springer Berlin Heidelberg, 2002: 123-136. - -* 主要内容 - - * 设计者需要为具体实现建立一个高度简化的抽象,这层抽象可以用定理证明验证正确性 - * 抽象层和具体实现同时在模型检测工具上运行,检测refinement - * 验证了四种Alpha内存模型,三种Itanium内存模型 - * 使用并行化的murphi模型检测工具 - - -### Condon 2001 - -* 论文名 -Condon A E, Hu A J. Automatable verification of sequential consistency[J]. Theory of Computing Systems, 2003, 36(5): 431-460. - -* 主要内容 - * 对实现内存系统的有限状态协议进行验证,看是否满足SC - * SC基于图定义 - * 协议满足SC当且仅当它的所有trace无环 - * observer 观察协议执行,收集信息,构造图 - * 图被送给 chekcer ,检查是否有环 - * 如果chekcer接受了observer产生的所有图,那么协议是正确的 - -* 参考文献 diff --git a/_posts/2014-03-16-learn-english-4.markdown b/_posts/2014-03-16-learn-english-4.markdown deleted file mode 100644 index dbf1517..0000000 --- a/_posts/2014-03-16-learn-english-4.markdown +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: default -title: 学习英语第四周 -category: 英语 -comments: true ---- - -# 学习英语第四周 - -## 阅读英文原著 -这周按照预定计划,把每天的阅读量降低了一半,每天20页左右。下面是时间表。 - -| Book | Time | Total Time | Progress | -|:----:| :----:| :----:| :----:| -|Harry Potter and the Philosopher's Stone| 2014.03.10 12:30-13:30 | 1h |80/309 | -|Harry Potter and the Philosopher's Stone| 2014.03.11 12:30-13:30 | 1h |112/309 | -|Harry Potter and the Philosopher's Stone| 2014.03.12 12:40-13:40 | 1h |142/309 | -|Harry Potter and the Philosopher's Stone| 2014.03.13 12:45-13:45 | 1h |180/309 | -|Harry Potter and the Philosopher's Stone| 2014.03.14 12:36-13:14 | 0.5h |214/309 | -|Harry Potter and the Philosopher's Stone| 2014.03.15 14:43-15:05 | 0.3h |227/309 | -|Harry Potter and the Philosopher's Stone| 2014.03.13 13:39-14:45 | 1h |261/309 | - - -## 学习音标 -这周的任务是第一次总复习 - - - -| 内容 | 时间 | -|:--:|:--:| -|元音复习 |2014.03.09 19:30-20:00 | -|元音复习 |2014.03.10 10:00-10:20 | -|元音复习,辅音复习 |2014.03.11 08:00-08:40 | -|辅音复习 |2014.03.12 08:00-08:35 | -|辅音复习 |2014.03.13 08:21-08:54 | -|辅音复习 |2014.03.14 08:30-09:00 | -|辅音复习 |2014.03.15 11:10-11:30 | -|辅音复习 |2014.03.15 09:00-09:20 | - -这周还是按原计划复习,*Harry Potter and the Philosopher's Stone*快看完了,这一周尝试了跟着有声读物来阅读,要不很多东西都不会读,感觉不错,也能跟上。音标第一轮复习也要结束了,还剩下连音。决定先把语法停掉。 - -下周音标就学两遍了,感觉可以了,决定开始学习 *English through Pictures*,看起来还不错。学完这个,再学语法吧! - -*Harry Potter and the Philosopher's Stone*下周就要读完了,一直在犹豫要不要停掉这个学下语法,还是决定不停了,要保持每天阅读的节奏。 diff --git a/_posts/2014-03-23-learn-english-5.markdown b/_posts/2014-03-23-learn-english-5.markdown deleted file mode 100644 index 136b329..0000000 --- a/_posts/2014-03-23-learn-english-5.markdown +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: default -title: 学习英语第五周 -category: 英语 -comments: true ---- - - -# 学习英语第五周 - -## 阅读英文原著 -这周按照预定计划,把每天的阅读量降低了一半,每天20页左右。下面是时间表。 - -| Book | Time | Total Time | Progress | -|:----:| :----:| :----:| :----:| -|Harry Potter and the Philosopher's Stone| 2014.03.17 12:45-14:15 | 1.5h |309/309 | -|Harry Potter and the Chamber Of Secrets| 2014.03.18 12:41-13:44 | 1h |41/341 | -|Harry Potter and the Chamber Of Secrets| 2014.03.19 12:26-13:04 | 0.6h |64/341 | -|Harry Potter and the Chamber Of Secrets| 2014.03.20 12:25-13:00 | 35m |86/341 | -|Harry Potter and the Chamber Of Secrets| 2014.03.23 20:53-21:22 | 29m |124/341 | - - - -## 听力 - -| 内容 | 时间 | -|:--:|:--:| -|连音复习 |2014.03.17 08:00-08:30 | -|English Through Picture I| 2014.03.18 08:30-09:00 | -|English Through Picture I| 2014.03.19 08:50-09:25 | -|English Through Picture I| 2014.03.20 08:50-09:25 | -|English Through Picture I| 2014.03.21 08:49-09:03 | - -这周的学习时间不多,不过还是在坚持学习,不间断。 diff --git a/_posts/2014-03-30-learn-english-6.markdown b/_posts/2014-03-30-learn-english-6.markdown deleted file mode 100644 index 7edbf46..0000000 --- a/_posts/2014-03-30-learn-english-6.markdown +++ /dev/null @@ -1,36 +0,0 @@ ---- -layout: default -title: 学习英语第六周 -category: 英语 -comments: true ---- - -# 学习英语第六周 - -## 阅读英文原著 - -| Book | Time | Total Time | Progress | -|:----:| :----:| :----:| :----:| -|Harry Potter and the Chamber of Secrets| 2014.03.24 12:39-13:15 | 36m |139/341| -|Harry Potter and the Chamber of Secrets| 2014.03.25 13:49-14:09 | 20m |160/341| -|Harry Potter and the Chamber of Secrets| 2014.03.26 12:30-13:00 | 30m |181/341| -|Harry Potter and the Chamber of Secrets| 2014.03.27 12:20-13:07 |47m |204/341| -|Harry Potter and the Chamber of Secrets| 2014.03.28 13:10-13:46 | 36m |226/341| -|Harry Potter and the Chamber of Secrets| 2014.03.29 11:38-12:13 | 35m |248/341| -|Harry Potter and the Chamber of Secrets| 2014.03.30 12:00-12:30 | 35m |264/341| - - -## 听力 - -| 内容 | 时间 | -|:--:|:--:| -|English Through Picture I| 2014.03.24 09:15-09:35 | -|English Through Picture I| 2014.03.25 09:21-09:40 | -|English Through Picture II| 2014.03.26 09:29-09:47 | -|English Through Picture II| 2014.03.27 09:45-10:21 | -|English Through Picture II| 2014.03.28 08:52-09:20 | -|English Through Picture II| 2014.03.29 11:10-11:37 | -|English Through Picture II| 2014.03.29 12:30-12:50 | - - -这周的学习时间不多,不过还是在坚持学习,不间断。 diff --git a/_posts/2014-04-20-learn-english-789.markdown b/_posts/2014-04-20-learn-english-789.markdown deleted file mode 100644 index 767d3c5..0000000 --- a/_posts/2014-04-20-learn-english-789.markdown +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: default -title: 学习英语第七周至第九周 -category: 英语 -comments: true ---- - - -# 学习英语第七周至第九周 - -## 阅读英文原著 - -| Book | Time | Total Time | Progress | -|:----:| :----:| :----:| :----:| -|Harry Potter and the Chamber of Secrets| 2014.03.31 12:30-14:30 | 2h |341/341| -| Harry Potter And The Prisoner Of Azkaban| 2014.04.02 12:30-13:30 | 1h | 30/435 | -| Harry Potter And The Prisoner Of Azkaban| 2014.04.03 ~ 2014.04.20 | 1h | 435/435 | - - -## 听力 - -| 内容 | 时间 | -|:--:|:--:| -|English Through Picture II| 2014.03.31 09:00-09:30 | -|English Through Picture III| 2014.04.01 ~ 2014.04.20 | - - -很久都没有在这上面记录了,从第七周到第九周,不过学习一直没有间断,读完了Harry Potter And The Prisoner Of Azkaban,学习完了English Through Picture III。ETP III已经开始第二遍,今天也要开始 Harry Potter And The Goblet of Fire。 - -要坚持一年呢,别忘了~ - - - diff --git a/_posts/2014-04-21-static-in-c.markdown b/_posts/2014-04-21-static-in-c.markdown deleted file mode 100644 index ddb710d..0000000 --- a/_posts/2014-04-21-static-in-c.markdown +++ /dev/null @@ -1,238 +0,0 @@ ---- -layout: default -title: 对C语言中的static关键字的深入理解 -categories: [技术, C语言, 计算机系统] -comments: true ---- - - - -# 对C语言中的static关键字的深入理解 - -在阅读一些项目源代码时,我发现很多时候,会把函数和变量声明为static,所以,很好奇为什么要这样做,于是有了下面这篇文章。 - -# 基本概念 - -使用static有三种情况: - -* 函数内部static变量 -* 函数外部static变量 -* static函数 - -函数内部的static变量,关键在于生命周期持久,他的值不会随着函数调用的结束而消失,下一次调用时,static变量的值,还保留着上次调用后的内容。 - - - -函数外部的static变量,以及static函数,关键在于私有性,它们只属于当前文件,其它文件看不到他们。例如: - -```c -/* test_static1.c */ -#include - -void foo() { -} - -static void bar() { -} - -int i = 3; -static int j = 4; - -int main(void){ - printf ("%d \n", i); - printf ("%d \n", j); - return 0; -} - - -``` - -```c -/* test_static2.c */ -void foo() { -} - -static void bar() { -} - -int i = 1; -static int j = 2; - -``` - -将两个文件一起编译 - -> gcc test_static1.c test_static2.c -o test_static - -编译器会提示: - - /tmp/ccuerF9V.o: In function `foo': - test_static2.c:(.text+0x0): multiple definition of `foo' - /tmp/cc9qncdw.o:test_static1.c:(.text+0x0): first defined here - /tmp/ccuerF9V.o:(.data+0x0): multiple definition of `i' - /tmp/cc9qncdw.o:(.data+0x0): first defined here - collect2: ld returned 1 exit status - - -把与非static变量i相的语句注释掉就不会有此提示i重复定义了,原因就在于使用static声明后,变量私有化了,不同文件中的同名变量不会相互冲突。 - -static 函数也与此类似,将函数声明为static,说明我们只在当前文件中使用这个函数,其它文件看不到,即使重名,也不会相互冲突。 - - -# 深入理解 - -> 从来就不应该仅仅满足于了解现象,还要了解现象的背后有什么 - -## 为什么函数内部的static变量和普通函数变量生命周期不一样 -我们的程序,从源代码经过编译,链接生成了可执行文件,可执行文件被加载到存储器中,然后执行。以Unix程序为例,每个Unix程序都有一个运行时存储器映像。可以理解为程序运行时,存储器中的数据分布。 - -![linux_rtmi](/assets/blog-images/linux_run_time_memory_image.png) - -图1 Linux运行时存储器映像 - -当程序运行时,操作系统会创建用户栈(User stack),一个函数被调用时,它的参数,局部变量,返回地址等等,都会被压入栈中,当函数执行结束后,这些数据就会被其它函数使用,所以函数调用结束后,局部变量的值不会被保持。我们将此区域放大,可以看到用户栈中都有哪些内容。 - -![sfs](/assets/blog-images/stack_frame_structure.png) - -图2 栈帧结构 - -而static变量与普通局部变量不同,它不是保留在栈中。注意图一中,有一块区域,"Loaded from executable file",其中有一块 .data, .bss区,static变量会被存储在这里,所以函数调用结束后,static变量的值仍然会得到保留。而 .data, .bss区,executable file,与程序的编译,链接,相关。 - -首先,多个源代码会分别被编译成可重定位目标程序,然后链接器会最终生成可执行目标程序。可重定位目标程序的结构如图3所示,可以看出,此时,.data, .bss区,已经出现。 - -![re](/assets/blog-images/relocatable_elf.png) - -图3 可重定位目标程序 - -.data 区存储已经初始化的全局C变量,.bss 区存储没有初始化的全局C变量,而编译器会为每个static变量在.data或者.bss中分配空间。 - -可执行目标程序的结构如图4所示 - -![ee](/assets/blog-images/executable_elf.png) - -图4 可执行目标程序 - -将图4与图1比较,就会发现,可执行目标程序的一部分被加载到存储器中,这就是"Loaded from executable file"的来源。 - -另外,从图一中,也可以看出,使用malloc分配的内存空间,与函数局部变量,static变量的不同。 - -## 为什么函数外部的static变量及static函数只对文件内部可见 - -要解释这个问题,我们首先要理解问题本身。这个问题的本质其实是,当我们遇到一个变量或者函数时,我们去哪里寻找它,static变量/函数与普通变量/函数的寻找方式有什么不同。 - -我们回到刚才的例子,这一次,仔细地观察编译链接时的提示信息: - -```c -/* test_static1.c */ -#include - -void foo() { -} - -static void bar() { -} - -int i = 3; -static int j = 4; - -int main(void){ - printf ("%d \n", i); - printf ("%d \n", j); - return 0; -} - - -``` - -```c -/* test_static2.c */ -void foo() { -} - -static void bar() { -} - -int i = 1; -static int j = 2; - -``` - -将两个文件一起编译 - -> gcc test_static1.c test_static2.c -o test_static - -编译器会提示: - - /tmp/ccuerF9V.o: In function `foo': - test_static2.c:(.text+0x0): multiple definition of `foo' - /tmp/cc9qncdw.o:test_static1.c:(.text+0x0): first defined here - /tmp/ccuerF9V.o:(.data+0x0): multiple definition of `i' - /tmp/cc9qncdw.o:(.data+0x0): first defined here - collect2: ld returned 1 exit status - - -你会发现,虽然我们只用了一条命令对两个文件进行编译链接,但是,实际上,两个源文件是被分别编译成/tmp/ccuerF9V.o及/tmp/cc9qncdw.o,并且,错误并不是出现在编译时,而是出现在链接时,链接器ld返回了1。链接是把两个可重新定位的目标程序,组合在一起,组合的时候,我们发现了变量i及函数foo的定义出现冲突。而声明为static的变量j及函数bar并没有提示冲突。 - -这说明,在ld进行链接时,需要进行某种检查,去发现冲突。ld的输入是每个源文件生成的可重定位目标文件,那么这些目标文件里一定会有一些信息,告诉ld它们有什么变量,然后ld才能检查是不是有冲突。 - -说起`可重定位目标文件`,我们一直都没有解释为什么要重定位。其实这很好理解,一个源文件编译后,如果生成的目标文件中,各个地址就是最终运行时的地址,那么这些地址很可能会和其它文件中的地址冲突。因为编译一个文件时,我们不会知道有其它文件的存在,所以编译时无法确定最终的地址。因此,编译单个文件时,生成的目标文件中的地址都是从0开始,链接时,链接器会将不同目标文件中的地址重新定位,最终生成可执行文件。注意这里的冲突和前面说的冲突不是一回事,这里的冲突是不同的可重定位目标文件中相同地址的冲突,前面一段讲的是同名变量之间的冲突。 - -此时,我们不得不回到可重定位目标文件的格式。 - -![re](/assets/blog-images/relocatable_elf.png) - -图3 可重定位目标程序 - -注意 .symtab节,这个节存储符号表,假设当前可重定位目标模块为m, 符号表会告诉我们m中定义和引用的符号信息,主要分为: - -* m定义,并可以被其它模块引用的全局符号:m中的非static函数,非static全局变量。 -* 由其它模块定义,并被m引用的全局符号:m中使用extern声明的变量 -* 只被m引用的本地符号:m中的static函数,static全局变量。 - -现在编译一下,然后用GNU READELF工具看一下符号表。 - -``` - $ gcc -c test_static1.c -o test_static1.o - $ readelf -s test_static1.o - -Symbol table '.symtab' contains 15 entries: - Num: Value Size Type Bind Vis Ndx Name - 0: 00000000 0 NOTYPE LOCAL DEFAULT UND - 1: 00000000 0 FILE LOCAL DEFAULT ABS test_static1.c - 2: 00000000 0 SECTION LOCAL DEFAULT 1 - 3: 00000000 0 SECTION LOCAL DEFAULT 3 - 4: 00000000 0 SECTION LOCAL DEFAULT 4 - 5: 00000005 5 FUNC LOCAL DEFAULT 1 bar - 6: 00000004 4 OBJECT LOCAL DEFAULT 3 j - 7: 00000000 0 SECTION LOCAL DEFAULT 5 - 8: 00000000 0 SECTION LOCAL DEFAULT 7 - 9: 00000000 0 SECTION LOCAL DEFAULT 8 - 10: 00000000 0 SECTION LOCAL DEFAULT 6 - 11: 00000000 5 FUNC GLOBAL DEFAULT 1 foo - 12: 00000000 4 OBJECT GLOBAL DEFAULT 3 i - 13: 0000000a 62 FUNC GLOBAL DEFAULT 1 main - 14: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf -``` - -表的数据结构不解释,有兴趣,看扩展阅读部分。 - -现在,假如你是链接器ld,我给你2个可重定位目标程序,你从中得到两个符号表,这时候,你就可以检查出两个符号表是否存在冲突了。 - -由于全局符号可能会定义相同的名字,链接器会有一套规则,来确定选择哪个符号。符号分为强符号与弱符号。 - -* 强符号:函数和已经初始化的全局变量是强符号 -* 弱符号:未初始化的全局变量是弱符号 - -处理相同名字的全局符号的规则是: - -1. 不允许有多个强符号 -2. 如果有一个强符号,多个弱符号,那么选择强符号 -3. 如果有多个弱符号,那么从中任意选择一个 - -明白了这些规则,你其实可以明白很多事情,不仅仅包括什么时候,变量名,函数名会冲突,还包括为什么要尽量避免使用全局变量,为什么要使用static把数据私有化。看看规则3,“任意”两个字,有没有让你感觉有一丝不适。 - -这也是为什么我们要探索事物背后机理的原因,不仅仅是在出现错误时,我们知道哪里有问题,还帮助我们写出更健壮的程序。 - - -# 扩展阅读 -《深入理解计算机系统》(Computer Systems, A Programmer's Perspective): 第七章 链接 diff --git a/_posts/2014-04-28-get-bci-by-jvmti.markdown b/_posts/2014-04-28-get-bci-by-jvmti.markdown deleted file mode 100644 index 0fc4c2e..0000000 --- a/_posts/2014-04-28-get-bci-by-jvmti.markdown +++ /dev/null @@ -1,386 +0,0 @@ ---- -layout: default -title: 使用JVMTI获取Java多线程程序指令执行次序 -categories: [技术, Java, 虚拟机] -comments: true ---- - -# 使用JVMTI获取Java多线程程序指令执行次序 - ---- - -在Java多线程程序中,由于线程调度,指令间的次序在每次运行时都可能不相同,有时候,我们需要得到指令次序,用来分析程序的行为。这样细粒度的底层行为用一般方法很难完成,我们需要借助 [JVM Tool Interface](http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html),即JVMTI,来帮助我们获取Java虚拟机执行时的信息。本文先介绍编写JVMTI程序的基本框架,然后介绍如何使用JVMTI来获取多线程程序中指令之间的次序。 - -## JVMTI简介 -JVMTI是用于编写开发与监视工具的编程接口,使用它可以检查并控制运行于Java虚拟机上的程序。使用它可以完成性能分析,调试,监视(monitoring),线程分析,覆盖分析(coverage analysis)等工具。 - -使用JVMTI可以编写出一个agent。在运行Java程序时,指定这个agent,那么当虚拟机运行程序时,如果agent中指定的一些事件发生,虚拟机就会调用agent中相应的回调函数。JVMTI提供了一系列可以指定的事件,以及获取虚拟机中信息的函数接口。 - - -## JVMTI基本编程方法 - -### 编写agent - -* 头文件 -agent程序中,需要包含 `jvmti.h`头文件,才能使用JVMTI中提供的接口。 - -```c -#include -``` - -* 基本事件 -和agent有关的两个基本事件是agent的启动与关闭,我们需要自己编写与启动与关闭相关的函数,这样,虚拟机才知道启动与关闭agent时,都需要做些什么。 - - 与启动相关的函数有两个,如果你的agent在虚拟机处于`OnLoad`阶段时启动,会调用`Agent_OnLoad`函数,如果你的agent在虚拟机处于`Live`阶段时启动,会调用`Agent_OnAttach `函数。 - - 我的理解是,如果你的agent想要全程监视一个程序的运行,就编写`Agent_OnLoad`,并在启动虚拟机时指定agent。如果你的agent想获取一个已经在运行的虚拟机中程序的信息,就编写`Agent_OnAttach`。 - - 两个函数的原型如下: - -```c -JNIEXPORT jint JNICALL -Agent_OnLoad(JavaVM *vm, char *options, void *reserved) -``` - -```c -JNIEXPORT jint JNICALL -Agent_OnAttach(JavaVM* vm, char *options, void *reserved) -``` - - 与agent关闭相关的函数是`Agent_OnUnload`,当agent要被关闭时,虚拟机会调用这个函数,函数原型为: - -```c -JNIEXPORT void JNICALL -Agent_OnUnload(JavaVM *vm) -``` - -* 程序基本框架 - -主要的内容框架在`Agent_OnLoad`中编写: - - 1 获取jvm环境 - -```c -/* get env */ -jvmtiEnv *jvmti = NULL; -jvmtiError error; - -error = (*jvm)->GetEnv(jvm, (void **)&jvmti, JVMTI_VERSION); -if (error != JNI_OK) { - fprintf(stderr, "Couldn't get JVMTI environment"); - return JNI_ERR; -} -``` - -可以为同一个虚拟机指定多个agent,每个agent都有自己的环境,在指定agent行为前,首先要获取的就是环境信息,后面的操作都是针对这个环境的。另外,JVMTI中的函数都会返回错误代码,在调用函数后,需要检查返回值,以确定函数调用是否成功。不同的函数会返回不同类型的错误码,可自行参阅JVMTI的API。 - -另外,需要注意,JVMTI程序可以使用C/C++编写,两者在调用函数时略有不同,上面的例子是用C编写,gcc编译。如果你使用C++编写,`GetEnv`需要这样调用: - -```c++ -error = (jvm)->GetEnv(reinterpret_cast(&jvmti), JVMTI_VERSION_1_1); -``` - -其它函数依次类推。 - -2 添加capabilities - -JVMTI中有很多事件,每个事件都对对应一些Capabilities,如果你想为此事件编写函数,就要开启相应的Capabilities,例如,我们想对 `JVMTI_EVENT_SINGLE_STEP` 事件编写函数,可以查到,需要开启`can_generate_single_step_events`: - -```c -/* add capabilities */] -jvmtiCapabilities capa; -memset(&capa, 0, sizeof(jvmtiCapabilities)); -capa.can_generate_single_step_events = 1; -error = (*jvmti)->AddCapabilities(jvmti, &capa); -check_jvmti_error(jvmti, error, \ - "Unable to get necessary JVMTI capabilities."); -``` - -如果开启的Capabilities多于一个,不用声明多个`jvmtiCapabilities`变量,只需要使用类似 - -```c -capa.can_generate_single_step_events = 1; -``` -的方式指定就行。 - -3 指定事件 - -JVMTI编写的目的是,当虚拟机中一个事件发生时,调用我们为此事件编写的函数。所以我们需要指定哪个事件发生时,通知agent: - -```c -/* set events */ -error = (*jvmti)->SetEventNotificationMode \ - (jvmti, JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, NULL); -check_jvmti_error(jvmti, error, "Cannot set event notification"); -``` - -其中 `JVMTI_EVENT_SINGLE_STEP` 就是事件代码。 - -> 需要特别注意的是,要先开启相关capabilities,然后才能指定事件。 - -4 设置回调函数 - -我们还需要为事件指定回调函数,并自行编写回调函数,事件回调函数的接口是由JVMTI指定的,例如`JVMTI_EVENT_SINGLE_STEP`事件的回调函数原型: - -``` -void JNICALL -SingleStep(jvmtiEnv *jvmti_env, - JNIEnv* jni_env, - jthread thread, - jmethodID method, - jlocation location) -``` - -为事件指定回调函数的方法是: - -```c -jvmtiEventCallbacks callbacks; - -/* add callbacks */ -memset(&callbacks, 0, sizeof(callbacks)); -callbacks.SingleStep = &callbackSingleStep; -error = (*jvmti)->SetEventCallbacks \ - (jvmti, &callbacks, (jint)sizeof(callbacks)); -check_jvmti_error(jvmti, error, "Canot set jvmti callbacks"); - -``` - -之后,我们需要自己编写 `callbackSingleStep`函数: - -```c -void JNICALL -callbackSingleStep( - jvmtiEnv *jvmti, - JNIEnv* jni, - jthread thread, - jmethodID method, - jlocation location) { - -} -``` - -### 运行agent - -运行agent,通过指定虚拟机参数来设定,例如运行`PossibleReordering`时: - -```sh -java -classpath . \ - -agentpath:`pwd`/jvmagent/TraceAgent.so PossibleReordering -``` -其中`TraceAgent.so`就是编译后生成的agent。 - -## 使用JVMTI获取多线程程序指令执行次序 - -我们知道,在Java虚拟机中的运行时数据区中,每个线程都有它的私有区域,每个线程有自己的PC寄存器,PC寄存器表示线程当前执行的指令在内存中的地址。其实我最初的目的是想得到这个PC的值,但是找了很久都没有找到,然后在JVMTI中找到了类似的概念。 - -在JVMTI中,介绍单步事件(Single Step Event)时说,当一个线程到达一个新的位置(location)时,单步事件就会产生。单步事件使agent以虚拟机允许的最细粒度,跟踪线程执行。 - -我们回到单步事件回调函数的原型: - -```c -void JNICALL -SingleStep(jvmtiEnv *jvmti_env, - JNIEnv* jni_env, - jthread thread, - jmethodID method, - jlocation location) -``` - -其中的 `location` 就是新指令的位置。 - -我们首先来编写一个Java多线程程序,这个程序是 《Java并发编程实战》(Java Concurrency in Practice) 中的一个例子,我做了一点变形: - -```java -import java.lang.Thread; - -public class PossibleReordering { - static int x = 0, y = 0; - static int a = 0, b = 0; - - public static void main(String[] args) throws InterruptedException { - - Thread one = new Thread(new Runnable() { - public void run() { - a = 1; - x = b; - } - }); - - Thread other = new Thread(new Runnable() { - public void run() { - b = 1; - y = a; - a = 1; - x = b; - a = 1; - x = b; - a = 1; - x = b; - a = 1; - x = b; - a = 1; - x = b; - a = 1; - x = b; - a = 1; - x = b; - } - }); - - - one.start(); - other.start(); - one.join(); - other.join(); - } -} -``` - -给Thread other多加了一些语句,用以区分两个线程。 - -这里有一个问题是,我们关心的其实只是两个线程的 `run`函数中指令的次序,而单步事件会在任何指令执行时,都调用回调函数,这就需要我们在回调函数中,只保留源代码中的两个线程的`run`函数中的指令的位置,其它的都过滤掉。 - -我们可以使用JVMTI提供的 `GetMethodName` 来得到函数名,使用 `GetMethodDeclaringClass`得到类名,然后通过比较类名和函数名,只保留 `run`中的指令: - -```c -error = (*jvmti)->GetMethodName( \ - jvmti, method, &method_name, &method_signature, SKIP_GENERIC); - -error = (*jvmti)->GetMethodDeclaringClass( \ - jvmti, method, &declaring_class); - - -if (strncmp(method_name, "run", 4) == 0 && \ - strstr(class_signature, "PossibleReordering") != NULL) { - printf("%s\t", thread_info.name); - printf("%s\t", class_signature); - printf("%lld\t", location); - printf("%s %lld:%lld\t", method_name, s_location, e_location); - printf("\n"); -} -``` - -执行下列命令: - -```sh -java -classpath . -agentpath:`pwd`/jvmagent/TraceAgent.so=log.txt PossibleReordering -``` - -即可得到指令次序信息: - -``` -Thread-0 LPossibleReordering$1; 0 run 0:10 -Thread-0 LPossibleReordering$1; 1 run 0:10 -Thread-1 LPossibleReordering$2; 0 run 0:80 -Thread-0 LPossibleReordering$1; 4 run 0:10 -Thread-0 LPossibleReordering$1; 7 run 0:10 -Thread-0 LPossibleReordering$1; 10 run 0:10 -Thread-1 LPossibleReordering$2; 1 run 0:80 -Thread-1 LPossibleReordering$2; 4 run 0:80 -Thread-1 LPossibleReordering$2; 7 run 0:80 -Thread-1 LPossibleReordering$2; 10 run 0:80 -Thread-1 LPossibleReordering$2; 11 run 0:80 -Thread-1 LPossibleReordering$2; 14 run 0:80 -Thread-1 LPossibleReordering$2; 17 run 0:80 -Thread-1 LPossibleReordering$2; 20 run 0:80 -Thread-1 LPossibleReordering$2; 21 run 0:80 -Thread-1 LPossibleReordering$2; 24 run 0:80 -Thread-1 LPossibleReordering$2; 27 run 0:80 -Thread-1 LPossibleReordering$2; 30 run 0:80 -Thread-1 LPossibleReordering$2; 31 run 0:80 -Thread-1 LPossibleReordering$2; 34 run 0:80 -Thread-1 LPossibleReordering$2; 37 run 0:80 -Thread-1 LPossibleReordering$2; 40 run 0:80 -Thread-1 LPossibleReordering$2; 41 run 0:80 -Thread-1 LPossibleReordering$2; 44 run 0:80 -Thread-1 LPossibleReordering$2; 47 run 0:80 -Thread-1 LPossibleReordering$2; 50 run 0:80 -Thread-1 LPossibleReordering$2; 51 run 0:80 -Thread-1 LPossibleReordering$2; 54 run 0:80 -Thread-1 LPossibleReordering$2; 57 run 0:80 -Thread-1 LPossibleReordering$2; 60 run 0:80 -Thread-1 LPossibleReordering$2; 61 run 0:80 -Thread-1 LPossibleReordering$2; 64 run 0:80 -Thread-1 LPossibleReordering$2; 67 run 0:80 -Thread-1 LPossibleReordering$2; 70 run 0:80 -Thread-1 LPossibleReordering$2; 71 run 0:80 -Thread-1 LPossibleReordering$2; 74 run 0:80 -Thread-1 LPossibleReordering$2; 77 run 0:80 -Thread-1 LPossibleReordering$2; 80 run 0:80 -``` - -最终的源代码中,我还输出了线程名,和方法的指令地址范围。 - -我们可以反编译 `PossibleReordering$1` 和 `PossibleReordering$2`,看看相应的指令范围是否可以对应上。 - -```sh -$ javap -c PossibleReordering\$1.class -Compiled from "PossibleReordering.java" -final class PossibleReordering$1 implements java.lang.Runnable { - PossibleReordering$1(); - Code: - 0: aload_0 - 1: invokespecial #1 // Method java/lang/Object."":()V - 4: return - - public void run(); - Code: - 0: iconst_1 - 1: putstatic #2 // Field PossibleReordering.a:I - 4: getstatic #3 // Field PossibleReordering.b:I - 7: putstatic #4 // Field PossibleReordering.x:I - 10: return -} -``` - -```sh -$ javap -c PossibleReordering\$2.class -Compiled from "PossibleReordering.java" -final class PossibleReordering$2 implements java.lang.Runnable { - PossibleReordering$2(); - Code: - 0: aload_0 - 1: invokespecial #1 // Method java/lang/Object."":()V - 4: return - - public void run(); - Code: - 0: iconst_1 - 1: putstatic #2 // Field PossibleReordering.b:I - 4: getstatic #3 // Field PossibleReordering.a:I - 7: putstatic #4 // Field PossibleReordering.y:I - 10: iconst_1 - 11: putstatic #3 // Field PossibleReordering.a:I - 14: getstatic #2 // Field PossibleReordering.b:I - 17: putstatic #5 // Field PossibleReordering.x:I - 20: iconst_1 - 21: putstatic #3 // Field PossibleReordering.a:I - 24: getstatic #2 // Field PossibleReordering.b:I - 27: putstatic #5 // Field PossibleReordering.x:I - 30: iconst_1 - 31: putstatic #3 // Field PossibleReordering.a:I - 34: getstatic #2 // Field PossibleReordering.b:I - 37: putstatic #5 // Field PossibleReordering.x:I - 40: iconst_1 - 41: putstatic #3 // Field PossibleReordering.a:I - 44: getstatic #2 // Field PossibleReordering.b:I - 47: putstatic #5 // Field PossibleReordering.x:I - 50: iconst_1 - 51: putstatic #3 // Field PossibleReordering.a:I - 54: getstatic #2 // Field PossibleReordering.b:I - 57: putstatic #5 // Field PossibleReordering.x:I - 60: iconst_1 - 61: putstatic #3 // Field PossibleReordering.a:I - 64: getstatic #2 // Field PossibleReordering.b:I - 67: putstatic #5 // Field PossibleReordering.x:I - 70: iconst_1 - 71: putstatic #3 // Field PossibleReordering.a:I - 74: getstatic #2 // Field PossibleReordering.b:I - 77: putstatic #5 // Field PossibleReordering.x:I - 80: return -} -``` - -可以看出,确实是一个线程的run方法指令范围是 `0:10` ,另一个是 `0:80`,说明我们正确获取了相应指令。 - -完整的源代码,包含如何编译,运行,可以在我的GitHub中找到:[AgentDemo](https://github.com/minixalpha/Demo/tree/master/AgentDemo) diff --git a/_posts/2014-05-05-learn-english-10-11.markdown b/_posts/2014-05-05-learn-english-10-11.markdown deleted file mode 100644 index 80571c1..0000000 --- a/_posts/2014-05-05-learn-english-10-11.markdown +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: default -title: 学习英语第十周至第十一周 -category: 英语 -comments: true ---- - -# 学习英语第十周至第十一周 - -## 阅读英文原著 - -| Book |Time | Progress | -|:----:| :----:| :----:| -| Harry Potter And The Goblet of Fire| 2014.04.21 ~ 2014.05.04 | 312/734 | - - -## 听力 - -| 内容 | 时间 | -|:--:|:--:| -|English Through Picture III| 2014.04.21 ~ 2014.05.04 | - -这两周还是每天阅读 Harry Potter,已经没有意志每天把时间表更新在这里了,详细的每天各个时间段的时间记录都记在手机上,从睡觉,上网,学习,工作,到阅读,健身,娱乐。就不在这里重复了。 - -English Through Picture III 阅读完第二遍了。下面开始学习赖世雄的语法广播。 - -要坚持一年呢,别忘了~ - - - - - diff --git a/_posts/2014-05-05-learn-english-1011.markdown b/_posts/2014-05-05-learn-english-1011.markdown deleted file mode 100644 index 9d1c573..0000000 --- a/_posts/2014-05-05-learn-english-1011.markdown +++ /dev/null @@ -1,29 +0,0 @@ -# 学习英语第十周至第十一周 - -标签:英语 - -## 阅读英文原著 - -| Book |Time | Progress | -|:----:| :----:| :----:| -| Harry Potter And The Goblet of Fire| 2014.04.21 ~ 2014.05.04 | 312/734 | - - - -## 学习音标 - -| 内容 | 时间 | -|:--:|:--:| -|English Through Picture III| 2014.04.21 ~ 2014.05.04 | - -这两周还是每天阅读 Harry Potter,已经没有意志每天把时间表更新在这里了,详细的每天各个时间段的时间记录都记在手机上,从睡觉,上网,学习,工作,到阅读,健身,娱乐。就不在这里重复了。 - -English Through Picture III 阅读完第二遍了。下面开始学习赖世雄的语法广播。 - - -要坚持一年呢,别忘了~ - - - - - diff --git a/_posts/2014-05-12-learn-english-12.markdown b/_posts/2014-05-12-learn-english-12.markdown deleted file mode 100644 index 6d3f97b..0000000 --- a/_posts/2014-05-12-learn-english-12.markdown +++ /dev/null @@ -1,39 +0,0 @@ ---- -layout: default -title: 学习英语第十二周 -category: 英语 -comments: true ---- - -# 学习英语第十二周 - -标签:英语 - -## 阅读英文原著 - -| Book |Time | Progress | -|:----:| :----:| :----:| -| Harry Potter And The Goblet of Fire| 2014.05.05 ~ 2014.05.11 | 732/734 | - - - -## 学习语法 - -| 内容 | 时间 | -|:--:|:--:| -|赖世雄语法广播第一二章| 2014.05.05 ~ 2014.05.11 | - -这周还是按步就班地学习,开始学习语法了,听了语法广播,感觉有些收获,以前注意不到的地方,现在都有一个基本的概念了。 - -另外,HP一边阅读一边听感觉还不错呢~ - -要坚持一年呢,别忘了~ - - - - - - - - - diff --git a/_posts/2014-05-21-read-openjdk-src-LinkedList.markdown b/_posts/2014-05-21-read-openjdk-src-LinkedList.markdown deleted file mode 100644 index 17f9c86..0000000 --- a/_posts/2014-05-21-read-openjdk-src-LinkedList.markdown +++ /dev/null @@ -1,202 +0,0 @@ ---- -layout: default -title: OpenJDK 源码阅读之 LinkedList -categories: [技术, Java, 源代码阅读] -comments: true ---- - -# OpenJDK 源码阅读之 LinkedList - ---- - -## 定义 - -```java -public class LinkedList - extends AbstractSequentialList - implements List, Deque, Cloneable, java.io.Serializable -{ -} -``` - -## 盲点 - -* serialVersionUID - -```java - private static final long serialVersionUID = 876323262645176354L; -``` - -序列化版本号,如果前一版本序列化后,后一版本发生了很大改变,就使用这个号告诉虚拟机,不能反序列化了。 - -## 问题 - -* writeObject - -比例一下 `ArrayList` 与 `LinkedList` 中的 writeObject - -```java - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException{ - // Write out element count, and any hidden stuff - int expectedModCount = modCount; - s.defaultWriteObject(); - - // Write out array length - s.writeInt(elementData.length); - - // Write out all elements in the proper order. - for (int i=0; i