帮同事分析Makefile文件

同事想看开源项目contiki,无奈被其复杂的Makefile搞晕而找我帮忙。我大致看了一些,由于这个项目涉及到多个目标平台,结构复杂可想而知啦-_-!!!。这里用相对简单的原生平台(native)来剖析一下。

大致上有四个重要的Makefile文件:

  1. 自己的应用程序Makefile,如examples/email/Makefile:

    这个定义了最终的目标应用程序名,如email:

    CONTIKI_PROJECT = email-client
    all: $(CONTIKI_PROJECT)
    
    APPS = email
    
    CONTIKI = ../..
    include $(CONTIKI)/Makefile.include
    
  2. 根目录的Makefile.include:

    这个提供了统一的Makefile框架,提供一般的编译选项和功能,各个平台可以覆盖这些默认规则。

  3. platform/native/Makefile.native:

    这个是平台相关的硬件,如串口、led等,以及定义最终链接生成的目标文件%.$(Target)的编译规则。原生平台(native)直接在Makefile.include里定义了最终目标%.$(Target)的规则了。其他的平台要定义宏CUSTOM_RULE_LINK来进行覆盖。

  4. cpu/native/Makefile.native

    这个定义目标文件和库的编译。如果这里不是native平台,要定义自己的链接和编译规则,要预先定义几个宏覆盖系统默认的规则:

    CUSTOM_RULE_LINK=1
    CUSTOM_RULE_C_TO_OBJECTDIR_O=1
    CUSTOM_RULE_ALLOBJS_TO_TARGETLIB=1
    

    上面分别对应链接、编译和打包成库。编译和打包成库直接在这个Makefile里,最终的链接在platform//Makefile.里。

讲了这么多,来看一下最技巧性的地方。

CONTIKI_PROJECT = email-client
all: $(CONTIKI_PROJECT)

这里只指定了一个email-client,不带任何扩展名。那make如果知道依赖什么能?这里就要用到make的隐式规则了。

隐式规则

make看到email-client这个目标会采用两条隐式规则:

%:%.c
    gcc $(CFLAGS) %< -o $@

或者

# 这个会生成中间文件xxx.o,make会在完成时删除。
%:%.o
    gcc %< -o $@

%.o:%.c
    gcc $(CFLAGS) -c %< -o $@  

那如果要让make执行我预定义的依赖项:

# @ 表示占位符,如果没有则代表删除这条规则
%:%.$(TAGET)
    @

则要删除%:%.c这条隐式规则才能执行我的%:%.$(TAGET)。至于第二条隐式规则相比我的%:%.$(TAGET)要复杂,make是比较聪明的不回绕远路的。

删除%:%.c规则(不加命令项):

%:%.c

重看Makefile.include

有了上面的知识,下面就比较好理解了,最终的目标是email-client.native,链接规则是:

ifndef CUSTOM_RULE_LINK
%.$(TARGET): %.co $(PROJECT_OBJECTFILES) $(PROJECT_LIBRARIES) contiki-$(TARGET).a
    $(LD) $(LDFLAGS) $(TARGET_STARTFILES) ${filter-out %.a,$^} ${filter %.a,$^} $(TARGET_LIBFILES) -o $@
endif

编译规则是:

ifndef CUSTOM_RULE_C_TO_OBJECTDIR_O
$(OBJECTDIR)/%.o: %.c
    $(CC) $(CFLAGS) -MMD -c $< -o $@
    @$(FINALIZE_DEPENDENCY)
endif

打包生产库规则:

ifndef CUSTOM_RULE_ALLOBJS_TO_TARGETLIB
contiki-$(TARGET).a: $(CONTIKI_OBJECTFILES)
    $(AR) $(AROPTS) $@ $^
endif

其他平台的类似,只是中间有很多步骤,这里不细讲。

Makefile自动产生依赖文件(二)

昨天写的Makefile自动产生依赖文件(一)是我以前用的方法总结,但近期发现了GCC本来就有这个功能,只是我没发现罢了-_-!!!。

这还说着摆弄Eclipse CDT开发环境时,无意中发现Eclipse产生的Makefile异常简洁,其核心的src/subdir.mk文件如下:

CPP_SRCS += 
../src/HelloWorld.cpp 

OBJS += 
./src/HelloWorld.o 

CPP_DEPS += 
./src/HelloWorld.d 

src/%.o: ../src/%.cpp
    @echo 'Building file:
 
lt;' @echo 'Invoking: Cygwin C++ Compiler' g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o "$@" "
 
lt;" @echo 'Finished building:
 
lt;' @echo ' ' 

细看src/%.o: ../src/%.cpp的隐规则,发现多了几个参数”-MMD -MP -MF -MT”。”-MMD“我还认的,就是直接产生.d文件而不是输出到屏幕,其他就陌生了。遇到问题找Man啦,发现正好解决了我前一篇博客所说的两个问题,gcc果然不我欺也。

  • -MP: 解决重命名头文件导致的依赖破裂,跟上次的思路一样,生成了dummy目标。
  • -MF: 这个是指定输出的.d文件格式。
  • -MT: 这个解决了输出的目标文件不带路径问题,可以指定目标文件的格式。

现在可以抛弃之前的makedepend,一个Makefile文件搞定一切:)。


更新

今天发现头文件更新不起作用,看来一下.d文件如下:

xxx.d: xxx.h //这里应为xxx.o:xxx.h
xxx.h:

惊讶的发现竟然没有xxx.o的依赖,再仔细检查了gcc的依赖选项发现之前没仔细看-MT修改了依赖项变成了.d了。其实-MT不用加上,加上就修改了目标名,最后的命令是:

$(OBJS):%.o:%.c
    gcc -c $(CFLAGS) -MMD -MP -MF"$(@:%.o=%.d)" -o $@
 
lt; 

这里用到了makefile静态规则(要详细看那边《跟我一起学Makefile》),这样就保证文件名不会被gcc给截断造成依赖文件的目标名错误。

最后贴个标准的吧:

SRC_DIR= src others
CFLAGS+=-Wall -g 
CFLAGS+=$(addprefix -I, $(SRC_DIR))
CFILES=$(shell find $(SRC_DIR) -maxdepth 1 -name "*.c")
OBJS=$(CFILES:%.c=%.o)
LIBS+= 

all:$(TARGET)

-include $(addsuffix /*.d, $(SRC_DIR))

$(TARGET):$(OBJS)
    gcc $(LDFLAGS) $^ -o $@ $(LIBS)

$(OBJS):%.o:%.c
    gcc -c $(CFLAGS) -MMD -MP -MF"$(@:%.o=%.d)" -o $@
 
lt; clean: -rm -f $(addsuffix /*.d, $(SRC_DIR)) $(addsuffix /*.o, $(SRC_DIR)) $(TARGET) 

Apache频频Crash

自从换到WordPress后Apache频频Crash,每次都得手动重启Apache(怀念以前小巧的Python博客)-_-!!!。最近比较忙也懒的理,我推测是VPS内存太小(512M),伺候不好MySQL和Apache这些大爷啊:)。前几天查看了下进程列表发现MySQL内存飙到150M了,实在忍无可忍便优化了一下MySQL,网上大致的意思是Innodb引擎耗内存,换用MyISAM。

修改/etc/mysql/my.cnf,在[mysql]里加入

default-storage-engin=MyISAM
loose-skip-innodb

注意MySQL5.5.18中skip-innodb已经改成loose-skip-innodb了。

重启MySQL,发现内存占用一直保持在50M内,效果还不错。顺便关闭了一些不要的服务(DNS等),如果Apache还Crash,那得优化Apache了-_-!!!。

参考

http://www.justwinit.cn/post/4785/

Makefile自动产生依赖文件(一)

一直以来用Makefile自动产生源文件(C/C++)的关联文件(.d)是一大问题。这里主要涉及到的是gcc的-MM选项(-M会包含系统头文件)。但gcc的-MM不是很完美,主要涉及到两方面:

  1. 没有目标文件路径

    g++ -MM src/string_parse.cpp
    

    输出:

    string_parse.o: src/string_parse.cpp src/string_parse.h
    

    而我们需要的是:

    src/string_parse.o: src/string_parse.cpp src/string_parse.h
    
  2. 头文件重命名时出现错误

    万一我们重命名了string_parse.h为string_other_parse.h则

    string_parse.o: src/string_parse.cpp src/string_other_parse.h
    

    make会找不到依赖文件str/string_other_parse.h报错。

参考网上的方案,我的解决方法是写个makedepend脚本处理:

#!/usr/bin/env bash
# Generate C/C++ file make depend file: Makefile.depends.
# Usage:
#   makedepend -f files -c (gcc|g++) -p flags

help() 
{
    echo "${0#*/} -f files -c (gcc|g++) -p flags"
}

while getopts "hf:c:p:" opt; do
    case $opt in
        h)
            help
            exit 0
            ;;
        f)
            files=$OPTARG
            ;;

        c)
            compiler=$OPTARG
            ;;

        p)
            flags=$OPTARG
            ;;
        *)
            exit 1
            ;;
    esac
done


if [ -z "$files" ] || [ -z "$compiler" ] || [ -z "$flags" ] ; then
    exit 1
fi

for file in $files
do
    obj="${file%.*}.o"
    result=`$compiler -MM $flags $file`
    if [ $? != 0 ] ; then
        exit 1
    fi

    result=${result/*:/$obj:}
    echo $result | sed -e 's/\//g'
    # add all file as target, so rename file will not error.
    echo $result | sed -e 's/.*://' -e 's/\//g' | fmt -1 | sed -e 's/^ *//' -e 's/$/:/'
done

在Makefile是这么用的:

Makefile.deps: $(FILES) $(HEADERS)
    makedepend -f "$(FILES)" -c "$(CXX)" -p "$(CPPFLAGS)" >$@

最后生成的Makefile.deps是这样的:

src/string_parse.o: src/string_parse.cpp src/string_parse.h
src/string_parse.cpp:
src/string_parse.h:

为了防止修改文件名后找不到,makedepend为每个依赖文件都建了一个空规则,make如果找不到文件,可以在这里找到。由于文件不存在,成为了伪目标,而伪目标永远是最新的,从而触发编译命令

参考:

陈皓的《跟我一起写Makefile》

Autodependencies with GNU make

http://blog.vjeux.com/category/makefile

科学

这是前天CCTV里报道世界末日破灭的专题节目,主持人的一句口号令我印象深刻–“相信科学”。是的,从小被灌输的真理就是这个相信科学,科学的反意词是迷信或者是宗教吧。可惜的是现在的中国人基本没有信仰(这是可怕之处),非得找出一个的话,除了信仰物质(钱)外就是这个科学了。但是科学能给人以真正的快乐吗,我看未必如此。

小时候当然都信科学了,那是人人的梦想似乎都是科学家吧:)。一次家里请来了个道士之类的,我很是反感,以至于奚落我的父母。我父母当然是传统的中国人,吃苦耐力,家庭为重,那是一个头上三尺有神灵是世界,但是他们过的心安理得,哪里像现在的年轻人,惴惴不安,迷失在社会之中。在那本《中国近代史》里说过,古代中国是氏族社会,国家的基本单位是氏族。族中有长老做领袖,有相应的族规,如果有人犯法或犯族规,要有本族先处理,官府不能干涉。在这种群体环境庇护下,加上宗教的安抚,使人们度过了多少个艰难困苦岁月。但是近代以来氏族社会的瓦解为家庭社会,再瓦解到现在年轻一代的个人社会(一人吃饱,全家不饿-_-!!!)。扯远了,总之用电影《Life of Pi》(国内翻译成少年派xxx让人费解)里Pi母亲的一句话–“科学就算能解释一切,单不能解释你的内心“以此反驳Pi父亲的唯科学论。

现在说宗教,宗教其实也像科学用来解释一切,在宗教这个大框架下,每个人都有自己的位置,所有的现象都有确切的解释,有自己的一套理论(甭管对不对),所以每个人的内心都是平静的,遇到突发的灾难,都能够请神灵保佑度过难关。但科学能保佑我们不受心灵的伤害吗?这很难说,况且科学也有解释不了的事情(宇宙、时间、未来),科学可能带给人焦虑。

我不是说科学不好,只是觉得每件事都有它的代价,科学也不例外。在国内相信科学的代价就是剥夺你选择信仰的权利。

可悲的是,当前我们都失去了自身信仰(我也是),不知道如果再来个灾难之类的,我们有几个能像少年派,这是我比较担心的。所以说先进不一定都好,每个人都要付出代价。

看Bash项目logdotsh

无聊中看到了团子的小屋写的Bash日志库logdotsh。下下来大致看了一下,觉的不错。我本是Bash新手,正需要这种项目练练手,看完以后收获很多。

大致上我知道Bash里没有库的概念,所以一般都单独一个配置脚本,用source命令进行包含。里面提供各种功能函数供用户调用。logdotsh也是这种库的形式,你用用它的时候必须在自己的脚本中包含它。

. ./log.sh

但Bash里有没提供像C/C++那样的#ifndef/#endif防止重复包含文件,所以里面设置了全局变量_log_set_default,每次检查是否已经包含,防止覆盖内部变量。

if [ -z "$_log_set_default" ]; then
    _log_set_default=1
    ...
fi

接下来是各种日志输出接口:do_log、log_msg、debug_msg、info_msg、warn_msg、error_msg等。每次调用先保存在/tmp日志文件上,再根据level觉得是否打印在屏幕上(有对应的颜色区分)。

我看到了从未遇到的Bash数组,看了ABS的介绍,感觉很不错。

西湖暴走

前天有事去市区,办完事出来吃了顿午餐(好像叫邻什么里,吃了芹菜炒肉丝什么的,奇怪的是吃出老妈烧的家常菜味道:)),下午闲着没事而且离西湖很近,就顺便冒着寒冷逛逛西湖。

由于不是周末,游人很冷清。望着西湖,我萌生了绕湖走一圈的冲动,跟电影中阿甘的情景一样。西湖对于我来说即熟悉又陌生,我希望找到我那个熟悉的西湖。我沿着湖边小路,穿梭在熟悉的湖面和陌生的人群之间。我发现湖面和人群之间有着巨大的鸿沟(我想起了唯一记得的顾城的《远和近》),我不喜欢拍人的风景照(一般旅游除了拍照还剩下什么呢?),因为对我来说人工是自然反面,而恰恰这么美丽的自然偏偏横插个人,拍出来的照片可想而知多么别扭。是的我认为有人的风景照是对风景的玷污,所以外出旅游我从不带相机,只要带上眼睛和一颗敬畏的心就够了,也许一篇游记也不错。抱歉又在扯淡了,当我穿过一群唱歌的大妈和仿古建筑后,我意识到西湖已经面目全非,是的除了湖水是真的外,其他都无一是近几年仿照的产物。至于原因全都毁与560年代,这个都知道,我也不说了。我无奈的叹息,除了叹息还能说什么呢,现在的西湖与任何一个游乐场有什么不同呢。所以唯一值得看的就是那变换莫测的湖水了。

走了一半,回头望湖水,远处的城市高楼隐藏在厚厚的PM2.5中,我幻想古时的风景和古人的生活。走着走着,我突然意识到西湖这个词包含的信息。古时的西湖在城市的西边,所以古时的市区应该在延安路这边,那时的城北应该是一片田园风光吧。反正一边走,这种稀奇古怪的想法喷涌而出,怪不得作家需要时不时的出去采风。

走到后来就没什么好说了,纯粹为了走而走,幸亏平时也常常跑步锻炼,倒感觉不到累。最后从苏堤沿白堤回到出发点,估计也就10来公里吧。回到住处,朋友问我在干什么,我脱口而出暴走西湖。是的,有时候人一时不去做,一辈子也不会去做。

enter image description here BTW:上图的亭子已经倒塌了,逝去了的东西是不可能复原的。

Ubuntu装Win7双系统

最近朋友老是推荐我装Win7,禁不住劝告就装了一下试试。一般来说要先装Windows再装Linux,这个地球人都知道。因为windows回覆盖MBR的引导记录,如果预先装了Linux那你就倒霉了(现在有了grub4dos情况好一点了)。反观Linux,因为有神器grub什么都能引导,那就不用怕覆盖了:)。

我已经装了Ubuntu,为了体验Win7就不能用普通方法安装了。

分区

首先第一步就是划分一个主分区给Win7(windows一定要装在主分区上,而不是逻辑分区)。我用Ubuntu Live CD启动,用了gparted无损分区软件搞定(特别适合分割调整分区,而且不用担心数据丢失,理论上是的)。最后的分区如下,/dev/sda4是给Win7的,分了40G的空间(Win7很耗空间)。其他要注意的是要去掉/dev/sda1的Boot标记(Linux用不到,主要是留给Windows用的),只有一个主分区需要Boot而且是/dev/sda4。

Device     Boot     Start      End       Blocks     Id  System
/dev/sda1            2048    20000767     9999360   83  Linux
/dev/sda2        20000768    20391935      195584   82  Linux swap / Solaris
/dev/sda3        20393982   894525439   437065729    5  Extended
/dev/sda4   *   894525440   976771071    41122816    7  HPFS/NTFS/exFAT
/dev/sda5        20393984   894523391   437064704   83  Linux

备份MBR

下一步是备份MBR,因为Win7会修改MBR,如果要找回之前的Ubuntu只能恢复MBR:

sudo dd if=/dev/sda of=./mbr.txt bs=512 count=1

这里扯一下MBR,用hexdump看看mbr.txt

hexdump mbr.txt

基本上Linux和Win7的分区表识别是相同的(4个分区,每个分区有分区类型:例如5表示扩展分区,其他的是什么文件系统),不同在于分区表前的446个字节。所以恢复是只要恢复MBR的446个字节。

装Win7

这个不用多说,地球人都会。

恢复MBR

装完了Win7当然进不去Ubuntu了,所以用Live CD启动进去恢复MBR了。

sudo dd if=./mbr.txt of=/dev/sda bs=446 count=1

更新Grub配置

恢复MBR后进入Ubuntu更新Grub配置,Grub会扫描分区从而识别Win7。

$ sudo update-grub
Generating grub.cfg ...
Found linux image: /boot/vmlinuz-3.2.0-34-generic-pae
Found initrd image: /boot/initrd.img-3.2.0-34-generic-pae
Found linux image: /boot/vmlinuz-3.2.0-33-generic-pae
Found initrd image: /boot/initrd.img-3.2.0-33-generic-pae
Found linux image: /boot/vmlinuz-3.2.0-32-generic-pae
Found initrd image: /boot/initrd.img-3.2.0-32-generic-pae
Found memtest86+ image: /boot/memtest86+.bin
Found Windows 7 (loader) on /dev/sda4
done

OK,大功告成。重启电脑,按住Shift键进入Grub选择Win7就就去了。进了Win7才发现没有网卡驱动、无线驱动,偏偏我的Dell笔记本是个裸机,连官网都找不到Win7驱动-_-!!!,看来还是Linux好啊。

BTW:今天发现Win7的设备管理器里可以在线搜索驱动,这个比较创新啊:)。A卡驱动仍旧没着落,无法安装,暂时用了Intel的集成显卡驱动。

DNS其他

DNS服务器类型

  1. Cache only服务器
    这类没有自己负责解析的域名,只有一个DNS根服务器文件用来进行递归查询。
  2. Forwarding服务器(例如:dnsmasq)
    这类干脆连DNS根服务器文件列表也没有,有什么查询抛给上层DNS服务器处理。

查询域名托管DNS服务器

如果想知道域名放在哪个DNS服务器上,一般那个DNS服务器的域名会有NS记录表示当前放在DNS服务器域名。

$dig freezhongzi.info

;; AUTHORITY SECTION:
freezhongzi.info.   600 IN  NS  f1g1ns1.dnspod.net.
freezhongzi.info.   600 IN  NS  f1g1ns2.dnspod.net.

返回结果中有两条NS记录对应托管的DNS服务器。如果在dnspod停用那两条NS记录,则返回的结果就只有A记录了。

DNS反解:

反解查询首先要转化为IP.in-addr.arpa的格式(xxx.xxx.xxx.xxx.in-addr.arpa),流程跟上面一样,从DNS根服务器一直查到我的IP地址拥有方burst的DNS服务器:

dns.burst.net
dns1.burst.net

为了进行DNS反向查询,有专门的域名.in-addr.arpa进行转换,拥有IP地址的人进行设置(不是注册域名或VPS的人,而是申请IP地址的人)。

这有什么用呢,一般的邮件服务器为了验证垃圾邮件,都对发送方的IP地址进行反向解析看看是不是固定的IP地址,从而杜绝垃圾邮件。

查询网站主机提供商

比如要查看本站的VPS提供商:

$dig -x xxx.xxx.xxx.xxx

;; ANSWER SECTION:
xxx.xxx.xxx.xxx.in-addr.arpa. 14313 IN    PTR xxx.xxx.xxx.xxx.static.hostnoc.net.

;; AUTHORITY SECTION:
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  NS2.HOSTNOC.EU.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  NS1.HOSTNOC.EU.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  dns1.burst.net.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  NS1.hostnoc.net.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  dns.burst.net.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  NS2.hostnoc.net.

可以发现NS记录中的有burst的dns,所以VPS是Burst。不要小看一个DNS,如果挖掘的够深,会有很多信息。

整个过程其实可以用dig完全看的清清楚楚:

$ dig +trace  www.freezhongzi.info

;; global options: +cmd
.           25684   IN  NS  f.root-servers.net.
.           25684   IN  NS  m.root-servers.net.
.           25684   IN  NS  d.root-servers.net.
.           25684   IN  NS  e.root-servers.net.
.           25684   IN  NS  k.root-servers.net.
.           25684   IN  NS  c.root-servers.net.
.           25684   IN  NS  j.root-servers.net.
.           25684   IN  NS  g.root-servers.net.
.           25684   IN  NS  h.root-servers.net.
.           25684   IN  NS  l.root-servers.net.
.           25684   IN  NS  a.root-servers.net.
.           25684   IN  NS  b.root-servers.net.
.           25684   IN  NS  i.root-servers.net.

info.           172800  IN  NS  c0.info.afilias-nst.info.
info.           172800  IN  NS  d0.info.afilias-nst.org.
info.           172800  IN  NS  b2.info.afilias-nst.org.
info.           172800  IN  NS  a2.info.afilias-nst.info.
info.           172800  IN  NS  b0.info.afilias-nst.org.
info.           172800  IN  NS  a0.info.afilias-nst.info.

freezhongzi.info.   86400   IN  NS  f1g1ns2.dnspod.net.
freezhongzi.info.   86400   IN  NS  f1g1ns1.dnspod.net.

www.freezhongzi.info.   600 IN  A   xxx.xxx.xxx.xxx
freezhongzi.info.   600 IN  NS  f1g1ns1.dnspod.net.
freezhongzi.info.   600 IN  NS  f1g1ns2.dnspod.net.

DNS初探

DNS是一个分布式系统,没有一个单独的主机知道所有的信息。

最简单的DNS系统就是/etc/hosts文件了,每行内容是”IP 主机名 主机名1 主机名2 …”。

域名(domain name)和主机名(hostname)组成完整的DNS标识,例如

www.freezhongzi.info,www是主机名,freezhongzi.info是域名,而

freezhongzi.info,freezhongzi是主机名,.info是域名。

以句号.结尾的域名,如www.freezhongzi.info.叫Fully Qualified Domain Name(FQDN),句号表示根域名,在DNS协议里用FQDN标识。

DNS递归查询(recursion query)

默认的递归查询都是由你的当前DNS服务器执行的,所以一般都设置RD位(例外是DNS根服务器是没有递归查询功能的,所以当DNS服务器向根服务器查询时不设置RD位)。

DNS查询的操作流程是:

  1. 客户端(linux/unix一般是gethostbyname和gethostbyaddr)向当前DNS服务器查询域名,一般查询A记录(IP地址)。
  2. DNS服务器收到客户端的请求之后,向DNS根服务器发送一样的A记录请求,因为DNS根服务器不支持递归查询,所以返回知道这个域名的DNS服务器列表(NS记录)和对应的IP地址。
  3. DNS服务器选择最靠前的下一跳DNS服务器(NS记录里)接着发送递归查询。
  4. 下一跳DNS服务器返回域名的IP地址,如果下一跳DNS服务器不支持递归查询而返回下下一跳DNS服务器列表重复步骤3。

从中可以看出递归查询与非递归查询的区别:

  • 非递归查询返回知道那个域名的DNS服务器地址,让你继续去查。
  • 递归查询是让DNS服务器一级级往下查直到查到IP地址为止,很像一个递归过程。

实例:

第一步:找出DNS根服务器:

查询DNS根的NS记录获得13个DNS根服务器域名和IP地址:

$dig . ns 或直接 $dig

;; AUTHORITY SECTION:
.           37383   IN  NS  b.root-servers.net.
.           37383   IN  NS  c.root-servers.net.
.           37383   IN  NS  f.root-servers.net.
.           37383   IN  NS  l.root-servers.net.
.           37383   IN  NS  i.root-servers.net.
.           37383   IN  NS  d.root-servers.net.
.           37383   IN  NS  e.root-servers.net.
.           37383   IN  NS  a.root-servers.net.
.           37383   IN  NS  k.root-servers.net.
.           37383   IN  NS  m.root-servers.net.
.           37383   IN  NS  g.root-servers.net.
.           37383   IN  NS  h.root-servers.net.
.           37383   IN  NS  j.root-servers.net.

;; ADDITIONAL SECTION:
k.root-servers.net. 3580415 IN  A   193.0.14.129
k.root-servers.net. 3581367 IN  AAAA    2001:7fd::1
a.root-servers.net. 3579080 IN  A   198.41.0.4
a.root-servers.net. 3567490 IN  AAAA    2001:503:ba3e::2:30
e.root-servers.net. 3580333 IN  A   192.203.230.10
d.root-servers.net. 3581308 IN  A   128.8.10.90
d.root-servers.net. 3530630 IN  AAAA    2001:500:2d::d
i.root-servers.net. 3580233 IN  A   192.36.148.17
i.root-servers.net. 3568782 IN  AAAA    2001:7fe::53
l.root-servers.net. 3579724 IN  A   199.7.83.42
l.root-servers.net. 3593302 IN  AAAA    2001:500:3::42
f.root-servers.net. 3580192 IN  A   192.5.5.241

上面的AAAA是IPv6地址记录。

而且貌似默认的电信DNS服务器的TTL(就是DNS服务器Cache时间)很不准(一下子15到515,不知道TTL到底是多少),而google的8.8.8.8很准一直从600秒(10钟)递减:)。

$dig www.freezhongzi.info @8.8.8.8
...
www.freezhongzi.info.   600 IN  A   xxx.xxx.xxx.xxx
...

$dig www.freezhongzi.info @8.8.8.8
...
www.freezhongzi.info.   576 IN  A   xxx.xxx.xxx.xxx
...

$dig www.freezhongzi.info @8.8.8.8
...
www.freezhongzi.info.   573 IN  A   xxx.xxx.xxx.xxx
...

第二步:随便选一个m.root-servers.net的IP地址执行递归查询:

$dig +norecurse www.freezhongzi.info @202.12.27.33

;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 12

;; QUESTION SECTION:
;www.freezhongzi.info.      IN  A

;; AUTHORITY SECTION:
info.           172800  IN  NS  b2.info.afilias-nst.org.
info.           172800  IN  NS  b0.info.afilias-nst.org.
info.           172800  IN  NS  a0.info.afilias-nst.info.
info.           172800  IN  NS  c0.info.afilias-nst.info.
info.           172800  IN  NS  d0.info.afilias-nst.org.
info.           172800  IN  NS  a2.info.afilias-nst.info.

;; ADDITIONAL SECTION:
a0.info.afilias-nst.info. 172800 IN A   199.254.31.1
a2.info.afilias-nst.info. 172800 IN A   199.249.113.1
b0.info.afilias-nst.org. 172800 IN  A   199.254.48.1
b2.info.afilias-nst.org. 172800 IN  A   199.249.121.1
c0.info.afilias-nst.info. 172800 IN A   199.254.49.1
d0.info.afilias-nst.org. 172800 IN  A   199.254.50.1
a0.info.afilias-nst.info. 172800 IN AAAA    2001:500:19::1
a2.info.afilias-nst.info. 172800 IN AAAA    2001:500:41::1
b0.info.afilias-nst.org. 172800 IN  AAAA    2001:500:1a::1
b2.info.afilias-nst.org. 172800 IN  AAAA    2001:500:49::1
c0.info.afilias-nst.info. 172800 IN AAAA    2001:500:1b::1
d0.info.afilias-nst.org. 172800 IN  AAAA    2001:500:1c::1

可以看到DNS根服务器不支持递归查询(没有RA标记)。

第三步:选择下一跳.info DNS服务器接着查询:

$dig www.freezhongzi.info @d0.info.afilias-nst.org.

;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;www.freezhongzi.info.      IN  A

;; AUTHORITY SECTION:
freezhongzi.info.   86400   IN  NS  f1g1ns2.dnspod.net.
freezhongzi.info.   86400   IN  NS  f1g1ns1.dnspod.net.

发现.info也不支持递归查询,返回的两个DNS服务器:

f1g1ns1.dnspod.net
f1g1ns2.dnspod.net

如果熟悉dnspod的会知道那是它的DNS服务器域名,我的freezhongzi.info就是由它托管的:)

最后一部就是向dnspod索要了:

$dig www.freezhongzi.info @f1g1ns1.dnspod.net

;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; ANSWER SECTION:
www.freezhongzi.info.   600 IN  A   xxx.xxx.xxx.xxx

;; AUTHORITY SECTION:
freezhongzi.info.   600 IN  NS  f1g1ns2.dnspod.net.
freezhongzi.info.   600 IN  NS  f1g1ns1.dnspod.net.

dnspod也不支持递归查询,但终于返回我要的IP地址了。

整个过程就是如图: