乐者为王

Do one thing, and do it well.

Git笔记之status

git status命令会显示当前项目状态的一个简单总结:

  • 当Git提示说“Untracked files”表示新增加的文件还没有被纳入到Git版本控制系统的追踪;
  • 如果提示说“Changed but not updated”表示在工作目录下做的修改还没有被添加到索引文件中;
  • 如果你看到提示说“Changes to be committed”则表示发现了你已经添加到索引文件里,但尚未提交的内容。

当然你也有可能喜欢短小的输出提示,那么可以使用-s选项:

1
git status -s
  • 它会用??表示“Untracked files”的文件;
  • 用红色的M表示“Changed but not updated”的文件;
  • 用绿色的M表示“Changes to be committed”;
  • 用绿色的A表示新增加但“Changes to be committed”的文件。

Git笔记之diff

今天,我们重点来探讨一下git diff的用法。

我们知道在Git提交环节存在三大部分:工作目录、索引文件、仓库。每当你对代码进行了修改,工作目录的状态就发生了改变。索引文件是连接工作目录和仓库的桥梁,每次当我们使用git add命令后,索引文件的内容就改变了,变得和工作目录同步。仓库是提交环节的最后阶段,只有在我们使用git commit命令将索引文件中的内容提交后,我们的代码才真正地进入了仓库。

git diff就是用来比较提交环节三大部分的差别。其中,纯粹的git diff是查看工作目录与索引文件之间的差别;git diff –cached是查看索引文件和仓库之间的差别;git diff HEAD则是查看工作目录和仓库之间的差别。

下图是三种查看方式的说明:

git-diff

git diff会输出全部的差别内容,但有时我们只想看看修改的内容的汇总,这时就可以使用--stat选项。这里是它的使用实例:

1
2
3
git diff --stat
git diff --cached --stat
git diff HEAD --stat

如果你想查看其它分支与当前分支的差别,只要运行以下命令即可:

1
git diff <otherbranch>

你也可以加上路径限定符,来比较某一个文件或目录:

1
git diff <otherbranch> <filename>

Git笔记之log

最简单的查看开发日志的命令是git log。如果你觉得git log给出的信息太单薄,那么可以使用git log -p,它不但会给出非常详细的开发日志,而且还会显示每次提交实际修改的内容。

git log支持很多的参数,可以将这些参数结合起来使用。下面是经常使用的一些参数:

1
2
3
4
5
6
7
-–name-only  # 只显示变更文件的名称
-–oneline    # 将提交信息压缩到一行显示
-–graph      # 显示所有提交的依赖树
-–reverse    # 按照逆序显示提交记录(最先提交的在最前面)
-–since      # 显示某个日期之后发生的提交
-–until      # 显示某个日期之前发生的提交
--decorate   # 显示每个commit的引用

如果你曾经与很多小伙伴工作在同一个分支上,也许会有这样的经历,master分支上的大量合并同步到你当前的分支。这使得我们很难分辨哪些变更是发生在master分支,哪些变更是发生在当前分支,且尚未合并到master分支。可以使用git log --no-merges master..查看尚未合并的变更。其中--no-merges选项表示着只显示没有合并到任何分支的变更,master..选项表示只显示没有合并到master分支的变更。

如果想查看文件中指定位置的变更怎么办?可以使用以下命令:

1
git log -L 1,1:<path>  # -L选项告诉Git只要输出指定文件中指定行的变更日志。

你还可以通过如下命令来定位具体的历史记录:

1
2
3
git log v3..v7                 # 显示v3之后直至v7的所有历史记录
git log v3..                   # 显示所有v3之后的历史记录
git log -–since="2 weeks ago"  # 显示2周前到现在的所有历史记录

如果这些都不够好用的话,还有一个--pretty选项,可以用来创建高级的自定义输出。譬如,你可以使用--pretty=raw查看日志的原始信息。

Git笔记之stash

在工作中,经常会碰到在修改代码以支持新功能时,发现线上版本出现问题需要在本地查错。因为还在进行中,所以并不想将半成品的代码提交,后面再来修改。这样就需要临时保存修改的文件,然后取出线上版本到工作目录。这个问题的解决之道就是使用git stash命令,它会暂存你的当前工作,这样以后随时可以再来修改:

1
git stash

现在,工作目录变得干净,可以安全地做些别的事情了。

要查看暂存的修改可以使用以下命令:

1
git stash list

稍后,在排除掉线上版本的错误后,你就可以取回所有暂存的修改:

1
git stash pop

现在让我们在脑中想象一个场景:在排除线上版本错误后,没有立即取回暂存的修改,而是做了其它的一些的变更,然后想把暂存的修改并入进来以完成工作。如果两次修改的代码没有冲突,那么事情完美结束;如果后修改的代码和暂存的修改有冲突,那么就会得到Merge conflict的警告。Git没有提供强制pop的功能,所以我们需要一些技巧来解决:

1
git stash show -p | git apply && git stash drop

git stash的其它一些可用选项如下所示:

1
2
3
4
git stash pop --index        # 不仅恢复工作目录,还恢复索引
git stash save "stash name"  # 使用save可以给暂存的修改添加名字
git stash save --keep-index  # 仅仅暂存未索引的文件
git stash clear              # 删除所有暂存的修改

Ubuntu 8.10下实现无线上网

机器是ThinkPad X60,自带Inter PRO/Wireless 3945ABG网卡。

查看网络设置

1
ifconfig

ifconfig

打开无线网卡电源

1
sudo iwconfig wlan0 txpower on

检索区域内的无线网络

1
iwlist wlan0 scan

iwlist-scan

无线网络设置

打开System -> Preferences -> Network Connections。在Wireless页上点击Add按钮,然后在SSID栏填写检索到的ESSID字符串NETGEAR。

wireless-config

如果无线连接需要输入密钥,那么可以在Wireless Security页上设置。譬如,我在路由器端设置的加密方式是WPA/WPA2-PSK (Wi-Fi Protected Access Pre-Shared Key),如图:

netgear-settings

所以在Wireless Security页上Securiy栏中选择WPA & WPA2 Personal,然后在Password栏输入设置的密码。然后就点OK,Ubuntu就去寻找网络了,等到状态从Never变为Now的时候就表示已经联网了。

2009/3/19更新

今天早上忽然发现笔记本不能无线上网了,iwlist wlan0 scan后发现结果为No scan result。使用sudo iwlist wlan0 scan命令,然后再执行iwlist wlan scan就可以看到AP了(原因为何还不知道)。

Git笔记之gitignore

通常我们会有些文件不希望纳入Git的管理,也不希望它们总是出现在未跟踪文件列表中。这些文件大多是些自动生成的文件,譬如编译过程中生成的临时文件,或者一些本地的配置文件。为解决此类问题,Git提供了文件忽略机制,我们可以创建一个名为.gitignore的文件,在其中列出需要忽略的文件模式。文件.gitignore的格式规范如下:

  • 所有空行或者以#开头的行都会被忽略;
  • 可以使用标准的glob模式匹配;
  • 匹配模式可以以“/”开头防止递归;
  • 匹配模式可以以“/”结尾指定目录;
  • 要忽略指定模式以外的文件或目录,可以在模式前加上“!”取反。

上面所谓的glob模式是指简化了的正则表达式:“*”匹配零个或多个任意字符;“[abc]”匹配任何一个列在方括号中的字符;“?”匹配一个任意字符;“[a-z]”匹配所有在这两个字符范围内的字符。“**”匹配任意中间目录,比如a/**/z可以匹配a/z,a/b/z或a/b/c/z等。

要养成一开始就设置好.gitignore文件的习惯,避免将来误提交一些无用的文件。

Git笔记之reset

git reset的作用是重置当前HEAD到指定状态,它有三个选项:--soft、--mixed和--hard。

  • --soft选项既不重置索引内容,也不改变工作目录中的任何内容,它仅仅是重置当前HEAD到指定commit。使用该选项后,git diff的返回信息不变,而git diff --cached和git diff HEAD的返回信息有变化。
  • --mixed是git reset的默认选项,它的作用是重置索引内容,将其定位到指定的commit,但不改变工作目录中的任何内容。使用该选项后,git diff和git diff HEAD的返回信息有变化,而git diff --cached的返回信息不变。
  • --hard选项会重置索引内容,将其定位到指定的commit,并且工作目录中的内容也会完全回退到指定commit时的状态。所以使用该选项后,git diff、git diff --cached和git diff HEAD的返回信息都为空。由于这个选项的杀伤力太大,使用时需要谨慎。

以下是根据三个选项的特性制作的表格:

是否重置索引 是否重置工作目录
--soft N N
--mixed Y N
--hard Y Y

有时候你可能刚刚把一个文件添加到暂存区里,然后就发现这是错误的,接着你可能想从暂存区中删除该文件,这时你可以使用下面这个命令:

1
git reset <file>

它仅从索引中移除文件,但不会触及工作目录。

Git笔记之快速入门

Git的官网地址:https://git-scm.com/

学习路线推介:新手请浏览gittutorial(7),然后是Everyday Git,接着是“git help command”,需要更全面的了解Git请看Git User's Manual

1、初始化仓库

1
2
3
mkdir gittest
cd gittest
git init  # 创建.git隐藏目录,即所谓的Git仓库,用来存储项目的所有历史和元信息

2、告诉项目你的信息

1
2
git config --global user.name "yourname"
git config --global user.email yourname@example.com

3、跟踪新文件

1
2
echo 'Hello Git!' > greeting
git add greeting

这里Git给greeting这个文件的内容生成快照,并把快照存储在Git称之为索引的暂存区。你也可以使用git add .来将当前目录下所有的文件内容加入到暂存区。

4、查看项目当前状态

1
git status

5、提交内容到仓库

1
git commit  # 将暂存区的内容写入仓库

在输入git commit后按回车会跳出一个文本编辑器窗口,要求开发者输入这次提交的开发信息。

git commit的选项:

  • -s 在提交日志信息末尾添加提交者的Signed-off-by行。
  • -m 使用给定的信息作为提交信息。没有该选项会自动打开一个编辑器以填写提交信息。
  • -a 自动将修改过的文件添到暂存区中(新建但没有git add的文件不会受到影响)。

6、查看项目历史

1
git log

如果你想看下每次提交的大致变动情况,可使用以下命令:

1
git log --stat --summary

使用WinDbg检测内存泄漏

英文原文:https://www.codeproject.com/Articles/31382/Memory-Leak-Detection-Using-WinDbg

简介

内存泄漏是一种费时的bug,通常由C++开发者制造。内存泄漏的检测经常是令人厌烦的。如果代码不是你写的,或者代码库相当巨大,事情就会变得很糟糕。

尽管市场上有可用的工具帮助你检测内存泄露,但这些工具大多数不是免费的。我发现WinDbg作为一个免费的强大的工具可以用来解决内存泄漏bug。至少,我们可以知道可能涉嫌引起内存泄漏的代码位置。COM接口泄漏不在这篇文章范围内。

WinDbg是来自Microsoft的一个强大的用户/内核空间调试器,它可以从这里下载和安装。

使用WinDbg

开始使用WinDbg:

  1. 配置符号文件路径指向Microsoft符号服务器SRV*d:\symbols*http://msdl.microsoft.com/download/symbols
  2. 将你的程序EXE/DLL的PDB(程序数据库)路径添加到符号文件路径中;
  3. 你还需要配置操作系统的标记去启用有内存泄漏的进程上的用户栈跟踪。这很简单,可以用gflags.exe做到。gflags.exe在WinDbg安装期间被安装。或者也可以通过命令行来完成,使用命令gflags.exe /i MemoryLeak.exe +ust即可。我的程序名是Test2.exe。因此,为了演示,需要用Test2.exe替换掉MemoryLeak.exe。下面的快照显示了应用Test2.exe的OS标记的设置。

cmd

一旦我们配置好WinDbg的符号文件路径,开启泄漏内存的进程,就可以把WinDbg挂载上去。挂载选项可以在WinDbg的File菜单下找到,或者可以使用F6快捷键启动。下面的快照显示了挂载界面:

attach

WinDbg的!heap命令用来显示堆。在WinDbg帮助文件里有!heap的详尽文档。

我已经开发了一个泄漏内存的小程序,在后面也将使用这个程序来做演示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int _tmain(int argc, _TCHAR* argv[])
{
    while(1)
    {
        AllocateMemory();
    }
    return 0;
}
void AllocateMemory()
{
    int* a = new int[2000];
    ZeroMemory(a, 8000);
    Sleep(1);
}

上面的程序泄漏一个大小为2000*4字节的整型数组。

把WinDbg挂载到进程上后,执行命令!heap -s。-s代表概要。下面是泄漏进程上!heap -s的输出:

1
2
3
4
5
6
7
8
9
10
11
12
0:001> !heap -s
NtGlobalFlag enables following debugging aids for new heaps:
    validate parameters
    stack back traces
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast
                    (k)     (k)    (k)     (k) length      blocks cont. heap
-----------------------------------------------------------------------------
   00150000 58000062    1024     12     12      1     1     1    0      0   L
   00250000 58001062      64     24     24     15     1     1    0      0   L
   00260000 58008060      64     12     12     10     1     1    0      0
   00330000 58001062   64576  47404  47404     13     4     1    0      0
-----------------------------------------------------------------------------

让进程执行一段时间,然后重新进入进程,再次执行!heap -s。下面显示的是该命令的输出:

1
2
3
4
5
6
7
8
9
10
11
12
0:001> !heap -s
NtGlobalFlag enables following debugging aids for new heaps:
   validate parameters
   stack back traces
   Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast
                     (k)     (k)    (k)     (k) length      blocks cont. heap
   -----------------------------------------------------------------------------
    00150000 58000062    1024     12     12      1     1     1    0      0   L
    00250000 58001062      64     24     24     15     1     1    0      0   L
    00260000 58008060      64     12     12     10     1     1    0      0
    00330000 58001062  261184 239484 239484     14     4     1    0      0
   -----------------------------------------------------------------------------

第11行显示了增长堆。上面的快照显示一个句柄为00330000的堆在增长。

执行!heap -stat -h 00330000。这个命令用来显示增长堆的堆统计。下面显示的是该命令的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
0:001> !heap -stat -h 00330000
heap @ 00330000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    1f64 76c6 - e905f58  (99.99)
    1800 1 - 1800  (0.00)
    824 2 - 1048  (0.00)
    238 2 - 470  (0.00)
    244 1 - 244  (0.00)
    4c 5 - 17c  (0.00)
    b0 2 - 160  (0.00)
    86 2 - 10c  (0.00)
    50 3 - f0  (0.00)
    74 2 - e8  (0.00)
    38 4 - e0  (0.00)
    48 3 - d8  (0.00)
    c4 1 - c4  (0.00)
    62 2 - c4  (0.00)
    be 1 - be  (0.00)
    b8 1 - b8  (0.00)
    ae 1 - ae  (0.00)
    ac 1 - ac  (0.00)
    55 2 - aa  (0.00)
    a4 1 - a4  (0.00)

上面的快照显示大小为1f64的76c6个块被分配(第5行)。如此巨大的相同大小的块的数量让我们怀疑这些可能是泄漏块。其余的块分配没有在增长的块数字。

下一步是去获得这些块的地址。使用命令!heap -flt s 1f64。这个命令过滤出堆的所有其它块,并且显示大小为1f64的块的详细信息。

下面显示的是该命令的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
0:001> !heap -flt s 1f64
    _HEAP @ 150000
    _HEAP @ 250000
    _HEAP @ 260000
    _HEAP @ 330000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        003360e0 03f0 0000  [07]   003360e8    01f64 - (busy)
        00338060 03f0 03f0  [07]   00338068    01f64 - (busy)
        00339fe0 03f0 03f0  [07]   00339fe8    01f64 - (busy)
        0033bf60 03f0 03f0  [07]   0033bf68    01f64 - (busy)
        0033dee0 03f0 03f0  [07]   0033dee8    01f64 - (busy)
        01420040 03f0 03f0  [07]   01420048    01f64 - (busy)
        01421fc0 03f0 03f0  [07]   01421fc8    01f64 - (busy)
        01423f40 03f0 03f0  [07]   01423f48    01f64 - (busy)
        01425ec0 03f0 03f0  [07]   01425ec8    01f64 - (busy)
        01427e40 03f0 03f0  [07]   01427e48    01f64 - (busy)
        01429dc0 03f0 03f0  [07]   01429dc8    01f64 - (busy)
        0142bd40 03f0 03f0  [07]   0142bd48    01f64 - (busy)
        0142dcc0 03f0 03f0  [07]   0142dcc8    01f64 - (busy)
        0142fc40 03f0 03f0  [07]   0142fc48    01f64 - (busy)
        01431bc0 03f0 03f0  [07]   01431bc8    01f64 - (busy)
        01433b40 03f0 03f0  [07]   01433b48    01f64 - (busy)
        01435ac0 03f0 03f0  [07]   01435ac8    01f64 - (busy)
        01437a40 03f0 03f0  [07]   01437a48    01f64 - (busy)
        014399c0 03f0 03f0  [07]   014399c8    01f64 - (busy)
        0143b940 03f0 03f0  [07]   0143b948    01f64 - (busy)
        0143d8c0 03f0 03f0  [07]   0143d8c8    01f64 - (busy)
        0143f840 03f0 03f0  [07]   0143f848    01f64 - (busy)
        014417c0 03f0 03f0  [07]   014417c8    01f64 - (busy)
        01443740 03f0 03f0  [07]   01443748    01f64 - (busy)
        014456c0 03f0 03f0  [07]   014456c8    01f64 - (busy)
        01447640 03f0 03f0  [07]   01447648    01f64 - (busy)
        014495c0 03f0 03f0  [07]   014495c8    01f64 - (busy)
        0144b540 03f0 03f0  [07]   0144b548    01f64 - (busy)
        0144d4c0 03f0 03f0  [07]   0144d4c8    01f64 - (busy)
        0144f440 03f0 03f0  [07]   0144f448    01f64 - (busy)
        014513c0 03f0 03f0  [07]   014513c8    01f64 - (busy)
        01453340 03f0 03f0  [07]   01453348    01f64 - (busy)
        014552c0 03f0 03f0  [07]   014552c8    01f64 - (busy)
        01457240 03f0 03f0  [07]   01457248    01f64 - (busy)
        014591c0 03f0 03f0  [07]   014591c8    01f64 - (busy)
        0145b140 03f0 03f0  [07]   0145b148    01f64 - (busy)
        0145d0c0 03f0 03f0  [07]   0145d0c8    01f64 - (busy)
        0145f040 03f0 03f0  [07]   0145f048    01f64 - (busy)
        01460fc0 03f0 03f0  [07]   01460fc8    01f64 - (busy)
        01462f40 03f0 03f0  [07]   01462f48    01f64 - (busy)
        01464ec0 03f0 03f0  [07]   01464ec8    01f64 - (busy)
        01466e40 03f0 03f0  [07]   01466e48    01f64 - (busy)
        01468dc0 03f0 03f0  [07]   01468dc8    01f64 - (busy)

使用任何来自上面输出中的UsrPtr列值,然后使用命令!heap -p -a UsrPtr去显示UsrPtr的调用栈。我选择了第27行的0143d8c8。

执行!heap -p -a 0143d8c8,我们得到下面显示的调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0:001> !heap -p -a 0143d8c8
    address 0143d8c8 found in
    _HEAP @ 330000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0143d8c0 03f0 0000  [07]   0143d8c8    01f64 - (busy)
        Trace: 0025
        7c96d6dc ntdll!RtlDebugAllocateHeap+0x000000e1
        7c949d18 ntdll!RtlAllocateHeapSlowly+0x00000044
        7c91b298 ntdll!RtlAllocateHeap+0x00000e64
        102c103e MSVCR90D!_heap_alloc_base+0x0000005e
        102cfd76 MSVCR90D!_heap_alloc_dbg_impl+0x000001f6
        102cfb2f MSVCR90D!_nh_malloc_dbg_impl+0x0000001f
        102cfadc MSVCR90D!_nh_malloc_dbg+0x0000002c
        102db25b MSVCR90D!malloc+0x0000001b
        102bd691 MSVCR90D!operator new+0x00000011
        102bd71f MSVCR90D!operator new[]+0x0000000f
        4113d8 Test2!AllocateMemory+0x00000028
        41145c Test2!wmain+0x0000002c
        411a08 Test2!__tmainCRTStartup+0x000001a8
        41184f Test2!wmainCRTStartup+0x0000000f
        7c816fd7 kernel32!BaseProcessStart+0x00000023

第16-19行显示了来自我们代码的函数。

注意:有时候,可能发生!heap -s命令不显示增长堆的情况。如果那样的话,使用!heap -stat -h命令列出带有堆的大小和块的数量的所有堆。识别在增长的块的数量,然后使用!heap -flt s SIZE(SIZE = 涉嫌块的大小)命令。

VirtualBox虚拟Ubuntu 8.10时共享文件夹的使用

我的环境:主机操作系统是Windows Server 2008,虚拟机操作系统是Ubuntu 8.10,虚拟机是VirtualBox 2.0.4。

安装增强功能包(Guest Additions)

安装好Ubuntu 8.10后,运行Ubuntu并登录。然后在VirtualBox的菜单里选择“Devices” -> “Install Guest Additions”。如图所示:

virtualbox-guest-addtions

你会发现在Ubuntu桌面上多出一个光盘图标,这张光盘默认被自动加载到了文件夹/media/cdrom0。在命令行下输入:

1
2
cd /media/cdrom0
sudo ./VboxLinuxAdditions.run

开始安装工具包。安装完毕后会提示要重启Ubuntu。

设置共享文件夹

重启完成后点击“Devices” -> “Shared Folders”菜单,添加一个共享文件夹,选项“Make Permanent”是指该文件夹是否是持久的。共享名可以任取一个自己喜欢的,比如“flamingo”,尽量使用英文名称。

挂载共享文件夹

重新进入虚拟Ubuntu,打开命令行窗口输入:

1
2
sudo mkdir /mnt/shared
sudo mount -t vboxsf flamingo /mnt/shared

其中“flamingo”是之前创建的共享文件夹的名字。现在Ubuntu和主机可以分享文件了。

假如你不想每一次都手动挂载,可以在/etc/fstab中添加一项:

1
flamingo /mnt/shared vboxsf defaults 0 0

这样每次系统启动时就能够自动挂载了。

需要注意的是,共享文件夹的名称千万不要和挂载点的名称相同。比如,上面的挂载点是/mnt/shared,如果共享文件夹的名字也是shared的话,在挂载的时候就会出现如下的错误信息(看 http://www.virtualbox.org/ticket/2265 ):

1
/sbin/mount.vboxsf: mounting failed with the error: Protocol error

原因分析可以看Tips on running Sun Virtualbox的Shared Folder on a Linux Guest节。

卸载的话使用下面的命令:

1
sudo umount -f /mnt/shared

2017/7/11更新

在设置共享文件夹时,如果勾选了“Auto-mount”,那么Ubuntu会自动在/media里建立以“sf_”为前缀并加上共享名的挂载点。不过因为这个挂载点默认的权限是给vbox创建的用户组“vboxsf”的,所以你会无法查看,可以修改/etc/group,把自己的用户名加到“vboxsf”组解决。并且,在这种情况下你通过手动修改/etc/fstab以实现自动挂载会失败。所以,若想通过修改/etc/fstab实现自动挂载,在设置共享文件夹时就不能勾选“Auto-mount”。