免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2379 | 回复: 1

FreeBSD系统编程[简体中文版] [复制链接]

论坛徽章:
0
发表于 2008-10-13 20:14 |显示全部楼层


文件:
freebsdsp.pdf
大小:
631KB
下载:
下载
FreeBSD系统编程

Copyright(C) 2001~2006 Nathan Boeger 和Mana Tominaga 版权所有,简体中文版由ChinaUnix论坛提供翻译。
目录
第一章: FreeBSD的Make
第二章: BSD自举
第三章: 进程和内核服务
第四章: 高级进程控制和信号
第五章: 基本I/O
第六章: 高级I/O
第七章: 进程资源和系统限制
第八章: FreeBSD 5.x
第一章 FreeBSD的make

译者:雨丝风片@chinaunix.net
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)的选项给出。
[Copy to clipboard]

[ - ]
CODE:
make -f OtherMakefile
1.3 语法
一个Makefile的结构由四个基本行组成,它们都可以通过在行尾添加‘\’字符来扩展到下一行(和shell编程相似)。注释是以‘#’号开始的,至行尾结束。
[Copy to clipboard]

[ - ]
CODE:
########################################
# Simple Makefile with comment example #
########################################
# when run, it will just echo hello
all:
   echo "hello"
要使用make来编译一个工程,首先需要确定在你的当前工作目录中已有一个正确的Makefile,然后再通过下列命令之一来使用make:
[Copy to clipboard]

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

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

[ - ]
CODE:
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:目标;这是一个通用的惯例,有助于避免错误和不必要的任务。
上例所示的依赖序列只是很简单的一个。下面是一个更为复杂和灵活的依赖序列,我们没有给出用于具体目标的命令:
[Copy to clipboard]

[ - ]
CODE:
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认为满足规则,它将通过执行相应的命令来创建特定的目标(比如编译该目标)。例如,使用单冒号:可以让你对需要进行编译的目标进行更为精细的控制。也就是说,你可以指定某个特定的目标文件每次都需要重新编译或者仅当它的源文件过时之后才编译。这些规则都是基于目标名字的结束符号的,如下:
如果目标名字以单冒号(:)结束,它将根据以下两个规则来创建:
  • 如果目标尚未存在,就像我们在上面举的例子里的all:一样,make就会创建它。
  • 如果任意一个源文件具有比当前目标更新的时间戳。在上例中如果app.h或myprog.c具有更新的时间戳,myprog.o就会被make。这种情况只需简单地用一下touch命令即可出现


    [Copy to clipboard]

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

    如果目标名字以感叹号(!)结束,只要make把它所需的全部依赖条件都创建完毕就会来创建它。
    你只能在目标或源文件的最后一个组成部分中使用通配表达式?、*和[],而且只能用于描述已经存在的文件。比如:
    [Copy to clipboard]

    [ - ]
    CODE:
    myprog.[oc]
    而使用花括号{}的表达式则不一定非得描述已经存在的文件。比如:
    [Copy to clipboard]

    [ - ]
    CODE:
    {mypgog,test}.o
    # the expression above would match myprog.o test.o only
    最后需要注意一点:可变表达式是按照目录顺序来处理的,而非字母顺序,就跟在shell表达式中一样。例如,如果你的目标有某些基于字母顺序的依赖条件,下面这个表达式可能就不对了:
    [Copy to clipboard]

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

    [ - ]
    CODE:
    MYSRC = a.c
    你只需要把这一行更新成新的名字即可,如下:
    [Copy to clipboard]

    [ - ]
    CODE:
    MYSRC = b.c
    你因此而节省了时间,这也正是make的首要角色:项目管理。
    变量可用$()或就用一个$来引用,但后者未被广泛使用,因此建议别那样写。
    [Copy to clipboard]

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

    这些变量可以在Makefile里用以下五种操作符进行定义:
  • 等号“=”是最常用的操作符,这和shell的情况是类似的。数值被直接赋给了变量。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    VAR =
  • 加号等号“+=”的意思是附加赋值,通过把所赋数值附加到当前数值的后面来完成对变量的赋值。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    VAR +=
  • 问号等号“?=”的意思是条件赋值,仅当该变量从未赋值时才进行赋值。条件赋值在向一个字符串数值添加前缀的时候非常有用。例如:
    [Copy to clipboard]

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

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

    [ - ]
    CODE:
    VAR !=

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

    [ - ]
    CODE:
      #####################
      # Example make file #
      #####################
      CURDIR != pwd
      CFLAGS ?= -g
      CFLAGS += -Wall -O2
      
      all:
          echo $CFLAGS
      #######################
      
      bash$ CFLAGS="-g -Wall" make
    在上例中,CURDIR被设置成了shell命令pwd的结果。(注意,这种赋值并不需要backtick命令(即' ')。)CFLAGS如果从未设置,会首先被设置成-g,然后不管它的当前值是什么,都会被附加上-Wall -O2。
    1.7 命令
    如果没有命令,那make什么都不是,只有把命令告诉make它才能完成它的工作。make只能运行这些命令,然后基于shell的退出状态判断这些命令是否成功。因此,如果命令失败,shell返回一个错误,make将会因错误而退出并于该处终止。make可以就被看作是另外一个命令——除了运行其它命令之外,它和那些命令并没有什么实际的交互。
    命令必须与一个目标相关联,任何一个目标都可以有多个命令。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    # example for an all-install target
    all-install:
       $(CC) $(CFLAGS)  $(MYSRC)
       cp  $(MYPROG) $(INSTALL_DIR)
       echo "Finished the build and install"
    每个命令都必须在一个目标之后以新行开始,在实际命令起始位置之前必须要有一个tab键,如上所示。
    对于大多数情况而言,Makefile里的命令只要是个有效的shell命令就行,命令还经常会包括变量。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    CPP = -g++
    CFLAGS = -Wall -O2
    myprog.o:
        $(CPP) $(CFLAGS) -c myprog.c
    下面这个例子告诉make使用给定值编译myprog.c。这些命令可能会比一行要长,它们也可以被写来完成其它的任务。这是非常重要的,因为编译器可以接受相当多的命令行选项、环境设置以及定义等等,比如:
    [Copy to clipboard]

    [ - ]
    CODE:
    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文件移走,这些操作都可以很方便的完成。
    [Copy to clipboard]

    [ - ]
    CODE:
    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仍将继续。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    clean:
        -rm -f *.o *.core $(APP)
        -mv $(LOG).log $(LOG)_$(DATE).log
    这将使得make忽略掉rm和mv命令可能遇到的错误。
    你还可以让make禁止掉“echo”之类的命令的输出。echo首先告诉make打印出包括echo语句在内的整个命令,然后再执行命令并将字符串打印到屏幕上:
    [Copy to clipboard]

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

    [ - ]
    CODE:
        @echo $(SOME_DEFINED_STRING)
    “-”号和“@”号都既可用于变量,又可用于形为字符串的命令,但需要确定你对变量命令的引用是正确的。下例演示了如何对命令使用@操作符:
    [Copy to clipboard]

    [ - ]
    CODE:
    ECHO = echo
    MESSAGE = "Print this message"
    msg::
      @$(ECHO) $(MESSAGE)  
    1.8 条件语句(#if,#ifndef等等)
    如果你对C和C++比较熟悉,那你肯定知道条件预处理命令。功能繁多的make也有一个类似的特性。条件语句使你可以选择Makefile里的哪个部分需要被处理。这些条件语句最多可以嵌套30层,并且可以放在Makefile里的任何地方。每条语句都必须以一个圆点(.)开始,而且条件语句块必须以.endif结束。
    条件语句允许使用逻辑操作符,比如逻辑AND“&&”、逻辑OR“||”,整条语句还可以用“!”操作符取反。“!”操作符具有最高优先级,其后依次是逻辑AND和逻辑OR。括号可以被用来指定优先级顺序。关系运算符也是可以使用的,比如“>”、“>=”、“
    [Copy to clipboard]

    [ - ]
    CODE:
    .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(  ) short hand [ .ifmake, .ifnmake, .elifmake, .elifnmake ]
    在上例中,make将以一个目标名字作为它的参数。如果这个目标已在命令行上给出,或者它就是缺省进行make的目标,那么该值就为真。下例中将根据make()表达式的规则给CFLAGS赋值:
    [Copy to clipboard]

    [ - ]
    CODE:
    .if make(debug)
      CFLAGS += -g
    .elif make(production)
      CFLAGS += -O2
    .endif
    下面是用简写形式表示的相同代码:
    [Copy to clipboard]

    [ - ]
    CODE:
    .ifmake debug
      CFLAGS += -g
    .elifmake production
      CFLAGS += -O2
    .endif
    target(  )
    这种形式将以一个目标名字作为参数。仅当该target已被定义时该值才为真。这个表达式没有简写形式。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    .if target(debug)
      FILES += $(DEBUG_FILES)
    .endif
    在上例中,如果debug目标返回真的话就会对FILES变量进行附加操作。
    empty (  )
    这种形式以一个变量为参数,并允许使用修饰符。当变量被扩展之后是一个空字符串时该值为真。这个表达式没有简写形式。此外需要注意的是,你在使用这个表达式的时候并不需要对数值进行引用,记住是VAR而不是$(VAR)。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    .if empty (CFLAGS)
       CFLAGS = -Wall -g
    .endif
    defined(  ) short hand [ .ifdef , .ifndef , .elifdef, elifndef ]
    下面这个例子采用一个变量作为参数。仅当变量已被定义时该值为真。
    [Copy to clipboard]

    [ - ]
    CODE:
    .if defined(OS_VER)
      .if $(OS_VER) == 4.4
         DIRS += /usr/local/4.4-STABLE_src
      .endif
    .else
      DIRS += /usr/src
    .endif
    下面是简写形式:
    [Copy to clipboard]

    [ - ]
    CODE:
    .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之前加一些空格。示例如下:
    [Copy to clipboard]

    [ - ]
    CODE:
    .if $(DEBUG) == 1
       $(CFLAGS) = -g
    .     ifndef $(DEBUG_FLAGS)
             $(FLAGS) = $(DEBUG_FLAGS)
    .     endif
    .endif
    exists(  )
    下面的例子演示了如何使用exists,以及如何给一个目标添加条件语句。如果存在tmp目录,make将运行-rm tmp/*.o命令。正如你在这个例子中看到的,.if语句仅在clean目标中被求值;所运行的命令必须遵从常规的命令语法。
    [Copy to clipboard]

    [ - ]
    CODE:
    clean:
        -rm -f *.o *.core
    .if exists(tmp)
        -rm tmp/*.o
    .endif
    1.9 系统Makefiles,模板以及.include指令
    C的一个重要特性就是它的简单明了的预处理指令,也就我们常说的#include。这个特性也在make中实现了。所不同的是,如果你想包含另外一个Makefile,那你得在文件末尾包含它,而不是像C那样在文件头包含,这是因为变量是按顺序进行赋值的。Makefile中又有Makefile可能会造成混淆,不过包含一个Makefile的基本语法却是很简单的。注意,在include单词前面必须要加一个圆点(.)。基本语法如下:
    [Copy to clipboard]

    [ - ]
    CODE:
    .include file name
    如果要指定一个位于系统make目录中的Makefile,可以使用尖括号:
    [Copy to clipboard]

    [ - ]
    CODE:
    .include
    如果所指定的文件位于当前目录或者是由-I命令行选项指定的目录中,则使用双引号,这和C的#include是类似的:
    [Copy to clipboard]

    [ - ]
    CODE:
    .include "file name"
    在下面的例子中,如果已在project.mk中定义了CFLAGS变量,它将被这里的新声明所取代。当包含多个Makefile时这就可能造成一些麻烦。
    [Copy to clipboard]

    [ - ]
    CODE:
    .include "../project.mk"
    CFLAGS = -g
    FreeBSD系统有很多的系统Makefile可供包含,与之对应的是完成各种任务的例程。在大多数的FreeBSD系统中,这些Makefile位于/usr/share/mk/目录里。除此之外,还有一个/etc/defaults/make.conf,它是可以被/etc/make.conf取代的。(细节请参见man make.conf。)
    下面是一个常用的Makefile的简单列表。如果你正准备进行大量的内核编程或应用程序移植,那就好好利用Makefile吧。这些Makefile的形式为bsd..mk,其中表示这些Makefile的用途。
  • bsd.dep.mk:这是一个用来处理Makefile依赖关系的非常有用的包含文件。
  • bsd.port.mk:这是一个在构建应用程序的ports的时候使用的包含文件。

    使用.include指令的好处就是你可以把你的工程的Makefile分成很多的片断。比如,你的工程可以有一个主Makefile,由所有其它的子Makefile进行包含。这个主Makefile可以包含编译器变量及其所需的选项。这样一来,就不必在每个Makefile里都指定编译器和所需选项了,对编译器名字的引用也因此而得到了简化。这些公共使用的片断之后还可以用于其它的Makefile,对编译程序等例程的修改也将随之在所有的Makefile中生效。
    1.10 高级选项
    高级的make选项主要为了增加灵活性。我们建议你仔细阅读一下make的man page,以便对make有一个深入的了解。下面这些选项是我们最常使用的。
    局部变量
    make可以使用专门定义的局部变量,其范围仅限于当前目标。下面列出了七个这样的局部变量,同时还给出了它们的System V兼容的老的表示形式。(不建议使用System V的老的表示形式,之所以列出它们,只是出于向后兼容的考虑。)
    这个变量就是目标的名字:
    [Copy to clipboard]

    [ - ]
    CODE:
    .TARGET
    old style notation: '@'
    这个变量包含了当前目标的全部源文件的列表:
    [Copy to clipboard]

    [ - ]
    CODE:
    .ALLSRC
    old style notation: '>'
    这个变量是当前目标所隐含的源文件。它的数值是这个目标的源文件的名字和路径。(示例见下面的.SUFFIX部分。)
    [Copy to clipboard]

    [ - ]
    CODE:
    .IMPSRC
    old style notation: '
    这个变量保存的是已经被确定为过期的源文件的列表:
    [Copy to clipboard]

    [ - ]
    CODE:
    .OODATE
    old style notation: '?'
    这个变量的值是不包括后缀和路径的文件名:
    [Copy to clipboard]

    [ - ]
    CODE:
    .PREFIX
    old style notation: '*'
    这个变量的值是档案文件的名字:
    [Copy to clipboard]

    [ - ]
    CODE:
    .ARCHIVE
    old style notation: '!'
    这个变量的值是档案成员的名字:
    [Copy to clipboard]

    [ - ]
    CODE:
    .MEMBER
    old style notation: '%'
    当在依赖关系行中使用这些局部变量的时候,只有.TARGET、.PREFIX、.ARCHIVE和.MEMBER具有该目标的值。
    另外还有一个非常不错的指令:
    [Copy to clipboard]

    [ - ]
    CODE:
    .undef
    这会让你非常方便地取消对一个变量的定义。注意,只有全局变量才能取消定义。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    .ifdef DEBUG
    .undef RELEASE
    .endif
    1.11 转换规则(后缀规则)
    转换规则规定了如何去创建一个目标。你可以利用这些规则,省下为每个目标文件编写规则的时间。语法很简单:.SUFFIXES: (suffix list)
    注意,可能有多个不同的后缀共享相同的转换后缀(.cpp、.cc、.c都可以转换成a.o)。如果没有列出后缀,make将把之前的所有后缀都删除。这可能会在你有多个.SUFFIXES规则的时候造成很大的混乱。我们建议你只使用一个规则,并把它放在Makefile的底部。把.SUFFIXES语句块中列出的东西和target进行比较,你就容易理解它的结构了。
    [Copy to clipboard]

    [ - ]
    CODE:
    .SUFFIXES: .cpp .c .o .sh
    .c.o:
         $(CC) $(CFLAGS) -c ${.IMPSRC}
    .cpp.o:
         $(CPP) $(CXXFLAGS) -c ${.IMPSRC}
    .sh:
         cp ${.IMPSRC} ${.TARGET}
         chmod +x ${.TARGET}
    上面给出的这些规则将会编译C和C++源文件。不过对于.sh:规则而言,它也可以告诉make去创建相应的shell脚本。注意,如果想在这个例子中列出某个shell脚本作为目标,那就不能添加.sh扩展名,不过那个shell脚本本身却必须是带有.sh后缀的。
    如果我们把不带.sh后缀的install_script列出来作为一个依赖条件,那就必须存在一个带有.sh后缀的shell脚本,名字为install_script.sh。也就是说,如果某个文件可以被列出来作为一个目标,那么在这个文件的存在期间,仅当make认为那个目标和该文件比起来过了期之后才会去创建它,make并不会去创建这个文件。示例见下;更多的信息请参见apps.h的例子:
    [Copy to clipboard]

    [ - ]
    CODE:
    all: install_script $(OBJS)
         $(CPP) $(CFLAGS) -o $(APP) $(OBJS) $(LINK)
    1.12 有用的命令行选项
    下面给出了一些非常容易学习和使用的命令行选项。这并不是一个完整的列表,要想知道其它的选项还得去看man page。
    -D  
    这个选项将在命令行上定义一个变量,如果你的Makefile里面有.ifdef语句,那么使用这个选项就非常方便了。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    .ifdef DEBUG
      CFLAGS += -g -D__DEBUG__
    .endif
    于是,当你运行命令make -D DEBUG的时候,make就会正确的设置CFLAGS,在编译你的应用程序的时候也把调试语句编进去。
    -E  
    这个选项将用环境变量的值取代Makefile中给变量赋的值。在使用这个选项之前,需要先设置你的shell中的环境变量。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    bash $ CFLAGS="-O2 -Wall" make -E CFLAGS
    -e
    和大写形式类似,-e将用环境变量取代Makefile中的所有变量。如果某个变量没有定义相应的环境变量,则按正常方式赋值。
    -f
    这个选项使你可以在命令行上指定Makefile,这一点在你需要多个Makefile时很有用。例如:
    [Copy to clipboard]

    [ - ]
    CODE:
    bash$  make -f Makefile.BSD
    -j  
    这个选项使你可以指定make能够派生出多少个job来。一般来说make只会派生出一个,但对于一个非常大的工程而言,如果想让你的硬件物尽其用的话,那就指定4个吧,如下:
    [Copy to clipboard]

    [ - ]
    CODE:
    make -j 4 world
    如果超过了4个,有时反而会延长执行时间,不过有些人倒是可能觉得CPU被六个或更多的job折腾的样子很有趣。
    -n
    这个选项对于Makefile的调试很有用,它可以让你看到make究竟会执行哪些命令,但又不会实际去执行它们。对于有很多命令的大型工程,最好是把输出重定向到一个外部文件中,否则就会有浩如烟海的命令显示出来。示例如下:
    [Copy to clipboard]

    [ - ]
    CODE:
    bash $ make -n >> make_debug.log 2>&1
    -V
    这个选项将基于全局的上下文打印变量的值。与前一个选项类似,make也不会去构建任何目标。你可以在命令行上指定多个-V选项,如下:
    [Copy to clipboard]

    [ - ]
    CODE:
    make -V CFLAGS -V LINK -V OBJS
    1.13 一个最后的例子
    下面列出的Makefile是一个可以重复使用的Makefile的例子。在你包含了它之后,它就知道从列出的.SUFFIXES规则中得知如何去编译C++源文件。它还知道如何去安装应用程序和清除开发目录。这显然并不是一个很好理解的Makefile,但它却是一个很好的创建通用模板风格的Makefile的范例,这种Makefile包含了那些用于开发的公共例程。这不只是节省了在创建每个Makefile时重复输入这些公共规则的时间,它还能让开发人员重复使用已知的好例程。
    [Copy to clipboard]

    [ - ]
    CODE:
    ########################################################
    #
    # FILE: Makefile
    #
    # AUTHOR: Nathan Boeger
    #
    # NOTES:
    #  This is a generic Makefile for *BSD make, you will
    #  need to customize the listed variables below inside
    #  the Makefile for your application.
    #
    # INSTALL_DIR = name of the directory that you want to install
    #   this applicaion (Ex: /usr/local/bin/ )
    #
    # APP          = name of the application
    #
    # C_SRC      = C source files (Ex: pstat.c )
    #
    # CPP_SRC  = CPP source files (Ex: node.cpp)
    #
    #
    # $Id: ch01.html,v 1.5 2004/08/10 14:41:39 nathan Exp $
    #########################################################
    # Make the OBJ's from our defined C & C++ files
    .ifdef CPP_SRC
    OBJS            =       ${CPP_SRC:.cpp=.o}
    .endif
    .ifdef C_SRC
    OBJS            +=      ${C_SRC:.c=.o}
    .endif
    # define the  Compiler. The compiler flags will be appended to
    # if defined, else they are just assigned the values below
    CPP             =        g++
    CFLAGS          +=       -Wall -Wmissing-prototypes -O
    LINK            +=       -lc
    # Add a debug flag.
    .ifdef DEBUG
      CFLAGS += -g
    .endif
    # Targets
    all: ${OBJS}
        $(CPP) $(CFLAGS) -o $(APP) ${OBJS} $(LINK)
    depend:
        $(CPP) -E -MM ${C_SRC} ${CPP_SRC}  > .depend
    #######################################################
    #
    #        INSTALL SECTION
    #
    # install will copy the defined application (APP) into the
    # directory INSTALL_DIR and chmod it 0755
    # for more information on install read MAN(1) install
    ########################################################
    install: all
        install -b -s $(APP) $(INSTALL_DIR)
      
    clean
         rm -f $(APP) *.o *.core
    # SUFFIX RULES
    .SUFFIXES: .cpp .c .o
    .c.o:
           $(CPP) $(CFLAGS) -c ${.IMPSRC}
    .cpp.o:
           $(CPP) $(CFLAGS) -c ${.IMPSRC}
    下面给出的Makefile是需要你在工程目录内部创建的:
    [Copy to clipboard]

    [ - ]
    CODE:
    #######################################################
    #       PROJECT Makefile
    #
    # This is what the programs makefile would look like
    # These are the only variables you will need to define
    ######################################################
    APP            = myapp
    C_SRC          = debug_logger.c
    CPP_SRC        = myapp.cpp  base_classes.cpp
    INSTALL_DIR    = /usr/local/bin/
    # And include the template Makefile, make sure its
    # path is correct.  
    .include "../../bsd_make.mk"

    第二章  自举BSD

    翻译(meilincore@chinaunix)
    2.1 自举BSD
    自举:在很少或者没有协助的情况下主动,努力的提升和发展(把她自己自举到顶点)
    Bootstrap : to promote or develop by initiative and effort with little or no assistance  www.m-w.com
    自举计算机是指加载操作系统的过程,该过程为:初始化硬件,读取一小部分代码到内存并执行.这一点代码接着加载一个大的操作系统.一旦操作系统被加载,它就需要创建自己的整个环境.这个过程,称做自举计算机,是一个复杂的,高度平台相关的过程.
    本章我们将从细节上探索FreeBSD在i386平台上的自举过程.相关的概念和过程与NetBSD和OpenBSD在i386平台上的自举程序相类似.注意一些汇编代码对于从实际上完成i386基本系统的启动任务是必须的.然而,我们不会从细节上回顾汇编代码而是主要集中在高层概念上,因此就算你不是专家讨论也是有意义的.
    注意:虽然本章中讨论的一些概念,特别是"实模式"和"保护模式",并不存在与现代象PPC或Alpha这些硬件构架当中.但是i386 BSD基本系统是目前为止最为广泛的系统并会继续如此(一个值得注意的例外是Mac OS X),而且适应很多情况.如果你对启动系统细节有兴趣,你可能需要自定义内核,自定义文件系统,设备驱动.同时.i386构架还广泛应用与嵌入式系统.给定一个安装平台(Given the install base),i386平台及其成果将继续在未来一些年里适用.甚至新的64位CPU到目前我们所知道的为止还是一样的启动过程.
    2.2 FreeBSD的自举过程
    FreeBSD使用一种三段启动过程,当你打开计算机电源或者重新启动.一旦BIOS完成系统检测.它将从DISK0加载第一个轨道到内存中.(每一个过程使用512字节的程序.刚好占用硬盘的一个块)第一轨道就是我们熟知的主引导记录(MBR),也就是boot0,第一个被计算机加载执行的程序.第二个程序,boot1,又是固定大小为512字节并知道如何读取slice信息和加载boot2.一旦boot2被加载它就有能力直接启动系统或者加载加载器(loader program),加载器固定大小为512字节.相当精致并被设计来允许对系统精确启动作更多的控制.
    boot0
    从BIOS加载的第一个程序,boot0,是一个固定大小为512字节的小程序,位于主引导记录(MBR).你可以在/usr/src/sys/boot/i386/boot找到该程序的源代码.当然现代计算机的BIOS可以设置成从各种不同的驱动器包括光驱,软驱和IDE硬盘启动.对于本章来说,我们将假设计算机是从第一个硬盘启动的.也就是磁盘驱动器0,C:,或者,对BIOS而言,0X80.
    从第一个磁盘的第一个扇区,512字节被读到内存位置0X7C00.然后,BIOS会检验内存位置0X7DFE的数字0XAA55(启动块代码的最后两个字节).这个位置索引数字0XAA55对i386是如此重要而被赋予了一个适当的名字----魔数.这意味着,只有在这个数字存在于内存位置0X7DFE的情况下BIOS才会把控制权转移给boot0被安置的内存位置0X7C00.
    这提出了在Intel i386系统上编写启动代码的一个要点:记着你代码中的第一个内存位置处(0X0)必须是一条指令.并且,当BIOS转移控制权给内存位置0X7C00的时候该位置必须包含一条指令.这可以是一个简单的跳转到其他位置或者启动程序主过程的入口.另外当启动代码被执行时你对CPU到底在干什么是完全没有控制的.因为这时候寄存器的状态也是未知的,你别指望已经设置好了适当的段和栈寄存器.这个小工作必须由启动代码来完成.因为目前操作系统还没有加载起来;所有的I/O必须通过BIOS过程来完成(Intel CPU 文档有一个完整列表)
    在boot0被加载并获得控制权以后,它将设置自己的寄存器和栈信息.接着,boot0重定位自己到一个低的内存位置并跳转到它主过程的新的偏移地址.这时候boot0必须还能够定位和启动其他磁盘和分区,在它的最后,boot0代码有一个小的常见的可启动类型列表,它们都必须在其最后两个字节包含魔数才可启动.
    最终,当boot0完成搜索可启动磁盘和分区以后,将提示用户作一个选择.如果在一个短的时间段里面没有任何选择或者某个键被按下,boot0将加载下一个启动块到内存并转移控制权给它.还是那样,这可以是任何操作系统的启动代码----你可以设置它加载Linux的自举代码或者甚至DOS.对BSD而言,下阶段的启动程序是boot1.
    boot1
    与boot0相似,boot1是一个非常简单且总共才512字节的程序;它也必须在最后两个字节包含魔数.其目的是定位和读取FreeBSD磁盘分区,接着定位和加载boot2.
    虽然在大多数情况下,boot1是被boot0加载的,但是这个顺序不必是唯一存在的选项.由于FreeBSD的灵活性你可以使用被称为专用磁盘的选项(说的就是那个更为声名狼籍的危险的专用磁盘)一个专用磁盘就是整个磁盘,或者BIOS的每一个扇区都属于FreeBSD.通常,你会在PC机磁盘上发现一个fdisk表,或者一个slice表,用来允许多个操作系统从单个PC机磁盘上启动.你可以选择使用一个专用磁盘并且直接从boot1启动;boot0完全没有必要安装在磁盘上.不管你采用那种实现,boot1都是非常重要的启动块并需要被加载
    Boot1被加载到内存位置0X7C00并操作在实模式;环境没有设置,寄存器也在未知状态.boot1必须设置好栈,定义所有的寄存器,并使用BIOS接口来做所有的I/O.一旦boot1被加载到内存并且控制权已经转移过来.它必须在自己的第一个内存位置(0X0)包含一条指令.所有这些都成功以后,boot1将读取系统磁盘搜索boot2.
    一旦boot2被加载.程序必须设置好boot2的环境;boot2是一个BTX客户端(boot Extender 启动扩展器)并且比前面的boot0和boot1稍微复杂一些.boot1需要加载boot2到内存位置0X9000并且它的入口是0X9010.然后,甚至在boot1加载并转移控制权给boot2以后,还有一个被boot2使用的过程存在于boot1.如果你阅读boot1的源代码你会注意到一个函数呼叫xread.这个函数用来通过BIOS从磁盘读取数据.因此,boot1在内存中的位置是非常重要的并且boot2必须知道它的位置才能正常工作.
    boot2
    目前为止我们已经加载了两个启动块和一个大的程序到内存中.转移了两次控制权且每次都重新设置了一个小的环境(栈,段寄存器等等...),并通过BIOS执行了一些有限的I/O.我们依然没有触及到加载操作系统的点子上来.如果你看过FreeBSD启动过程中的屏幕,到目前为止你可能看见F1和可爱的ASCII螺旋线.你可能印象当中不会觉得这都是,但他的却是原原本本,精确原生的汇编代码让这个启动过程看起来如此幽雅而轻巧
    现在到最后一个自举过程了,boot2.最后阶段很简单并且可以发生两件事中的一件:boot2加载加载器(我们将在下一节讨论这个)或者,boot2加载内核并直接启动而完全不使用加载器.如果你曾经打断boot2的加载过程,你可能已经看过这个boot2输出到屏幕的东西:
    [Copy to clipboard]

    [ - ]
    CODE:
    >>FreeBSD/i386 BOOT
    Defualt: 0:ad(0,a)/boot/loader
    boot:
    如果你按下回车,boot2会简单的加载默认加载器,象列出的那样.当然,如果你只是键入"boot kernel"接着它就会加载内核(/kernel)并启动.你可以,当然,改变这些默认值.如果你想找到更多信息请阅读boot( 8 )的文档
    早先我们提到过boot2是一个BTX客户端(Boot Extender 启动扩展器).这留下什么呢?BTX提供者是一个基础的虚拟86寻址环境.该来讨论一下Intel硬件内存寻址的历史了.
    目前为止我们都避免提及内存寻址方式.这可能会比较混淆因为Intel CPU要忍受历史问题,并且启动代码设计通常是留给那些非写不可的开发人员.除非你要移植一个系统到新的构架因而你的代码必须完全是平台依赖的,通常,一个程序员永远也不会被派去写一个启动加载器.然而启动过程对于需要编写设备驱动代码或者内核相关编程的开发人员来说是非常重要的.这就是一些开发人员会在这里碰到BTX加载器.
    大约从8088到80186,Intel处理器只有一种方式进行内存寻址,叫做实模式.这些早期的CPU有巨大的16位寄存器和20位内存地址.然后问题来了,你如何在16位的寄存器中构成一个20位的地址呢?答案就是,用两个16位寄存器,一个提供基地址而另一个提供这个基地址的偏移量.基地址寄存器被左移4位这样当两个合并的时候一个令人惊异的20位地址就被算出来了.用所有这些灵巧的段寄存器和位移,早期的Intel处理器可以寻址总共1M空间.今天这甚至还不够容纳一个Word文档,作为一个傲慢的例子.
    一旦80386席卷而来,寻址1M就不够了;用户需要更多的内存并且程序开始使用更多的内存.一种新的寻址模式叫做保护模式被发明了出来.新的保护模式允许寻址高达4G内存.
    新方式的另一个好处就是对于汇编程序员来说更容易实现.主要的区别就是你的扩展寄存器(它们还是那些16为寄存器不过386现在是32位了)可以包含一个完整的32位地址.甚至你以前的段寄存器现在都被保护了.程序不能写入或者读取他们.这些段寄存器现在被用来定位你的内存中的真实地址,这个过程包括权限验校位(读写等等...)并引入了MMU(memory management unit内存管理单元)
    现在回到BTX客户端的问题.我们使用BTX程序有什么好处呢?很简单:灵活性.BTX提供了足够的服务因此一个小的,拥有漂亮接口的程序可以被写出并能在加载内核的过程中非常灵活.在FreeBSD系统中这就是加载器.从下一节有将看到加载器真的是多么的漂亮和灵活.因此本节余下的部分我们将涵盖基本的BTX服务.
    BTX服务可以被归类到两个基本组.第一组是由直接函数呼叫(类似于系统呼叫)提供的系统服务.另外一组是不由客户端直接呼叫的环境服务.这些服务类似于一个操作系统,然而BTX程序是象单任务环境那样操作的.
    直接呼叫提供的BTX服务由两个系统呼叫组成.exit 和 exec.当exit被呼叫的时候BTX加载器结束并且系统重启.最后的一个系统呼叫exec将转移控制权给提供的地址.在个控制权移交是在超级用户模式下完成,并且新的程序可以离开被保护的CPU模式
    BTX加载器提供的环境服务是非常基础的.BTX加载器处理所有的中断.这些中断被送到合适的BIOS处理器.BTX也模拟BIOS扩展内存传输呼叫.并且最后还模拟一些汇编指令,它们是pushf,popf,cli,sti.iret和hlt.
    最后注意:所有编写来运行在BTX环境中的程序都必须被编译并连接到btxld.更多信息请阅读BTX加载器联机手册
    2.3加载器
    最后的启动阶段是由加载器组成的.加载器程序是一个标准命令(引用为"内建命令")和一个Forth解释器的联合(基于ficl).加载器将允许用户同系统启动进行交互或者允许系统恢复和维护.通过加载器用户可以选择载入或者卸载内核模块.用户也可以设置或者取消设置相关变量,比如rootdevice(根设备)和module_path(模块路径).这些也可以在/boot/loader.conf里面改变.加载器读取的默认文件在/boot/defaults/loader.conf.默认文件也包含了许多可用的选项.这写文件都被构造得和/etc/rc.conf类似.
    加载器对于内核和设备驱动除错是非常有用的.通过加载器你可以通知内核在除错器起用的状态下启动.或者,你可以为设备驱动测试加载相关的内核模块.如果你准备编写任何内核模块或者设备驱动的话你最好是阅读加载器的所有文档.首先从联机手册开始,然后复习/boot/loader.conf中的所有选项.一路上加载器在你需要扩展BSD或者诊断内核崩溃的时候将非常有用.
    2.4开始内核服务
    我们终于到了内核已经被载入内存,CPU的控制权的也被传给它的阶段了.一旦内核被加载,它需要从初始化开始运行并准备系统进入多任务模式.该初始化包括三个主要组成部分.前两个是机器相关的,由c和汇编混合编写成.前两个阶段准备系统和初始化CPU内存管理单元(MMU,如果它存在)并处理硬件初始化.最后一个阶段继续建立基本内核环境并让系统准备好运行进程1(/sbin/init).前两个阶段是高度平台依赖的.因为每一种构架都有相关需要.我们将提供一个对这两个阶段的高层次浏览并在本书稍后当我们涵盖到设备驱动的时候从细节上来了解这些概念.
    阶段 1 & 2 内核装配和c起始过程
    虽然内核一旦被加载就不再对系统做任何假设,加载器还是会传送一些信息给内核,比如启动设备和启动标记.另外,内核必须创建自己的环境并让系统准备好运行进程0(下面解释).这些任务包括CPU检测,运行时刻任务建立,和内存数量检测.
    CPU识别是非常重要的一步,因为每个平台可以有多种不同的CPU(i386是其中之一),不是所有的CPU都会支持同样的特性.比如,拿MMX指令集来说,虽然它对于内核来说不是一个重要特性,但是对于浮点单元就是,因此如果这个特性不被该CPU所支持的话就必须用软件来模拟.这对于所有其他不被支持的特性和已知错误或者CPU的固有特性来说都是成立的
    第二个阶段会初始化系统硬件和内存.这包括探测硬件,初始化I/O设备,为内核结构分配内存,建立系统消息缓存.该阶段你会看到启动屏幕中闪过很多硬件列表.按照BSD传统,这个阶段是通过呼叫cpu_startup()函数来初始化.
    第3阶段 和 进程 0
    一旦cpu_startup()函数返回,内核就需要建立进程0.进程0通常称为交换器,如果你运行ps命令会看到它在活动.然而从感官上来说并没有这样一个名为swapper的二进制文件附属于该进程.这对于其他四个在现代FreeBSD系统上找到进程都是成立的:pagedaemon,vmdaemon,bufdaemon和syncer.为避免复杂化,我们只说这些进程是VM子系统的一部分;我们在进程一章中再讨论它们.着重理解他们是在启动过程中由内核创建而不是文件系统中的二进制程序,并且是相当平台依赖性的.他们是用c语言写成并在起始平台环境初始化完毕以后开始的
    init和系统shell脚本
    在所有的汇编和平台依赖性代码执行完毕以后,内核终于执行第一个真正的程序:/sbin/init.该程序是相当简短的(在FreeBSD上,总共约1,700行).象我们在BSD进程一章所讨论的那样,这就是那个所有进程都遗传自的进程.这样设计的实力在于,因为/sbin/init只是文件系统中的一个二进制程序,你可以自己写一个自定义版的.对于/sbin/init在启动时的主要目标就是运行系统启动脚本并为系统进入多用户模式做准备.留意信号:/sbin/init应当能优雅的处理信号,否则你的系统可能结束在/sbin/init程序的一个奇怪的状态并且在系统启动时崩溃掉.同时运行中/sbin/init可以接受信号来执行某些任务.比如,如果你想让系统不为某个相关的终端生产进程.象列在/etc/ttys里面的.你可以标记想要的终端为关闭状态并执行下面的命令使init读取/etc/ttys并只为列出的标记状态为开的终端生产进程.
    [Copy to clipboard]

    [ - ]
    CODE:
    bash$kill -HUP 1
    注意除非你很小心,否则可能会以一个你不能登录的系统告终(到关于信号的一章里面查看详细信息.)
    init程序会在启动过程中设置系统以进入多用户模式.这是很灵巧的.引入了象开始每一个守护进程并设置网络信息这样枯燥的任务.在UNIX系统中.有一些这样的途径,主要引入shell脚本.在某些版本的Linux和System V系统中,对应与某个运行级别的可启动脚本位于/etc/rc.d.然而BSD使用了简单得多的办法.这就是在/etc/里面找到的的rc脚本
    这些脚本通常是不能被编辑的,而是,在/etc/fc.conf里面设置变量(来改变行为).象PicoBSD的自定义安装你可能需要建立自己的脚本;PicoBSD是高度磁盘空间敏感的,并有相关的文件系统需要.一个重要的提示,/usr/local/etc/rc.d/文件夹是特别的.该文件夹的特别意义在于其中找到的每个.sh可执行文件都会在/etc/rc脚本之后被执行.为了方便系统管理这个文件夹替代了老的/etc/rc.local文件(/etc/rc.local是以前在系统启动尾声的时候启动自定义脚本或者程序的办法)
    BSD rc脚本包括值得注意的条目列表如下:
    [Copy to clipboard]

    [ - ]
    CODE:
    /etc/rc - 主脚本并且第一个被呼叫,挂接文件系统并运行所有需要的rc脚本
    /etc/rc.atm - 用来配置ATM网络.
    /etc/rc.devfs - 设置/dev/权限和连接
    /etc/rc.diskless1 - 第一个diskless客户端脚本
    /etc/rc.diskless2 - 第二个diskless客户端脚本
    /etc/rc.firewall - 用于防火墙规则
    /etc/rc.firewall6 - 用于IPV6防火墙规则
    /etc/rc.i386 - intel系统相关需要
    /etc/rc.isdn - isdn网络设置
    /etc/rc.network - IPV4网络设置
    /etc/rc.network6 - IPV6网络设置
    /etc/rc.pccard - 膝上电脑pc card控制器
    /etc/rc.serial - 设置串口设备
    /etc/rc.sysctl - 在系统启动时设置sysctl选项
    /usr/local/etc/rc.d/ - 存有自定义启动脚本的普通文件夹
    这里有一个例子:
    如果你想让rsync作为守护进程这个脚本会起作用:
    [Copy to clipboard]

    [ - ]
    CODE:
    #!/bin/sh
    if [ -x /usr/local/bin/rsync ]; then
            /usr/local/bin/rsync --deamon
    fi
    这将首先检测rsync是否存在然后以守护模式运行它


    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/4775/showart_1296280.html
  • 论坛徽章:
    0
    发表于 2008-10-24 14:29 |显示全部楼层
    这么好的文档,顶一个:wink:
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则 发表回复

      

    北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
    未成年举报专区
    中国互联网协会会员  联系我们:huangweiwei@itpub.net
    感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

    清除 Cookies - ChinaUnix - Archiver - WAP - TOP