﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-iniwf-随笔分类-Linux</title><link>http://www.cppblog.com/iniwf/category/9826.html</link><description>风是温柔的，雨是伤心的，云是快乐的，月是多情的，爱是迷失的，恋是醉人的，情是难忘的，天是长久的，地是永恒的</description><language>zh-cn</language><lastBuildDate>Thu, 02 Jul 2009 03:37:29 GMT</lastBuildDate><pubDate>Thu, 02 Jul 2009 03:37:29 GMT</pubDate><ttl>60</ttl><item><title>Linux下PCI设备驱动程序开发</title><link>http://www.cppblog.com/iniwf/archive/2009/07/02/89053.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Thu, 02 Jul 2009 03:19:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/07/02/89053.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/89053.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/07/02/89053.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/89053.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/89053.html</trackback:ping><description><![CDATA[转自<a href="http://www.ibm.com/developerworks/cn/linux/l-pci/index.html">http://www.ibm.com/developerworks/cn/linux/l-pci/index.html</a><br><br>
<p>2004 年 3 月 09 日</p>
<blockquote>PCI是一种广泛采用的总线标准，它提供了许多优于其它总线标准（如EISA）的新特性，目前已经成为计算机系统中应用最为广泛，并且最为通用的总线标准。Linux的内核能较好地支持PCI总线，本文以Intel 386体系结构为主，探讨了在Linux下开发PCI设备驱动程序的基本框架。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><a name=1><span class=atitle>一、PCI总线系统体系结构</span></a></p>
<p>PCI是外围设备互连（Peripheral Component Interconnect）的简称，作为一种通用的总线接口标准，它在目前的计算机系统中得到了非常广泛的应用。PCI提供了一组完整的总线接口规范，其目的是描述如何将计算机系统中的外围设备以一种结构化和可控化的方式连接在一起，同时它还刻画了外围设备在连接时的电气特性和行为规约，并且详细定义了计算机系统中的各个不同部件之间应该如何正确地进行交互。 </p>
<p>无论是在基于Intel芯片的PC机中，或是在基于Alpha芯片的工作站上，PCI毫无疑问都是目前使用最广泛的一种总线接口标准。同旧式的ISA总线不同，PCI将计算机系统中的总线子系统与存储子系统完全地分开，CPU通过一块称为PCI桥（PCI-Bridge）的设备来完成同总线子系统的交互，如图1所示。 </p>
<br><a name=N10045><strong>图1 PCI子系统的体系结构</strong></a><br><img alt="" src="http://www.ibm.com/developerworks/cn/linux/l-pci/images/1.jpg"> <br>
<p>由于使用了更高的时钟频率，因此PCI总线能够获得比ISA总线更好的整体性能。PCI总线的时钟频率一般在25MHz到33MHz范围内，有些甚至能够达到66MHz或者133MHz，而在64位系统中则最高能达到266MHz。尽管目前PCI设备大多采用32位数据总线，但PCI规范中已经给出了64位的扩展实现，从而使PCI总线能够更好地实现平台无关性，现在PCI总线已经能够用于IA-32、Alpha、PowerPC、SPARC64和IA-64等体系结构中。 </p>
<p>PCI总线具有三个非常显著的优点，使得它能够完成最终取代ISA总线这一历史使命： <br>
<ul>
    <li>在计算机和外设间传输数据时具有更好的性能；
    <li>能够尽量独立于具体的平台；
    <li>可以很方便地实现即插即用。 </li>
</ul>
<p>&#160;</p>
<p>图2是一个典型的基于PCI总线的计算机系统逻辑示意图，系统的各个部分通过PCI总线和PCI-PCI桥连接在一起。从图中不难看出，CPU和RAM需要通过PCI桥连接到PCI总线0（即主PCI总线），而具有PCI接口的显卡则可以直接连接到主PCI总线上。PCI-PCI桥是一个特殊的PCI设备，它负责将PCI总线0和PCI总线1（即从PCI主线）连接在一起，通常PCI总线1称为PCI-PCI桥的下游（downstream），而PCI总线0则称为PCI-PCI桥的上游（upstream）。图中连接到从PCI总线上的是SCSI卡和以太网卡。为了兼容旧的ISA总线标准，PCI总线还可以通过PCI-ISA桥来连接ISA总线，从而能够支持以前的ISA设备。图中ISA总线上连接着一个多功能I/O控制器，用于控制键盘、鼠标和软驱。 </p>
<br><a name=N10069><strong>图2 PCI系统示意图</strong></a><br><img alt="" src="http://www.ibm.com/developerworks/cn/linux/l-pci/images/2.jpg"> <br>
<p>在此我只对PCI总线系统体系结构作了概括性介绍，如果读者想进一步了解，David A Rusling在The Linux Kernel（http://tldp.org/LDP/tlk/dd/pci.html）中对Linux的PCI子系统有比较详细的介绍。 </p>
<br>
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="35" sizcache="2">
    <tbody sizset="35" sizcache="1">
        <tr>
            <td><img height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"><br><img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></td>
        </tr>
    </tbody>
</table>
<table class=no-print cellSpacing=0 cellPadding=0 align=right sizset="36" sizcache="2">
    <tbody sizset="37" sizcache="2">
        <tr align=right sizset="37" sizcache="2">
            <td sizset="37" sizcache="2"><img height=4 alt="" src="http://www.ibm.com/i/c.gif" width="100%"><br>
            <table cellSpacing=0 cellPadding=0 border=0 sizset="37" sizcache="2">
                <tbody sizset="37" sizcache="1">
                    <tr>
                        <td vAlign=center><img height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><br></td>
                        <td vAlign=top align=right><a class=fbox href="http://www.ibm.com/developerworks/cn/linux/l-pci/index.html#main" cmImpressionSent="1"><strong><u><font color=#800080>回页首</font></u></strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=2><span class=atitle>二、Linux驱动程序框架</span></a></p>
<p>Linux将所有外部设备看成是一类特殊文件，称之为&#8220;设备文件&#8221;，如果说系统调用是Linux内核和应用程序之间的接口，那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节，使得应用程序可以像操作普通文件一样来操作外部设备。 </p>
<p><a name=N10080><span class=smalltitle><strong><font face=Arial>1. 字符设备和块设备</font></strong></span></a></p>
<p>Linux抽象了对硬件的处理，所有的硬件设备都可以像普通文件一样来看待：它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作，而驱动程序的主要任务也就是要实现这些系统调用函数。Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示，例如，系统中的第一个IDE硬盘使用/dev/hda表示。每个设备文件对应有两个设备号：一个是主设备号，标识该设备的种类，也标识了该设备所使用的驱动程序；另一个是次设备号，标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致，否则用户进程将无法访问到设备驱动程序。 </p>
<p>在Linux操作系统下有两类主要的设备文件：一类是字符设备，另一类则是块设备。字符设备是以字节为单位逐个进行I/O操作的设备，在对字符设备发出读写请求时，实际的硬件I/O紧接着就发生了，一般来说字符设备中的缓存是可有可无的，而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区，当用户进程对设备进行读写请求时，驱动程序先查看缓冲区中的内容，如果缓冲区中的数据能满足用户的要求就返回相应的数据，否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的，其目的是避免耗费过多的CPU时间来等待操作的完成。一般说来，PCI卡通常都属于字符设备。 </p>
<p>所有已经注册（即已经加载了驱动程序）的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件，同时为其分配相应的主设备号和次设备号。例如，下面的命令： </p>
<p sizset="38" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="38" sizcache="2">
    <tbody sizset="38" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>[root@gary root]# mknod  /dev/lp0  c  6  0
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>将建立一个主设备号为6，次设备号为0的字符设备文件/dev/lp0。当应用程序对某个设备文件进行系统调用时，Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序，并从用户态进入到核心态，再由驱动程序判断该设备的次设备号，最终完成对相应硬件的操作。 </p>
<p><a name=N10099><span class=smalltitle><strong><font face=Arial>2. 设备驱动程序接口</font></strong></span></a></p>
<p>Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口，这是通过include/linux/fs.h中的数据结构file_operations来完成的： </p>
<p sizset="39" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="39" sizcache="2">
    <tbody sizset="39" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>struct file_operations {
            struct module *owner;
            loff_t (*llseek) (struct file *, loff_t, int);
            ssize_t (*read) (struct file *, char *, size_t, loff_t *);
            ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
            int (*readdir) (struct file *, void *, filldir_t);
            unsigned int (*poll) (struct file *, struct poll_table_struct *);
            int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
            int (*mmap) (struct file *, struct vm_area_struct *);
            int (*open) (struct inode *, struct file *);
            int (*flush) (struct file *);
            int (*release) (struct inode *, struct file *);
            int (*fsync) (struct file *, struct dentry *, int datasync);
            int (*fasync) (int, struct file *, int);
            int (*lock) (struct file *, int, struct file_lock *);
            ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
            ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
            ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
            unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
            <!-- code sample is too wide -->};
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>当应用程序对设备文件进行诸如open、close、read、write等操作时，Linux内核将通过file_operations结构访问驱动程序提供的函数。例如，当应用程序对设备文件执行读操作时，内核将调用file_operations结构中的read函数。 </p>
<p><a name=N100AC><span class=smalltitle><strong><font face=Arial>2. 设备驱动程序模块</font></strong></span></a></p>
<p>Linux下的设备驱动程序可以按照两种方式进行编译，一种是直接静态编译成内核的一部分，另一种则是编译成可以动态加载的模块。如果编译进内核的话，会增加内核的大小，还要改动内核的源文件，而且不能动态地卸载，不利于调试，所有推荐使用模块方式。 </p>
<p>从本质上来讲，模块也是内核的一部分，它不同于普通的应用程序，不能调用位于用户态下的C或者C++库函数，而只能调用Linux内核提供的函数，在/proc/ksyms中可以查看到内核提供的所有函数。 </p>
<p>在以模块方式编写驱动程序时，要实现两个必不可少的函数init_module( )和cleanup_module( )，而且至少要包含&lt;linux/krernel.h&gt;和&lt;linux/module.h&gt;两个头文件。在用gcc编译内核模块时，需要加上-DMODULE -D__KERNEL__ -DLINUX这几个参数，编译生成的模块（一般为.o文件）可以使用命令insmod载入Linux内核，从而成为内核的一个组成部分，此时内核会调用模块中的函数init_module( )。当不需要该模块时，可以使用rmmod命令进行卸载，此进内核会调用模块中的函数cleanup_module( )。任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。 </p>
<p><a name=N100BB><span class=smalltitle><strong><font face=Arial>3. 设备驱动程序结构</font></strong></span></a></p>
<p>了解设备驱动程序的基本结构（或者称为框架），对开发人员而言是非常重要的，Linux的设备驱动程序大致可以分为如下几个部分：驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。 </p>
<ul>
    <li><strong>驱动程序的注册与注销</strong>
    <p>向系统增加一个驱动程序意味着要赋予它一个主设备号，这可以通过在驱动程序的初始化过程中调用register_chrdev( )或者register_blkdev( )来完成。而在关闭字符设备或者块设备时，则需要通过调用unregister_chrdev( )或unregister_blkdev( )从内核中注销设备，同时释放占用的主设备号。 </p>
    <li><strong>设备的打开与释放</strong>
    <p>打开设备是通过调用file_operations结构中的函数open( )来完成的，它是驱动程序用来为今后的操作完成初始化准备工作的。在大部分驱动程序中，open( )通常需要完成下列工作： </p>
    <ol>
        <li>检查设备相关错误，如设备尚未准备好等。
        <li>如果是第一次打开，则初始化硬件设备。
        <li>识别次设备号，如果有必要则更新读写操作的当前位置指针f_ops。
        <li>分配和填写要放在file-&gt;private_data里的数据结构。
        <li>使用计数增1。 </li>
    </ol>
    <p>释放设备是通过调用file_operations结构中的函数release( )来完成的，这个设备方法有时也被称为close( )，它的作用正好与open( )相反，通常要完成下列工作： </p>
    <ol>
        <li>使用计数减1。
        <li>释放在file-&gt;private_data中分配的内存。
        <li>如果使用计算为0，则关闭设备。 </li>
    </ol>
    <li><strong>设备的读写操作</strong>
    <p>字符设备的读写操作相对比较简单，直接使用函数read( )和write( )就可以了。但如果是块设备的话，则需要调用函数block_read( )和block_write( )来进行数据读写，这两个函数将向设备请求表中增加读写请求，以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的，因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据，或者需要执行写操作将数据写入设备，那么就要执行真正的数据传输，这是通过调用数据结构blk_dev_struct中的函数request_fn( )来完成的。 </p>
    <li><strong>设备的控制操作</strong>
    <p>除了读写操作外，应用程序有时还需要对设备进行控制，这可以通过设备驱动程序中的函数ioctl( )来完成。ioctl( )的用法与具体设备密切关联，因此需要根据设备的实际情况进行具体分析。 </p>
    <li><strong>设备的中断和轮询处理</strong> <br>
    <p>对于不支持中断的硬件设备，读写时需要轮流查询设备状态，以便决定是否继续进行数据传输。如果设备支持中断，则可以按中断方式进行操作。 </p>
    </li>
</ul>
<br>
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="40" sizcache="2">
    <tbody sizset="40" sizcache="1">
        <tr>
            <td><img height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"><br><img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></td>
        </tr>
    </tbody>
</table>
<table class=no-print cellSpacing=0 cellPadding=0 align=right sizset="41" sizcache="2">
    <tbody sizset="42" sizcache="2">
        <tr align=right sizset="42" sizcache="2">
            <td sizset="42" sizcache="2"><img height=4 alt="" src="http://www.ibm.com/i/c.gif" width="100%"><br>
            <table cellSpacing=0 cellPadding=0 border=0 sizset="42" sizcache="2">
                <tbody sizset="42" sizcache="1">
                    <tr>
                        <td vAlign=center><img height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><br></td>
                        <td vAlign=top align=right><a class=fbox href="http://www.ibm.com/developerworks/cn/linux/l-pci/index.html#main" cmImpressionSent="1"><strong><u><font color=#800080>回页首</font></u></strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=3><span class=atitle>三、PCI驱动程序实现</span></a></p>
<p><a name=N1011D><span class=smalltitle><strong><font face=Arial>1. 关键数据结构</font></strong></span></a></p>
<p>PCI设备上有三种地址空间：PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间，其中I/O空间和存储空间提供给设备驱动程序使用，而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化，配置好所有的PCI设备，包括中断号以及I/O基址，并在文件/proc/pci中列出所有找到的PCI设备，以及这些设备的参数和属性。 </p>
<p>Linux驱动程序通常使用结构（struct）来表示一种设备，而结构体中的变量则代表某一具体设备，该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备，每个设备之间用次设备号进行区分，如果采用结构数据来代表所有能由该驱动程序驱动的设备，那么就可以简单地使用数组下标来表示次设备号。 </p>
<p>在PCI驱动程序中，下面几个关键数据结构起着非常核心的作用： </p>
<ul sizset="43" sizcache="2">
    <li sizset="43" sizcache="2"><strong>pci_driver</strong>
    <p>这个数据结构在文件include/linux/pci.h里，这是Linux内核版本2.4之后为新型的PCI设备驱动程序所添加的，其中最主要的是用于识别设备的id_table结构，以及用于检测设备的函数probe( )和卸载设备的函数remove( )： </p>
    <p sizset="43" sizcache="2">
    <table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="43" sizcache="2">
        <tbody sizset="43" sizcache="1">
            <tr>
                <td class=code-outline>
                <pre class=displaycode>struct pci_driver {
                struct list_head node;
                char *name;
                const struct pci_device_id *id_table;
                int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);
                void (*remove) (struct pci_dev *dev);
                int  (*save_state) (struct pci_dev *dev, u32 state);
                int  (*suspend)(struct pci_dev *dev, u32 state);
                int  (*resume) (struct pci_dev *dev);
                int  (*enable_wake) (struct pci_dev *dev, u32 state, int enable);
                };
                </pre>
                </td>
            </tr>
        </tbody>
    </table>
    <br></p>
    <li sizset="44" sizcache="2"><strong>pci_dev</strong>
    <p>这个数据结构也在文件include/linux/pci.h里，它详细描述了一个PCI设备几乎所有的硬件信息，包括厂商ID、设备ID、各种资源等： </p>
    <p sizset="44" sizcache="2">
    <table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="44" sizcache="2">
        <tbody sizset="44" sizcache="1">
            <tr>
                <td class=code-outline>
                <pre class=displaycode>struct pci_dev {
                struct list_head global_list;
                struct list_head bus_list;
                struct pci_bus  *bus;
                struct pci_bus  *subordinate;
                void        *sysdata;
                struct proc_dir_entry *procent;
                unsigned int    devfn;
                unsigned short  vendor;
                unsigned short  device;
                unsigned short  subsystem_vendor;
                unsigned short  subsystem_device;
                unsigned int    class;
                u8      hdr_type;
                u8      rom_base_reg;
                struct pci_driver *driver;
                void        *driver_data;
                u64     dma_mask;
                u32             current_state;
                unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];
                unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];
                unsigned int    irq;
                struct resource resource[DEVICE_COUNT_RESOURCE];
                struct resource dma_resource[DEVICE_COUNT_DMA];
                struct resource irq_resource[DEVICE_COUNT_IRQ];
                char        name[80];
                char        slot_name[8];
                int     active;
                int     ro;
                unsigned short  regs;
                int (*prepare)(struct pci_dev *dev);
                int (*activate)(struct pci_dev *dev);
                int (*deactivate)(struct pci_dev *dev);
                };
                </pre>
                </td>
            </tr>
        </tbody>
    </table>
    <br></p>
    </li>
</ul>
<p><a name=N1014F><span class=smalltitle><strong><font face=Arial>2. 基本框架</font></strong></span></a></p>
<p>在用模块方式实现PCI设备驱动程序时，通常至少要实现以下几个部分：初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。下面给出一个典型的PCI设备驱动程序的基本框架，从中不难体会到这几个关键模块是如何组织起来的。 </p>
<p sizset="45" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="45" sizcache="2">
    <tbody sizset="45" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>/* 指明该驱动程序适用于哪一些PCI设备 */
            static struct pci_device_id demo_pci_tbl [] __initdata = {
            {PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,
            PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},
            {0,}
            };
            /* 对特定PCI设备进行描述的数据结构 */
            struct demo_card {
            unsigned int magic;
            /* 使用链表保存所有同类的PCI设备 */
            struct demo_card *next;
            /* ... */
            }
            /* 中断处理模块 */
            static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
            {
            /* ... */
            }
            /* 设备文件操作接口 */
            static struct file_operations demo_fops = {
            owner:      THIS_MODULE,   /* demo_fops所属的设备模块 */
            read:       demo_read,    /* 读设备操作*/
            write:      demo_write,    /* 写设备操作*/
            ioctl:      demo_ioctl,    /* 控制设备操作*/
            mmap:       demo_mmap,    /* 内存重映射操作*/
            open:       demo_open,    /* 打开设备操作*/
            release:    demo_release    /* 释放设备操作*/
            /* ... */
            };
            /* 设备模块信息 */
            static struct pci_driver demo_pci_driver = {
            name:       demo_MODULE_NAME,    /* 设备模块名称 */
            id_table:   demo_pci_tbl,    /* 能够驱动的设备列表 */
            probe:      demo_probe,    /* 查找并初始化设备 */
            remove:     demo_remove    /* 卸载设备模块 */
            /* ... */
            };
            static int __init demo_init_module (void)
            {
            /* ... */
            }
            static void __exit demo_cleanup_module (void)
            {
            pci_unregister_driver(&amp;demo_pci_driver);
            }
            /* 加载驱动程序模块入口 */
            module_init(demo_init_module);
            /* 卸载驱动程序模块入口 */
            module_exit(demo_cleanup_module);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>上面这段代码给出了一个典型的PCI设备驱动程序的框架，是一种相对固定的模式。需要注意的是，同加载和卸载模块相关的函数或数据结构都要在前面加上__init、__exit等标志符，以使同普通函数区分开来。构造出这样一个框架之后，接下去的工作就是如何完成框架内的各个功能模块了。 </p>
<p><a name=N10162><span class=smalltitle><strong><font face=Arial>3. 初始化设备模块</font></strong></span></a></p>
<p>在Linux系统下，想要完成对一个PCI设备的初始化，需要完成以下工作：</p>
<ul>
    <li>检查PCI总线是否被Linux内核支持；
    <li>检查设备是否插在总线插槽上，如果在的话则保存它所占用的插槽的位置等信息。
    <li>读出配置头中的信息提供给驱动程序使用。 </li>
</ul>
<p>当Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作的同时，会建立起系统中所有PCI设备的拓扑结构，此后当PCI驱动程序需要对设备进行初始化时，一般都会调用如下的代码： </p>
<p sizset="46" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="46" sizcache="2">
    <tbody sizset="46" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>static int __init demo_init_module (void)
            {
            /* 检查系统是否支持PCI总线 */
            if (!pci_present())
            return -ENODEV;
            /* 注册硬件驱动程序 */
            if (!pci_register_driver(&amp;demo_pci_driver)) {
            pci_unregister_driver(&amp;demo_pci_driver);
            return -ENODEV;
            }
            /* ... */
            return 0;
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>驱动程序首先调用函数pci_present( )检查PCI总线是否已经被Linux内核支持，如果系统支持PCI总线结构，这个函数的返回值为0，如果驱动程序在调用这个函数时得到了一个非0的返回值，那么驱动程序就必须得中止自己的任务了。在2.4以前的内核中，需要手工调用pci_find_device( )函数来查找PCI设备，但在2.4以后更好的办法是调用pci_register_driver( )函数来注册PCI设备的驱动程序，此时需要提供一个pci_driver结构，在该结构中给出的probe探测例程将负责完成对硬件的检测工作。 </p>
<p sizset="47" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="47" sizcache="2">
    <tbody sizset="47" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>
            static int __init demo_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
            {
            struct demo_card *card;
            /* 启动PCI设备 */
            if (pci_enable_device(pci_dev))
            return -EIO;
            /* 设备DMA标识 */
            if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK)) {
            return -ENODEV;
            }
            /* 在内核空间中动态申请内存 */
            if ((card = kmalloc(sizeof(struct demo_card), GFP_KERNEL)) == NULL) {
            printk(KERN_ERR "pci_demo: out of memory\n");
            return -ENOMEM;
            }
            memset(card, 0, sizeof(*card));
            /* 读取PCI配置信息 */
            card-&gt;iobase = pci_resource_start (pci_dev, 1);
            card-&gt;pci_dev = pci_dev;
            card-&gt;pci_id = pci_id-&gt;device;
            card-&gt;irq = pci_dev-&gt;irq;
            card-&gt;next = devs;
            card-&gt;magic = DEMO_CARD_MAGIC;
            /* 设置成总线主DMA模式 */
            pci_set_master(pci_dev);
            /* 申请I/O资源 */
            request_region(card-&gt;iobase, 64, card_names[pci_id-&gt;driver_data]);
            return 0;
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p><a name=N1018B><span class=smalltitle><strong><font face=Arial>4. 打开设备模块</font></strong></span></a></p>
<p>在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申请控制权的时候，非阻塞方式遇忙返回，否则进程主动接受调度，进入睡眠状态，等待其它进程释放对设备的控制权。 </p>
<p sizset="48" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="48" sizcache="2">
    <tbody sizset="48" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>static int demo_open(struct inode *inode, struct file *file)
            {
            /* 申请中断，注册中断处理程序 */
            request_irq(card-&gt;irq, &amp;demo_interrupt, SA_SHIRQ,
            card_names[pci_id-&gt;driver_data], card)) {
            /* 检查读写模式 */
            if(file-&gt;f_mode &amp; FMODE_READ) {
            /* ... */
            }
            if(file-&gt;f_mode &amp; FMODE_WRITE) {
            /* ... */
            }
            /* 申请对设备的控制权 */
            down(&amp;card-&gt;open_sem);
            while(card-&gt;open_mode &amp; file-&gt;f_mode) {
            if (file-&gt;f_flags &amp; O_NONBLOCK) {
            /* NONBLOCK模式，返回-EBUSY */
            up(&amp;card-&gt;open_sem);
            return -EBUSY;
            } else {
            /* 等待调度，获得控制权 */
            card-&gt;open_mode |= f_mode &amp; (FMODE_READ | FMODE_WRITE);
            up(&amp;card-&gt;open_sem);
            /* 设备打开计数增1 */
            MOD_INC_USE_COUNT;
            /* ... */
            }
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p><a name=N1019B><span class=smalltitle><strong><font face=Arial>5. 数据读写和控制信息模块</font></strong></span></a></p>
<p>PCI设备驱动程序可以通过demo_fops 结构中的函数demo_ioctl( )，向应用程序提供对硬件进行控制的接口。例如，通过它可以从I/O寄存器里读取一个数据，并传送到用户空间里： </p>
<p sizset="49" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="49" sizcache="2">
    <tbody sizset="49" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>static int demo_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
            <!-- code sample is too wide -->{
            /* ... */
            switch(cmd) {
            case DEMO_RDATA:
            /* 从I/O端口读取4字节的数据 */
            val = inl(card-&gt;iobae + 0x10);
            /* 将读取的数据传输到用户空间 */
            return 0;
            }
            /* ... */
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>事实上，在demo_fops里还可以实现诸如demo_read( )、demo_mmap( )等操作，Linux内核源码中的driver目录里提供了许多设备驱动程序的源代码，找那里可以找到类似的例子。在对资源的访问方式上，除了有I/O指令以外，还有对外设I/O内存的访问。对这些内存的操作一方面可以通过把I/O内存重新映射后作为普通内存进行操作，另一方面也可以通过总线主DMA（Bus Master DMA）的方式让设备把数据通过DMA传送到系统内存中。 </p>
<p><a name=N101AE><span class=smalltitle><strong><font face=Arial>6. 中断处理模块</font></strong></span></a></p>
<p>PC的中断资源比较有限，只有0~15的中断号，因此大部分外部设备都是以共享的形式申请中断号的。当中断发生的时候，中断处理程序首先负责对中断进行识别，然后再做进一步的处理。 </p>
<p sizset="50" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="50" sizcache="2">
    <tbody sizset="50" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
            {
            struct demo_card *card = (struct demo_card *)dev_id;
            u32 status;
            spin_lock(&amp;card-&gt;lock);
            /* 识别中断 */
            status = inl(card-&gt;iobase + GLOB_STA);
            if(!(status &amp; INT_MASK))
            {
            spin_unlock(&amp;card-&gt;lock);
            return;  /* not for us */
            }
            /* 告诉设备已经收到中断 */
            outl(status &amp; INT_MASK, card-&gt;iobase + GLOB_STA);
            spin_unlock(&amp;card-&gt;lock);
            /* 其它进一步的处理，如更新DMA缓冲区指针等 */
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p><a name=N101BE><span class=smalltitle><strong><font face=Arial>7. 释放设备模块</font></strong></span></a></p>
<p>释放设备模块主要负责释放对设备的控制权，释放占用的内存和中断等，所做的事情正好与打开设备模块相反：</p>
<p sizset="51" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="51" sizcache="2">
    <tbody sizset="51" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>static int demo_release(struct inode *inode, struct file *file)
            {
            /* ... */
            /* 释放对设备的控制权 */
            card-&gt;open_mode &amp;= (FMODE_READ | FMODE_WRITE);
            /* 唤醒其它等待获取控制权的进程 */
            wake_up(&amp;card-&gt;open_wait);
            up(&amp;card-&gt;open_sem);
            /* 释放中断 */
            free_irq(card-&gt;irq, card);
            /* 设备打开计数增1 */
            MOD_DEC_USE_COUNT;
            /* ... */
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p><a name=N101CE><span class=smalltitle><strong><font face=Arial>8. 卸载设备模块</font></strong></span></a></p>
<p>卸载设备模块与初始化设备模块是相对应的，实现起来相对比较简单，主要是调用函数pci_unregister_driver( )从Linux内核中注销设备驱动程序： </p>
<p sizset="52" sizcache="2">
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="52" sizcache="2">
    <tbody sizset="52" sizcache="1">
        <tr>
            <td class=code-outline>
            <pre class=displaycode>static void __exit demo_cleanup_module (void)
            {
            pci_unregister_driver(&amp;demo_pci_driver);
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<br>
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="53" sizcache="2">
    <tbody sizset="53" sizcache="1">
        <tr>
            <td><img height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"><br><img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></td>
        </tr>
    </tbody>
</table>
<table class=no-print cellSpacing=0 cellPadding=0 align=right sizset="54" sizcache="2">
    <tbody sizset="55" sizcache="2">
        <tr align=right sizset="55" sizcache="2">
            <td sizset="55" sizcache="2"><img height=4 alt="" src="http://www.ibm.com/i/c.gif" width="100%"><br>
            <table cellSpacing=0 cellPadding=0 border=0 sizset="55" sizcache="2">
                <tbody sizset="55" sizcache="1">
                    <tr>
                        <td vAlign=center><img height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><br></td>
                        <td vAlign=top align=right><a class=fbox href="http://www.ibm.com/developerworks/cn/linux/l-pci/index.html#main" cmImpressionSent="1"><strong><u><font color=#800080>回页首</font></u></strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=4><span class=atitle>四、小结</span></a></p>
<p>PCI总线不仅是目前应用广泛的计算机总线标准，而且是一种兼容性最强、功能最全的计算机总线。而Linux作为一种新的操作系统，其发展前景是无法估量的，同时也为PCI总线与各种新型设备互连成为可能。由于Linux源码开放，因此给连接到PCI总线上的任何设备编写驱动程序变得相对容易。本文介绍如何编译Linux下的PCI驱动程序，针对的内核版本是2.4。 </p>
<br><br>
<p><a name=resources><span class=atitle>参考资料 </span></a></p>
<ul>
    <li>David A Rusling在 <a href="http://tldp.org/LDP/tlk/dd/pci.html" cmImpressionSent="1"><u><font color=#0000ff>The Linux Kernel</font></u></a>中对Linux的PCI子系统进行了比较详细的介绍。 <br><br>
    <li><a href="http://tldp.org/HOWTO/PCI-HOWTO.html" cmImpressionSent="1"><u><font color=#0000ff>Linux PCI-HOWTO</font></u></a>是了解Linux下PCI设备的最好读物。 <br><br>
    <li>毛德操，胡希明，Linux内核源代码情景分析，杭州：浙江大学出版社，2001<br><br>
    <li>Alessandro Rubini,，Linux Device Drivers（2nd Edition） USA：O&#8217;Reilly，2001<br><br>
    <li>Tomshanley，DonAderson，PCI系统结构（第四版），北京：电子工业出版社，2000<br></li>
</ul>
<br><br>
<p><a name=author><span class=atitle>关于作者</span></a></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0 sizset="56" sizcache="2">
    <tbody sizset="56" sizcache="1">
        <tr>
            <td colSpan=3><img height=5 alt="" src="http://www.ibm.com/i/c.gif" width="100%"></td>
        </tr>
        <tr vAlign=top align=left>
            <td>
            <p>&#160;</p>
            </td>
            <td><img height=5 alt="" src="http://www.ibm.com/i/c.gif" width=4></td>
            <td width="100%">
            <p>肖文鹏，北京理工大学计算机系的一名硕士研究生，主要从事操作系统和分布式计算环境的研究，喜爱Linux和Python。你可以通过 <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#120;&#105;&#97;&#111;&#119;&#112;&#64;&#50;&#54;&#51;&#46;&#110;&#101;&#116;&#63;&#99;&#99;&#61;" cmImpressionSent="1"><u><font color=#0000ff>xiaowp@263.net</font></u></a>与他取得联系。 </p>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/iniwf/aggbug/89053.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-07-02 11:19 <a href="http://www.cppblog.com/iniwf/archive/2009/07/02/89053.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux核心交互图 http://www.makelinux.net/kernel_map</title><link>http://www.cppblog.com/iniwf/archive/2009/03/17/76886.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Tue, 17 Mar 2009 10:12:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/03/17/76886.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/76886.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/03/17/76886.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/76886.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/76886.html</trackback:ping><description><![CDATA[<a href="http://www.makelinux.net/kernel_map">http://www.makelinux.net/kernel_map</a><br><br><img id=lkmi title="drag and zoom me" style="LEFT: 5px; WIDTH: 768px; CURSOR: move; POSITION: absolute; TOP: 84px; HEIGHT: 576px" height=576 alt="Linux kernel map" src="http://www.makelinux.net/kernel_map.d/LKM21_768.png" width=768 useMap=#idmap border=0> 
<img src ="http://www.cppblog.com/iniwf/aggbug/76886.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-03-17 18:12 <a href="http://www.cppblog.com/iniwf/archive/2009/03/17/76886.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Makefile学习教程: 跟我一起写 Makefile</title><link>http://www.cppblog.com/iniwf/archive/2009/03/16/76776.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Mon, 16 Mar 2009 13:30:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/03/16/76776.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/76776.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/03/16/76776.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/76776.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/76776.html</trackback:ping><description><![CDATA[作者:陈皓. 来源:<a href="http://www.csdn.net/" target=_top><u><font color=#4571d0>http://www.csdn.net</font></u></a>
<p>
<div class=twikiToc>
<ul>
    <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sa5Qtjlz.tcPw"><u><font color=#666666>Makefile学习教程: 跟我一起写 Makefile</font></u></a>
    <ul>
        <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sa5OOFabXjfoA"><u><font color=#666666>0 Makefile概述</font></u></a>
        <ul>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sawMNIbg0DwoM"><u><font color=#666666>0.1 关于程序的编译和链接</font></u></a> </li>
        </ul>
        <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sasS2ZeUsjr3E"><u><font color=#666666>1 Makefile 介绍</font></u></a>
        <ul>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saFo4MNg5pK8Y"><u><font color=#666666>1.1 Makefile的规则</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#safvghBu.bIEs"><u><font color=#666666>1.2 一个示例</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saUhuTfg0njOg"><u><font color=#666666>1.3 make是如何工作的</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sakYRADlySYSU"><u><font color=#666666>1.4 makefile中使用变量</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saI0kS8BRp2aM"><u><font color=#666666>1.5 让make自动推导</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saZz8bKL2yj32"><u><font color=#666666>1.6 另类风格的makefile</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sajVLYsI/Te76"><u><font color=#666666>1.7 清空目标文件的规则</font></u></a> </li>
        </ul>
        <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saUwmWQH9kI.I"><u><font color=#666666>2 Makefile 总述</font></u></a>
        <ul>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saE5AkeJJa502"><u><font color=#666666>2.1 Makefile里有什么？</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saMBWVx.XqyII"><u><font color=#666666>2.2Makefile的文件名</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#safRh/qN.aud2"><u><font color=#666666>2.3 引用其它的Makefile</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saJlQ/WyAfG6."><font color=#666666><u>2.4 环境变量 MAKEFILES </u></font></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sakmFYqQxnGBk"><u><font color=#666666>2.5 make的工作方式</font></u></a> </li>
        </ul>
        <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saiq29h0Ym1Bc"><u><font color=#666666>3 Makefile书写规则</font></u></a>
        <ul>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#samig.o8A6/iY"><u><font color=#666666>3.1 规则举例</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saRuj5N56iBD2"><u><font color=#666666>3.2 规则的语法</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saCyyQ9dQni8o"><u><font color=#666666>3.3 在规则中使用通配符</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sa.l9r9vXz/c2"><u><font color=#666666>3.4 文件搜寻</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sax9yWM/IDfvE"><u><font color=#666666>3.5 伪目标</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#savYQiB4DeEVQ"><u><font color=#666666>3.6 多目标</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sa0U2FeigPDZs"><u><font color=#666666>3.7 静态模式</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#sar/BOa9ebBdQ"><u><font color=#666666>3.8 自动生成依赖性</font></u></a> </li>
        </ul>
        <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saciXNKlLtQME"><u><font color=#666666>4 Makefile 书写命令</font></u></a>
        <ul>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saCADAE0R8KnA"><u><font color=#666666>4.1 显示命令</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saNGoM/Hp.BSg"><u><font color=#666666>4.2 命令执行</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saejriK4eEx6A"><u><font color=#666666>4.3 命令出错</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saRJsMKKuxk32"><u><font color=#666666>4.4 嵌套执行make</font></u></a>
            <li><a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#saVJ9lkWtLG16"><u><font color=#666666>4.5 定义命令包</font></u></a> </li>
        </ul>
        </li>
    </ul>
    </li>
</ul>
</div>
<h3><a name=sa5OOFabXjfoA></a>0 Makefile概述 </h3>
<hr>
什么是makefile？或许很多Winodws的程序员都不知道这个东西，因为那些Windows的IDE都为你做了这个工作，但我觉得要作一个好的和professional的程序员，makefile还是要懂。这就好像现在有这么多的HTML的编辑器，但如果你想成为一个专业人士，你还是要了解HTML的标识的含义。特别在Unix下的软件编译，你就不能不自己写makefile了，会不会写makefile，从一个侧面说明了一个人是否具备完成大型工程的能力。
<p>因为，makefile关系到了整个工程的编译规则。一个工程中的源文件不计数，其按类型、功能、模块分别放在若干个目录中，makefile定义了一系列的规则来指定，哪些文件需要先编译，哪些文件需要后编译，哪些文件需要重新编译，甚至于进行更复杂的功能操作，因为makefile就像一个Shell脚本一样，其中也可以执行操作系统的命令。
<p>makefile带来的好处就是——&#8220;自动化编译&#8221;，一旦写好，只需要一个make命令，整个工程完全自动编译，极大的提高了软件开发的效率。make是一个命令工具，是一个解释makefile中指令的命令工具，一般来说，大多数的IDE都有这个命令，比如：Delphi的make，Visual C++的nmake，Linux下GNU的make。可见，makefile都成为了一种在工程方面的编译方法。
<p>现在讲述如何写makefile的文章比较少，这是我想写这篇文章的原因。当然，不同产商的make各不相同，也有不同的语法，但其本质都是在&#8220;文件依赖性&#8221;上做文章，这里，我仅对GNU的make进行讲述，我的环境是RedHat Linux 8.0，make的版本是3.80。必竟，这个make是应用最为广泛的，也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的（POSIX.2）。
<p>在这篇文档中，将以C/C++的源码作为我们基础，所以必然涉及一些关于C/C++的编译的知识，相关于这方面的内容，还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。
<p>
<h4><a name=sawMNIbg0DwoM></a>0.1 关于程序的编译和链接 </h4>
在此，我想多说关于程序编译的一些规范和方法，一般来说，无论是C、C++、还是pas，首先要把源文件编译成中间代码文件，在Windows下也就是 .obj 文件，UNIX下是 .o 文件，即 Object File，这个动作叫做编译（compile）。然后再把大量的Object File合成执行文件，这个动作叫作链接（link）。
<p>编译时，编译器需要的是语法的正确，函数与变量的声明的正确。对于后者，通常是你需要告诉编译器头文件的所在位置（头文件中应该只是声明，而定义应该放在C/C++文件中），只要所有的语法正确，编译器就可以编译出中间目标文件。一般来说，每个源文件都应该对应于一个中间目标文件（O文件或是OBJ文件）。
<p>链接时，主要是链接函数和全局变量，所以，我们可以使用这些中间目标文件（O文件或是OBJ文件）来链接我们的应用程序。链接器并不管函数所在的源文件，只管函数的中间目标文件（Object File），在大多数时候，由于源文件太多，编译生成的中间目标文件太多，而在链接时需要明显地指出中间目标文件名，这对于编译很不方便，所以，我们要给中间目标文件打个包，在Windows下这种包叫&#8220;库文件&#8221;（Library File)，也就是 .lib 文件，在UNIX下，是Archive File，也就是 .a 文件。
<p>总结一下，源文件首先会生成中间目标文件，再由中间目标文件生成执行文件。在编译时，编译器只检测程序语法，和函数、变量是否被声明。如果函数未被声明，编译器会给出一个警告，但可以生成Object File。而在链接程序时，链接器会在所有的Object File中找寻函数的实现，如果找不到，那到就会报链接错误码（Linker Error），在VC下，这种错误一般是：Link 2001错误，意思说是说，链接器未能找到函数的实现。你需要指定函数的Object File.
<p>好，言归正传，GNU的make有许多的内容，闲言少叙，还是让我们开始吧。
<p>
<h3><a name=sasS2ZeUsjr3E></a>1 Makefile 介绍 </h3>
<hr>
make命令执行时，需要一个 Makefile 文件，以告诉make命令需要怎么样的去编译和链接程序。
<p>首先，我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册，在这个示例中，我们的工程有8个C文件，和3个头文件，我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是：
<ol>
    <li>如果这个工程没有编译过，那么我们的所有C文件都要编译并被链接。
    <li>如果这个工程的某几个C文件被修改，那么我们只编译被修改的C文件，并链接目标程序。
    <li>如果这个工程的头文件被改变了，那么我们需要编译引用了这几个头文件的C文件，并链接目标程序。 </li>
</ol>
<p>只要我们的Makefile写得够好，所有的这一切，我们只用一个make命令就可以完成，make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译，从而自己编译所需要的文件和链接目标程序。
<h4><a name=saFo4MNg5pK8Y></a>1.1 Makefile的规则 </h4>
在讲述这个Makefile之前，还是让我们先来粗略地看一看Makefile的规则。
<pre style="PADDING-BOTTOM: 0px">target ... : prerequisites ...
command
...
...
</pre>
target也就是一个目标文件，可以是Object File，也可以是执行文件。还可以是一个标签（Label），对于标签这种特性，在后续的&#8220;伪目标&#8221;章节中会有叙述。
<p>prerequisites就是，要生成那个target所需要的文件或是目标。
<p>command也就是make需要执行的命令。（任意的Shell命令）
<p>这是一个文件的依赖关系，也就是说，target这一个或多个的目标文件依赖于prerequisites中的文件，其生成规则定义在command中。说白一点就是说，prerequisites中如果有一个以上的文件比target文件要新的话，command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
<p>说到底，Makefile的东西就是这样一点，好像我的这篇文档也该结束了。呵呵。还不尽然，这是Makefile的主线和核心，但要写好一个Makefile还不够，我会以后面一点一点地结合我的工作经验给你慢慢到来。内容还多着呢。：）
<h4><a name=safvghBu.bIEs></a>1.2 一个示例 </h4>
正如前面所说的，如果一个工程有3个头文件，和8个C文件，我们为了完成前面所述的那三个规则，我们的Makefile应该是下面的这个样子的。
<pre style="PADDING-BOTTOM: 0px">    edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
</pre>
反斜杠（\）是换行符的意思。这样比较便于Makefile的易读。我们可以把这个内容保存在文件为&#8220;Makefile&#8221;或&#8220;makefile&#8221;的文件中，然后在该目录下直接输入命令&#8220;make&#8221;就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件，那么，只要简单地执行一下&#8220;make clean&#8221;就可以了。
<p>在这个makefile中，目标文件（target）包含：执行文件edit和中间目标文件（*.o），依赖文件（prerequisites）就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件，而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的，换言之，目标文件是哪些文件更新的。
<p>在定义好依赖关系后，后续的那一行定义了如何生成目标文件的操作系统命令，一定要以一个Tab键作为开头。记住，make并不管命令是怎么工作的，他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期，如果prerequisites文件的日期要比targets文件的日期要新，或者target不存在的话，那么，make就会执行后续定义的命令。
<p>这里要说明一点的是，clean不是一个文件，它只不过是一个动作名字，有点像C语言中的lable一样，其冒号后什么也没有，那么，make就不会自动去找文件的依赖性，也就不会自动执行其后所定义的命令。要执行其后的命令，就要在make命令后明显得指出这个lable的名字。这样的方法非常有用，我们可以在一个makefile中定义不用的编译或是和编译无关的命令，比如程序的打包，程序的备份，等等。
<p>
<h4><a name=saUhuTfg0njOg></a>1.3 make是如何工作的 </h4>
在默认的方式下，也就是我们只输入make命令。那么，
<p>
<ol>
    <li>make会在当前目录下找名字叫&#8220;Makefile&#8221;或&#8220;makefile&#8221;的文件。
    <li>如果找到，它会找文件中的第一个目标文件（target），在上面的例子中，他会找到&#8220;edit&#8221;这个文件，并把这个文件作为最终的目标文件。
    <li>如果edit文件不存在，或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新，那么，他就会执行后面所定义的命令来生成edit这个文件。
    <li>如果edit所依赖的.o文件也存在，那么make会在当前文件中找目标为.o文件的依赖性，如果找到则再根据那一个规则生成.o文件。（这有点像一个堆栈的过程）
    <li>当然，你的C文件和H文件是存在的啦，于是make会生成 .o 文件，然后再用 .o 文件生命make的终极任务，也就是执行文件edit了。 </li>
</ol>
<p>这就是整个make的依赖性，make会一层又一层地去找文件的依赖关系，直到最终编译出第一个目标文件。在找寻的过程中，如果出现错误，比如最后被依赖的文件找不到，那么make就会直接退出，并报错，而对于所定义的命令的错误，或是编译不成功，make根本不理。make只管文件的依赖性，即，如果在我找了依赖关系之后，冒号后面的文件还是不在，那么对不起，我就不工作啦。
<p>通过上述分析，我们知道，像clean这种，没有被第一个目标文件直接或间接关联，那么它后面所定义的命令将不会被自动执行，不过，我们可以显示要make执行。即命令——&#8220;make clean&#8221;，以此来清除所有的目标文件，以便重编译。
<p>于是在我们编程中，如果这个工程已被编译过了，当我们修改了其中一个源文件，比如file.c，那么根据我们的依赖性，我们的目标file.o会被重编译（也就是在这个依性关系后面所定义的命令），于是file.o的文件也是最新的啦，于是file.o的文件修改时间要比edit要新，所以edit也会被重新链接了（详见edit目标文件后定义的命令）。
<p>而如果我们改变了&#8220;command.h&#8221;，那么，kdb.o、command.o和files.o都会被重编译，并且，edit会被重链接。
<h4><a name=sakYRADlySYSU></a>1.4 makefile中使用变量 </h4>
在上面的例子中，先让我们看看edit的规则：
<pre style="PADDING-BOTTOM: 0px">      edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
</pre>
我们可以看到[.o]文件的字符串被重复了两次，如果我们的工程需要加入一个新的[.o]文件，那么我们需要在两个地方加（应该是三个地方，还有一个地方在clean中）。当然，我们的makefile并不复杂，所以在两个地方加也不累，但如果makefile变得复杂，那么我们就有可能会忘掉一个需要加入的地方，而导致编译失败。所以，为了makefile的易维护，在makefile中我们可以使用变量。makefile的变量也就是一个字符串，理解成C语言中的宏可能会更好。
<p>比如，我们声明一个变量，叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ，反正不管什么啦，只要能够表示obj文件就行了。我们在makefile一开始就这样定义： </p>
<pre style="PADDING-BOTTOM: 0px">     objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
</pre>
于是，我们就可以很方便地在我们的makefile中以&#8220;$(objects)&#8221;的方式来使用这个变量了，于是我们的改良版makefile就变成下面这个样子：
<pre style="PADDING-BOTTOM: 0px">    objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
</pre>
<p>于是如果有新的 .o 文件加入，我们只需简单地修改一下 objects 变量就可以了。
<p>关于变量更多的话题，我会在后续给你一一道来。
<h4><a name=saI0kS8BRp2aM></a>1.5 让make自动推导 </h4>
GNU的make很强大，它可以自动推导文件以及文件依赖关系后面的命令，于是我们就没必要去在每一个[.o]文件后都写上类似的命令，因为，我们的make会自动识别，并自己推导命令。
<p>只要make看到一个[.o]文件，它就会自动的把[.c]文件加在依赖关系中，如果make找到一个whatever.o，那么whatever.c，就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来，于是，我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。 </p>
<pre style="PADDING-BOTTOM: 0px">    objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
</pre>
这种方法，也就是make的&#8220;隐晦规则&#8221;。上面文件内容中，&#8220;.PHONY&#8221;表示，clean是个伪目标文件。
<p>关于更为详细的&#8220;隐晦规则&#8221;和&#8220;伪目标文件&#8221;，我会在后续给你一一道来。
<h4><a name=saZz8bKL2yj32></a>1.6 另类风格的makefile </h4>
即然我们的make可以自动推导命令，那么我看到那堆[.o]和[.h]的依赖就有点不爽，那么多的重复的[.h]，能不能把其收拢起来，好吧，没有问题，这个对于make来说很容易，谁叫它提供了自动推导命令和文件的功能呢？来看看最新风格的makefile吧。
<pre style="PADDING-BOTTOM: 0px">    objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
rm edit $(objects)
</pre>
这种风格，让我们的makefile变得很简单，但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这种风格的，一是文件的依赖关系看不清楚，二是如果文件一多，要加入几个新的.o文件，那就理不清楚了。
<h4><a name=sajVLYsI/Te76></a>1.7 清空目标文件的规则 </h4>
每个Makefile中都应该写一个清空目标文件（.o和执行文件）的规则，这不仅便于重编译，也很利于保持文件的清洁。这是一个&#8220;修养&#8221;（呵呵，还记得我的《编程修养》吗）。一般的风格都是：
<pre style="PADDING-BOTTOM: 0px">        clean:
rm edit $(objects)
</pre>
更为稳健的做法是：
<pre style="PADDING-BOTTOM: 0px">        .PHONY : clean
clean :
-rm edit $(objects)
</pre>
前面说过，.PHONY意思表示clean是一个&#8220;伪目标&#8221;，。而在rm命令前面加了一个小减号的意思就是，也许某些文件出现问题，但不要管，继续做后面的事。当然，clean的规则不要放在文件的开头，不然，这就会变成make的默认目标，相信谁也不愿意这样。不成文的规矩是——&#8220;clean从来都是放在文件的最后&#8221;。
<p>
<p>上面就是一个makefile的概貌，也是makefile的基础，下面还有很多makefile的相关细节，准备好了吗？准备好了就来。
<hr>
<h3><a name=saUwmWQH9kI.I></a>2 Makefile 总述 </h3>
<h4><a name=saE5AkeJJa502></a>2.1 Makefile里有什么？ </h4>
Makefile里主要包含了五个东西：显式规则、隐晦规则、变量定义、文件指示和注释。
<p>
<ol>
    <li>显式规则。显式规则说明了，如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出，要生成的文件，文件的依赖文件，生成的命令。
    <li>隐晦规则。由于我们的make有自动推导的功能，所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile，这是由make所支持的。
    <li>变量的定义。在Makefile中我们要定义一系列的变量，变量一般都是字符串，这个有点你C语言中的宏，当Makefile被执行时，其中的变量都会被扩展到相应的引用位置上。
    <li>文件指示。其包括了三个部分，一个是在一个Makefile中引用另一个Makefile，就像C语言中的include一样；另一个是指根据某些情况指定Makefile中的有效部分，就像C语言中的预编译#if一样；还有就是定义一个多行的命令。有关这一部分的内容，我会在后续的部分中讲述。
    <li>注释。Makefile中只有行注释，和UNIX的Shell脚本一样，其注释是用&#8220;#&#8221;字符，这个就像C/C++中的&#8220;//&#8221;一样。如果你要在你的Makefile中使用&#8220;#&#8221;字符，可以用反斜框进行转义，如：&#8220;\#&#8221;。 </li>
</ol>
<p>最后，还值得一提的是，在Makefile中的命令，必须要以[Tab]键开始。
<h4><a name=saMBWVx.XqyII></a>2.2Makefile的文件名 </h4>
默认的情况下，make命令会在当前目录下按顺序找寻文件名为&#8220;GNUmakefile&#8221;、&#8220;makefile&#8221;、&#8220;Makefile&#8221;的文件，找到了解释这个文件。在这三个文件名中，最好使用&#8220;Makefile&#8221;这个文件名，因为，这个文件名第一个字符为大写，这样有一种显目的感觉。最好不要用&#8220;GNUmakefile&#8221;，这个文件是GNU的make识别的。有另外一些make只对全小写的&#8220;makefile&#8221;文件名敏感，但是基本上来说，大多数的make都支持&#8220;makefile&#8221;和&#8220;Makefile&#8221;这两种默认文件名。
<p>当然，你可以使用别的文件名来书写Makefile，比如：&#8220;Make.Linux&#8221;，&#8220;Make.Solaris&#8221;，&#8220;Make.AIX&#8221;等，如果要指定特定的Makefile，你可以使用make的&#8220;-f&#8221;和&#8220;--file&#8221;参数，如：make -f Make.Linux或make --file Make.AIX。
<h4><a name=safRh/qN.aud2></a>2.3 引用其它的Makefile </h4>
在Makefile使用include关键字可以把别的Makefile包含进来，这很像C语言的#include，被包含的文件会原模原样的放在当前文件的包含位置。include的语法是：
<div class=BeautifierPlugin>
<div class=fragment>
<pre style="PADDING-BOTTOM: 0px">
<pre style="PADDING-BOTTOM: 0px">include &lt;filename&gt;</pre>
</pre>
</div>
</div>
filename可以是当前操作系统Shell的文件模式（可以保含路径和通配符）
<p>在include前面可以有一些空字符，但是绝不能是[Tab]键开始。include和<filename>可以用一个或多个空格隔开。举个例子，你有这样几个Makefile：a.mk、b.mk、c.mk，还有一个文件叫foo.make，以及一个变量$(bar)，其包含了e.mk和f.mk，那么，下面的语句： </p>
<pre style="PADDING-BOTTOM: 0px">    include foo.make *.mk $(bar)
</pre>
等价于：
<pre style="PADDING-BOTTOM: 0px">    include foo.make a.mk b.mk c.mk e.mk f.mk
</pre>
make命令开始时，会把找寻include所指出的其它Makefile，并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话，make会在当前目录下首先寻找，如果当前目录下没有找到，那么，make还会在下面的几个目录下找：
<p>
<ol>
    <li>如果make执行时，有&#8220;-I&#8221;或&#8220;--include-dir&#8221;参数，那么make就会在这个参数所指定的目录下去寻找。
    <li>如果目录
    <prefix>/include（一般是：/usr/local/bin或/usr/include）存在的话，make也会去找。 </li>
</ol>
<p>如果有文件没有找到的话，make会生成一条警告信息，但不会马上出现致命错误。它会继续载入其它的文件，一旦完成makefile的读取，make会再重试这些没有找到，或是不能读取的文件，如果还是不行，make才会出现一条致命信息。如果你想让make不理那些无法读取的文件，而继续执行，你可以在include前加一个减号&#8220;-&#8221;。如：
<div class=BeautifierPlugin>
<div class=fragment>
<pre style="PADDING-BOTTOM: 0px">
<pre style="PADDING-BOTTOM: 0px">-include &lt;filename&gt;</pre>
</pre>
</div>
</div>
其表示，无论include过程中出现什么错误，都不要报错继续执行。和其它版本make兼容的相关命令是sinclude，其作用和这一个是一样的。
<h4><a name=saJlQ/WyAfG6.></a>2.4 环境变量 MAKEFILES </h4>
如果你的当前环境中定义了环境变量MAKEFILES，那么，make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile，用空格分隔。只是，它和include不同的是，从这个环境变中引入的Makefile的&#8220;目标&#8221;不会起作用，如果环境变量中定义的文件发现错误，make也会不理。
<p>但是在这里我还是建议不要使用这个环境变量，因为只要这个变量一被定义，那么当你使用make时，所有的Makefile都会受到它的影响，这绝不是你想看到的。在这里提这个事，只是为了告诉大家，也许有时候你的Makefile出现了怪事，那么你可以看看当前环境中有没有定义这个变量。
<h4><a name=sakmFYqQxnGBk></a>2.5 make的工作方式 </h4>
GNU的make工作时的执行步骤入下：（想来其它的make也是类似）
<p>
<ol>
    <li>读入所有的Makefile。
    <li>读入被include的其它Makefile。
    <li>初始化文件中的变量。
    <li>推导隐晦规则，并分析所有规则。
    <li>为所有的目标文件创建依赖关系链。
    <li>根据依赖关系，决定哪些目标要重新生成。
    <li>执行生成命令。 </li>
</ol>
<p>1-5步为第一个阶段，6-7为第二个阶段。第一个阶段中，如果定义的变量被使用了，那么，make会把其展开在使用的位置。但make并不会完全马上展开，make使用的是拖延战术，如果变量出现在依赖关系的规则中，那么仅当这条依赖被决定要使用了，变量才会在其内部展开。
<p>当然，这个工作方式你不一定要清楚，但是知道这个方式你也会对make更为熟悉。有了这个基础，后续部分也就容易看懂了。
<h3><a name=saiq29h0Ym1Bc></a>3 Makefile书写规则 </h3>
<hr>
规则包含两个部分，一个是依赖关系，一个是生成目标的方法。
<p>在Makefile中，规则的顺序是很重要的，因为，Makefile中只应该有一个最终目标，其它的目标都是被这个目标所连带出来的，所以一定要让make知道你的最终目标是什么。一般来说，定义在Makefile中的目标可能会有很多，但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个，那么，第一个目标会成为最终的目标。make所完成的也就是这个目标。
<p>好了，还是让我们来看一看如何书写规则。
<h4><a name=samig.o8A6/iY></a>3.1 规则举例 </h4>
<pre style="PADDING-BOTTOM: 0px"> foo.o : foo.c defs.h       # foo模块
cc -c -g foo.c
</pre>
看到这个例子，各位应该不是很陌生了，前面也已说过，foo.o是我们的目标，foo.c和defs.h是目标所依赖的源文件，而只有一个命令&#8220;cc -c -g foo.c&#8221;（以Tab键开头）。这个规则告诉我们两件事：
<p>
<ol>
    <li>文件的依赖关系，foo.o依赖于foo.c和defs.h的文件，如果foo.c和defs.h的文件日期要比foo.o文件日期要新，或是foo.o不存在，那么依赖关系发生。
    <li>如果生成（或更新）foo.o文件。也就是那个cc命令，其说明了，如何生成foo.o这个文件。（当然foo.c文件include了defs.h文件） </li>
</ol>
<p>
<h4><a name=saRuj5N56iBD2></a>3.2 规则的语法 </h4>
<pre style="PADDING-BOTTOM: 0px">      targets : prerequisites
command
...
</pre>
或是这样：
<pre style="PADDING-BOTTOM: 0px">      targets : prerequisites ; command
command
...
</pre>
targets是文件名，以空格分开，可以使用通配符。一般来说，我们的目标基本上是一个文件，但也有可能是多个文件。
<p>command是命令行，如果其不与&#8220;target:prerequisites&#8221;在一行，那么，必须以[Tab键]开头，如果和prerequisites在一行，那么可以用分号做为分隔。（见上）
<p>prerequisites也就是目标所依赖的文件（或依赖目标）。如果其中的某个文件要比目标文件要新，那么，目标就被认为是&#8220;过时的&#8221;，被认为是需要重生成的。这个在前面已经讲过了。
<p>如果命令太长，你可以使用反斜框（&#8216;\&#8217;）作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事，文件的依赖关系和如何成成目标文件。
<p>一般来说，make会以UNIX的标准Shell，也就是/bin/sh来执行命令。
<h4><a name=saCyyQ9dQni8o></a>3.3 在规则中使用通配符 </h4>
<p>如果我们想定义一系列比较类似的文件，我们很自然地就想起使用通配符。make支持三各通配符：&#8220;*&#8221;，&#8220;?&#8221;和&#8220;[...]&#8221;。这是和Unix的B-Shell是相同的。
<p>波浪号（&#8220;~&#8221;）字符在文件名中也有比较特殊的用途。如果是&#8220;~/test&#8221;，这就表示当前用户的$HOME目录下的test目录。而&#8220;~hchen/test&#8221;则表示用户hchen的宿主目录下的test目录。（这些都是Unix下的小知识了，make也支持）而在Windows或是MS-DOS下，用户没有宿主目录，那么波浪号所指的目录则根据环境变量&#8220;HOME&#8221;而定。
<p>通配符代替了你一系列的文件，如&#8220;*.c&#8221;表示所以后缀为c的文件。一个需要我们注意的是，如果我们的文件名中有通配符，如：&#8220;*&#8221;，那么可以用转义字符&#8220;\&#8221;，如&#8220;\*&#8221;来表示真实的&#8220;*&#8221;字符，而不是任意长度的字符串。
<p>好吧，还是先来看几个例子吧： </p>
<pre style="PADDING-BOTTOM: 0px">    clean:
rm -f *.o
</pre>
上面这个例子我不不多说了，这是操作系统Shell所支持的通配符。这是在命令中的通配符。
<pre style="PADDING-BOTTOM: 0px">    print: *.c
lpr -p $?
touch print
</pre>
上面这个例子说明了通配符也可以在我们的规则中，目标print依赖于所有的[.c]文件。其中的&#8220;$?&#8221;是一个自动化变量，我会在后面给你讲述。
<pre style="PADDING-BOTTOM: 0px">    objects = *.o
</pre>
上面这个例子，表示了，通符同样可以用在变量中。并不是说[*.o]会展开，不！objects的值就是&#8220;*.o&#8221;。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开，也就是让objects的值是所有[.o]的文件名的集合，那么，你可以这样：
<pre style="PADDING-BOTTOM: 0px">    objects := $(wildcard *.o)
</pre>
这种用法由关键字&#8220;wildcard&#8221;指出，关于Makefile的关键字，我们将在后面讨论。
<h4><a name=sa.l9r9vXz/c2></a>3.4 文件搜寻 </h4>
<p>在一些大的工程中，有大量的源文件，我们通常的做法是把这许多的源文件分类，并存放在不同的目录中。所以，当make需要去找寻文件的依赖关系时，你可以在文件前加上路径，但最好的方法是把一个路径告诉make，让make在自动去找。
<p>Makefile文件中的特殊变量&#8220;VPATH&#8221;就是完成这个功能的，如果没有指明这个变量，make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量，那么，make就会在当当前目录找不到的情况下，到所指定的目录中去找寻文件了。 </p>
<pre style="PADDING-BOTTOM: 0px">    VPATH = src:../headers
</pre>
上面的的定义指定两个目录，&#8220;src&#8221;和&#8220;../headers&#8221;，make会按照这个顺序进行搜索。目录由&#8220;冒号&#8221;分隔。（当然，当前目录永远是最高优先搜索的地方）
<p>另一个设置文件搜索路径的方法是使用make的&#8220;vpath&#8221;关键字（注意，它是全小写的），这不是变量，这是一个make的关键字，这和上面提到的那个VPATH变量很类似，但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种：
<ol>
    <li>vpath &lt; pattern&gt; &lt; directories&gt; <br>为符合模式&lt; pattern&gt;的文件指定搜索目录&lt; directories&gt;。
    <li>vpath &lt; pattern&gt;<br>清除符合模式&lt; pattern&gt;的文件的搜索目录。
    <li>vpath <br>清除所有已被设置好了的文件搜索目录。 </li>
</ol>
<p>vapth使用方法中的&lt; pattern&gt;需要包含&#8220;%&#8221;字符。&#8220;%&#8221;的意思是匹配零或若干字符，例如，&#8220;%.h&#8221;表示所有以&#8220;.h&#8221;结尾的文件。&lt; pattern&gt;指定了要搜索的文件集，而&lt; directories&gt;则指定了
<pattern>的文件集的搜索的目录。例如： </p>
<pre style="PADDING-BOTTOM: 0px">    vpath %.h ../headers
</pre>
该语句表示，要求make在&#8220;../headers&#8221;目录下搜索所有以&#8220;.h&#8221;结尾的文件。（如果某文件在当前目录没有找到的话）
<p>我们可以连续地使用vpath语句，以指定不同搜索策略。如果连续的vpath语句中出现了相同的&lt; pattern&gt;，或是被重复了的&lt; pattern&gt;，那么，make会按照vpath语句的先后顺序来执行搜索。如： </p>
<pre style="PADDING-BOTTOM: 0px">    vpath %.c foo
vpath %   blish
vpath %.c bar
</pre>
其表示&#8220;.c&#8221;结尾的文件，先在&#8220;foo&#8221;目录，然后是&#8220;blish&#8221;，最后是&#8220;bar&#8221;目录。
<pre style="PADDING-BOTTOM: 0px">    vpath %.c foo:bar
vpath %   blish
</pre>
而上面的语句则表示&#8220;.c&#8221;结尾的文件，先在&#8220;foo&#8221;目录，然后是&#8220;bar&#8221;目录，最后才是&#8220;blish&#8221;目录。
<h4><a name=sax9yWM/IDfvE></a>3.5 伪目标 </h4>
<p>最早先的一个例子中，我们提到过一个&#8220;clean&#8221;的目标，这是一个&#8220;伪目标&#8221;， </p>
<pre style="PADDING-BOTTOM: 0px">    clean:
rm *.o temp
</pre>
正像我们前面例子中的&#8220;clean&#8221;一样，即然我们生成了许多文件编译文件，我们也应该提供一个清除它们的&#8220;目标&#8221;以备完整地重编译而用。 （以&#8220;make clean&#8221;来使用该目标）
<p>因为，我们并不生成&#8220;clean&#8221;这个文件。&#8220;伪目标&#8221;并不是一个文件，只是一个标签，由于&#8220;伪目标&#8221;不是文件，所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个&#8220;目标&#8221;才能让其生效。当然，&#8220;伪目标&#8221;的取名不能和文件名重名，不然其就失去了&#8220;伪目标&#8221;的意义了。
<p>当然，为了避免和文件重名的这种情况，我们可以使用一个特殊的标记&#8220;.PHONY&#8221;来显示地指明一个目标是&#8220;伪目标&#8221;，向make说明，不管是否有这个文件，这个目标就是&#8220;伪目标&#8221;。 </p>
<pre style="PADDING-BOTTOM: 0px">    .PHONY : clean
</pre>
只要有这个声明，不管是否有&#8220;clean&#8221;文件，要运行&#8220;clean&#8221;这个目标，只有&#8220;make clean&#8221;这样。于是整个过程可以这样写：
<pre style="PADDING-BOTTOM: 0px">     .PHONY: clean
clean:
rm *.o temp
</pre>
伪目标一般没有依赖的文件。但是，我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为&#8220;默认目标&#8221;，只要将其放在第一个。一个示例就是，如果你的Makefile需要一口气生成若干个可执行文件，但你只想简单地敲一个make完事，并且，所有的目标文件都写在一个Makefile中，那么你可以使用&#8220;伪目标&#8221;这个特性：
<pre style="PADDING-BOTTOM: 0px">    all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
</pre>
我们知道，Makefile中的第一个目标会被作为其默认目标。我们声明了一个&#8220;all&#8221;的伪目标，其依赖于其它三个目标。由于伪目标的特性是，总是被执行的，所以其依赖的那三个目标就总是不如&#8220;all&#8221;这个目标新。所以，其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。&#8220;.PHONY : all&#8221;声明了&#8220;all&#8221;这个目标为&#8220;伪目标&#8221;。
<p>随便提一句，从上面的例子我们可以看出，目标也可以成为依赖。所以，伪目标同样也可成为依赖。看下面的例子： </p>
<pre style="PADDING-BOTTOM: 0px">    .PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
</pre>
&#8220;make clean&#8221;将清除所有要被清除的文件。&#8220;cleanobj&#8221;和&#8220;cleandiff&#8221;这两个伪目标有点像&#8220;子程序&#8221;的意思。我们可以输入&#8220;make cleanall&#8221;和&#8220;make cleanobj&#8221;和&#8220;make cleandiff&#8221;命令来达到清除不同种类文件的目的
<p>
<h4><a name=savYQiB4DeEVQ></a>3.6 多目标 </h4>
<p>Makefile的规则中的目标可以不止一个，其支持多目标，有可能我们的多个目标同时依赖于一个文件，并且其生成的命令大体类似。于是我们就能把其合并起来。当然，多个目标的生成规则的执行命令是同一个，这可能会可我们带来麻烦，不过好在我们的可以使用一个自动化变量&#8220;$@&#8221;（关于自动化变量，将在后面讲述），这个变量表示着目前规则中所有的目标的集合，这样说可能很抽象，还是看一个例子吧。 </p>
<pre style="PADDING-BOTTOM: 0px">    bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) &gt; $@
<pre style="PADDING-BOTTOM: 0px">    上述规则等价于：
</pre>
bigoutput : text.g
generate text.g -big &gt; bigoutput
littleoutput : text.g
generate text.g -little &gt; littleoutput
</pre>
其中，-$(subst output,,$@)中的&#8220;$&#8221;表示执行一个Makefile的函数，函数名为subst，后面的为参数。关于函数，将在后面讲述。这里的这个函数是截取字符串的意思，&#8220;$@&#8221;表示目标的集合，就像一个数组，&#8220;$@&#8221;依次取出目标，并执于命令。
<p>
<p>
<h4><a name=sa0U2FeigPDZs></a>3.7 静态模式 </h4>
<p>静态模式可以更加容易地定义多目标的规则，可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法：
<div class=BeautifierPlugin>
<div class=fragment>
<pre style="PADDING-BOTTOM: 0px">
<pre style="PADDING-BOTTOM: 0px">&lt;targets ...&gt;: &lt;target-pattern&gt;: &lt;prereq-patterns ...&gt;
　　　&lt;commands&gt;
...</pre>
</pre>
</div>
</div>
<p>targets定义了一系列的目标文件，可以有通配符。是目标的一个集合。
<p>target-parrtern是指明了targets的模式，也就是的目标集模式。
<p>prereq-parrterns是目标的依赖模式，它对target-parrtern形成的模式再进行一次依赖目标的定义。
<p>这样描述这三个东西，可能还是没有说清楚，还是举个例子来说明一下吧。如果我们的&lt;target-parrtern&gt;定义成&#8220;%.o&#8221;，意思是我们的<target>集合中都是以&#8220;.o&#8221;结尾的，而如果我们的&lt;prereq-parrterns&gt;定义成&#8220;%.c&#8221;，意思是对&lt;target-parrtern&gt;所形成的目标集进行二次定义，其计算方法是，取&lt;target-parrtern&gt;模式中的&#8220;%&#8221;（也就是去掉了[.o]这个结尾），并为其加上[.c]这个结尾，形成的新集合。
<p>所以，我们的&#8220;目标模式&#8221;或是&#8220;依赖模式&#8221;中都应该有&#8220;%&#8221;这个字符，如果你的文件名中有&#8220;%&#8221;那么你可以使用反斜杠&#8220;\&#8221;进行转义，来标明真实的&#8220;%&#8221;字符。
<p>看一个例子： </p>
<pre style="PADDING-BOTTOM: 0px">    objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $&lt; -o $@
</pre>
上面的例子中，指明了我们的目标从$object中获取，&#8220;%.o&#8221;表明要所有以&#8220;.o&#8221;结尾的目标，也就是&#8220;foo.o bar.o&#8221;，也就是变量$object集合的模式，而依赖模式&#8220;%.c&#8221;则取模式&#8220;%.o&#8221;的&#8220;%&#8221;，也就是&#8220;foo bar&#8221;，并为其加下&#8220;.c&#8221;的后缀，于是，我们的依赖目标就是&#8220;foo.c bar.c&#8221;。而命令中的&#8220;$&lt;&#8221;和&#8220;$@&#8221;则是自动化变量，&#8220;$&lt;&#8221;表示所有的依赖目标集（也就是&#8220;foo.c bar.c&#8221;），&#8220;$@&#8221;表示目标集（也褪恰癴oo.o bar.o&#8221;）。于是，上面的规则展开后等价于下面的规则：
<pre style="PADDING-BOTTOM: 0px">    foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
</pre>
试想，如果我们的&#8220;%.o&#8221;有几百个，那种我们只要用这种很简单的&#8220;静态模式规则&#8221;就可以写完一堆规则，实在是太有效率了。&#8220;静态模式规则&#8221;的用法很灵活，如果用得好，那会一个很强大的功能。再看一个例子：
<pre style="PADDING-BOTTOM: 0px">    files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $&lt; -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $&lt;
</pre>
<p>$(filter %.o,$(files))表示调用Makefile的filter函数，过滤&#8220;$filter&#8221;集，只要其中模式为&#8220;%.o&#8221;的内容。其的它内容，我就不用多说了吧。这个例字展示了Makefile中更大的弹性。
<h4><a name=sar/BOa9ebBdQ></a>3.8 自动生成依赖性 </h4>
<p>在Makefile中，我们的依赖关系可能会需要包含一系列的头文件，比如，如果我们的main.c中有一句&#8220;#include "defs.h"&#8221;，那么我们的依赖关系应该是： </p>
<pre style="PADDING-BOTTOM: 0px">    main.o : main.c defs.h
</pre>
但是，如果是一个比较大型的工程，你必需清楚哪些C文件包含了哪些头文件，并且，你在加入或删除头文件时，也需要小心地修改Makefile，这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情，我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个&#8220;-M&#8221;的选项，即自动找寻源文件中包含的头文件，并生成一个依赖关系。例如，如果我们执行下面的命令：
<pre style="PADDING-BOTTOM: 0px">    cc -M main.c
</pre>
其输出是：
<pre style="PADDING-BOTTOM: 0px">    main.o : main.c defs.h
</pre>
于是由编译器自动生成的依赖关系，这样一来，你就不必再手动书写若干文件的依赖关系，而由编译器自动生成了。需要提醒一句的是，如果你使用GNU的C/C++编译器，你得用&#8220;-MM&#8221;参数，不然，&#8220;-M&#8221;参数会把一些标准库的头文件也包含进来。
<p>gcc -M main.c的输出是： </p>
<pre style="PADDING-BOTTOM: 0px">    main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
/usr/include/bits/sched.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/include/bits/wchar.h /usr/include/gconv.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
/usr/include/bits/stdio_lim.h
</pre>
gcc -MM main.c的输出则是：
<pre style="PADDING-BOTTOM: 0px">    main.o: main.c defs.h
</pre>
那么，编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来，我们的Makefile也要根据这些源文件重新生成，让Makefile自已依赖于源文件？这个功能并不现实，不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中，为每一个&#8220;name.c&#8221;的文件都生成一个&#8220;name.d&#8221;的Makefile文件，[.d]文件中就存放对应[.c]文件的依赖关系。
<p>于是，我们可以写出[.c]文件和[.d]文件的依赖关系，并让make自动更新或自成[.d]文件，并把其包含在我们的主Makefile中，这样，我们就可以自动化地生成每个文件的依赖关系了。
<p>这里，我们给出了一个模式规则来产生[.d]文件： </p>
<pre style="PADDING-BOTTOM: 0px">    %.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $&lt; &gt; $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' &lt; $@.$$$$ &gt; $@; \
rm -f $@.$$$$
</pre>
<p>这个规则的意思是，所有的[.d]文件依赖于[.c]文件，&#8220;rm -f $@&#8221;的意思是删除所有的目标，也就是[.d]文件，第二行的意思是，为每个依赖文件&#8220;$&lt;&#8221;，也就是[.c]文件生成依赖文件，&#8220;$@&#8221;表示模式&#8220;%.d&#8221;文件，如果有一个C文件是name.c，那么&#8220;%&#8221;就是&#8220;name&#8221;，&#8220;$$$$&#8221;意为一个随机编号，第二行生成的文件有可能是&#8220;name.d.12345&#8221;，第三行使用sed命令做了一个替换，关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。
<p>总而言之，这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖，即把依赖关系： </p>
<pre style="PADDING-BOTTOM: 0px">    main.o : main.c defs.h
</pre>
转成：
<pre style="PADDING-BOTTOM: 0px">    main.o main.d : main.c defs.h
</pre>
于是，我们的[.d]文件也会自动更新了，并会自动生成了，当然，你还可以在这个[.d]文件中加入的不只是依赖关系，包括生成的命令也可一并加入，让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作，接下来，我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的&#8220;include&#8221;命令，来引入别的Makefile文件（前面讲过），例如：
<pre style="PADDING-BOTTOM: 0px">    sources = foo.c bar.c
include $(sources:.c=.d)
</pre>
上述语句中的&#8220;$(sources:.c=.d)&#8221;中的&#8220;.c=.d&#8221;的意思是做一个替换，把变量$(sources)所有[.c]的字串都替换成[.d]，关于这个&#8220;替换&#8221;的内容，在后面我会有更为详细的讲述。当然，你得注意次序，因为include是按次来载入文件，最先载入的[<no>.d]文件中的目标会成为默认目标
<h3><a name=saciXNKlLtQME></a>4 Makefile 书写命令 </h3>
<hr>
<p>每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令，每条命令的开头必须以[Tab]键开头，除非，命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略，但是如果该空格或空行是以Tab键开头的，那么make会认为其是一个空命令。
<p>我们在UNIX下可能会使用不同的Shell，但是make的命令默认是被&#8220;/bin/sh&#8221;——UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中，&#8220;#&#8221;是注释符，很像C/C++中的&#8220;//&#8221;，其后的本行字符都被注释。
<p>
<h4><a name=saCADAE0R8KnA></a>4.1 显示命令 </h4>
<p>通常，make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用&#8220;@&#8221;字符在命令行前，那么，这个命令将不被make显示出来，最具代表性的例子是，我们用这个功能来像屏幕显示一些信息。如： </p>
<pre style="PADDING-BOTTOM: 0px">    @echo 正在编译XXX模块......
</pre>
当make执行时，会输出&#8220;正在编译XXX模块......&#8221;字串，但不会输出命令，如果没有&#8220;@&#8221;，那么，make将输出：
<pre style="PADDING-BOTTOM: 0px">    echo 正在编译XXX模块......
正在编译XXX模块......
</pre>
如果make执行时，带入make参数&#8220;-n&#8221;或&#8220;--just-print&#8221;，那么其只是显示命令，但不会执行命令，这个功能很有利于我们调试我们的Makefile，看看我们书写的命令是执行起来是什么样子的或是什么顺序的。
<p>而make参数&#8220;-s&#8221;或&#8220;--slient&#8221;则是全面禁止命令的显示。
<p>
<h4><a name=saNGoM/Hp.BSg></a>4.2 命令执行 </h4>
<p>当依赖目标新于目标时，也就是当规则的目标需要被更新时，make会一条一条的执行其后的命令。需要注意的是，如果你要让上一条命令的结果应用在下一条命令时，你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令，你希望第二条命令得在cd之后的基础上运行，那么你就不能把这两条命令写在两行上，而应该把这两条命令写在一行上，用分号分隔。如： </p>
<pre style="PADDING-BOTTOM: 0px">    示例一：
exec:
cd /home/hchen
pwd
示例二：
exec:
cd /home/hchen; pwd
</pre>
<p>当我们执行&#8220;make exec&#8221;时，第一个例子中的cd没有作用，pwd会打印出当前的Makefile目录，而第二个例子中，cd就起作用了，pwd会打印出&#8220;/home/hchen&#8221;。
<p>make一般是使用环境变量SHELL中所定义的系统Shell来执行命令，默认情况下使用UNIX的标准Shell——/bin/sh来执行命令。但在MS-DOS下有点特殊，因为MS-DOS下没有SHELL环境变量，当然你也可以指定。如果你指定了UNIX风格的目录形式，首先，make会在SHELL所指定的路径中找寻命令解释器，如果找不到，其会在当前盘符中的当前目录中寻找，如果再找不到，其会在PATH环境变量中所定义的所有路径中寻找。MS-DOS中，如果你定义的命令解释器没有找到，其会给你的命令解释器加上诸如&#8220;.exe&#8221;、&#8220;.com&#8221;、&#8220;.bat&#8221;、&#8220;.sh&#8221;等后缀。
<p>
<h4><a name=saejriK4eEx6A></a>4.3 命令出错 </h4>
<p>每当命令运行完后，make会检测每个命令的返回码，如果命令返回成功，那么make会执行下一条命令，当规则中所有的命令成功返回后，这个规则就算是成功完成了。如果一个规则中的某个命令出错了（命令退出码非零），那么make就会终止执行当前规则，这将有可能终止所有规则的执行。
<p>有些时候，命令的出错并不表示就是错误的。例如mkdir命令，我们一定需要建立一个目录，如果目录不存在，那么mkdir就成功执行，万事大吉，如果目录存在，那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录，于是我们就不希望mkdir出错而终止规则的运行。
<p>为了做到这一点，忽略命令的出错，我们可以在Makefile的命令行前加一个减号&#8220;-&#8221;（在Tab键之后），标记为不管命令出不出错都认为是成功的。如： </p>
<pre style="PADDING-BOTTOM: 0px">   clean:
-rm -f *.o
</pre>
还有一个全局的办法是，给make加上&#8220;-i&#8221;或是&#8220;--ignore-errors&#8221;参数，那么，Makefile中所有命令都会忽略错误。而如果一个规则是以&#8220;.IGNORE&#8221;作为目标的，那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法，你可以根据你的不同喜欢设置。
<p>还有一个要提一下的make的参数的是&#8220;-k&#8221;或是&#8220;--keep-going&#8221;，这个参数的意思是，如果某规则中的命令出错了，那么就终目该规则的执行，但继续执行其它规则。
<h4><a name=saRJsMKKuxk32></a>4.4 嵌套执行make </h4>
<p>在一些大的工程中，我们会把我们不同模块或是不同功能的源文件放在不同的目录中，我们可以在每个目录中都书写一个该目录的Makefile，这有利于让我们的Makefile变得更加地简洁，而不至于把所有的东西全部写在一个Makefile中，这样会很难维护我们的Makefile，这个技术对于我们模块编译和分段编译有着非常大的好处。
<p>例如，我们有一个子目录叫subdir，这个目录下有个Makefile文件，来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写： </p>
<pre style="PADDING-BOTTOM: 0px">    subsystem:
cd subdir &amp;&amp; $(MAKE)
其等价于：
subsystem:
$(MAKE) -C subdir
</pre>
定义$(MAKE)宏变量的意思是，也许我们的make需要一些参数，所以定义成一个变量比较利于维护。这两个例子的意思都是先进入&#8220;subdir&#8221;目录，然后执行make命令。
<p>我们把这个Makefile叫做&#8220;总控Makefile&#8221;，总控Makefile的变量可以传递到下级的Makefile中（如果你显示的声明），但是不会覆盖下层的Makefile中所定义的变量，除非指定了&#8220;-e&#8221;参数。
<p>如果你要传递变量到下级Makefile中，那么你可以使用这样的声明：
<div class=BeautifierPlugin>
<div class=fragment>
<pre style="PADDING-BOTTOM: 0px">
<pre style="PADDING-BOTTOM: 0px">export &lt;variable ...&gt;</pre>
</pre>
</div>
</div>
如果你不想让某些变量传递到下级Makefile中，那么你可以这样声明：
<div class=BeautifierPlugin>
<div class=fragment>
<pre style="PADDING-BOTTOM: 0px">
<pre style="PADDING-BOTTOM: 0px">unexport &lt;variable ...&gt;</pre>
</pre>
</div>
</div>
如：
<pre style="PADDING-BOTTOM: 0px">
示例一：
export variable = value
其等价于：
variable = value
export variable
其等价于：
export variable := value
其等价于：
variable := value
export variable
示例二：
export variable += value
其等价于：
variable += value
export variable
</pre>
如果你要传递所有的变量，那么，只要一个export就行了。后面什么也不用跟，表示传递所有的变量。
<p>需要注意的是，有两个变量，一个是SHELL，一个是MAKEFLAGS，这两个变量不管你是否export，其总是要传递到下层Makefile中，特别是MAKEFILES变量，其中包含了make的参数信息，如果我们执行&#8220;总控Makefile&#8221;时有make参数或是在上层Makefile中定义了这个变量，那么MAKEFILES变量将会是这些参数，并会传递到下层Makefile中，这是一个系统级的环境变量。
<p>但是make命令中的有几个参数并不往下传递，它们是&#8220;-C&#8221;,&#8220;-f&#8221;,&#8220;-h&#8221;&#8220;-o&#8221;和&#8220;-W&#8221;（有关Makefile参数的细节将在后面说明），如果你不想往下层传递参数，那么，你可以这样来： </p>
<pre style="PADDING-BOTTOM: 0px">
subsystem:
cd subdir &amp;&amp; $(MAKE) MAKEFLAGS=
</pre>
如果你定义了环境变量MAKEFLAGS，那么你得确信其中的选项是大家都会用到的，如果其中有&#8220;-t&#8221;,&#8220;-n&#8221;,和&#8220;-q&#8221;参数，那么将会有让你意想不到的结果，或许会让你异常地恐慌。
<p>还有一个在&#8220;嵌套执行&#8221;中比较有用的参数，&#8220;-w&#8221;或是&#8220;--print-directory&#8221;会在make的过程中输出一些信息，让你看到目前的工作目录。比如，如果我们的下级make目录是&#8220;/home/hchen/gnu/make&#8221;，如果我们使用&#8220;make -w&#8221;来执行，那么当进入该目录时，我们会看到： </p>
<pre style="PADDING-BOTTOM: 0px">
make: Entering directory `/home/hchen/gnu/make'.
</pre>
而在完成下层make后离开目录时，我们会看到：
<pre style="PADDING-BOTTOM: 0px">
make: Leaving directory `/home/hchen/gnu/make'
</pre>
当你使用&#8220;-C&#8221;参数来指定make下层Makefile时，&#8220;-w&#8221;会被自动打开的。如果参数中有&#8220;-s&#8221;（&#8220;--slient&#8221;）或是&#8220;--no-print-directory&#8221;，那么，&#8220;-w&#8221;总是失效的。
<h4><a name=saVJ9lkWtLG16></a>4.5 定义命令包 </h4>
<p>如果Makefile中出现一些相同命令序列，那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以&#8220;define&#8221;开始，以&#8220;endef&#8221;结束，如： </p>
<pre style="PADDING-BOTTOM: 0px">    define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
</pre>
这里，&#8220;run-yacc&#8221;是这个命令包的名字，其不要和Makefile中的变量重名。在&#8220;define&#8221;和&#8220;endef&#8221;中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序，因为Yacc程序总是生成&#8220;y.tab.c&#8221;的文件，所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。
<pre style="PADDING-BOTTOM: 0px">    foo.c : foo.y
$(run-yacc)
</pre>
我们可以看见，要使用这个命令包，我们就好像使用变量一样。在这个命令包的使用中，命令包&#8220;run-yacc&#8221;中的&#8220;$^&#8221;就是&#8220;foo.y&#8221;，&#8220;$@&#8221;就是&#8220;foo.c&#8221;（有关这种以&#8220;$&#8221;开头的特殊变量，我们会在后面介绍），make在执行命令包时，命令包中的每个命令会被依次独立执行。 
<img src ="http://www.cppblog.com/iniwf/aggbug/76776.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-03-16 21:30 <a href="http://www.cppblog.com/iniwf/archive/2009/03/16/76776.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>