现在的位置: 首页 > 综合 > 正文

Contiki Makefile 详细解读

2018年04月11日 ⁄ 综合 ⁄ 共 8777字 ⁄ 字号 评论关闭

makefile.include文件,这个是在contiki系统根目录中,而非工程目录,在contiki源码中有很多工程,在example目录里面,每一个目录对应一个工程,比如cc2530dk目录中就对应的是cc2530平台的工程,记住有makefile文件的当前目录才是工程目录,所以makefile.include的当前目录就不是工程目录。

 本文将从makefile.include开始解读,一步一步深入包含contiki操作系统的工程的编译过程(以cc2530dk为例)。其中会涉及到makefile和shell相关知识,我会一一解释清楚。先打开example/cc2530dk/makfile文件

  1. CONTIKI_PROJECT = hello-world blink-hello
    timer-test sensors-demo

  2. all: $(CONTIKI_PROJECT) 

  3. CONTIKI = ../..
  4. include $(CONTIKI)/Makefile.include

CONTIKI_PROJECT变量定义的是最后生成的目标文件,即可执行文件,这里生成了四个可执行文件,为什么要这么做呢?因为通常来讲makefile只能生成一个终极目标,这里用到了all伪目标(没有命令行的规则,或者没有规则的命令),即all依赖于以上四个可执行的文件。如果不这样做的话,即没有用all伪目标,则最终只能生成一个可执行文件hello-world 或 blin-hello.第三句定义变量CONTIKI
为上级目录的上级目录,即为contiki的根目录,因为要用下面一句 
include $(CONTIKI)/Makefile.include  包含根目录里面的makefile.include ,此时make就会停止读取当前的Makefile,转而去读Makefile.include, Makefile.include,内容较多,我们逐步来解析。


这里ifndef是用来判断一个变量是否已经定义,因为之前定义了CONTIKI,所以不会执行,即不会报错!

  1. ifeq ($(TARGET),)
  2.   -include Makefile.target
  3.   ifeq ($(TARGET),)
  4.     ${info TARGET not defined, using target 'native'}
  5.     TARGET=native
  6.   else
  7.     ${info using saved target '$(TARGET)'}
  8.   endif
  9. endif

ifeq是用来判断两个参数是否相等,如果相等,则往下执行。因为之前没有的定义TARGET变量,所以其为空,即条件判断为真,往下执行...包含Makefile.target,记住此时还是在makefile的当前目录中工作,而不是转到根目录了。所以会在工程目录里面包含Makefile.target文件,我们找到打开它:

TARGET = cc2530dk 只有这一句就是定义TARGET变量为cc2530dk继续上面的第二个ifeq,此时TARGET变量不为空了,所以下面一句不会执行,而执行else语句info为一个显示信息的工具,显示using
saved target cc2530dk

  1. ifeq ($(DEFINES),)
  2.   -include Makefile.$(TARGET).defines
  3.   ifneq ($(DEFINES),)
  4.     ${info using saved defines '$(DEFINES)'}
  5.   endif
  6. endif

这几句和上面的意思一样,只不过换成DEFINES变量,工程目录中没有Makefile.cc2530dk.defines文件,所以DEFINES仍为空,所以ifneq条件判断为假,所以什么也不执行。

 

  1. ifndef HOST_OS
  2.   ifeq ($(OS),Windows_NT)
  3.   ## TODO: detect more specific Windows set-ups,
  4.   ## e.g. CygWin, MingW, VisualC, Watcom, Interix
  5.     HOST_OS := Windows
  6.   else
  7.     HOST_OS := $(shell uname)
  8.   endif
  9. endif

这几句就不用解释了,提一下$(shell uname)值为Linux,所以HOST_OS要么为Windows,要么为Linux。

  1. usage:
  2.     @echo "make MAKETARGETS... [TARGET=(TARGET)] [savetarget] [targets]"

  3. targets:
  4.     @ls -1 $(CONTIKI)/platform
    $(TARGETDIRS) | grep -v
    CVS

  5. savetarget:
  6.     -@rm -f Makefile.target
  7.     @echo "saving Makefile.target"
  8.     @echo >Makefile.target "TARGET
    = $(TARGET)"


  9. savedefines:
  10.     -@rm -f Makefile.$(TARGET).defines
  11.     @echo "saving Makefile.$(TARGET).defines"
  12.     @echo >Makefile.$(TARGET).defines "DEFINES
    = $(DEFINES)"

这一句是将usage作为伪目标,如果执行make的时候,指定了终极目标为usage,即make usage,则回显

make MAKETARGETS... [TARGET=(TARGET)] [savetarget] [targets],这句是指导我们怎么用make命令来编译contiki源码。记住echo前要加@,要不然会显示echo"make MAKETARGETS... [TARGET=(TARGET)]
[savetarget] [targets]"。

 

这句同样以targets为终极目标时,将会列出contiki/platform目录下的所有文件(TARGETDIRS变量此时为空),

grep -v CVS 是显示所有不包含CVS的行,这里还没弄清楚CVS是啥意思!我们不用管,例如我们执行

make targets 则出现 

  1. apple2enh
  2. atari
  3. avr-atmega128rfa1
  4. avr-raven
  5. avr-ravenlcd
  6. avr-ravenusb
  7. avr-rcb
  8. avr-zigbit
  9. c128
  10. c64
  11. cc2530dk
  12. cc2538dk
  13. cooja
  14. econotag
  15. esb
  16. ...

这里也是将savetarget作为终极目标,第一句是强制删除Makefile.target文件,然后回显saving Makefile.target,这里提一下在@rm前面的'-',它的作用就是如果这条命令执行失败的话,make工作照常进行,即往下执行。

然后将TARGET=cc2530dk重定向到Makefile.target文件中,这里将>Makefile.target放在"TARGET = $(TARGET)"的后面也可以,这里面有一个问题就是,当我执行这个命令的时候,会提示权限不够,只有进入了root,才会顺利执行这个命令,但是当我们执行make命令的时候,权限为普通用户,那么如果执行savetarget怎么会执行第三句呢?

那么以savetarget为终极目标有啥用呢?答案在 根目录中的README-BUILDING文件

  1. OBJECTDIR = obj_$(TARGET)

定义变量OBJECTDIR为obj_cc2530dk

  1. LOWERCASE = -abcdefghijklmnopqrstuvwxyz
  2. UPPERCASE = _ABCDEFGHIJKLMNOPQRSTUVWXYZ
  3. TARGET_UPPERCASE := ${strip ${shell echo $(TARGET) | sed
    y!$(LOWERCASE)!$(UPPERCASE)!}}
  4. CFLAGS += -DCONTIKI=-DCONTIKI_TARGET_$(TARGET_UPPERCASE)=1

首先定义了LOWERCASE和UPPERCASE变量,第三局看起来有点复杂,我们从最里面一层一层往外剥,首先看sed命令,可以去这个博客看sed的用法http://lsscto.blog.51cto.com/779396/880538,!为分隔符(/
,也可以) ,y表示转换资料中的字元,即将echo显示的内容作为sed的输入或资料进行转换,将其中的小写字母转换成大写字母,即将cc2530dk转换成CC2530DK,然后作为shell的返回值提供给strip,strip是make的内置函数,其作用是去掉该字串中开头和结尾的空格符,并将中间多个连续空字符合并为一个空字符。则最终TARGET_UPPERCASE变量的值为CC2530DK。第四句追加变量CFLAGS,此为c编译器的命令行选项变量,定义了CONTIKI=1 
CONTIKI_TARGET_CC2530DK=1

  1. include $(CONTIKI)/core/net/rime/Makefile.rime
  2. include $(CONTIKI)/core/net/mac/Makefile.mac
  3. SYSTEM = process.c procinit.c autostart.c
    elfloader.c profile.c \
  4.           compower.c serial-line.c
  5. THREADS = mt.c
  6. LIBS = memb.c mmem.c timer.c
    list.c etimer.c ctimer.c energest.c
    rtimer.c stimer.c \
  7.           print-stats.c ifft.c crc16.c
    random.c checkpoint.c ringbuf.c
  8. DEV = nullradio.c

包含Makefile.rime,我们看一下这个文件下内容

  1. ifdef UIP_CONF_IPV6
  2. #RIME_UIP6 = rime-udp.c
  3. RIME_BASE = rimeaddr.c timesynch.c rimestats.c
  4. else
  5. RIME_CHAMELEON = chameleon.c channel.c chameleon-raw.c
    chameleon-bitopt.c
  6. RIME_BASE = rimeaddr.c rime.c timesynch.c
    \
  7.                  rimestats.c announcement.c polite-announcement.c
    \
  8.                  broadcast-announcement.c
  9. RIME_SINGLEHOP = broadcast.c stbroadcast.c unicast.c
    stunicast.c \
  10.                  runicast.c abc.c \
  11.                  rucb.c polite.c ipolite.
  12. RIME_MULTIHOP = netflood.c multihop.c rmh.c
    trickle.c
  13. RIME_MESH = mesh.c route.c route-discovery.c
  14. RIME_COLLECT = collect.c collect-neighbor.c
    neighbor-discovery.c \
  15.          collect-link-estimate.c
  16. RIME_RUDOLPH = rudolph0.c rudolph1.c rudolph2.c
  17. endif # UIP_CONF_IPV6

  18. CONTIKI_SOURCEFILES += $(RIME_BASE) \
  19.                $(RIME_SINGLEHOP) \
  20.                $(RIME_MULTIHOP) \
  21.                $(RIME_MESH) \
  22.                $(RIME_COLLECT) \
  23.                $(RIME_RUDOLPH) \
  24.                $(RIME_CHAMELEON) \ 
  25.                $(RIME_UIP6)

如果定义了UIP_CONF_IPV6这个变量定义了 ,则往下执行,#后面为注释,因为UIP_CONF_IPV6这个变量没有定义,所以执行else下面的语句
这几句定义了几个和rime协议栈相关的变量,这些变量为相应的源文件列表。
最后追加CONTIKI_SOURCEFILES变量,将以上定义的变量引用到此变量中,注意其中RIME_UIP6为空。打开Makefile.mac

  1. CONTIKI_SOURCEFILES += cxmac.c
    xmac.c nullmac.c lpp.c frame802154.c
    sicslowmac.c nullrdc.c nullrdc-noframer.c
    mac.c
  2. CONTIKI_SOURCEFILES += framer-nullmac.c
    framer-802154.c csma.c contikimac.c
    phase.c

这两句追加CONTIKI_SOURCEFILES变量,将mac层相关的源文件添加到将要编译的源文件列表中。注意此源文件列表中不包含Tdma_mac.c 和Ctdma_mac.c。
 
进入到Makefile.include刚才那两句include的下面,定义了几个变量分别表示contiki系统中不同部分的源文件列表,如SYSTEM表示contiki 内核进程调度等的源文件列表。

  1. include $(CONTIKI)/core/net/Makefile.uip
  2. include $(CONTIKI)/core/net/rpl/Makefile.rpl

  3. CTK = ctk.c
  4. CTKVNC = $(CTK) ctk-vncserver.c
    libconio.c vnc-server.c vnc-out.c
    ctk-vncfont.c

定义UIP和追加NET变量,ctk为contiki的GUI和VNC功能

  1. ifndef CONTIKI_NO_NET
  2.   CONTIKIFILES = $(SYSTEM) $(LIBS) $(NET) $(THREADS) $(DHCP) $(DEV)
  3. else
  4.   CONTIKIFILES = $(SYSTEM) $(LIBS) $(THREADS) $(DEV) sicslowpan.c
    fakeuip.c
  5. endif

  6. CONTIKI_SOURCEFILES += $(CONTIKIFILES)

没有定义CONTIKI_NO_NET,所以定义CONTIKIFILES变量,包括NET相关的源文件,如果定义了此变量,则不包含与NET相关的源文件.将CONTIKIFILES变量的内容添加到CONTIKI_SOURCEFILES变量中。自此我们知道了CONTIKI_SOURCEFILES变量中包含的源文件,这些都是需要编译连接的。

  1. CONTIKIDIRS += ${addprefix $(CONTIKI)/core/,dev
    lib net net/mac net/rime \
  2.                  net/rpl sys cfs ctk lib/ctk loader . }

这里追加CONTIKIDIRS变量,看字面意思就知道这个变量代表contiki的目录列表addprefx为make的内置函数,这个函数完成的功能是为后面每一个文件名添加前缀$(CONTIKI)/core/,那么CONTIKIDIRS最终的值为$(CONTIKI)/core/dev $(CONTIKI)/core/lib $(CONTIKI)/core/net
.......这些是contiki内核的源文件目录列表。

  1. oname = ${patsubst %.c,%.o,${patsubst
    %.S,%.o,$(1)}}

  2. CONTIKI_OBJECTFILES = ${addprefix $(OBJECTDIR)/,${call oname, $(CONTIKI_SOURCEFILES)}}

  3. PROJECT_OBJECTFILES = ${addprefix $(OBJECTDIR)/,${call oname, $(PROJECT_SOURCEFILES)}}

这个是自定义函数,那么这个函数的作用是什么呢?patsubst是make内置函数,它的功能是替换字符串中符合%.S模式的单词替换成%.o模式,返回的值是替换后的新字符串;继续调用patsubst,将符合%.c模式的单词替换成%.o模式的单词,最终返回替换后的新字符串。那么这个函数的功能就不言而喻了,它实现的是将位置变量$(1)所代表的字符串中凡是符合%.S
%.c模式的单词都替换成%.c模式,然后返回替换后的新字符串。这里$(1)表示的就是此函数的第一个参数值,如上面的调用中表示的就是$(CONTIKI_SOURCEFILES),知道了oname函数的功能,这句就很好理解了。用call调用oname函数,将CONTIKI_SOURCEFILES代表的所有源文件列表中.c或.S后缀结尾的文件名全部替换成.o结尾的文件名,然后返回;再调用addprefix函数,添加前缀$(OBJECTDIR),OBJECTDIR这个变量的值为obj_cc2530dk,则在所有文件前面中添加前缀obj_cc2530dk,比如某一个源文件为rime.c,执行此语句后变成obj_cc2530_rime.o,这是目标文件。那么CONTIKI_OBJECTFILES就是代表所有contiki中源文件在经过编译之后生成的目标文件列表。

  1. ifdef APPS
  2.   APPDS = ${wildcard ${foreach DIR, $(APPDIRS), ${addprefix
    $(DIR)/, $(APPS)}}} \
  3.          ${wildcard ${addprefix $(CONTIKI)/apps/, $(APPS)} \
  4.          ${addprefix $(CONTIKI)/platform/$(TARGET)/apps/, $(APPS)} \
  5.          $(APPS)}
  6.   APPINCLUDES = ${foreach APP, $(APPS), ${wildcard
    ${foreach DIR, $(APPDS), $(DIR)/Makefile.$(APP)}}}
  7.   -include $(APPINCLUDES)
  8.   APP_SOURCES = ${foreach APP, $(APPS), $($(APP)_src)}
  9.   DSC_SOURCES = ${foreach APP, $(APPS), $($(APP)_dsc)}
  10.   CONTIKI_SOURCEFILES += $(APP_SOURCES) $(DSC_SOURCES)
  11. endif

包含应用程序的makefile文件如果定义了APPS,则包含,没有就不包含,在cc2530中我们没有定义这个变量,所以就不包含应用程序目录中的文件。那么在其他的平台如sky中的某一个工程,会定义APPS,此时定义APPDIRS变量。假如APPS 为telnet (apps目录中的某一个目录文件),则用wildcard函数找出与此APPS相关的一些目录列表,wilcard后面的参数代表是的是一种模式,它是在当前目录中搜索符合这种模式的文件,并返回这种模式的文件名列表。注意这里在当前目录中搜索,包括目录的子目录,子目录的子目录等。然后定义APPINCLUDES
包含apps源文件的头文件列表APP_SOURCES 代表apps的源文件列表  DSC_SOURCES不知道,追加CONTIKI_SOURCEFILES变量 将上面两个变量所代表的源文件列表添加进来。

  1. target_makefile := $(wildcard
    $(CONTIKI)/platform/$(TARGET)/Makefile.$(TARGET) ${foreach
    TDIR, $(TARGETDIRS), $(TDIR)/$(TARGET)/Makefile.$(TARGET)})

  2. ifeq ($(strip $(target_makefile)),)

      ${error The target platform "$(TARGET)" does not exist (maybe it was misspelled?)}

    else

      ifeq (${wildcard $(OBJECTDIR)},)

        DUMMY := ${shell mkdir $(OBJECTDIR)}

      endif

      ifneq (1, ${words $(target_makefile)})

        ${error More than one TARGET Makefile found: $(target_makefile)}

      endif

      include $(target_makefile)

    endif

在当前目录中搜索符合上述模式的文件。即搜索文件$(CONTIKI)/platform/cc2530dk/Makefile.cc2530dk,那我们找找看!我们找到了确实有这个文件打开它看看:这是和cc2530dk平台相关的makefile

抱歉!评论已关闭.