2017年4月

Inxi 最初是为控制台和 IRC(网络中继聊天)开发的一个强大且优秀的命令行系统信息脚本。可以使用它获取用户的硬件和系统信息,它也用于调试或者社区技术支持工具。

使用 Inxi 可以很容易的获取所有的硬件信息:硬盘、声卡、显卡、网卡、CPU 和 RAM 等。同时也能够获取大量的操作系统信息,比如硬件驱动、Xorg 、桌面环境、内核、GCC 版本,进程,开机时间和内存等信息。

运行在命令行和 IRC 上的 Inxi 输出略有不同,IRC 上会有一些可供用户使用的默认过滤器和颜色选项。支持的 IRC 客户端有:BitchX、Gaim/Pidgin、ircII、Irssi、 Konversation、 Kopete、 KSirc、 KVIrc、 Weechat 和 Xchat 以及其它的一些客户端,它们具有展示内置或外部 Inxi 输出的能力。

在 Linux 系统上安装 Inxi

大多数主流 Linux 发行版的仓库中都有 Inxi ,包括大多数 BSD 系统。

$ sudo apt-get install inxi   [On Debian/Ubuntu/Linux Mint]
$ sudo yum install inxi       [On CentOs/RHEL/Fedora]
$ sudo dnf install inxi       [On Fedora 22+]

在使用 Inxi 之前,用下面的命令查看 Inxi 所有依赖和推荐的应用,以及各种目录,并显示需要安装哪些包来支持给定的功能。

$ inxi --recommends 

Inxi 的输出:

inxi will now begin checking for the programs it needs to operate. First a check of the main languages and tools
inxi uses. Python is only for debugging data collection.
---------------------------------------------------------------------------
Bash version: 4.3.42(1)-release
Gawk version: 4.1.3,
Sed version: 
Sudo version: 1.8.16
Python version: 2.7.12
---------------------------------------------------------------------------
Test One: Required System Directories (Linux Only).
If one of these system directories is missing, inxi cannot operate:
/proc....................................................................... Present
/sys........................................................................ Present
All the  directories are present.
---------------------------------------------------------------------------
Test Two: Required Core Applications.
If one of these applications is missing, inxi cannot operate:
df (info: partition data)................................................... /bin/df
gawk (info: core tool)...................................................... /usr/bin/gawk
grep (info: string search).................................................. /bin/grep
lspci (info: hardware data)................................................. /usr/bin/lspci
ps (info: process data)..................................................... /bin/ps
readlink.................................................................... /bin/readlink
sed (info: string replace).................................................. /bin/sed
tr (info: character replace)................................................ /usr/bin/tr
uname (info: kernel data)................................................... /bin/uname
wc (info: word character count)............................................. /usr/bin/wc
All the  applications are present.
---------------------------------------------------------------------------
Test Three: Script Recommends for Graphics Features.
NOTE: If you do not use X these do not matter (like a headless server). Otherwise, if one of these applications
is missing, inxi will have incomplete output:
glxinfo (info: -G glx info)................................................. /usr/bin/glxinfo
xdpyinfo (info: -G multi screen resolution)................................. /usr/bin/xdpyinfo
xprop (info: -S desktop data)............................................... /usr/bin/xprop
xrandr (info: -G single screen resolution).................................. /usr/bin/xrandr
All the  applications are present.
---------------------------------------------------------------------------
Test Four: Script Recommends for Remaining Features.
If one of these applications is missing, inxi will have incomplete output:
dig (info: -i first wlan ip default test)................................... /usr/bin/dig
dmidecode (info: -M if no sys machine data; -m memory)...................... /usr/sbin/dmidecode
file (info: -o unmounted file system)....................................... /usr/bin/file
hciconfig (info: -n -i bluetooth data)...................................... /bin/hciconfig
hddtemp (info: -Dx show hdd temp)........................................... /usr/sbin/hddtemp
ifconfig (info: -i ip lan-deprecated)....................................... /sbin/ifconfig
ip (info: -i ip lan)........................................................ /sbin/ip
sensors (info: -s sensors output)........................................... /usr/bin/sensors
strings (info: -I sysvinit version)......................................... /usr/bin/strings
lsusb (info: -A usb audio;-N usb networking)................................ /usr/bin/lsusb
modinfo (info: -Ax,-Nx module version)...................................... /sbin/modinfo
runlevel (info: -I runlevel)................................................ /sbin/runlevel
sudo (info: -Dx hddtemp-user;-o file-user).................................. /usr/bin/sudo
uptime (info: -I uptime (check which package owns Debian)).................. /usr/bin/uptime
All the  applications are present.
---------------------------------------------------------------------------
Test Five: Script Recommends for Remaining Features.
One of these downloaders needed for options -i/-w/-W (-U/-! [11-15], if supported):
wget (info: -i wan ip;-w/-W;-U/-! [11-15] (if supported))................... /usr/bin/wget
curl (info: -i wan ip;-w/-W;-U/-! [11-15] (if supported))................... /usr/bin/curl
All the  applications are present.
---------------------------------------------------------------------------
Test Six: System Directories for Various Information.
(Unless otherwise noted, these are for GNU/Linux systems)
If one of these directories is missing, inxi may have incomplete output:
/sys/class/dmi/id (info: -M system, motherboard, bios)...................... Present
/dev (info: -l,-u,-o,-p,-P,-D disk partition data).......................... Present
/dev/disk/by-label (info: -l,-o,-p,-P partition labels)..................... Present
/dev/disk/by-uuid (info: -u,-o,-p,-P partition uuid)........................ Present
All the  directories are present.
---------------------------------------------------------------------------
Test Seven: System Files for Various Information.
(Unless otherwise noted, these are for GNU/Linux systems)
If one of these files is missing, inxi may have incomplete output:
/proc/asound/cards (info: -A sound card data)............................... Present
/proc/asound/version (info: -A ALSA data)................................... Present
/proc/cpuinfo (info: -C cpu data)........................................... Present
/etc/lsb-release (info: -S distro version data [deprecated])................ Present
/proc/mdstat (info: -R mdraid data)......................................... Present
/proc/meminfo (info: -I memory data)........................................ Present
/etc/os-release (info: -S distro version data).............................. Present
/proc/partitions (info: -p,-P partitions data).............................. Present
/proc/modules (info: -G module data)........................................ Present
/proc/mounts (info: -P,-p partition advanced data).......................... Present
/var/run/dmesg.boot (info: -D,-d disk data [BSD only])...................... Missing
/proc/scsi/scsi (info: -D Advanced hard disk data [used rarely])............ Present
/var/log/Xorg.0.log (info: -G graphics driver load status).................. Present
The following files are missing from your system:
File: /var/run/dmesg.boot
---------------------------------------------------------------------------
All tests completed.

Inxi 工具的基本用法

用下面的基本用法获取系统和硬件的详细信息。

获取 Linux 系统信息

Inix 不加任何选项就能输出下面的信息:CPU 、内核、开机时长、内存大小、硬盘大小、进程数、登录终端以及 Inxi 版本。

$ inxi
CPU~Dual core Intel Core i5-4210U (-HT-MCP-) speed/max~2164/2700 MHz Kernel~4.4.0-21-generic x86_64 Up~3:15 Mem~3122.0/7879.9MB HDD~1000.2GB(20.0% used) Procs~234 Client~Shell inxi~2.2.35

获取内核和发行版本信息

使用 Inxi 的 -S 选项查看本机系统信息(主机名、内核信息、桌面环境和发行版):

$ inxi -S
System: Host: TecMint Kernel: 4.4.0-21-generic x86_64 (64 bit) Desktop: Cinnamon 3.0.7
Distro: Linux Mint 18 Sarah

获取电脑机型

使用 -M 选项查看机型(笔记本/台式机)、产品 ID 、机器版本、主板、制造商和 BIOS 等信息:

$ inxi -M
Machine:   System: LENOVO (portable) product: 20354 v: Lenovo Z50-70
Mobo: LENOVO model: Lancer 5A5 v: 31900059WIN Bios: LENOVO v: 9BCN26WW date: 07/31/2014

获取 CPU 及主频信息

使用 -C 选项查看完整的 CPU 信息,包括每核 CPU 的频率及可用的最大主频。

$ inxi -C
CPU:       Dual core Intel Core i5-4210U (-HT-MCP-) cache: 3072 KB 
clock speeds: max: 2700 MHz 1: 1942 MHz 2: 1968 MHz 3: 1734 MHz 4: 1710 MHz

获取显卡信息

使用 -G 选项查看显卡信息,包括显卡类型、显示服务器、系统分辨率、GLX 渲染器和 GLX 版本等等(LCTT 译注: GLX 是一个 X 窗口系统的 OpenGL 扩展)。

$ inxi -G
Graphics:  Card-1: Intel Haswell-ULT Integrated Graphics Controller
Card-2: NVIDIA GM108M [GeForce 840M]
Display Server: X.Org 1.18.4 drivers: intel (unloaded: fbdev,vesa) Resolution: [email protected]
GLX Renderer: Mesa DRI Intel Haswell Mobile GLX Version: 3.0 Mesa 11.2.0

获取声卡信息

使用 -A 选项查看声卡信息:

$ inxi -A
Audio:     Card-1 Intel 8 Series HD Audio Controller driver: snd_hda_intel Sound: ALSA v: k4.4.0-21-generic
Card-2 Intel Haswell-ULT HD Audio Controller driver: snd_hda_intel

获取网卡信息

使用 -N 选项查看网卡信息:

$ inxi -N
Network:   Card-1: Realtek RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller driver: r8169
Card-2: Realtek RTL8723BE PCIe Wireless Network Adapter driver: rtl8723be

获取硬盘信息

使用 -D 选项查看硬盘信息(大小、ID、型号):

$ inxi -D
Drives:    HDD Total Size: 1000.2GB (20.0% used) ID-1: /dev/sda model: ST1000LM024_HN size: 1000.2GB

获取简要的系统信息

使用 -b 选项显示上述信息的简要系统信息:

$ inxi -b 
System:    Host: TecMint Kernel: 4.4.0-21-generic x86_64 (64 bit) Desktop: Cinnamon 3.0.7
Distro: Linux Mint 18 Sarah
Machine:   System: LENOVO (portable) product: 20354 v: Lenovo Z50-70
Mobo: LENOVO model: Lancer 5A5 v: 31900059WIN Bios: LENOVO v: 9BCN26WW date: 07/31/2014
CPU:       Dual core Intel Core i5-4210U (-HT-MCP-) speed/max: 2018/2700 MHz
Graphics:  Card-1: Intel Haswell-ULT Integrated Graphics Controller
Card-2: NVIDIA GM108M [GeForce 840M]
Display Server: X.Org 1.18.4 drivers: intel (unloaded: fbdev,vesa) Resolution: [email protected]
GLX Renderer: Mesa DRI Intel Haswell Mobile GLX Version: 3.0 Mesa 11.2.0
Network:   Card-1: Realtek RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller driver: r8169
Card-2: Realtek RTL8723BE PCIe Wireless Network Adapter driver: rtl8723be
Drives:    HDD Total Size: 1000.2GB (20.0% used)
Info:      Processes: 233 Uptime: 3:23 Memory: 3137.5/7879.9MB Client: Shell (bash) inxi: 2.2.35  

获取硬盘分区信息

使用 -p 选项输出完整的硬盘分区信息,包括每个分区的分区大小、已用空间、可用空间、文件系统以及文件系统类型。

$ inxi -p
Partition: ID-1: / size: 324G used: 183G (60%) fs: ext4 dev: /dev/sda10
ID-2: swap-1 size: 4.00GB used: 0.00GB (0%) fs: swap dev: /dev/sda9

获取完整的 Linux 系统信息

使用 -F 选项查看可以完整的 Inxi 输出(安全起见比如网络 IP 地址信息没有显示,下面的示例只显示部分输出信息):

$ inxi -F 
System:    Host: TecMint Kernel: 4.4.0-21-generic x86_64 (64 bit) Desktop: Cinnamon 3.0.7
Distro: Linux Mint 18 Sarah
Machine:   System: LENOVO (portable) product: 20354 v: Lenovo Z50-70
Mobo: LENOVO model: Lancer 5A5 v: 31900059WIN Bios: LENOVO v: 9BCN26WW date: 07/31/2014
CPU:       Dual core Intel Core i5-4210U (-HT-MCP-) cache: 3072 KB 
clock speeds: max: 2700 MHz 1: 1716 MHz 2: 1764 MHz 3: 1776 MHz 4: 1800 MHz
Graphics:  Card-1: Intel Haswell-ULT Integrated Graphics Controller
Card-2: NVIDIA GM108M [GeForce 840M]
Display Server: X.Org 1.18.4 drivers: intel (unloaded: fbdev,vesa) Resolution: [email protected]
GLX Renderer: Mesa DRI Intel Haswell Mobile GLX Version: 3.0 Mesa 11.2.0
Audio:     Card-1 Intel 8 Series HD Audio Controller driver: snd_hda_intel Sound: ALSA v: k4.4.0-21-generic
Card-2 Intel Haswell-ULT HD Audio Controller driver: snd_hda_intel
Network:   Card-1: Realtek RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller driver: r8169
IF: enp1s0 state: up speed: 100 Mbps duplex: full mac: 28:d2:44:eb:bd:98
Card-2: Realtek RTL8723BE PCIe Wireless Network Adapter driver: rtl8723be
IF: wlp2s0 state: down mac: 38:b1:db:7c:78:c7
Drives:    HDD Total Size: 1000.2GB (20.0% used) ID-1: /dev/sda model: ST1000LM024_HN size: 1000.2GB
Partition: ID-1: / size: 324G used: 183G (60%) fs: ext4 dev: /dev/sda10
ID-2: swap-1 size: 4.00GB used: 0.00GB (0%) fs: swap dev: /dev/sda9
RAID:      No RAID devices: /proc/mdstat, md_mod kernel module present
Sensors:   System Temperatures: cpu: 56.0C mobo: N/A
Fan Speeds (in rpm): cpu: N/A
Info:      Processes: 234 Uptime: 3:26 Memory: 3188.9/7879.9MB Client: Shell (bash) inxi: 2.2.35 

使用 Inxi 工具监控 Linux 系统

下面是监控 Linux 系统进程、开机时间和内存的几个选项的使用方法。

监控 Linux 进程的内存使用

使用下面的命令查看进程数、开机时间和内存使用情况:

$ inxi -I
Info:      Processes: 232 Uptime: 3:35 Memory: 3256.3/7879.9MB Client: Shell (bash) inxi: 2.2.35 

监控进程占用的 CPU 和内存资源

Inxi 默认显示 前 5 个最消耗 CPU 和内存的进程-t 选项和 c 选项一起使用查看前 5 个最消耗 CPU 资源的进程,查看最消耗内存的进程使用 -t 选项和 m 选项; -t选项 和 cm 选项一起使用显示前 5 个最消耗 CPU 和内存资源的进程。

----------------- Linux CPU Usage ----------------- 
$ inxi -t c 
Processes: CPU: % used - top 5 active
1: cpu: 53.7% command: plugin-container pid: 3066
2: cpu: 20.0% command: java pid: 1527
3: cpu: 19.7% command: firefox pid: 3018
4: cpu: 4.6% command: Xorg pid: 2114
5: cpu: 3.0% command: cinnamon pid: 2835
----------------- Linux Memoery Usage ----------------- 
$ inxi -t m
Processes: Memory: MB / % used - Used/Total: 3212.5/7879.9MB - top 5 active
1: mem: 980.51MB (12.4%) command: plugin-container pid: 3066
2: mem: 508.96MB (6.4%) command: java pid: 1527
3: mem: 507.89MB (6.4%) command: firefox pid: 3018
4: mem: 244.05MB (3.0%) command: chrome pid: 7405
5: mem: 211.46MB (2.6%) command: chrome pid: 6146
----------------- Linux CPU and Memory Usage ----------------- 
$ inxi -t cm
Processes: CPU: % used - top 5 active
1: cpu: 53.7% command: plugin-container pid: 3066
2: cpu: 20.0% command: java pid: 1527
3: cpu: 19.7% command: firefox pid: 3018
4: cpu: 4.6% command: Xorg pid: 2114
5: cpu: 3.0% command: cinnamon pid: 2835
Memory: MB / % used - Used/Total: 3223.6/7879.9MB - top 5 active
1: mem: 991.93MB (12.5%) command: plugin-container pid: 3066
2: mem: 508.96MB (6.4%) command: java pid: 1527
3: mem: 507.86MB (6.4%) command: firefox pid: 3018
4: mem: 244.45MB (3.1%) command: chrome pid: 7405
5: mem: 211.68MB (2.6%) command: chrome pid: 6146

可以在选项 cm 后跟一个整数(在 1-20 之间)设置显示多少个进程,下面的命令显示了前 10 个最消耗 CPU 和内存的进程:

$ inxi -t cm10
Processes: CPU: % used - top 10 active
1: cpu: 53.4% command: plugin-container pid: 3066
2: cpu: 19.8% command: java pid: 1527
3: cpu: 19.5% command: firefox pid: 3018
4: cpu: 4.5% command: Xorg pid: 2114
5: cpu: 3.0% command: cinnamon pid: 2835
6: cpu: 2.8% command: chrome pid: 7405
7: cpu: 1.1% command: pulseaudio pid: 2733
8: cpu: 1.0% command: soffice.bin pid: 7799
9: cpu: 0.9% command: chrome pid: 5763
10: cpu: 0.5% command: chrome pid: 6179
Memory: MB / % used - Used/Total: 3163.1/7879.9MB - top 10 active
1: mem: 976.82MB (12.3%) command: plugin-container pid: 3066
2: mem: 511.70MB (6.4%) command: java pid: 1527
3: mem: 466.01MB (5.9%) command: firefox pid: 3018
4: mem: 244.40MB (3.1%) command: chrome pid: 7405
5: mem: 203.71MB (2.5%) command: chrome pid: 6146
6: mem: 199.74MB (2.5%) command: chrome pid: 5763
7: mem: 168.30MB (2.1%) command: cinnamon pid: 2835
8: mem: 165.51MB (2.1%) command: soffice.bin pid: 7799
9: mem: 158.91MB (2.0%) command: chrome pid: 6179
10: mem: 151.83MB (1.9%) command: mysqld pid: 1259

监控网络设备

下面的命令会列出网卡信息,包括接口信息、网络频率、mac 地址、网卡状态和网络 IP 等信息。

$ inxi -Nni
Network:   Card-1: Realtek RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller driver: r8169
IF: enp1s0 state: up speed: 100 Mbps duplex: full mac: 28:d2:44:eb:bd:98
Card-2: Realtek RTL8723BE PCIe Wireless Network Adapter driver: rtl8723be
IF: wlp2s0 state: down mac: 38:b1:db:7c:78:c7
WAN IP: 111.91.115.195 IF: wlp2s0 ip-v4: N/A
IF: enp1s0 ip-v4: 192.168.0.103

监控 CPU 温度和电脑风扇转速

可以使用 -s 选项监控 配置了传感器的机器 获取温度和风扇转速:

$ inxi -s
Sensors:   System Temperatures: cpu: 53.0C mobo: N/A
Fan Speeds (in rpm): cpu: N/A

用 Linux 查看天气预报

使用 -w 选项查看本地区的天气情况(虽然使用的 API 可能不是很可靠),使用 -W <different_location> 设置另外的地区。

$ inxi -w
Weather:   Conditions: 93 F (34 C) - smoke Time: February 20, 1:38 PM IST
$ inxi -W Mumbai,India
Weather:   Conditions: 93 F (34 C) - smoke Time: February 20, 1:38 PM IST
$ inxi -W Nairobi,Kenya
Weather:   Conditions: 70 F (21 C) - Mostly Cloudy Time: February 20, 11:08 AM EAT

查看所有的 Linux 仓库信息

另外,可以使用 -r 选项查看一个 Linux 发行版的仓库信息:

$ inxi -r 
Repos:     Active apt sources in file: /etc/apt/sources.list.d/dawidd0811-neofetch-xenial.list
deb http://ppa.launchpad.net/dawidd0811/neofetch/ubuntu xenial main
deb-src http://ppa.launchpad.net/dawidd0811/neofetch/ubuntu xenial main
Active apt sources in file: /etc/apt/sources.list.d/dhor-myway-xenial.list
deb http://ppa.launchpad.net/dhor/myway/ubuntu xenial main
deb-src http://ppa.launchpad.net/dhor/myway/ubuntu xenial main
Active apt sources in file: /etc/apt/sources.list.d/official-package-repositories.list
deb http://packages.linuxmint.com sarah main upstream import backport
deb http://archive.ubuntu.com/ubuntu xenial main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu xenial-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu xenial-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://archive.canonical.com/ubuntu/ xenial partner
Active apt sources in file: /etc/apt/sources.list.d/qbittorrent-team-qbittorrent-stable-xenial.list
deb http://ppa.launchpad.net/qbittorrent-team/qbittorrent-stable/ubuntu xenial main
deb-src http://ppa.launchpad.net/qbittorrent-team/qbittorrent-stable/ubuntu xenial main
Active apt sources in file: /etc/apt/sources.list.d/slgobinath-safeeyes-xenial.list
deb http://ppa.launchpad.net/slgobinath/safeeyes/ubuntu xenial main
deb-src http://ppa.launchpad.net/slgobinath/safeeyes/ubuntu xenial main
Active apt sources in file: /etc/apt/sources.list.d/snwh-pulp-xenial.list
deb http://ppa.launchpad.net/snwh/pulp/ubuntu xenial main
deb-src http://ppa.launchpad.net/snwh/pulp/ubuntu xenial main
Active apt sources in file: /etc/apt/sources.list.d/twodopeshaggy-jarun-xenial.list
deb http://ppa.launchpad.net/twodopeshaggy/jarun/ubuntu xenial main
deb-src http://ppa.launchpad.net/twodopeshaggy/jarun/ubuntu xenial main
Active apt sources in file: /etc/apt/sources.list.d/ubuntu-mozilla-security-ppa-xenial.list
deb http://ppa.launchpad.net/ubuntu-mozilla-security/ppa/ubuntu xenial main
deb-src http://ppa.launchpad.net/ubuntu-mozilla-security/ppa/ubuntu xenial main

下面是查看 Inxi 的安装版本、快速帮助和打开 man 主页的方法,以及更多的 Inxi 使用细节。

$ inxi -v   #显示版本信息
$ inxi -h   #快速帮助
$ man inxi  #打开 man 主页

浏览 Inxi 的官方 GitHub 主页 https://github.com/smxi/inxi 查看更多的信息。

Inxi 是一个功能强大的获取硬件和系统信息的命令行工具。这也是我使用过的最好的 获取硬件和系统信息的命令行工具 之一。

写下你的评论。如果你知道其他的像 Inxi 这样的工具,我们很高兴和你一起讨论。


作者简介:

Aaron Kili 是一个 Linux 和 F.O.S.S 的狂热爱好者,即任的 Linux 系统管理员,web 开发者,TecMint 网站的专栏作者,他喜欢使用计算机工作,并且乐于分享计算机技术。


via: http://www.tecmint.com/inxi-command-to-find-linux-system-information/

作者:Aaron Kili 译者:vim-kakali 校对:wxy

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

这是一篇用屏幕截图解释如何在 AWS EC2 Linux 服务器上打开端口的教程。它能帮助你管理 EC2 服务器上特定端口的服务。

AWS(即 Amazon Web Services)不是 IT 世界中的新术语了。它是亚马逊提供的云服务平台。它的免费帐户能为你提供一年的有限免费服务。这是尝试新技术而不用花费金钱的最好的方式之一。

AWS 提供服务器计算作为他们的服务之一,他们称之为 EC(弹性计算)。使用它可以构建我们的 Linux 服务器。我们已经看到了如何在 AWS 上设置免费的 Linux 服务器了。

默认情况下,所有基于 EC2 的 Linux 服务器都只打开 22 端口,即 SSH 服务端口(允许所有 IP 的入站连接)。因此,如果你托管了任何特定端口的服务,则要为你的服务器在 AWS 防火墙上打开相应端口。

同样它的 1 到 65535 的端口是打开的(对于所有出站流量)。如果你想改变这个,你可以使用下面的方法编辑出站规则。

在 AWS 上为你的服务器设置防火墙规则很容易。你能够在几秒钟内为你的服务器打开端口。我将用截图指导你如何打开 EC2 服务器的端口。

步骤 1 :

登录 AWS 帐户并进入 EC2 管理控制台。进入 “网络及安全” Network & Security 菜单下的 安全组 Security Groups ,如下高亮显示:

AWS EC2 management console

AWS EC2 管理控制台

步骤 2 :

安全组 Security Groups 中选择你的 EC2 服务器,并在 行动 Actions 菜单下选择 编辑入站规则 Edit inbound rules

AWS inbound rules

AWS 入站规则菜单

步骤 3:

现在你会看到入站规则窗口。你可以在此处添加/编辑/删除入站规则。这有几个如 http、nfs 等列在下拉菜单中,它们可以为你自动填充端口。如果你有自定义服务和端口,你也可以定义它。

AWS add inbound rule

AWS 添加入站规则

比如,如果你想要打开 80 端口,你需要选择:

  • 类型:http
  • 协议:TCP
  • 端口范围:80
  • 源:任何来源:打开 80 端口接受来自“任何IP(0.0.0.0/0)”的请求;我的 IP:那么它会自动填充你当前的公共互联网 IP

步骤 4:

就是这样了。保存完毕后,你的服务器入站 80 端口将会打开!你可以通过 telnet 到 EC2 服务器公共域名的 80 端口来检验(可以在 EC2 服务器详细信息中找到)。

你也可以在 ping.eu 等网站上检验。


同样的方式可以编辑出站规则,这些更改都是即时生效的。


via: http://kerneltalks.com/virtualization/how-to-open-port-on-aws-ec2-linux-server/

作者:Shrikant Lavhate 译者:geekpi 校对:jasminepeng

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

一些 Fedora 用户把大部分甚至是所有时间花费在了命令行终端上。 终端可让您访问整个系统,以及数以千计的强大的实用程序。 但是,它默认情况下一次只显示一个命令行会话。 即使有一个大的终端窗口,整个窗口也只会显示一个会话。 这浪费了空间,特别是在大型显示器和高分辨率的笔记本电脑屏幕上。 但是,如果你可以将终端分成多个会话呢? 这正是 tmux 最方便的地方,或者说不可或缺的。

安装并启动 tmux

tmux 应用程序的名称来源于 终端 terminal 复用器 muxer 多路复用器 multiplexer 。 换句话说,它可以将您的单终端会话分成多个会话。 它管理窗口和窗格:

  • 窗口 window 是一个单一的视图 - 也就是终端中显示的各种东西。
  • 窗格 pane 是该视图的一部分,通常是一个终端会话。

开始前,请在系统上安装 tmux 应用程序。 你需要为您的用户帐户设置 sudo 权限(如果需要,请查看本文获取相关说明)。

sudo dnf -y install tmux

运行 tmux程序:

tmux

状态栏

首先,似乎什么也没有发生,除了出现在终端的底部的状态栏:

Start of tmux session

底部栏显示:

  • [0] – 这是 tmux 服务器创建的第一个会话。编号从 0 开始。tmux 服务器会跟踪所有的会话确认其是否存活。
  • 0:testuser@scarlett:~ – 有关该会话的第一个窗口的信息。编号从 0 开始。这表示窗口的活动窗格中的终端归主机名 scarletttestuser 用户所有。当前目录是 ~ (家目录)。
  • * – 显示你目前在此窗口中。
  • “scarlett.internal.fri” – 你正在使用的 tmux 服务器的主机名。
  • 此外,还会显示该特定主机上的日期和时间。

当你向会话中添加更多窗口和窗格时,信息栏将随之改变。

tmux 基础知识

把你的终端窗口拉伸到最大。现在让我们尝试一些简单的命令来创建更多的窗格。默认情况下,所有的命令都以 Ctrl+b 开头。

  • Ctrl+b, " 水平分割当前单个窗格。 现在窗口中有两个命令行窗格,一个在顶部,一个在底部。请注意,底部的新窗格是活动窗格。
  • Ctrl+b, % 垂直分割当前单个窗格。 现在你的窗口中有三个命令行窗格,右下角的窗格是活动窗格。

tmux window with three panes

注意当前窗格周围高亮显示的边框。要浏览所有的窗格,请做以下操作:

  • Ctrl+b,然后点箭头键
  • Ctrl+b, q,数字会短暂的出现在窗格上。在这期间,你可以你想要浏览的窗格上对应的数字。

现在,尝试使用不同的窗格运行不同的命令。例如以下这样的:

  • 在顶部窗格中使用 ls 命令显示目录内容。
  • 在左下角的窗格中使用 vi 命令,编辑一个文本文件。
  • 在右下角的窗格中运行 top 命令监控系统进程。

屏幕将会如下显示:

tmux session with three panes running different commands

到目前为止,这个示例中只是用了一个带多个窗格的窗口。你也可以在会话中运行多个窗口。

  • 为了创建一个新的窗口,请敲Ctrl+b, c 。请注意,状态栏显示当前有两个窗口正在运行。(敏锐的读者会看到上面的截图。)
  • 要移动到上一个窗口,请敲 Ctrl+b, p
  • 要移动到下一个窗口,请敲 Ctrl+b, n
  • 要立即移动到特定的窗口,请敲 Ctrl+b 然后跟上窗口编号。

如果你想知道如何关闭窗格,只需要使用 exitlogout,或者 Ctrl+d 来退出特定的命令行 shell。一旦你关闭了窗口中的所有窗格,那么该窗口也会消失。

脱离和附加

tmux 最强大的功能之一是能够脱离和重新附加到会话。 当你脱离的时候,你可以离开你的窗口和窗格独立运行。 此外,您甚至可以完全注销系统。 然后,您可以登录到同一个系统,重新附加到 tmux 会话,查看您离开时的所有窗口和窗格。 脱离的时候你运行的命令一直保持运行状态。

为了脱离一个会话,请敲 Ctrl+b, d。然后会话消失,你重新返回到一个标准的单一 shell。如果要重新附加到会话中,使用一下命令:

tmux attach-session

当你连接到主机的网络不稳定时,这个功能就像救生员一样有用。如果连接失败,会话中的所有的进程都会继续运行。只要连接恢复了,你就可以恢复正常,就好像什么事情也没有发生一样。

如果这些功能还不够,在每个会话的顶层窗口和窗格中,你可以运行多个会话。你可以列举出这些窗口和窗格,然后通过编号或者名称把他们附加到正确的会话中:

tmux list-sessions

延伸阅读

本文只触及的 tmux 的表面功能。你可以通过其他方式操作会话:

  • 将一个窗格和另一个窗格交换
  • 将窗格移动到另一个窗口中(可以在同一个会话中也可以在不同的会话中)
  • 设定快捷键自动执行你喜欢的命令
  • ~/.tmux.conf 文件中配置你最喜欢的配置项,这样每一个会话都会按照你喜欢的方式呈现

有关所有命令的完整说明,请查看以下参考:


作者简介:

Paul W. Frields 自 1997 年以来一直是 Linux 用户和爱好者,并于 2003 年加入 Fedora 项目,这个项目刚推出不久。他是 Fedora 项目委员会的创始成员,在文档,网站发布,宣传,工具链开发和维护软件方面都有贡献。他于2008 年 2 月至 2010 年 7 月加入 Red Hat,担任 Fedora 项目负责人,并担任 Red Hat 的工程经理。目前他和妻子以及两个孩子居住在弗吉尼亚。


via: https://fedoramagazine.org/use-tmux-more-powerful-terminal/

作者:Paul W. Frields 译者:Flowsnow 校对:wxy

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

2016 年 Git 发生了 惊天动地 地变化,发布了五大新特性¹ (从 v2.7v2.11 )和十六个补丁²。189 位作者³贡献了 3676 个提交master 分支,比 2015 年多了 15%!总计有 1545 个文件被修改,其中增加了 276799 行并移除了 100973 行

但是,通过统计提交的数量和代码行数来衡量生产力是一种十分愚蠢的方法。除了深度研究过的开发者可以做到凭直觉来判断代码质量的地步,我们普通人来作仲裁难免会因我们常人的判断有失偏颇。

谨记这一条于心,我决定整理这一年里六个我最喜爱的 Git 特性涵盖的改进,来做一次分类回顾。这篇文章作为一篇中篇推文有点太过长了,所以我不介意你们直接跳到你们特别感兴趣的特性去。

  • 完成 git worktree 命令
  • 更多方便的 git rebase 选项
  • git lfs 梦幻的性能加速
  • git diff 实验性的算法和更好的默认结果
  • git submodules 差强人意
  • git stash 的90 个增强

在我们开始之前,请注意在大多数操作系统上都自带有 Git 的旧版本,所以你需要检查你是否在使用最新并且最棒的版本。如果在终端运行 git --version 返回的结果小于 Git v2.11.0,请立刻跳转到 Atlassian 的快速指南 更新或安装 Git 并根据你的平台做出选择。

[所需的“引用”]

在我们进入高质量内容之前还需要做一个短暂的停顿:我觉得我需要为你展示我是如何从公开文档生成统计信息(以及开篇的封面图片)的。你也可以使用下面的命令来对你自己的仓库做一个快速的 年度回顾

¹ 2016 年匹配 vX.Y.0 格式的里程碑
$ git for-each-ref --sort=-taggerdate --format \
'%(refname) %(taggerdate)' refs/tags | grep "v\d\.\d*\.0 .* 2016"
² 2016 年匹配 vX.Y.Z 格式的里程碑
$ git for-each-ref --sort=-taggerdate --format '%(refname) %(taggerdate)' refs/tags | grep "v\d\.\d*\.[^0] .* 2016"
³ 2016 年做过提交的贡献者数量
$ git shortlog -s -n --since=2016-01-01 --until=2017-01-01
⁴ 2016 年的提交数量
$ git log --oneline --since=2016-01-01 --until=2017-01-01 | wc -l
⁵ 以及 2015 年的提交数量
$ git log --oneline --since=2015-01-01 --until=2016-01-01 | wc -l
⁶ 2016 年添加、删除行数
$ git diff --shortstat `git rev-list -1 --until=2016-01-01 master` \
 `git rev-list -1 --until=2017-01-01 master`

以上的命令是在 Git 的 master 分支运行的,所以不会显示其他出色的分支上没有合并的工作。如果你使用这些命令,请记住提交的数量和代码行数不是应该值得信赖的度量方式。请不要使用它们来衡量你的团队成员的贡献。

现在,让我们开始说好的回顾……

完成 Git 工作树 worktree

git worktree 命令首次出现于 Git v2.5 ,但是在 2016 年有了一些显著的增强。两个有价值的新特性在 v2.7 被引入:list 子命令,和为二分搜索增加了命令空间的 refs。而 lock/unlock 子命令则是在 v2.10 被引入。

什么是工作树呢?

git worktree 命令允许你同步地检出和操作处于不同路径下的同一仓库的多个分支。例如,假如你需要做一次快速的修复工作但又不想扰乱你当前的工作区,你可以使用以下命令在一个新路径下检出一个新分支:

$ git worktree add -b hotfix/BB-1234 ../hotfix/BB-1234
Preparing ../hotfix/BB-1234 (identifier BB-1234)
HEAD is now at 886e0ba Merged in bedwards/BB-13430-api-merge-pr (pull request #7822)

工作树不仅仅是为分支工作。你可以检出多个 里程碑 tag 作为不同的工作树来并行构建或测试它们。例如,我从 Git v2.6 和 v2.7 的里程碑中创建工作树来检验不同版本 Git 的行为特征。

$ git worktree add ../git-v2.6.0 v2.6.0
Preparing ../git-v2.6.0 (identifier git-v2.6.0)
HEAD is now at be08dee Git 2.6

$ git worktree add ../git-v2.7.0 v2.7.0
Preparing ../git-v2.7.0 (identifier git-v2.7.0)
HEAD is now at 7548842 Git 2.7

$ git worktree list
/Users/kannonboy/src/git         7548842 [master]
/Users/kannonboy/src/git-v2.6.0  be08dee (detached HEAD)
/Users/kannonboy/src/git-v2.7.0  7548842 (detached HEAD)

$ cd ../git-v2.7.0 && make

你也使用同样的技术来并行构造和运行你自己应用程序的不同版本。

列出工作树

git worktree list 子命令(于 Git v2.7 引入)显示所有与当前仓库有关的工作树。

$ git worktree list
/Users/kannonboy/src/bitbucket/bitbucket       37732bd [master]
/Users/kannonboy/src/bitbucket/staging         d5924bc [staging]
/Users/kannonboy/src/bitbucket/hotfix-1234     37732bd [hotfix/1234]

二分查找工作树

git bisect 是一个简洁的 Git 命令,可以让我们对提交记录执行一次二分搜索。通常用来找到哪一次提交引入了一个指定的退化。例如,如果在我的 master 分支最后的提交上有一个测试没有通过,我可以使用 git bisect 来贯穿仓库的历史来找寻第一次造成这个错误的提交。

$ git bisect start

# 找到已知通过测试的最后提交
# (例如最新的发布里程碑)
$ git bisect good v2.0.0

# 找到已知的出问题的提交
# (例如在 `master` 上的提示)
$ git bisect bad master

# 告诉 git bisect 要运行的脚本/命令;
# git bisect 会在 “good” 和 “bad”范围内
# 找到导致脚本以非 0 状态退出的最旧的提交
$ git bisect run npm test

在后台,bisect 使用 refs 来跟踪 “good” 与 “bad” 的提交来作为二分搜索范围的上下界限。不幸的是,对工作树的粉丝来说,这些 refs 都存储在寻常的 .git/refs/bisect 命名空间,意味着 git bisect 操作如果运行在不同的工作树下可能会互相干扰。

到了 v2.7 版本,bisect 的 refs 移到了 .git/worktrees/$worktree_name/refs/bisect, 所以你可以并行运行 bisect 操作于多个工作树中。

锁定工作树

当你完成了一颗工作树的工作,你可以直接删除它,然后通过运行 git worktree prune 等它被当做垃圾自动回收。但是,如果你在网络共享或者可移除媒介上存储了一颗工作树,如果工作树目录在删除期间不可访问,工作树会被完全清除——不管你喜不喜欢!Git v2.10 引入了 git worktree lockunlock 子命令来防止这种情况发生。

# 在我的 USB 盘上锁定 git-v2.7 工作树
$ git worktree lock /Volumes/Flash_Gordon/git-v2.7 --reason \
"In case I remove my removable media"
# 当我完成时,解锁(并删除)该工作树
$ git worktree unlock /Volumes/Flash_Gordon/git-v2.7
$ rm -rf /Volumes/Flash_Gordon/git-v2.7
$ git worktree prune

--reason 标签允许为未来的你留一个记号,描述为什么当初工作树被锁定。git worktree unlocklock 都要求你指定工作树的路径。或者,你可以 cd 到工作树目录然后运行 git worktree lock . 来达到同样的效果。

更多 Git 变基 rebase 选项

2016 年三月,Git v2.8 增加了在拉取过程中交互进行变基的命令 git pull --rebase=interactive 。对应地,六月份 Git v2.9 发布了通过 git rebase -x 命令对执行变基操作而不需要进入交互模式的支持。

Re-啥?

在我们继续深入前,我假设读者中有些并不是很熟悉或者没有完全习惯变基命令或者交互式变基。从概念上说,它很简单,但是与很多 Git 的强大特性一样,变基散发着听起来很复杂的专业术语的气息。所以,在我们深入前,先来快速的复习一下什么是 变基 rebase

变基操作意味着将一个或多个提交在一个指定分支上重写。git rebase 命令是被深度重载了,但是 rebase 这个名字的来源事实上还是它经常被用来改变一个分支的基准提交(你基于此提交创建了这个分支)。

从概念上说,rebase 通过将你的分支上的提交临时存储为一系列补丁包,接着将这些补丁包按顺序依次打在目标提交之上。

对 master 分支的一个功能分支执行变基操作 (git reabse master)是一种通过将 master 分支上最新的改变合并到功能分支的“保鲜法”。对于长期存在的功能分支,规律的变基操作能够最大程度的减少开发过程中出现冲突的可能性和严重性。

有些团队会选择在合并他们的改动到 master 前立即执行变基操作以实现一次快速合并 (git merge --ff <feature>)。对 master 分支快速合并你的提交是通过简单的将 master ref 指向你的重写分支的顶点而不需要创建一个合并提交。

变基是如此方便和功能强大以致于它已经被嵌入其他常见的 Git 命令中,例如拉取操作 git pull 。如果你在本地 master 分支有未推送的提交,运行 git pull 命令从 origin 拉取你队友的改动会造成不必要的合并提交。

这有点混乱,而且在繁忙的团队,你会获得成堆的不必要的合并提交。git pull --rebase 将你本地的提交在你队友的提交上执行变基而不产生一个合并提交。

这很整洁吧!甚至更酷,Git v2.8 引入了一个新特性,允许你在拉取时 交互地 变基。

交互式变基

交互式变基是变基操作的一种更强大的形态。和标准变基操作相似,它可以重写提交,但它也可以向你提供一个机会让你能够交互式地修改这些将被重新运用在新基准上的提交。

当你运行 git rebase --interactive (或 git pull --rebase=interactive)时,你会在你的文本编辑器中得到一个可供选择的提交列表视图。

$ git rebase master --interactive

pick 2fde787 ACE-1294: replaced miniamalCommit with string in test
pick ed93626 ACE-1294: removed pull request service from test
pick b02eb9a ACE-1294: moved fromHash, toHash and diffType to batch
pick e68f710 ACE-1294: added testing data to batch email file

# Rebase f32fa9d..0ddde5f onto f32fa9d (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to 
# bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.

注意到每一条提交旁都有一个 pick。这是对 rebase 而言,“照原样留下这个提交”。如果你现在就退出文本编辑器,它会执行一次如上文所述的普通变基操作。但是,如果你将 pick 改为 edit 或者其他 rebase 命令中的一个,变基操作会允许你在它被重新运用前改变它。有效的变基命令有如下几种:

  • reword:编辑提交信息。
  • edit:编辑提交了的文件。
  • squash:将提交与之前的提交(同在文件中)合并,并将提交信息拼接。
  • fixup:将本提交与上一条提交合并,并且逐字使用上一条提交的提交信息(这很方便,如果你为一个很小的改动创建了第二个提交,而它本身就应该属于上一条提交,例如,你忘记暂存了一个文件)。
  • exec: 运行一条任意的 shell 命令(我们将会在下一节看到本例一次简洁的使用场景)。
  • drop: 这将丢弃这条提交。

你也可以在文件内重新整理提交,这样会改变它们被重新应用的顺序。当你对不同的主题创建了交错的提交时这会很顺手,你可以使用 squash 或者 fixup 来将其合并成符合逻辑的原子提交。

当你设置完命令并且保存这个文件后,Git 将递归每一条提交,在每个 rewordedit 命令处为你暂停来执行你设计好的改变,并且自动运行 squashfixupexecdrop 命令。

非交互性式执行

当你执行变基操作时,本质上你是在通过将你每一条新提交应用于指定基址的头部来重写历史。git pull --rebase 可能会有一点危险,因为根据上游分支改动的事实,你的新建历史可能会由于特定的提交遭遇测试失败甚至编译问题。如果这些改动引起了合并冲突,变基过程将会暂停并且允许你来解决它们。但是,整洁的合并改动仍然有可能打断编译或测试过程,留下破败的提交弄乱你的提交历史。

但是,你可以指导 Git 为每一个重写的提交来运行你的项目测试套件。在 Git v2.9 之前,你可以通过绑定 git rebase --interactiveexec 命令来实现。例如这样:

$ git rebase master −−interactive −−exec=”npm test”

……这会生成一个交互式变基计划,在重写每条提交后执行 npm test ,保证你的测试仍然会通过:

pick 2fde787 ACE-1294: replaced miniamalCommit with string in test
exec npm test
pick ed93626 ACE-1294: removed pull request service from test
exec npm test
pick b02eb9a ACE-1294: moved fromHash, toHash and diffType to batch
exec npm test
pick e68f710 ACE-1294: added testing data to batch email file
exec npm test

# Rebase f32fa9d..0ddde5f onto f32fa9d (4 command(s))

如果出现了测试失败的情况,变基会暂停并让你修复这些测试(并且将你的修改应用于相应提交):

291 passing
1 failing

1) Host request "after all" hook:
Uncaught Error: connect ECONNRESET 127.0.0.1:3001
…
npm ERR! Test failed.
Execution failed: npm test
You can fix the problem, and then run
        git rebase −−continue

这很方便,但是使用交互式变基有一点臃肿。到了 Git v2.9,你可以这样来实现非交互式变基:

$ git rebase master -x "npm test"

可以简单替换 npm testmakerakemvn clean install,或者任何你用来构建或测试你的项目的命令。

小小警告

就像电影里一样,重写历史可是一个危险的行当。任何提交被重写为变基操作的一部分都将改变它的 SHA-1 ID,这意味着 Git 会把它当作一个全新的提交对待。如果重写的历史和原来的历史混杂,你将获得重复的提交,而这可能在你的团队中引起不少的疑惑。

为了避免这个问题,你仅仅需要遵照一条简单的规则:

永远不要变基一条你已经推送的提交!

坚持这一点你会没事的。

Git LFS 的性能提升

Git 是一个分布式版本控制系统,意味着整个仓库的历史会在克隆阶段被传送到客户端。对包含大文件的项目——尤其是大文件经常被修改——初始克隆会非常耗时,因为每一个版本的每一个文件都必须下载到客户端。Git LFS 大文件存储 Large File Storage )是一个 Git 拓展包,由 Atlassian、GitHub 和其他一些开源贡献者开发,通过需要时才下载大文件的相对版本来减少仓库中大文件的影响。更明确地说,大文件是在检出过程中按需下载的而不是在克隆或抓取过程中。

在 Git 2016 年的五大发布中,Git LFS 自身就有四个功能版本的发布:v1.2 到 v1.5。你可以仅对 Git LFS 这一项来写一系列回顾文章,但是就这篇文章而言,我将专注于 2016 年解决的一项最重要的主题:速度。一系列针对 Git 和 Git LFS 的改进极大程度地优化了将文件传入/传出服务器的性能。

长期过滤进程

当你 git add 一个文件时,Git 的净化过滤系统会被用来在文件被写入 Git 目标存储之前转化文件的内容。Git LFS 通过使用 净化过滤器 clean filter 将大文件内容存储到 LFS 缓存中以缩减仓库的大小,并且增加一个小“指针”文件到 Git 目标存储中作为替代。

污化过滤器 smudge filter 是净化过滤器的对立面——正如其名。在 git checkout 过程中从一个 Git 目标仓库读取文件内容时,污化过滤系统有机会在文件被写入用户的工作区前将其改写。Git LFS 污化过滤器通过将指针文件替代为对应的大文件将其转化,可以是从 LFS 缓存中获得或者通过读取存储在 Bitbucket 的 Git LFS。

传统上,污化和净化过滤进程在每个文件被增加和检出时只能被唤起一次。所以,一个项目如果有 1000 个文件在被 Git LFS 追踪 ,做一次全新的检出需要唤起 git-lfs-smudge 命令 1000 次。尽管单次操作相对很迅速,但是经常执行 1000 次独立的污化进程总耗费惊人。、

针对 Git v2.11(和 Git LFS v1.5),污化和净化过滤器可以被定义为长期进程,为第一个需要过滤的文件调用一次,然后为之后的文件持续提供污化或净化过滤直到父 Git 操作结束。Lars Schneider,Git 的长期过滤系统的贡献者,简洁地总结了对 Git LFS 性能改变带来的影响。

使用 12k 个文件的测试仓库的过滤进程在 macOS 上快了 80 倍,在 Windows 上 快了 58 倍。在 Windows 上,这意味着测试运行了 57 秒而不是 55 分钟。

这真是一个让人印象深刻的性能增强!

LFS 专有克隆

长期运行的污化和净化过滤器在对向本地缓存读写的加速做了很多贡献,但是对大目标传入/传出 Git LFS 服务器的速度提升贡献很少。 每次 Git LFS 污化过滤器在本地 LFS 缓存中无法找到一个文件时,它不得不使用两次 HTTP 请求来获得该文件:一个用来定位文件,另外一个用来下载它。在一次 git clone 过程中,你的本地 LFS 缓存是空的,所以 Git LFS 会天真地为你的仓库中每个 LFS 所追踪的文件创建两个 HTTP 请求:

幸运的是,Git LFS v1.2 提供了专门的 git lfs clone 命令。不再是一次下载一个文件; git lfs clone 禁止 Git LFS 污化过滤器,等待检出结束,然后从 Git LFS 存储中按批下载任何需要的文件。这允许了并行下载并且将需要的 HTTP 请求数量减半。

自定义 传输路由器 Transfer Adapter

正如之前讨论过的,Git LFS 在 v1.5 中提供对长期过滤进程的支持。不过,对另外一种类型的可插入进程的支持早在今年年初就发布了。 Git LFS 1.3 包含了对 可插拔传输路由器 pluggable transfer adapter 的支持,因此不同的 Git LFS 托管服务可以定义属于它们自己的协议来向或从 LFS 存储中传输文件。

直到 2016 年底,Bitbucket 是唯一一个执行专属 Git LFS 传输协议 Bitbucket LFS Media Adapter 的托管服务商。这是为了从 Bitbucket 的一个被称为 chunking 的 LFS 存储 API 独特特性中获益。Chunking 意味着在上传或下载过程中,大文件被分解成 4MB 的 文件块 chunk

分块给予了 Bitbucket 支持的 Git LFS 三大优势:

  1. 并行下载与上传。默认地,Git LFS 最多并行传输三个文件。但是,如果只有一个文件被单独传输(这也是 Git LFS 污化过滤器的默认行为),它会在一个单独的流中被传输。Bitbucket 的分块允许同一文件的多个文件块同时被上传或下载,经常能够神奇地提升传输速度。
  2. 可续传的文件块传输。文件块都在本地缓存,所以如果你的下载或上传被打断,Bitbucket 的自定义 LFS 流媒体路由器会在下一次你推送或拉取时仅为丢失的文件块恢复传输。
  3. 免重复。Git LFS,正如 Git 本身,是一种可定位的内容;每一个 LFS 文件都由它的内容生成的 SHA-256 哈希值认证。所以,哪怕你稍微修改了一位数据,整个文件的 SHA-256 就会修改而你不得不重新上传整个文件。分块允许你仅仅重新上传文件真正被修改的部分。举个例子,想想一下 Git LFS 在追踪一个 41M 的 精灵表格 spritesheet 。如果我们增加在此精灵表格上增加 2MB 的新的部分并且提交它,传统上我们需要推送整个新的 43M 文件到服务器端。但是,使用 Bitbucket 的自定义传输路由,我们仅仅需要推送大约 7MB:先是 4MB 文件块(因为文件的信息头会改变)和我们刚刚添加的包含新的部分的 3MB 文件块!其余未改变的文件块在上传过程中被自动跳过,节省了巨大的带宽和时间消耗。

可自定义的传输路由器是 Git LFS 的一个伟大的特性,它们使得不同服务商在不重载核心项目的前提下体验适合其服务器的优化后的传输协议。

更佳的 git diff 算法与默认值

不像其他的版本控制系统,Git 不会明确地存储文件被重命名了的事实。例如,如果我编辑了一个简单的 Node.js 应用并且将 index.js 重命名为 app.js,然后运行 git diff,我会得到一个看起来像一个文件被删除另一个文件被新建的结果。

我猜测移动或重命名一个文件从技术上来讲是一次删除后跟着一次新建,但这不是对人类最友好的描述方式。其实,你可以使用 -M 标志来指示 Git 在计算差异时同时尝试检测是否是文件重命名。对之前的例子,git diff -M 给我们如下结果:

第二行显示的 similarity index 告诉我们文件内容经过比较后的相似程度。默认地,-M 会处理任意两个超过 50% 相似度的文件。这意味着,你需要编辑少于 50% 的行数来确保它们可以被识别成一个重命名后的文件。你可以通过加上一个百分比来选择你自己的 similarity index,如,-M80%

到 Git v2.9 版本,无论你是否使用了 -M 标志, git diffgit log 命令都会默认检测重命名。如果不喜欢这种行为(或者,更现实的情况,你在通过一个脚本来解析 diff 输出),那么你可以通过显式的传递 --no-renames 标志来禁用这种行为。

详细的提交

你经历过调用 git commit 然后盯着空白的 shell 试图想起你刚刚做过的所有改动吗?verbose 标志就为此而来!

不像这样:

Ah crap, which dependency did I just rev?

# Please enter the commit message for your changes. Lines starting
# with ‘#’ will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with ‘origin/master’.
#
# Changes to be committed:
# new file: package.json
#

……你可以调用 git commit --verbose 来查看你改动造成的行内差异。不用担心,这不会包含在你的提交信息中:

--verbose 标志并不是新出现的,但是直到 Git v2.9 你才可以通过 git config --global commit.verbose true 永久的启用它。

实验性的 Diff 改进

当一个被修改部分前后几行相同时,git diff 可能产生一些稍微令人迷惑的输出。如果在一个文件中有两个或者更多相似结构的函数时这可能发生。来看一个有些刻意人为的例子,想象我们有一个 JS 文件包含一个单独的函数:

/* @return {string} "Bitbucket" */
function productName() {
  return "Bitbucket";
}

现在想象一下我们刚提交的改动包含一个我们专门做的 另一个可以做相似事情的函数:

/* @return {string} "Bitbucket" */
function productId() {
  return "Bitbucket";
}

/* @return {string} "Bitbucket" */
function productName() {
  return "Bitbucket";
}

我们希望 git diff 显示开头五行被新增,但是实际上它不恰当地将最初提交的第一行也包含进来。

错误的注释被包含在了 diff 中!这虽不是世界末日,但每次发生这种事情总免不了花费几秒钟的意识去想 啊? 在十二月,Git v2.11 介绍了一个新的实验性的 diff 选项,--indent-heuristic,尝试生成从美学角度来看更赏心悦目的 diff。

在后台,--indent-heuristic 在每一次改动造成的所有可能的 diff 中循环,并为它们分别打上一个 “不良” 分数。这是基于启发式的,如差异文件块是否以不同等级的缩进开始和结束(从美学角度讲“不良”),以及差异文件块前后是否有空白行(从美学角度讲令人愉悦)。最后,有着最低不良分数的块就是最终输出。

这个特性还是实验性的,但是你可以通过应用 --indent-heuristic 选项到任何 git diff 命令来专门测试它。如果,如果你喜欢尝鲜,你可以这样将其在你的整个系统内启用:

$ git config --global diff.indentHeuristic true

子模块 Submodule 差强人意

子模块允许你从 Git 仓库内部引用和包含其他 Git 仓库。这通常被用在当一些项目管理的源依赖也在被 Git 跟踪时,或者被某些公司用来作为包含一系列相关项目的 monorepo 的替代品。

由于某些用法的复杂性以及使用错误的命令相当容易破坏它们的事实,Submodule 得到了一些坏名声。

但是,它们还是有着它们的用处,而且,我想这仍然是用于需要厂商依赖项的最好选择。 幸运的是,2016 对子模块的用户来说是伟大的一年,在几次发布中落地了许多意义重大的性能和特性提升。

并行抓取

当克隆或则抓取一个仓库时,加上 --recurse-submodules 选项意味着任何引用的子模块也将被克隆或更新。传统上,这会被串行执行,每次只抓取一个子模块。直到 Git v2.8,你可以附加 --jobs=n 选项来使用 n 个并行线程来抓取子模块。

我推荐永久的配置这个选项:

$ git config --global submodule.fetchJobs 4

……或者你可以选择使用任意程度的平行化。

浅层化子模块

Git v2.9 介绍了 git clone —shallow-submodules 标志。它允许你抓取你仓库的完整克隆,然后递归地以一个提交的深度浅层化克隆所有引用的子模块。如果你不需要项目的依赖的完整记录时会很有用。

例如,一个仓库有着一些混合了的子模块,其中包含有其他厂商提供的依赖和你自己其它的项目。你可能希望初始化时执行浅层化子模块克隆,然后深度选择几个你想工作与其上的项目。

另一种情况可能是配置持续集成或部署工作。Git 需要一个包含了子模块的超级仓库以及每个子模块最新的提交以便能够真正执行构建。但是,你可能并不需要每个子模块全部的历史记录,所以仅仅检索最新的提交可以为你省下时间和带宽。

子模块的替代品

--reference 选项可以和 git clone 配合使用来指定另一个本地仓库作为一个替代的对象存储,来避免跨网络重新复制你本地已经存在的对象。语法为:

$ git clone --reference <local repo> <url>

到 Git v2.11,你可以使用 —reference 选项与 —recurse-submodules 结合来设置子模块指向一个来自另一个本地仓库的子模块。其语法为:

$ git clone --recurse-submodules --reference <local repo> <url>

这潜在的可以省下大量的带宽和本地磁盘空间,但是如果引用的本地仓库不包含你克隆的远程仓库所必需的所有子模块时,它可能会失败。

幸运的是,方便的 —-reference-if-able 选项将会让它优雅地失败,然后为丢失了的被引用的本地仓库的所有子模块回退为一次普通的克隆。

$ git clone --recurse-submodules --reference-if-able \
<local repo> <url>

子模块的 diff

在 Git v2.11 之前,Git 有两种模式来显示对更新你的仓库子模块的提交之间的差异。

git diff —-submodule=short 显示你的项目引用的子模块中的旧提交和新提交(这也是如果你整体忽略 --submodule 选项的默认结果):

git diff —submodule=log 有一点啰嗦,显示更新了的子模块中任意新建或移除的提交的信息中统计行。

Git v2.11 引入了第三个更有用的选项:—-submodule=diff。这会显示更新后的子模块所有改动的完整的 diff。

git stash 的 90 个增强

不像子模块,几乎没有 Git 用户不钟爱 git stashgit stash 临时搁置(或者 藏匿)你对工作区所做的改动使你能够先处理其他事情,结束后重新将搁置的改动恢复到先前状态。

自动搁置

如果你是 git rebase 的粉丝,你可能很熟悉 --autostash 选项。它会在变基之前自动搁置工作区所有本地修改然后等变基结束再将其复用。

$ git rebase master --autostash
Created autostash: 54f212a
HEAD is now at 8303dca It's a kludge, but put the tuple from the database in the cache.
First, rewinding head to replay your work on top of it...
Applied autostash.

这很方便,因为它使得你可以在一个不洁的工作区执行变基。有一个方便的配置标志叫做 rebase.autostash 可以将这个特性设为默认,你可以这样来全局启用它:

$ git config --global rebase.autostash true

rebase.autostash 实际上自从 Git v1.8.4 就可用了,但是 v2.7 引入了通过 --no-autostash 选项来取消这个标志的功能。如果你对未暂存的改动使用这个选项,变基会被一条工作树被污染的警告禁止:

$ git rebase master --no-autostash
Cannot rebase: You have unstaged changes.
Please commit or stash them.

补丁式搁置

说到配置标签,Git v2.7 也引入了 stash.showPatchgit stash show 的默认行为是显示你搁置文件的汇总。

$ git stash show
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

-p 标志传入会将 git stash show 变为 “补丁模式”,这将会显示完整的 diff:

stash.showPatch 将这个行为定为默认。你可以将其全局启用:

$ git config --global stash.showPatch true

如果你使能 stash.showPatch 但却之后决定你仅仅想要查看文件总结,你可以通过传入 --stat 选项来重新获得之前的行为。

$ git stash show --stat
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

顺便一提:--no-patch 是一个有效选项但它不会如你所希望的取消 stash.showPatch。不仅如此,它会传递给用来生成补丁时潜在调用的 git diff 命令,然后你会发现完全没有任何输出。

简单的搁置标识

如果你惯用 git stash ,你可能知道你可以搁置多次改动然后通过 git stash list 来查看它们:

$ git stash list
stash@{0}: On master: crazy idea that might work one day
stash@{1}: On master: desperate samurai refactor; don't apply
stash@{2}: On master: perf improvement that I forgot I stashed
stash@{3}: On master: pop this when we use Docker in production

但是,你可能不知道为什么 Git 的搁置有着这么难以理解的标识(stash@{1}stash@{2} 等),或许你可能将它们勾勒成 “仅仅是 Git 的癖好吧”。实际上就像很多 Git 特性一样,这些奇怪的标志实际上是 Git 数据模型的一个非常巧妙使用(或者说是滥用了的)的结果。

在后台,git stash 命令实际创建了一系列特定的提交目标,这些目标对你搁置的改动做了编码并且维护一个 reglog 来保存对这些特殊提交的引用。 这也是为什么 git stash list 的输出看起来很像 git reflog 的输出。当你运行 git stash apply stash@{1} 时,你实际上在说,“从 stash reflog 的位置 1 上应用这条提交。”

到了 Git v2.11,你不再需要使用完整的 stash@{n} 语句。相反,你可以通过一个简单的整数指出该搁置在 stash reflog 中的位置来引用它们。

$ git stash show 1
$ git stash apply 1
$ git stash pop 1

讲了很多了。如果你还想要多学一些搁置是怎么保存的,我在 这篇教程 中写了一点这方面的内容。


好了,结束了。感谢您的阅读!我希望您喜欢阅读这份长篇大论,正如我乐于在 Git 的源码、发布文档和 man 手册中探险一番来撰写它。如果你认为我忘记了一些重要的事,请留下一条评论或者在 Twitter 上让我知道,我会努力写一份后续篇章。

至于 Git 接下来会发生什么,这要靠广大维护者和贡献者了(其中有可能就是你!)。随着 Git 的采用日益增长,我猜测简化、改进的用户体验,和更好的默认结果将会是 2017 年 Git 主要的主题。随着 Git 仓库变得越来越大、越来越旧,我猜我们也可以看到继续持续关注性能和对大文件、深度树和长历史的改进处理。

如果你关注 Git 并且很期待能够和一些项目背后的开发者会面,请考虑来 Brussels 花几周时间来参加 Git Merge 。我会在那里发言!但是更重要的是,很多维护 Git 的开发者将会出席这次会议而且一年一度的 Git 贡献者峰会很可能会指定来年发展的方向。

或者如果你实在等不及,想要获得更多的技巧和指南来改进你的工作流,请参看这份 Atlassian 的优秀作品: Git 教程

封面图片是由 instaco.de 生成的。


via: https://medium.com/hacker-daily/git-in-2016-fad96ae22a15

作者:Tim Pettersen 译者:xiaow6 校对:wxy

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

这是调试器工作原理系列文章的第一篇,我不确定这个系列会有多少篇文章,会涉及多少话题,但我仍会从这篇基础开始。

这一篇会讲什么

我将为大家展示 Linux 中调试器的主要构成模块 - ptrace 系统调用。这篇文章所有代码都是基于 32 位 Ubuntu 操作系统。值得注意的是,尽管这些代码是平台相关的,将它们移植到其它平台应该并不困难。

缘由

为了理解我们要做什么,让我们先考虑下调试器为了完成调试都需要什么资源。调试器可以开始一个进程并调试这个进程,又或者将自己同某个已经存在的进程关联起来。调试器能够单步执行代码,设定断点并且将程序执行到断点,检查变量的值并追踪堆栈。许多调试器有着更高级的特性,例如在调试器的地址空间内执行表达式或者调用函数,甚至可以在进程执行过程中改变代码并观察效果。

尽管现代的调试器都十分的复杂(我没有检查,但我确信 gdb 的代码行数至少有六位数),但它们的工作的原理却是十分的简单。调试器的基础是操作系统与编译器 / 链接器提供的一些基础服务,其余的部分只是简单的编程而已。

Linux 的调试 - ptrace

Linux 调试器中的瑞士军刀便是 ptrace 系统调用(使用 man 2 ptrace 命令可以了解更多)。这是一种复杂却强大的工具,可以允许一个进程控制另外一个进程并从 内部替换 Peek and poke 被控制进程的内核镜像的值(Peek and poke 在系统编程中是很知名的叫法,指的是直接读写内存内容)。

接下来会深入分析。

执行进程的代码

我将编写一个示例,实现一个在“跟踪”模式下运行的进程。在这个模式下,我们将单步执行进程的代码,就像机器码(汇编代码)被 CPU 执行时一样。我将分段展示、讲解示例代码,在文章的末尾也有完整 c 文件的下载链接,你可以编译、执行或者随心所欲的更改。

更进一步的计划是实现一段代码,这段代码可以创建可执行用户自定义命令的子进程,同时父进程可以跟踪子进程。首先是主函数:

int main(int argc, char** argv)
{
    pid_t child_pid;

    if (argc < 2) {
        fprintf(stderr, "Expected a program name as argument\n");
        return -1;
    }

    child_pid = fork();
    if (child_pid == 0)
        run_target(argv[1]);
    else if (child_pid > 0)
        run_debugger(child_pid);
    else {
        perror("fork");
        return -1;
    }

    return 0;
}

看起来相当的简单:我们用 fork 创建了一个新的子进程(这篇文章假定读者有一定的 Unix/Linux 编程经验。我假定你知道或至少了解 fork、exec 族函数与 Unix 信号)。if 语句的分支执行子进程(这里称之为 “target”),else if 的分支执行父进程(这里称之为 “debugger”)。

下面是 target 进程的代码:

void run_target(const char* programname)
{
    procmsg("target started. will run '%s'\n", programname);

    /* Allow tracing of this process */
    if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
        perror("ptrace");
        return;
    }

    /* Replace this process's image with the given program */
    execl(programname, programname, 0);
}

这段代码中最值得注意的是 ptrace 调用。在 sys/ptrace.h 中,ptrace 是如下定义的:

long ptrace(enum __ptrace_request request, pid_t pid,
                 void *addr, void *data);

第一个参数是 _request_,这是许多预定义的 PTRACE_* 常量中的一个。第二个参数为请求分配进程 ID。第三个与第四个参数是地址与数据指针,用于操作内存。上面代码段中的 ptrace 调用发起了 PTRACE_TRACEME 请求,这意味着该子进程请求系统内核让其父进程跟踪自己。帮助页面上对于 request 的描述很清楚:

意味着该进程被其父进程跟踪。任何传递给该进程的信号(除了 SIGKILL)都将通过 wait() 方法阻塞该进程并通知其父进程。此外,该进程的之后所有调用 exec() 动作都将导致 SIGTRAP 信号发送到此进程上,使得父进程在新的程序执行前得到取得控制权的机会。如果一个进程并不需要它的的父进程跟踪它,那么这个进程不应该发送这个请求。(pid、addr 与 data 暂且不提)

我高亮了这个例子中我们需要注意的部分。在 ptrace 调用后,run_target 接下来要做的就是通过 execl 传参并调用。如同高亮部分所说明,这将导致系统内核在 execl 创建进程前暂时停止,并向父进程发送信号。

是时候看看父进程做什么了。

void run_debugger(pid_t child_pid)
{
    int wait_status;
    unsigned icounter = 0;
    procmsg("debugger started\n");

    /* Wait for child to stop on its first instruction */
    wait(&wait_status);

    while (WIFSTOPPED(wait_status)) {
        icounter++;
        /* Make the child execute another instruction */
        if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) {
            perror("ptrace");
            return;
        }

        /* Wait for child to stop on its next instruction */
        wait(&wait_status);
    }

    procmsg("the child executed %u instructions\n", icounter);
}

如前文所述,一旦子进程调用了 exec,子进程会停止并被发送 SIGTRAP 信号。父进程会等待该过程的发生并在第一个 wait() 处等待。一旦上述事件发生了,wait() 便会返回,由于子进程停止了父进程便会收到信号(如果子进程由于信号的发送停止了,WIFSTOPPED 就会返回 true)。

父进程接下来的动作就是整篇文章最需要关注的部分了。父进程会将 PTRACE_SINGLESTEP 与子进程 ID 作为参数调用 ptrace 方法。这就会告诉操作系统,“请恢复子进程,但在它执行下一条指令前阻塞”。周而复始地,父进程等待子进程阻塞,循环继续。当 wait() 中传出的信号不再是子进程的停止信号时,循环终止。在跟踪器(父进程)运行期间,这将会是被跟踪进程(子进程)传递给跟踪器的终止信号(如果子进程终止 WIFEXITED 将返回 true)。

icounter 存储了子进程执行指令的次数。这么看来我们小小的例子也完成了些有用的事情 - 在命令行中指定程序,它将执行该程序并记录它从开始到结束所需要的 cpu 指令数量。接下来就让我们这么做吧。

测试

我编译了下面这个简单的程序并利用跟踪器运行它:

#include <stdio.h>

int main()
{
    printf("Hello, world!\n");
    return 0;
}

令我惊讶的是,跟踪器花了相当长的时间,并报告整个执行过程共有超过 100,000 条指令执行。仅仅是一条输出语句?什么造成了这种情况?答案很有趣(至少你同我一样痴迷与机器/汇编语言)。Linux 的 gcc 默认会动态的将程序与 c 的运行时库动态地链接。这就意味着任何程序运行前的第一件事是需要动态库加载器去查找程序运行所需要的共享库。这些代码的数量很大 - 别忘了我们的跟踪器要跟踪每一条指令,不仅仅是主函数的,而是“整个进程中的指令”。

所以当我将测试程序使用静态编译时(通过比较,可执行文件会多出 500 KB 左右的大小,这部分是 C 运行时库的静态链接),跟踪器提示只有大概 7000 条指令被执行。这个数目仍然不小,但是考虑到在主函数执行前 libc 的初始化以及主函数执行后的清除代码,这个数目已经是相当不错了。此外,printf 也是一个复杂的函数。

仍然不满意的话,我需要的是“可以测试”的东西 - 例如可以完整记录每一个指令运行的程序执行过程。这当然可以通过汇编代码完成。所以我找到了这个版本的 “Hello, world!” 并编译了它。

section    .text
    ; The _start symbol must be declared for the linker (ld)
    global _start

_start:

    ; Prepare arguments for the sys_write system call:
    ;   - eax: system call number (sys_write)
    ;   - ebx: file descriptor (stdout)
    ;   - ecx: pointer to string
    ;   - edx: string length
    mov    edx, len
    mov    ecx, msg
    mov    ebx, 1
    mov    eax, 4

    ; Execute the sys_write system call
    int    0x80

    ; Execute sys_exit
    mov    eax, 1
    int    0x80

section   .data
msg db    'Hello, world!', 0xa
len equ    $ - msg

当然,现在跟踪器提示 7 条指令被执行了,这样一来很容易区分它们。

深入指令流

上面那个汇编语言编写的程序使得我可以向你介绍 ptrace 的另外一个强大的用途 - 详细显示被跟踪进程的状态。下面是 run_debugger 函数的另一个版本:

void run_debugger(pid_t child_pid)
{
    int wait_status;
    unsigned icounter = 0;
    procmsg("debugger started\n");

    /* Wait for child to stop on its first instruction */
    wait(&wait_status);

    while (WIFSTOPPED(wait_status)) {
        icounter++;
        struct user_regs_struct regs;
        ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
        unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.eip, 0);

        procmsg("icounter = %u.  EIP = 0x%08x.  instr = 0x%08x\n",
                    icounter, regs.eip, instr);

        /* Make the child execute another instruction */
        if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) {
            perror("ptrace");
            return;
        }

        /* Wait for child to stop on its next instruction */
        wait(&wait_status);
    }

    procmsg("the child executed %u instructions\n", icounter);
}

不同仅仅存在于 while 循环的开始几行。这个版本里增加了两个新的 ptrace 调用。第一条将进程的寄存器值读取进了一个结构体中。 sys/user.h 定义有 user_regs_struct。如果你查看头文件,头部的注释这么写到:

/* 这个文件只为了 GDB 而创建
   不用详细的阅读.如果你不知道你在干嘛,
   不要在除了 GDB 以外的任何地方使用此文件  */

不知道你做何感想,但这让我觉得我们找对地方了。回到例子中,一旦我们在 regs 变量中取得了寄存器的值,我们就可以通过将 PTRACE_PEEKTEXT 作为参数、 regs.eip(x86 上的扩展指令指针)作为地址,调用 ptrace ,读取当前进程的当前指令(警告:如同我上面所说,文章很大程度上是平台相关的。我简化了一些设定 - 例如,x86 指令集不需要调整到 4 字节,我的32位 Ubuntu unsigned int 是 4 字节。事实上,许多平台都不需要。从内存中读取指令需要预先安装完整的反汇编器。我们这里没有,但实际的调试器是有的)。下面是新跟踪器所展示出的调试效果:

$ simple_tracer traced_helloworld
[5700] debugger started
[5701] target started. will run 'traced_helloworld'
[5700] icounter = 1\.  EIP = 0x08048080\.  instr = 0x00000eba
[5700] icounter = 2\.  EIP = 0x08048085\.  instr = 0x0490a0b9
[5700] icounter = 3\.  EIP = 0x0804808a.  instr = 0x000001bb
[5700] icounter = 4\.  EIP = 0x0804808f.  instr = 0x000004b8
[5700] icounter = 5\.  EIP = 0x08048094\.  instr = 0x01b880cd
Hello, world!
[5700] icounter = 6\.  EIP = 0x08048096\.  instr = 0x000001b8
[5700] icounter = 7\.  EIP = 0x0804809b.  instr = 0x000080cd
[5700] the child executed 7 instructions

现在,除了 icounter,我们也可以观察到指令指针与它每一步所指向的指令。怎么来判断这个结果对不对呢?使用 objdump -d 处理可执行文件:

$ objdump -d traced_helloworld

traced_helloworld:     file format elf32-i386

Disassembly of section .text:

08048080 <.text>:
 8048080:     ba 0e 00 00 00          mov    $0xe,%edx
 8048085:     b9 a0 90 04 08          mov    $0x80490a0,%ecx
 804808a:     bb 01 00 00 00          mov    $0x1,%ebx
 804808f:     b8 04 00 00 00          mov    $0x4,%eax
 8048094:     cd 80                   int    $0x80
 8048096:     b8 01 00 00 00          mov    $0x1,%eax
 804809b:     cd 80                   int    $0x80

这个结果和我们跟踪器的结果就很容易比较了。

 将跟踪器关联到正在运行的进程

如你所知,调试器也能关联到已经运行的进程。现在你应该不会惊讶,ptrace 通过以 PTRACE_ATTACH 为参数调用也可以完成这个过程。这里我不会展示示例代码,通过上文的示例代码应该很容易实现这个过程。出于学习目的,这里使用的方法更简便(因为我们在子进程刚开始就可以让它停止)。

代码

上文中的简单的跟踪器(更高级的,可以打印指令的版本)的完整c源代码可以在这里找到。它是通过 4.4 版本的 gcc 以 -Wall -pedantic --std=c99 编译的。

结论与计划

诚然,这篇文章并没有涉及很多内容 - 我们距离亲手完成一个实际的调试器还有很长的路要走。但我希望这篇文章至少可以使得调试这件事少一些神秘感。ptrace 是功能多样的系统调用,我们目前只展示了其中的一小部分。

单步调试代码很有用,但也只是在一定程度上有用。上面我通过 C 的 “Hello World!” 做了示例。为了执行主函数,可能需要上万行代码来初始化 C 的运行环境。这并不是很方便。最理想的是在 main 函数入口处放置断点并从断点处开始分步执行。为此,在这个系列的下一篇,我打算展示怎么实现断点。

参考

撰写此文时参考了如下文章:


via: http://eli.thegreenplace.net/2011/01/23/how-debuggers-work-part-1

作者:Eli Bendersky 译者:YYforymj 校对:wxy

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

最近几年中,面向数据的设计已经受到了很多的关注 —— 一种强调内存中数据布局的编程风格,包括如何访问以及将会引发多少的 cache 缺失。由于在内存读取操作中缺失所占的数量级要大于命中的数量级,所以缺失的数量通常是优化的关键标准。这不仅仅关乎那些对性能有要求的 code-data 结构设计的软件,由于缺乏对内存效益的重视而成为软件运行缓慢、膨胀的一个很大因素。

高效缓存数据结构的中心原则是将事情变得平滑和线性。比如,在大部分情况下,存储一个序列元素更倾向于使用普通数组而不是链表 —— 每一次通过指针来查找数据都会为 cache 缺失增加一份风险;而普通数组则可以预先获取,并使得内存系统以最大的效率运行

如果你知道一点内存层级如何运作的知识,下面的内容会是想当然的结果——但是有时候即便它们相当明显,测试一下任不失为一个好主意。几年前 Baptiste Wicht 测试过了 std::vector vs std::list vs std::deque,(后者通常使用分块数组来实现,比如:一个数组的数组)。结果大部分会和你预期的保持一致,但是会存在一些违反直觉的东西。作为实例:在序列链表的中间位置做插入或者移除操作被认为会比数组快,但如果该元素是一个 POD 类型,并且不大于 64 字节或者在 64 字节左右(在一个 cache 流水线内),通过对要操作的元素周围的数组元素进行移位操作要比从头遍历链表来的快。这是由于在遍历链表以及通过指针插入/删除元素的时候可能会导致不少的 cache 缺失,相对而言,数组移位则很少会发生。(对于更大的元素,非 POD 类型,或者你已经有了指向链表元素的指针,此时和预期的一样,链表胜出)

多亏了类似 Baptiste 这样的数据,我们知道了内存布局如何影响序列容器。但是关联容器,比如 hash 表会怎么样呢?已经有了些权威推荐:Chandler Carruth 推荐的带局部探测的开放寻址(此时,我们没必要追踪指针),以及Mike Acton 推荐的在内存中将 value 和 key 隔离(这种情况下,我们可以在每一个 cache 流水线中得到更多的 key), 这可以在我们必须查找多个 key 时提高局部性能。这些想法很有意义,但再一次的说明:测试永远是好习惯,但由于我找不到任何数据,所以只好自己收集了。

测试

我测试了四个不同的 quick-and-dirty 哈希表实现,另外还包括 std::unordered_map 。这五个哈希表都使用了同一个哈希函数 —— Bob Jenkins 的 SpookyHash(64 位哈希值)。(由于哈希函数在这里不是重点,所以我没有测试不同的哈希函数;我同样也没有检测我的分析中的总内存消耗。)实现会通过简短的代码在测试结果表中标注出来。

  • UMstd::unordered_map 。在 VS2012 和 libstdc++-v3 (libstdc++-v3: gcc 和 clang 都会用到这东西)中,UM 是以链表的形式实现,所有的元素都在链表中,bucket 数组中存储了链表的迭代器。VS2012 中,则是一个双链表,每一个 bucket 存储了起始迭代器和结束迭代器;libstdc++ 中,是一个单链表,每一个 bucket 只存储了一个起始迭代器。这两种情况里,链表节点是独立申请和释放的。最大负载因子是 1 。
  • Ch:分离的、链状 bucket 指向一个元素节点的单链表。为了避免分开申请每一个节点,元素节点存储在普通数组池中。未使用的节点保存在一个空闲链表中。最大负载因子是 1。
  • OL:开地址线性探测 —— 每一个 bucket 存储一个 62 bit 的 hash 值,一个 2 bit 的状态值(包括 empty,filled,removed 三个状态),key 和 value 。最大负载因子是 2/3。
  • DO1:“data-oriented 1” —— 和 OL 相似,但是哈希值、状态值和 key、values 分离在两个隔离的平滑数组中。
  • DO2:“data-oriented 2” —— 与 OL 类似,但是哈希/状态,keys 和 values 被分离在 3 个相隔离的平滑数组中。

在我的所有实现中,包括 VS2012 的 UM 实现,默认使用尺寸为 2 的 n 次方。如果超出了最大负载因子,则扩展两倍。在 libstdc++ 中,UM 默认尺寸是一个素数。如果超出了最大负载因子,则扩展为下一个素数大小。但是我不认为这些细节对性能很重要。素数是一种对低 bit 位上没有足够熵的低劣 hash 函数的挽救手段,但是我们正在用的是一个很好的 hash 函数。

OL,DO1 和 DO2 的实现将共同的被称为 OA(open addressing)——稍后我们将发现它们在性能特性上非常相似。在每一个实现中,单元数从 100 K 到 1 M,有效负载(比如:总的 key + value 大小)从 8 到 4 k 字节我为几个不同的操作记了时间。 keys 和 values 永远是 POD 类型,keys 永远是 8 个字节(除了 8 字节的有效负载,此时 key 和 value 都是 4 字节)因为我的目的是为了测试内存影响而不是哈希函数性能,所以我将 key 放在连续的尺寸空间中。每一个测试都会重复 5 遍,然后记录最小的耗时。

测试的操作在这里:

  • Fill:将一个随机的 key 序列插入到表中(key 在序列中是唯一的)。
  • Presized fill:和 Fill 相似,但是在插入之间我们先为所有的 key 保留足够的内存空间,以防止在 fill 过程中 rehash 或者重申请。
  • Lookup:执行 100 k 次随机 key 查找,所有的 key 都在 table 中。
  • Failed lookup: 执行 100 k 次随机 key 查找,所有的 key 都不在 table 中。
  • Remove:从 table 中移除随机选择的半数元素。
  • Destruct:销毁 table 并释放内存。

你可以在这里下载我的测试代码。这些代码只能在 64 机器上编译(包括Windows和Linux)。在 main() 函数顶部附近有一些开关,你可把它们打开或者关掉——如果全开,可能会需要一两个小时才能结束运行。我收集的结果也放在了那个打包文件里的 Excel 表中。(注意: Windows 和 Linux 在不同的 CPU 上跑的,所以时间不具备可比较性)代码也跑了一些单元测试,用来验证所有的 hash 表实现都能运行正确。

我还顺带尝试了附加的两个实现:Ch 中第一个节点存放在 bucket 中而不是 pool 里,二次探测的开放寻址。 这两个都不足以好到可以放在最终的数据里,但是它们的代码仍放在了打包文件里面。

结果

这里有成吨的数据!! 这一节我将详细的讨论一下结果,但是如果你对此不感兴趣,可以直接跳到下一节的总结。

Windows

这是所有的测试的图表结果,使用 Visual Studio 2012 编译,运行于 Windows 8.1 和 Core i7-4710HQ 机器上。

 title=

从左至右是不同的有效负载大小,从上往下是不同的操作(注意:不是所有的Y轴都是相同的比例!)我将为每一个操作总结一下主要趋向。

Fill

在我的 hash 表中,Ch 稍比任何的 OA 变种要好。随着哈希表大小和有效负载的加大,差距也随之变大。我猜测这是由于 Ch 只需要从一个空闲链表中拉取一个元素,然后把它放在 bucket 前面,而 OA 不得不搜索一部分 bucket 来找到一个空位置。所有的 OA 变种的性能表现基本都很相似,当然 DO1 稍微有点优势。

在小负载的情况,UM 几乎是所有 hash 表中表现最差的 —— 因为 UM 为每一次的插入申请(内存)付出了沉重的代价。但是在 128 字节的时候,这些 hash 表基本相当,大负载的时候 UM 还赢了点。因为,我所有的实现都需要重新调整元素池的大小,并需要移动大量的元素到新池里面,这一点我几乎无能为力;而 UM 一旦为元素申请了内存后便不需要移动了。注意大负载中图表上夸张的跳步!这更确认了重新调整大小带来的问题。相反,UM 只是线性上升 —— 只需要重新调整 bucket 数组大小。由于没有太多隆起的地方,所以相对有效率。

Presized fill

大致和 Fill 相似,但是图示结果更加的线性光滑,没有太大的跳步(因为不需要 rehash ),所有的实现差距在这一测试中要缩小了些。大负载时 UM 依然稍快于 Ch,问题还是在于重新调整大小上。Ch 仍是稳步少快于 OA 变种,但是 DO1 比其它的 OA 稍有优势。

Lookup

所有的实现都相当的集中。除了最小负载时,DO1 和 OL 稍快,其余情况下 UM 和 DO2 都跑在了前面。(LCTT 译注: 你确定?)真的,我无法描述 UM 在这一步做的多么好。尽管需要遍历链表,但是 UM 还是坚守了面向数据的本性。

顺带一提,查找时间和 hash 表的大小有着很弱的关联,这真的很有意思。 哈希表查找时间期望上是一个常量时间,所以在的渐进视图中,性能不应该依赖于表的大小。但是那是在忽视了 cache 影响的情况下!作为具体的例子,当我们在具有 10 k 条目的表中做 100 k 次查找时,速度会便变快,因为在第一次 10 k - 20 k 次查找后,大部分的表会处在 L3 中。

Failed lookup

相对于成功查找,这里就有点更分散一些。DO1 和 DO2 跑在了前面,但 UM 并没有落下,OL 则是捉襟见肘啊。我猜测,这可能是因为 OL 整体上具有更长的搜索路径,尤其是在失败查询时;内存中,hash 值在 key 和 value 之飘来荡去的找不着出路,我也很受伤啊。DO1 和 DO2 具有相同的搜索长度,但是它们将所有的 hash 值打包在内存中,这使得问题有所缓解。

Remove

DO2 很显然是赢家,但 DO1 也未落下。Ch 则落后,UM 则是差的不是一丁半点(主要是因为每次移除都要释放内存);差距随着负载的增加而拉大。移除操作是唯一不需要接触数据的操作,只需要 hash 值和 key 的帮助,这也是为什么 DO1 和 DO2 在移除操作中的表现大相径庭,而其它测试中却保持一致。(如果你的值不是 POD 类型的,并需要析构,这种差异应该是会消失的。)

Destruct

Ch 除了最小负载,其它的情况都是最快的(最小负载时,约等于 OA 变种)。所有的 OA 变种基本都是相等的。注意,在我的 hash 表中所做的所有析构操作都是释放少量的内存 buffer 。但是 在Windows中,释放内存的消耗和大小成比例关系。(而且,这是一个很显著的开支 —— 申请 ~1 GB 的内存需要 ~100 ms 的时间去释放!)

UM 在析构时是最慢的一个(小负载时,慢的程度可以用数量级来衡量),大负载时依旧是稍慢些。对于 UM 来讲,释放每一个元素而不是释放一组数组真的是一个硬伤。

Linux

我还在装有 Linux Mint 17.1 的 Core i5-4570S 机器上使用 gcc 4.8 和 clang 3.5 来运行了测试。gcc 和 clang 的结果很相像,因此我只展示了 gcc 的;完整的结果集合包含在了代码下载打包文件中,链接在上面。

 title=

大部分结果和 Windows 很相似,因此我只高亮了一些有趣的不同点。

Lookup

这里 DO1 跑在前头,而在 Windows 中 DO2 更快些。(LCTT 译注: 这里原文写错了吧?)同样,UM 和 Ch 落后于其它所有的实现——过多的指针追踪,然而 OA 只需要在内存中线性的移动即可。至于 Windows 和 Linux 结果为何不同,则不是很清楚。UM 同样比 Ch 慢了不少,特别是大负载时,这很奇怪;我期望的是它们可以基本相同。

Failed lookup

UM 再一次落后于其它实现,甚至比 OL 还要慢。我再一次无法理解为何 UM 比 Ch 慢这么多,Linux 和 Windows 的结果为何有着如此大的差距。

Destruct

在我的实现中,小负载的时候,析构的消耗太少了,以至于无法测量;在大负载中,线性增加的比例和创建的虚拟内存页数量相关,而不是申请到的数量?同样,要比 Windows 中的析构快上几个数量级。但是并不是所有的都和 hash 表有关;我们在这里可以看出不同系统和运行时内存系统的表现。貌似,Linux 释放大内存块是要比 Windows 快上不少(或者 Linux 很好的隐藏了开支,或许将释放工作推迟到了进程退出,又或者将工作推给了其它线程或者进程)。

UM 由于要释放每一个元素,所以在所有的负载中都比其它慢上几个数量级。事实上,我将图片做了剪裁,因为 UM 太慢了,以至于破坏了 Y 轴的比例。

总结

好,当我们凝视各种情况下的数据和矛盾的结果时,我们可以得出什么结果呢?我想直接了当的告诉你这些 hash 表变种中有一个打败了其它所有的 hash 表,但是这显然不那么简单。不过我们仍然可以学到一些东西。

首先,在大多数情况下我们“很容易”做的比 std::unordered_map 还要好。我为这些测试所写的所有实现(它们并不复杂;我只花了一两个小时就写完了)要么是符合 unordered_map 要么是在其基础上做的提高,除了大负载(超过128字节)中的插入性能, unordered_map 为每一个节点独立申请存储占了优势。(尽管我没有测试,我同样期望 unordered_map 能在非 POD 类型的负载上取得胜利。)具有指导意义的是,如果你非常关心性能,不要假设你的标准库中的数据结构是高度优化的。它们可能只是在 C++ 标准的一致性上做了优化,但不是性能。:P

其次,如果不管在小负载还是超负载中,若都只用 DO1 (开放寻址,线性探测,hashes/states 和 key/vaules分别处在隔离的普通数组中),那可能不会有啥好表现。这不是最快的插入,但也不坏(还比 unordered_map 快),并且在查找,移除,析构中也很快。你所知道的 —— “面向数据设计”完成了!

注意,我的为这些哈希表做的测试代码远未能用于生产环境——它们只支持 POD 类型,没有拷贝构造函数以及类似的东西,也未检测重复的 key,等等。我将可能尽快的构建一些实际的 hash 表,用于我的实用库中。为了覆盖基础部分,我想我将有两个变种:一个基于 DO1,用于小的,移动时不需要太大开支的负载;另一个用于链接并且避免重新申请和移动元素(就像 unordered_map ),用于大负载或者移动起来需要大开支的负载情况。这应该会给我带来最好的两个世界。

与此同时,我希望你们会有所启迪。最后记住,如果 Chandler Carruth 和 Mike Acton 在数据结构上给你提出些建议,你一定要听。


作者简介:

我是一名图形程序员,目前在西雅图做自由职业者。之前我在 NVIDIA 的 DevTech 软件团队中工作,并在美少女特工队工作室中为 PS3 和 PS4 的 Infamous 系列游戏开发渲染技术。

自 2002 年起,我对图形非常感兴趣,并且已经完成了一系列的工作,包括:雾、大气雾霾、体积照明、水、视觉效果、粒子系统、皮肤和头发阴影、后处理、镜面模型、线性空间渲染、和 GPU 性能测量和优化。

你可以在我的博客了解更多和我有关的事,除了图形,我还对理论物理和程序设计感兴趣。

你可以在 [email protected] 或者在 Twitter(@Reedbeta)/Google+ 上关注我。我也会经常在 StackExchange 上回答计算机图形的问题。


via: http://reedbeta.com/blog/data-oriented-hash-table/

作者:Nathan Reed 译者:sanfusu 校对:wxy

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