分类 技术 下的文章

在你的日常工作中,不可能每天都从头开始去开发一个新的应用程序。而真实的情况是,在日常工作中,我们大多数时候所面对的都是遗留下来的一个代码库,去修改一些特性的内容或者现存的一些代码行,这是我们在日常工作中很重要的一部分。而这也就是分布式版本控制系统 git 的价值所在。现在,我们来深入了解怎么去使用 git 的历史以及如何很轻松地去浏览它的历史。

Git 历史

首先和最重要的事是,什么是 git 历史?正如其名字一样,它是一个 git 仓库的提交历史。它包含一堆提交信息,其中有它们的作者的名字、该提交的哈希值以及提交日期。查看一个 git 仓库历史的方法很简单,就是一个 git log 命令。

旁注:为便于本文的演示,我们使用 Ruby on Rails 的仓库的 master 分支。之所以选择它的理由是因为,Rails 有良好的 git 历史,漂亮的提交信息、引用以及对每个变更的解释。如果考虑到代码库的大小、维护者的年龄和数量,Rails 肯定是我见过的最好的仓库。当然了,我并不是说其它的 git 仓库做的不好,它只是我见过的比较好的一个仓库。

那么,回到 Rails 仓库。如果你在 Ralis 仓库上运行 git log。你将看到如下所示的输出:

commit 66ebbc4952f6cfb37d719f63036441ef98149418
Author: Arthur Neves <[email protected]>
Date:   Fri Jun 3 17:17:38 2016 -0400

    Dont re-define class SQLite3Adapter on test

    We were declaring  in a few tests, which depending of    the order load will cause an error, as the super class could change.

    see https://github.com/rails/rails/commit/ac1c4e141b20c1067af2c2703db6e1b463b985da#commitcomment-17731383

commit 755f6bf3d3d568bc0af2c636be2f6df16c651eb1
Merge: 4e85538 f7b850e
Author: Eileen M. Uchitelle <[email protected]>
Date:   Fri Jun 3 10:21:49 2016 -0400

    Merge pull request #25263 from abhishekjain16/doc_accessor_thread

    [skip ci] Fix grammar

commit f7b850ec9f6036802339e965c8ce74494f731b4a
Author: Abhishek Jain <[email protected]>
Date:   Fri Jun 3 16:49:21 2016 +0530

    [skip ci] Fix grammar

commit 4e85538dddf47877cacc65cea6c050e349af0405
Merge: 082a515 cf2158c
Author: Vijay Dev <[email protected]>
Date:   Fri Jun 3 14:00:47 2016 +0000

    Merge branch 'master' of github.com:rails/docrails

    Conflicts:
        guides/source/action_cable_overview.md

commit 082a5158251c6578714132e5c4f71bd39f462d71
Merge: 4bd11d4 3bd30d9
Author: Yves Senn <[email protected]>
Date:   Fri Jun 3 11:30:19 2016 +0200

    Merge pull request #25243 from sukesan1984/add_i18n_validation_test

    Add i18n_validation_test

commit 4bd11d46de892676830bca51d3040f29200abbfa
Merge: 99d8d45 e98caf8
Author: Arthur Nogueira Neves <[email protected]>
Date:   Thu Jun 2 22:55:52 2016 -0400

    Merge pull request #25258 from alexcameron89/master

    [skip ci] Make header bullets consistent in engines.md

commit e98caf81fef54746126d31076c6d346c48ae8e1b
Author: Alex Kitchens <[email protected]>
Date:   Thu Jun 2 21:26:53 2016 -0500

    [skip ci] Make header bullets consistent in engines.md

正如你所见,git log 展示了提交的哈希、作者及其 email 以及该提交创建的日期。当然,git 输出的可定制性很强大,它允许你去定制 git log 命令的输出格式。比如说,我们只想看提交信息的第一行,我们可以运行 git log --oneline,它将输出一个更紧凑的日志:

66ebbc4 Dont re-define class SQLite3Adapter on test
755f6bf Merge pull request #25263 from abhishekjain16/doc_accessor_thread
f7b850e [skip ci] Fix grammar4e85538 Merge branch 'master' of github.com:rails/docrails
082a515 Merge pull request #25243 from sukesan1984/add_i18n_validation_test
4bd11d4 Merge pull request #25258 from alexcameron89/master
e98caf8 [skip ci] Make header bullets consistent in engines.md
99d8d45 Merge pull request #25254 from kamipo/fix_debug_helper_test
818397c Merge pull request #25240 from matthewd/reloadable-channels
2c5a8ba Don't blank pad day of the month when formatting dates
14ff8e7 Fix debug helper test

如果你想看 git log 的全部选项,我建议你去查阅 git log 的 man 页面,你可以在一个终端中输入 man git-log 或者 git help log 来获得。

小提示:如果你觉得 git log 看起来太恐怖或者过于复杂,或者你觉得看它太无聊了,我建议你去寻找一些 git 的 GUI 或命令行工具。在之前,我使用过 GitX ,我觉得它很不错,但是,由于我看命令行更“亲切”一些,在我尝试了 tig 之后,就再也没有去用过它。

寻找尼莫

现在,我们已经知道了关于 git log 命令的一些很基础的知识之后,我们来看一下,在我们的日常工作中如何使用它更加高效地浏览历史。

假如,我们怀疑在 String#classify 方法中有一个预期之外的行为,我们希望能够找出原因,并且定位出实现它的代码行。

为达到上述目的,你可以使用的第一个命令是 git grep,通过它可以找到这个方法定义在什么地方。简单来说,这个命令输出了匹配特定模式的那些行。现在,我们来找出定义它的方法,它非常简单 —— 我们对 def classify 运行 grep,然后看到的输出如下:

➜  git grep 'def classify'

activesupport/lib/active_support/core_ext/string/inflections.rb:    def classifyactivesupport/lib/active_support/inflector/methods.rb:    def classify(table_name)tools/profile:    def classify

现在,虽然我们已经看到这个方法是在哪里创建的,但是,并不能够确定它是哪一行。如果,我们在 git grep 命令上增加 -n 标志,git 将提供匹配的行号:

➜  git grep -n 'def classify'

activesupport/lib/active_support/core_ext/string/inflections.rb:205:  def classifyactivesupport/lib/active_support/inflector/methods.rb:186:    def classify(table_name)tools/profile:112:    def classify

更好看了,是吧?考虑到上下文,我们可以很轻松地找到,这个方法在 activesupport/lib/active_support/core_ext/string/inflections.rb 的第 205 行的 classify 方法,它看起来像这样,是不是很容易?

# Creates a class name from a plural table name like Rails does for table names to models.
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
#   'ham_and_eggs'.classify # => "HamAndEgg"
#   'posts'.classify        # => "Post"
    def classify
        ActiveSupport::Inflector.classify(self)
    end

尽管我们找到的这个方法是在 String 上的一个常见的调用,它调用了 ActiveSupport::Inflector 上的另一个同名的方法。根据之前的 git grep 的结果,我们可以很轻松地发现结果的第二行, activesupport/lib/active_support/inflector/methods.rb 在 186 行上。我们正在寻找的方法是这样的:

# Creates a class name from a plural table name like Rails does for table
# names to models. Note that this returns a string and not a Class (To
# convert to an actual class follow +classify+ with constantize).
#
#   classify('ham_and_eggs') # => "HamAndEgg"
#   classify('posts')        # => "Post"
#
# Singular names are not handled correctly:
#
#   classify('calculus')     # => "Calculus"
def classify(table_name)
    # strip out any leading schema name
    camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
end

酷!考虑到 Rails 仓库的大小,我们借助 git grep 找到它,用时都没有超越 30 秒。

那么,最后的变更是什么?

现在,我们已经找到了所要找的方法,现在,我们需要搞清楚这个文件所经历的变更。由于我们已经知道了正确的文件名和行数,我们可以使用 git blame。这个命令展示了一个文件中每一行的最后修订者和修订的内容。我们来看一下这个文件最后的修订都做了什么:

git blame activesupport/lib/active_support/inflector/methods.rb

虽然我们得到了这个文件每一行的最后的变更,但是,我们更感兴趣的是对特定方法(176 到 189 行)的最后变更。让我们在 git blame 命令上增加一个选项,让它只显示那些行的变化。此外,我们将在命令上增加一个 -s (忽略)选项,去跳过那一行变更时的作者名字和修订(提交)的时间戳:

git blame -L 176,189 -s activesupport/lib/active_support/inflector/methods.rb

9fe8e19a 176)   #Creates a class name from a plural table name like Rails does for table
5ea3f284 177)   # names to models. Note that this returns a string and not a Class (To
9fe8e19a 178)   # convert to an actual class follow +classify+ with #constantize).
51cd6bb8 179)   #
6d077205 180)   #   classify('ham_and_eggs') # => "HamAndEgg"
9fe8e19a 181)   #   classify('posts')        # => "Post"
51cd6bb8 182)   #
51cd6bb8 183)   # Singular names are not handled correctly:
5ea3f284 184)   #
66d6e7be 185)   #   classify('calculus')     # => "Calculus"
51cd6bb8 186)   def classify(table_name)
51cd6bb8 187)     # strip out any leading schema name
5bb1d4d2 188)     camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
51cd6bb8 189)     end

现在,git blame 命令的输出展示了指定行的全部内容以及它们各自的修订。让我们来看一下指定的修订,换句话说就是,每个变更都修订了什么,我们可以使用 git show 命令。当指定一个修订哈希(像 66d6e7be)作为一个参数时,它将展示这个修订的全部内容。包括作者名字、时间戳以及完整的修订内容。我们来看一下 188 行最后的修订都做了什么?

git show 5bb1d4d2

你亲自做实验了吗?如果没有做,我直接告诉你结果,这个令人惊叹的 提交 是由 Schneems 完成的,他通过使用 frozen 字符串做了一个非常有趣的性能优化,这在我们当前的场景中是非常有意义的。但是,由于我们在这个假设的调试会话中,这样做并不能告诉我们当前问题所在。因此,我们怎么样才能够通过研究来发现,我们选定的方法经过了哪些变更?

搜索日志

现在,我们回到 git 日志,现在的问题是,怎么能够看到 classify 方法经历了哪些修订?

git log 命令非常强大,因此它提供了非常多的列表选项。我们尝试使用 -p 选项去看一下保存了这个文件的 git 日志内容,这个选项的意思是在 git 日志中显示这个文件的完整补丁:

git log -p activesupport/lib/active_support/inflector/methods.rb

这将给我们展示一个很长的修订列表,显示了对这个文件的每个修订。但是,正如下面所显示的,我们感兴趣的是对指定行的修订。对命令做一个小的修改,只显示我们希望的内容:

git log -L 176,189:activesupport/lib/active_support/inflector/methods.rb

git log 命令接受 -L 选项,它用一个行的范围和文件名做为参数。它的格式可能有点奇怪,格式解释如下:

git log -L <start-line>,<end-line>:<path-to-file>

当我们运行这个命令之后,我们可以看到对这些行的一个修订列表,它将带我们找到创建这个方法的第一个修订:

commit 51xd6bb829c418c5fbf75de1dfbb177233b1b154
Author: Foo Bar <[email protected]>
Date:   Tue Jun 7 19:05:09 2011 -0700

    Refactor

diff--git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -58,0 +135,14 @@
+    # Create a class name from a plural table name like Rails does for table names to models.
+    # Note that this returns a string and not a Class. (To convert to an actual class
+    # follow +classify+ with +constantize+.)
+    #
+    # Examples:
+    #   "egg_and_hams".classify # => "EggAndHam"
+    #   "posts".classify        # => "Post"
+    #
+    # Singular names are not handled correctly:
+    #   "business".classify     # => "Busines"
+    def classify(table_name)
+      # strip out any leading schema name
+      camelize(singularize(table_name.to_s.sub(/.*\./, '')))
+    end

现在,我们再来看一下 —— 它是在 2011 年提交的。git 可以让我们重回到这个时间。这是一个很好的例子,它充分说明了足够的提交信息对于重新了解当时的上下文环境是多么的重要,因为从这个提交信息中,我们并不能获得足够的信息来重新理解当时的创建这个方法的上下文环境,但是,话说回来,你不应该对此感到恼怒,因为,你看到的这些项目,它们的作者都是无偿提供他们的工作时间和精力来做开源工作的。(向开源项目贡献者致敬!)

回到我们的正题,我们并不能确认 classify 方法最初实现是怎么回事,考虑到这个第一次的提交只是一个重构。现在,如果你认为,“或许、有可能、这个方法不在 176 行到 189 行的范围之内,那么就你应该在这个文件中扩大搜索范围”,这样想是对的。我们看到在它的修订提交的信息中提到了“重构”这个词,它意味着这个方法可能在那个文件中是真实存在的,而且是在重构之后它才存在于那个行的范围内。

但是,我们如何去确认这一点呢?不管你信不信,git 可以再次帮助你。git log 命令有一个 -S 选项,它可以传递一个特定的字符串作为参数,然后去查找代码变更(添加或者删除)。也就是说,如果我们执行 git log -S classify 这样的命令,我们可以看到所有包含 classify 字符串的变更行的提交。

如果你在 Ralis 仓库上运行上述命令,首先你会发现这个命令运行有点慢。但是,你应该会发现 git 实际上解析了在那个仓库中的所有修订来匹配这个字符串,其实它的运行速度是非常快的。在你的指尖下 git 再次展示了它的强大之处。因此,如果去找关于 classify 方法的第一个修订,我们可以运行如下的命令:

git log -S 'def classify'

它将返回所有这个方法的引用和修改的地方。如果你一直往下看,你将看到日志中它的最后的提交:

commit db045dbbf60b53dbe013ef25554fd013baf88134
Author: David Heinemeier Hansson <[email protected]>
Date:   Wed Nov 24 01:04:44 2004 +0000
    Initial
    git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4 5ecf4fe2-1ee6-0310-87b1-e25e094e27de

很酷!是吧?它初次被提交到 Rails,是由 DHH 在一个 svn 仓库上做的!这意味着 classify 大概在一开始就被提交到了 Rails 仓库。现在,我们去看一下这个提交的所有变更信息,我们运行如下的命令:

git show db045dbbf60b53dbe013ef25554fd013baf88134

非常好!我们终于找到它的根源了。现在,我们使用 git log -S 'def classify' 的输出,结合 git log -L 命令来跟踪这个方法都发生了哪些变更。

下次见

当然,我们并没有真的去修改任何 bug,因为我们只是去尝试使用一些 git 命令,来演示如何查看 classify 方法的演变历史。但是不管怎样,git 是一个非常强大的工具,我们必须学好它、用好它。我希望这篇文章可以帮助你掌握更多的关于如何使用 git 的知识。

你喜欢这些内容吗?


作者简介:

后端工程师,对 Ruby、Go、微服务、构建弹性架构来解决大规模部署带来的挑战很感兴趣。我在阿姆斯特丹的 Rails Girls 担任顾问,维护着一个小而精的列表,并且经常为开源做贡献。

那个列表是我写的关于软件开发、编程语言以及任何我感兴趣的东西。


via: https://ieftimov.com/learn-your-tools-navigating-git-history

作者:Ilija Eftimov 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

我们的手机、主机以及笔记本电脑这样的数字设备已经变得如此成熟,以至于它们进化成为我们的一部分,而不只是一种设备。

在应用和软件的帮助下,处理器执行许多任务。我们是否曾经想过是什么给了这些软件这样的能力?它们是如何执行它们的逻辑的?它们的大脑在哪?

我们知道 CPU (或称处理器)是那些需要处理数据和执行逻辑任务的设备的大脑。

cpu image

在处理器的深处有那些不一样的概念呢?它们是如何演化的?一些处理器是如何做到比其它处理器更快的?让我们来看看关于处理器的主要术语,以及它们是如何影响处速度的。

架构

处理器有不同的架构,你一定遇到过不同类型的程序说它们是 64 位或 32 位的,这其中的意思就是程序支持特定的处理器架构。

如果一颗处理器是 32 位的架构,这意味着这颗处理器能够在一个处理周期内处理一个 32 位的数据。

同理可得,64 位的处理器能够在一个周期内处理一个 64 位的数据。

同时,你可以使用的内存大小决定于处理器的架构,你可以使用的内存总量为 2 的处理器架构的幂次方(如:2^64)。

16 位架构的处理器,仅仅有 64 kb 的内存使用。32 位架构的处理器,最大可使用的 RAM 是 4 GB,64 位架构的处理器的可用内存是 16 EB。

核心

在电脑上,核心是基本的处理单元。核心接收指令并且执行它。越多的核心带来越快的速度。把核心比作工厂里的工人,越多的工人使工作能够越快的完成。另一方面,工人越多,你所付出的薪水也就越多,工厂也会越拥挤;相对于核心来说,越多的核心消耗更多的能量,比核心少的 CPU 更容易发热。

时钟速度

CPU CLOCK SPEED

GHz 是 GigaHertz 的简写,Giga 意思是 10 亿次,Hertz (赫兹)意思是一秒有几个周期,2 GHz 的处理器意味着处理器一秒能够执行 20 亿个周期 。

它也以“频率”或者“时钟速度”而熟知。这项数值越高,CPU 的性能越好。

CPU 缓存

CPU 缓存是处理器内部的一块小的存储单元,用来存储一些内存。不管如何,我们需要执行一些任务时,数据需要从内存传递到 CPU,CPU 的工作速度远快于内存,CPU 在大多数时间是在等待从内存传递过来的数据,而此时 CPU 是处于空闲状态的。为了解决这个问题,内存持续的向 CPU 缓存发送数据。

一般的处理器会有 2 ~ 3 Mb 的 CPU 缓存。高端的处理器会有 6 Mb 的 CPU 缓存,越大的缓存,意味着处理器更好。

印刷工艺

晶体管的大小就是处理器平板印刷的大小,尺寸通常是纳米,更小的尺寸意味者更紧凑。这可以让你有更多的核心,更小的面积,更小的能量消耗。

最新的 Intel 处理器有 14 nm 的印刷工艺。

热功耗设计(TDP)

代表着平均功耗,单位是瓦特,是在全核心激活以基础频率来处理 Intel 定义的高复杂度的负载时,处理器所散失的功耗。

所以,越低的热功耗设计对你越好。一个低的热功耗设计不仅可以更好的利用能量,而且产生更少的热量。

battery

桌面版的处理器通常消耗更多的能量,热功耗消耗的能量能在 40% 以上,相对应的移动版本只有不到桌面版本的 1/3。

内存支持

我们已经提到了处理器的架构是如何影响到我们能够使用的内存总量,但这只是理论上而已。在实际的应用中,我们所能够使用的内存的总量对于处理器的规格来说是足够的,它通常是由处理器规格详细规定的。

RAM

它也指出了内存所支持的 DDR 的版本号。

超频

前面我们讲过时钟频率,超频是程序强迫 CPU 执行更多的周期。游戏玩家经常会使他们的处理器超频,以此来获得更好的性能。这样确实会增加速度,但也会增加消耗的能量,产生更多的热量。

一些高端的处理器允许超频,如果我们想让一个不支持超频的处理器超频,我们需要在主板上安装一个新的 BIOS 。 这样通常会成功,但这种情况是不安全的,也是不建议的。

超线程(HT)

如果不能添加核心以满足特定的处理需要,那么超线程是建立一个虚拟核心的方式。

如果一个双核处理器有超线程,那么这个双核处理器就有两个物理核心和两个虚拟核心,在技术上讲,一个双核处理器拥有四个核心。

结论

处理器有许多相关的数据,这些对数字设备来说是最重要的部分。我们在选择设备时,我们应该在脑海中仔细的检查处理器在上面提到的数据。

时钟速度、核心数、CPU 缓存,以及架构是最重要的数据。印刷尺寸以及热功耗设计重要性差一些 。

仍然有疑惑? 欢迎评论,我会尽快回复的。


via: http://www.theitstuff.com/processors-everything-need-know

作者:Rishabh Kandari 译者:singledo 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

我们知道有问题,但是并不知道问题的详细情况。

(本文发表于 1 月份)最近 Windows 和 Linux 都发送了重大安全更新,为防范这个尚未完全公开的问题,在最坏的情况下,它可能会导致性能下降多达一半。

在过去的几周,Linux 内核陆续打了几个补丁。Microsoft 自 11 月份开始也内部测试了 Windows 更新,并且它预计在下周二的例行补丁中将这个改进推送到主流 Windows 构建版中。Microsoft 的 Azure 也在下周的维护窗口中做好了安排,而 Amazon 的 AWS 也安排在周五对相关的设施进行维护。

自从 Linux 第一个补丁 (参见 KPTI:内核页表隔离的当前的发展) 明确描绘了出现的错误以后。虽然 Linux 和 Windows 基于不同的考虑,对此持有不同的看法,但是这两个操作系统 —— 当然还有其它的 x86 操作系统,比如 FreeBSD 和 macOS — 对系统内存的处理采用了相同的方式,因为对于操作系统在这一部分特性是与底层的处理器高度耦合的。

保持地址跟踪

在一个系统中的每个内存字节都是隐性编码的,这些编码数字是每个字节的地址。早期的操作系统使用物理内存地址,但是,物理内存地址由于各种原因,它并不很合适。例如,在地址中经常会有空隙,并且(尤其是 32 位的系统上)物理地址很难操作,需要 36 位数字,甚至更多。

因此,现在操作系统完全依赖一个叫虚拟内存的概念。虚拟内存系统允许程序和内核一起在一个简单、清晰、统一的环境中各自去操作。而不是使用空隙和其它奇怪的东西的物理内存,每个程序和内核自身都使用虚拟地址去访问内存。这些虚拟地址是连续的 —— 不用担心有空隙 —— 并且合适的大小也更便于操作。32 位的程序仅可以看到 32 位的地址,而不用管物理地址是 36 位还是更多位。

虽然虚拟地址对每个软件几乎是透明的,但是,处理器最终还是需要知道虚拟地址引用的物理地址是哪个。因此,有一个虚拟地址到物理地址的映射,它保存在一个被称为页面表的数据结构中。操作系统构建页面表,使用一个由处理器决定的布局,并且处理器和操作系统在虚拟地址和物理地址之间进行转换时就需要用到页面表。

这个映射过程是非常重要的,它也是现代操作系统和处理器的重要基础,处理器有专用的缓存 — Translation Lookaside Buffer(简称 TLB)—— 它保存了一定数量的虚拟地址到物理地址的映射,这样就不需要每次都使用全部页面。

虚拟内存的使用为我们提供了很多除了简单寻址之外的有用的特性。其中最主要的是,每个程序都有了自己独立的一组虚拟地址,有了它自己的一组虚拟地址到物理地址的映射。这就是用于提供“内存保护”的关键技术,一个程序不能破坏或者篡改其它程序使用的内存,因为其它程序的内存并不在它的地址映射范围之内。

由于每个进程使用一个单独的映射,因此每个程序也就有了一个额外的页面表,这就使得 TLB 缓存很拥挤。TLB 并不大 —— 一般情况下总共可以容纳几百个映射 —— 而系统使用的页面表越多,TLB 能够包含的任何特定的虚拟地址到物理地址的映射就越少。

一半一半

为了更好地使用 TLB,每个主流的操作系统都将虚拟地址范围一分为二。一半用于程序;另一半用于内核。当进程切换时,仅有一半的页面表条目发生变化 —— 仅属于程序的那一半。内核的那一半是每个程序公用的(因为只有一个内核)并且因此它可以为每个进程使用相同的页面表映射。这对 TLB 的帮助非常大;虽然它仍然会丢弃属于进程的那一半内存地址映射;但是它还保持着另一半属于内核的映射。

这种设计并不是一成不变的。在 Linux 上做了一项工作,使它可以为一个 32 位的进程提供整个地址范围,而不用在内核页面表和每个进程之间共享。虽然这样为程序提供了更多的地址空间,但这是以牺牲性能为代价的,因为每次内核代码需要运行时,TLB 重新加载内核的页面表条目。因此,这种方法并没有广泛应用到 x86 的系统上。

在内核和每个程序之间分割虚拟地址的这种做法的一个负面影响是,内存保护被削弱了。如果内核有它自己的一组页面表和虚拟地址,它将在不同的程序之间提供相同的保护;内核内存将是简单的不可见。但是使用地址分割之后,用户程序和内核使用了相同的地址范围,并且从原理上来说,一个用户程序有可能去读写内核内存。

为避免这种明显不好的情况,处理器和虚拟地址系统有一个 “Ring” 或者 “模式”的概念。x86 处理器有许多 Ring,但是对于这个问题,仅有两个是相关的:“user” (Ring 3)和 “supervisor”(ring 0)。当运行普通的用户程序时,处理器将置为用户模式 (Ring 3)。当运行内核代码时,处理器将处于 Ring 0 —— supervisor 模式,也称为内核模式。

这些 Ring 也用于从用户程序中保护内核内存。页面表并不仅仅有虚拟地址到物理地址的映射;它也包含关于这些地址的元数据,包含哪个 Ring 可能访问哪个地址的信息。内核页面表条目被标记为仅有 Ring 0 可以访问;程序的条目被标记为任何 Ring 都可以访问。如果一个处于 Ring 3 中的进程去尝试访问标记为 Ring 0 的内存,处理器将阻止这个访问并生成一个意外错误信息。运行在 Ring 3 中的用户程序不能得到内核以及运行在 Ring 0 内存中的任何东西。

至少理论上是这样的。大量的补丁和更新表明,这个地方已经被突破了。这就是最大的谜团所在。

Ring 间迁移

这就是我们所知道的。每个现代处理器都执行一定数量的推测运行。例如,给一些指令,让两个数加起来,然后将结果保存在内存中,在查明内存中的目标是否可访问和可写入之前,一个处理器可能已经推测性地做了加法。在一些常见案例中,在地址可写入的地方,处理器节省了一些时间,因为它以并行方式计算出内存中的目标是什么。如果它发现目标位置不可写入 —— 例如,一个程序尝试去写入到一个没有映射的地址或压根就不存在的物理位置 —— 然后它将产生一个意外错误,而推测运行就白做了。

Intel 处理器,尤其是(虽然不是 AMD 的)允许对 Ring 3 代码进行推测运行并写入到 Ring 0 内存中的处理器上。处理器并不完全阻止这种写入,但是推测运行轻微扰乱了处理器状态,因为,为了查明目标位置是否可写入,某些数据已经被加载到缓存和 TLB 中。这又意味着一些操作可能快几个周期,或者慢几个周期,这取决于它们所需要的数据是否仍然在缓存中。除此之外,Intel 的处理器还有一些特殊的功能,比如,在 Skylake 处理器上引入的软件保护扩展(SGX)指令,它改变了一点点访问内存的方式。同样的,处理器仍然是保护 Ring 0 的内存不被来自 Ring 3 的程序所访问,但是同样的,它的缓存和其它内部状态已经发生了变化,产生了可测量的差异。

我们至今仍然并不知道具体的情况,到底有多少内核的内存信息泄露给了用户程序,或者信息泄露的情况有多容易发生。以及有哪些 Intel 处理器会受到影响?也或者并不完全清楚,但是,有迹象表明每个 Intel 芯片都使用了推测运行(是自 1995 年 Pentium Pro 以来的所有主流处理器吗?),它们都可能会因此而泄露信息。

这个问题第一次被披露是由来自 奥地利的 Graz Technical University 的研究者。他们披露的信息表明这个问题已经足够破坏内核模式地址空间布局随机化(内核 ASLR,或称 KASLR)。ASLR 是防范 缓冲区溢出 漏洞利用的最后一道防线。启用 ASLR 之后,程序和它们的数据被置于随机的内存地址中,它将使一些安全漏洞利用更加困难。KASLR 将这种随机化应用到内核中,这样就使内核的数据(包括页面表)和代码也随机化分布。

Graz 的研究者开发了 KAISER,一组防范这个问题的 Linux 内核补丁。

如果这个问题正好使 ASLR 的随机化被破坏了,这或许将成为一个巨大的灾难。ASLR 是一个非常强大的保护措施,但是它并不是完美的,这意味着对于黑客来说将是一个很大的障碍,一个无法逾越的障碍。整个行业对此的反应是 —— Windows 和 Linux 都有一个非常重要的变化,秘密开发 —— 这表明不仅是 ASLR 被破坏了,而且从内核泄露出信息的更普遍的技术被开发出来了。确实是这样的,研究者已经 在 Twitter 上发布信息,他们已经可以随意泄露和读取内核数据了。另一种可能是,漏洞可能被用于从虚拟机中“越狱”,并可能会危及 hypervisor。

Windows 和 Linux 选择的解决方案是非常相似的,将 KAISER 分为两个区域:内核页面表的条目不再是由每个进程共享。在 Linux 中,这被称为内核页面表隔离(KPTI)。

应用补丁后,内存地址仍然被一分为二:这样使内核的那一半几乎是空的。当然它并不是非常的空,因为一些内核片断需要永久映射,不论进程是运行在 Ring 3 还是 Ring 0 中,它都几乎是空的。这意味着如果恶意用户程序尝试去探测内核内存以及泄露信息,它将会失败 —— 因为那里几乎没有信息。而真正的内核页面中只有当内核自身运行的时刻它才能被用到。

这样做就破坏了最初将地址空间分割的理由。现在,每次切换到用户程序时,TLB 需要实时去清除与内核页面表相关的所有条目,这样就失去了启用分割带来的性能提升。

影响的具体大小取决于工作负载。每当一个程序被调入到内核 —— 从磁盘读入、发送数据到网络、打开一个文件等等 —— 这种调用的成本可能会增加一点点,因为它强制 TLB 清除了缓存并实时加载内核页面表。不使用内核的程序可能会观测到 2 - 3 个百分点的性能影响 —— 这里仍然有一些开销,因为内核仍然是偶尔会运行去处理一些事情,比如多任务等等。

但是大量调用进入到内核的工作负载将观测到很大的性能损失。在一个基准测试中,一个除了调入到内核之外什么都不做的程序,观察到 它的性能下降大约为 50%;换句话说就是,打补丁后每次对内核的调用的时间要比不打补丁调用内核的时间增加一倍。基准测试使用的 Linux 的网络回环(loopback)也观测到一个很大的影响,比如,在 Postgres 的基准测试中大约是 17%。真实的数据库负载使用了实时网络可能观测到的影响要低一些,因为使用实时网络时,内核调用的开销基本是使用真实网络的开销。

虽然对 Intel 系统的影响是众所周知的,但是它们可能并不是唯一受影响的。其它的一些平台,比如 SPARC 和 IBM 的 S390,是不受这个问题影响的,因为它们的处理器的内存管理并不需要分割地址空间和共享内核页面表;在这些平台上的操作系统一直就是将它们的内核页面表从用户模式中隔离出来的。但是其它的,比如 ARM,可能就没有这么幸运了;适用于 ARM Linux 的类似补丁 正在开发中。


PETER BRIGHT 是 Ars 的一位技术编辑。他涉及微软、编程及软件开发、Web 技术和浏览器、以及安全方面。它居住在纽约的布鲁克林。


via: https://arstechnica.com/gadgets/2018/01/whats-behind-the-intel-design-flaw-forcing-numerous-patches/

作者:PETER BRIGHT 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

lftp 是一个文件传输程序。它可以用于复杂的 FTP、 HTTP/HTTPS 和其他连接。如果指定了站点 URL,那么 lftp 将连接到该站点,否则会使用 open 命令建立连接。它是所有 Linux/Unix 命令行用户的必备工具。我目前写了一些关于 Linux 下超快命令行下载加速器,比如 Axel 和 prozilla。lftp 是另一个能做相同的事,但有更多功能的工具。lftp 可以处理几种文件访问方式:

  1. ftp
  2. ftps
  3. http
  4. https
  5. hftp
  6. fish
  7. sftp
  8. file

那么 lftp 的独特之处是什么?

  • lftp 中的每个操作都是可靠的,即任何非致命错误都被忽略,并且重复进行操作。所以如果下载中断,它会自动重新启动。即使 FTP 服务器不支持 REST 命令,lftp 也会尝试从开头检索文件,直到文件传输完成。
  • lftp 具有类似 shell 的命令语法,允许你在后台并行启动多个命令。
  • lftp 有一个内置的镜像功能,可以下载或更新整个目录树。还有一个反向镜像功能(mirror -R),它可以上传或更新服务器上的目录树。镜像也可以在两个远程服务器之间同步目录,如果可用的话会使用 FXP。

如何使用 lftp 作为下载加速器

lftppget 命令。它能让你并行下载。语法是:

lftp -e 'pget -n NUM -c url; exit'

例如,使用 pget 分 5个部分下载 http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.22.2.tar.bz2

$ cd /tmp 
$ lftp -e 'pget -n 5 -c http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.22.2.tar.bz2'

示例输出:

45108964 bytes transferred in 57 seconds (775.3K/s)
lftp :~>quit

这里:

  1. pget - 并行下载文件
  2. -n 5 - 将最大连接数设置为 5
  3. -c - 如果当前目录存在 lfile.lftp-pget-status,则继续中断的传输

如何在 Linux/Unix 中使用 lftp 来加速 ftp/https下载

再尝试添加 exit 命令:

$ lftp -e 'pget -n 10 -c https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.15.tar.xz; exit'

关于并行下载的说明

请注意,通过使用下载加速器,你将增加远程服务器负载。另请注意,lftp 可能无法在不支持多点下载的站点上工作,或者防火墙阻止了此类请求。

其它的命令提供了更多功能。有关更多信息,请参考 lftp 的 man 页面:

man lftp

关于作者

作者是 nixCraft 的创建者,经验丰富的系统管理员,也是 Linux 操作系统/Unix shell 脚本的培训师。他曾与全球客户以及IT、教育、国防和太空研究以及非营利部门等多个行业合作。在 Twitter、Facebook、Google + 上关注他。通过 RSS/XML 订阅获取最新的系统管理、Linux/Unix 以及开源主题教程。


via: https://www.cyberciti.biz/tips/linux-unix-download-accelerator.html

作者:Vivek Gite 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

有时,在使用命令行的时候,我们需要快速找到某一个命令的二进制文件所在位置。这种情况下可以选择 find 命令,但使用它会耗费时间,可能也会出现意料之外的情况。有一个专门为这种情况设计的命令:whereis

在这篇文章里,我们会通过一些便于理解的例子来解释这一命令的基础内容。但在这之前,值得说明的一点是,下面出现的所有例子都在 Ubuntu 16.04 LTS 下测试过。

Linux whereis 命令

whereis 命令可以帮助用户寻找某一命令的二进制文件,源码以及帮助页面。下面是它的格式:

whereis [options] [-BMS directory... -f] name...

这是这一命令的 man 页面给出的解释:

whereis 可以查找指定命令的二进制文件、源文件和帮助文件。 被找到的文件在显示时,会去掉主路径名,然后再去掉文件的(单个)尾部扩展名 (如: .c),来源于源代码控制的 s. 前缀也会被去掉。接下来,whereis 会尝试在标准的 Linux 位置里寻找具体程序,也会在由 $PATH$MANPATH 指定的路径中寻找。

下面这些以 Q&A 形式出现的例子,可以给你一个关于如何使用 whereis 命令的直观感受。

Q1. 如何用 whereis 命令寻找二进制文件所在位置?

假设你想找,比如说,whereis 命令自己所在位置。下面是你具体的操作:

whereis whereis

How to find location of binary file using whereis

需要注意的是,输出的第一个路径才是你想要的结果。使用 whereis 命令,同时也会显示帮助页面和源码所在路径。(如果能找到的情况下会显示,但是在这一例中没有找到)所以你在输出中看见的第二个路径就是帮助页面文件所在位置。

Q2. 怎么在搜索时规定只搜索二进制文件、帮助页面,还是源代码呢?

如果你想只搜索,假设说,二进制文件,你可以使用 -b 这一命令行选项。例如:

whereis -b cp

How to specifically search for binaries, manuals, or source code

类似的, -m-s 这两个 选项分别对应 帮助页面和源码。

Q3.如何限制 whereis 命令搜索位置?

默认情况下,whereis 是从由匹配符所定义的硬编码路径来寻找文件的。但如果你想的话,你可以用命令行选项来限制搜索。例如,如果你只想在 /usr/bin 寻找二进制文件,你可以用 -B 这一选项来实现。

whereis -B /usr/bin/ -f cp

注意:使用这种方式时可以给出多个路径。使用 -f 这一选项来明确分隔目录列表和要搜索的文件名。

类似的,如果你想只搜索帮助文件或源码,你可以对应使用 -M-S 这两个选项。

Q4. 如何查看 whereis 的搜索路径?

与此相对应的也有一个选项。只要在 whereis 后加上 -l

whereis -l

这是例子的部分输出结果:

How to see paths that whereis uses for search

Q5. 如何找到一个有异常条目的命令?

对于 whereis 命令来说,如果一个命令对每个显式的请求类型都不止一项,则该命令被视为异常。例如,没有可用文档的命令,或者对应文档分散在各处的命令都可以算作异常命令。 当使用 -u 这一选项,whereis 就会显示那些有异常条目的命令。

例如,下面这一例子就显示,在当前目录中,没有对应文档或有多个文档的命令。

whereis -m -u *

总结

我觉得,whereis 不是那种你需要经常使用的命令行工具。但在遇到某些特殊情况时,它绝对会让你的生活变得轻松。我们已经涉及了这一工具提供的一些重要命令行选项,所以要注意练习。想了解更多信息,直接去看它的 man 页面吧。


via: https://www.howtoforge.com/linux-whereis-command/

作者:Himanshu Arora 译者:wenwensnow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

怎样使用 curses 函数读取键盘并操作屏幕。

之前的文章介绍了 ncurses 库,并提供了一个简单的程序展示了一些将文本放到屏幕上的 curses 函数。在接下来的文章中,我将介绍如何使用其它的 curses 函数。

探险

当我逐渐长大,家里有了一台苹果 II 电脑。我和我兄弟正是在这台电脑上自学了如何用 AppleSoft BASIC 写程序。我在写了一些数学智力游戏之后,继续创造游戏。作为 80 年代的人,我已经是龙与地下城桌游的粉丝,在游戏中角色扮演一个追求打败怪物并在陌生土地上抢掠的战士或者男巫,所以我创建一个基本的冒险游戏也在情理之中。

AppleSoft BASIC 支持一种简洁的特性:在标准分辨率图形模式(GR 模式)下,你可以检测屏幕上特定点的颜色。这为创建一个冒险游戏提供了捷径。比起创建并更新周期性传送到屏幕的内存地图,我现在可以依赖 GR 模式为我维护地图,我的程序还可以在玩家的角色(LCTT 译注:此处 character 双关一个代表玩家的角色,同时也是一个字符)在屏幕四处移动的时候查询屏幕。通过这种方式,我让电脑完成了大部分艰难的工作。因此,我的自顶向下的冒险游戏使用了块状的 GR 模式图形来展示我的游戏地图。

我的冒险游戏使用了一张简单的地图,上面有一大片绿地伴着山脉从中间蔓延向下和一个在左上方的大湖。我要粗略地为桌游战役绘制这个地图,其中包含一个允许玩家穿过到远处的狭窄通道。

图 1. 一个有湖和山的简单桌游地图

你可以用 curses 绘制这个地图,并用字符代表草地、山脉和水。接下来,我描述怎样使用 curses 那样做,以及如何在 Linux 终端创建和进行类似的一个冒险游戏。

构建程序

在我的上一篇文章,我提到了大多数 curses 程序以相同的一组指令获取终端类型和设置 curses 环境:

initscr();
cbreak();
noecho();

在这个程序,我添加了另外的语句:

keypad(stdscr, TRUE);

这里的 TRUE 标志允许 curses 从用户终端读取小键盘和功能键。如果你想要在你的程序中使用上下左右方向键,你需要使用这里的 keypad(stdscr, TRUE)

这样做了之后,你现在可以开始在终端屏幕上绘图了。curses 函数包括了一系列在屏幕上绘制文本的方法。在我之前的文章中,我展示了 addch()addstr() 函数以及在添加文本之前先移动到指定屏幕位置的对应函数 mvaddch()mvaddstr()。为了在终端上创建这个冒险游戏的地图,你可以使用另外一组函数:vline()hline(),以及它们对应的函数 mvvline()mvhline()。这些 mv 函数接受屏幕坐标、一个要绘制的字符和要重复此字符的次数的参数。例如,mvhline(1, 2, '-', 20) 将会绘制一条开始于第一行第二列并由 20 个横线组成的线段。

为了以编程方式绘制地图到终端屏幕上,让我们先定义这个 draw_map() 函数:

#define GRASS     ' '
#define EMPTY     '.'
#define WATER     '~'
#define MOUNTAIN  '^'
#define PLAYER    '*'

void draw_map(void)
{
    int y, x;

    /* 绘制探索地图 */

    /* 背景 */

    for (y = 0; y < LINES; y++) {
        mvhline(y, 0, GRASS, COLS);
    }

    /* 山和山道 */

    for (x = COLS / 2; x < COLS * 3 / 4; x++) {
        mvvline(0, x, MOUNTAIN, LINES);
    }

    mvhline(LINES / 4, 0, GRASS, COLS);

    /* 湖 */

    for (y = 1; y < LINES / 2; y++) {
        mvhline(y, 1, WATER, COLS / 3);
    }
}

在绘制这副地图时,记住填充大块字符到屏幕所使用的 mvvline()mvhline() 函数。我绘制从 0 列开始的字符水平线(mvhline)以创建草地区域,直到占满整个屏幕的高度和宽度。我绘制从 0 行开始的多条垂直线(mvvline)在此上添加了山脉,绘制单行水平线添加了一条山道(mvhline)。并且,我通过绘制一系列短水平线(mvhline)创建了湖。这种绘制重叠方块的方式看起来似乎并没有效率,但是记住在我们调用 refresh() 函数之前 curses 并不会真正更新屏幕。

绘制完地图,创建游戏就还剩下进入循环让程序等待用户按下上下左右方向键中的一个然后让玩家图标正确移动了。如果玩家想要移动的地方是空的,就应该允许玩家到那里。

你可以把 curses 当做捷径使用。比起在程序中实例化一个版本的地图并复制到屏幕这么复杂,你可以让屏幕为你跟踪所有东西。inch() 函数和相关联的 mvinch() 函数允许你探测屏幕的内容。这让你可以查询 curses 以了解玩家想要移动到的位置是否被水填满或者被山阻挡。这样做你需要一个之后会用到的一个帮助函数:

int is_move_okay(int y, int x)
{
    int testch;

    /* 如果要进入的位置可以进入,返回 true */

    testch = mvinch(y, x);
    return ((testch == GRASS) || (testch == EMPTY));
}

如你所见,这个函数探测行 x、列 y 并在空间未被占据的时候返回 true,否则返回 false

这样我们写移动循环就很容易了:从键盘获取一个键值然后根据是上下左右键移动用户字符。这里是一个这种循环的简单版本:


    do {
        ch = getch();

        /* 测试输入的值并获取方向 */

        switch (ch) {
        case KEY_UP:
            if ((y > 0) && is_move_okay(y - 1, x)) {
                y = y - 1;
            }
            break;
        case KEY_DOWN:
            if ((y < LINES - 1) && is_move_okay(y + 1, x)) {
                y = y + 1;
            }
            break;
        case KEY_LEFT:
            if ((x > 0) && is_move_okay(y, x - 1)) {
                x = x - 1;
            }
            break;
        case KEY_RIGHT
            if ((x < COLS - 1) && is_move_okay(y, x + 1)) {
                x = x + 1;
            }
            break;
        }
    }
    while (1);

为了在游戏中使用这个循环,你需要在循环里添加一些代码来启用其它的键(例如传统的移动键 WASD),以提供让用户退出游戏和在屏幕上四处移动的方法。这里是完整的程序:

/* quest.c */

#include 
#include 

#define GRASS     ' '
#define EMPTY     '.'
#define WATER     '~'
#define MOUNTAIN  '^'
#define PLAYER    '*'

int is_move_okay(int y, int x);
void draw_map(void);

int main(void)
{
    int y, x;
    int ch;

    /* 初始化curses */

    initscr();
    keypad(stdscr, TRUE);
    cbreak();
    noecho();

    clear();

    /* 初始化探索地图 */

    draw_map();

    /* 在左下角初始化玩家 */

    y = LINES - 1;
    x = 0;

    do {
    /* 默认获得一个闪烁的光标--表示玩家字符 */

    mvaddch(y, x, PLAYER);
    move(y, x);
    refresh();

    ch = getch();

    /* 测试输入的键并获取方向 */

    switch (ch) {
    case KEY_UP:
    case 'w':
    case 'W':
        if ((y > 0) && is_move_okay(y - 1, x)) {
        mvaddch(y, x, EMPTY);
        y = y - 1;
        }
        break;
    case KEY_DOWN:
    case 's':
    case 'S':
        if ((y < LINES - 1) && is_move_okay(y + 1, x)) {
        mvaddch(y, x, EMPTY);
        y = y + 1;
        }
        break;
    case KEY_LEFT:
    case 'a':
    case 'A':
        if ((x > 0) && is_move_okay(y, x - 1)) {
        mvaddch(y, x, EMPTY);
        x = x - 1;
        }
        break;
    case KEY_RIGHT:
    case 'd':
    case 'D':
        if ((x < COLS - 1) && is_move_okay(y, x + 1)) {
        mvaddch(y, x, EMPTY);
        x = x + 1;
        }
        break;
    }
    }
    while ((ch != 'q') && (ch != 'Q'));

    endwin();

    exit(0);
}

int is_move_okay(int y, int x)
{
    int testch;

    /* 当空间可以进入时返回true */

    testch = mvinch(y, x);
    return ((testch == GRASS) || (testch == EMPTY));
}

void draw_map(void)
{
    int y, x;

    /* 绘制探索地图 */

    /* 背景 */

    for (y = 0; y < LINES; y++) {
    mvhline(y, 0, GRASS, COLS);
    }

    /* 山脉和山道 */

    for (x = COLS / 2; x < COLS * 3 / 4; x++) {
    mvvline(0, x, MOUNTAIN, LINES);
    }

    mvhline(LINES / 4, 0, GRASS, COLS);

    /* 湖 */

    for (y = 1; y < LINES / 2; y++) {
    mvhline(y, 1, WATER, COLS / 3);
    }
}

在完整的程序清单中,你可以看见使用 curses 函数创建游戏的完整布置:

  1. 初始化 curses 环境。
  2. 绘制地图。
  3. 初始化玩家坐标(左下角)
  4. 循环:

    • 绘制玩家的角色。
    • 从键盘获取键值。
    • 对应地上下左右调整玩家坐标。
    • 重复。
  5. 完成时关闭curses环境并退出。

开始玩

当你运行游戏时,玩家的字符在左下角初始化。当玩家在游戏区域四处移动的时候,程序创建了“一串”点。这样可以展示玩家经过了的点,让玩家避免经过不必要的路径。

图 2. 初始化在左下角的玩家

图 3. 玩家可以在游戏区域四处移动,例如湖周围和山的通道

为了创建上面这样的完整冒险游戏,你可能需要在他/她的角色在游戏区域四处移动的时候随机创建不同的怪物。你也可以创建玩家可以发现在打败敌人后可以掠夺的特殊道具,这些道具应能提高玩家的能力。

但是作为起点,这是一个展示如何使用 curses 函数读取键盘和操纵屏幕的好程序。

下一步

这是一个如何使用 curses 函数更新和读取屏幕和键盘的简单例子。按照你的程序需要做什么,curses 可以做得更多。在下一篇文章中,我计划展示如何更新这个简单程序以使用颜色。同时,如果你想要学习更多 curses,我鼓励你去读位于 Linux 文档计划的 Pradeep Padala 写的如何使用 NCURSES 编程


via: http://www.linuxjournal.com/content/creating-adventure-game-terminal-ncurses

作者:Jim Hall 译者:Leemeans 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出