开源中文网

您的位置: 首页 > FreeBSD > 正文

FreeBSD系统编程[简体中文版]1

来源:  作者:

目录 
第一章: FreeBSD的Make 
第二章: BSD自举 
第三章: 进程和内核服务 
第四章: 高级进程控制和信号 
第五章: 基本I/O  
第六章: 高级I/O 
第七章: 进程资源和系统限制 
第八章: FreeBSD 5.x  

[align=center]第一章 FreeBSD的make[/align] 
[align=center]译者:雨丝风片@chinaunix.net[/align] 

1.1 FreeBSD的make 

作为常用的和基本的Unix软件开发工具,make是一个可以跟踪全部的文件依赖关系的非常好的簿记工具程序。要管理依赖关系这样的项目细节常常需要花费很多的时间,甚至会拖延开发进度。当多个开发人员合作一个项目的时候,依赖关系的跟踪就可能变得相当困难了。事实上,正确地使用make可以帮助我们加快应用程序的开发,从而提高生产效率。 

虽然make最初的设计是用来对应用程序版本构建的维护过程进行管理的,我们实际上还可以通过创建一系列的基于目标依赖关系的Unix shell命令来让make完成多种多样的额外工作。这些依赖关系可以用很多种方式定义——包括需要进行编译的源文件、所需的库文件、shell命令以及其它的目标。 

make有多种风格的版本,其中包括GNU make和System V make。并不是在每个make版本中都有我们接下来讨论的那些特性,具体使用哪个版本完全取决于你的个人喜好。我们将主要关注跟随FreeBSD一起发布的make(也叫做bmake或pmake),尤其是如何通过它来编译和更新FreeBSD系统,也就是所谓的make world。虽然我们关注的是FreeBSD make,但我们在这里讨论的所有东西对于各种BSD版本来说都是适用的。 

我们首先会讲述一个Makefile的基本文件布局和语法。如果这对于你来说太简单了,那你可以直接跳到本章结束处的示例部分去阅读。(注意,我们给出的代码示例只用于演示我们关于make目标和依赖关系的讨论,它们并不一定是可以运行的代码。) 

当然,和其它工具程序一样,最开始应该先去看看man page,以对make提供的命令行选项的概要和细节有一个正式的了解。同时,和其它工具程序一样,学习make的最好方法就是使用它。创建一些小型的源文件(可以使用任何语言),然后尝试一些下面给出的例子。我们希望读完本章之后你除了理解make的语法规则之外,还知道它是如何工作的。 

1.2 Makefile布局 

总的说来,你使用make的方式就是让它去读一个Makefile,你需要在Makefile里指定一个目标及其依赖关系。在运行的时候,make会按顺序搜索名字为Makefile或makefile的文件。这个Makefile通常是放在一个工程的根目录下的,如果想指定其它的Makefile,可以在命令行上用-f (filename)的选项给出。 

make -f OtherMakefile


1.3 语法 

一个Makefile的结构由四个基本行组成,它们都可以通过在行尾添加‘\’字符来扩展到下一行(和shell编程相似)。注释是以‘#’号开始的,至行尾结束。 

########################################

# Simple Makefile with comment example #

########################################



# when run, it will just echo hello

all:

   echo "hello"


要使用make来编译一个工程,首先需要确定在你的当前工作目录中已有一个正确的Makefile,然后再通过下列命令之一来使用make: 

bash___FCKpd___2nbsp;make    



bash___FCKpd___2nbsp;make all 



bash___FCKpd___2nbsp;make <target name>


1.4 目标 

用来指定目标的方式有很多种,不过最常用的就是用目标文件或一个工程的名字。工程名字不应当包含有空格或标点符号,不过这只是个惯例而已;少量的空格和标点符号也是允许的。这个名字必须写在一个新行的开头,必须以单冒号(:)、双冒号(::)或感叹号(!)三者之一结束。 

myprog:

     <some commands to the compile myprog target>



another::

     <some commands to the compile another target>



sample!

     <some commands to the compile sample target>


在这些目标名字之后是所需的依赖条件,包括名字、变量以及其它的目标等等。如果你的依赖条件太多的话,可以用一个‘\’和一个newline来将它们分开。所有的依赖条件都必须Makefile内定义或者存在于某个外部文件中,否则make将无法知道如何去完成依赖操作。 

一些示例如下: 

all: driver.cpp network_class.cpp file_io_class.cpp network_libs.cpp file_io_libs.cpp



all: myprog.o 



myprog.o: 


上例中,all和myprog.o是要make的目标。注意myprog.o既是一个目标又是一个依赖条件。make首先会到myprog.o那儿,执行它的命令,然后返回到all那儿,再执行它的命令。这种操作序列是make的功能基础。 

按照惯例,all:目标是你的目标中的最高者,这意味着make将从这儿开始去寻找要完成all:目标都需要哪些东西。不过all:目标并不是必需的,如果没有的话,make就会简单地选择所有列出的目标中的第一个,只对其实施操作,除非你在命令行上指定了某个目标。对于那些有一个核心的应用程序需要维护和构建的工程来说,我们建议你使用all:目标;这是一个通用的惯例,有助于避免错误和不必要的任务。 

上例所示的依赖序列只是很简单的一个。下面是一个更为复杂和灵活的依赖序列,我们没有给出用于具体目标的命令: 

all: myprog.o lib



lib: lex



lex:



myprog.o: app.h


注意,在这个例子中,all:目标有两个依赖条件:myprog.o和lib。这两个依赖条件本身又都是目标,make将首先去编译myprog.o。在make编译myprog.o的时候,会发现有一个和app.h的依赖关系。app.h并没有在这个makefile里定义,但app.h却是一个在当前目录中的头文件。 

用于myprog.o的命令完成之后,make即返回到all:处,继续处理下一个依赖条件,在此例中是lib。依赖条件lib本身也有一个依赖条件lex,所以make在完成lib之前会先去完成lex:。 

注意:正如你所看到的,这些依赖关系可能会非常长,或者嵌套得很深。如果你有一个很大的Makefile,那一定要好好地组织一下,把目标的顺序弄好。 

1.5 求值规则 

依赖关系是按照依赖于目标名字结束符号的严格规则来求值的。一旦make认为满足规则,它将通过执行相应的命令来创建特定的目标(比如编译该目标)。例如,使用单冒号:可以让你对需要进行编译的目标进行更为精细的控制。也就是说,你可以指定某个特定的目标文件每次都需要重新编译或者仅当它的源文件过时之后才编译。这些规则都是基于目标名字的结束符号的,如下: 

如果目标名字以单冒号(:)结束,它将根据以下两个规则来创建: 
[list=1] 
[*]如果目标尚未存在,就像我们在上面举的例子里的all:一样,make就会创建它。 
[*]如果任意一个源文件具有比当前目标更新的时间戳。在上例中如果app.h或myprog.c具有更新的时间戳,myprog.o就会被make。这种情况只需简单地用一下touch命令即可出现 
[/list] 
    touch myprog.c


如果目标名字以双冒号(::)结束,它将根据以下三个规则来创建: 
[list=1] 
[*]如果任意一个源文件具有比当前目标更新地时间戳。 
[*]该目标不存在。 
[*]该目标没有与之关联的源文件。 
[/list] 

如果目标名字以感叹号(!)结束,只要make把它所需的全部依赖条件都创建完毕就会来创建它。 

你只能在目标或源文件的最后一个组成部分中使用通配表达式?、*和[],而且只能用于描述已经存在的文件。比如: 

myprog.[oc]


而使用花括号{}的表达式则不一定非得描述已经存在的文件。比如: 

{mypgog,test}.o



# the expression above would match myprog.o test.o only


最后需要注意一点:可变表达式是按照目录顺序来处理的,而非字母顺序,就跟在shell表达式中一样。例如,如果你的目标有某些基于字母顺序的依赖条件,下面这个表达式可能就不对了: 

{dprog,aprog,bprog,cprog}.cpp


1.6 变量 

make能够使用变量这一点是非常重要的。例如,你有一个名字为a.c的源文件,由于某种原因你想把它的名字改成b.c。通常情况下,你得把你的makefile里的每个a.c的实例都改成b.c。但是,如果你写成以下方式: 

MYSRC = a.c


你只需要把这一行更新成新的名字即可,如下: 

MYSRC = b.c


你因此而节省了时间,这也正是make的首要角色:项目管理。 

变量可用$(<变量名字>)或就用一个$来引用,但后者未被广泛使用,因此建议别那样写。 

$(GCC) = /usr/local/bin/gcc


make有四种不同类型的变量,下面将按照被搜索的顺序列出它们。(make的搜索将一直进行到发现某个数值的第一个实例为止。) 
[list=1] 
[*]局部变量:这些是赋给特定目标的数值。 
[*]命令行:命令行变量是在命令行上传给make的。 
[*]全局变量:全局变量是在该Makefile或任何所包含的Makefile内赋值的。你在一个Makefile内最常看到的就是这些变量。 
[*]环境变量:环境变量是在Makefile之外,也就是运行make的shell里设置的。 
[/list] 

这些变量可以在Makefile里用以下五种操作符进行定义: 

[list=1] 
[*]等号“=”是最常用的操作符,这和shell的情况是类似的。数值被直接赋给了变量。例如: 
VAR = < value >

[*]加号等号“+=”的意思是附加赋值,通过把所赋数值附加到当前数值的后面来完成对变量的赋值。例如: 
VAR += < value to append >

[*]问号等号“?=”的意思是条件赋值,仅当该变量从未赋值时才进行赋值。条件赋值在向一个字符串数值添加前缀的时候非常有用。例如: 
VAR ?= < value if unset >

[*]冒号等号“:=”的意思是扩展赋值,在赋值前会对所赋数值进行扩展;通常这种扩展是在所赋变量被引用的时候才进行的。例如: 
VAR := < value to expand >

[*]感叹号等号“!=”的意思是shell命令赋值。在命令被扩展并发给shell执行完毕之后,将命令结果赋给变量。结果中的newline字符将被空格取代。例如: 
VAR != < shell command to execute >

[/list] 

注意:有些变量是在外部的系统级的Makefile内定义的(在/etc/make.conf或/etc/defaults/make.conf中),如果你在设置环境变量方面遇到了问题,就去检查一下系统级文件里的设置。 
        
一个完整的例子如下: 

  #####################

  # Example make file #

  #####################

  CURDIR != pwd

  CFLAGS ?= -g

  CFLAGS += -Wall -O2

  

  all:

      echo $CFLAGS 

  #######################

  

  bash___FCKpd___18nbsp;CFLAGS="-g -Wall" make


在上例中,CURDIR被设置成了shell命令pwd的结果。(注意,这种赋值并不需要backtick命令(即' ')。)CFLAGS如果从未设置,会首先被设置成-g,然后不管它的当前值是什么,都会被附加上-Wall -O2。 

1.7 命令 

如果没有命令,那make什么都不是,只有把命令告诉make它才能完成它的工作。make只能运行这些命令,然后基于shell的退出状态判断这些命令是否成功。因此,如果命令失败,shell返回一个错误,make将会因错误而退出并于该处终止。make可以就被看作是另外一个命令——除了运行其它命令之外,它和那些命令并没有什么实际的交互。 

命令必须与一个目标相关联,任何一个目标都可以有多个命令。例如: 

# example for an all-install target



all-install:

   $(CC) $(CFLAGS)  $(MYSRC)

   cp  $(MYPROG) $(INSTALL_DIR)

   echo "Finished the build and install"

每个命令都必须在一个目标之后以新行开始,在实际命令起始位置之前必须要有一个tab键,如上所示。 

对于大多数情况而言,Makefile里的命令只要是个有效的shell命令就行,命令还经常会包括变量。例如: 

CPP = -g++

CFLAGS = -Wall -O2



myprog.o:

    $(CPP) $(CFLAGS) -c myprog.c

下面这个例子告诉make使用给定值编译myprog.c。这些命令可能会比一行要长,它们也可以被写来完成其它的任务。这是非常重要的,因为编译器可以接受相当多的命令行选项、环境设置以及定义等等,比如: 

CLFAGS = $(LINK_FLAGS) $(LINK_LIBS) $(OTHER_LIBS) \ 

$(OPTIMIZER_FLAGS) $(DEFINES) $(NO_KERNEL) $(OBJS) \ 

$(CPU_TYPE_FLAGS) $(USE_MY_MALLOC) $(UDEFINES) \

$(SRC_FILE)  $(OTHER_SRC_FILES)

下面这个例子告诉make删除所有的object文件、core文件以及应用程序本身,然后把log文件移走,这些操作都可以很方便的完成。 

CPP = -g++

CFLAGS = -Wall -O2

APP = myapp

DATE != date +%m%d%y_%H_%M

LOG = debug





myprog.o:

      $(CPP) $(CFLAGS) -c myprog.c

      rm -f  *.o  *.core $(APP)

      mv  $(LOG).log  $(LOG)_$(DATE).log



clean:

    rm -f *.o *.core $(APP)

    mv $(LOG).log $(LOG)_$(DATE).log

但是,如果并不存在log文件,make就会因错误而退出。为了避免这种情况,可以在命令之前加上“-”号。通过在命令前面加上减号,你就告诉了make忽略执行命令时遇到的错误。(不过make仍然会打印出错误信息。)因此,在某个命令出现错误之后make仍将继续。例如: 

clean:

    -rm -f *.o *.core $(APP)

    -mv $(LOG).log $(LOG)_$(DATE).log


这将使得make忽略掉rm和mv命令可能遇到的错误。 

你还可以让make禁止掉“echo”之类的命令的输出。echo首先告诉make打印出包括echo语句在内的整个命令,然后再执行命令并将字符串打印到屏幕上: 
    echo $(SOME_DEFINED_STRING)


要避免这种情况,可以在echo命令之前加上“@”符号,这将告诉make只打印字符串,比如: 
    @echo $(SOME_DEFINED_STRING)


“-”号和“@”号都既可用于变量,又可用于形为字符串的命令,但需要确定你对变量命令的引用是正确的。下例演示了如何对命令使用@操作符: 
ECHO = echo

MESSAGE = "Print this message"



msg::

  @$(ECHO) $(MESSAGE)  


1.8 条件语句(#if,#ifndef等等) 

如果你对C和C++比较熟悉,那你肯定知道条件预处理命令。功能繁多的make也有一个类似的特性。条件语句使你可以选择Makefile里的哪个部分需要被处理。这些条件语句最多可以嵌套30层,并且可以放在Makefile里的任何地方。每条语句都必须以一个圆点(.)开始,而且条件语句块必须以.endif结束。 

条件语句允许使用逻辑操作符,比如逻辑AND“&&”、逻辑OR“||”,整条语句还可以用“!”操作符取反。“!”操作符具有最高优先级,其后依次是逻辑AND和逻辑OR。括号可以被用来指定优先级顺序。关系运算符也是可以使用的,比如“>”、“>=”、“<”、“<=”、“==”和“!=”。这些操作符可被用于十进制和十六进制的数值。对于字符串则可以使用“==”和“!=”操作符。如果没有给出操作符,那么将会把数值和0进行比较。 

在下面的例子中,在对VER变量进行赋值之后对条件进行测试。注意,如果VER变量未被赋值,则最后的.else条目将被求值为真,TAG将被赋成2.4_stable的值。 

.if $(VER) >= 2.4

  TAG = 2.4_current

.elif $(VER) == 2.3

  TAG = 2.3_release

.else

  TAG = 2.4_stable

.endif


条件语句可用于测试变量,也可以用于下面这种函数风格的表达式。 

这些用法中有些是有简写形式的。出于兼容性方面的考虑,我们列出了简写形式。非简写形式意思更为确定,也更容易被理解,但需要你敲入更多的字符。 

在使用简写形式的时候是不必使用括号的。此外,简写形式还可以和if/else语句以及其它的简写形式混合在一起: 

make( < arg > ) short hand [ .ifmake, .ifnmake, .elifmake, .elifnmake ]  

在上例中,make将以一个目标名字作为它的参数。如果这个目标已在命令行上给出,或者它就是缺省进行make的目标,那么该值就为真。下例中将根据make()表达式的规则给CFLAGS赋值: 

.if make(debug)

  CFLAGS += -g

.elif make(production)

  CFLAGS += -O2

.endif


下面是用简写形式表示的相同代码: 

.ifmake debug

  CFLAGS += -g

.elifmake production

  CFLAGS += -O2

.endif


target( < arg > ) 

这种形式将以一个目标名字作为参数。仅当该target已被定义时该值才为真。这个表达式没有简写形式。例如: 

.if target(debug)

  FILES += $(DEBUG_FILES)

.endif


在上例中,如果debug目标返回真的话就会对FILES变量进行附加操作。 

empty ( < arg > ) 

这种形式以一个变量为参数,并允许使用修饰符。当变量被扩展之后是一个空字符串时该值为真。这个表达式没有简写形式。此外需要注意的是,你在使用这个表达式的时候并不需要对数值进行引用,记住是VAR而不是$(VAR)。例如: 

.if empty (CFLAGS)

   CFLAGS = -Wall -g

.endif


defined( < arg > ) short hand [ .ifdef , .ifndef , .elifdef, elifndef ] 

下面这个例子采用一个变量作为参数。仅当变量已被定义时该值为真。 

.if defined(OS_VER)

  .if $(OS_VER) == 4.4

     DIRS += /usr/local/4.4-STABLE_src

  .endif

.else

  DIRS += /usr/src

.endif


下面是简写形式: 

.ifdef OS_VER

. if $(OS_VER) == 4.4

  DIRS += /usr/local/4.4-STABLE_src

. endif

.else

  OS_VER = 3.2

  DIRS += /usr/src

.endif


正如你所看到的,make允许嵌套的条件和define表达式。和C不同的是,你不能对if语句和变量赋值语句进行缩进。如果想让你的条件语句块更清晰一点的话,你可以在圆点之后、if之前加一些空格。示例如下: 

.if $(DEBUG) == 1

   $(CFLAGS) = -g

.     ifndef $(DEBUG_FLAGS)

         $(FLAGS) = $(DEBUG_FLAGS)

.     endif

.endif


exists( < arg > ) 

下面的例子演示了如何使用exists,以及如何给一个目标添加条件语句。如果存在tmp目录,make将运行-rm tmp/*.o命令。正如你在这个例子中看到的,.if语句仅在clean目标中被求值;所运行的命令必须遵从常规的命令语法。 

clean:

    -rm -f *.o *.core

.if exists(tmp)

    -rm tmp/*.o

.endif

Tags:FreeBSD 系统 编程
关于开源中文网 - 联系我们 - 广告服务 - 网站地图 - 版权声明