﻿<?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>IT博客网-流浪妖精のSKY-文章分类-学习文章</title><link>http://www.cnitblog.com/flutist1225/category/4178.html</link><description>&lt;div align="right"&gt;&lt;img src="http://www.cnitblog.com/images/cnitblog_com/flutist1225/3799/r_r_yang.png"&gt;&lt;/div&gt;
&lt;!--- daliy English --&gt;
&lt;span id="dict_daily"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;script language="JavaScript" src="http://dict.cn/daily.php" defer="defer"&gt;&lt;br /&gt;
&lt;/script&gt;
&lt;!--Music --&gt;
&lt;span style="display:none"&gt;
&lt;embed src="http://tvgame.5617.com/download/20060425_095213_249_346.mp3" loop=true autostart=true volume=100 type=audio/x-pn-realaudio-plugin Initfn=load-types mime-types=mime.types &gt;
&lt;/span&gt;</description><language>zh-cn</language><lastBuildDate>Thu, 08 Mar 2007 14:04:24 GMT</lastBuildDate><pubDate>Thu, 08 Mar 2007 14:04:24 GMT</pubDate><ttl>60</ttl><item><title>Linux ARP缓存表 </title><link>http://www.cnitblog.com/flutist1225/articles/19996.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:28:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19996.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19996.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19996.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19996.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19996.html</trackback:ping><description><![CDATA[   arp_tbl是一个类型为struct neigh_table的全局变量，它是一个ARP的缓存表，也称为邻居表。协议栈通过ARP协议获取到的网络上邻居主机的IP地址与MAC地址的对应关系都会保存在这个表中，以备下次与邻居通讯时使用，同时，ARP模块自身也会提供一套相应的机制来更新和维护这个邻居表。下面逐个分析arp_tbl中的重要成员数据与函数。<br />    entry_size，key_len，kmem_cachep。<br />    entry_size是一个入口的大小，也就是arp_tbl中一个邻居的大小，邻居用struct neighbour结构体表示，该结构体的最后一个成员是u8 primary_key[0]，用于存放IP地址，作为这个邻居的哈希主键。所以entry_size的大小就是sizeof(struct neighbour) + 4，因为是用IP地址作主键，所以key_len就是4。kmem_cachep是一个后备高速缓存，创建一个邻居需要的内存从这个后备高速缓存中去取。<br />    hash_buckets，hash_mask，entries，hash。<br />    hash_buckets是一个哈希数组，里面存放了arp_tbl当前维护的所有的邻居，hash_mask是哈希数组大小的掩码，其初始值为1，所以hash_buckets的初始大小为2(0到hash_mask的空间范围)。entries是整个arp_tbl中邻居的数量，当entries大于hash_mask+1的时候，hash_buckets增长为原来的两部。成员hash是一个哈希函数指针，用于计算哈希值。<br />    phash_buckets，PNEIGH_HASHMASK。<br />    这是用于代理ARP的邻居哈希表，PNEIGH_HASHMASK固定为0xF,所以phash_buckets固定有16项，其它与hash_buckets相同。<br />    id。<br />    id作为这个邻居表的一个名称，是一个字符串信息，内核协议栈的arp_tbl的id是arp_cache。<br />    gc_interval，gc_thresh1，gc_thresh2，gc_thresh3。<br />    gc_thresh3是arp_tbl中允许拥有的邻居数量的上限，一旦超过这个上限，并且表中没有可以清理掉的垃圾邻居，那么就无法创建新的邻居，这个值缺省被置为1024。gc_thresh2是第二个阀值，如果表中的邻居数量超过这个阀值，并且在需要创建新的邻居时，发现已经超过5秒时间表没有被刷新过，则必须立即刷新arp_tbl表，进行强制垃圾回收，这个值缺省被置为512。gc_thresh1的用途暂时还没有发现，它缺省被置为128。gc_interval应该是常规的垃圾回收间隔时间，被缺省置为30秒，但目前在源代码中似乎没有看到它的应用。强制垃圾收集的工作即是把引用计数为1，且状态中没有NUD_PERMANENT的邻居全部从arp_tbl表中删除。<br />    gc_timer。<br />    这是一个常规垃圾回收的定时器，其定时处理函数是neigh_periodic_timer。该定时器超时后，处理函数处理hash_buckets表中的一项，下次超时后，再处理下一项，这里的垃圾回收比强制垃圾回收条件要宽松得多，如果邻居的状态为NUD_PERMANENT或NUD_IN_TIMER(该邻居正在解析中)，则不能回收。当邻居的引用计数为1时，并且邻居状态为NUD_FAILED(解析失败)或者该邻居距最近一次被使用时间已超过参数表中gc_staletime的值(缺省为60秒),则可以作为垃圾回收。回收完毕后，要设置下一次进行回收的时间(gc_timer的超时时间)，下次回收时间为参数表中base_reachable_time的值(缺省设为30秒)的一半，再除以hash_buckets哈希表中的项数。也就是，基本上15秒左右会把整个arp_tbl缓存表进行一次垃圾回收。<br />    proxy_timer，proxy_queue，proxy_redo。<br />    proxy_timer是一个关于代理ARP的定时器，proxy_queue是一个待处理的代理ARP数据包的队列，每次定时器超时，处理函数neigh_proxy_process依次检查队列中每一个代理ARP数据包(struct sk_buff)，对于超时，且满足相关条件的，调用proxy_redo进行处理。有关代理ARP，将专门分析讲述，这里暂时略过。<br />    constructor。<br />    这是一个邻居的初始化函数指针，每次创建出一个邻居后，需要马上调用这个函数对新创建的邻居进行一些初始化操作。邻居创建完，已经被赋于一个IP地址(邻居结构体的primary_key成员)，该函数首先根据这个IP地址来确定其地址类型，然后为邻居选择相应的操作函数集(初始化邻居结构体的一些成员，在讲到邻居结构体内容时再进行分析)。<br />    pconstructor，pdestructor。<br />    这是代理ARP的邻居的构建和析构函数指针，在IPv4模块中，未提供这两个函数，所以它们的指针值为空。<br />    parms。<br />    这是一个结构体struct neigh_parms的链表，系统中每个网络设备接口对应链表中一个节点，表示该设备接口上的邻居的一些传输参数。同时，链表中还有一个缺省的项。<br />    last_rand，hash_rand<br />    这两个成员其实没有联系，hash_rand是用于邻居哈希表hash_buckets的一个随机数，last_rand用于记录一个时间，即上次为parms链表中每个节点生成reachable_time的时间，reachable_time是需要被定时刷新的。<br />    stats。<br />    记录arp_tbl被操作次数的一些统计数据。 
<p>    结构体struct neigh_table是一个哈希表，用于描述物理上互相连接的机器的信息。ARP缓存myarp_tbl就是这样的一个结构。在分析ARP相关的初始化之前，我们先来看一下这个结构体：<br />    truct neigh_table<br />    {<br />        struct neigh_table  *next;<br />        int         family;<br />        int         entry_size;<br />        int         key_len;<br />        __u32       (*hash)(const void *pkey, const struct net_device *);<br />        int         (*constructor)(struct neighbour *);<br />        int         (*pconstructor)(struct pneigh_entry *);<br />        void        (*pdestructor)(struct pneigh_entry *);<br />        void        (*proxy_redo)(struct sk_buff *skb);<br />        char        *id;<br />        struct neigh_parms  parms;<br />        /* HACK. gc_* shoul follow parms without a gap! */<br />        int         gc_interval;<br />        int         gc_thresh1;<br />        int         gc_thresh2;<br />        int         gc_thresh3;<br />        unsigned long       last_flush;<br />        struct timer_list   gc_timer;<br />        struct timer_list   proxy_timer;<br />        struct sk_buff_head proxy_queue;<br />        atomic_t            entries;<br />        rwlock_t            lock;<br />        unsigned long       last_rand;<br />        kmem_cache_t        *kmem_cachep;         struct neigh_statistics *stats;         struct neighbour    **hash_buckets;         unsigned int        hash_mask;<br />        __u32               hash_rnd;<br />        unsigned int        hash_chain_gc;<br />        struct pneigh_entry **phash_buckets;<br />#ifdef CONFIG_PROC_FS<br />        struct proc_dir_entry   *pde;<br />#endif<br />    };<br />    entry_size是一个入口的长度，一个入口代表一个neighbour的信息,hash_buckets即为存放所有邻居的一个哈希数组，每一项对应一条neighbour链表。struct neighbour用于代表一个neighbour，包含了其信息，下面是其重要的一些成员：<br />    dev代表与这个邻居相连的网络设备；nud_state代表邻居的状态（未完成，无法访问，过时，失败）；ha表示邻居的mac地址；hh是以太网包的头部缓存；arp_queue是等待这个邻居的硬件地址的IP包队列；ops是对该neighbour节点操作的一套函数；primary_key是哈希表的主键，一般为IP地址。<br />    key_len是哈希表主键的长度，一般IP地址长度为4。<br />    几个函数分别为哈希函数，构造和析构函数。<br />    parms是ARP缓存的一些参数，包括ARP包传输时间，重发时间，队列长度和代理队列长度等等。<br />    ARP缓存有一个回收机制（garbage collection），上面以gc_开头的参数用来设置回收的频率和阀值等等。<br />    stats是一些关于邻居的统计信息。<br />    ARP初始化的第一个步是初始化ARP缓存myarp_tbl，并把它加到全局链表neigh_tables的表头，其实，系统中所有的neigh_table都放在这个表中。<br />    ptype_base是一个有16项的哈希数组，每种协议包类型都注册在这个数组中。arp包，其类型是ETH_P_ARP，其接收函数是 myarp_rcv。有了这个注册信息，当设备上收到一个网络包（packet）的时候，会分配一个sk_buff(skb)，将数据拷贝进这个缓冲区，然后调用netif_rx把skb放入等待队列（input_pkt_queue）中，并且产生一个软中断。当系统处理这个软中断的时候，会调用 net_rx_action，它根据网络包的类型，调用相应的接收函数来处理。如果是ARP包，则调用myarp_rcv。</p><p><br />    ARP缓存myarp_tbl是用于描述物理上相互连接的机器的信息的一个哈希表，关于这个缓存，我们前面作过分析。现在我们来看看，当主机收到一个需要本地接收的ARP请求时，如何向ARP缓存中更新一个ARP信息。<br />    当网络设备收到一个ARP数据包后，最终会调用到协议栈中的myarp_process处理函数，这个函数的处理会涉及到路由表的查询和更新。但我们现在的my_inet模块还没有真正完成路由表的初始化，所以略过其中很多细节，只关注ARP缓存的更新与查询。<br />    myarp_process通过调用__neigh_lookup函数更新ARP缓存，下面是该函数的定义：<br />    static inline struct neighbour * __neigh_lookup(struct neigh_table *tbl,<br />                                                    const void *pkey,<br />                                                    struct net_device *dev,<br />                                                    int creat)<br />    参数tbl是ARP缓存哈希表，传入全局变量myarp_tbl。pkey是哈希主键，传入发送端ip地址，在我们的实验环境中，是通过 172.16.48.1向172.16.48.11发送icmp回显请求包来触发myarp_process的执行，所以，pkey就是ip地址 172.16.48.1。dev是接收到该数据包的网络接口。create表示在缓存中不存在该机器的信息时，是否需要创建一个，我们现在的目的是更新，所以选择是，传入1。<br />    该函数首先调用neigh_lookup在ARP缓存中查找。neigh_lookup首先通过pkey,dev计算得到一个哈希值hash_val，再找到myarp_tbl中的一个链表myarp_tbl-&gt;hash_buckets[hash_val]，遍历该链表，如果能找到dev, pkey都相等的项，就是我们所要找的struct neighbour。在这里，还要更新myarp_tbl的成员stats中的一统计数据。<br />    显然，第一次收到ARP请求包，我们是找不到ARP缓存信息的，所以neigh_lookup返回NULL，__neigh_lookup判断 create值，如果不需要创建，就直接返回，如果需要，则调用neigh_create进行缓存信息的创建。<br />    neigh_create首先在内存中分配一个struct neighbour结构体。myarp_tbl的成员entries记录了该ARP缓存中已经存在了多少条缓存信息，如果数量超过了gc_thresh3 (1024)，或者数量超过了gc_thresh2(512)，并且距离上次缓存刷新时间还不到5秒，则需要先强制进行缓存垃圾回收，对于一些未使用的缓存信息进行清理。如果清理后，缓存数量还是超过gc_thresh3，则无法再进行创建，出错返回。对于新创建的neighbour，先给赋一些缺省值。<br />    然后调用myarp_tbl的构造函数，对新创建的 neighbour进一步初始化。具体的初始化步骤不再详述，我们可以从最后创建出来的neighbour的内容看到一些东西。<br />    接下来，由于新增加了缓存项，需要对myarp_tbl的大小进行调整，如果有需要，需要扩大其容量。<br />    最后，把新创建的neighbour添加到链表示，<br />    新创建的neighbour的内容应该是这样子的：<br />    struct neighbour<br />    {<br />        struct neighbour    *next       =原来的链表头;<br />        struct neigh_table  *tbl        =myarp_tbl;<br />        struct neigh_parms  *parms      =in_dev-&gt;arp_parms;<br />        struct net_device       *dev    =dev;<br />        unsigned long       used        =now;<br />        unsigned long       confirmed   =now - 60秒;<br />        unsigned long       updated     =now;<br />        __u8            flags;<br />        __u8            nud_state       =NUD_NONE;<br />        __u8            type            =RTN_LOCAL;<br />        __u8            dead            =1;<br />        atomic_t        probes;<br />        rwlock_t        lock;<br />        unsigned char       ha[(MAX_ADDR_LEN+sizeof(unsigned long)-1)&amp;~(sizeof(unsigned long)-1)];<br />                                        //ha是mac地址，在后续的操作会给它赋上值。<br />        struct hh_cache     *hh         =NULL;<br />        atomic_t        refcnt          =1;<br />        int         (*output)(struct sk_buff *skb) = this-&gt;ops-&gt;connected_output;<br />        struct sk_buff_head arp_queue;<br />        struct timer_list   timer;<br />                            timer.function = neigh_timer_handler;<br />                            timer.data = this;<br />        struct neigh_ops    *ops        =&amp;myarp_hh_ops;<br />        u8          primary_key[0]      =172.16.48.1（分配内存时，本身就加了4的);<br />    };<br /></p>    在发送一个IP数据报时，当在确定数据报的输出路由时，需要为本次通讯绑定一个邻居，TCP/IP协议栈用结构体struct neighbour表示一个邻居。绑定邻居首先调用的函数是arp_bind_neighbour，该函数调用_neigh_lookup_errno函数从ARP缓存表中以目的IP地址(网关IP或者同一子网内的对端IP地址)为哈希主键，寻找相应的邻居，如果找不到，则创建。<br />    首先是调用neigh_lookup函数从arp_tbl的成员hash_buckets中寻找，如果找到的邻居的成员dev等于当前的网络设备接口，且primary_key等于当前的目的IP地址，则即为需要的邻居，返回即可。<br />    如果找不到，则需要通过调用neigh_create函数创建一个新的邻居。下面看一下表示邻居的结构体struct neighbour的成员，以及创建时为这些成员初始化了什么样的值。<br />    parms。<br />    parms指向ARP缓存表arp_tbl的parms成员，包含了该邻居上的一些传输参数。<br />    dev。<br />    dev成员指向该邻居所在子网内的本机网络设备接口。<br />    type。<br />    该邻居的IP地址类型，这个值由FIB表根据邻居的IP地址来确定，比如类型RTN_UNICAST，RTN_LOCAL等。<br />    ops, output。<br />    ops是该邻居的一组操作函数集，包括输出函数output，直接输出函数dev_queue_xmit，错误报告函数arp_error_report，arp解析函数solicit等。根据type的不同，操作函数集略有不同，这在arp_tbl的constructor函数中确定。output即为ops中的普通输出函数output，因为其比较常用，所以单独为其设置一个成员。<br />    timer。<br />    这是邻居的定时器，用于解析ARP，其超时函数是neigh_timer_handler。<br />    arp_queue<br />    这是一个struct sk_buff的队列，协议栈在发送一个IP数据包时，如果还未进行arp解析，则先把该IP数据包放入arp_queue，然后进行ARP解析。<br /><br />    下面重点看一下struct neighbour的成员nud_state，它是邻居的当前状态，邻居在创建，解析的过程中，其状态经历了一系例的变迁过程。<br />    当一个邻居刚刚被创建，其状态是NUD_NONE，此时，邻居被创建出来，并根据其IP地址被加入到arp_tbl的哈希表hash_buckets中，但它还没有被解析，其成员ha(邻居的硬件地址)为空。直到向这个邻居发送IP数据报，并且发送流程到达网络层的最后一个发送函数ip_finish_output2时，调用邻居的output成员函数，一般这个函数是neigh_resolve_output(根据type的不同会略有不同)。它会先进行ARP解析，然后把IP数据报发往正确的邻居。<br />    因为当前状态是NUD_NONE，neigh_resolve_output首先判断parms的成员mcast_probes加上app_probes的值，这两个参数表示ARP解析时尝试的次数，mcast_probes缺省值置为3，app_probes是0，如果它们的和为零，则不作ARP解析尝试，直接将状态置为NUD_FAILED。当前不为零，所以进行解析，首先置邻居的成员probes为parms的ucast_probes，缺省为3，这样，该邻居等于是已经尝试了3次了，另外还只能多3次(mcast_probes)，即该邻居的解析最多尝试3次，如果3次不成功，则失败，结束。然后将状态置为NUD_INCOMPLETE，表示正在解析中，并把待解析的socket缓冲skb放入arp_queue队列中，然后启动邻居的定时器开始ARP解析。<br />    定时器处理函数neigh_timer_handler首先确定下次的超时时间(如果这次解析没有成功)为当前时间加上parms的成员retrans_time的值（缺省设置为1秒)。所以，ARP解析的发包超时时间是1秒，连续尝试3次。ARP解析是通过ops的成员函数solicit去做的，该成员函数指针指向的arp_solicit函数，arp_solicit会实际发送ARP请求包。<br />    如果arp_solicit发送ARP请求包，连续三次没有得到正确的回应，则把nud_state置为NUD_FAILED，调用ops的成员函数error_report报告错误。并把skb从arp_queue队列中取出。error_report错误报告置dst的超时时间为当前时间，并删除skb，关于dst，跟FIB相关，涉及到FIB时再详细分析。<br />    置为NUD_FAILED的邻居在下次进行常规垃圾回收时，被删除回收掉。 
<p>    协议栈在发送一个IP数据报之前，需要发送ARP请求是为了解析IP数据报的目的IP地址对应的硬件地址。ARP协议可以承载多种硬件类型和多种协议，这里我们只关注以太网上的IP协议，那ARP请求的目的就是为了解析邻居的以太网MAC地址。<br />    ARP请求/应答数据报总共有28字节，其具体格式如下所示(在以太网,IP协议的情况下)：<br />    字段含义：硬件类型 协议类型 硬件地址长度 协议地址长度 op<br />    字段长度：2              2             1                    1                    2<br />    字段含义：发送端以太网地址 发送端IP地址 目的以太网地址 目的IP地址<br />    字段长度：6                           4                      6                      4<br />    硬件类型，对于10Mbps以太网，其取值是ARPHRD_ETHER(值为1)，协议类型，对于IP协议来说，就是ETH_P_IP(0x0800),在上述条件限定下，硬件地址长度为6，协议地址长度为4，op的值为ARPOP_REQUEST(值为1，表示是ARP请求，或者ARPOP_REPLY(值为2，表示是ARP应答)。<br />    发送端以太网地址填发送接口的MAC地址，发送端IP地址填源IP地址(关于源IP地址的选取下文会有详细分析)，目的以太网地址，因为当前是ARP请求，所以填入以太网广播地址0xFF:0xFF:0xFF:0xFF:0xFF:0xFF，目的IP地址填入IP数据报的目的地址。<br />    这里，发送端IP地址的选取是一个可配置的选项，文件/proc/sys/net/ipv4/conf/设备名/arp_announce记录的是它的配置值，这是一个整型数值，取值范围为0-2，0表示只要待发送IP数据报的源地址为本地地址，就取该地址为发送端IP地址，这也是缺省配置；1表示若待发送IP数据报的源地址为本地地址，并且该IP地址与目的IP地址在同一子网内，则取该地址为发送端IP地址，否则从所有的本地地址中取一个与目的IP地址在同一子网内的地址；2表示完全忽略IP数据报中的地址，直接从所有本地输出网络接口中选取一个最为合适的发送端IP地址。配置级别越高，我们能够获得ARP回应的机率也就越大。<br />    构建好的ARP请求数据报通过函数dev_queue_xmit发送出去。</p><p>    在分析接收和处理ARP的回应数据之前，先复习一下协议栈接收网络数据的一个基本流程，网卡驱动程序会创建一个skb，并填入接收到的数据，并把这个skb传给协议栈的第一个接收函数netif_rx。softnet_data是一组全局变量，每个CPU拥有一个，它是一个结构体struct softnet_data，其定义如下：<br />    struct softnet_data<br />    {<br />        struct net_device   *output_queue;<br />        struct sk_buff_head input_pkt_queue;<br />        struct list_head    poll_list;<br />        struct sk_buff      *completion_queue;<br /><br />        struct net_device   backlog_dev;<br />    };<br />    input_pkt_queue是一个接收队列，从网卡接收上来的skb一般会放在这里队列中待处理，这个队列的长度是有限制的，netdev_max_backlog就是该队列长度的上限，值为1000，如果队列中的skb数量已经达到1000，对于新收到的skb会直接丢弃。当新接收到一个skb，首先检查input_pkt_queue队列，如果队列长度未太到上限，且队列中已经存在skb，则直接把新收到的skb放在队列尾，返回成功。如果队列为空，则先调用函数netif_rx_schedule把接收工作启动起来，再把skb放入队列。<br />    netif_rx_schedule把softnet_data的成员backlog_dev连入poll_list，产生一个软中断NET_RX_SOFTIRQ。随后该中断处理函数net_rx_action被执行，net_rx_action检查softnet_data的poll_list队列，如果不空，则开始进行处理接收数据。<br />    net_rx_action的处理时间有一个限制，当该函数处理时间超过1个时钟滴嗒后，必须退出，重新产生一个NET_RX_SOFTIRQ，在下一个中断处理中继续处理。同时，net_rx_action的处理数据报的数量也有一个限制，netdev_budget是一个全局变量，值为300，net_rx_action每处理完300个数据报，也必须退出，在下一个中断处理中继续处理。<br />    softnet_data的成员backlog_dev在系统初始化时，被置了一些值，大体如下：<br />    set_bit( __LINK_STATE_START, &amp;queue-&gt;backlog_dev.state );<br />    queue-&gt;backlog_dev.weight = weight_p;<br />    queue-&gt;backlog_dev.poll = process_backlog;<br />    atomic_set( &amp;queue-&gt;backlog_dev.refcnt, 1 );<br />    weight_p是一个全局变量，值为64，process_backlog是net_rx_action调用，用于实际处理接收数据报的函数，变量queue-&gt;backlog_dev.quota在每次调用netif_rx_schedule时置为64(如果quota当前值小于0，则加上64)。process_backlog负责把数据报从input_pkt_queue中取出来，传给函数netif_receive_skb，函数处理时间不得超过一个时钟滴嗒，并且处理数据报数量存在限制，超过限制，返回-1，处理完所有数据报，返回0。<br />    netif_receive_skb对于每一个收到的skb，到已注册的协议类型中去匹配，先是匹配列表ptype_all，ptype_all中注册的struct packet_type表示要接收处理所有协议的数据报，struct packet_type是表示网络层协议类型(也即以太网首部中的帧类型字段)的一个数据结构，其定义如下：<br />    struct packet_type {<br />        __be16          type;       //协议类型<br />        struct net_device   *dev;   //指定的设备，为NULL表示接收所有设备上的数据报。<br />        int         (*func) (struct sk_buff *,<br />                        struct net_device *,<br />                        struct packet_type *,<br />                        struct net_device *); //接收处理函数<br />        void            *af_packet_priv;<br />        struct list_head    list;           //注册到ptype_all和ptype_base中用。<br />    };<br />    对于匹配到的struct packet_type结构(dev为NULL，或者dev等于skb的dev)，调用其func成员，把skb传递给它处理。<br />    匹配完ptype_all后，netif_receive_skb再匹配ptype_base数组中注册的协议类型，skb有一个成员protocol，其值即为以太网首部中的帧类型，在ptype_base中匹配到协议相同，并且dev符合要求的，调用其func成员即可。<br />    arp模块在初始化时，就往ptype_base中注册了一个协议类型ETH_P_ARP，其接收处理函数为arp_rcv，所以ARP应答最终到达函数arp_rcv。<br />    arp_rcv首先检查skb的长度是否到达一个ARP应答包的基本长度要求，再检查应答包中的硬件地址长度字段值是否跟设备的硬件地址长度一致，再检查数据报是否是本地接收，协议地址长度是否为4。检查无误后，克隆一个skb，交由arp_process处理。<br />    arp_process中处理所有收到的ARP数据报，这次，我们只关注收到的ARP回应包。ARP回应包中的发送端IP地址即为邻居的IP地址，我们以这个IP地址为主键到ARP缓存arp_tbl中去寻找这个邻居节点(hash_buckets成员)，因为在发送ARP请求之前，进行邻居绑定的时候，我们已经建立了一个未被解析的邻居，所以这次寻找肯定是成功的，如果没有找到，则直接放弃，不作处理。<br />    找到了这个邻居，我们就要用新的ARP回应包的内容刷新这个邻居的内容，但为了防止邻居被过度频繁刷新，引起抖动，只有距邻居上次刷新时间超过parms-&gt;locktime(缺省置为1秒)后才允许再次刷新，这是一个可配置的项，可通过修改文件/proc/sys/net/ipv4/neigh/设备名/locktime进行修改。<br />    实际的刷新工作在neigh_update函数中完成，新的邻居状态为NUD_REACHABLE，该函数中首先更新邻居的成员数据confirmed和updated为当前时间，然后删除邻居的定时器（定时器正在等待下一次重发ARP请求)，把定时器修改到距当前时间parms-&gt;reachable_time(初始设置为30秒，实际值在15-45秒之间随机变动)。并把新的MAC地址复制到邻居的成员ha中。<br />    邻居的成员hh是一个硬件头缓存，它是一个结构体struct hh_cache，其定义如下：<br />    struct hh_cache<br />    {<br />        struct hh_cache *hh_next;<br />        atomic_t    hh_refcnt;<br />        unsigned short  hh_type;<br />        int     hh_len;<br />        int     (*hh_output)(struct sk_buff *skb);<br />        rwlock_t    hh_lock;<br /><br />#define HH_DATA_MOD 16<br />#define HH_DATA_OFF(__len) \<br />        (HH_DATA_MOD - (((__len - 1) &amp; (HH_DATA_MOD - 1)) + 1))<br />#define HH_DATA_ALIGN(__len) \<br />        (((__len)+(HH_DATA_MOD-1))&amp;~(HH_DATA_MOD - 1))<br />        unsigned long   hh_data[HH_DATA_ALIGN(LL_MAX_HEADER) / sizeof(long)];<br />    };<br />    邻居的成员hh是一个链表，每种协议对应一个节点，协议类型记录在hh_type中，我们现在只处理IP协议，所以这个链表中总是只有一项，hh_data是缓存的硬件头（对于以太网来说，就是以太网头)，hh_output是输出函数，有了hh，下次再发送数据报，就不需要重新构建以太网头了。当ARP解析完成后，需要更新hh缓冲。<br />    邻居的成员output用于输出IP数据报，目前它指向neigh_resolve_output，这里，需要把它改为neigh_connected_output，即下次发送数据报，不再需要解析邻居的MAC地址了。最后把邻居的arp_queue队列上等待发送的skb全部通过neigh_connect_output发送出去。到这里，ARP解析算是全部完成了。<br />    前面还留有一个问题，就是邻居的定时器被再次启动了，此时邻居是处于NUD_REACHABLE状态的，下面再来看邻居定时器的超时处理函数neigh_timer_handler，处于NUD_REACHABLE状态的邻居，如果距上次被证实(收到邻居的ARP回应包，确认这个邻居还是有效的)时间超过了parms-&gt;reachable_time，且距上次被使用时间不足parms-&gt;delay_probe_time(缺省设置为5秒)，则邻居进入NUD_DELAY(延迟)状态，如果距上次使用时间也超过了5秒，则进入NUD_STALE(过期)状态，进入延迟和过期状态的邻居，其成员output重新指向neigh_resolve_output。<br />    进入延迟状态的邻居，在下一个邻居超时处理中，如果发现邻居最近又被证实过了，且距上次证实时间不足5秒，则恢复为NUD_REACHABLE状态，否则进入NUD_PROBE(探测)状态，NUD_PROBE最多允许有3次尝试机会(ucast_probes)。具体流程同NUD_INCOMPLETE状态。而进入NUD_STALE状态的邻居，只有下次在向它发送数据报时，才会恢复到NUD_DELAY状态。<br />    当主机收到一个来自邻居的ARP请求数据报时，也会去搜索arp_tbl缓存，找不到则创建一个新的邻居。然后把来自请求数据报的邻居的mac地址填入，邻居被直接置成NUD_STALE(过期)状态。如果未被使用，则在60秒(parms-&gt;gc_staletime)后被定期回收的定时器处理函数回收掉。</p><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19996.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:28 <a href="http://www.cnitblog.com/flutist1225/articles/19996.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>例解 autoconf 和 automake 生成 Makefile 文件 </title><link>http://www.cnitblog.com/flutist1225/articles/19995.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:26:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19995.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19995.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19995.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19995.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19995.html</trackback:ping><description><![CDATA[
		<p>
				<a name="N1006A">
						<span class="atitle">
								<strong>
										<font size="4">模拟需求</font>
								</strong>
						</span>
				</a>
		</p>
		<p>假设源文件按如下目录存放，如图1所示，运用autoconf和automake生成makefile文件。</p>
		<br />
		<a name="N10075">
				<strong>图 1文件目录结构</strong>
		</a>
		<br />
		<img height="190" alt="图 1文件目录结构" src="http://www-128.ibm.com/developerworks/cn/linux/l-makefile/images/image001.jpg" width="217" border="0" />
		<br />
		<p>假设src是我们源文件目录，include目录存放其他库的头文件，lib目录存放用到的库文件，然后开始按模块存放，每个模块都有一个对应的目录，模块下再分子模块，如apple、orange。每个子目录下又分core，include，shell三个目录，其中core和shell目录存放.c文件，include的存放.h文件，其他类似。</p>
		<p>样例程序功能：基于多线程的数据读写保护（联系作者获取整个autoconf和automake生成的Makefile工程和源码，E-mail：<a href="mailto:normalnotebook@126.com"><font color="#5c81a7">normalnotebook@126.com</font></a>）。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-makefile/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font face="Verdana" color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N1008F">
						<span class="atitle">
								<strong>
										<font size="4">工具简介</font>
								</strong>
						</span>
				</a>
		</p>
		<p>所必须的软件：autoconf/automake/m4/perl/libtool（其中libtool非必须）。</p>
		<p>autoconf是一个用于生成可以自动地配置软件源码包，用以适应多种UNIX类系统的shell脚本工具，其中autoconf需要用到 m4，便于生成脚本。automake是一个从Makefile.am文件自动生成Makefile.in的工具。为了生成Makefile.in，automake还需用到perl，由于automake创建的发布完全遵循GNU标准，所以在创建中不需要perl。libtool是一款方便生成各种程序库的工具。</p>
		<p>目前automake支持三种目录层次：flat、shallow和deep。</p>
		<p>1) flat指的是所有文件都位于同一个目录中。</p>
		<p>就是所有源文件、头文件以及其他库文件都位于当前目录中，且没有子目录。Termutils就是这一类。</p>
		<p>2) shallow指的是主要的源代码都储存在顶层目录，其他各个部分则储存在子目录中。</p>
		<p>就是主要源文件在当前目录中，而其它一些实现各部分功能的源文件位于各自不同的目录。automake本身就是这一类。</p>
		<p>3) deep指的是所有源代码都被储存在子目录中；顶层目录主要包含配置信息。</p>
		<p>就是所有源文件及自己写的头文件位于当前目录的一个子目录中，而当前目录里没有任何源文件。 GNU cpio和GNU tar就是这一类。</p>
		<p>flat类型是最简单的，deep类型是最复杂的。不难看出，我们的模拟需求正是基于第三类deep型，也就是说我们要做挑战性的事情：)。注：我们的测试程序是基于多线程的简单程序。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-makefile/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font face="Verdana" color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N100B3">
						<span class="atitle">
								<strong>
										<font size="4">生成 Makefile 的来龙去脉</font>
								</strong>
						</span>
				</a>
		</p>
		<p>首先进入 project 目录，在该目录下运行一系列命令，创建和修改几个文件，就可以生成符合该平台的Makefile文件，操作过程如下：</p>
		<p>1) 运行autoscan命令</p>
		<p>2) 将configure.scan 文件重命名为configure.in，并修改configure.in文件</p>
		<p>3) 在project目录下新建Makefile.am文件，并在core和shell目录下也新建makefile.am文件</p>
		<p>4) 在project目录下新建NEWS、 README、 ChangeLog 、AUTHORS文件</p>
		<p>5) 将/usr/share/automake-1.X/目录下的depcomp和complie文件拷贝到本目录下</p>
		<p>6) 运行aclocal命令</p>
		<p>7) 运行autoconf命令</p>
		<p>8) 运行automake -a命令</p>
		<p>9) 运行./confiugre脚本</p>
		<p>可以通过图2看出产生Makefile的流程，如图所示：</p>
		<br />
		<a name="N100DC">
				<strong>图 2生成Makefile流程图</strong>
		</a>
		<br />
		<img height="338" alt="图 2生成Makefile流程图" src="http://www-128.ibm.com/developerworks/cn/linux/l-makefile/images/image002.gif" width="468" border="0" />
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-makefile/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font face="Verdana" color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N100EC">
						<span class="atitle">
								<strong>
										<font size="4">Configure.in的八股文</font>
								</strong>
						</span>
				</a>
		</p>
		<p>当我们利用autoscan工具生成confiugre.scan文件时，我们需要将confiugre.scan重命名为confiugre.in文件。confiugre.in调用一系列autoconf宏来测试程序需要的或用到的特性是否存在，以及这些特性的功能。</p>
		<p>下面我们就来目睹一下confiugre.scan的庐山真面目：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section"># Process this file with autoconf to produce a configure script.AC_PREREQ(2.59)AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)AC_CONFIG_SRCDIR([config.h.in])AC_CONFIG_HEADER([config.h])# Checks for programs.AC_PROG_CC# Checks for libraries.# FIXME: Replace `main' with a function in `-lpthread':AC_CHECK_LIB([pthread], [main])# Checks for header files.# Checks for typedefs, structures, and compiler characteristics.# Checks for library functions.AC_OUTPUT</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>每个configure.scan文件都是以AC_INIT开头，以AC_OUTPUT结束。我们不难从文件中看出confiugre.in文件的一般布局：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">AC_INIT 测试程序 测试函数库 测试头文件 测试类型定义 测试结构 测试编译器特性 测试库函数 测试系统调用AC_OUTPUT</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>上面的调用次序只是建议性质的，但我们还是强烈建议不要随意改变对宏调用的次序。</p>
		<p>现在就开始修改该文件：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">$mv configure.scan configure.in$vim configure.in</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>修改后的结果如下：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">		#                                -*- Autoconf -*-# Process this file with autoconf to produce a configure script.AC_PREREQ(2.59)AC_INIT(test, 1.0, normalnotebook@126.com)AC_CONFIG_SRCDIR([src/ModuleA/apple/core/test.c])AM_CONFIG_HEADER(config.h)AM_INIT_AUTOMAKE(test,1.0)# Checks for programs.AC_PROG_CC# Checks for libraries.# FIXME: Replace `main' with a function in `-lpthread':AC_CHECK_LIB([pthread], [pthread_rwlock_init])AC_PROG_RANLIB# Checks for header files.# Checks for typedefs, structures, and compiler characteristics.# Checks for library functions.AC_OUTPUT([Makefile		src/lib/Makefile		src/ModuleA/apple/core/Makefile		src/ModuleA/apple/shell/Makefile		])		</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>其中要将AC_CONFIG_HEADER([config.h])修改为：AM_CONFIG_HEADER(config.h), 并加入AM_INIT_AUTOMAKE(test,1.0)。由于我们的测试程序是基于多线程的程序，所以要加入AC_PROG_RANLIB，不然运行automake命令时会出错。在AC_OUTPUT输入要创建的Makefile文件名。</p>
		<p>由于我们在程序中使用了读写锁，所以需要对库文件进行检查，即AC_CHECK_LIB([pthread], [main])，该宏的含义如下：</p>
		<br />
		<img height="166" alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-makefile/images/table1.gif" width="537" border="0" />
		<br />
		<p>其中，LIBS是link的一个选项，详细请参看后续的Makefile文件。由于我们在程序中使用了读写锁，所以我们测试pthread库中是否存在pthread_rwlock_init函数。</p>
		<p>由于我们是基于deep类型来创建makefile文件，所以我们需要在四处创建Makefile文件。即：project目录下，lib目录下，core和shell目录下。 </p>
		<p>Autoconf提供了很多内置宏来做相关的检测，限于篇幅关系，我们在这里对其他宏不做详细的解释，具体请参看参考文献1和参考文献2，也可参看autoconf信息页。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-makefile/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font face="Verdana" color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N10148">
						<span class="atitle">
								<strong>
										<font size="4">实战Makefile.am</font>
								</strong>
						</span>
				</a>
		</p>
		<p>Makefile.am是一种比Makefile更高层次的规则。只需指定要生成什么目标，它由什么源文件生成，要安装到什么目录等构成。</p>
		<p>表一列出了可执行文件、静态库、头文件和数据文件，四种书写Makefile.am文件个一般格式。</p>
		<br />
		<a name="N10156">
				<strong>表 1Makefile.am一般格式</strong>
		</a>
		<br />
		<img height="351" alt="表 1Makefile.am一般格式" src="http://www-128.ibm.com/developerworks/cn/linux/l-makefile/images/table2.gif" width="522" border="0" />
		<br />
		<p>对于可执行文件和静态库类型，如果只想编译，不想安装到系统中，可以用noinst_PROGRAMS代替bin_PROGRAMS，noinst_LIBRARIES代替lib_LIBRARIES。</p>
		<p>Makefile.am还提供了一些全局变量供所有的目标体使用：</p>
		<br />
		<a name="N1016E">
				<strong>表 2 Makefile.am中可用的全局变量</strong>
		</a>
		<br />
		<img height="221" alt="表 2 Makefile.am中可用的全局变量" src="http://www-128.ibm.com/developerworks/cn/linux/l-makefile/images/table3.gif" width="523" border="0" />
		<br />
		<p>在Makefile.am中尽量使用相对路径，系统预定义了两个基本路径：</p>
		<br />
		<a name="N10183">
				<strong>表 3Makefile.am中可用的路径变量</strong>
		</a>
		<br />
		<img height="112" alt="表 3Makefile.am中可用的路径变量" src="http://www-128.ibm.com/developerworks/cn/linux/l-makefile/images/table4.gif" width="522" border="0" />
		<br />
		<p>在上文中我们提到过安装路径，automake设置了默认的安装路径：</p>
		<p>1) 标准安装路径</p>
		<p>默认安装路径为：$(prefix) = /usr/local，可以通过./configure --prefix=&lt;new_path&gt;的方法来覆盖。</p>
		<p>其它的预定义目录还包括：bindir = $(prefix)/bin, libdir = $(prefix)/lib, datadir = $(prefix)/share, sysconfdir = $(prefix)/etc等等。</p>
		<p>2) 定义一个新的安装路径</p>
		<p>比如test, 可定义testdir = $(prefix)/test, 然后test_DATA =test1 test2，则test1，test2会作为数据文件安装到$(prefix)/ /test目录下。</p>
		<p>我们首先需要在工程顶层目录下（即project/）创建一个Makefile.am来指明包含的子目录：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">SUBDIRS=src/lib src/ModuleA/apple/shell src/ModuleA/apple/core CURRENTPATH=$(shell /bin/pwd)INCLUDES=-I$(CURRENTPATH)/src/include -I$(CURRENTPATH)/src/ModuleA/apple/include export INCLUDES</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>由于每个源文件都会用到相同的头文件，所以我们在最顶层的Makefile.am中包含了编译源文件时所用到的头文件，并导出，见蓝色部分代码。</p>
		<p>我们将lib目录下的swap.c文件编译成libswap.a文件，被apple/shell/apple.c文件调用，那么lib目录下的Makefile.am如下所示：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">noinst_LIBRARIES=libswap.alibswap_a_SOURCES=swap.cINCLUDES=-I$(top_srcdir)/src/includ</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>细心的读者可能就会问：怎么表1中给出的是bin_LIBRARIES，而这里是noinst_LIBRARIES？这是因为如果只想编译，而不想安装到系统中，就用noinst_LIBRARIES代替bin_LIBRARIES，对于可执行文件就用noinst_PROGRAMS代替bin_PROGRAMS。对于安装的情况，库将会安装到$(prefix)/lib目录下，可执行文件将会安装到${prefix}/bin。如果想安装该库，则Makefile.am示例如下：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">bin_LIBRARIES=libswap.alibswap_a_SOURCES=swap.cINCLUDES=-I$(top_srcdir)/src/includeswapincludedir=$(includedir)/swapswapinclude_HEADERS=$(top_srcdir)/src/include/swap.h</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>最后两行的意思是将swap.h安装到${prefix}/include /swap目录下。</p>
		<p>接下来，对于可执行文件类型的情况，我们将讨论如何写Makefile.am？对于编译apple/core目录下的文件，我们写成的Makefile.am如下所示：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">noinst_PROGRAMS=testtest_SOURCES=test.c test_LDADD=$(top_srcdir)/src/ModuleA/apple/shell/apple.o $(top_srcdir)/src/lib/libswap.a test_LDFLAGS=-D_GNU_SOURCEDEFS+=-D_GNU_SOURCE#LIBS=-lpthread</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>由于我们的test.c文件在链接时，需要apple.o和libswap.a文件，所以我们需要在test_LDADD中包含这两个文件。对于Linux下的信号量/读写锁文件进行编译，需要在编译选项中指明-D_GNU_SOURCE。所以在test_LDFLAGS中指明。而test_LDFLAGS只是链接时的选项，编译时同样需要指明该选项，所以需要DEFS来指明编译选项，由于DEFS已经有初始值，所以这里用+=的形式指明。从这里可以看出，Makefile.am中的语法与Makefile的语法一致，也可以采用条件表达式。如果你的程序还包含其他的库，除了用AC_CHECK_LIB宏来指明外，还可以用LIBS来指明。</p>
		<p>如果你只想编译某一个文件，那么Makefile.am如何写呢？这个文件也很简单，写法跟可执行文件的差不多，如下例所示：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">noinst_PROGRAMS=appleapple_SOURCES=apple.cDEFS+=-D_GNU_SOURCE</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>我们这里只是欺骗automake，假装要生成apple文件，让它为我们生成依赖关系和执行命令。所以当你运行完automake命令后，然后修改apple/shell/下的Makefile.in文件，直接将LINK语句删除，即：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">…….clean-noinstPROGRAMS:	-test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)apple$(EXEEXT): $(apple_OBJECTS) $(apple_DEPENDENCIES) 	@rm -f apple$(EXEEXT)#$(LINK) $(apple_LDFLAGS) $(apple_OBJECTS) $(apple_LDADD) $(LIBS)…….</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>通过上述处理，就可以达到我们的目的。从图1中不难看出为什么要修改Makefile.in的原因，而不是修改其他的文件。</p>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-makefile/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font face="Verdana" color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<span class="atitle">
						<a name="download">
								<strong>
										<font size="4">下载</font>
								</strong>
						</a>
				</span>
		</p>
		<table class="data-table-1" cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<th>名字</th>
								<th style="TEXT-ALIGN: right">大小</th>
								<th>下载方法</th>
						</tr>
						<tr>
								<td nowrap="">project.rar</td>
								<td style="TEXT-ALIGN: right" nowrap=""> </td>
								<td nowrap="">
										<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/linux/l-makefile/project.rar">
												<strong>
														<font face="Verdana" color="#5c81a7">HTTP</font>
												</strong>
										</a>
								</td>
						</tr>
				</tbody>
		</table> <img src ="http://www.cnitblog.com/flutist1225/aggbug/19995.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:26 <a href="http://www.cnitblog.com/flutist1225/articles/19995.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux 引导过程内幕</title><link>http://www.cnitblog.com/flutist1225/articles/19994.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:24:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19994.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19994.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19994.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19994.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19994.html</trackback:ping><description><![CDATA[
		<p>早期时，启动一台计算机意味着要给计算机喂一条包含引导程序的纸带，或者手工使用前端面板地址/数据/控制开关来加载引导程序。尽管目前的计算机已经装备了很多工具来简化引导过程，但是这一切并没有对整个过程进行必要的简化。</p>
		<p>让我们先从高级的视角来查看 Linux 引导过程，这样就可以看到整个过程的全貌了。然后将回顾一下在各个步骤到底发生了什么。在整个过程中，参考一下内核源代码可以帮助我们更好地了解内核源代码树，并在以后对其进行深入分析。</p>
		<p>
				<a name="N10064">
						<span class="atitle">
								<font size="4">概述</font>
						</span>
				</a>
		</p>
		<p>图 1 是我们在 20,000 英尺的高度看到的视图。</p>
		<br />
		<a name="fig1">
				<strong>图 1. Linux 引导过程在 20,000 英尺处的视图</strong>
		</a>
		<br />
		<img height="300" alt="Linux 引导过程在 20,000 英尺处的视图" src="http://www-128.ibm.com/developerworks/cn/linux/l-linuxboot/fig1.gif" width="529" />
		<br />
		<p>当系统首次引导时，或系统被重置时，处理器会执行一个位于已知位置处的代码。在个人计算机（PC）中，这个位置在基本输入/输出系统（BIOS）中，它保存在主板上的闪存中。嵌入式系统中的中央处理单元（CPU）会调用这个重置向量来启动一个位于闪存/ROM 中的已知地址处的程序。在这两种情况下，结果都是相同的。因为 PC 提供了很多灵活性，BIOS 必须确定要使用哪个设备来引导系统。稍后我们将详细介绍这个过程。</p>
		<p>当找到一个引导设备之后，第一阶段的引导加载程序就被装入 RAM 并执行。这个引导加载程序在大小上小于 512 字节（一个扇区），其作用是加载第二阶段的引导加载程序。 </p>
		<p>当第二阶段的引导加载程序被装入 RAM 并执行时，通常会显示一个动画屏幕，并将 Linux 和一个可选的初始 RAM 磁盘（临时根文件系统）加载到内存中。在加载映像时，第二阶段的引导加载程序就会将控制权交给内核映像，然后内核就可以进行解压和初始化了。在这个阶段中，第二阶段的引导加载程序会检测系统硬件、枚举系统链接的硬件设备、挂载根设备，然后加载必要的内核模块。完成这些操作之后启动第一个用户空间程序（<code>init</code>），并执行高级系统初始化工作。</p>
		<p>这就是 Linux 引导的整个过程。现在让我们深入挖掘一下这个过程，并深入研究一下 Linux 引导过程的一些详细信息。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-linuxboot/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N1008D">
						<span class="atitle">
								<font size="4">系统启动</font>
						</span>
				</a>
		</p>
		<p>系统启动阶段依赖于引导 Linux 系统上的硬件。在嵌入式平台中，当系统加电或重置时，会使用一个启动环境。这方面的例子包括 U-Boot、RedBoot 和 Lucent 的 MicroMonitor。嵌入式平台通常都是与引导监视器搭配销售的。这些程序位于目标硬件上的闪存中的某一段特殊区域，它们提供了将 Linux 内核映像下载到闪存并继续执行的方法。除了可以存储并引导 Linux 映像之外，这些引导监视器还执行一定级别的系统测试和硬件初始化过程。在嵌入式平台中，这些引导监视器通常会涉及第一阶段和第二阶段的引导加载程序。</p>
		<table cellspacing="0" cellpadding="0" width="40%" align="right" border="0">
				<tbody>
						<tr>
								<td width="10">
										<img height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" />
								</td>
								<td>
										<table cellspacing="0" cellpadding="5" width="100%" border="1">
												<tbody>
														<tr>
																<td bgcolor="#eeeeee">
																		<a name="N1009A">
																				<strong>提取 MBR 的信息</strong>
																		</a>
																		<br />
																		<p>要查看 MBR 的内容，请使用下面的命令：</p>
																		<code># <span class="boldcode"><strong><font face="Lucida Console">dd if=/dev/hda of=mbr.bin bs=512 count=1</font></strong></span> # <span class="boldcode"><strong><font face="Lucida Console">od -xa mbr.bin</font></strong></span></code>
																		<p>这个 <code>dd</code> 命令需要以 root 用户的身份运行，它从 /dev/hda（第一个 IDE 盘） 上读取前 512 个字节的内容，并将其写入 <code>mbr.bin</code> 文件中。<code>od</code> 命令会以十六进制和 ASCII 码格式打印这个二进制文件的内容。</p>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<p>在 PC 中，引导 Linux 是从 BIOS 中的地址 0xFFFF0 处开始的。BIOS 的第一个步骤是加电自检（POST）。POST 的工作是对硬件进行检测。BIOS 的第二个步骤是进行本地设备的枚举和初始化。</p>
		<p>给定 BIOS 功能的不同用法之后，BIOS 由两部分组成：POST 代码和运行时服务。当 POST 完成之后，它被从内存中清理了出来，但是 BIOS 运行时服务依然保留在内存中，目标操作系统可以使用这些服务。 </p>
		<p>要引导一个操作系统，BIOS 运行时会按照 CMOS 的设置定义的顺序来搜索处于活动状态并且可以引导的设备。引导设备可以是软盘、CD-ROM、硬盘上的某个分区、网络上的某个设备，甚至是 USB 闪存。</p>
		<p>通常，Linux 都是从硬盘上引导的，其中主引导记录（MBR）中包含主引导加载程序。MBR 是一个 512 字节大小的扇区，位于磁盘上的第一个扇区中（0 道 0 柱面 1 扇区）。当 MBR 被加载到 RAM 中之后，BIOS 就会将控制权交给 MBR。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-linuxboot/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N100C8">
						<span class="atitle">
								<font size="4">第一阶段引导加载程序</font>
						</span>
				</a>
		</p>
		<p>MBR 中的主引导加载程序是一个 512 字节大小的映像，其中包含程序代码和一个小分区表（参见图 2）。前 446 个字节是主引导加载程序，其中包含可执行代码和错误消息文本。接下来的 64 个字节是分区表，其中包含 4 个分区的记录（每个记录的大小是 16 个字节）。MBR 以两个特殊数字的字节（0xAA55）结束。这个数字会用来进行 MBR 的有效性检查。</p>
		<br />
		<a name="fig2">
				<strong>图 2. MBR 剖析</strong>
		</a>
		<br />
		<img height="469" alt="MBR 剖析" src="http://www-128.ibm.com/developerworks/cn/linux/l-linuxboot/fig2.gif" width="479" />
		<br />
		<p>主引导加载程序的工作是查找并加载次引导加载程序（第二阶段）。它是通过在分区表中查找一个活动分区来实现这种功能的。当找到一个活动分区时，它会扫描分区表中的其他分区，以确保它们都不是活动的。当这个过程验证完成之后，就将活动分区的引导记录从这个设备中读入 RAM 中并执行它。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-linuxboot/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N100E4">
						<span class="atitle">
								<font size="4">第二阶段引导加载程序</font>
						</span>
				</a>
		</p>
		<p>次引导加载程序（第二阶段引导加载程序）可以更形象地称为内核加载程序。这个阶段的任务是加载 Linux 内核和可选的初始 RAM 磁盘。</p>
		<table cellspacing="0" cellpadding="0" width="40%" align="right" border="0">
				<tbody>
						<tr>
								<td width="10">
										<img height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" />
								</td>
								<td>
										<table cellspacing="0" cellpadding="5" width="100%" border="1">
												<tbody>
														<tr>
																<td bgcolor="#eeeeee">
																		<a name="N100F1">
																				<strong>GRUB 阶段引导加载程序</strong>
																		</a>
																		<br />
																		<p>
																				<code>/boot/grub</code> 目录中包含了 <code>stage1</code>、<code>stage1.5</code> 和 <code>stage2</code> 引导加载程序，以及很多其他加载程序（例如，CR-ROM 使用的是 <code>iso9660_stage_1_5</code>）。</p>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<p>在 x86 PC 环境中，第一阶段和第二阶段的引导加载程序一起称为 Linux Loader（LILO）或 GRand Unified Bootloader（GRUB）。由于 LILO 有一些缺点，而 GRUB 克服了这些缺点，因此下面让我们就来看一下 GRUB。（有关 GRUB、LILO 和相关主题的更多内容，请参阅本文后面的 <a href="http://www-128.ibm.com/developerworks/cn/linux/l-linuxboot/index.html?ca=dwcn-newsletter-linux#resources"><font color="#996699">参考资料</font></a> 部分的内容。）</p>
		<p>关于 GRUB，很好的一件事情是它包含了有关 Linux 文件系统的知识。GRUB 不像 LILO 一样使用裸扇区，而是可以从 ext2 或 ext3 文件系统中加载 Linux 内核。它是通过将两阶段的引导加载程序转换成三阶段的引导加载程序来实现这项功能的。阶段 1 （MBR）引导了一个阶段 1.5 的引导加载程序，它可以理解包含 Linux 内核映像的特殊文件系统。这方面的例子包括 <code>reiserfs_stage1_5</code>（要从 Reiser 日志文件系统上进行加载）或 <code>e2fs_stage1_5</code>（要从 ext2 或 ext3 文件系统上进行加载）。当阶段 1.5 的引导加载程序被加载并运行时，阶段 2 的引导加载程序就可以进行加载了。</p>
		<p>当阶段 2 加载之后，GRUB 就可以在请求时显示可用内核列表（在 <code>/etc/grub.conf</code> 中进行定义，同时还有几个软符号链接 <code>/etc/grub/menu.lst</code> 和 <code>/etc/grub.conf</code>）。我们可以选择内核甚至修改附加内核参数。另外，我们也可以使用一个命令行的 shell 对引导过程进行高级手工控制。 </p>
		<p>将第二阶段的引导加载程序加载到内存中之后，就可以对文件系统进行查询了，并将默认的内核映像和 <code>initrd</code> 映像加载到内存中。当这些映像文件准备好之后，阶段 2 的引导加载程序就可以调用内核映像了。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-linuxboot/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N10137">
						<span class="atitle">
								<font size="4">内核</font>
						</span>
				</a>
		</p>
		<table cellspacing="0" cellpadding="0" width="40%" align="right" border="0">
				<tbody>
						<tr>
								<td width="10">
										<font size="4">
												<img height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" />
										</font>
								</td>
								<td>
										<table cellspacing="0" cellpadding="5" width="100%" border="1">
												<tbody>
														<tr>
																<td bgcolor="#eeeeee">
																		<a name="N10141">
																				<strong>GRUB 中的手工引导</strong>
																		</a>
																		<br />
																		<p>在 GRUB 命令行中，我们可以使用 <code>initrd</code> 映像引导一个特定的内核，方法如下：</p>
																		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
																				<tbody>
																						<tr>
																								<td>
																										<code>
																												<pre class="section">grub&gt; kernel /bzImage-2.6.14.2   [Linux-bzImage, setup=0x1400, size=0x29672e]grub&gt; initrd /initrd-2.6.14.2.img   [Linux-initrd @ 0x5f13000, 0xcc199 bytes]grub&gt; bootUncompressing Linux... Ok, booting the kernel.</pre>
																										</code>
																								</td>
																						</tr>
																				</tbody>
																		</table>
																		<br />
																		<p>如果您不知道要引导的内核的名称，只需使用斜线（/）然后按下 Tab 键即可。GRUB 会显示内核和 <code>initrd</code> 映像列表。</p>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<p>当内核映像被加载到内存中，并且阶段 2 的引导加载程序释放控制权之后，内核阶段就开始了。内核映像并不是一个可执行的内核，而是一个压缩过的内核映像。通常它是一个 zImage（压缩映像，小于 512KB）或一个 bzImage（较大的压缩映像，大于 512KB），它是提前使用 zlib 进行压缩过的。在这个内核映像前面是一个例程，它实现少量硬件设置，并对内核映像中包含的内核进行解压，然后将其放入高端内存中，如果有初始 RAM 磁盘映像，就会将它移动到内存中，并标明以后使用。然后该例程会调用内核，并开始启动内核引导的过程。</p>
		<p>当 bzImage（用于 i386 映像）被调用时，我们从 <code>./arch/i386/boot/head.S</code> 的 <code>start</code> 汇编例程开始执行（主要流程图请参看图 3）。这个例程会执行一些基本的硬件设置，并调用 <code>./arch/i386/boot/compressed/head.S</code> 中的 <code>startup_32</code> 例程。此例程会设置一个基本的环境（堆栈等），并清除 Block Started by Symbol（BSS）。然后调用一个叫做 <code>decompress_kernel</code> 的 C 函数（在 <code>./arch/i386/boot/compressed/misc.c</code> 中）来解压内核。当内核被解压到内存中之后，就可以调用它了。这是另外一个 <code>startup_32</code> 函数，但是这个函数在 <code>./arch/i386/kernel/head.S</code> 中。</p>
		<p>在这个新的 <code>startup_32</code> 函数（也称为清除程序或进程 0）中，会对页表进行初始化，并启用内存分页功能。然后会为任何可选的浮点单元（FPU）检测 CPU 的类型，并将其存储起来供以后使用。然后调用 <code>start_kernel</code> 函数（在 <code>init/main.c</code> 中），它会将您带入与体系结构无关的 Linux 内核部分。实际上，这就是 Linux 内核的 <code>main</code> 函数。</p>
		<br />
		<a name="fig3">
				<strong>图 3. Linux 内核 i386 引导的主要函数流程 </strong>
		</a>
		<br />
		<img height="288" alt="Linux 内核 i386 引导的主要函数流程 " src="http://www-128.ibm.com/developerworks/cn/linux/l-linuxboot/fig3.gif" width="443" />
		<br />
		<p>通过调用 <code>start_kernel</code>，会调用一系列初始化函数来设置中断，执行进一步的内存配置，并加载初始 RAM 磁盘。最后，要调用 <code>kernel_thread</code>（在 <code>arch/i386/kernel/process.c</code> 中）来启动 <code>init</code> 函数，这是第一个用户空间进程（user-space process）。最后，启动空任务，现在调度器就可以接管控制权了（在调用 <code>cpu_idle</code> 之后）。通过启用中断，抢占式的调度器就可以周期性地接管控制权，从而提供多任务处理能力。</p>
		<p>在内核引导过程中，初始 RAM 磁盘（<code>initrd</code>）是由阶段 2 引导加载程序加载到内存中的，它会被复制到 RAM 中并挂载到系统上。这个 <code>initrd</code> 会作为 RAM 中的临时根文件系统使用，并允许内核在没有挂载任何物理磁盘的情况下完整地实现引导。由于与外围设备进行交互所需要的模块可能是 <code>initrd</code> 的一部分，因此内核可以非常小，但是仍然需要支持大量可能的硬件配置。在内核引导之后，就可以正式装备根文件系统了（通过 <code>pivot_root</code>）：此时会将 <code>initrd</code> 根文件系统卸载掉，并挂载真正的根文件系统。</p>
		<table cellspacing="0" cellpadding="0" width="40%" align="right" border="0">
				<tbody>
						<tr>
								<td width="10">
										<img height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" />
								</td>
								<td>
										<table cellspacing="0" cellpadding="5" width="100%" border="1">
												<tbody>
														<tr>
																<td bgcolor="#eeeeee">
																		<a name="N101DB">
																				<strong>decompress_kernel 输出</strong>
																		</a>
																		<br />
																		<p>函数 <code>decompress_kernel</code> 就是显示我们通常看到的解压消息的地方： </p>
																		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
																				<tbody>
																						<tr>
																								<td>
																										<code>
																												<pre class="section">Uncompressing Linux... Ok, booting the kernel.</pre>
																										</code>
																								</td>
																						</tr>
																				</tbody>
																		</table>
																		<br />
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<code>initrd</code> 函数让我们可以创建一个小型的 Linux 内核，其中包括作为可加载模块编译的驱动程序。这些可加载的模块为内核提供了访问磁盘和磁盘上的文件系统的方法，并为其他硬件提供了驱动程序。由于根文件系统是磁盘上的一个<em>文件系统</em>，因此 <code>initrd</code> 函数会提供一种启动方法来获得对磁盘的访问，并挂载真正的根文件系统。在一个没有硬盘的嵌入式环境中，<code>initrd</code> 可以是最终的根文件系统，或者也可以通过网络文件系统（NFS）来挂载最终的根文件系统。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-linuxboot/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N101FD">
						<span class="atitle">
								<font size="4">Init</font>
						</span>
				</a>
		</p>
		<p>当内核被引导并进行初始化之后，内核就可以启动自己的第一个用户空间应用程序了。这是第一个调用的使用标准 C 库编译的程序。在此之前，还没有执行任何标准的 C 应用程序。</p>
		<p>在桌面 Linux 系统上，第一个启动的程序通常是 <code>/sbin/init</code>。但是这不是一定的。很少有嵌入式系统会需要使用 <code>init</code> 所提供的丰富初始化功能（这是通过 <code>/etc/inittab</code> 进行配置的）。在很多情况下，我们可以调用一个简单的 shell 脚本来启动必需的嵌入式应用程序。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-linuxboot/index.html?ca=dwcn-newsletter-linux#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N10216">
						<span class="atitle">
								<font size="4">结束语</font>
						</span>
				</a>
		</p>
		<p>与 Linux 本身非常类似，Linux 的引导过程也非常灵活，可以支持众多的处理器和硬件平台。最初，加载引导加载程序提供了一种简单的方法，不用任何花架子就可以引导 Linux。LILO 引导加载程序对引导能力进行了扩充，但是它却缺少文件系统的感知能力。最新一代的引导加载程序，例如 GRUB，允许 Linux 从一些文件系统（从 Minix 到 Reise）上进行引导。 </p>
		<br />
<img src ="http://www.cnitblog.com/flutist1225/aggbug/19994.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:24 <a href="http://www.cnitblog.com/flutist1225/articles/19994.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于Linux的USB设备的详细介绍 </title><link>http://www.cnitblog.com/flutist1225/articles/19993.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:23:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19993.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19993.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19993.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19993.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19993.html</trackback:ping><description><![CDATA[ <strong>引言<br /></strong><br />   通用串行总线(USB)是一种快速而灵活地连接配件与计算机工作站的接口，其应用非常广泛。Linux中除了包含对USB主机控制器的驱动，还含有USB设备控制器，尤其是集成在StrongARM SA1110处理器上的控制器的驱动。这些控制器驱动通过使用USB可使基于Linux的嵌入式系统与主机 (运行的可以是Linux，或不是)进行通信。这里提供三种方法给运行Linux操作系统的嵌入式系统增加USB支持，可采用其中一种与USB主机展开通信。<br /><br />   第一种，最复杂的设备采用专门编写的内核模块解析标准USB总线上通行的错综复杂的高层协议；相应的USB主机定制驱动和应用程序来完成连接。第二种，有些基于Linux的设备把总线当作一种简单的运行在主机上的点对点串行连接使用；主机应用程序采用主机操作系统提供的USB编程界面，而其外在表现则仿佛是在通过一种典型的串行端口进行通信。第三种，另有一些设备把USB看作一种以太网络，它们用主机作网关，把USB设备与办公LAN或 Internet相连接。通常的做法是使用专门的主机驱动实现它。<br /><br />   最佳方案的选择取决于研发所需时间，以及针对具体嵌入式应用，要把USB接口作成什么样。以下对这三种方法如何在基于Linux的USB设备上的应用逐一进行描述。本文是关于如何在基于Linux的照相机和PDA之类的USB设备上使用Linux的论述,在此，USB是指由方形连接器而非扁平矩形连接器构成的USB设备。<br /><br />   <strong>内核模块<br /></strong><br />   把USB加到基于Linux的设备上的第一种方法是编写一个定制的Linux内核模块。这种方法通常要求相应开发主机操作系统(Windows、Linux以及其它OS)的驱动。<br /><br />   借助定制内核模块在设备中的安装，可以进行文件系统仿真等，使嵌入式应用将其USB主机当作远程存储设备对待。这一方法的另一潜在用途是构成一种存储转发字符设备，从嵌入式应用程序中缓冲数据流，直到USB主机连接完成建立为止。<br /><br />   对于基于StrongARM的Linux设备，其USB应用内核模块调用sa1100_usb_open()，对管理芯片的板上USB设备控制器外设的内核代码进行初始化。然后该模块调用sa1100_usb_get_descriptor_ptr()和 sa1100_usb_set_string_descriptor()，通过枚举过程对USB主机的给定USB描述符进行设置。这些描述符包括设备供货商及产品的数字标识符、正文字符串等主机可用来对设备进行识别的信息。甚至有一个序列号域，以便主机唯一地识别设备或对USB上相同设备的多个实例加以区分。<br /><br />   内核模块必须在开始USB通信前完成USB描述符的建立，这是因为枚举过程由USB设备控制器驱动，一旦USB主机连上后会自动执行。一切准备就绪后，USB设备模块便调用sa1100_usb_start()，告诉内核接受来自主机的USB连接请求。如果模块在USB主机连上前调用 sa1100_set_configured_ callback()，那么内核将会在枚举过程结束时调用所提供的回调函数。回调函数能很好地对设备完成连接状态进行可视化指示。
<p>    如果USB通信不再需要，那么设备的内核模块便调用sa1100_usb_stop()，然后是 sa1100_usb_close()，关闭SA1100的USB控制器。<br /><br />   StrongARM USB控制器支持数据传输作业的bulk-in 和bulk-out。在从USB主机接收数据包时，内核模块调用sa1100_usb_recv()，把数据缓冲区和回调函数地址传递给它。然后内核的底层USB设备控制代码对来自主机的bulk-out包进行检索，把内容放于缓冲区中，并调用回调函数。<br /><br />   回调函数必须从接收缓冲区提取数据并保存于其它位置或者把缓冲区空间加到一个队列中，为下一个数据包的接收分配新的缓冲区。而后回调函数二次调用sa1100_usb_recv()，在需要时进行下一个数据包的接收。过程与对USB主机的数据传输相类似。在聚集起一帧的数据量后，内核模块将数据的地址、长度和回调地址传递给 sa1100_usb_send()。传输完成时，内核调用回调函数。<br /><br />  <strong> 主机<br /></strong><br />   主机端USB驱动的几个例子在主流的Linux版本以及 Linux内核档案组织分配的原始内核源中都有提供。用于Handspring Visor(drivers/usb/serial/visor.c)的模块是编写较为简洁易懂的模块之一，作为USB主机端模块的模板 (drivers/usb/usb-skeleton.c)使用。<br /><br />  <strong> 高速串行<br /></strong><br />   对于大多数实际应用来说， 可以把USB总线当作一种高速串行端口考虑。如此在某些类型的嵌入式设备和应用中对它进行原型模拟是有意义的。StrongARM处理器的Linux内核提供现成的USB设备驱动专工于此，称作usb-char。<br /><br />   在希望与USB主机通信时，Linux USB设备应用程序只是打开对其usb-char设备节点(字符型，最大10，最小240)的连接，然后开始读写数据即可。read()和 write()操作将一直返回错误值直到USB主机连上为止。一旦连接建立和枚举完成，便开始通信，就像USB是一种点对点串行端口一样。<br /><br />   由于这种USB数据传递方法十分直接且实用，因此usb-char设备得到高效使用。它还为其它USB通信方法的实现提供了重要的参照基准。<br /><br />   usb-char的实际动作从usbc_open()功能开始，部分内容示于列表1中。<br /><br />   列表1：打开USB上的串行连接<br /><br />   static int usbc_open(struct inode *pInode, struct file *pFile)<br /><br />   {<br /><br />   int retval = 0;<br /><br />   /* start usb core */<br /><br />   sa1100_usb_open(_sb-char?;<br /><br />   /* allocate memory for in-transit USB packets */<br /><br />   tx_buf = (char*) kmalloc(TX_PACKET_SIZE, GFP_KERNEL | GFP_DMA);<br /><br />   packet_buffer = (char*) kmalloc(RX_PACKET_SIZE, GFP_KERNEL | GFP_DMA);<br /><br />   /* allocate memory for the receive buffer; the contents of this<br /><br />   buffer are provided during read() */<br /><br />   rx_ring.buf = (char*) kmalloc(RBUF_SIZE, GFP_KERNEL);<br /><br />   /* set up USB descriptors */<br /><br />   twiddle_descriptors();<br /><br />   /* enable USB i/o */<br /><br />   sa1100_usb_start();<br /><br />   /* set up to receive a packet */<br /><br />   kick_start_rx();<br /><br />   return 0;</p><p>   twiddle_descriptors ()功能建立起设备的USB描述符。在描述符全部建起后，准备从USB主机枚举并接收一个数据帧。kick_start_rx()所需的代码大多数情况下只是一种对sa1100_usb_recv() 的调用以建立回调而已。当USB主机发送数据包时，设备的内核通过回调调用rx_done_callback_packet_buffer()函数，把数据包的内容移入usb-char 设备点上由read()返回的FIFO队列。<br /><br />   <strong>主机<br /></strong><br />   对于运行Linux的USB主机，usb-char相应的USB主机模块称为usbserial模块。大多数Linux版本都包括Usbserial模块，尽管通常不是自动装入。在USB与设备的连接建立之前，usbserial 由modprobe 或 insmod载入。<br /><br />   一旦USB设备开始枚举，主机上的应用程序便用usbserial设备点(字符型，最大188，最小0以上)之一与设备进行通信。这些节点通常命名为/dev/ttyUSBn。Usbserial模块在内核报文日志记录中报告它把哪个节点指定给USB设备使用：<br /><br />   usbserial.c: 通用转换器删除<br /><br />   usbserial.c: 通用转换器当前连到ttyUSB0上。连接建立后，USB主机上的应用程序便通过读写指定的节点与USB设备进行通信。<br /><br />   Linux 主机上usbserial模块的一种替代选择是一种称作libusb(libusb.sourceforge.net)的库。这种库使用低层内核系统调用进行USB数据传输，而不是通过usbserial模块，在某种程度上跨Linux内核版本建立和使用时更方便。Libusb库还提供大量有用的调试功能，这一点在对运行在USB链路上的复杂通信协议进行除错时有帮助。用libusb与采用usb-char的USB设备进行通信时，Linux主机应用程序使用usb_open()函数建立与该设备的连接。然后应用程序使用usb_bulk_read()和usb_bulk_write()与设备交换数据。<br /><br />   <strong>USB上的以太网<br /></strong><br />   另一种选择是把USB作为一种以太网络来对待。Linux具有在主机和设备端均可实现这种功能的模块。由于iPAQ硬件既没有可接入的串行端口也没有一种专用的网络接口，因此，iPAQ 的Linux内核专门采用这种通信策略，在StrongARM的Linux内核中，usb-eth模块(arch/arm/mach- sa1100/usb-eth.c)对用USB作物理媒介的虚构以太网设备进行仿真。一旦创建后，这一网络界面便被指定一个IP地址，否则作为通常的以太网硬件对待。一旦USB主机连上后，usb-eth模块便能使USB设备“看到” Internet(如果存在Internet的话)，ping测其它IP地址，甚至“谈论”DHCP, HTTP, NFS, telnet, 和e-mail。简言之，任何在实际的以太网界面上运行的应用将不折不扣地在usb-eth接口上得到实现，因为它们不能分辨出其正在使用的不是实在的以太网硬件。<br /><br />   <strong>主机<br /></strong><br />   在Linux主机上，相应的Ethernet-over-USB内核模块称为usbnet。当usbnet模块得到安装且设备的USB连接建立完成时，usbnet模块便针对主机端内核及用户应用创建一个与实际硬件酷似的虚构以太网界面，主机端应用程序通过运行设备IP地址 ping测，可以检查USB设备的存在。如果ping测成功，设备便加上了。<br /><br />   <strong>结语<br /></strong><br />   Linux不再只是USB主机使用，当今它也是USB设备的合适选择，Linux 下的USB通信是非常灵活和易用的。</p><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19993.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:23 <a href="http://www.cnitblog.com/flutist1225/articles/19993.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Intel E100 网卡驱动实例分析</title><link>http://www.cnitblog.com/flutist1225/articles/19992.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:22:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19992.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19992.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19992.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19992.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19992.html</trackback:ping><description><![CDATA[本来是做zero-copy的，顺便把分析记录写下来，供大家参考，如果有错误清大家多包涵。只挑重要的来说，一些细节的地方我也不大懂，要看芯片手册才行，我们作软件的就别看那么细了，最重要是把主要流程弄清除。 
<p> </p><ol><li><p>系统结构定义</p></li></ol><p>以下定义的结构，要保证长度是32bit的整数，也就是4bytes对齐，在自己添加成员的时候尤其小心。</p><p><font color="#0000ff">struct</font> cb 字面理解为control block；</p><p><font color="#0000ff">struct</font> nic 网卡的基本信息，该结构是针对单个网卡的，而不是针对网卡驱动整个系统；<br /> </p><ol start="2"><li><p>子例程分析</p></li></ol><p><font color="#0000ff">static</font><font color="#0000ff">inline</font><font color="#0000ff">void</font> e100_enable_irq(<font color="#0000ff">struct</font> nic *nic)</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    unsigned</font><font color="#0000ff">long</font> flags;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    spin_lock_irqsave(&amp;nic-&gt;cmd_lock, flags);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    writeb(irq_mask_none, &amp;nic-&gt;csr-&gt;scb.cmd_hi);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    spin_unlock_irqrestore(&amp;nic-&gt;cmd_lock, flags);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    e100_write_flush(nic);</p><p>}</p><p><font color="#0000ff">static</font><font color="#0000ff">inline</font><font color="#0000ff">void</font> e100_disable_irq(<font color="#0000ff">struct</font> nic *nic)</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    unsigned</font><font color="#0000ff">long</font> flags;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    spin_lock_irqsave(&amp;nic-&gt;cmd_lock, flags);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    writeb(irq_mask_all, &amp;nic-&gt;csr-&gt;scb.cmd_hi);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    spin_unlock_irqrestore(&amp;nic-&gt;cmd_lock, flags);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    e100_write_flush(nic);</p><p style="MARGIN-BOTTOM: 0cm" align="left">}</p><p>这两个函数看意思就是把nic指向的网卡的irq打开于关闭，在写寄存器的时候要spin_lock_irq；</p><p>e100_write_flush是把内容立即刷新，这里的做法比较简单，就是把pci的总线读一下，这样write的过程就被迫完成了。</p><ol start="3"><li><p>总体分析</p></li></ol><p>初始化过程：</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">int</font> e100_hw_init(<font color="#0000ff">struct</font> nic *nic)</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    int</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    e100_hw_reset(nic); // 作芯片的复位</p><p style="MARGIN-BOTTOM: 0cm" align="left">    DPRINTK(HW, ERR, "e100_hw_init\n");</p><p style="MARGIN-BOTTOM: 0cm" align="left">// 如果是中断期间，返回错误</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(!<font color="#ff0000">in_interrupt</font>() &amp;&amp; (err = e100_self_test(nic)))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = e100_phy_init(nic))) // 芯片的初始化，以及后面执行了各种命令</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = e100_exec_cmd(nic, cuc_load_base, 0)))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = e100_exec_cmd(nic, ruc_load_base, 0)))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = <span id="highlight_tag" style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; PADDING-BOTTOM: 0px; COLOR: #ee6600; PADDING-TOP: 0px; BACKGROUND-COLOR: yellow; EE6600: ">e100_exec_cb</span>(nic, NULL, e100_load_ucode)))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = <span id="highlight_tag" style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; PADDING-BOTTOM: 0px; COLOR: #ee6600; PADDING-TOP: 0px; BACKGROUND-COLOR: yellow; EE6600: ">e100_exec_cb</span>(nic, NULL, e100_configure)))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = <span id="highlight_tag" style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; PADDING-BOTTOM: 0px; COLOR: #ee6600; PADDING-TOP: 0px; BACKGROUND-COLOR: yellow; EE6600: ">e100_exec_cb</span>(nic, NULL, e100_setup_iaaddr)))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = e100_exec_cmd(nic, cuc_dump_addr,</p><p style="MARGIN-BOTTOM: 0cm" align="left">                nic-&gt;dma_addr + offsetof(<font color="#0000ff">struct</font> mem, stats))))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = e100_exec_cmd(nic, cuc_dump_reset, 0)))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#ff0000">    e100_disable_irq(nic); // 关闭中断</font></p><p>}</p><p> </p><p><font color="#0000ff">static</font><font color="#0000ff">void</font> e100_watchdog(<font color="#0000ff">unsigned</font><font color="#0000ff">long</font> data)</p><p>{</p><p>…</p><p>// 根据MII的监测工具进行监测，如果发现有网卡动作，则调整统计信息，把网卡设置成up/down状态</p><p style="MARGIN-BOTTOM: 0cm" align="left">    mii_ethtool_gset(&amp;nic-&gt;mii, &amp;cmd);</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(mii_link_ok(&amp;nic-&gt;mii) &amp;&amp; !<font color="#ff0000">netif_carrier_ok</font>(nic-&gt;netdev)) {</p><p style="MARGIN-BOTTOM: 0cm" align="left">        DPRINTK(LINK, INFO, "link up, %sMbps, %s-duplex\n",</p><p style="MARGIN-BOTTOM: 0cm" align="left">                cmd.speed == SPEED_100 ? "100" : "10",</p><p style="MARGIN-BOTTOM: 0cm" align="left">                cmd.duplex == DUPLEX_FULL ? "full" : "half");</p><p style="MARGIN-BOTTOM: 0cm" align="left">    } <font color="#0000ff">else</font><font color="#0000ff">if</font>(!mii_l    ink_ok(&amp;nic-&gt;mii) &amp;&amp; <font color="#ff0000">netif_carrier_ok</font>(nic-&gt;netdev)) {</p><p style="MARGIN-BOTTOM: 0cm" align="left">        DPRINTK(LINK, INFO, "link down\n");</p><p style="MARGIN-BOTTOM: 0cm" align="left">}</p><p style="MARGIN-BOTTOM: 0cm" align="left"> </p><p style="MARGIN-BOTTOM: 0cm" align="left">    mii_check_link(&amp;nic-&gt;mii);</p><p>…</p><p><font color="#ff0000">// 最后，watch_dog不是做一次，所以做完了这次，要用mod_timer启动下一次检查</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#ff0000">    mod_timer(&amp;nic-&gt;watchdog, jiffies + E100_WATCHDOG_PERIOD);</font></p><p>}</p><p>发包过程：</p><p><font color="#0000ff">static</font><font color="#0000ff">inline</font><font color="#0000ff">int</font> e100_tx_clean(<font color="#0000ff">struct</font> nic *nic) // 对发包队列进行清理</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    struct</font> cb *cb;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    int</font> tx_cleaned = 0;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    spin_lock(&amp;nic-&gt;cb_lock); // 要上锁，其实我觉得这里会影响速度；但是100M网卡，影响也不大，对1000M网卡，这样肯定不行</p><p style="MARGIN-BOTTOM: 0cm" align="left">    DPRINTK(TX_DONE, DEBUG, "cb-&gt;status = 0x%04X\n",</p><p style="MARGIN-BOTTOM: 0cm" align="left">    nic-&gt;cb_to_clean-&gt;status);</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Clean CBs marked complete */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    for</font>(cb = nic-&gt;cb_to_clean;</p><p style="MARGIN-BOTTOM: 0cm" align="left">            cb-&gt;status &amp; <font color="#ff0000">cpu_to_le16</font>(cb_complete); // 把CPU字节转成机器字节</p><p style="MARGIN-BOTTOM: 0cm" align="left">            cb = nic-&gt;cb_to_clean = cb-&gt;next) {</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        if</font>(likely(cb-&gt;skb != NULL)) {</p><p style="MARGIN-BOTTOM: 0cm" align="left">            nic-&gt;net_stats.tx_packets++;</p><p style="MARGIN-BOTTOM: 0cm" align="left">            nic-&gt;net_stats.tx_bytes += cb-&gt;skb-&gt;len;</p><p style="MARGIN-BOTTOM: 0cm" align="left">            pci_unmap_single( nic-&gt;pdev, // 解除PCI通道的DMA映射</p><p style="MARGIN-BOTTOM: 0cm" align="left">                    le32_to_cpu(cb-&gt;u.tcb.tbd.buf_addr),</p><p style="MARGIN-BOTTOM: 0cm" align="left">                    le16_to_cpu(cb-&gt;u.tcb.tbd.size),</p><p style="MARGIN-BOTTOM: 0cm" align="left">                    PCI_DMA_TODEVICE);</p><p style="MARGIN-BOTTOM: 0cm" align="left">            dev_kfree_skb_any(cb-&gt;skb); // 才可以释放skb</p><p style="MARGIN-BOTTOM: 0cm" align="left">            cb-&gt;skb = NULL; // 把指针设置为空，要用这个作判断，所以还是C++好</p><p style="MARGIN-BOTTOM: 0cm" align="left">            tx_cleaned = 1;</p><p style="MARGIN-BOTTOM: 0cm" align="left">        }</p><p style="MARGIN-BOTTOM: 0cm" align="left">        cb-&gt;status = 0;</p><p style="MARGIN-BOTTOM: 0cm" align="left">        nic-&gt;cbs_avail++;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    }</p><p style="MARGIN-BOTTOM: 0cm" align="left">    spin_unlock(&amp;nic-&gt;cb_lock);<br /> </p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Recover from running out of Tx resources in xmit_frame */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(unlikely(tx_cleaned &amp;&amp; netif_queue_stopped(nic-&gt;netdev)))</p><p style="MARGIN-BOTTOM: 0cm" align="left">        netif_wake_queue(nic-&gt;netdev); // 唤醒该网卡的等待队列<br /> </p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    return</font> tx_cleaned;</p><p>}</p><p>控制队列的操作，原理和上面一样：</p><p><font color="#0000ff">static</font><font color="#0000ff">void</font> e100_clean_cbs(<font color="#0000ff">struct</font> nic *nic)</p><p><font color="#0000ff">static</font><font color="#0000ff">int</font> e100_alloc_cbs(<font color="#0000ff">struct</font> nic *nic)</p><p>启动接收过程</p><p><font color="#0000ff">static</font><font color="#0000ff">inline</font><font color="#0000ff">void</font> e100_start_receiver(<font color="#0000ff">struct</font> nic *nic, <font color="#0000ff">struct</font> rx *rx)</p><p>给收包过程分配skb，这个是非常重要的过程，主要完成skb的分配工作，如果rx队列没有skb，则new一个，否则把状态同步一下，然后直接使用旧的skb，用于提高效率。分配好的skb要作pci_map动作，就是把内存挂在网卡的DMA通道，等有中断发生，内存就是网络数据包了，效验的动作在后面会作。</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">inline</font><font color="#0000ff">int</font> e100_rx_alloc_skb(<font color="#0000ff">struct</font> nic *nic, <font color="#0000ff">struct</font> rx *rx)</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left">// 分配skb</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(!(rx-&gt;skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN)))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    return</font> -ENOMEM;</p><p style="MARGIN-BOTTOM: 0cm" align="left"> </p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Align, init, and map the RFD. */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left">    rx-&gt;skb-&gt;dev = nic-&gt;netdev;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    skb_reserve(rx-&gt;skb, NET_IP_ALIGN); // 保留IP对齐，用于VLAN的偏移，一般是2个字节</p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong>    memcpy(rx-&gt;skb-&gt;data, &amp;nic-&gt;blank_rfd, <font color="#0000ff">sizeof</font>(<font color="#0000ff">struct</font> rfd));</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong>// 在skb-&gt;data保留了一段内存作RFD，应该是状态寄存器，e100网卡的DMA通道前面的内存是用于做状态标志的，实际测试是16个字节</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left">    rx-&gt;dma_addr = pci_map_single(nic-&gt;pdev, rx-&gt;skb-&gt;data,</p><p style="MARGIN-BOTTOM: 0cm" align="left">                RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);</p><p style="MARGIN-BOTTOM: 0cm" align="left">// 映射到PCI的DMA通道，这样有中断发生就可以直接送到内存(skb-&gt;data)</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(pci_dma_mapping_error(rx-&gt;dma_addr)) {</p><p style="MARGIN-BOTTOM: 0cm" align="left">        dev_kfree_skb_any(rx-&gt;skb);</p><p style="MARGIN-BOTTOM: 0cm" align="left">        rx-&gt;skb = 0;</p><p style="MARGIN-BOTTOM: 0cm" align="left">        rx-&gt;dma_addr = 0;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> -ENOMEM;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    }</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Link the RFD to end of RFA by linking previous RFD to</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">* this one, and clearing EL bit of previous. */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(rx-&gt;prev-&gt;skb) {<font color="#ff0000"> // 如果prev队列没有给释放，太好了，直接把状态清除就可以了</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        struct</font> rfd *prev_rfd = (<font color="#0000ff">struct</font> rfd *)rx-&gt;prev-&gt;skb-&gt;data;</p><p style="MARGIN-BOTTOM: 0cm" align="left">        put_unaligned(cpu_to_le32(rx-&gt;dma_addr),</p><p style="MARGIN-BOTTOM: 0cm" align="left">                (u32 *)&amp;prev_rfd-&gt;link);</p><p style="MARGIN-BOTTOM: 0cm" align="left">        wmb();</p><p style="MARGIN-BOTTOM: 0cm" align="left">        prev_rfd-&gt;command &amp;= ~cpu_to_le16(cb_el);</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#ff0000"><strong>       pci_dma_sync_single_for_device(nic-&gt;pdev, rx-&gt;prev-&gt;dma_addr,</strong></font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#ff0000"><strong>       sizeof(struct rfd), PCI_DMA_TODEVICE);</strong></font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#ff0000"><strong>// DMA通道同步，把状态寄存器与外面的内存同步一下</strong></font></p><p style="MARGIN-BOTTOM: 0cm" align="left">    }</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    return</font> 0;</p><p style="MARGIN-BOTTOM: 0cm" align="left">}</p><p>// 主要的收包过程，有中断发生后，这个函数把接收的包首先解除PCI_DMA映射，然后纠错，最后要把包送到协议栈</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">inline</font><font color="#0000ff">int</font> e100_rx_indicate(<font color="#0000ff">struct</font> nic *nic, <font color="#0000ff">struct</font> rx *rx,</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">unsigned</font><font color="#0000ff">int</font> *work_done, <font color="#0000ff">unsigned</font><font color="#0000ff">int</font> work_to_do)</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    struct</font> sk_buff *skb = rx-&gt;skb;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    struct</font> rfd *rfd = (<font color="#0000ff">struct</font> rfd *)skb-&gt;data;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    u16 rfd_status, actual_size;</p><p style="MARGIN-BOTTOM: 0cm" align="left"> </p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(unlikely(work_done &amp;&amp; *work_done &gt;= work_to_do))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> -EAGAIN;</p><p style="MARGIN-BOTTOM: 0cm" align="left"> </p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Need to sync before taking a peek at cb_complete bit */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000"><strong>// 同步一下状态，也就是skb前16字节的内存，后面根据rdf_status判断包是否收全了</strong></font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong>    pci_dma_sync_single_for_cpu(nic-&gt;pdev, rx-&gt;dma_addr,</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong><font color="#0000ff">                sizeof</font>(<font color="#0000ff">struct</font> rfd), PCI_DMA_FROMDEVICE);</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong>    rfd_status = le16_to_cpu(rfd-&gt;status);</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"> </p><p style="MARGIN-BOTTOM: 0cm" align="left">    DPRINTK(RX_STATUS, DEBUG, "status=0x%04X\n", rfd_status);</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* If data isn't ready, nothing to indicate */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(unlikely(!(rfd_status &amp; cb_complete)))</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> -ENODATA;<br /> </p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Get actual data size */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left">    actual_size = le16_to_cpu(rfd-&gt;actual_size) &amp; 0x3FFF;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong>// 判断包是否收全</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(unlikely(actual_size &gt; RFD_BUF_LEN - <font color="#0000ff">sizeof</font>(<font color="#0000ff">struct</font> rfd)))</p><p style="MARGIN-BOTTOM: 0cm" align="left">        actual_size = RFD_BUF_LEN - <font color="#0000ff">sizeof</font>(<font color="#0000ff">struct</font> rfd);</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Get data */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000"><strong>// 解除DMA映射，这样skb-&gt;data就可以自由了</strong></font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong>    pci_unmap_single(nic-&gt;pdev, rx-&gt;dma_addr,</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong>                RFD_BUF_LEN, PCI_DMA_FROMDEVICE);</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* this allows for a fast restart without re-enabling interrupts */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(le16_to_cpu(rfd-&gt;command) &amp; cb_el)</p><p style="MARGIN-BOTTOM: 0cm" align="left">        nic-&gt;ru_running = RU_SUSPENDED;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Pull off the RFD and put the actual data (minus eth hdr) */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left">    skb_reserve(skb, <font color="#0000ff">sizeof</font>(<font color="#0000ff">struct</font> rfd)); // 如果是VLAN，把指针调整一下</p><p style="MARGIN-BOTTOM: 0cm" align="left">    skb_put(skb, actual_size);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    skb-&gt;protocol = eth_type_trans(skb, nic-&gt;netdev);</p><p style="MARGIN-BOTTOM: 0cm" align="left">// 作错包乱包检查</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(unlikely(!(rfd_status &amp; cb_ok))) {</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Don't indicate if hardware indicates errors */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left">        nic-&gt;net_stats.rx_dropped++;</p><p style="MARGIN-BOTTOM: 0cm" align="left">        dev_kfree_skb_any(skb);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    } <font color="#0000ff">else</font><font color="#0000ff">if</font>(actual_size &gt; ETH_DATA_LEN + VLAN_ETH_HLEN) {</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* Don't indicate oversized frames */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left">        nic-&gt;rx_over_length_errors++;</p><p style="MARGIN-BOTTOM: 0cm" align="left">        nic-&gt;net_stats.rx_dropped++;</p><p style="MARGIN-BOTTOM: 0cm" align="left">        dev_kfree_skb_any(skb);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    } <font color="#0000ff">else</font> {</p><p style="MARGIN-BOTTOM: 0cm" align="left">// 终于正确收到了，统计数据都要作下增加</p><p style="MARGIN-BOTTOM: 0cm" align="left">    nic-&gt;net_stats.rx_packets++;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    nic-&gt;net_stats.rx_bytes += actual_size;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    nic-&gt;netdev-&gt;last_rx = jiffies;</p><p style="MARGIN-BOTTOM: 0cm" align="left">// 送到协议栈</p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong><font color="#0000ff">    #ifdef</font> CONFIG_E100_NAPI</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong>        netif_receive_skb(skb); // NAPI的poll方式，使用软中断</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff"><strong>   #else</strong></font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><strong>       netif_rx(skb); // 普通的中断方式，使用硬中断</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff"><strong>   #endif</strong></font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        if</font>(work_done)</p><p style="MARGIN-BOTTOM: 0cm" align="left">            (*work_done)++;</p><p style="MARGIN-BOTTOM: 0cm" align="left">   }</p><p style="MARGIN-BOTTOM: 0cm" align="left">    rx-&gt;skb = NULL;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    return</font> 0;</p><p style="MARGIN-BOTTOM: 0cm" align="left">}</p><p> </p><p>// 收报skb的清除</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">inline</font><font color="#0000ff">void</font> e100_rx_clean(<font color="#0000ff">struct</font> nic *nic, <font color="#0000ff">unsigned</font><font color="#0000ff">int</font> *work_done,</p><p><font color="#0000ff">            unsigned</font><font color="#0000ff">int</font> work_to_do)</p><p> </p><p>// 下面这两个函数针对收报队列的管理，也就是调用e100_rx_clean, e100_rx_alloc_skb，用户状态的链表，实际上比较简单，如果哪个给送走了，就检查，再分配一个；</p><p>// 因为e100是百兆网卡，所以只有一个用户太的skb管理队列，e1000系列的则硬件中维护另外一个队列，一次可以map 1024个skb</p><p><font color="#0000ff">static</font><font color="#0000ff">void</font> e100_rx_clean_list(<font color="#0000ff">struct</font> nic *nic)</p><p><font color="#0000ff">static</font><font color="#0000ff">int</font> e100_rx_alloc_list(<font color="#0000ff">struct</font> nic *nic)</p><p> </p><p>// 初始化中断</p><p><font color="#0000ff">static</font> irqreturn_t e100_intr(<font color="#0000ff">int</font> irq, <font color="#0000ff">void</font> *dev_id, <font color="#0000ff">struct</font> pt_regs *regs)</p><p> </p><p>设置POLL的函数：</p><p> </p><p><font color="#0000ff">static</font><font color="#0000ff">int</font> e100_poll(<font color="#0000ff">struct</font> net_device *netdev, <font color="#0000ff">int</font> *budget)</p><p><font color="#0000ff">static</font><font color="#0000ff">void</font> e100_netpoll(<font color="#0000ff">struct</font> net_device *netdev)</p><p> </p><p>网卡启动：</p><p style="MARGIN-BOTTOM: 0cm" align="left">对应ifconfig eth0 up这样的命令</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">int</font> e100_up(<font color="#0000ff">struct</font> nic *nic)</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    int</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = e100_rx_alloc_list(nic))) // 分配收包队列</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> err;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = e100_alloc_cbs(nic))) // 分配控制队列</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        goto</font> err_rx_clean_list;</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = e100_hw_init(nic))) // 硬件初始化</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        goto</font> err_clean_cbs;</p><p style="MARGIN-BOTTOM: 0cm" align="left">    e100_set_multicast_list(nic-&gt;netdev); // 多播？</p><p style="MARGIN-BOTTOM: 0cm" align="left">    e100_start_receiver(nic, 0); // 准备工作</p><p style="MARGIN-BOTTOM: 0cm" align="left">    mod_timer(&amp;nic-&gt;watchdog, jiffies); // 时间狗，自动检查网卡状态</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>((err = request_irq(nic-&gt;pdev-&gt;irq, e100_intr, SA_SHIRQ,</p><p style="MARGIN-BOTTOM: 0cm" align="left">                nic-&gt;netdev-&gt;name, nic-&gt;netdev))) // 请求IRQ分配</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        goto</font> err_no_irq;</p><p style="MARGIN-BOTTOM: 0cm" align="left">        netif_wake_queue(nic-&gt;netdev); // 唤醒网络队列，通知核心，这个网卡启动了</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        #ifdef</font> CONFIG_E100_NAPI</p><p style="MARGIN-BOTTOM: 0cm" align="left">            netif_poll_enable(nic-&gt;netdev); // NAPI方式，把pool使能 </p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* enable ints _after_ enabling poll, preventing a race between</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">* disable ints+schedule */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        #endif</font></p><p style="MARGIN-BOTTOM: 0cm" align="left">        e100_enable_irq(nic); // 使能中断，NAPI方式也需要，普通方式更需要</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">        return</font> 0;</p><p style="MARGIN-BOTTOM: 0cm" align="left">err_no_irq:</p><p style="MARGIN-BOTTOM: 0cm" align="left">        del_timer_sync(&amp;nic-&gt;watchdog);</p><p style="MARGIN-BOTTOM: 0cm" align="left">err_clean_cbs:</p><p style="MARGIN-BOTTOM: 0cm" align="left">        e100_clean_cbs(nic);</p><p style="MARGIN-BOTTOM: 0cm" align="left">err_rx_clean_list:</p><p style="MARGIN-BOTTOM: 0cm" align="left">        e100_rx_clean_list(nic);</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    return</font> err;</p><p>}</p><p> </p><p><strong>Ifconfig eth0 down</strong></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">void</font> e100_down(<font color="#0000ff">struct</font> nic *nic) // 对应e100_up的逆向操作，比较简单</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    #ifdef</font> CONFIG_E100_NAPI</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#008000">/* wait here for poll to complete */</font></p><p style="MARGIN-BOTTOM: 0cm" align="left">        netif_poll_disable(nic-&gt;netdev); </p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    #endif</font></p><p style="MARGIN-BOTTOM: 0cm" align="left">    netif_stop_queue(nic-&gt;netdev);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    e100_hw_reset(nic);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    free_irq(nic-&gt;pdev-&gt;irq, nic-&gt;netdev);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    del_timer_sync(&amp;nic-&gt;watchdog);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    netif_carrier_off(nic-&gt;netdev);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    e100_clean_cbs(nic);   </p><p style="MARGIN-BOTTOM: 0cm" align="left">    e100_rx_clean_list(nic);</p><p style="MARGIN-BOTTOM: 0cm" align="left">}<br /> </p><p style="MARGIN-BOTTOM: 0cm" align="left">Ethtools对应的函数，这里都列出来了</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">struct</font> ethtool_ops e100_ethtool_ops = {</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_settings = e100_get_settings,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .set_settings = e100_set_settings,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_drvinfo = e100_get_drvinfo,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_regs_len = e100_get_regs_len,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_regs = e100_get_regs,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_wol = e100_get_wol,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .set_wol = e100_set_wol,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_msglevel = e100_get_msglevel,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .set_msglevel = e100_set_msglevel,   </p><p style="MARGIN-BOTTOM: 0cm" align="left">    .nway_reset = e100_nway_reset,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_link = e100_get_link,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_eeprom_len = e100_get_eeprom_len,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_eeprom = e100_get_eeprom,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .set_eeprom = e100_set_eeprom,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_ringparam = e100_get_ringparam,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .set_ringparam = e100_set_ringparam,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .self_test_count = e100_diag_test_count,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .self_test = e100_diag_test,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_strings = e100_get_strings,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .phys_id = e100_phys_id,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_stats_count = e100_get_stats_count,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .get_ethtool_stats = e100_get_ethtool_stats,</p><p style="MARGIN-BOTTOM: 0cm" align="left">};</p><p> </p><p>// 对应标准网卡驱动程序的一些封装函数</p><p><font color="#0000ff">static</font><font color="#0000ff">int</font> e100_open(<font color="#0000ff">struct</font> net_device *netdev)</p><p><font color="#0000ff">static</font><font color="#0000ff">int</font> e100_close(<font color="#0000ff">struct</font> net_device *netdev)</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">int</font> __devinit e100_probe(<font color="#0000ff">struct</font> pci_dev *pdev,</p><p><font color="#0000ff">const</font><font color="#0000ff">struct</font> pci_device_id *ent)</p><p> </p><p><font color="#0000ff">static</font><font color="#0000ff">void</font> __devexit e100_remove(<font color="#0000ff">struct</font> pci_dev *pdev)</p><p><font color="#0000ff">static</font><font color="#0000ff">int</font> e100_suspend(<font color="#0000ff">struct</font> pci_dev *pdev, u32 state)</p><p><font color="#0000ff">static</font><font color="#0000ff">int</font> e100_resume(<font color="#0000ff">struct</font> pci_dev *pdev)</p><p><font color="#0000ff">static</font><font color="#0000ff">void</font> e100_shutdown(<font color="#0000ff">struct</font> device *dev)</p><p> </p><p>// 这个是网卡驱动的函数表，每个网卡都有的</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">struct</font> pci_driver e100_driver = {</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .name = DRV_NAME,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .id_table = e100_id_table,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .probe = e100_probe,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    .remove = __devexit_p(e100_remove),</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    #ifdef</font> CONFIG_PM</p><p style="MARGIN-BOTTOM: 0cm" align="left">        .suspend = e100_suspend,</p><p style="MARGIN-BOTTOM: 0cm" align="left">        .resume = e100_resume,</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    #endif</font></p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    #if</font> ( LINUX_VERSION_CODE &gt;= KERNEL_VERSION(2,6,0) ) </p><p style="MARGIN-BOTTOM: 0cm" align="left">        .driver = {</p><p style="MARGIN-BOTTOM: 0cm" align="left">        .shutdown = e100_shutdown,</p><p style="MARGIN-BOTTOM: 0cm" align="left">    }</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    #endif</font></p><p style="MARGIN-BOTTOM: 0cm" align="left">};</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">int</font> __init e100_init_module(<font color="#0000ff">void</font>)</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    if</font>(((1 &lt;&lt; debug) - 1) &amp; NETIF_MSG_DRV) {</p><p style="MARGIN-BOTTOM: 0cm" align="left">        printk(KERN_INFO PFX "%s, %s\n", DRV_DESCRIPTION, DRV_VERSION);</p><p style="MARGIN-BOTTOM: 0cm" align="left">        printk(KERN_INFO PFX "%s\n", DRV_COPYRIGHT);</p><p style="MARGIN-BOTTOM: 0cm" align="left">    }</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">    return</font> pci_module_init(&amp;e100_driver);</p><p style="MARGIN-BOTTOM: 0cm" align="left">}</p><p style="MARGIN-BOTTOM: 0cm" align="left"><font color="#0000ff">static</font><font color="#0000ff">void</font> __exit e100_cleanup_module(<font color="#0000ff">void</font>)</p><p style="MARGIN-BOTTOM: 0cm" align="left">{</p><p style="MARGIN-BOTTOM: 0cm" align="left">    pci_unregister_driver(&amp;e100_driver);</p><p style="MARGIN-BOTTOM: 0cm" align="left">}</p><p style="MARGIN-BOTTOM: 0cm" align="left">// 模块标准函数</p><p style="MARGIN-BOTTOM: 0cm" align="left">module_init(e100_init_module);</p><p>module_exit(e100_cleanup_module);</p> <br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19992.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:22 <a href="http://www.cnitblog.com/flutist1225/articles/19992.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ALinux网桥的实现分析与使用</title><link>http://www.cnitblog.com/flutist1225/articles/19991.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:20:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19991.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19991.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19991.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19991.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19991.html</trackback:ping><description><![CDATA[
		<p>
				<a name="N10042">
						<span class="atitle">
								<font size="4">一、Linux内核网桥的实现分析</font>
						</span>
				</a>
		</p>
		<p>Linux 内核分别在2.2 和 2.4内核中实现了网桥。但是2.2 内核和 2.4内核的实现有很大的区别，2.4中的实现几乎是全部重写了所有的实现代码。本文以2.4.0内核版本为例进行分析。 </p>
		<p>在分析具体的实现之前，先描述几个概念，有助于对网桥的功能及实现有更深的理解。</p>
		<ol>
				<li>冲突域 
<p>一个冲突域由所有能够看到同一个冲突或者被该冲突涉及到的设备组成。以太网使用C S M A / C D（Carrier Sense Multiple Access with Collision Detection，带有冲突监测的载波侦听多址访问）技术来保证同一时刻，只有一个节点能够在冲突域内传送数据。网桥或者交换机，构成了一个冲突域的边界。缺省情况下，网桥中的每个端口实际上就是一个冲突域的结束点。</p></li>
				<li>广播域 
<p>一个广播域由所有能够看到一个广播数据包的设备组成。一个路由器，构成一个广播域的边界。网桥能够延伸到的最大范围就是一个广播域。缺省的情况下，一个网桥或交换机的所有端口在同一个广播域中。VLAN技术可以把交换机或者网桥的不同端口分割成不同的广播域。一般情况下， 一个广播域代表一个逻辑网段。</p></li>
				<li>网桥中的CAM表 
<p>网桥和交换机一样，为了能够实现对数据包的转发，网桥保存着许多（MAC，端口）项。所有的这些项组成一个表，叫做CAM表。每个项有超时机制，如果一定时间内未接收到以这个MAC为源MAC地址的数据包，这个项就会被删除。</p></li>
		</ol>
		<br />
		<a name="N10065">
				<strong>图1：一个交换网络的逻辑图</strong>
		</a>
		<br />
		<img alt="" src="http://www-128.ibm.com/developerworks/cn/linux/kernel/l-netbr/images/image001.gif" />
		<br />
		<p>在Linux内核网桥的实现中，一个逻辑网段用net_bridge结构体表示。一个逻辑网段需要保留的信息有：</p>
		<ol>
				<li>本逻辑网段中所有的端口(port_list) 
<p>每个端口用net_bridge_port结构体来表示，从net_bridge_port结构体中可以看出，它主要有:</p><ol><li>逻辑网段中的下一个端口(next) 
</li><li>本端口所属的逻辑网段(br) 
</li><li>本端口所指向的物理网卡（dev） 
</li><li>本端口在网桥中的编号(port_no) 
</li><li>用于生成树管理的信息 </li></ol></li>
				<li>
						<p>一个逻辑网段中可以具有很多个端口，所有的端口都挂在以port_list为链表头的链表上。</p>本网段中CAM表（hash[BR_HASH_SIZE]） 
<p>CAM表中的每个项用net_bridge_fdb_entry结构体代表，每项中有：</p><ol><li>用于CAM表连接的链表指针（next_hash，pprev_hash） 
</li><li>此项当前的引用计数（use_count） 
</li><li>MAC地址（addr） 
</li><li>此项所对应的端口（dst） 
</li><li>处理MAC超时（ageing_timer） 
</li><li>是否是本机的MAC地址（is_local） 
</li><li>是否是静态MAC地址（is_static） </li></ol></li>
				<li>
						<p>一个逻辑网段中的所有表项形成一个CAM表，他们之间的组织关系是一个HASH链表。HASH链的个数为BR_HASH_SIZE（256）。</p>本逻辑网段用于和外部通信的虚拟网络设备（dev） 
<p>Linux网桥可以在网桥上为每个逻辑网段配置一个IP，用于和外部通信。实际上这个IP不是配置在一个特定的物理网卡上面， 而是建立一个虚拟的网卡，虚拟网卡可以附在每个同一逻辑网段的物理网卡上，让这个网卡可以象所有的物理网卡一样工作。从而使网桥可以和外部通信。</p></li>
				<li>本逻辑网段虚拟网卡的统计数据（statistics） 
<p>按照Linux网卡驱动的接口，一个网卡的统计信息是由每个网卡的私有数据处理的。一般的写法是用dev-&gt;priv来指向每个网卡的统计数据。网卡的get_stats方法就是用来读取统计数据。</p></li>
				<li>用户一个网段的生成树(STP)信息 </li>
		</ol>
		<p>以上对几个结构体的描述和分析可以通过下图来表示：</p>
		<br />
		<a name="N100C9">
				<strong>图2：Linux网桥数据结构描述图</strong>
		</a>
		<br />
		<img alt="" src="http://www-128.ibm.com/developerworks/cn/linux/kernel/l-netbr/images/image002.gif" />
		<br />
		<p>描述了网桥的数据结构后，就可以开始数据包处理流程的分析。</p>
		<p>网桥处理包遵循着以下几条原则：</p>
		<ol>
				<li>在一个接口上接收到的包不会再在那个接口上发送这个数据包。 
</li>
				<li>每个接收到的数据包都要学习其源MAC地址。 
</li>
				<li>如果数据包是多播包或广播包，则要在同一个网段中除了接收端口外的其他所有端口发送这个数据包，如果上层协议栈对多播包感兴趣，则需要把数据包提交给上层协议栈。 
</li>
				<li>如果数据包的目的MAC地址不能在CAM表中找到，则要在同一个网段中除了接收端口外的其他所有端口发送这个数据包。 
</li>
				<li>如果能够在CAM表中查询到目的MAC地址，则在特定的端口上发送这个数据包，如果发送端口和接收端口是同一端口，则不发送。 </li>
		</ol>
		<p>在网络软中断处理函数net_rx_action中，嵌入了handle_bridge用于把数据包skb送入网桥模块处理。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)			if (skb-&gt;dev-&gt;br_port != NULL &amp;&amp;			    br_handle_frame_hook != NULL) {				handle_bridge(skb, pt_prev);				dev_put(rx_dev);				continue;			}#endif</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>br_handle_frame_hook是网桥处理接收到数据包的中入口，网桥初始化（br_init）的时候，把br_handle_frame_hook赋值为br_handle_frame。skb-&gt;dev-&gt;br_port用于判断接收到这个数据包的接口是否是网桥中的一个端口，如果是，skb-&gt;dev-&gt;br_port不为NULL，那么数据包应该由网桥处理。反之，数据包由上层协议栈处理。网桥中虚拟网卡对应的数据包就是在这个判断点时不再进入网桥。（实际上虚拟网卡并不会自己主动接收数据包，而是在网桥处理中把数据包向本地上层协议栈提交，并且修改了skb-&gt;dev，使得数据包不会多次进入桥处理代码）。</p>
		<p>前面提到，网桥处理接收包的入口是br_handle_frame(net/bridge/br_input.c)函数。</p>
		<p>br_handle_frame函数首先从skb中获得这个包属于的逻辑网段。然后调用__br_handle_frame进行转发处理。 br_handle_frame函数里有一个值得了解的地方，里面有一个加读锁。因为在转发中需要读CAM表，所以必须加读锁，避免在这个过程中另外的内核控制路径(如多处理机上另外一个CPU上的系统调用)修改CAM表。</p>
		<p>对输入包的转发决策都是在__br_handle_frame函数中。这个函数的处理可以分为以下几个部分：</p>
		<ol type="a">
				<li>如果网桥的虚拟网卡处于混杂模式，那么每个接收到的数据包都需要克隆一份送到AF_PACKET协议处理体(网络软中断函数net_rx_action中ptype_all链的处理)。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">if (br-&gt;dev.flags &amp; IFF_PROMISC) {		struct sk_buff *skb2;		skb2 = skb_clone(skb, GFP_ATOMIC);		if (skb2) {			passedup = 1;			br_pass_frame_up(br, skb2);		}	}</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
				<li>如果源MAC地址是多播或者是广播地址，那么这个包格式是错误的，简单的丢弃。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">		if (skb-&gt;mac.ethernet-&gt;h_source[0] &amp; 1)		goto freeandout;		</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
				<li>如果是一个多播包，则需要向本机的上层协议栈传送这个数据包（如果在之前没有向上提交的话，即passedup为0。如果为1，则前面已经发送了，现在就不需要提交了，在后面中的处理都是一样的）。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">	if (!passedup &amp;&amp;	    (dest[0] &amp; 1) &amp;&amp;	    (br-&gt;dev.flags &amp; IFF_ALLMULTI || br-&gt;dev.mc_list != NULL)) {		struct sk_buff *skb2;		skb2 = skb_clone(skb, GFP_ATOMIC);		if (skb2) {			passedup = 1;			br_pass_frame_up(br, skb2);		}	}	</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
				<li>如果启动了生成树协议，一个生成树包需要由生成树协议处理模块单独处理。如果不支持，则这个包的目的MAC肯定在CAM中查询不到，所以是向所有的端口发送（除接收口）。这样才不会影响整个网络的生成树协议运行。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">		if (br-&gt;stp_enabled &amp;&amp;	    !memcmp(dest, bridge_ula, 5) &amp;&amp;	    !(dest[5] &amp; 0xF0))		goto handle_special_frame;		</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
				<li>如果接收端口不是处于LEARNING或者FORWARDING，那么就学习这个包的源MAC地址，或者更新CAM表中相应项的定时器。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">if (p-&gt;state == BR_STATE_LEARNING ||	    p-&gt;state == BR_STATE_FORWARDING)		br_fdb_insert(br, p, skb-&gt;mac.ethernet-&gt;h_source, 0);</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
				<li>如果是一个多播包或广播包，则调用br_flood函数向每个口发送（除接收口）这个数据包。如果之前没有提交上层协议，则需要克隆一个包提交上层协议。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">	if (dest[0] &amp; 1) {		br_flood(br, skb, 1);		if (!passedup)			br_pass_frame_up(br, skb);		else			kfree_skb(skb);		return;	}	</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
				<li>用接收到数据包的目的MAC地址查询CAM表。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">dst = br_fdb_get(br, dest);</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
				<li>查询CAM表后，如果能够找到表项，并且目的MAC是到本机的虚拟网卡的，那么就需要把这个包提交给上层协议。网桥就是通过这个地方的处理和外部通信，实现远程管理的目的。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">	if (dst != NULL &amp;&amp; dst-&gt;is_local) {		if (!passedup)			br_pass_frame_up(br, skb);		else			kfree_skb(skb);		br_fdb_put(dst);		return;	}	</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
				<li>如果查询CAM表有结果，并且目的MAC不是到本地的，那么就通过调用br_forward发送到特定的端口。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">	if (dst != NULL) {		br_forward(dst-&gt;dst, skb);		br_fdb_put(dst);		return;	}	</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
				<li>如果在CAM表中查询不到数据包的目的MAC地址，那么就需要向别的每个端口发送这个数据包。调用br_flood来进行这个处理。 <br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">	br_flood(br, skb, 0);	return;	</font></code></pre></td></tr></tbody></table><br /><br /><br /></li>
		</ol>
		<p>在br_forward和br_flood函数中都必须判断源接口和目的接口是否是同一个，如果是同一端口，就不发送这个数据包。数据包的最后发送都是通过统一的发送接口dev_queue_xmit函数来完成的。</p>
		<p>以下就是数据包的处理流程：</p>
		<br />
		<a name="N101A3">
				<strong>图3：数据包处理流程图</strong>
		</a>
		<br />
		<img alt="" src="http://www-128.ibm.com/developerworks/cn/linux/kernel/l-netbr/images/image003.gif" />
		<br />
		<p>前面多次提到网桥的虚拟网卡，实际上在网桥中，这个网卡存在着一个net_device结构（在net_bridge里），但是不存在着实际的物理设备，而是附在网桥中每个物理网卡上面。这个虚拟网卡的支持函数在（br_device.c）。因为是虚拟的网卡，所以没有物理中断产生，每个需要发送到这个设备的数据包都是靠判断数据包的目的MAC地址来决定是否需要提交到本地上层协议栈（在__br_handle_frame判断）。</p>
		<p>如果数据包需要向上层协议提交，都调用br_pass_frame_up函数来处理。在这个函数中，首先把skb-&gt;dev设置成br-&gt;dev。然后再模拟在中断中处理数据包一样，进行相应的处理， 然后调用netif_rx放入接收队列。这里有一个要十分注意的地方，这个数据包的skb-&gt;dev已经变成br-&gt;dev。所以在网络接收软中断处理函数net_rx_action中不会再次进入handle_bridge了。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb){	br-&gt;statistics.rx_packets++;	br-&gt;statistics.rx_bytes += skb-&gt;len;	skb-&gt;dev = &amp;br-&gt;dev;	skb-&gt;pkt_type = PACKET_HOST;	skb_pull(skb, skb-&gt;mac.raw - skb-&gt;data);	skb-&gt;protocol = eth_type_trans(skb, &amp;br-&gt;dev);	netif_rx(skb);}</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<font face="Lucida Console">
												<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" />
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<font face="Lucida Console">
												<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
												<br />
										</font>
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<font face="Lucida Console">
																				<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																				<br />
																		</font>
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/linux/kernel/l-netbr/#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N101BA">
						<span class="atitle">
								<font size="4">二、配置内核 2.4 Linux 网桥</font>
						</span>
				</a>
		</p>
		<p>要配置网桥，首先需要网桥的配置工具bridge-utils。这个配置程序的源代码可以在 <a href="http://bridge.sourceforge.net/bridge-utils/" target="_blank"><font color="#5c81a7">http://bridge.sourceforge.net/bridge-utils/</font></a> 下载。编译成功之后，就可以生成网桥配置的主要工具brctl。 </p>
		<p>下面，我们将用brctl对以下网络拓扑配置网桥，使Linux能够对数据包进行交换。</p>
		<br />
		<img alt="" src="http://www-128.ibm.com/developerworks/cn/linux/kernel/l-netbr/images/image004.gif" />
		<br />
		<p>上图中，有五台主机。其中中间那台主机装有linux ，安装了网桥模块，而且有四块物理网卡，分别连接同一网段的其他主机。我们希望其成为一个网桥，为其他四台主机(IP分别为192.168.1.2 ，192.168.1.3，192.168.1.4，192.168.1.5) 之间转发数据包。同时，为了方便管理，希望网桥能够有一个IP（192.168.1.1），那样管理员就可以在192.168.1.0/24网段内的主机上telnet到网桥，对其进行配置，实现远程管理。</p>
		<p>前一节中提到，网桥在同一个逻辑网段转发数据包。针对上面的拓扑，这个逻辑网段就是192.168.1.0/24网段。我们为这个逻辑网段一个名称，br_192。首先需要配置这样一个逻辑网段。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"># brctl addbr br_192			(建立一个逻辑网段，名称为br_192)</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>实际上，我们可以把逻辑网段192.168.1.0/24看作使一个VLAN ，而br_192则是这个VLAN的名称。</p>
		<p>建立一个逻辑网段之后，我们还需要为这个网段分配特定的端口。在Linux中，一个端口实际上就是一个物理网卡。而每个物理网卡的名称则分别为eth0，eth1，eth2，eth3。我们需要把每个网卡一一和br_192这个网段联系起来，作为br_192中的一个端口。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"># brctl addif br_192 eth0			(让eth0成为br_192的一个端口)# brctl addif br_192 eth1			(让eth1成为br_192的一个端口)# brctl addif br_192 eth0			(让eth2成为br_192的一个端口)# brctl addif br_192 eth3			(让eth3成为br_192的一个端口)</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>网桥的每个物理网卡作为一个端口，运行于混杂模式，而且是在链路层工作，所以就不需要IP了。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"># ifconfig eth0 0.0.0.0# ifconfig eth1 0.0.0.0# ifconfig eth2 0.0.0.0# ifconfig eth3 0.0.0.0</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>然后给br_192的虚拟网卡配置IP：192.168.1.1。那样就能远程管理网桥。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"># ifconfig br_192 192.168.1.1</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>给br_192配置了IP之后，网桥就能够工作了。192.168.1.0/24网段内的主机都可以telnet到网桥上对其进行配置。</p>
		<p>以上配置的是一个逻辑网段，实际上Linux网桥也能配置成多个逻辑网段(相当于交换机中划分多个VLAN)。具体的方法可以参考bridge-util中的HOWTO。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/kernel/l-netbr/#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N10201">
						<span class="atitle">
								<font size="4">三、总结</font>
						</span>
				</a>
		</p>
		<p>本文分析了Linux网桥的实现，并且举例说明如何配置网桥。 通过学习网桥的实现，就能够了解网络中二层交换的原理。 </p>
		<p>网桥和交换机的功能非常相似，所以在分析网桥的时候，绝大多数情况下可以用交换机的处理方法来分析网桥的动作。</p> <br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19991.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:20 <a href="http://www.cnitblog.com/flutist1225/articles/19991.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>FreeSWAN 结构框架</title><link>http://www.cnitblog.com/flutist1225/articles/19990.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:19:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19990.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19990.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19990.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19990.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19990.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 目录																																																																																1																																												     																																						...&nbsp;&nbsp;<a href='http://www.cnitblog.com/flutist1225/articles/19990.html'>阅读全文</a><img src ="http://www.cnitblog.com/flutist1225/aggbug/19990.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:19 <a href="http://www.cnitblog.com/flutist1225/articles/19990.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux 2.6 调度系统分析</title><link>http://www.cnitblog.com/flutist1225/articles/19989.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:18:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19989.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19989.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19989.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19989.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19989.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 本文从 Linux 2.4 调度系统的缺陷入手，详细分析了 Linux 2.6 调度系统的原理和实现细节，并对与调度系统相关的负载平衡、NUMA 结构以及实时性能进行了分析和评价。文末，作者从调度系统的发展和实现出发，对 Linux 的发展特点和方向提出了自己的看法。																																				1． 前言							...&nbsp;&nbsp;<a href='http://www.cnitblog.com/flutist1225/articles/19989.html'>阅读全文</a><img src ="http://www.cnitblog.com/flutist1225/aggbug/19989.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:18 <a href="http://www.cnitblog.com/flutist1225/articles/19989.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>嵌入式系统 Boot Loader 技术内幕</title><link>http://www.cnitblog.com/flutist1225/articles/19987.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:16:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19987.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19987.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19987.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19987.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19987.html</trackback:ping><description><![CDATA[
		<p>
				<a name="1">
						<span class="atitle">
								<font size="4">1. 引言</font>
						</span>
				</a>
		</p>
		<p>在专用的嵌入式板子运行 GNU/Linux 系统已经变得越来越流行。一个嵌入式 Linux 系统从软件的角度看通常可以分为四个层次： </p>
		<p>1. <strong>引导加载程序。</strong>包括固化在固件(firmware)中的 boot 代码(可选)，和 Boot Loader 两大部分。 </p>
		<p>2. <strong>Linux 内核。</strong>特定于嵌入式板子的定制内核以及内核的启动参数。 </p>
		<p>3. <strong>文件系统。</strong>包括根文件系统和建立于 Flash 内存设备之上文件系统。通常用 ram disk 来作为 root fs。 </p>
		<p>4. <strong>用户应用程序。</strong>特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有：MicroWindows 和 MiniGUI 懂。 </p>
		<p>引导加载程序是系统加电后运行的第一段软件代码。回忆一下 PC 的体系结构我们可以知道，PC 机中的引导加载程序由 BIOS(其本质就是一段固件程序)和位于硬盘 MBR 中的 OS Boot Loader（比如，LILO 和 GRUB 等）一起组成。BIOS 在完成硬件检测和资源分配后，将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中，然后将控制权交给 OS Boot Loader。Boot Loader 的主要运行任务就是将内核映象从硬盘上读到 RAM 中，然后跳转到内核的入口点去运行，也即开始启动操作系统。 </p>
		<p>而在嵌入式系统中，通常并没有像 BIOS 那样的固件程序（注，有的嵌入式 CPU 也会内嵌一段短小的启动程序），因此整个系统的加载启动任务就完全由 Boot Loader 来完成。比如在一个基于 ARM7TDMI core 的嵌入式系统中，系统在上电或复位时通常都从地址 0x00000000 处开始执行，而在这个地址处安排的通常就是系统的 Boot Loader 程序。 </p>
		<p>本文将从 Boot Loader 的概念、Boot Loader 的主要任务、Boot Loader 的框架结构以及 Boot Loader 的安装等四个方面来讨论嵌入式系统的 Boot Loader。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-btloader/index.html#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">
								<font size="4">2. Boot Loader 的概念</font>
						</span>
				</a>
		</p>
		<p>简单地说，Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序，我们可以初始化硬件设备、建立内存空间的映射图，从而将系统的软硬件环境带到一个合适的状态，以便为最终调用操作系统内核准备好正确的环境。 </p>
		<p>通常，Boot Loader 是严重地依赖于硬件而实现的，特别是在嵌入式世界。因此，在嵌入式世界里建立一个通用的 Boot Loader 几乎是不可能的。尽管如此，我们仍然可以对 Boot Loader 归纳出一些通用的概念来，以指导用户特定的 Boot Loader 设计与实现。 </p>
		<p>
				<a name="N1006D">
						<span class="smalltitle">
								<strong>
										<font size="3">1. Boot Loader 所支持的 CPU 和嵌入式板</font>
								</strong>
						</span>
				</a>
		</p>
		<p>每种不同的 CPU 体系结构都有不同的 Boot Loader。有些 Boot Loader 也支持多种体系结构的 CPU，比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU 的体系结构外，Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说，对于两块不同的嵌入式板而言，即使它们是基于同一种 CPU 而构建的，要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上，通常也都需要修改 Boot Loader 的源程序。 </p>
		<p>
				<a name="N10076">
						<span class="smalltitle">
								<strong>
										<font size="3">2. Boot Loader 的安装媒介（Installation Medium）</font>
								</strong>
						</span>
				</a>
		</p>
		<p>系统加电或复位后，所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如，基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x00000000 取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如：ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在系统加电后，CPU 将首先执行 Boot Loader 程序。 </p>
		<p>下图1就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图。 </p>
		<br />
		<a name="N10084">
				<strong>图1 固态存储设备的典型空间分配结构</strong>
		</a>
		<br />
		<img alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-btloader/images/image001.gif" />
		<br />
		<p>
				<a name="N1008F">
						<span class="smalltitle">
								<strong>
										<font size="3">3. 用来控制 Boot Loader 的设备或机制</font>
								</strong>
						</span>
				</a>
		</p>
		<p>主机和目标机之间一般通过串口建立连接，Boot Loader 软件在执行时通常会通过串口来进行 I/O，比如：输出打印信息到串口，从串口读取用户控制字符等。 </p>
		<p>
				<a name="N10098">
						<span class="smalltitle">
								<strong>
										<font size="3">4. Boot Loader 的启动过程是单阶段（Single Stage）还是多阶段（Multi-Stage）</font>
								</strong>
						</span>
				</a>
		</p>
		<p>通常多阶段的 Boot Loader 能提供更为复杂的功能，以及更好的可移植性。从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程，也即启动过程可以分为 stage 1 和 stage 2 两部分。而至于在 stage 1 和 stage 2 具体完成哪些任务将在下面讨论。 </p>
		<p>
				<a name="N100A1">
						<span class="smalltitle">
								<strong>
										<font size="3">5. Boot Loader 的操作模式 (Operation Mode)</font>
								</strong>
						</span>
				</a>
		</p>
		<p>大多数 Boot Loader 都包含两种不同的操作模式："启动加载"模式和"下载"模式，这种区别仅对于开发人员才有意义。但从最终用户的角度看，Boot Loader 的作用就是用来加载操作系统，而并不存在所谓的启动加载模式与下载工作模式的区别。 </p>
		<p>
				<strong>启动加载（Boot loading）模式：</strong>这种模式也称为"自主"（Autonomous）模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行，整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式，因此在嵌入式产品发布的时侯，Boot Loader 显然必须工作在这种模式下。 </p>
		<p>
				<strong>下载（Downloading）模式：</strong>在这种模式下，目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机（Host）下载文件，比如：下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中，然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用；此外，以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。 </p>
		<p>像 Blob 或 U-Boot 等这样功能强大的 Boot Loader 通常同时支持这两种工作模式，而且允许用户在这两种工作模式之间进行切换。比如，Blob 在启动时处于正常的启动加载模式，但是它会延时 10 秒等待终端用户按下任意键而将 blob 切换到下载模式。如果在 10 秒内没有用户按键，则 blob 继续启动 Linux 内核。 </p>
		<p>
				<a name="N100B9">
						<span class="smalltitle">
								<strong>
										<font size="3">6. BootLoader 与主机之间进行文件传输所用的通信设备及协议</font>
								</strong>
						</span>
				</a>
		</p>
		<p>最常见的情况就是，目标机上的 Boot Loader 通过串口与主机之间进行文件传输，传输协议通常是 xmodem／ymodem／zmodem 协议中的一种。但是，串口传输的速度是有限的，因此通过以太网连接并借助 TFTP 协议来下载文件是个更好的选择。 </p>
		<p>此外，在论及这个话题时，主机方所用的软件也要考虑。比如，在通过以太网连接和 TFTP 协议来下载文件时，主机方必须有一个软件用来的提供 TFTP 服务。 </p>
		<p>在讨论了 BootLoader 的上述概念后，下面我们来具体看看 BootLoader 的应该完成哪些任务。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-btloader/index.html#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3">
						<span class="atitle">
								<font size="4">3. Boot Loader 的主要任务与典型结构框架</font>
						</span>
				</a>
		</p>
		<p>在继续本节的讨论之前，首先我们做一个假定，那就是：假定内核映像与根文件系统映像都被加载到 RAM 中运行。之所以提出这样一个假设前提是因为，在嵌入式系统中内核映像与根文件系统映像也可以直接在 ROM 或 Flash 这样的固态存储设备中直接运行。但这种做法无疑是以运行速度的牺牲为代价的。 </p>
		<p>从操作系统的角度看，Boot Loader 的总目标就是正确地调用内核来执行。 </p>
		<p>另外，由于 Boot Loader 的实现依赖于 CPU 的体系结构，因此大多数 Boot Loader 都分为 stage1 和 stage2 两大部分。依赖于 CPU 体系结构的代码，比如设备初始化代码等，通常都放在 stage1 中，而且通常都用汇编语言来实现，以达到短小精悍的目的。而 stage2 则通常用C语言来实现，这样可以实现给复杂的功能，而且代码会具有更好的可读性和可移植性。 </p>
		<p>Boot Loader 的 stage1 通常包括以下步骤(以执行的先后顺序)： </p>
		<ul>
				<li>硬件设备初始化。 <br /><br /></li>
				<li>为加载 Boot Loader 的 stage2 准备 RAM 空间。 <br /><br /></li>
				<li>拷贝 Boot Loader 的 stage2 到 RAM 空间中。 <br /><br /></li>
				<li>设置好堆栈。 <br /><br /></li>
				<li>跳转到 stage2 的 C 入口点。 <br /><br /></li>
		</ul>
		<p>Boot Loader 的 stage2 通常包括以下步骤(以执行的先后顺序)： </p>
		<ul>
				<li>初始化本阶段要使用到的硬件设备。 <br /><br /></li>
				<li>检测系统内存映射(memory map)。 <br /><br /></li>
				<li>将 kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中。 <br /><br /></li>
				<li>为内核设置启动参数。 <br /><br /></li>
				<li>调用内核。 </li>
		</ul>
		<p>
				<a name="N10125">
						<span class="smalltitle">
								<strong>
										<font size="3">3.1 Boot Loader 的 stage1</font>
								</strong>
						</span>
				</a>
		</p>
		<p>
				<strong>3.1.1 基本的硬件初始化</strong>
		</p>
		<p>这是 Boot Loader 一开始就执行的操作，其目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境。它通常包括以下步骤（以执行的先后顺序）： </p>
		<p>1． <strong>屏蔽所有的中断。</strong>为中断提供服务通常是 OS 设备驱动程序的责任，因此在 Boot Loader 的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写 CPU 的中断屏蔽寄存器或状态寄存器（比如 ARM 的 CPSR 寄存器）来完成。 </p>
		<p>2． <strong>设置 CPU 的速度和时钟频率。</strong></p>
		<p>3． <strong>RAM 初始化。</strong>包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。 </p>
		<p>4． <strong>初始化 LED。</strong>典型地，通过 GPIO 来驱动 LED，其目的是表明系统的状态是 OK 还是 Error。如果板子上没有 LED，那么也可以通过初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息来完成这一点。 </p>
		<p>5． <strong>关闭 CPU 内部指令／数据 cache。</strong></p>
		<p>
				<strong>3.1.2 为加载 stage2 准备 RAM 空间</strong>
		</p>
		<p>为了获得更快的执行速度，通常把 stage2 加载到 RAM 空间中来执行，因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。 </p>
		<p>由于 stage2 通常是 C 语言执行代码，因此在考虑空间大小时，除了 stage2 可执行映象的大小外，还必须把堆栈空间也考虑进来。此外，空间大小最好是 memory page 大小(通常是 4KB)的倍数。一般而言，1M 的 RAM 空间已经足够了。具体的地址范围可以任意安排，比如 blob 就将它的 stage2 可执行映像安排到从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行。但是，将 stage2 安排到整个 RAM 空间的最顶 1MB(也即(RamEnd-1MB) - RamEnd)是一种值得推荐的方法。 </p>
		<p>为了后面的叙述方便，这里把所安排的 RAM 空间范围的大小记为：stage2_size(字节)，把起始地址和终止地址分别记为：stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">stage2_end＝stage2_start＋stage2_size</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>另外，还必须确保所安排的地址范围的的确确是可读写的 RAM 空间，因此，必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于 blob 的方法，也即：以 memory page 为被测试单位，测试每个 memory page 开始的两个字是否是可读写的。为了后面叙述的方便，我们记这个检测算法为：test_mempage，其具体步骤如下： </p>
		<p>1． 先保存 memory page 一开始两个字的内容。 </p>
		<p>2． 向这两个字中写入任意的数字。比如：向第一个字写入 0x55，第 2 个字写入 0xaa。 </p>
		<p>3． 然后，立即将这两个字的内容读回。显然，我们读到的内容应该分别是 0x55 和 0xaa。如果不是，则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。 </p>
		<p>4． 再向这两个字中写入任意的数字。比如：向第一个字写入 0xaa，第 2 个字中写入 0x55。 </p>
		<p>5． 然后，立即将这两个字的内容立即读回。显然，我们读到的内容应该分别是 0xaa 和 0x55。如果不是，则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。 </p>
		<p>6． 恢复这两个字的原始内容。测试完毕。 </p>
		<p>为了得到一段干净的 RAM 空间范围，我们也可以将所安排的 RAM 空间范围进行清零操作。 </p>
		<p>
				<strong>3.1.3 拷贝 stage2 到 RAM 中</strong>
		</p>
		<p>拷贝时要确定两点：(1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址；(2) RAM 空间的起始地址。 </p>
		<p>
				<strong>3.1.4 设置堆栈指针 sp</strong>
		</p>
		<p>堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4)，也即在 3.1.2 节所安排的那个 1MB 的 RAM 空间的最顶端(堆栈向下生长)。 </p>
		<p>此外，在设置堆栈指针 sp 之前，也可以关闭 led 灯，以提示用户我们准备跳转到 stage2。 </p>
		<p>经过上述这些执行步骤后，系统的物理内存布局应该如下图2所示。 </p>
		<p>
				<strong>3.1.5 跳转到 stage2 的 C 入口点</strong>
		</p>
		<p>在上述一切都就绪后，就可以跳转到 Boot Loader 的 stage2 去执行了。比如，在 ARM 系统中，这可以通过修改 PC 寄存器为合适的地址来实现。 </p>
		<br />
		<a name="N101A0">
				<strong>图2 bootloader 的 stage2 可执行映象刚被拷贝到 RAM 空间时的系统内存布局</strong>
		</a>
		<br />
		<img alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-btloader/images/image002.gif" />
		<br />
		<p>
				<a name="N101AB">
						<span class="smalltitle">
								<strong>
										<font size="3">3.2 Boot Loader 的 stage2 </font>
								</strong>
						</span>
				</a>
		</p>
		<p>正如前面所说，stage2 的代码通常用 C 语言来实现，以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通 C 语言应用程序不同的是，在编译和链接 boot loader 这样的程序时，我们不能使用 glibc 库中的任何支持函数。其原因是显而易见的。这就给我们带来一个问题，那就是从那里跳转进 main() 函数呢？直接把 main() 函数的起始地址作为整个 stage2 执行映像的入口点或许是最直接的想法。但是这样做有两个缺点：1)无法通过main() 函数传递函数参数；2)无法处理 main() 函数返回的情况。一种更为巧妙的方法是利用 trampoline(弹簧床)的概念。也即，用汇编语言写一段trampoline 小程序，并将这段 trampoline 小程序来作为 stage2 可执行映象的执行入口点。然后我们可以在 trampoline 汇编小程序中用 CPU 跳转指令跳入 main() 函数中去执行；而当 main() 函数返回时，CPU 执行路径显然再次回到我们的 trampoline 程序。简而言之，这种方法的思想就是：用这段 trampoline 小程序来作为 main() 函数的外部包裹(external wrapper)。 </p>
		<p>下面给出一个简单的 trampoline 程序示例(来自blob)： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">.text.globl _trampoline_trampoline:	bl	main	/* if main ever returns we just call it again */	b	_trampoline</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>可以看出，当 main() 函数返回后，我们又用一条跳转指令重新执行 trampoline 程序――当然也就重新执行 main() 函数，这也就是 trampoline(弹簧床)一词的意思所在。 </p>
		<p>
				<strong>3.2.1初始化本阶段要使用到的硬件设备</strong>
		</p>
		<p>这通常包括：（1）初始化至少一个串口，以便和终端用户进行 I/O 输出信息；（2）初始化计时器等。 </p>
		<p>在初始化这些设备之前，也可以重新把 LED 灯点亮，以表明我们已经进入 main() 函数执行。 </p>
		<p>设备初始化完成后，可以输出一些打印信息，程序名字字符串、版本号等。 </p>
		<p>
				<strong>3.2.2 检测系统的内存映射（memory map）</strong>
		</p>
		<p>所谓内存映射就是指在整个 4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如，在 SA-1100 CPU 中，从 0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间，而在 Samsung S3C44B0X CPU 中，从 0x0c00,0000 到 0x1000,0000 之间的 64M 地址空间被用作系统的 RAM 地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM，但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说，具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上，而让剩下的那部分预留 RAM 地址空间处于未使用状态。 <strong>由于上述这个事实，因此 Boot Loader 的 stage2 必须在它想干点什么 (比如，将存储在 flash 上的内核映像读到 RAM 空间中) 之前检测整个系统的内存映射情况，也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元，哪些是处于 "unused" 状态的。</strong></p>
		<p>
				<strong>(1) 内存映射的描述</strong>
		</p>
		<p>可以用如下数据结构来描述 RAM 地址空间中的一段连续(continuous)的地址范围：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">typedef struct memory_area_struct {	u32 start; /* the base address of the memory region */	u32 size; /* the byte number of the memory region */	int used;} memory_area_t;</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这段 RAM 地址空间中的连续地址范围可以处于两种状态之一：(1)used=1，则说明这段连续的地址范围已被实现，也即真正地被映射到 RAM 单元上。(2)used=0，则说明这段连续的地址范围并未被系统所实现，而是处于未使用状态。 </p>
		<p>基于上述 memory_area_t 数据结构，整个 CPU 预留的 RAM 地址空间可以用一个 memory_area_t 类型的数组来表示，如下所示： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">memory_area_t memory_map[NUM_MEM_AREAS] = {	[0 ... (NUM_MEM_AREAS - 1)] = {		.start = 0,		.size = 0,		.used = 0	},};</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>(2) 内存映射的检测 </p>
		<p>下面我们给出一个可用来检测整个 RAM 地址空间内存映射情况的简单而有效的算法： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">/* 数组初始化 */for(i = 0; i &lt; NUM_MEM_AREAS; i++)	memory_map[i].used = 0;/* first write a 0 to all memory locations */for(addr = MEM_START; addr &lt; MEM_END; addr += PAGE_SIZE)	* (u32 *)addr = 0;for(i = 0, addr = MEM_START; addr &lt; MEM_END; addr += PAGE_SIZE) {     /*      * 检测从基地址 MEM_START+i*PAGE_SIZE 开始,大小为* PAGE_SIZE 的地址空间是否是有效的RAM地址空间。      */     调用3.1.2节中的算法test_mempage()；     if ( current memory page isnot a valid ram page) {		/* no RAM here */		if(memory_map[i].used )			i++;		continue;	}		/*	 * 当前页已经是一个被映射到 RAM 的有效地址范围	 * 但是还要看看当前页是否只是 4GB 地址空间中某个地址页的别名？	 */	if(* (u32 *)addr != 0) { /* alias? */		/* 这个内存页是 4GB 地址空间中某个地址页的别名 */		if ( memory_map[i].used )			i++;		continue;	}		/*	 * 当前页已经是一个被映射到 RAM 的有效地址范围	 * 而且它也不是 4GB 地址空间中某个地址页的别名。	 */	if (memory_map[i].used == 0) {		memory_map[i].start = addr;		memory_map[i].size = PAGE_SIZE;		memory_map[i].used = 1;	} else {		memory_map[i].size += PAGE_SIZE;	}} /* end of for (…) */</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在用上述算法检测完系统的内存映射情况后，Boot Loader 也可以将内存映射的详细信息打印到串口。 </p>
		<p>
				<strong>3.2.3 加载内核映像和根文件系统映像</strong>
		</p>
		<p>
				<strong>(1) 规划内存占用的布局</strong>
		</p>
		<p>这里包括两个方面：(1)内核映像所占用的内存范围；（2）根文件系统所占用的内存范围。在规划内存占用的布局时，主要考虑基地址和映像的大小两个方面。 </p>
		<p>对于内核映像，一般将其拷贝到从(MEM_START＋0x8000) 这个基地址开始的大约1MB大小的内存范围内(嵌入式 Linux 的内核一般都不操过 1MB)。为什么要把从 MEM_START 到 MEM_START＋0x8000 这段 32KB 大小的内存空出来呢？这是因为 Linux 内核要在这段内存中放置一些全局数据结构，如：启动参数和内核页表等信息。 </p>
		<p>而对于根文件系统映像，则一般将其拷贝到 MEM_START+0x0010,0000 开始的地方。如果用 Ramdisk 作为根文件系统映像，则其解压后的大小一般是1MB。 </p>
		<p>
				<strong>（2）从 Flash 上拷贝</strong>
		</p>
		<p>由于像 ARM 这样的嵌入式 CPU 通常都是在统一的内存地址空间中寻址 Flash 等固态存储设备的，因此从 Flash 上读取数据与从 RAM 单元中读取数据并没有什么不同。用一个简单的循环就可以完成从 Flash 设备上拷贝映像的工作： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> while(count) {	*dest++ = *src++; /* they are all aligned with word boundary */	count -= 4; /* byte number */};</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<strong>3.2.4 设置内核的启动参数</strong>
		</p>
		<p>应该说，在将内核映像和根文件系统映像拷贝到 RAM 空间中后，就可以准备启动 Linux 内核了。但是在调用内核之前，应该作一步准备工作，即：设置 Linux 内核的启动参数。 </p>
		<p>Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始，以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">/* The list ends with an ATAG_NONE node. */#define ATAG_NONE	0x00000000struct tag_header {	u32 size; /* 注意，这里size是字数为单位的 */	u32 tag;};……struct tag {	struct tag_header hdr;	union {		struct tag_core		core;		struct tag_mem32	mem;		struct tag_videotext	videotext;		struct tag_ramdisk	ramdisk;		struct tag_initrd	initrd;		struct tag_serialnr	serialnr;		struct tag_revision	revision;		struct tag_videolfb	videolfb;		struct tag_cmdline	cmdline;		/*		 * Acorn specific		 */		struct tag_acorn	acorn;		/*		 * DC21285 specific		 */		struct tag_memclk	memclk;	} u;};</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在嵌入式 Linux 系统中，通常需要由 Boot Loader 设置的常见启动参数有：ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。 </p>
		<p>比如，设置 ATAG_CORE 的代码如下： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">params = (struct tag *)BOOT_PARAMS;	params-&gt;hdr.tag = ATAG_CORE;	params-&gt;hdr.size = tag_size(tag_core);	params-&gt;u.core.flags = 0;	params-&gt;u.core.pagesize = 0;	params-&gt;u.core.rootdev = 0;	params = tag_next(params);</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>其中，BOOT_PARAMS 表示内核启动参数在内存中的起始基地址，指针 params 是一个 struct tag 类型的指针。宏 tag_next() 将以指向当前标记的指针为参数，计算紧临当前标记的下一个标记的起始地址。注意，内核的根文件系统所在的设备ID就是在这里设置的。 </p>
		<p>下面是设置内存映射情况的示例代码： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">for(i = 0; i &lt; NUM_MEM_AREAS; i++) {		if(memory_map[i].used) {			params-&gt;hdr.tag = ATAG_MEM;			params-&gt;hdr.size = tag_size(tag_mem32);			params-&gt;u.mem.start = memory_map[i].start;			params-&gt;u.mem.size = memory_map[i].size;						params = tag_next(params);		}}</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>可以看出，在 memory_map［］数组中，每一个有效的内存段都对应一个 ATAG_MEM 参数标记。 </p>
		<p>Linux 内核在启动时可以以命令行参数的形式来接收信息，利用这一点我们可以向内核提供那些内核不能自己检测的硬件参数信息，或者重载(override)内核自己检测到的信息。比如，我们用这样一个命令行参数字符串"console=ttyS0,115200n8"来通知内核以 ttyS0 作为控制台，且串口采用 "115200bps、无奇偶校验、8位数据位"这样的设置。下面是一段设置调用内核命令行参数字符串的示例代码： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">char *p;	/* eat leading white space */	for(p = commandline; *p == ' '; p++)		;	/* skip non-existent command lines so the kernel will still    * use its default command line.	 */	if(*p == '\0')		return;	params-&gt;hdr.tag = ATAG_CMDLINE;	params-&gt;hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) &gt;&gt; 2;	strcpy(params-&gt;u.cmdline.cmdline, p);	params = tag_next(params);</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>请注意在上述代码中，设置 tag_header 的大小时，必须包括字符串的终止符'\0'，此外还要将字节数向上圆整4个字节，因为 tag_header 结构中的size 成员表示的是字数。 </p>
		<p>下面是设置 ATAG_INITRD 的示例代码，它告诉内核在 RAM 中的什么地方可以找到 initrd 映象(压缩格式)以及它的大小： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">	params-&gt;hdr.tag = ATAG_INITRD2;	params-&gt;hdr.size = tag_size(tag_initrd);		params-&gt;u.initrd.start = RAMDISK_RAM_BASE;	params-&gt;u.initrd.size = INITRD_LEN;		params = tag_next(params);</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>下面是设置 ATAG_RAMDISK 的示例代码，它告诉内核解压后的 Ramdisk 有多大（单位是KB）： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">params-&gt;hdr.tag = ATAG_RAMDISK;params-&gt;hdr.size = tag_size(tag_ramdisk);	params-&gt;u.ramdisk.start = 0;params-&gt;u.ramdisk.size = RAMDISK_SIZE; /* 请注意，单位是KB */params-&gt;u.ramdisk.flags = 1; /* automatically load ramdisk */	params = tag_next(params);</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>最后，设置 ATAG_NONE 标记，结束整个启动参数列表： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">static void setup_end_tag(void){	params-&gt;hdr.tag = ATAG_NONE;	params-&gt;hdr.size = 0;}</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<strong>3.2.5 调用内核</strong>
		</p>
		<p>Boot Loader 调用 Linux 内核的方法是直接跳转到内核的第一条指令处，也即直接跳转到 MEM_START＋0x8000 地址处。在跳转时，下列条件要满足： </p>
		<p>1． CPU 寄存器的设置： </p>
		<ul>
				<li>R0＝0； <br /><br /></li>
				<li>R1＝机器类型 ID；关于 Machine Type Number，可以参见 <strong>linux/arch/arm/tools/mach-types。</strong><br /><br /></li>
				<li>R2＝启动参数标记列表在 RAM 中起始基地址； <br /><br /></li>
		</ul>
		<p>2． CPU 模式： </p>
		<ul>
				<li>必须禁止中断（IRQs和FIQs）； <br /><br /></li>
				<li>CPU 必须 SVC 模式； <br /><br /></li>
		</ul>
		<p>3． Cache 和 MMU 的设置：</p>
		<ul>
				<li>MMU 必须关闭； <br /><br /></li>
				<li>指令 Cache 可以打开也可以关闭； <br /><br /></li>
				<li>数据 Cache 必须关闭； </li>
		</ul>
		<p>如果用 C 语言，可以像下列示例代码这样来调用内核： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">void (*theKernel)(int zero, int arch, u32 params_addr) = (void (*)(int, int, u32))KERNEL_RAM_BASE;……theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>注意，theKernel()函数调用应该永远不返回的。如果这个调用返回，则说明出错。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-btloader/index.html#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="4">
						<span class="atitle">
								<font size="4">4. 关于串口终端</font>
						</span>
				</a>
		</p>
		<p>在 boot loader 程序的设计与实现中，没有什么能够比从串口终端正确地收到打印信息能更令人激动了。此外，向串口终端打印信息也是一个非常重要而又有效的调试手段。但是，我们经常会碰到串口终端显示乱码或根本没有显示的问题。造成这个问题主要有两种原因：(1) boot loader 对串口的初始化设置不正确。(2) 运行在 host 端的终端仿真程序对串口的设置不正确，这包括：波特率、奇偶校验、数据位和停止位等方面的设置。 </p>
		<p>此外，有时也会碰到这样的问题，那就是：在 boot loader 的运行过程中我们可以正确地向串口终端输出信息，但当 boot loader 启动内核后却无法看到内核的启动输出信息。对这一问题的原因可以从以下几个方面来考虑： </p>
		<p>(1) 首先请确认你的内核在编译时配置了对串口终端的支持，并配置了正确的串口驱动程序。 </p>
		<p>(2) 你的 boot loader 对串口的初始化设置可能会和内核对串口的初始化设置不一致。此外，对于诸如 s3c44b0x 这样的 CPU，CPU 时钟频率的设置也会影响串口，因此如果 boot loader 和内核对其 CPU 时钟频率的设置不一致，也会使串口终端无法正确显示信息。 </p>
		<p>(3) 最后，还要确认 boot loader 所用的内核基地址必须和内核映像在编译时所用的运行基地址一致，尤其是对于 uClinux 而言。假设你的内核映像在编译时用的基地址是 0xc0008000，但你的 boot loader 却将它加载到 0xc0010000 处去执行，那么内核映像当然不能正确地执行了。 </p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<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">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<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-128.ibm.com/developerworks/cn/linux/l-btloader/index.html#main">
																				<strong>
																						<font color="#996699">回页首</font>
																				</strong>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="5">
						<span class="atitle">
								<font size="4">5. 结束语</font>
						</span>
				</a>
		</p>
		<p>Boot Loader 的设计与实现是一个非常复杂的过程。如果不能从串口收到那激动人心的"uncompressing linux.................. done, booting the kernel……"内核启动信息，恐怕谁也不能说："嗨，我的 boot loader 已经成功地转起来了！"。</p>
<img src ="http://www.cnitblog.com/flutist1225/aggbug/19987.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:16 <a href="http://www.cnitblog.com/flutist1225/articles/19987.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IP分片重组的分析和常见碎片攻击 </title><link>http://www.cnitblog.com/flutist1225/articles/19986.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:15:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19986.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19986.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19986.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19986.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19986.html</trackback:ping><description><![CDATA[一 前言 <br /><br />本文对linux的IP组装算法进行了分析，因为IP碎片经常用于DOS等攻击，在文章后面我结合了一些攻击方法进行了更进一步的说明。内核主要参考版本是2.2.16，另外简要的介绍了2.4.0-test3中的一些变化. <br /><br />二 目录 <br /><br />1- 概述 <br />2- 关键数据结构 <br />3- 重要函数说明 <br />4- 2.4系列的变化 <br />5- 常见碎片攻击 <br /><br /><br />1. 概述 <br /><br />在linux源代码中，ip分片重组的全部程序几乎都在都在\net\ipv4\ip_fragment.c文件中。其对外提供一个函数接口ip_defrag()。其函数原型如下： <br /><br />struct sk_buff *ip_defrag(struct sk_buff *skb) <br /><br />众所周知，网络数据报在linux的网络堆栈中是以sk_buff的结构传送的，ip_defrag()的功能就是接受分片的数据包(sk_buff)，并试图进行组合，当完整的包组合好时，将新的sk_buff返还，否则返回一个空指针。 <br /><br />此函数在其他文件中的调用如下： <br /><br />ip层接收主函数为ip_rcv()(\net\ipv4\ip_input.c),任何IP包都需经过此函数处理。如果此包是发往本机，则调用ip_local_deliver()函数（\net\ipv4\ip_input.c）进行处理，一般的系统碎片只有在到达最终目的的时候才进行重组（尽管在传输过程中可能被进一步分成更小的片）。在ip_local_deliver()中我们可发现如下代码： <br /><br />if (sysctl_ip_always_defrag == 0 &amp;&amp; /*编译时未设置提前组装*/ <br />(iph-〉frag_off &amp; htons(IP_MF|IP_OFFSET))) { /*判断是否是分片包*/ <br />skb = ip_defrag(skb); /*条件满足，进行组装*/ <br />if (!skb) /*若组装好则进行下一步处理，出错 <br />return 0; 或仍未组装完返回*/ <br />iph = skb-〉nh.iph; /*重新定位ip头的指针*/ <br />} <br /><br />iph-〉frag_off只有在设置MF（more fragment）或offset!=0才意味着是分片包，因此此处的检验理所当然，但为什么判断sysctl_ip_always_defrag == 0呢？在看ip_rcv()时我们应该已经注意到在刚进行了版本号，长度，校验和等判断后，有如下一段代码： <br /><br />if (sysctl_ip_always_defrag != 0 &amp;&amp; <br />iph-〉frag_off &amp; htons(IP_MF|IP_OFFSET)) { <br />skb = ip_defrag(skb); <br />if (!skb) <br />return 0; <br />iph = skb-〉nh.iph; <br />ip_send_check(iph); <br />} <br /><br />即如果sysctl_ip_always_defrag==1的话，ip_defrag()的调用位置将有变化，对任何进来的IP分片都要进行重组，可以想像，如果此机器作路由器的话，将对所有的分片组装好后，才会进行转发。此举一般是没有必要的。这个值可以通过sysctl命令动态设置，用sysctl -a可以看到在一般的系统中，此值被设为0： <br /><br />#sysctl -a <br />...... <br />net.ipv4.ip_always_defrag = 0 <br />...... <br /><br /><br />2. 关键数据结构（2.2系列） <br /><br />每一个分片用ipfrag结构表示： <br /><br />/* Describe an IP fragment. */ <br />struct ipfrag { <br />int offset; /* offset of fragment in IP datagram */ <br />int end; /* last byte of data in datagram */ <br />int len; /* length of this fragment */ <br />struct sk_buff *skb; /* complete received fragment */ <br />unsigned char *ptr; /* pointer into real fragment data */ <br />struct ipfrag *next; /* linked list pointers */ <br />struct ipfrag *prev; <br />}; <br /><br />这些分片形成一个双向链表（在linux内核中，若需要使用链表，除非有特殊需要，否则推荐双向链表，见document\CodingStyle）,表示一个未组装完的分片队列（属于一个ip包）。 <br />这个链表的头指针要放在ipq结构中： <br /><br />/* Describe an entry in the "incomplete datagrams" queue. */ <br />struct ipq { <br />struct iphdr *iph; /* pointer to IP header */ <br />struct ipq *next; /* linked list pointers */ <br />struct ipfrag *fragments; /* linked list of received fragments */ <br />int len; /* total length of original datagram */ <br />short ihlen; /* length of the IP header */ <br />struct timer_list timer; /* when will this queue expire? */ <br />struct ipq **pprev; <br />struct device *dev; /* Device - for icmp replies */ <br />}; <br /><br />注意每个ipq保留了一个定时器（即struct timer_list timer;）。 <br /><br />ipq也会形成一个链表，它们是内核当前未组装完的所有IP包。为了便于查找，保留了一个 <br />hash表： <br />#define IPQ_HASHSZ 64 <br />struct ipq *ipq_hash[IPQ_HASHSZ]; <br />#define ipqhashfn(id, saddr, daddr, prot) \ <br />((((id) 〉〉 1) ^ (saddr) ^ (daddr) ^ (prot)) &amp; (IPQ_HASHSZ - 1)) <br /><br />--------_____________ <br />| 1 | | <br />-------- ----------- ------------ ------------ <br />Hash表 | 2 | | ipq1 |----〉| ipfrag1 |-----〉| ipfrag2 |------〉....... <br />-------- ------------ ------------- ------------ <br />...... | <br />-------- \/ <br />| 63 | ------------ ------------- ----------- <br />-------- | ipq2 |----〉| ipfrag1 |-----〉| ipfrag2 |------〉....... <br />------------ ------------- ----------- <br />| <br />\/ <br />------------ ------------- ----------- <br />| ipq3 |----〉| ipfrag1 |-----〉| ipfrag2 |------〉....... <br />------------ ------------- ----------- <br />| <br />\/ <br />........ <br /><br />每个IP包用如下四元组表示：（id,saddr,daddr,protocol）,四个值都相同的碎片保留在一个IPQ中，即可组装成一个完整的IP包。 <br /><br />此结构在2.4内核中有了改动，具体将在下文中声明。 <br /><br />3. 重要函数说明（2.2系列） <br /><br />3.1 ip_defrag() <br />ip_defrag()是整个流程的入口，下面我们首先对ip_defrag()作一定的说明。 <br /><br />(1)为了防止因保留分片而造成内存消耗过大，linux设置了界限来防止这种情况，如果超过了内存使用的上限，则清空内存中最老的队列（ipq）.所用内存的大小保存在变量ip_frag_mem中，当然，对它的读写都应是“原子”操作（atomic_sub，atomic_add，atomic_read，etc）。 <br />其定义在文件ip_fragment.c前部： <br /><br />atomic_t ip_frag_mem = ATOMIC_INIT(0); /* Memory used for fragments */ <br /><br />if (atomic_read(&amp;ip_frag_mem) 〉 sysctl_ipfrag_high_thresh) <br />ip_evictor(); <br /><br />ip_evicator的具体操作将在下文中描述。 <br /><br />（2）以id, saddr, daddr, protocol为标志检索是否已经建立了相应的ipq，若发现，则返回ipq的指针，并重置定时器。 <br /><br />qp = ip_find(iph, skb-〉dst); <br /><br />（3）此时有一个if/else对，其作用是： <br />如果ipq已经存在，则证明已经有同一个包的其他分片到达。检查此片是不是第一个分片（因为分片到达顺序可能错乱），若是，将ip头信息和头长度保留在ipq结构中（）； <br />if (offset == 0) { <br />/* Fragmented frame replaced by unfragmented copy? */ <br />if ((flags &amp; IP_MF) == 0) <br />goto out_freequeue; <br />qp-〉ihlen = ihl; <br />memcpy(qp-〉iph, iph, (ihl + 8)); <br />} <br /><br />如果不存在，当然要建立一个了： <br />qp = ip_create(skb, iph); <br />if (!qp) <br />goto out_freeskb; <br /><br />ip_create便是分配出一块内存，初始化这个ipq，并在hash表中登记。 <br /><br />到此为止ipq已经肯定存在了，不管是已经存在的，还是我们刚才生成的。 <br /><br />（4）对包的长度进行检测，如果超过了ip包的最大范围，则报警，并丢弃此包。jolt2便是利用这点将window系统打瘫的。由于linux做了这种检查，所以基本免受其害。 <br /><br />（5）调节end值（数据的结尾位置），如果是最后一个包，则最终整个ip包的长度便可以知道了，为了组装时方便，将其记录到ipq中。 <br />/* Determine the position of this fragment. */ <br />end = offset + ntohs(iph-〉tot_len) - ihl; <br /><br />/* Is this the final fragment? */ <br />if ((flags &amp; IP_MF) == 0) <br />qp-〉len = end; <br /><br />（6）接下来很长一段代码（line481-line586）便是定位这份分片在整个数据包中的位置。如果分片之间有重合（恶意攻击和其他异常），则能归并便归并。这个问题我们将在后面（常见碎片攻击中）详谈。 <br /><br />（7）此时我们已经知道这个分片的具体位置了。我们要生成一份新的ipfrag结构，并将其放到 <br />我们刚才找到的正确位置上去。 <br />tfp = ip_frag_create(offset, end, skb, ptr); <br />if (!tfp) <br />goto out_freeskb; <br /><br />/* Insert this fragment in the chain of fragments. */ <br />tfp-〉prev = prev; <br />tfp-〉next = next; <br />if (prev != NULL) <br />prev-〉next = tfp; <br />else <br />qp-〉fragments = tfp; <br /><br />if (next != NULL) <br />next-〉prev = tfp; <br /><br />（8）ip_done函数检查是否所有的分片已经到齐，如果到齐，则将其组装成一个新的sk_buff（调用ip_glue）,并最终返回到调用ip_defrag的地方。 <br /><br />if (ip_done(qp)) { /*全部到齐了么？*/ <br />/* Glue together the fragments. */ <br />skb = ip_glue(qp); <br />/* Free the queue entry. */ <br />out_freequeue: <br />ip_free(qp); /*原有的ipq结构已经不需要了，释放。*/ <br />out_skb: <br />return skb; /*组装完成，可以返回了*/ <br />} <br /><br />如果没有到齐，则返回NULL. <br /><br />至此全部组装过程结束。 <br /><br />3.2 ip_evictor（） <br /><br />当分片所用的内存超过一定的上限时(sysctl_ipfrag_high_thresh)会调用ip_evicator以释放内存。 <br />ip_evicator会找寻可清空的IPQ，并将其清空，直到到达到可用的下限（sysctl_ipfrag_low_thresh） <br />。 <br /><br />这个值在ip_fragment.c中按如下定义： <br />int sysctl_ipfrag_high_thresh = 256*1024; <br />int sysctl_ipfrag_low_thresh = 192*1024; <br /><br />同样，用sysctl -a可可看到这两参数，同时可以动态修改。 <br />＃sysctl -a <br />...... <br />net.ipv4.ipfrag_low_thresh = 196608 <br />net.ipv4.ipfrag_high_thresh = 262144 <br />...... <br /><br />理论上ip_evicator应该采用LRU算法，将最古老的IPQ清除。但目前linux(包括2.4.0)没有实现此功能，只是将hash表按次序清空，这样的好处是简单易行。 <br /><br />3.3 ip_glue（） <br /><br />ip_glue（）函数将负责将一个所有分片已经到齐的的IP包组合好。当这一步进行时，所有的分片已经按顺序排好，并解决了所有的重叠问题。因此其流程相应很简单。 <br />首先生成一个足够大的（足以容纳所有的分片包长度的总和）新的skbuff： <br />skb = dev_alloc_skb(len); <br />if (!skb) <br />goto out_nomem; <br />调整一些必要的指针后，就在一个while循环中依次将原有分片的内容用memcoy拷贝到新的skbuff中。再进行一些指针调整后，过程结束，将新的skbuff返回。 <br /><br />3.4 ip_expire（） <br /><br />前面已经提到，每个ipq保留了一个定时器，当一定时间以后组装还没有完成，将清空此队列。定时器的值保留在sysctl_ipfrag_time中： <br />int sysctl_ipfrag_time = IP_FRAG_TIME; <br />（在/include/net/ip.h中有#define IP_FRAG_TIME (30 * HZ) ） <br />此值也可以用sysctl设置。 <br />定时器的具体实现机制的没有分析。 <br /><br />4. 2.4系列的变化 <br /><br />其实如果仔细看一下，2.4的分片组装代码的流程与2.2系列基本相同，不同的是将函数的分工变化了。由于原ipfrag结构保留的结构均可在skbuff中得到，在2.4中将此结构取消了，并对ipq结构做了一些修改。其他主要变化有： <br />1）ip_defrag分成了ip_defrag和ip_frag_queue两部分。 <br />2）ip_glue换名成ip_frag_reasm,流程基本未动。 <br />3）现在ipq中用meat保留现有的分片长度的累加值（已经解决重叠），如果此值到达总长度，则意味着所有的分片到达，因此取消了ip_done函数，不必每次遍历一次链表，因此在效率上有了较大的提高，抗小碎片攻击的能力得到加强。 <br /><br /><br />5.常见碎片攻击 <br /><br />IP碎片经常被用来作DOS攻击，典型的例子便是teardrop和jolt2，其原理都是利用发送异常的分片，如果操作系统的内核在处理分片重组时没有考虑到所有的异常情况，将可能引向异常的流程，造成拒绝服务（DOS）。 <br /><br />我们首先要仔细考虑一下linux在处理分片重叠时的办法。 <br />代码主要在ip_defrag中，首先要遍历链表，定位此分片的位置，具体就是给prev和next两指针赋上正确数值。然后处理与前面的重合,代码如下： <br /><br />/* We found where to put this one. Check for overlap with <br />* preceding fragment, and, if needed, align things so that <br />* any overlaps are eliminated. <br />*/ <br />if ((prev != NULL) &amp;&amp; (offset 〈 prev-〉end)) { <br />i = prev-〉end - offset; <br />offset += i; /* ptr into datagram */ <br />ptr += i; /* ptr into fragment data */ <br />} <br /><br />注意此处offset已经乘了8,即以byte为单位了。举个形象一点的例子，如果有这样两个分片： <br /><br />offset1=0 end1=256 <br />------------------------- <br />| Frag1(先到) |〈---------prev <br />------------------------- <br /><br />offset2=64 end2=640 <br />------------------------------------------ <br />| Frag2（后到） | <br />------------------------------------------ <br /><br />处理后变为： <br /><br />offset1=0 end1=256 <br />------------------------- <br />| Frag1(先到) |〈---------prev <br />------------------------- <br /><br />offset2=256 end2=640 <br />----------------------- <br />| Frag2（后到） | <br />----------------------- <br /><br />紧接着做与后面分片重叠的处理，代码如下： <br />/* Look for overlap with succeeding segments. <br />* If we can merge fragments, do it. <br />*/ <br />for (tmp = next; tmp != NULL; tmp = tfp) { <br />tfp = tmp-〉next; <br />if (tmp-〉offset 〉= end) <br />break; /* no overlaps at all */ <br /><br />i = end - next-〉offset; /* overlap is ’i’ bytes */ <br />tmp-〉len -= i; /* so reduce size of */ <br />tmp-〉offset += i; /* next fragment */ <br />tmp-〉ptr += i; <br /><br />/* If we get a frag size of 〈= 0, remove it and the packet <br />* that it goes with. <br />*/ <br />if (tmp-〉len 〈= 0) { <br />if (tmp-〉prev != NULL) <br />tmp-〉prev-〉next = tmp-〉next; <br />else <br />qp-〉fragments = tmp-〉next; <br /><br />if (tmp-〉next != NULL) <br />tmp-〉next-〉prev = tmp-〉prev; <br /><br />/* We have killed the original next frame. */ <br />next = tfp; <br /><br />frag_kfree_skb(tmp-〉skb); <br />frag_kfree_s(tmp, sizeof(struct ipfrag)); <br />} <br />} <br /><br />其中if (tmp-〉len 〈= 0)判断后面的是为了处理teardrop攻击的，将在后面描述。 <br /><br />我们继续用图表示，如果有这样两个分片： <br /><br />offset1=128 end1=960 <br />---------------------－－－－－－－－－－---- <br />next-------〉| Frag1(先到) | <br />(tmp) -----------------------－－－－－－－－－－-- <br /><br />offset2=64 end2=320 <br />------------------------- <br />| Frag2（后到） | <br />------------------------- <br /><br />处理后将变为： <br /><br />offset1=320 end1=960 <br />--------－－－－－－－－－－---- <br />next-------〉| Frag1(先到) | <br />(tmp) ----------－－－－－－－－－－-- <br /><br />offset2=64 end2=320 <br />------------------------- <br />| Frag2（后到） | <br />------------------------- <br /><br />更复杂的情况不再一一列举，下面我们便可看一下具体利用分片的攻击办法： <br /><br />（1）Teardrop(CERT CA-97.29，bugtraq id 124) <br /><br />许多老系统在处理分片组装时存在漏洞，发送异常的分片包会使系统运行异常，teardrop <br />便是一个经典的利用这个漏洞的攻击程序。其原理如下（以linux为例）： <br />发送两个分片IP包，其中第二个IP包完全与第一在位置上重合。如下图： <br /><br />〈- len1 -〉 <br />------------------------- <br />| Frag1 | <br />------------------------- <br />offset1 end1 <br /><br />〈- len2 -〉 <br />------------- <br />| Frag2 | <br />------------- <br />offset2 end2 <br /><br />在linux(2.0内核)中有以下处理: <br />当发现有位置重合时（offset2改len2的值： <br />len2=end2-offset2; <br />注意此时len2变成了一个小于零的值，在以后处理时若不加注意便会出现系统崩溃的问题。 <br />但具体到什么地方出现问题没有追踪过，毕竟这已经是陈年旧事了。 <br />新的版本检查了这个值的大小，如果出现小于零的情况，则把这个分片丢掉。 <br /><br />（2）Jolt2(MS00-029) <br /><br />jolt2是2000年五月份出现的新的利用分片进行的攻击程序，几乎可以造成当前所有的windows <br />平台（95,98,NT,2000）死机。原理是发送许多相同的分片包，且这些包的offset值 <br />(8190*8=65520 bytes)与总长度(48 bytes)之和超出了单个IP包的长度限制（65536 bytes）。 <br />如下图： <br /><br />0 65535 <br />------------......------------- <br />| Max normal Fragment | <br />------------......------------- <br /><br />65520 65568(〉65535) <br />---------------- <br />|Jolt2 Fragment| <br />---------------- <br /><br />在linux中这种几乎马上就会被丢掉，在ip_defrag中有： <br />/* Attempt to construct an oversize packet. */if((ntohs(iph-〉tot_len) + ((int) offset)) 〉 65535)goto out_oversize; <br />尽管在后面（out_oversize出）对报警信息已经做了<span id="highlight_tag" style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; PADDING-BOTTOM: 0px; COLOR: #ee6600; PADDING-TOP: 0px; BACKGROUND-COLOR: yellow; EE6600: ">net_ratelimit</span>()处理，但在遭受攻击时,每5秒便打印一条信息还是很繁人，可以更改<span id="highlight_tag" style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; PADDING-BOTTOM: 0px; COLOR: #ee6600; PADDING-TOP: 0px; BACKGROUND-COLOR: yellow; EE6600: ">net_ratelimit</span>()的间隔时间和干脆关掉此条警告。 <br /><br />对windows系统便不知道它是怎么处理的了，一打的话CPU便会达到100%。2000的SP1号称已解决了问题，但没有试过。 <br /><br />（3）bugtraq id 376 Linux IP Fragment Overlap Vulnerability <br /><br />此种攻击对2.0.33内核有效，其实此攻击事实上并不是分片组装算法的问题，而是在在实现上出现了小的纰漏，在ip_glue中有： <br /><br />if(len〉65535) <br />{ <br />printk("Oversized IP packet from %s.\n", in_ntoa(qp-〉iph-〉saddr)); <br />ip_statistics.IpReasmFails++; <br />ip_free(qp); <br />return NULL; <br />} <br /><br />问题出现在printk上，如果对方一直用超大碎片（len〉65535），内核将会无节制的调用printk报警。而printk这种操作是相当耗费资源的，因此造成DOS。 <br />在2.0.34版中改成了： <br />NETDEBUG(printk("Oversized IP packet from %s.\n", in_ntoa(qp-〉iph-〉saddr))); <br />而/include/net/sock.h中： <br />#if 1 <br />#define NETDEBUG(x) do { } while (0) <br />#else <br />#define NETDEBUG(x) do { x; } while (0) <br />#endif <br />即只有在调试是才打开此功能，正常时不作任何事。 <br /><br />而后来的版本中加入了<span id="highlight_tag" style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; PADDING-BOTTOM: 0px; COLOR: #ee6600; PADDING-TOP: 0px; BACKGROUND-COLOR: yellow; EE6600: ">net_ratelimit</span>（）函数，限制成最多5秒钟发出一次内核警告： <br />if (<span id="highlight_tag" style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; PADDING-BOTTOM: 0px; COLOR: #ee6600; PADDING-TOP: 0px; BACKGROUND-COLOR: yellow; EE6600: ">net_ratelimit</span>()) <br />printk(KERN_INFO <br />"Oversized IP packet from %d.%d.%d.%d.\n", <br />NIPQUAD(qp-〉iph-〉saddr)); <br />这个问题不光在分片组装时用到，所有的网络部分的代码在打印调试信息时都要考虑大量日志造成拒绝服务的问题。目前的比较好且通用解决办法便是通过<span id="highlight_tag" style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; PADDING-BOTTOM: 0px; COLOR: #ee6600; PADDING-TOP: 0px; BACKGROUND-COLOR: yellow; EE6600: ">net_ratelimit</span>（）函数。 <br /><br />（4）bugtraq id 543 Linux IPChains Fragment Overlap Vulnerability <br /><br />ipchains在处理分片包，只处理第一个（offset==0&amp;&amp;MF=1）,因为只有这个包有TCP,UDP的头信息，其他后续的分片不作防火墙规则匹配，直接通过。假如防火墙之后的系统的IP分片组装类似如下做法：若有重叠，后来的包覆盖前面来的包。这样的话攻击者可以首先造一个可以通过防火墙规则的合法分片(如一个可访问的端口 )，再造一个与之重叠的分片，改掉前一个片中的信息（如一个不可访问的端口），这样最终的结果便是突破了防火墙的检测。 <br /><br />但此种方法只是理论上的，要依靠防火墙后面的主机的分片组装算法的具体实现。反正如果后面也是一台linux的话便无效，因为在处理重叠的时候linux不允许改变位置在自己之前的片的内容（详见上面的讨论）。 <br />此方法在2.2.11以后的内核版本中更难实施，在ipchains处理分片时会检查分片的长度，如果过小则返回FW_BLOCK，即丢弃。 <br /><br />（5）其他 <br /><br />碎片攻击不光会攻击操作系统，由于许多网络工具，如防火墙，入侵检测系统（IDS）也在内部作了分片组装，如果处理不当，也同样会遭受攻击，如著名的checkpoint的防火墙FW-1某些版本（最新的已经改正了）便同样会受到碎片DOS攻击（可详见nsfocus第12期月刊《了解Check Point FW-1状态表》）. <br /><br />碎片也可以用来逃避IDS检测，许多网络入侵检测系统的机理是单IP包检测，没有处理分片，即使是象ISS这样的公司也是在最新的5.0版本中才实现了组装功能，更不用说snort了，其IP组装插件经常造成core dump,因此大多数人都将此功能关闭了。 <br /><br />参考资料： <br /><br />[1.] linux2.0.33,2.0.34,2.2.16,2.4.0-test3的源代码。 <br />[2.] securityfocus的漏洞资料。 <br />[3.] bugtraq上的一些邮件，数目众多，恕不一一列举 <img src ="http://www.cnitblog.com/flutist1225/aggbug/19986.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:15 <a href="http://www.cnitblog.com/flutist1225/articles/19986.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SAMBA配置详解</title><link>http://www.cnitblog.com/flutist1225/articles/19985.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:13:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19985.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19985.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19985.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19985.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19985.html</trackback:ping><description><![CDATA[一个运行SAMBA配置恰当的Linux服务器可以替代Windows NT/2000服务器， 它一般能共享目录, 提供活动目录服务(active directory service ,ADS) 但是它可以做为主域控制器(Primary Domain Controller, PDC), 进行 Windows 2000/NT/98/95 作为客户机的用户认证 ，共享资源（目录和打印机） 和定制用户会话。<br />这篇文章主要集中到这些方面。<br /><br />许多的计算机环境都以Windows 服务器提供的功能为基础，装有SAMBA的Linux 服务器会在不改变客户机的情况下，替代所有基于Windows系统提供的功能。<br />以下的要讨论的步骤假定：SAMBA已经安装并且运行正确的机器将被用做服务器。读者需要 Linux和Windows服务器的基本的知识。<br /><br /><br /><br />案例学习<br /><br />考虑Linux/Samba 服务器作为主域控制器（PDC）, 每个认证过的用户进入 两种共享的目录，一个是公共空间，一个是私人空间。在这篇文章里，讨论一种进 入私人数据空间的极为常见的情况，就是每个用户有一个个人的目录。<br /><br /><br /><br />要考虑的细节：<br /><br />Linux/Samba NetBIOS 名字:SMBServer<br />Windows 域名(工作组): THEDOMAIN<br />每个用户的私人分区: H: (Windows) =&gt; /home/ (Linux server)<br />公共分区: P: (Windows) =&gt; /home/public<br /><br /><br />图 1 显示了一个简单的网络示意图，客户机运行Windows系统，使用 Windows NT/2000服务器提供的资源和服务。 这个服务器能被 Linux/SAMBA服务器替代。<br /><br /><br />Fig. 1 – 在Windows服务器上运行的主域控制器和 文件服务器<br /><br />配置<br /><br />遵循以下步骤：<br /><br />1) 创建要在主域服务器(Linux/Samba)待认证的用户。<br />使用adduser 命令, useradd 或 userconf, 你可以使用一些用户管理的工具，也可以 是带有图形界面的(Webmin，Linuxconf，Yast等).<br /><br />需要确认如果用户只进入Linux/Samba服务（如果你想）， 这就是说用户不必进入Linux命令行，这样的话只有把home目录设成/dev/null ，命令行设成/bin/false。<br /><br />2) 把UNIX用户转换成Linux/Samba/Windows用户,生成smbpasswd 文件。<br /><br />cat /etc/passwd | mksmbpasswd.sh &gt; /etc/samba/smbpasswd<br /><br />另一个方法是，执行一下的SAMBA命令来创建用户和定义密码：<br /><br />smbadduser<br />smbpasswd<br /><br />这些命令和adduser与passwd一样有类似的作用。<br /><br />3) 编辑SAMBA的配置文件（smb.conf）， 你要确定加入或减去下列标有comment的可选项：<br /><br />netbios name = SMBServer<br />workgroup = THEDOMAIN<br />server string = Linux Samba NT Server<br />log file = /var/log/samba/%m.log<br />max log file = 0<br />security = user<br />encrypt password = yes<br />smb password file = /etc/samba/smbpasswd<br />ssl CA certificate = /usr/share/ssl/.... (cancel comment)<br />socket options = (cancel comment)<br />local master = yes<br />preferred master = yes<br />domain master = yes<br />domain logons = yes<br />logon script = logon.bat<br />wins support = yes<br /><br />注意：<br />做为每一个用户的特有的登陆(login)， 需要使用"%U.bat"文件替换 原先的“登陆描述”(login script)。这样每一个用户都有一个的带有自 己用户名的“登陆描述”， %u 也是可以使用的. 如果你想定义用户属于 那个组，你可以使用 %g或%G，这些参数和其他参数的定义可以在手册 中找到。(man smb.conf)<br /><br />4) 创建共享资源<br />编辑smb.conf 文件 并注释所有的“共享”的例子，加入以下 的信息，如果没有必要的话，不用更改：<br /><br />[netlogon]<br />comment = Initialization Scripts<br />path = /home/netlogon<br />read only = yes<br />guest ok = yes<br />browseable = no<br /><br />[home]<br />comment = User Directory<br />path = /home/%U<br />browseable = yes<br />writable = yes<br /><br />[public]<br />comment = Public Directory<br />path = /home/public<br />browseable = yes<br />writable = yes<br />guest ok = yes<br />create mask = 0777<br />force create mask = 0777<br /><br />保存smb.conf 文件。<br /><br />5) 你可以使用如下的命令来验证smb.conf是否正确：<br /><br />testparm<br /><br />这些命令分析smb.conf 文件并报告发现的错误。<br /><br />6) 分别使用权限0754和0777 创建/home/netlogon 和/home/public目录。<br /><br />7) 编辑logon描述文件logon.bat。<br />重要提示: 使用DOS/Windows文字编辑器 （比如Notepad或Edit）来创建logon.bat文件 （所以保存的文本文件是微软兼容的形式），你也可以在Linux上做这件事但是你必须转换成正确的文本形式。 你可以使用比如 Vim的命令":set textmode"得到有微软行结尾符的文件。<br /><br />net time SMBServer /y (you can also use: /yes instead of /y )<br />net use H: SMBServerhome -y (you can also use: /yes or /y instead of -y )<br />net use P: SMBServerpublic -y<br /><br />8) 加入SMBServer信息到lmhosts文件中。<br />编辑/etc/samba/lmhosts 文件后 /etc/lmhosts)文件并且 加入关于SMBServer信息的一行。<br /><br />SMB服务器, 比如: 192.168.0.10 SMBServer<br /><br />9) 重启动SAMBA的后台程序（smbd）。<br /><br />service smb restart<br /><br />如果在你的Linux版本中上面的命令不工作，你可以使用如下命令：<br />ps -auxgx | grep smb<br />kill -9 &lt;process ID of smb&gt;<br />smbd<br /><br />10) 使用smbclient来验证以上的配置是正确的。<br /><br />smbclient -L //SMBServer<br /><br />如果"Password:"显示出来, 按"Enter" 键，服务器的共享的 资源会显示出来。<br /><br />11) 使用Windows 95/98/NT 计算机在域THEDOMAIN中进行客户登陆， 使用Linux/Samba创建的用户（看步骤1和2）。<br /><br />在95/98/ME中, 配置可以按照一下的顺序：<br /><br />开始 =&gt; 设置 =&gt; 控制面板=&gt; 网络 =&gt;微软网络客户 =&gt; 属性。<br /><br />Windows NT/2000（工作站/专业版）中也是类似的用法， 可能顺序不是一样。<br /><br />单击选项"Start session in Windows NT/2000 domain" 并写下域名 THEDOMAIN (WORKGROUP)。<br /><br />一个配置文件的实例<br /><br />一个完整的SAMBA配置文件罗列如下，这个文件在不通的Linux分发版本中测试通过。 读者可以修改它以达到自己想要的结果。其中每条指令都被恰当的注释。<br /><br />最后，给那些的想快速配置SAMBA的人的建议是安装 Webmin或者SWAT，这些工具可以让你配置时轻松一点。<br /><br />#============================================================#<br /># /etc/smb.conf<br />#------------------------------------------------------------------------------------------------------------#<br /># SAMBA主配置文件<br /># 配置文件的骨架，根据你的需求选择其中的参数。<br />#------------------------------------------------------------------------------------------------------------#<br /># 测试通过的系统： Solaris系统和 Linux各个发布版本<br /># RedHat 6.0, 7.0 和 7.1<br /># Solaris 7<br /># Slackware 7.x<br /># Mandrake 6.1, 7.0 和 8.1<br /># SuSe 7.2<br />#------------------------------------------------------------------------------------------------------------#<br /># 最近一次更改时间： 08/12/2001<br /># 作者：Sebastian Sasias - sasias@Linuxmail.org<br />#============================================================#<br />#<br /># 这个文件按照SAMBA规范来开发的，可以参见smb.conf(5)手册。<br />#<br /># OBS: 更改本文件以后,使用"testparm"命令来测试。<br />#<br />#======================== Global Options =======================#<br />#<br /># 总配置<br />#<br />[global]<br />#......................................................................................................................................#<br /># workgroup = NT-Domain-Name o Workgroup-Name，比如： THEDOMAIN<br /># PDC域<br />workgroup = THEDOMAIN （不区分大小写）<br />#......................................................................................................................................#<br /># 在其他的机器中声明的本机器的名称<br />netbios name = SMBServer<br />#......................................................................................................................................#<br /># 这个声明会出现在Windows的“网络邻居”中<br />server string = Samba Server de este lugar<br />#......................................................................................................................................#<br /># 这一行由于安全的原因很关键，只许在局域网中特定的计算机的连接。<br /># 在这个例子中，是192.168.8.0（C级网络）的网络<br /># 和“环路”(loopback)的接口是可以连接的。<br /># 更多的细节，请阅读smb.conf man手册。<br /># 比如：只有从规定开始的ip以后的地址才能共享资源。<br /># 192.168.8 和 127 (以后的注释)<br />; hosts allow = 192.168.8. 127.<br />#......................................................................................................................................#<br /># 如果你想自动载入一个打印机的清单，这样就不必一个一个手动录入，可以使用：<br />; load printers = yes<br />#......................................................................................................................................#<br /># 覆盖printcap的路径是可能的。<br />; printcap name = /etc/printcap<br />#......................................................................................................................................#<br /># 在SystemV系统中 printcap对lpstat名称属性一定允许<br /># 自动地从SystemV（这么个词！:-)）的spool系统中取得打印机列表。<br />; printcap name = lpstat<br />#......................................................................................................................................#<br /># 如果打印机系统是非标准的，就需要指定是什么打印系统。<br /># 现在支持的打印系统有：<br /># bsd, sysv, plp, lprng, aix, hpux, qnx<br />; printing = bsd<br />#......................................................................................................................................#<br /># 如果你需要一个guest帐户，你不要注释掉下面这行。<br /># 你一定么加入这条到/etc/passwd里，否则这个用户无“人”可用。<br />; guest account = pcguest<br />#......................................................................................................................................#<br /># 下面这条就是使得每台计算机都有一个不同的log文件，<br /># 通过这个文件和SAMBA服务器相连。<br />log file = /var/log/samba/log.%m<br />#......................................................................................................................................#<br /># 设置log文件长度的限制（单位 Kb)。<br />max log size = 50<br />#......................................................................................................................................#<br /># 阅读security_level.txt for得到更多的细节<br /># 指定验证密码的方式<br /># 用户级的安全策略＝每个用户都有自己的密码 （SAMBA密码）<br />security = user<br />#......................................................................................................................................#<br /># 如果使用服务器级的安全策略，验证过程在另一个机器上进行。<br /># 只有在使用服务器级的安全策略时，才使用值"password server"<br /># 密码服务器等于【认证服务器地址】。<br />; password server = &lt;NT-Server-Name&gt;<br />#......................................................................................................................................#<br /># 如果你想使用加密的密码，请阅读Samba文档中的ENCRYPTION.TXT，<br /># Win95.txt和WinNT.txt。<br /># 你只有清楚的了解这个属性的足够多的信息才能使用它。<br /># 信息：Win95，Win98和 WinNT 可以发送加密的密码。<br />encrypt passwords = yes<br />#......................................................................................................................................#<br /># 使用下列的行来定制你的配置。<br /># 在网络中的每个计算机，%m 取代了自身netbios的名字。<br />; include = /usr/local/samba/lib/smb.conf.%m<br />#......................................................................................................................................#<br /># 在你会发现文档和一些流行的“提示”会告诉你下面的选项可以得到更好的性能。<br /># 试试！<br /># 阅读speed.txt和手册来知道更多的细节。<br />socket options = TCP_NODELAY<br />#......................................................................................................................................#<br /># Samba 可以配置多种的网络接口。<br /># 如果你使用多种网络接口，你一定要在下面列出来。<br /># 阅读手册来知道更多的细节。<br />; interfaces = 192.168.8.2/24 192.168.12.2/24<br />#......................................................................................................................................#<br /># Browser 控制选项：<br /># 如果你不想让samba做为网络中的主browser， 设置"local master = no"。<br />local master = yes<br />#......................................................................................................................................#<br /># 在OS 水平上，本服务器当选主browser优先权的设置。<br /># 一般地，缺省值可能就可以了。<br />; os level = 33<br />#......................................................................................................................................#<br /># 域主机指定Samba成为域中主Browser。<br /># 这样允许Samba运行域控制器和在不同的TCP/IP子网中能被“看成”一台机器。<br /># 如果你使用了 Windows NT/2000域控制器，你就不应该使用它。<br />domain master = yes<br />#......................................................................................................................................#<br /># 更高级的域主在启动中使得Samba成为一个局域的Browser，<br /># 这使得它有更多的机会（选举成为域主）。<br /># 如果我们有2个以上的服务器，级别高的服务器会更受“欢迎”，<br /># 客户机会在一个列表中搜寻到一台服务器的。<br />preferred master = yes<br />#......................................................................................................................................#<br /># 只有你使用NT/2000 服务器在以一个主域控制器(PDC)在运行，你才能使用下条。<br />; domain controller = &lt;NT-Domain-Controller-SMBName&gt;<br />#......................................................................................................................................#<br /># 如果你想把SAMBA当成Windows 9x/Me 工作站的“域登陆服务器”，你要使用下条。<br />domain logons = yes<br />#......................................................................................................................................#<br /># 如果你使用了“域登陆”，你一定要使用一个登陆脚本，<br /># 在Windows网络中的每台机器或者每个用户。<br /><br /># 每个工作站的特定登陆批处理是<br />; logon script = %m.bat<br /><br /># 每个用户的特定登陆批处理是<br />; logon script = %U.bat<br />#......................................................................................................................................#<br /># 那里存放零星的profiles文件 (只对Win95和 WinNT有效)<br /># %L 取代这个服务器的NetBIOS名字， %U 取代用户名<br /># 如果你使用它，一定不要注释掉下面的Profiles共享<br />; logon path = \%LProfiles\%U<br />#......................................................................................................................................#<br /># Windows互联网解析服务器：<br /># WINS支持 — 告知NMBD使能它的WINS 服务器。<br /># WINS协议 把机器名转换成IP地址，<br /># 它象TCP/IP中DNS那样工作。<br />; wins support = yes<br />#......................................................................................................................................#<br /># WINS服务器－告知Samba的NMBD部件成为WINS的一个客户。<br /># SAMBA服务器可以成为其中的一个：WINS 服务器或 WINS客户机，<br /># 但是不可以同时2者皆是。<br /># 这里WINS IP服务器一定要指定。<br />; wins server = 192.168.8.1<br />#......................................................................................................................................#<br /># WINS代理－告知Samba回应那些没有WINS能力的客户的名字解析的请求，<br /># 这个情况只有在网络中至少有一台WINS服务器时才有效。<br /># 缺省是不。<br />; wins proxy = yes<br />#......................................................................................................................................#<br /># DNS代理－告知Samba是否解析NetBIOS名字<br /># 版本1.9.17 内建的缺省是“是”, 从版本1.9.18 变成了“否”<br /># 这里我们可以告知SAMBA名字解析使用DNS或者不。<br /># dns proxy = yes<br /># dns proxy = no (name resolution will be made by using the file lmhosts )<br />#......................................................................................................................................#<br /># 如果登陆的驱动盘没有被指定，Z：单元会自动登陆的。<br />logon drive = P:<br />#......................................................................................................................................#<br /># 当一个登陆出现时，这个脚本被执行： /etc/samba/netlogon/SAMBA.BAT<br /># 并且使用"netuse"来登陆磁盘单元<br />logon script = SAMBA.BAT<br /><br />#====================== Share Definitions ========================#<br /><br /># 每个用户的私人目录<br /># 单位 P：<br /><br />[homes]<br />comment = Home Directories<br />browseable = no<br />writable = yes<br />readonly = no<br />force create mode = 0700<br />create mode = 0700<br />force directory mode = 0700<br />directory mode = 700<br /><br />#------------------------------------------------------------------------------------------------------------#<br /># 临时文件目录<br /># 单位 T:<br /><br />[tmp]<br />comment = Tempora Files<br />path = /tmp<br />readonly = no<br />public = yes<br />writable = yes<br />force create mode = 0777<br />create mode = 0777<br />force directory mode = 0777<br />directory mode = 0777<br /><br />#------------------------------------------------------------------------------------------------------------#<br /># 服务器的CD-ROM<br /># 单位 L:<br /><br />[cdrom]<br />comment = CD-ROM<br />path = /mnt/cdrom<br />public = yes<br />writable = no<br /><br />#------------------------------------------------------------------------------------------------------------#<br /># 组，根据/home/grp.name_group来的<br /># /home/user/group 是 /home/grp.name_group一个链接<br /># grp.name_group 使用权限770<br /># 单位 G：<br /><br />[group]<br />comment = Directory of Group<br />path = /home/%u/group<br />writable = yes<br />readonly = no<br />force create mode = 0770<br />create mode = 0770<br />force directory mode = 0770<br />directory mode = 0770<br /><br />#------------------------------------------------------------------------------------------------------------#<br /># 这个单元存放应用软件，安装软件，专用软件等。<br /># /net 和 /net/install的权限是 755, 比如这里， root是它的拥有者<br /># 单位 N：<br /><br />[net]<br />comment = Directory Net<br />path = /net<br />writable = yes<br />readonly = no<br />force create mode = 0750<br />create mode = 0750<br />force directory mode = 0750<br />directory mode = 0750<br /><br />#------------------------------------------------------------------------------------------------------------#<br />[netlogon]<br />comment = Logon Services in the Network<br />path = /etc/samba/netlogon<br />guest ok = yes<br />writable = no<br />locking = no<br />public = no<br />browseable = yes<br />share modes = no<br /><br />#------------------------------------------------------------------------------------------------------------#<br />#============================================================#<br /><br />最后的考虑<br /><br />SAMBA包和其他的一些在Linux上使用的工具在不断的发展中，因此 有可能这里讲的一些细节会失去效用。实际上在SAMBA的发展中一些参 数名字在配置文件中变化很小，并且保持了更优化的结构。<br /><br />如果你在SAMBA配置中发现一些未知参数的错误信息，你可能有2个 简单的办法解决它：<br /><br />* 阅读缺省的smb.conf文件，里面相同的行一般会有注释，可以提供 一些“可能产生问题的参数”的信息。<br />* 阅读SAMBA文档, 从描述最近一次的版本变化的文件开始。 <br /><br /><br />参考：书目和软件工具<br /><br />* SAMBA，官方网址: http://www.samba.org<br />* Webmin，官方网址: http://www.webmin.com a remote administration tool for computers running UNIX systems.<br />* GNU 项目 和自由软件基金会：http://www.gnu.org<br />* 一个有意思的网址，那里可以得到RPM包： http://www.rpmfind.net<br />* LinNeighborhood 网址: http://www.bnro.de/~schmidjo， 一个有意思的Linux工具，使用它可以通过“SAMBA网络”共享资源。<br /><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19985.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:13 <a href="http://www.cnitblog.com/flutist1225/articles/19985.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>主动FTP与被动FTP－权威解释</title><link>http://www.cnitblog.com/flutist1225/articles/19984.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 13:12:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19984.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19984.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19984.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19984.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19984.html</trackback:ping><description><![CDATA[处理防火墙和其他网络连接问题时最常见的一个难题是主动FTP与被动FTP的区别以及如何完美地支持它们。幸运地是，本文能够帮助你清除在防火墙环境中如何支持FTP这个问题上的一些混乱。 <br /><br />本文也许不像题目声称的那样是一个权威解释，但我已经听到了很多好的反馈意见，也看到了本文在许多地方被引用，知道了很多人都认为它很有用。虽然我一直在找寻改进的方法，但如果你发现某个地方讲的不够清楚，需要更多的解释，请告诉我！最近的修改是增加了主动FTP和被动FTP会话中命令的例子。这些会话的例子应该对更好地理解问题有所帮助。例子中还提供了非常棒的图例来解释FTP会话过程的步骤。现在，正题开始了... <br /><br />基础 <br /><br />FTP是仅基于TCP的服务，不支持UDP。 与众不同的是FTP使用2个端口，一个数据端口和一个命令端口（也可叫做控制端口）。通常来说这两个端口是21－命令端口和20－数据端口。但当我们发现根据（FTP工作）方式的不同数据端口并不总是20时，混乱产生了。 <br /><br />主动FTP <br /><br />主动方式的FTP是这样的：客户端从一个任意的非特权端口N（N&gt;1024）连接到FTP服务器的命令端口，也就是21端口。然后客户端开始监听端口N+1，并发送FTP命令“port N+1”到FTP服务器。接着服务器会从它自己的数据端口（20）连接到客户端指定的数据端口（N+1）。 <br /><br />针对FTP服务器前面的防火墙来说，必须允许以下通讯才能支持主动方式FTP： <br /><br /><br />1. 任何端口到FTP服务器的21端口 （客户端初始化的连接 S&lt;-C） <br />2. FTP服务器的21端口到大于1024的端口（服务器响应客户端的控制端口 S-&gt;C） <br />3. FTP服务器的20端口到大于1024的端口（服务器端初始化数据连接到客户端的数据端口 S-&gt;C） <br />4. 大于1024端口到FTP服务器的20端口（客户端发送ACK响应到服务器的数据端口 S&lt;-C） <br /><br /><br />在第1步中，客户端的命令端口与FTP服务器的命令端口建立连接，并发送命令“PORT 1027”。然后在第2步中，FTP服务器给客户端的命令端口返回一个\"ACK\"。在第3步中，FTP服务器发起一个从它自己的数据端口（20）到客户端先前指定的数据端口（1027）的连接，最后客户端在第4步中给服务器端返回一个\"ACK\"。 <br /><br />主动方式FTP的主要问题实际上在于客户端。FTP的客户端并没有实际建立一个到服务器数据端口的连接，它只是简单的告诉服务器自己监听的端口号，服务器再回来连接客户端这个指定的端口。对于客户端的防火墙来说，这是从外部系统建立到内部客户端的连接，这是通常会被阻塞的。 <br /><br />主动FTP的例子 <br /><br />下面是一个主动FTP会话的实际例子。当然服务器名、IP地址和用户名都做了改动。在这个例子中，FTP会话从 testbox1.slacksite.com (192.168.150.80)，一个运行标准的FTP命令行客户端的Linux工作站，发起到testbox2.slacksite.com (192.168.150.90)，一个运行ProFTPd 1.2.2RC2的Linux工作站。debugging（-d）选项用来在FTP客户端显示连接的详细过程。红色的文字是 debugging信息，显示的是发送到服务器的实际FTP命令和所产生的回应信息。服务器的输出信息用黑色字表示，用户的输入信息用粗体字表示。 <br /><br />仔细考虑这个对话过程我们会发现一些有趣的事情。我们可以看到当 PORT 命令被提交时，它指定了客户端(192.168.150.80)上的一个端口而不是服务器的。当我们用被动FTP时我们会看到相反的现象。我们再来关注 PORT命令的格式。就象你在下面的例子看到的一样，它是一个由六个被逗号隔开的数字组成的序列。前四个表示IP地址，后两个组成了用于数据连接的端口号。用第五个数乘以256再加上第六个数就得到了实际的端口号。下面例子中端口号就是( (14*256) + 178) = 3762。我们可以用netstat来验证这个端口信息。 <br /><br />testbox1: {/home/p-t/slacker/public_html} % ftp -d testbox2 <br />Connected to testbox2.slacksite.com. <br />220 testbox2.slacksite.com FTP server ready. <br />Name (testbox2:slacker): slacker <br />---&gt; USER slacker <br />331 Password required for slacker. <br />Password: TmpPass <br />---&gt; PASS XXXX <br />230 User slacker logged in. <br />---&gt; SYST <br />215 UNIX Type: L8 <br />Remote system type is UNIX. <br />Using binary mode to transfer files. <br />ftp&gt; ls <br />ftp: setsockopt (ignored): Permission denied <br />---&gt; PORT 192,168,150,80,14,178 <br />200 PORT command successful. <br />---&gt; LIST <br />150 Opening ASCII mode data connection for file list. <br />drwx------ 3 slacker users 104 Jul 27 01:45 public_html <br />226 Transfer complete. <br />ftp&gt; quit <br />---&gt; QUIT <br />221 Goodbye. <br /><br />被动FTP <br /><br />为了解决服务器发起到客户的连接的问题，人们开发了一种不同的FTP连接方式。这就是所谓的被动方式，或者叫做PASV，当客户端通知服务器它处于被动模式时才启用。 <br /><br />在被动方式FTP中，命令连接和数据连接都由客户端，这样就可以解决从服务器到客户端的数据端口的入方向连接被防火墙过滤掉的问题。当开启一个 FTP连接时，客户端打开两个任意的非特权本地端口（N &gt; 1024和N+1）。第一个端口连接服务器的21端口，但与主动方式的FTP不同，客户端不会提交PORT命令并允许服务器来回连它的数据端口，而是提交 PASV命令。这样做的结果是服务器会开启一个任意的非特权端口（P &gt; 1024），并发送PORT P命令给客户端。然后客户端发起从本地端口N+1到服务器的端口P的连接用来传送数据。 <br /><br />对于服务器端的防火墙来说，必须允许下面的通讯才能支持被动方式的FTP: <br /><br /><br />1. 从任何端口到服务器的21端口 （客户端初始化的连接 S&lt;-C） <br />2. 服务器的21端口到任何大于1024的端口 （服务器响应到客户端的控制端口的连接 S-&gt;C） <br />3. 从任何端口到服务器的大于1024端口 （入；客户端初始化数据连接到服务器指定的任意端口 S&lt;-C） <br />4. 服务器的大于1024端口到远程的大于1024的端口（出；服务器发送ACK响应和数据到客户端的数据端口 S-&gt;C） <br /><br /><br />画出来的话，被动方式的FTP连接过程大概是下图的样子： <br /><br /><br /><br />在第1步中，客户端的命令端口与服务器的命令端口建立连接，并发送命令“PASV”。然后在第2步中，服务器返回命令\"PORT 2024\"，告诉客户端（服务器）用哪个端口侦听数据连接。在第3步中，客户端初始化一个从自己的数据端口到服务器端指定的数据端口的数据连接。最后服务器在第4 步中给客户端的数据端口返回一个\"ACK\"响应。 <br /><br />被动方式的FTP解决了客户端的许多问题，但同时给服务器端带来了更多的问题。最大的问题是需要允许从任意远程终端到服务器高位端口的连接。幸运的是，许多FTP守护程序，包括流行的WU-FTPD允许管理员指定FTP服务器使用的端口范围。详细内容参看附录1。 <br /><br />第二个问题是客户端有的支持被动模式，有的不支持被动模式，必须考虑如何能支持这些客户端，以及为他们提供解决办法。例如，Solaris提供的FTP命令行工具就不支持被动模式，需要第三方的FTP客户端，比如ncftp。 <br /><br />随着WWW的广泛流行，许多人习惯用web浏览器作为FTP客户端。大多数浏览器只在访问ftp://这样的URL时才支持被动...??的配置。 <br /><br />被动FTP的例子 <br /><br />下面是一个被动FTP会话的实际例子，只是服务器名、IP地址和用户名都做了改动。在这个例子中，FTP会话从 testbox1.slacksite.com (192.168.150.80)，一个运行标准的FTP命令行客户端的Linux工作站，发起到testbox2.slacksite.com (192.168.150.90)，一个运行ProFTPd 1.2.2RC2的Linux工作站。debugging（-d）选项用来在FTP客户端显示连接的详细过程。红色的文字是 debugging信息，显示的是发送到服务器的实际FTP命令和所产生的回应信息。服务器的输出信息用黑色字表示，用户的输入信息用粗体字表示。 <br /><br />注意此例中的PORT命令与主动FTP例子的不同。这里，我们看到是服务器(192.168.150.90)而不是客户端的一个端口被打开了。可以跟上面的主动FTP例子中的PORT命令格式对比一下。 <br /><br />testbox1: {/home/p-t/slacker/public_html} % ftp -d testbox2 <br />Connected to testbox2.slacksite.com. <br />220 testbox2.slacksite.com FTP server ready. <br />Name (testbox2:slacker): slacker <br />---&gt; USER slacker <br />331 Password required for slacker. <br />Password: TmpPass <br />---&gt; PASS XXXX <br />230 User slacker logged in. <br />---&gt; SYST <br />215 UNIX Type: L8 <br />Remote system type is UNIX. <br />Using binary mode to transfer files. <br />ftp&gt; passive <br />Passive mode on. <br />ftp&gt; ls <br />ftp: setsockopt (ignored): Permission denied <br />---&gt; PASV <br />227 Entering Passive Mode (192,168,150,90,195,149). <br />---&gt; LIST <br />150 Opening ASCII mode data connection for file list <br />drwx------ 3 slacker users 104 Jul 27 01:45 public_html <br />226 Transfer complete. <br />ftp&gt; quit <br />---&gt; QUIT <br />221 Goodbye. <br /><br />总结 <br /><br />下面的图表会帮助管理员们记住每种FTP方式是怎样工作的： <br /><br />主动FTP： <br />命令连接：客户端 &gt;1024端口 -&gt; 服务器 21端口 <br />数据连接：客户端 &gt;1024端口 &lt;- 服务器 20端口 <br /><br />被动FTP： <br />命令连接：客户端 &gt;1024端口 -&gt; 服务器 21端口 <br />数据连接：客户端 &gt;1024端口 -&gt; 服务器 &gt;1024端口 <br /><br />下面是主动与被动FTP优缺点的简要总结： <br /><br />主动FTP对FTP服务器的管理有利，但对客户端的管理不利。因为FTP服务器企图与客户端的高位随机端口建立连接，而这个端口很有可能被客户端的防火墙阻塞掉。被动FTP对FTP客户端的管理有利，但对服务器端的管理不利。因为客户端要与服务器端建立两个连接，其中一个连到一个高位随机端口，而这个端口很有可能被服务器端的防火墙阻塞掉。 <br /><br />幸运的是，有折衷的办法。既然FTP服务器的管理员需要他们的服务器有最多的客户连接，那么必须得支持被动FTP。我们可以通过为FTP服务器指定一个有限的端口范围来减小服务器高位端口的暴露。这样，不在这个范围的任何端口会被服务器的防火墙阻塞。虽然这没有消除所有针对服务器的危险，但它大大减少了危险。详细信息参看附录1。 <br /><br /><br />参考资料 <br /><br />O\'Reilly出版的《组建Internet防火墙》（第二版，Brent Chapman，Elizabeth Zwicky著）是一本很不错的参考资料。里面讲述了各种Internet协议如何工作，以及有关防火墙的例子。 <br /><br />最权威的FTP参考资料是RFC 959，它是FTP协议的官方规范。RFC的资料可以从许多网站上下载，例如：<a href="ftp://nic.merit.edu/documents/rfc/rfc0959.txt" target="_blank"><font color="#002033">ftp://nic.merit.edu/documents/rfc/rfc0959.txt</font></a> 。 <br /><br />Active FTP vs. Passive FTP, Appendix 1 <br /><br /><br /><br />几种常见FTP服务器与被动方式有关的设置 <br /><br />ProFTPD <br />参考: <a href="http://www.castaglia.org/proftpd/doc...-HOWTO-NAT.txt" target="_blank"><font color="#002033">http://www.castaglia.org/proftpd/doc...-HOWTO-NAT.txt</font></a><br /><br />MasqueradeAddress <br />PassivePorts <br /><br />vsftpd <br />参考: <a href="http://vsftpd.beasts.org/vsftpd_conf.html" target="_blank"><font color="#002033">http://vsftpd.beasts.org/vsftpd_conf.html</font></a><br /><br />pasv_enable <br />pasv_max_port <br />pasv_min_port <br />pasv_address <br /><br />Pure-FTPd <br /><br />参考: <a href="http://www.pureftpd.org/README" target="_blank"><font color="#002033">http://www.pureftpd.org/README</font></a><br /><br />-p --passiveportrange &lt;minport:maxport&gt; <br />-P --forcepassiveip &lt;ip address&gt; <br /><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19984.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 21:12 <a href="http://www.cnitblog.com/flutist1225/articles/19984.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用U-BOOT构建嵌入式系统的引导装载程序（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19981.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:52:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19981.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19981.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19981.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19981.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19981.html</trackback:ping><description><![CDATA[
		<p>
				<strong>1 U-BOOT简介<br /><br /></strong>　　U-BOOT是由德国的工程师Wolfgang Denk从8XXROM代码发展而来的，它支持很多处理器，比如PowerPC、ARM、MIPS和x86。目前，U-BOOT源代码在sourceforge网站的社区服务器中，Internet上有一群自由开发人员对其进行维护和开发，它的项目主页是http://sourceforge.net/projects/U-BOOT。U-BOOT的最新版本源代码可以在Sourceforge的CVS服务器中匿名获得。<br />#cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/U-BOOT login<br />#cvs -z6 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/U-BOOT \ co -P modulename<br /><br />1.1 U-BOOT源代码目录结构<br /><br />◆ board：和一些已有开发板有关的文件，比如Makefile和u-boot.lds等都和具体开发板的硬件和地址分配有关。<br />◆ common：与体系结构无关的文件，实现各种命令的C文件。<br />◆ cpu：CPU相关文件，其中的子目录都是以U-BOOT所支持的CPU为名，比如有子目录arm926ejs、mips、mpc8260和nios等，每个特定的子目录中都包括cpu.c和interrupt.c，start.S。其中cpu.c初始化CPU、设置指令Cache和数据Cache等；interrupt.c设置系统的各种中断和异常，比如快速中断、开关中断、时钟中断、软件中断、预取中止和未定义指令等；start.S是U-BOOT启动时执行的第一个文件，它主要是设置系统堆栈和工作方式，为进入C程序奠定基础。<br />◆ disk：disk驱动的分区处理代码。<br />◆ doc：文档。<br />◆ drivers：通用设备驱动程序，比如各种网卡、支持CFI的Flash、串口和USB总线等。<br />◆fs:支持文件系统的文件，U-BOOT现在支持cramfs、fat、fdos、jffs2和registerfs。<br />◆ include：头文件，还有对各种硬件平台支持的汇编文件，系统的配置文件和对文件系统支持的文件。<br />◆ net：与网络有关的代码，BOOTP协议、TFTP协议、RARP协议和NFS文件系统的实现。<br />◆ lib_arm：与ARM体系结构相关的代码。<br />◆ tools：创建S-Record格式文件 和U-BOOT images的工具。<br /><br />1.2 U-BOOT的特点<br /><br />　　U-BOOT支持SCC/FEC以太网、OOTP/TFTP引导、IP和MAC的预置功能，这一点和其它BootLoader(如BLOB和RedBoot等)类似。但U-BOOT还具有一些特有的功能。<br /><br />◆ 在线读写Flash、DOC、IDE、IIC、EEROM、RTC，其它的BootLoader根本不支持IDE和DOC的在线读写。<br />◆ 支持串行口kermit和S-record下载代码，U-BOOT本身的工具可以把ELF32格式的可执行文件转换成为 S-record格式，直接从串口下载并执行。<br />◆ 识别二进制、ELF32、uImage格式的Image，对Linux引导有特别的支持。U-BOOT对Linux 内核进一步封装为uImage。封装如下：<br />#{CROSS_COMPILE}-objcopy -O binary -R.note -R.comment -S vmlinux \ linux.bin <br />#gzip -9 linux.bin<br />#tools/mkimage -A arm -O linux -T kernel -C gzip -a 0xc0008000 -e\<br />0xc0008000 -n “Linux-2.4.20” -d linux.bin.gz /tftpboot/uImage<br />即在Linux内核镜像vmLinux前添加了一个特殊的头，这个头在include/image.h中定义，包括目标操作系统的种类(比如Linux，VxWorks等)、目标CPU的体系机构(比如ARM、PowerPC等)、映像文件压缩类型(比如gzip、bzip2等)、加载地址、入口地址、映像名称和映像的生成时间。当系统引导时，U-BOOT会对这个文件头进行CRC校验，如果正确，才会跳到内核执行。如下所示：<br />WT-ARM9# bootm 0xc1000000<br />## Checking Image at 0xc100000 ...<br />Image Name: Linux-2.4.20<br />Created: 2004-07-02 22:10:11 UTC<br />Image Type: ARM Linux Kernel Image (gzip compressed)<br />Data Size: 550196 Bytes = 537 kB = 0 MB<br />Load Address: 0xc0008000<br />Entry Point: 0xc0008000<br />Verifying Checksum ... OK<br />Uncompressing Kernel Image ……… OK<br />◆ 单任务软件运行环境。U-BOOT可以动态加载和运行独立的应用程序，这些独立的应用程序可以利用U-BOOT控制台的I/O函数、内存申请和中断服务等。这些应用程序还可以在没有操作系统的情况下运行，是测试硬件系统很好的工具。<br />◆ 监控(minitor)命令集：读写I/O，内存，寄存器、内存、外设测试功能等 <br />◆ 脚本语言支持(类似BASH脚本)。利用U-BOOT中的autoscr命令，可以在U-BOOT中运行“脚本”。首先在文本文件中输入需要执行的命令，然后用tools/mkimage封装，然后下载到开发板上，用autoscr执行就可以了。<br />① 编辑如下的脚本example.script。<br />echo<br />echo Network Configuration:<br />echo ----------------------<br />echo Target:<br />printenv ipaddr hostname<br />echo<br />echo Server:<br />printenv serverip rootpath<br />echo<br />② 用tools/mkimage对脚本进行封装。<br /># mkimage -A ARM -O linux -T script -C none -a 0 -e 0 -n "autoscr example script" -d example.script /tftpboot/example.img<br />Image Name: autoscr example script<br />Created: Wes Sep 8 01:15:02 2004<br />Image Type: ARM Linux Script (uncompressed)<br />Data Size: 157 Bytes = 0.15 kB = 0.00 MB<br />Load Address: 0x00000000<br />Entry Point: 0x00000000<br />Contents:<br />Image 0: 149 Bytes = 0 kB = 0 MB<br />③ 在U-BOOT中加载并执行这个脚本。<br />WT-ARM9# tftp 100000 /tftpboot/example.img<br />ARP broadcast 1<br />TFTP from server 10.0.0.2; our IP address is 10.0.0.99<br />Filename '/tftpboot/TQM860L/example.img'.<br />Load address: 0x100000<br />Loading: #<br />done<br />Bytes transferred = 221 (dd hex)<br />WT-ARM9# autoscr 100000<br />## Executing script at 00100000<br />Network Configuration:<br />----------------------<br />Target:<br />ipaddr=10.0.0.99<br />hostname=arm<br />Server:<br />serverip=10.0.0.2<br />rootpath=/nfsroot<br />WT-ARM9#<br />◆ 支持WatchDog、LCD logo和状态指示功能等。如果系统支持splash screen，U-BOOT启动时，会把这个图像显示到LCD上，给用户更友好的感觉。<br />◆ 支持MTD和文件系统。U-BOOT作为一种强大的BootLoader，它不仅支持MTD，而且可以在MTD基础上实现多种文件系统，比如cramfs、fat和jffs2等。<br />◆ 支持中断。由于传统的BootLoader都分为stage1和stage2，所以在stage2中添加中断处理服务十分困难，比如BLOB；而U-BOOT是把两个部分放到了一起，所以添加中断服务程序就很方便。<br />◆ 详细的开发文档。由于大多数BootLoader都是开源项目，所以文档都不是很充分。U-BOOT的维护人员意识到了这个问题，充分记录了开发文档，所以它的移植要比BLOB等缺少文档的BootLoader方便。<br /><br /><strong>2 对U-BOOT-1.1.0的修改</strong><br /><br />　　为了使U-BOOT-1.1.0支持新的开发板，一种简便的做法是在U-BOOT已经支持的开发板中选择一种接近的进行修改。由于U-BOOT-1.10不支持ARM-922T内核，所以选择基于ARM-920T内核的smdk2400为模板。相关的源代码在board/smdk2400/下。<br /><br />2.1 支持ARM-922T内核的代码修改<br /><br />修改以下代码，使U-BOOT支持arm-922t内核。<br />① 在include/目录下新建文件arm922t.h，内容如下：<br />#ifndef __ARM922T_H__<br />#define __ARM922T_H__<br />#endif<br />② 在include/目录下新建文件wt-arm9.h，该文件描述了ARM922T中Timer、UART等寄存器的结构及若干宏定义。具体内容要参考相关处理器手册。<br />③ 在cpu/目录下新建目录arm922t，将目录arm920t下的内容复制后，参考手册分别修改cpu.c、interrupts.c和serial.c，其它文件不修改。<br /><br />2.2 开发板的支持<br /><br />　　建立自己开发板的目录和相关文件。<br />① 在include/configs目录中添加头文件lh7a400.h。这个文件是lh7a400开发板的配置文件，它包括开发板的CPU、系统时钟、RAM、Flash系统及其它相关的配置信息。其格式可参考include/configs/smdk2400.h。<br />② 在board/目录下新建wt-arm9目录，创建如下文件：flash.c、lhmemsetup.c、wt- arm9.c、Makefile和u-boot.lds。<br />◆ flash.c。U-BOOT 读、写和删除Flash设备的源代码文件。由于不同开发板中Flash存储器的种类各不相同，所以，修改flash.c时需参考相应的Flash芯片手册。它包括如下几个函数：<br />unsigned long flash_init (void )，Flash初始化；<br />void flash_print_info (flash_info_t *info)，打印Flash信息；<br />int flash_erase (flash_info_t *info， int s_first， int s_last)，Flash擦除；<br />volatile static int write_dword (flash_info_t *info， ulong dest， ulong data)，Flash写入；<br />int write_buff (flash_info_t *info， uchar *src， ulong addr， ulong cnt)，从内存复制数据。<br />◆ lhmemsetup.c。初始化时钟、SMC控制器和SDRAM控制器。<br />◆ wt-arm9.c。设置各种总线时钟，打开数据Cache和指令Cache，并设置相关内存参数。<br />◆ Makefile。直接拷贝board/smdk2400/Makefile，作如下修改：<br />OBJS ：= wt-arm9.o flash.o lhmemsetup.o<br />◆ u-boot.lds。设置U-BOOT中各个目标文件的连接地址，直接拷贝 board/smdk2400/u-boot.lds，作如下修改：<br />.text<br />{<br />cpu/arm922t/start.o (.text)<br />*(.text)<br />}<br /><br />2.3 添加网口设备控制程序<br /><br />　　在drivers/目录中添加网口设备控制程序dm9000.c 和dm9000.h，其中dm9000.c 主要包括以下函数：<br />int eth_init (bd_t *bd)，初始化网络设备；<br />void eth_halt (void)，关闭网络设备；<br />int eth_send (volatile void *packet，int len)，发送数据包；<br />int eth_rx (void) 接收数据包。<br />用中断方式处理数据包的收发，因此还定义了另外两个函数：<br />void InitInterrupt (void) ，中断初始化；<br />void dm9000_irq (void) ，中断处理。<br />以上两个函数在cpu/arm922t/interrupts.c中被调用，最后在drivers/Makefile中加入dm9000.o。<br /><br />2.4 修改Makefile<br /><br />　　在u-boot-1.1.0/Makefile中加入<br />lh7a400_config : unconfig<br />@./mkconfig $(@:_config=) arm arm922t wt-arm9<br />其中“arm”是CPU的种类， arm922t 是ARM CPU对应的代码目录，wt-arm9是自己开发板对应的目录。<br />交叉编译器安装在/opt/arm/3.3/bin/目录下，所以把CROSS_COMPILE设置成相应的路径：<br />export CROSS_COMPILE = /opt/arm/3.3/bin/arm-elf-<br /><br />2.5 生成目标文件<br /><br />　　 先运行make clean，<br />[zeng@localhost u-boot-1.1.0]$make clean<br />然后运行make lh7a400_config，<br />[zeng@localhost u-boot-1.1.0]$ make lh7a400_config<br />Configuring for lh7a400 board...<br />再运行make，<br />[zeng@localhost u-boot-1.1.0]$make<br />之后会生成三个文件：<br />u-boot——ELF格式的文件，可以被大多数Debug程序识别；<br />u-boot.bin——二进制bin文件，纯粹的U-BOOT二进制执行代码，不保存ELF格式和调试信息。这个文件一般用于烧录到用户开发板中；<br />u-boot.srec——Motorola S-Record格式，可以通过串行口下载到开发板中。<br /><br />2.6 测 试<br /><br />　　通过JTAG口将u-boot.bin烧写到Flash的零地址，复位后执行u-boot。若运行正常，会从串口返回如下信息：<br />U-Boot 1.1.0 (Aug 21 2004 ?18:44:37)<br />U-BooT code: C3F80000 -&gt; C3FA51A0 BSS: -&gt; C3FA96EC<br />IRQ Stack: c3f1ff7c<br />FIQ Stack: c3f1ef7c<br />RAM Configuration:<br />Bank #0: c0000000 8 MB<br />Bank #1: c1000000 8 MB<br />……<br />Flash: 32 MB<br />In: serial<br />Out: serial<br />Err: serial<br />WT-ARM9 #<br /><br />　　输入help得到所有命令列表，help command 列出该命令的功能。紧接着测试Flash和网卡，如果都正常工作的话，表明移植U-BOOT的工作基本完成，可以接着调试内核和文件系统。<br /><br /><strong>结 语</strong><br /><br />　　BootLoader是操作系统和硬件的枢纽，它为操作系统内核的启动提供了必要的条件和参数。在移植过程中，开发人员除了要掌握BootLoader的结构和工作流程外，还要对相关硬件有一定的了解。目前，笔者移植的U-BOOT已经能够稳定地运行在开发板上，而且可以通过Flash和网络加载内核和文件系统，为后续开发，特别是驱动程序的开发奠定了良好的基础。</p>
		<p> </p>
		<p>
				<strong>基于Atmel at91rm9200的armlinux的bootloader启动代码分析<br /></strong>
				<font class="tiny">
						<strong>日期：</strong> 5月 05 @ 23:45:26 CST<br /><strong>文章主题：</strong> 编程开发</font>
				<br />
				<br />
				<font class="content">前阶段做了一次基于at91rm9200引导部分的技术分析，主要采用了u-boot，这里只面向使用at91rm9200板子的的朋友做个简单的推敲，希望起到抛砖引玉的作用<br /><br />Author : balancesli<br />mail : balancesli@thizlinux.com.cn<br /><br />前阶段做了一次基于at91rm9200引导部分的技术分析，主要采用了u-boot，这里只面向使用at91rm9200板子的<br />的朋友做个简单的推敲，希望起到抛砖引玉的作用.<br /><br />关键词 : <br />u-boot: 一个开源的面向多个目标平台(ppc, mips, arm, x86)的bootloader.<br />at91rm9200 : Atmel 公司生产的基于arm9核的Soc处理器.<br /><br />以下先给出at91rm9200引导流程图<br /><br />Boot program Flow Diagram <br /><br />Device Setup<br />|<br />|<br />Boot SPI DataFlash Boot --&gt; Download from DataFlash --&gt; run<br />|<br />| <br />TWI EEPROM Boot --&gt; Download from EEPROM --&gt; run<br />|<br />|<br />Parallel Boot --&gt; Download from 8-bit Device --&gt; <br />| <br />| Xmodem protocol <br />| |---DBGU Serial Download ---------------------&gt; run<br />|____|<br />| DFU protocol<br />|-----USB download -----------------------&gt; run <br /><br />在这里我主要介绍通过片内引导和片外引导, 片内引导主要采用串口下载并引导u-boot，并完成程序被烧写到Flash上, <br />然后就可以通过跳线的方式从片外引导执行已经烧写到片外Flash上的引导程序(bootloader).<br /><br />这里要提及的是at91rm9200内部本身有128k的片内rom,其固化了一个bootloader和uploader, 用来支持程序的<br />下载和引导,而且其内部固化的程序提供了很多内部服务接口(Internel Service)供我们来使用，例如Xmodem，Tempo<br />DataFlash, CRC, Sine服务接口，这样我们就可以利用它所提供的Service interface API完成程序的下载。<br />这里主要介绍Xmodem接口服务。<br /><br />at91rm9200内部固化的代码在设计上采用了面向对象的设计方法，如下:<br /><br /><br />typedef struct _AT91S_Service <br />{<br />char data;<br />char (*MainMethod)();<br />char (*ChildMethod)();<br />}AT91S_Service, *AT91PS_Service;<br /><br />char AT91F_MainMethod()<br />{<br /><br />}<br />char AT91F_ChildMethod()<br />{<br /><br />}<br /><br />/*init the Service */<br />AT91PS_Service AT91F_OpenDevice(AT91PS_Service pService)<br />{<br />pService-&gt;data = 0;<br />pService-&gt;MainMethod = AT91F_MainMethod;<br />pService-&gt;ChildMethod = AT91F_ChildMethod;<br />}<br /><br />//使用方法如下<br />AT91S_Service service;<br />AT91PS_Service pService = AT91F_OpenDevice(&amp;service);<br />pService-&gt;AT91F_MainMethmod();<br />.....<br /><br />通过如上代码片断可以看出它采用了类似面向对象的设计方法。<br />其实如果各位朋友接触过的话或者看过这本书的话，应该很容易便接受它。<br />下面以Xmodem服务为例子介绍:<br /><br /><br />at91rm9200内部提供的服务包含了几个服务对象, 这些对象在片内启动xmodem协议Host端和Targe端通讯时会用到.<br /><br /><br />typedef struct _AT91S_RomBoot <br />{<br />const unsigned int version;<br />// Peripheral descriptors<br />const AT91S_MEMCDesc MEMC_DESC;<br />const AT91S_STDesc SYSTIMER_DESC;<br />const AT91S_Pio2Desc PIOA_DESC;<br />const AT91S_Pio2Desc PIOB_DESC;<br />const AT91S_USART2Desc DBGU_DESC;<br />const AT91S_USART2Desc USART0_DESC;<br />const AT91S_USART2Desc USART1_DESC;<br />const AT91S_USART2Desc USART2_DESC;<br />const AT91S_USART2Desc USART3_DESC;<br />const AT91S_TWIDesc TWI_DESC;<br />const AT91S_SPIDesc SPI_DESC;<br /><br />// Objects entry<br />const AT91PF_OpenPipe OpenPipe;<br />const AT91PF_OpenSBuffer OpenSBuffer;<br />const AT91PF_OpenSvcUdp OpenSvcUdp;<br />const AT91PF_OpenSvcXmodem OpenSvcXmodem;<br />const AT91PF_OpenCtlTempo OpenCtlTempo;<br />const AT91PF_OpenDfuDesc OpenDfuDesc;<br />const AT91PF_OpenUsbDesc OpenUsbDesc;<br />const AT91PF_OpenSvcDataFlash OpenSvcDataFlash;<br />const AT91PF_SVC_CRC16 CRC16;<br />const AT91PF_SVC_CRCCCITT CRCCCITT;<br />const AT91PF_SVC_CRCHDLC CRCHDLC;<br />const AT91PF_SVC_CRC32 CRC32;<br />// Array<br />const AT91PS_SVC_CRC_BIT_REV Bit_Reverse_Array;<br />const AT91PS_SINE_TAB SineTab;<br />const AT91PF_Sinus Sine;<br />} AT91S_RomBoot;<br /><br />//AT91S_Pipe<br />typedef struct _AT91S_Pipe<br />{<br />// A pipe is linked with a peripheral and a buffer<br />AT91PS_SvcComm pSvcComm;<br />AT91PS_Buffer pBuffer;<br /><br />// Callback functions with their arguments<br />void (*WriteCallback) (AT91S_PipeStatus, void *);<br />void (*ReadCallback) (AT91S_PipeStatus, void *);<br />void *pPrivateReadData;<br />void *pPrivateWriteData;<br /><br />// Pipe methods<br />AT91S_PipeStatus (*Write) (<br />struct _AT91S_Pipe *pPipe,<br />char const * pData,<br />unsigned int size,<br />void (*callback) (AT91S_PipeStatus, void *),<br />void *privateData<br />);<br /><br />AT91S_PipeStatus (*Read) (<br />struct _AT91S_Pipe *pPipe,<br />char *pData,<br />unsigned int size,<br />void (*callback) (AT91S_PipeStatus, void *),<br />void *privateData<br />);<br /><br />AT91S_PipeStatus (*AbortWrite)(struct _AT91S_Pipe *pPipe);<br />AT91S_PipeStatus (*AbortRead)(struct _AT91S_Pipe *pPipe);<br />AT91S_PipeStatus (*AbortRead)(struct _AT91S_Pipe *pPipe);<br />AT91S_PipeStatus (*Reset)(struct _AT91S_Pipe *pPipe);<br />char (*IsWritten)(struct _AT91S_Pipe *pPipe, char const *pVoid);<br />char (*IsReceived) (struct _AT91S_Pipe *pPipe, char const *pVoid);<br />} AT91S_Pipe;<br /><br />//AT91S_Buff<br />typedef struct _AT91S_SBuffer<br />{<br />AT91S_Buffer parent;<br />char *pRdBuffer;<br />char const *pWrBuffer;<br />unsigned int szRdBuffer;<br />unsigned int szWrBuffer;<br />unsigned int stRdBuffer;<br />unsigned int stWrBuffer;<br />} AT91S_SBuffer;<br /><br />// AT91S_SvcTempo<br />typedef struct _AT91S_SvcTempo<br />{<br /><br />// Methods:<br />AT91S_TempoStatus (*Start) (<br />struct _AT91S_SvcTempo *pSvc,<br />unsigned int timeout,<br />unsigned int reload,<br />void (*callback) (AT91S_TempoStatus, void *),<br />void *pData);<br />AT91S_TempoStatus (*Stop) (struct _AT91S_SvcTempo *pSvc);<br /><br />struct _AT91S_SvcTempo *pPreviousTempo;<br />struct _AT91S_SvcTempo *pNextTempo;<br /><br />// Data<br />unsigned int TickTempo; //* timeout value<br />unsigned int ReloadTempo;//* Reload value for periodic execution<br />void (*TempoCallback)(AT91S_TempoStatus, void *);<br />void *pPrivateData;<br />AT91E_SvcTempo flag;<br />} AT91S_SvcTempo;<br /><br />// AT91S_CtrlTempo<br />typedef struct _AT91S_CtlTempo<br />{<br />// Members:<br /><br />// Start and stop for Timer hardware<br />AT91S_TempoStatus (*CtlTempoStart) (void *pTimer);<br />AT91S_TempoStatus (*CtlTempoStop) (void *pTimer);<br /><br />// Start and stop for Tempo service<br />AT91S_TempoStatus (*SvcTempoStart) (<br />struct _AT91S_SvcTempo *pSvc,<br />unsigned int timeout,<br />unsigned int reload,<br />void (*callback) (AT91S_TempoStatus, void *),<br />void *pData);<br />AT91S_TempoStatus (*SvcTempoStop) (struct _AT91S_SvcTempo *pSvc);<br />AT91S_TempoStatus (*CtlTempoSetTime)(struct _AT91S_CtlTempo *pCtrl, unsigned int NewTime);<br />AT91S_TempoStatus (*CtlTempoGetTime)(struct _AT91S_CtlTempo *pCtrl);<br />AT91S_TempoStatus (*CtlTempoIsStart)(struct _AT91S_CtlTempo *pCtrl);<br />AT91S_TempoStatus (*CtlTempoCreate) (struct _AT91S_CtlTempo *pCtrl,struct _AT91S_SvcTempo *pTempo);<br />AT91S_TempoStatus (*CtlTempoRemove) (struct _AT91S_CtlTempo *pCtrl,struct _AT91S_SvcTempo *pTempo);<br />AT91S_TempoStatus (*CtlTempoTick) (struct _AT91S_CtlTempo *pCtrl);<br /><br />// Data:<br /><br />void *pPrivateData; // Pointer to devived class<br />void const *pTimer; // hardware<br />AT91PS_SvcTempo pFirstTempo;<br />AT91PS_SvcTempo pNewTempo;<br />} AT91S_CtlTempo;<br /><br /><br /><br />//以下代码是上面几个对象的使用范例，通过这样就可以完成Host端和Targe端之间的xmodem通讯,并可以下载代码了。<br /><br />AT91S_RomBoot const *pAT91;<br />AT91S_SBuffer sXmBuffer;<br />AT91S_SvcXmodem svcXmodem;<br />AT91S_Pipe xmodemPipe;<br />AT91S_CtlTempo ctlTempo;<br /><br />AT91PS_Buffer pXmBuffer;<br />AT91PS_SvcComm pSvcXmodem;<br />unsigned int SizeDownloaded; <br /><br />/* Init of ROM services structure */<br />pAT91 = AT91C_ROM_BOOT_ADDRESS;//这里取得内部ROM服务的入口地址<br /><br />/* Tempo Initialization */<br />pAT91-&gt;OpenCtlTempo(&amp;ctlTempo, (void *) &amp;(pAT91-&gt;SYSTIMER_DESC));<br />ctlTempo.CtlTempoStart((void *) &amp;(pAT91-&gt;SYSTIMER_DESC));<br /><br />/* Xmodem Initialization */<br />pXmBuffer = pAT91-&gt;OpenSBuffer(&amp;sXmBuffer);<br />pSvcXmodem = pAT91-&gt;OpenSvcXmodem(&amp;svcXmodem, (AT91PS_USART)AT91C_BASE_DBGU, &amp;ctlTempo);<br />pAT91-&gt;OpenPipe(&amp;xmodemPipe, pSvcXmodem, pXmBuffer);<br />xmodemPipe.Read(&amp;xmodemPipe, (char *)AT91C_UBOOT_BASE_ADDRESS, AT91C_UBOOT_MAXSIZE, <br />AT91F_XmodemProtocol, 0); <br />while(XmodemComplete !=1);<br /><br /><br /><br />//上面部分主要针对at91rm9200片内启动时我们可以使用的片内接口服务介绍，玩H9200的朋友可以参考一下便知道缘由。<br /><br />下面主要介绍at91rm9200片外启动时所使用的bootloader--&gt;u-boot.<br /><br />一. bootloader <br />BootLoader(引导装载程序)是嵌入式系统软件开发的非常重要的环节，它把操作系统和硬件平台衔接在一起，<br />是跟硬件体系密切相关的。<br /><br /><br /><br />1.1 典型的嵌入式系统软件部分Image memory layout : bootloader , bootloader param, kernel, rootfs.<br /><br />1.2 引导模式 : 1. bootstrap或download<br />2. autoboot<br />1.3 u-boot简介 :<br />u-boot是由Wolfgang Denk开发，它支持(mips, ppc, arm, x86)等目标体系，<br />可以在http://sourceforge.net 上下载获得源码，<br /><br />1.4 u-boot源代码目录结构<br /><br />board：开发板相关的源码，不同的板子对应一个子目录，内部放着主板相关代码。<br /><br />at91rm9200dk/at91rm9200.c, config.mk, Makefile, flash.c ,u-boot.lds等都和具体开发板的硬件和地址分配有关。<br /><br />common：与体系结构无关的代码文件，实现了u-boot所有命令，<br />其中内置了一个shell脚本解释器(hush.c, a prototype Bourne shell grammar parser), busybox中也使用了它.<br /><br /><br />cpu：与cpu相关代码文件，其中的所有子目录都是以u-boot所支持的cpu命名.<br /><br />at91rm9200/at45.c, at91rm9200_ether.c, cpu.c, interrupts.c serial.c, start.S, config.mk, Makefile等.<br />其中cpu.c负责初始化CPU、设置指令Cache和数据Cache等；<br /><br />interrupt.c负责设置系统的各种中断和异常，比如快速中断、开关中断、时钟中断、软件中断、<br />预取中止和未定义指令等；<br /><br />start.S负责u-boot启动时执行的第一个文件，它主要是设置系统堆栈和工作方式，为跳转到C程序入口点.<br /><br />disk：设备分区处理代码。<br /><br />doc：u-boot相关文档。<br /><br /><br />drivers：u-boot所支持的设备驱动代码, 网卡、支持CFI的Flash、串口和USB总线等。<br /><br />fs: u-boot所支持支持文件系统访问存取代码， 如jffs2.<br /><br />include：u-boot head文件，主要是与各种硬件平台相关的头文件，<br />如include/asm-arm/arch-at91rm9200/, include/asm-arm/proc-armv<br /><br />net：与网络有关的代码，BOOTP协议、TFTP协议、RARP协议代码实现.<br /><br />lib_arm：与arm体系相关的代码。(这里我们主要面向的是ARM体系，所以该目录是我们主要研究对象)<br /><br />tools：编译后会生成mkimage工具，用来对生成的raw bin文件加入u-boot特定的image_header.<br /><br /><br />1.5 u-boot的功能介绍<br /><br /><br />　u-boot支持SCC/FEC以太网、OOTP/TFTP引导、IP和MAC的功能.<br /><br />读写Flash、DOC、IDE、IIC、EEROM、RTC<br /><br />支持串行口kermit和S-record下载代码, 并直接从串口下载并执行。<br /><br />在我们生成的内核镜像时，要做如下处理.<br />1. arm-linux-objcopy -O binary -R.note -R.comment -S vmlinux linux.bin <br />2. gzip -9 linux.bin<br />3. mkimage -A arm -O linux -T kernel -C gzip -a 0xc0008000 -e 0xc0008000 -n <br />"Linux-2.4.19-rmk7” -d linux.bin.gz uImage<br /><br />即在Linux内核镜像vmLinux前添加了一个特殊的头，这个头在include/image.h中定义，<br />typedef struct image_header <br />{<br />uint32_t ih_magic; /* Image Header Magic Number */<br />uint32_t ih_hcrc; /* Image Header CRC Checksum */<br />uint32_t ih_time; /* Image Creation Timestamp */<br />uint32_t ih_size; /* Image Data Size */<br />uint32_t ih_load; /* Data Load Address */<br />uint32_t ih_ep; /* Entry Point Address */<br />uint32_t ih_dcrc; /* Image Data CRC Checksum */<br />uint8_t ih_os; /* Operating System */<br />uint8_t ih_arch; /* CPU architecture */<br />uint8_t ih_type; /* Image Type */<br />uint8_t ih_comp; /* Compression Type */<br />uint8_t ih_name[IH_NMLEN]; /* Image Name */<br />} image_header_t;<br /><br />当u-boot引导时会对这个文件头进行CRC校验，如果正确，才会跳到内核执行.<br /><br />如果u-boot启动以后会出现<br />u-boot&gt;<br />敲入help, 会出现大量的命令提示，Monitor command<br />go - start application at address 'addr'<br />run - run commands in an environment variable<br />bootm - boot application image from memory<br />bootp - boot image via network using BootP/TFTP protocol<br />tftpboot- boot image via network using TFTP protocol<br />and env variables "ipaddr" and "serverip"<br />(and eventually "gatewayip")<br />rarpboot- boot image via network using RARP/TFTP protocol<br />diskboot- boot from IDE devicebootd - boot default, i.e., run 'bootcmd'<br />loads - load S-Record file over serial line<br />loadb - load binary file over serial line (kermit mode)<br />md - memory display<br />mm - memory modify (auto-incrementing)<br />nm - memory modify (constant address)<br />mw - memory write (fill) <br />cp - memory copy<br />cmp - memory compare<br />crc32 - checksum calculation<br />imd - i2c memory display<br />imm - i2c memory modify (auto-incrementing)<br />inm - i2c memory modify (constant address)<br />imw - i2c memory write (fill)<br />icrc32 - i2c checksum calculation<br />iprobe - probe to discover valid I2C chip addresses<br />iloop - infinite loop on address range<br />isdram - print SDRAM configuration information<br />sspi - SPI utility commands<br />base - print or set address offset<br />printenv- print environment variables<br />setenv - set environment variables<br />saveenv - save environment variables to persistent storage<br />protect - enable or disable FLASH write protection<br />erase - erase FLASH memory<br />flinfo - print FLASH memory information<br />bdinfo - print Board Info structure<br />iminfo - print header information for application image<br />coninfo - print console devices and informations<br />ide - IDE sub-system<br />loop - infinite loop on address range<br />mtest - simple RAM test<br />icache - enable or disable instruction cache<br />dcache - enable or disable data cache<br />reset - Perform RESET of the CPU<br />echo - echo args to console<br />version - print monitor version<br />help - print online help<br />? - alias for 'help'<br /><br />u-boot支持大量的命令可用, 这里就不作介绍，大家有兴趣可以看看u-boot 的README文档<br />3.3 对u-boot-1.0.0的修改和移植<br /><br />1.6 关于u-boot的移植如下，由于u-boot的软件设计体系非常清晰，它的移植工作并不复杂，<br />相信各位的代码阅读功力不错的话，参照如下就可以完成。<br /><br />If the system board that you have is not listed, then you will need<br />to port U-Boot to your hardware platform. To do this, follow these<br />steps:<br /><br />1. Add a new configuration option for your board to the toplevel<br />"Makefile" and to the "MAKEALL" script, using the existing<br />entries as examples. Note that here and at many other places<br />boards and other names are listed in alphabetical sort order. Please<br />keep this order.<br /><br />2. Create a new directory to hold your board specific code. Add any<br />files you need. In your board directory, you will need at least<br />the "Makefile", a ".c", "flash.c" and "u-boot.lds".<br /><br />3. Create a new configuration file "include/configs/.h" for<br />your board<br /><br />4. If you're porting U-Boot to a new CPU, then also create a new<br />directory to hold your CPU specific code. Add any files you need.<br /><br />5. Run "make _config" with your new name.<br /><br />6. Type "make", and you should get a working "u-boot.srec" file<br /><br />7. Debug and solve any problems that might arise.<br />[Of course, this last step is much harder than it sounds.]<br /><br />为了使u-boot-1.0.0支持新的开发板，一种简便的做法是在u-boot已经支持的开发板中参考选择一种较接近板的进行修改,<br />幸运的是在u-boot-1.0.0中已经有了at91rm9200的支持。<br /><br />1.7 与at91rm9200相关的u-boot代码<br /><br />在include/configs/at91rm9200dk.h 它包括开发板的CPU、系统时钟、RAM、Flash系统及其它相关的配置信息。<br />在include/asm-arm/AT91RM9200.h, 该文件描述了H9200寄存器的结构及若干宏定义。<br />具体内容要参考相关处理器手册。<br />在cpu/at91rm9200/目录下别为cpu.c、interrupts.c和serial.c等文件.<br />在board/at91rm9200dk/目录下分别为flash.c、at91rm9200dk.c, config.mk, Makefile,u-boot.lds<br /><br />flash.c : u-boot读、写和删除Flash设备的源代码文件。由于不同开发板中Flash存储器的种类各不相同，<br />所以，修改flash.c时需参考相应的Flash芯片手册。它包括如下几个函数：<br />unsigned long flash_init (void )，Flash初始化；<br />void flash_print_info (flash_info_t *info)，打印Flash信息；<br />int flash_erase (flash_info_t *info， int s_first， int s_last)，Flash擦除；<br />volatile static int write_dword (flash_info_t *info， ulong dest， ulong data)，Flash写入；<br />int write_buff (flash_info_t *info， uchar *src， ulong addr， ulong cnt)，从内存复制数据。<br /><br />u-boot.lds :linker scripte, 设置u-boot中各个目标文件的连接地址.<br /><br />网口设备控制程序<br /><br />　　在drivers/目录中网口设备控制程序cs8900, bcm570x等, 还可以添加其他网卡驱动<br />int eth_init (bd_t *bd) : 初始化网络设备；<br />void eth_halt (void) : 关闭网络设备；<br />int eth_send (volatile void *packet，int len) : 发送数据包；<br />int eth_rx (void) : 接收数据包。<br /><br /><br />Makefile<br /><br />　　在u-boot-1.0.0/Makefile中 <br />at91rm9200dk_config : unconfig<br />./mkconfig $(@:_config=) arm at91rm9200 at91rm9200dk<br /><br />1.8 编译u-boot<br /><br />　 make at91rm9200_config<br />Configuring for at91rm9200 board...<br />make all<br />生成三个文件：u-boot.bin, u-boot, u-boot.srec<br /><br />u-boot.bin is a raw binary image<br />u-boot is an image in ELF binary format<br />u-boot.srec is in Motorola S-Record format (objcopy -O srec -R.note -R.comment -S [inputfile] [outfile]<br /><br /><br />以上工作完成我们可以通过串口将u-boot.bin下载到主板的SDRAM中，它会自动执行， 并出现uboot&gt;<br />这里我们可以通过串口把boot.bin, u-boot.bin.gz下载到主板，再用u-boot的提供的写flash功能分别<br />把boot.bin, u-boot.bin.gz写入到flash中，完成以上工作后，对主板跳线选择片外启动，<br />板子复位后会自动启动u-boot.<br /><br /><br /><br /><br />二.loader.bin, boot.bin, u-boot.bin代码执行流分析.<br /><br />以上三个文件时at91rm9200启动所需要的三个bin,他们的实现代码并不难。<br />如果是你是采用at91rm9200的评估版，应该能得到其源码。<br /><br />2.1 loader.bin 执行流程,这个文件主要在片内启动从串口下载代码时会用到<br />loader/entry.S init cpu<br />b main ---&gt; crt0.S<br />--&gt; copydata --&gt; clearbss --&gt; b boot<br />main.c --&gt; boot --&gt;<br />/*Get internel rom service address*/<br />/* Init of ROM services structure */ <br />pAT91 = AT91C_ROM_BOOT_ADDRESS;<br /><br />/* Xmodem Initialization */<br />--&gt; pAT91-&gt;OpenSBuffer<br />--&gt; pAT91-&gt;OpenSvcXmodem<br />/* System Timer initialization */<br />---&gt; AT91F_AIC_ConfigureIt<br />/* Enable ST interrupt */<br />AT91F_AIC_EnableIt<br />AT91F_DBGU_Printk("XMODEM: Download U-BOOT ");<br /><br />Jump.S<br />// Jump to Uboot BaseAddr exec<br />Jump((unsigned int)AT91C_UBOOT_BASE_ADDRESS) <br /><br /><br /><br />2.2 boot.bin执行流程 该文件会在从片内启动时被下载到板子上，以后还会被烧写到片外Flash中，以便在片外启动时<br />用它来引导并解压u-boot.gz，并跳转到u-boot来执行。<br />boot/entry.S<br />b main --&gt; crt0.S --&gt; copydata --&gt; clearbss --&gt; b boot<br /><br />T91F_DBGU_Printk(" ");<br />AT91F_DBGU_Printk("************************************** ");<br />AT91F_DBGU_Printk("** Welcome to at91rm9200 ** ");<br />AT91F_DBGU_Printk("************************************** ");<br /><br />boot/misc.s /* unzip uboot.bin.gz */<br />----&gt; decompress_image(SRC,DST,LEN) --&gt; gunzip <br /><br />//jump to ubootBaseAddr exec 这里跳转到解压u-boot.gz的地址处直接开始执行u-boot<br />asm("mov pc,%0" : : "r" (DST));<br /><br />2.3 uboot.bin执行流程<br />u-boot/cpu/at91rm9200/start.S <br />start ---&gt;reset <br />---&gt; copyex ---&gt; cpu_init_crit <br />---&gt; /* set up the stack */ --&gt; start_armboot<br />u-boot/lib_arm/board.c<br /><br />init_fnc_t *init_sequence[] = {<br />cpu_init, /* basic cpu dependent setup */<br />board_init, /* basic board dependent setup */<br />interrupt_init, /* set up exceptions */<br />env_init, /* initialize environment */<br />init_baudrate, /* initialze baudrate settings */<br />serial_init, /* serial communications setup */<br />console_init_f, /* stage 1 init of console */<br />display_banner, /* say that we are here */<br />dram_init, /* configure available RAM banks */<br />display_dram_config,<br />checkboard,<br />NULL,<br />};<br /><br />---&gt; start_armboot ---&gt; call init_sequence<br />---&gt; flash_init --&gt; display_flash_config <br />---&gt; nand_init ---&gt; AT91F_DataflashInit <br />---&gt; dataflash_print_info --&gt; env_relocate<br />---&gt; drv_vfd_init --&gt; devices_init --&gt; jumptable_init<br />---&gt; console_init_r --&gt; misc_init_r --&gt; enable_interrupts<br />---&gt; cs8900_get_enetaddr --&gt; board_post_init --&gt; <br /><br />u-boot/common/main.c<br />for (;;) <br />{ /* shell parser */<br />main_loop () --&gt; u_boot_hush_start --&gt; readline<br />--&gt; abortboot <br />--&gt;printf("Hit any key to stop autoboot: %2d ", bootdelay);<br />}<br /><br /><br />以上是at91rm9200启动并进入u-boot的执行流分析。后面u-boot还会将uImage解压到特定的位置并开始执行内核代码。<br /><br /><br />三. 综述<br />总之, 不同厂商的出的Soc片子在启动方式大都提供片内和片外启动两种方式，一般都是在片内固化一段小程序<br />方便于程序开发而已，在其DataSheet文档中有详尽的描述。若是对at92rm9200有兴趣或玩过的朋友，可以与我共同探讨相互学习。<br /></font>
		</p>
		<br />
<img src ="http://www.cnitblog.com/flutist1225/aggbug/19981.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:52 <a href="http://www.cnitblog.com/flutist1225/articles/19981.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>跟我一起写 Makefile（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19979.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:49:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19979.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19979.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19979.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19979.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19979.html</trackback:ping><description><![CDATA[
		<div class="twikiToc">
				<ul>
						<li>
								<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#Makefile_Makefile">Makefile学习教程: 跟我一起写 Makefile</a>
								<ul>
										<li>
												<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#0_Makefile">0 Makefile概述</a>
												<ul>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#0_1">0.1 关于程序的编译和链接</a>
														</li>
												</ul>
										</li>
										<li>
												<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#1_Makefile">1 Makefile 介绍</a>
												<ul>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#1_1_Makefile">1.1 Makefile的规则</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#1_2">1.2 一个示例</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#1_3_make">1.3 make是如何工作的</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#1_4_makefile">1.4 makefile中使用变量</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#1_5_make">1.5 让make自动推导</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#1_6_makefile">1.6 另类风格的makefile</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#1_7">1.7 清空目标文件的规则</a>
														</li>
												</ul>
										</li>
										<li>
												<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#2_Makefile">2 Makefile 总述</a>
												<ul>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#2_1_Makefile">2.1 Makefile里有什么？</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#2_2Makefile">2.2Makefile的文件名</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#2_3_Makefile">2.3 引用其它的Makefile</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#2_4_MAKEFILES">2.4 环境变量 MAKEFILES </a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#2_5_make">2.5 make的工作方式</a>
														</li>
												</ul>
										</li>
										<li>
												<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#3_Makefile">3 Makefile书写规则</a>
												<ul>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#3_1">3.1 规则举例</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#3_2">3.2 规则的语法</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#3_3">3.3 在规则中使用通配符</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#3_4">3.4 文件搜寻</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#3_5">3.5 伪目标</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#3_6">3.6 多目标</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#3_7">3.7 静态模式</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#3_8">3.8 自动生成依赖性</a>
														</li>
												</ul>
										</li>
										<li>
												<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#4_Makefile">4 Makefile 书写命令</a>
												<ul>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#4_1">4.1 显示命令</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#4_2">4.2 命令执行</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#4_3">4.3 命令出错</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#4_4_make">4.4 嵌套执行make</a>
														</li>
														<li>
																<a href="http://www.stlchina.org/twiki/bin/view.pl/ScriptProgram/LearnMakefile#4_5">4.5 定义命令包</a>
														</li>
												</ul>
										</li>
								</ul>
						</li>
				</ul>
		</div>
		<h3>
				<a name="0_Makefile">
				</a>
				<a name="0_Makefile_">
				</a>0 Makefile概述 </h3>
		<hr />
什么是makefile？或许很多Winodws的程序员都不知道这个东西，因为那些Windows的IDE都为你做了这个工作，但我觉得要作一个好的和professional的程序员，makefile还是要懂。这就好像现在有这么多的HTML的编辑器，但如果你想成为一个专业人士，你还是要了解HTML的标识的含义。特别在Unix下的软件编译，你就不能不自己写makefile了，会不会写makefile，从一个侧面说明了一个人是否具备完成大型工程的能力。 
<p>因为，makefile关系到了整个工程的编译规则。一个工程中的源文件不计数，其按类型、功能、模块分别放在若干个目录中，makefile定义了一系列的规则来指定，哪些文件需要先编译，哪些文件需要后编译，哪些文件需要重新编译，甚至于进行更复杂的功能操作，因为makefile就像一个Shell脚本一样，其中也可以执行操作系统的命令。 </p><p>makefile带来的好处就是——“自动化编译”，一旦写好，只需要一个make命令，整个工程完全自动编译，极大的提高了软件开发的效率。make是一个命令工具，是一个解释makefile中指令的命令工具，一般来说，大多数的IDE都有这个命令，比如：Delphi的make，Visual C++的nmake，Linux下GNU的make。可见，makefile都成为了一种在工程方面的编译方法。 </p><p>现在讲述如何写makefile的文章比较少，这是我想写这篇文章的原因。当然，不同产商的make各不相同，也有不同的语法，但其本质都是在“文件依赖性”上做文章，这里，我仅对GNU的make进行讲述，我的环境是RedHat Linux 8.0，make的版本是3.80。必竟，这个make是应用最为广泛的，也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的（POSIX.2）。 </p><p>在这篇文档中，将以C/C++的源码作为我们基础，所以必然涉及一些关于C/C++的编译的知识，相关于这方面的内容，还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。 </p><p> </p><h4><a name="0_1"></a><a name="0_1_"></a>0.1 关于程序的编译和链接 </h4>在此，我想多说关于程序编译的一些规范和方法，一般来说，无论是C、C++、还是pas，首先要把源文件编译成中间代码文件，在Windows下也就是 .obj 文件，UNIX下是 .o 文件，即 Object File，这个动作叫做编译（compile）。然后再把大量的Object File合成执行文件，这个动作叫作链接（link）。 
<p>编译时，编译器需要的是语法的正确，函数与变量的声明的正确。对于后者，通常是你需要告诉编译器头文件的所在位置（头文件中应该只是声明，而定义应该放在C/C++文件中），只要所有的语法正确，编译器就可以编译出中间目标文件。一般来说，每个源文件都应该对应于一个中间目标文件（O文件或是OBJ文件）。 </p><p>链接时，主要是链接函数和全局变量，所以，我们可以使用这些中间目标文件（O文件或是OBJ文件）来链接我们的应用程序。链接器并不管函数所在的源文件，只管函数的中间目标文件（Object File），在大多数时候，由于源文件太多，编译生成的中间目标文件太多，而在链接时需要明显地指出中间目标文件名，这对于编译很不方便，所以，我们要给中间目标文件打个包，在Windows下这种包叫“库文件”（Library File)，也就是 .lib 文件，在UNIX下，是Archive File，也就是 .a 文件。 </p><p>总结一下，源文件首先会生成中间目标文件，再由中间目标文件生成执行文件。在编译时，编译器只检测程序语法，和函数、变量是否被声明。如果函数未被声明，编译器会给出一个警告，但可以生成Object File。而在链接程序时，链接器会在所有的Object File中找寻函数的实现，如果找不到，那到就会报链接错误码（Linker Error），在VC下，这种错误一般是：Link 2001错误，意思说是说，链接器未能找到函数的实现。你需要指定函数的Object File. </p><p>好，言归正传，GNU的make有许多的内容，闲言少叙，还是让我们开始吧。 </p><p> </p><h3><a name="1_Makefile"></a><a name="1_Makefile_"></a>1 Makefile 介绍 </h3><hr />
make命令执行时，需要一个 Makefile 文件，以告诉make命令需要怎么样的去编译和链接程序。 
<p>首先，我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册，在这个示例中，我们的工程有8个C文件，和3个头文件，我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是： </p><ol><li>如果这个工程没有编译过，那么我们的所有C文件都要编译并被链接。 
</li><li>如果这个工程的某几个C文件被修改，那么我们只编译被修改的C文件，并链接目标程序。 
</li><li>如果这个工程的头文件被改变了，那么我们需要编译引用了这几个头文件的C文件，并链接目标程序。 </li></ol><p>只要我们的Makefile写得够好，所有的这一切，我们只用一个make命令就可以完成，make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译，从而自己编译所需要的文件和链接目标程序。 </p><h4><a name="1_1_Makefile"></a><a name="1_1_Makefile_"></a>1.1 Makefile的规则 </h4>在讲述这个Makefile之前，还是让我们先来粗略地看一看Makefile的规则。<pre>target ... : prerequisites ...   command   ...   ...</pre>target也就是一个目标文件，可以是Object File，也可以是执行文件。还可以是一个标签（Label），对于标签这种特性，在后续的“伪目标”章节中会有叙述。 
<p>prerequisites就是，要生成那个target所需要的文件或是目标。 </p><p>command也就是make需要执行的命令。（任意的Shell命令） </p><p>这是一个文件的依赖关系，也就是说，target这一个或多个的目标文件依赖于prerequisites中的文件，其生成规则定义在command中。说白一点就是说，prerequisites中如果有一个以上的文件比target文件要新的话，command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。 </p><p>说到底，Makefile的东西就是这样一点，好像我的这篇文档也该结束了。呵呵。还不尽然，这是Makefile的主线和核心，但要写好一个Makefile还不够，我会以后面一点一点地结合我的工作经验给你慢慢到来。内容还多着呢。：） </p><h4><a name="1_2"></a><a name="1_2_"></a>1.2 一个示例 </h4>正如前面所说的，如果一个工程有3个头文件，和8个C文件，我们为了完成前面所述的那三个规则，我们的Makefile应该是下面的这个样子的。<pre>    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的易读。我们可以把这个内容保存在文件为“Makefile”或“makefile”的文件中，然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件，那么，只要简单地执行一下“make clean”就可以了。 
<p>在这个makefile中，目标文件（target）包含：执行文件edit和中间目标文件（*.o），依赖文件（prerequisites）就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件，而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的，换言之，目标文件是哪些文件更新的。 </p><p>在定义好依赖关系后，后续的那一行定义了如何生成目标文件的操作系统命令，一定要以一个Tab键作为开头。记住，make并不管命令是怎么工作的，他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期，如果prerequisites文件的日期要比targets文件的日期要新，或者target不存在的话，那么，make就会执行后续定义的命令。 </p><p>这里要说明一点的是，clean不是一个文件，它只不过是一个动作名字，有点像C语言中的lable一样，其冒号后什么也没有，那么，make就不会自动去找文件的依赖性，也就不会自动执行其后所定义的命令。要执行其后的命令，就要在make命令后明显得指出这个lable的名字。这样的方法非常有用，我们可以在一个makefile中定义不用的编译或是和编译无关的命令，比如程序的打包，程序的备份，等等。 </p><p> </p><h4><a name="1_3_make"></a><a name="1_3_make_"></a>1.3 make是如何工作的 </h4>在默认的方式下，也就是我们只输入make命令。那么， 
<p> </p><ol><li>make会在当前目录下找名字叫“Makefile”或“makefile”的文件。 
</li><li>如果找到，它会找文件中的第一个目标文件（target），在上面的例子中，他会找到“edit”这个文件，并把这个文件作为最终的目标文件。 
</li><li>如果edit文件不存在，或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新，那么，他就会执行后面所定义的命令来生成edit这个文件。 
</li><li>如果edit所依赖的.o文件也存在，那么make会在当前文件中找目标为.o文件的依赖性，如果找到则再根据那一个规则生成.o文件。（这有点像一个堆栈的过程） 
</li><li>当然，你的C文件和H文件是存在的啦，于是make会生成 .o 文件，然后再用 .o 文件生命make的终极任务，也就是执行文件edit了。 </li></ol><p>这就是整个make的依赖性，make会一层又一层地去找文件的依赖关系，直到最终编译出第一个目标文件。在找寻的过程中，如果出现错误，比如最后被依赖的文件找不到，那么make就会直接退出，并报错，而对于所定义的命令的错误，或是编译不成功，make根本不理。make只管文件的依赖性，即，如果在我找了依赖关系之后，冒号后面的文件还是不在，那么对不起，我就不工作啦。 </p><p>通过上述分析，我们知道，像clean这种，没有被第一个目标文件直接或间接关联，那么它后面所定义的命令将不会被自动执行，不过，我们可以显示要make执行。即命令——“make clean”，以此来清除所有的目标文件，以便重编译。 </p><p>于是在我们编程中，如果这个工程已被编译过了，当我们修改了其中一个源文件，比如file.c，那么根据我们的依赖性，我们的目标file.o会被重编译（也就是在这个依性关系后面所定义的命令），于是file.o的文件也是最新的啦，于是file.o的文件修改时间要比edit要新，所以edit也会被重新链接了（详见edit目标文件后定义的命令）。 </p><p>而如果我们改变了“command.h”，那么，kdb.o、command.o和files.o都会被重编译，并且，edit会被重链接。 </p><h4><a name="1_4_makefile"></a><a name="1_4_makefile_"></a>1.4 makefile中使用变量 </h4>在上面的例子中，先让我们看看edit的规则：<pre>      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>     objects = main.o kbd.o command.o display.o               insert.o search.o files.o utils.o</pre>于是，我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了，于是我们的改良版makefile就变成下面这个样子：<pre>    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><p>关于变量更多的话题，我会在后续给你一一道来。 </p><h4><a name="1_5_make"></a><a name="1_5_make_"></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>    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的“隐晦规则”。上面文件内容中，“.PHONY”表示，clean是个伪目标文件。 
<p>关于更为详细的“隐晦规则”和“伪目标文件”，我会在后续给你一一道来。 </p><h4><a name="1_6_makefile"></a>1.6 另类风格的makefile </h4>即然我们的make可以自动推导命令，那么我看到那堆[.o]和[.h]的依赖就有点不爽，那么多的重复的[.h]，能不能把其收拢起来，好吧，没有问题，这个对于make来说很容易，谁叫它提供了自动推导命令和文件的功能呢？来看看最新风格的makefile吧。<pre>    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="1_7"></a><a name="1_7_"></a>1.7 清空目标文件的规则 </h4>每个Makefile中都应该写一个清空目标文件（.o和执行文件）的规则，这不仅便于重编译，也很利于保持文件的清洁。这是一个“修养”（呵呵，还记得我的《编程修养》吗）。一般的风格都是：<pre>        clean:            rm edit $(objects)</pre>更为稳健的做法是：<pre>        .PHONY : clean        clean :                -rm edit $(objects)</pre>前面说过，.PHONY意思表示clean是一个“伪目标”，。而在rm命令前面加了一个小减号的意思就是，也许某些文件出现问题，但不要管，继续做后面的事。当然，clean的规则不要放在文件的开头，不然，这就会变成make的默认目标，相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。 
<p> </p><p>上面就是一个makefile的概貌，也是makefile的基础，下面还有很多makefile的相关细节，准备好了吗？准备好了就来。 </p><hr /><h3><a name="2_Makefile"></a><a name="2_Makefile_"></a>2 Makefile 总述 </h3><h4><a name="2_1_Makefile"></a><a name="2_1_Makefile_"></a>2.1 Makefile里有什么？ </h4>Makefile里主要包含了五个东西：显式规则、隐晦规则、变量定义、文件指示和注释。 
<p> </p><ol><li>显式规则。显式规则说明了，如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出，要生成的文件，文件的依赖文件，生成的命令。 
</li><li>隐晦规则。由于我们的make有自动推导的功能，所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile，这是由make所支持的。 
</li><li>变量的定义。在Makefile中我们要定义一系列的变量，变量一般都是字符串，这个有点你C语言中的宏，当Makefile被执行时，其中的变量都会被扩展到相应的引用位置上。 
</li><li>文件指示。其包括了三个部分，一个是在一个Makefile中引用另一个Makefile，就像C语言中的include一样；另一个是指根据某些情况指定Makefile中的有效部分，就像C语言中的预编译#if一样；还有就是定义一个多行的命令。有关这一部分的内容，我会在后续的部分中讲述。 
</li><li>注释。Makefile中只有行注释，和UNIX的Shell脚本一样，其注释是用“#”字符，这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符，可以用反斜框进行转义，如：“\#”。 </li></ol><p>最后，还值得一提的是，在Makefile中的命令，必须要以[Tab]键开始。 </p><h4><a name="2_2Makefile"></a><a name="2_2Makefile_"></a>2.2Makefile的文件名 </h4>默认的情况下，make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件，找到了解释这个文件。在这三个文件名中，最好使用“Makefile”这个文件名，因为，这个文件名第一个字符为大写，这样有一种显目的感觉。最好不要用“GNUmakefile”，这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感，但是基本上来说，大多数的make都支持“makefile”和“Makefile”这两种默认文件名。 
<p>当然，你可以使用别的文件名来书写Makefile，比如：“Make.Linux”，“Make.Solaris”，“Make.AIX”等，如果要指定特定的Makefile，你可以使用make的“-f”和“--file”参数，如：make -f Make.Linux或make --file Make.AIX。 </p><h4><a name="2_3_Makefile"></a>2.3 引用其它的Makefile </h4>在Makefile使用include关键字可以把别的Makefile包含进来，这很像C语言的#include，被包含的文件会原模原样的放在当前文件的包含位置。include的语法是： 
<div class="fragment"><pre>include &lt;filename&gt;</pre><pre> </pre></div>filename可以是当前操作系统Shell的文件模式（可以保含路径和通配符） 
<p>在include前面可以有一些空字符，但是绝不能是[Tab]键开始。include和<filename></filename>可以用一个或多个空格隔开。举个例子，你有这样几个Makefile：a.mk、b.mk、c.mk，还有一个文件叫foo.make，以及一个变量$(bar)，其包含了e.mk和f.mk，那么，下面的语句： </p><pre>    include foo.make *.mk $(bar)</pre>等价于：<pre>    include foo.make a.mk b.mk c.mk e.mk f.mk</pre>make命令开始时，会把找寻include所指出的其它Makefile，并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话，make会在当前目录下首先寻找，如果当前目录下没有找到，那么，make还会在下面的几个目录下找： 
<p> </p><ol><li>如果make执行时，有“-I”或“--include-dir”参数，那么make就会在这个参数所指定的目录下去寻找。 
</li><li>如果目录 <prefix></prefix>/include（一般是：/usr/local/bin或/usr/include）存在的话，make也会去找。 </li></ol><p>如果有文件没有找到的话，make会生成一条警告信息，但不会马上出现致命错误。它会继续载入其它的文件，一旦完成makefile的读取，make会再重试这些没有找到，或是不能读取的文件，如果还是不行，make才会出现一条致命信息。如果你想让make不理那些无法读取的文件，而继续执行，你可以在include前加一个减号“-”。如： </p><div class="fragment"><pre>-include &lt;filename&gt;</pre><pre> </pre></div>其表示，无论include过程中出现什么错误，都不要报错继续执行。和其它版本make兼容的相关命令是sinclude，其作用和这一个是一样的。 
<h4><a name="2_4_MAKEFILES"></a>2.4 环境变量 MAKEFILES </h4>如果你的当前环境中定义了环境变量MAKEFILES，那么，make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile，用空格分隔。只是，它和include不同的是，从这个环境变中引入的Makefile的“目标”不会起作用，如果环境变量中定义的文件发现错误，make也会不理。 
<p>但是在这里我还是建议不要使用这个环境变量，因为只要这个变量一被定义，那么当你使用make时，所有的Makefile都会受到它的影响，这绝不是你想看到的。在这里提这个事，只是为了告诉大家，也许有时候你的Makefile出现了怪事，那么你可以看看当前环境中有没有定义这个变量。 </p><h4><a name="2_5_make"></a><a name="2_5_make_"></a>2.5 make的工作方式 </h4>GNU的make工作时的执行步骤入下：（想来其它的make也是类似） 
<p> </p><ol><li>读入所有的Makefile。 
</li><li>读入被include的其它Makefile。 
</li><li>初始化文件中的变量。 
</li><li>推导隐晦规则，并分析所有规则。 
</li><li>为所有的目标文件创建依赖关系链。 
</li><li>根据依赖关系，决定哪些目标要重新生成。 
</li><li>执行生成命令。 </li></ol><p>1-5步为第一个阶段，6-7为第二个阶段。第一个阶段中，如果定义的变量被使用了，那么，make会把其展开在使用的位置。但make并不会完全马上展开，make使用的是拖延战术，如果变量出现在依赖关系的规则中，那么仅当这条依赖被决定要使用了，变量才会在其内部展开。 </p><p>当然，这个工作方式你不一定要清楚，但是知道这个方式你也会对make更为熟悉。有了这个基础，后续部分也就容易看懂了。 </p><h3><a name="3_Makefile"></a><a name="3_Makefile_"></a>3 Makefile书写规则 </h3><hr />
规则包含两个部分，一个是依赖关系，一个是生成目标的方法。 
<p>在Makefile中，规则的顺序是很重要的，因为，Makefile中只应该有一个最终目标，其它的目标都是被这个目标所连带出来的，所以一定要让make知道你的最终目标是什么。一般来说，定义在Makefile中的目标可能会有很多，但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个，那么，第一个目标会成为最终的目标。make所完成的也就是这个目标。 </p><p>好了，还是让我们来看一看如何书写规则。 </p><h4><a name="3_1"></a><a name="3_1_"></a>3.1 规则举例 </h4><pre> foo.o : foo.c defs.h       # foo模块            cc -c -g foo.c</pre>看到这个例子，各位应该不是很陌生了，前面也已说过，foo.o是我们的目标，foo.c和defs.h是目标所依赖的源文件，而只有一个命令“cc -c -g foo.c”（以Tab键开头）。这个规则告诉我们两件事： 
<p> </p><ol><li>文件的依赖关系，foo.o依赖于foo.c和defs.h的文件，如果foo.c和defs.h的文件日期要比foo.o文件日期要新，或是foo.o不存在，那么依赖关系发生。 
</li><li>如果生成（或更新）foo.o文件。也就是那个cc命令，其说明了，如何生成foo.o这个文件。（当然foo.c文件include了defs.h文件） </li></ol><p> </p><h4><a name="3_2"></a><a name="3_2_"></a>3.2 规则的语法 </h4><pre>      targets : prerequisites        command        ...</pre>或是这样： <pre>      targets : prerequisites ; command            command            ...</pre>targets是文件名，以空格分开，可以使用通配符。一般来说，我们的目标基本上是一个文件，但也有可能是多个文件。 
<p>command是命令行，如果其不与“target:prerequisites”在一行，那么，必须以[Tab键]开头，如果和prerequisites在一行，那么可以用分号做为分隔。（见上） </p><p>prerequisites也就是目标所依赖的文件（或依赖目标）。如果其中的某个文件要比目标文件要新，那么，目标就被认为是“过时的”，被认为是需要重生成的。这个在前面已经讲过了。 </p><p>如果命令太长，你可以使用反斜框（‘\’）作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事，文件的依赖关系和如何成成目标文件。 </p><p>一般来说，make会以UNIX的标准Shell，也就是/bin/sh来执行命令。 </p><h4><a name="3_3"></a><a name="3_3_"></a>3.3 在规则中使用通配符 </h4><p>如果我们想定义一系列比较类似的文件，我们很自然地就想起使用通配符。make支持三各通配符：“*”，“?”和“[...]”。这是和Unix的B-Shell是相同的。 </p><p>波浪号（“~”）字符在文件名中也有比较特殊的用途。如果是“~/test”，这就表示当前用户的$HOME目录下的test目录。而“~hchen/test”则表示用户hchen的宿主目录下的test目录。（这些都是Unix下的小知识了，make也支持）而在Windows或是MS-DOS下，用户没有宿主目录，那么波浪号所指的目录则根据环境变量“HOME”而定。 </p><p>通配符代替了你一系列的文件，如“*.c”表示所以后缀为c的文件。一个需要我们注意的是，如果我们的文件名中有通配符，如：“*”，那么可以用转义字符“\”，如“\*”来表示真实的“*”字符，而不是任意长度的字符串。 </p><p>好吧，还是先来看几个例子吧： </p><pre>    clean:         rm -f *.o</pre>上面这个例子我不不多说了，这是操作系统Shell所支持的通配符。这是在命令中的通配符。 <pre>    print: *.c         lpr -p $?         touch print</pre>上面这个例子说明了通配符也可以在我们的规则中，目标print依赖于所有的[.c]文件。其中的“$?”是一个自动化变量，我会在后面给你讲述。 <pre>    objects = *.o</pre>上面这个例子，表示了，通符同样可以用在变量中。并不是说[*.o]会展开，不！objects的值就是“*.o”。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开，也就是让objects的值是所有[.o]的文件名的集合，那么，你可以这样： <pre>    objects := $(wildcard *.o)</pre>这种用法由关键字“wildcard”指出，关于Makefile的关键字，我们将在后面讨论。 
<h4><a name="3_4"></a><a name="3_4_"></a>3.4 文件搜寻 </h4><p>在一些大的工程中，有大量的源文件，我们通常的做法是把这许多的源文件分类，并存放在不同的目录中。所以，当make需要去找寻文件的依赖关系时，你可以在文件前加上路径，但最好的方法是把一个路径告诉make，让make在自动去找。 </p><p>Makefile文件中的特殊变量“VPATH”就是完成这个功能的，如果没有指明这个变量，make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量，那么，make就会在当当前目录找不到的情况下，到所指定的目录中去找寻文件了。 </p><pre>    VPATH = src:../headers</pre>上面的的定义指定两个目录，“src”和“../headers”，make会按照这个顺序进行搜索。目录由“冒号”分隔。（当然，当前目录永远是最高优先搜索的地方） 
<p>另一个设置文件搜索路径的方法是使用make的“vpath”关键字（注意，它是全小写的），这不是变量，这是一个make的关键字，这和上面提到的那个VPATH变量很类似，但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种： </p><ol><li>vpath &lt; pattern&gt; &lt; directories&gt; <br />为符合模式&lt; pattern&gt;的文件指定搜索目录&lt; directories&gt;。 
</li><li>vpath &lt; pattern&gt;<br />清除符合模式&lt; pattern&gt;的文件的搜索目录。 
</li><li>vpath <br />清除所有已被设置好了的文件搜索目录。 </li></ol><p>vapth使用方法中的&lt; pattern&gt;需要包含“%”字符。“%”的意思是匹配零或若干字符，例如，“%.h”表示所有以“.h”结尾的文件。&lt; pattern&gt;指定了要搜索的文件集，而&lt; directories&gt;则指定了 <pattern></pattern>的文件集的搜索的目录。例如： </p><pre>    vpath %.h ../headers</pre>该语句表示，要求make在“../headers”目录下搜索所有以“.h”结尾的文件。（如果某文件在当前目录没有找到的话） 
<p>我们可以连续地使用vpath语句，以指定不同搜索策略。如果连续的vpath语句中出现了相同的&lt; pattern&gt;，或是被重复了的&lt; pattern&gt;，那么，make会按照vpath语句的先后顺序来执行搜索。如： </p><pre>    vpath %.c foo    vpath %   blish    vpath %.c bar</pre>其表示“.c”结尾的文件，先在“foo”目录，然后是“blish”，最后是“bar”目录。 <pre>    vpath %.c foo:bar    vpath %   blish</pre>而上面的语句则表示“.c”结尾的文件，先在“foo”目录，然后是“bar”目录，最后才是“blish”目录。 
<h4><a name="3_5"></a><a name="3_5_"></a>3.5 伪目标 </h4><p>最早先的一个例子中，我们提到过一个“clean”的目标，这是一个“伪目标”， </p><pre>    clean:            rm *.o temp</pre>正像我们前面例子中的“clean”一样，即然我们生成了许多文件编译文件，我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 （以“make clean”来使用该目标） 
<p>因为，我们并不生成“clean”这个文件。“伪目标”并不是一个文件，只是一个标签，由于“伪目标”不是文件，所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然，“伪目标”的取名不能和文件名重名，不然其就失去了“伪目标”的意义了。 </p><p>当然，为了避免和文件重名的这种情况，我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”，向make说明，不管是否有这个文件，这个目标就是“伪目标”。 </p><pre>    .PHONY : clean</pre>只要有这个声明，不管是否有“clean”文件，要运行“clean”这个目标，只有“make clean”这样。于是整个过程可以这样写： <pre>     .PHONY: clean    clean:            rm *.o temp</pre>伪目标一般没有依赖的文件。但是，我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”，只要将其放在第一个。一个示例就是，如果你的Makefile需要一口气生成若干个可执行文件，但你只想简单地敲一个make完事，并且，所有的目标文件都写在一个Makefile中，那么你可以使用“伪目标”这个特性： <pre>    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中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标，其依赖于其它三个目标。由于伪目标的特性是，总是被执行的，所以其依赖的那三个目标就总是不如“all”这个目标新。所以，其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目标”。 
<p>随便提一句，从上面的例子我们可以看出，目标也可以成为依赖。所以，伪目标同样也可成为依赖。看下面的例子： </p><pre>    .PHONY: cleanall cleanobj cleandiff    cleanall : cleanobj cleandiff            rm program    cleanobj :            rm *.o    cleandiff :            rm *.diff</pre>“make clean”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的 
<p> </p><h4><a name="3_6"></a><a name="3_6_"></a>3.6 多目标 </h4><p>Makefile的规则中的目标可以不止一个，其支持多目标，有可能我们的多个目标同时依赖于一个文件，并且其生成的命令大体类似。于是我们就能把其合并起来。当然，多个目标的生成规则的执行命令是同一个，这可能会可我们带来麻烦，不过好在我们的可以使用一个自动化变量“$@”（关于自动化变量，将在后面讲述），这个变量表示着目前规则中所有的目标的集合，这样说可能很抽象，还是看一个例子吧。 </p><pre>    bigoutput littleoutput : text.g            generate text.g -$(subst output,,$@) &gt; $@<pre>    上述规则等价于：</pre>    bigoutput : text.g    generate text.g -big &gt; bigoutput    littleoutput : text.g    generate text.g -little &gt; littleoutput    </pre>其中，-$(subst output,,$@)中的“$”表示执行一个Makefile的函数，函数名为subst，后面的为参数。关于函数，将在后面讲述。这里的这个函数是截取字符串的意思，“$@”表示目标的集合，就像一个数组，“$@”依次取出目标，并执于命令。 
<p> </p><p> </p><h4><a name="3_7"></a><a name="3_7_"></a>3.7 静态模式 </h4><p>静态模式可以更加容易地定义多目标的规则，可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法： </p><div class="fragment"><pre>&lt;targets ...&gt;: &lt;target-pattern&gt;: &lt;prereq-patterns ...&gt;　　　&lt;commands&gt;...</pre><pre> </pre></div><p>targets定义了一系列的目标文件，可以有通配符。是目标的一个集合。 </p><p>target-parrtern是指明了targets的模式，也就是的目标集模式。 </p><p>prereq-parrterns是目标的依赖模式，它对target-parrtern形成的模式再进行一次依赖目标的定义。 </p><p>这样描述这三个东西，可能还是没有说清楚，还是举个例子来说明一下吧。如果我们的<target-parrtern></target-parrtern>定义成“%.o”，意思是我们的<target></target>集合中都是以“.o”结尾的，而如果我们的 <prereq-parrterns></prereq-parrterns>定义成“%.c”，意思是对<target-parrtern></target-parrtern>所形成的目标集进行二次定义，其计算方法是，取<target-parrtern></target-parrtern>模式中的“%”（也就是去掉了[.o]这个结尾），并为其加上[.c]这个结尾，形成的新集合。 </p><p>所以，我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符，如果你的文件名中有“%”那么你可以使用反斜杠“\”进行转义，来标明真实的“%”字符。 </p><p>看一个例子： </p><pre>    objects = foo.o bar.o    all: $(objects)    $(objects): %.o: %.c            $(CC) -c $(CFLAGS) ___FCKpd___34lt; -o $@</pre>上面的例子中，指明了我们的目标从$object中获取，“%.o”表明要所有以“.o”结尾的目标，也就是“foo.o bar.o”，也就是变量$object集合的模式，而依赖模式“%.c”则取模式“%.o”的“%”，也就是“foo bar”，并为其加下“.c”的后缀，于是，我们的依赖目标就是“foo.c bar.c”。而命令中的“$&lt;”和“$@”则是自动化变量，“$&lt;”表示所有的依赖目标集（也就是“foo.c bar.c”），“$@”表示目标集（也褪恰癴oo.o bar.o”）。于是，上面的规则展开后等价于下面的规则： <pre>    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>试想，如果我们的“%.o”有几百个，那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则，实在是太有效率了。“静态模式规则”的用法很灵活，如果用得好，那会一个很强大的功能。再看一个例子： <pre>    files = foo.elc bar.o lose.o    $(filter %.o,$(files)): %.o: %.c            $(CC) -c $(CFLAGS) ___FCKpd___36lt; -o $@    $(filter %.elc,$(files)): %.elc: %.el            emacs -f batch-byte-compile ___FCKpd___36lt;</pre><p>$(filter %.o,$(files))表示调用Makefile的filter函数，过滤“$filter”集，只要其中模式为“%.o”的内容。其的它内容，我就不用多说了吧。这个例字展示了Makefile中更大的弹性。 </p><h4><a name="3_8"></a><a name="3_8_"></a>3.8 自动生成依赖性 </h4><p>在Makefile中，我们的依赖关系可能会需要包含一系列的头文件，比如，如果我们的main.c中有一句“#include "defs.h"”，那么我们的依赖关系应该是： </p><pre>    main.o : main.c defs.h</pre>但是，如果是一个比较大型的工程，你必需清楚哪些C文件包含了哪些头文件，并且，你在加入或删除头文件时，也需要小心地修改Makefile，这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情，我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项，即自动找寻源文件中包含的头文件，并生成一个依赖关系。例如，如果我们执行下面的命令： <pre>    cc -M main.c</pre>其输出是： <pre>    main.o : main.c defs.h</pre>于是由编译器自动生成的依赖关系，这样一来，你就不必再手动书写若干文件的依赖关系，而由编译器自动生成了。需要提醒一句的是，如果你使用GNU的C/C++编译器，你得用“-MM”参数，不然，“-M”参数会把一些标准库的头文件也包含进来。 
<p>gcc -M main.c的输出是： </p><pre>    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>    main.o: main.c defs.h</pre>那么，编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来，我们的Makefile也要根据这些源文件重新生成，让Makefile自已依赖于源文件？这个功能并不现实，不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中，为每一个“name.c”的文件都生成一个“name.d”的Makefile文件，[.d]文件中就存放对应[.c]文件的依赖关系。 
<p>于是，我们可以写出[.c]文件和[.d]文件的依赖关系，并让make自动更新或自成[.d]文件，并把其包含在我们的主Makefile中，这样，我们就可以自动化地生成每个文件的依赖关系了。 </p><p>这里，我们给出了一个模式规则来产生[.d]文件： </p><pre>    %.d: %.c            @set -e; rm -f $@;              $(CC) -M $(CPPFLAGS) ___FCKpd___42lt; &gt; $@.$$;              sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' &lt; $@.$$ &gt; $@;              rm -f $@.$$</pre><p>这个规则的意思是，所有的[.d]文件依赖于[.c]文件，“rm -f $@”的意思是删除所有的目标，也就是[.d]文件，第二行的意思是，为每个依赖文件“$&lt;”，也就是[.c]文件生成依赖文件，“$@”表示模式“%.d”文件，如果有一个C文件是name.c，那么“%”就是“name”，“$$$$”意为一个随机编号，第二行生成的文件有可能是“name.d.12345”，第三行使用sed命令做了一个替换，关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。 </p><p>总而言之，这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖，即把依赖关系： </p><pre>    main.o : main.c defs.h</pre>转成： <pre>    main.o main.d : main.c defs.h</pre>于是，我们的[.d]文件也会自动更新了，并会自动生成了，当然，你还可以在这个[.d]文件中加入的不只是依赖关系，包括生成的命令也可一并加入，让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作，接下来，我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的“include”命令，来引入别的Makefile文件（前面讲过），例如： <pre>    sources = foo.c bar.c    include $(sources:.c=.d)</pre>上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换，把变量$(sources)所有[.c]的字串都替换成[.d]，关于这个“替换”的内容，在后面我会有更为详细的讲述。当然，你得注意次序，因为include是按次来载入文件，最先载入的[<no></no>.d]文件中的目标会成为默认目标 
<h3><a name="4_Makefile"></a><a name="4_Makefile_"></a>4 Makefile 书写命令 </h3><hr /><p>每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令，每条命令的开头必须以[Tab]键开头，除非，命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略，但是如果该空格或空行是以Tab键开头的，那么make会认为其是一个空命令。 </p><p>我们在UNIX下可能会使用不同的Shell，但是make的命令默认是被“/bin/sh”——UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中，“#”是注释符，很像C/C++中的“//”，其后的本行字符都被注释。 </p><p> </p><h4><a name="4_1"></a><a name="4_1_"></a>4.1 显示命令 </h4><p>通常，make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前，那么，这个命令将不被make显示出来，最具代表性的例子是，我们用这个功能来像屏幕显示一些信息。如： </p><pre>    @echo 正在编译XXX模块......</pre>当make执行时，会输出“正在编译XXX模块......”字串，但不会输出命令，如果没有“@”，那么，make将输出： <pre>    echo 正在编译XXX模块......    正在编译XXX模块......</pre>如果make执行时，带入make参数“-n”或“--just-print”，那么其只是显示命令，但不会执行命令，这个功能很有利于我们调试我们的Makefile，看看我们书写的命令是执行起来是什么样子的或是什么顺序的。 
<p>而make参数“-s”或“--slient”则是全面禁止命令的显示。 </p><p> </p><h4><a name="4_2"></a><a name="4_2_"></a>4.2 命令执行 </h4><p>当依赖目标新于目标时，也就是当规则的目标需要被更新时，make会一条一条的执行其后的命令。需要注意的是，如果你要让上一条命令的结果应用在下一条命令时，你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令，你希望第二条命令得在cd之后的基础上运行，那么你就不能把这两条命令写在两行上，而应该把这两条命令写在一行上，用分号分隔。如： </p><pre>    示例一：        exec:                cd /home/hchen                pwd    示例二：        exec:                cd /home/hchen; pwd</pre><p>当我们执行“make exec”时，第一个例子中的cd没有作用，pwd会打印出当前的Makefile目录，而第二个例子中，cd就起作用了，pwd会打印出“/home/hchen”。 </p><p>make一般是使用环境变量SHELL中所定义的系统Shell来执行命令，默认情况下使用UNIX的标准Shell——/bin/sh来执行命令。但在MS-DOS下有点特殊，因为MS-DOS下没有SHELL环境变量，当然你也可以指定。如果你指定了UNIX风格的目录形式，首先，make会在SHELL所指定的路径中找寻命令解释器，如果找不到，其会在当前盘符中的当前目录中寻找，如果再找不到，其会在PATH环境变量中所定义的所有路径中寻找。MS-DOS中，如果你定义的命令解释器没有找到，其会给你的命令解释器加上诸如“.exe”、“.com”、“.bat”、“.sh”等后缀。 </p><p> </p><h4><a name="4_3"></a><a name="4_3_"></a>4.3 命令出错 </h4><p>每当命令运行完后，make会检测每个命令的返回码，如果命令返回成功，那么make会执行下一条命令，当规则中所有的命令成功返回后，这个规则就算是成功完成了。如果一个规则中的某个命令出错了（命令退出码非零），那么make就会终止执行当前规则，这将有可能终止所有规则的执行。 </p><p>有些时候，命令的出错并不表示就是错误的。例如mkdir命令，我们一定需要建立一个目录，如果目录不存在，那么mkdir就成功执行，万事大吉，如果目录存在，那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录，于是我们就不希望mkdir出错而终止规则的运行。 </p><p>为了做到这一点，忽略命令的出错，我们可以在Makefile的命令行前加一个减号“-”（在Tab键之后），标记为不管命令出不出错都认为是成功的。如： </p><pre>   clean:            -rm -f *.o</pre>还有一个全局的办法是，给make加上“-i”或是“--ignore-errors”参数，那么，Makefile中所有命令都会忽略错误。而如果一个规则是以“.IGNORE”作为目标的，那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法，你可以根据你的不同喜欢设置。 
<p>还有一个要提一下的make的参数的是“-k”或是“--keep-going”，这个参数的意思是，如果某规则中的命令出错了，那么就终目该规则的执行，但继续执行其它规则。 </p><h4><a name="4_4_make"></a>4.4 嵌套执行make </h4><p>在一些大的工程中，我们会把我们不同模块或是不同功能的源文件放在不同的目录中，我们可以在每个目录中都书写一个该目录的Makefile，这有利于让我们的Makefile变得更加地简洁，而不至于把所有的东西全部写在一个Makefile中，这样会很难维护我们的Makefile，这个技术对于我们模块编译和分段编译有着非常大的好处。 </p><p>例如，我们有一个子目录叫subdir，这个目录下有个Makefile文件，来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写： </p><pre>    subsystem:            cd subdir &amp;&amp; $(MAKE)其等价于：    subsystem:            $(MAKE) -C subdir<pre>定义$(MAKE)宏变量的意思是，也许我们的make需要一些参数，所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录，然后执行make命令。我们把这个Makefile叫做“总控Makefile”，总控Makefile的变量可以传递到下级的Makefile中（如果你显示的声明），但是不会覆盖下层的Makefile中所定义的变量，除非指定了“-e”参数。如果你要传递变量到下级Makefile中，那么你可以使用这样的声明：<div class="fragment"><pre>export &lt;variable ...&gt;</pre></div></pre></pre>如果你不想让某些变量传递到下级Makefile中，那么你可以这样声明： 
<div class="fragment"><pre>unexport &lt;variable ...&gt;</pre><pre> </pre></div>如： <pre>        示例一：        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的参数信息，如果我们执行“总控Makefile”时有make参数或是在上层Makefile中定义了这个变量，那么MAKEFILES变量将会是这些参数，并会传递到下层Makefile中，这是一个系统级的环境变量。 </p><p>但是make命令中的有几个参数并不往下传递，它们是“-C”,“-f”,“-h”“-o”和“-W”（有关Makefile参数的细节将在后面说明），如果你不想往下层传递参数，那么，你可以这样来： </p><pre>        subsystem:            cd subdir &amp;&amp; $(MAKE) MAKEFLAGS=</pre>如果你定义了环境变量MAKEFLAGS，那么你得确信其中的选项是大家都会用到的，如果其中有“-t”,“-n”,和“-q”参数，那么将会有让你意想不到的结果，或许会让你异常地恐慌。 
<p>还有一个在“嵌套执行”中比较有用的参数，“-w”或是“--print-directory”会在make的过程中输出一些信息，让你看到目前的工作目录。比如，如果我们的下级make目录是“/home/hchen/gnu/make”，如果我们使用“make -w”来执行，那么当进入该目录时，我们会看到： </p><pre>        make: Entering directory `/home/hchen/gnu/make'.</pre>而在完成下层make后离开目录时，我们会看到： <pre>        make: Leaving directory `/home/hchen/gnu/make'</pre>当你使用“-C”参数来指定make下层Makefile时，“-w”会被自动打开的。如果参数中有“-s”（“--slient”）或是“--no-print-directory”，那么，“-w”总是失效的。 
<h4><a name="4_5"></a><a name="4_5_"></a>4.5 定义命令包 </h4><p>如果Makefile中出现一些相同命令序列，那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始，以“endef”结束，如： </p><pre>    define run-yacc    yacc $(firstword $^)    mv y.tab.c $@    endef</pre>这里，“run-yacc”是这个命令包的名字，其不要和Makefile中的变量重名。在“define”和“endef”中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序，因为Yacc程序总是生成“y.tab.c”的文件，所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。 <pre>    foo.c : foo.y            $(run-yacc)</pre>我们可以看见，要使用这个命令包，我们就好像使用变量一样。在这个命令包的使用中，命令包“run-yacc”中的“$^”就是“foo.y”，“$@”就是“foo.c”（有关这种以“$”开头的特殊变量，我们会在后面介绍），make在执行命令包时，命令包中的每个命令会被依次独立执行。<br /><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19979.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:49 <a href="http://www.cnitblog.com/flutist1225/articles/19979.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux BOOTLOADER全程详解(Arm S3C2410) </title><link>http://www.cnitblog.com/flutist1225/articles/19978.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:39:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19978.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19978.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19978.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19978.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19978.html</trackback:ping><description><![CDATA[网上关于Linux的BOOTLOADER文章不少了,但是大都是vivi,blob等比较庞大的程序,读起来不太方便,编译出的文件也比较大,而且更多的是面向开发用的引导代码,做成产品时还要裁减,这一定程度影响了开发速度,对初学者学习开销也比较大,在此分析一种简单的BOOTLOADER,是在三星公司提供的2410 BOOTLOADER上稍微修改后的结果,编译出来的文件大小不超过4k,希望对大家有所帮助. 
<p>1.几个重要的概念 </p><p>COMPRESSED KERNEL and DECOMPRESSED KERNEL </p><p>压缩后的KERNEL,按照文档资料,现在不提倡使用DECOMPRESSED KERNEL,而要使用COMPRESSED KERNEL,它包括了解压器.因此要在ram分配时给压缩和解压的KERNEL提供足够空间,这样它们不会相互覆盖. </p><p>当执行指令跳转到COMPRESSED KERNEL后,解压器就开始工作,如果解压器探测到解压的代码会覆盖掉COMPRESSED KERNEL,那它会直接跳到COMPRESSED KERNEL后存放数据,并且重新定位KERNEL,所以如果没有足够空间,就会出错. </p><p>Jffs2 File System </p><p>可以使armlinux应用中产生的数据保存在FLASH上,我的板子还没用到这个. </p><p>RAMDISK </p><p>使用RAMDISK可以使ROOT FILE SYSTEM在没有其他设备的情况下启动.一般有两种加载方式,我就介绍最常用的吧,把COMPRESSED RAMDISK IMAGE放到指定地址,然后由BOOTLOADER把这个地址通过启动参数的方式ATAG_INITRD2传递给KERNEL.具体看代码分析. </p><p>启动参数(摘自IBM developer) </p><p>在调用内核之前，应该作一步准备工作，即：设置 Linux 内核的启动参数。Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始，以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中. </p><p>在嵌入式 Linux 系统中，通常需要由 BOOTLOADER 设置的常见启动参数有：ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。 </p><p>(注)参数也可以用COMMANDLINE来设定,在我的BOOTLOADER里,我两种都用了. </p><p>2.开发环境和开发板配置: </p><p>CPU:S3C2410,BANK6上有64M的SDRAM(两块),BANK0上有32M NOR FLASH,串口当然是逃不掉的.这样,按照数据手册,地址分配如下: </p><p>0x4000_0000开始是4k的片内DRAM. </p><p>0x0000_0000开始是32M FLASH 16bit宽度 </p><p>0x3000_0000开始是64M SDRAM 32bit宽度 </p><p>注意:控制寄存器中的BANK6和BANK7部分必须相同. </p><p>0x4000_0000(片内DRAM)存放4k以内的BOOTLOADER IMAGE </p><p>0x3000_0100开始存放启动参数 </p><p>0x3120_0000 存放COMPRESSED KERNEL IMAGE </p><p>0x3200_0000 存放COMPRESSED RAMDISK </p><p>0x3000_8000 指定为DECOMPRESSED KERNEL IMAGE ADDRESS </p><p>0x3040_0000 指定为DECOMPRESSED RAMDISK IMAGE ADDRESS </p><p>开发环境:Redhat Linux,armgcc toolchain, armlinux KERNEL </p><p>如何建立armgcc的编译环境:建议使用toolchain,而不要自己去编译armgcc,偶试过好多次,都以失败告终. </p><p>先下载arm-gcc 3.3.2 toolchain </p><p>将arm-linux-gcc-3.3.2.tar.bz2 解压到 /toolchain </p><p># tar jxvf arm-linux-gcc-3.3.2.tar.bz2 </p><p># mv /usr/local/arm/3.3.2 /toolchain </p><p>在makefile 中在把arch=arm CROSS_COMPILE设置成toolchain的路径 </p><p>还有就是INCLUDE = -I ../include -I /root/my/usr/local/arm/3.3.2/include.,否则库函数就不能用了 </p><p>3.启动方式: </p><p>可以放在FLASH里启动,或者用Jtag仿真器.由于使用NOR FLASH,根据2410的手册,片内的4K DRAM在不需要设置便可以直接使用,而其他存储器必须先初始化,比如告诉memory controller,BANK6里有两块SDRAM,数据宽度是32bit,= =.否则memory control会按照复位后的默认值来处理存储器.这样读写就会产生错误. </p><p>所以第一步,通过仿真器把执行代码放到0x4000_0000,(在编译的时候,设定TEXT_BAS </p><p>E=0x40000000) </p><p>第二步,通过 AxD把linux KERNEL IMAGE放到目标地址(SDRAM)中,等待调用 </p><p>第三步,执行BOOTLOADER代码,从串口得到调试数据,引导armlinux </p><p>4.代码分析 </p><p>讲了那么多执行的步骤,是想让大家对启动有个大概印象,接着就是BOOTLOADER内部的代码分析了,BOOTLOADER文章内容网上很多,我这里精简了下,删除了不必要的功能. </p><p>BOOTLOADER一般分为2部分,汇编部分和c语言部分,汇编部分执行简单的硬件初始化,C部分负责复制数据,设置启动参数,串口通信等功能. </p><p>BOOTLOADER的生命周期: </p><p>1. 初始化硬件,比如设置UART(至少设置一个),检测存储器= =. </p><p>2. 设置启动参数,这是为了告诉内核硬件的信息,比如用哪个启动界面,波特率 = =. </p><p>3. 跳转到Linux KERNEL的首地址. </p><p>4. 消亡 </p><p> </p><p>当然,在引导阶段,象vivi等,都用虚地址,如果你嫌烦的话,就用实地址,都一样. </p><p>我们来看代码: </p><p>2410init.s </p><p>.global _start//开始执行处 </p><p>_start: </p><p>//下面是中断向量 </p><p>b reset @ Supervisor Mode//重新启动后的跳转 </p><p>…… </p><p>…… </p><p>reset: </p><p>ldr r0,=WTCON /WTCON地址为53000000,watchdog的控制寄存器 */ </p><p>ldr r1,=0x0 /*关watchdog*/ </p><p>str r1,[r0] </p><p> </p><p>ldr r0,=INTMSK </p><p>ldr r1,=0xffffffff /*屏蔽所有中断*/ </p><p>str r1,[r0] </p><p> </p><p>ldr r0,=INTSUBMSK </p><p>ldr r1,=0x3ff /*子中断也一样*/ </p><p>str r1,[r0] </p><p>/*Initialize Ports...for display LED.*/ </p><p>ldr r0, =GPFCON </p><p>ldr r1, =0x55aa </p><p>str r1, [r0] </p><p>ldr r0, =GPFUP </p><p>ldr r1, =0xff </p><p>str r1, [r0] </p><p>ldr r0,=GPFDAT </p><p>ldr r1,=POWEROFFLED1 </p><p>str r1,[r0] </p><p>/* Setup clock Divider control register </p><p>* you must configure CLKDIVN before LOCKTIME or MPLL UPLL </p><p>* because default CLKDIVN 1,1,1 set the SDMRAM Timing Conflict </p><p>nop </p><p>* FCLK:HCLK:PCLK = 1:2:4 in this case </p><p>*/ </p><p>ldr r0,=CLKDIVN </p><p>ldr r1,=0x3 </p><p>str r1,[r0] </p><p> </p><p>/*To reduce PLL lock time, adjust the LOCKTIME register. */ </p><p>ldr r0,=LOCKTIME </p><p>ldr r1,=0xffffff </p><p>str r1,[r0] </p><p>/*Configure MPLL */ </p><p>ldr r0,=MPLLCON </p><p>ldr r1,=((M_MDIV&lt;&lt;12)+(M_PDIV&lt;&lt;4)+M_SDIV) //Fin=12MHz,Fout=203MHz </p><p>str r1,[r0] </p><p>ldr r1,=GSTATUS2 </p><p>ldr r10,[r1] </p><p>tst r10,#OFFRST </p><p>bne 1000f </p><p>//以上这段,我没动,就用三星写的了,下面是主要要改的地方 </p><p>/* MEMORY C0NTROLLER(MC)设置*/ </p><p>add r0,pc,#MCDATA - (.+8)// r0指向MCDATA地址,那里存放着MC初始化要用到的数据 </p><p>ldr r1,=BWSCON // r1指向MC控制器寄存器的首地址 </p><p>add r2,r0,#52 // 复制次数,偏移52字 </p><p> </p><p>1: //按照偏移量进行循环复制 </p><p>ldr r3,[r0],#4 </p><p>str r3,[r1],#4 </p><p>cmp r2,r0 </p><p>bne 1b </p><p>.align 2 </p><p> </p><p>MCDATA: </p><p>.word (0+(B1_BWSCON&lt;&lt;4)+(B2_BWSCON&lt;&lt;8)+(B3_BWSCON&lt;&lt;12)+(B4_BWSCON&lt;&lt;16)+(B5_BWSCON&lt;&lt;20)+(B6_BWSCON&lt;&lt;24)+(B7_BWSCON&lt;&lt;28)) </p><p>上面这行就是BWSCON的数据,具体参数意义如下: </p><p> </p><p>需要更改设置DW6 和DW7都设置成10,即32bit,DW0 设置成01,即16bit </p><p>下面都是每个BANK的控制器数据,大都是时钟相关,可以用默认值,设置完MC后,就跳到调用main函数的部分 </p><p>.word ((B0_Tacs&lt;&lt;13)+(B0_Tcos&lt;&lt;11)+(B0_Tacc&lt;&lt;8)+(B0_Tcoh&lt;&lt;6)+(B0_Tah&lt;&lt;4)+(B0_Tacp&lt;&lt;2)+(B0_PMC)) </p><p>.word ((B1_Tacs&lt;&lt;13)+(B1_Tcos&lt;&lt;11)+(B1_Tacc&lt;&lt;8)+(B1_Tcoh&lt;&lt;6)+(B1_Tah&lt;&lt;4)+(B1_Tacp&lt;&lt;2)+(B1_PMC)) </p><p>.word ((B2_Tacs&lt;&lt;13)+(B2_Tcos&lt;&lt;11)+(B2_Tacc&lt;&lt;8)+(B2_Tcoh&lt;&lt;6)+(B2_Tah&lt;&lt;4)+(B2_Tacp&lt;&lt;2)+(B2_PMC)) </p><p>.word ((B3_Tacs&lt;&lt;13)+(B3_Tcos&lt;&lt;11)+(B3_Tacc&lt;&lt;8)+(B3_Tcoh&lt;&lt;6)+(B3_Tah&lt;&lt;4)+(B3_Tacp&lt;&lt;2)+(B3_PMC)) </p><p>.word ((B4_Tacs&lt;&lt;13)+(B4_Tcos&lt;&lt;11)+(B4_Tacc&lt;&lt;8)+(B4_Tcoh&lt;&lt;6)+(B4_Tah&lt;&lt;4)+(B4_Tacp&lt;&lt;2)+(B4_PMC)) </p><p>.word ((B5_Tacs&lt;&lt;13)+(B5_Tcos&lt;&lt;11)+(B5_Tacc&lt;&lt;8)+(B5_Tcoh&lt;&lt;6)+(B5_Tah&lt;&lt;4)+(B5_Tacp&lt;&lt;2)+(B5_PMC)) </p><p>.word ((B6_MT&lt;&lt;15)+(B6_Trcd&lt;&lt;2)+(B6_SCAN)) </p><p>.word ((B7_MT&lt;&lt;15)+(B7_Trcd&lt;&lt;2)+(B7_SCAN)) </p><p>.word ((REFEN&lt;&lt;23)+(TREFMD&lt;&lt;22)+(Trp&lt;&lt;20)+(Trc&lt;&lt;18)+(Tchr&lt;&lt;16)+REFCNT) </p><p>.word 0xB2 /* REFRESH Control Register */ </p><p>.word 0x30 /* BANKSIZE Register : Burst Mode */ </p><p>.word 0x30 /* SDRAM Mode Register */ </p><p> </p><p>.align 2 </p><p>.global call_main //调用main函数,函数参数都为0 </p><p>call_main: </p><p>ldr sp,STACK_START </p><p>mov fp,#0 /* no previous frame, so fp=0*/ </p><p>mov a1, #0 /* set argc to 0*/ </p><p>mov a2, #0 /* set argv to NUL*/ </p><p>bl main /* call main*/ </p><p>STACK_START: </p><p>.word STACK_BASE </p><p>undefined_instruction: </p><p>software_interrupt: </p><p>prefetch_abort: </p><p>data_abort: </p><p>not_used: </p><p>irq: </p><p>fiq: </p><p>/*以上是主要的汇编部分,实现了时钟设置,串口设置watchdog关闭,中断关闭功能(如果有需要还可以降频使用),然后转入main*/ </p><p>2410init.c file </p><p>int main(int argc,char **argv) </p><p>{ </p><p>u32 test = 0; </p><p>void (*theKERNEL)(int zero, int arch, unsigned long params_addr) = (void (*)(int, int, unsigned long))RAM_COMPRESSED_KERNEL_BASE; //压缩后的IMAGE地址 </p><p>int i,k=0; </p><p>// downPt=(RAM_COMPRESSED_KERNEL_BASE); </p><p>chkBs=(_RAM_STARTADDRESS);//SDRAM开始的地方 </p><p>// fromPt=(FLASH_LINUXKERNEL); </p><p>MMU_EnableICache(); </p><p>ChangeClockDivider(1,1); // 1:2:4 </p><p>ChangeMPllvalue(M_MDIV,M_PDIV,M_SDIV); //Fin=12MHz FCLK=200MHz </p><p>Port_Init();//设置I/O端口,在使用com口前,必须调用这个函数,否则通信芯片根本得不到数据 </p><p>Uart_Init(PCLK, 115200);//PCLK使用默认的200000,拨特率115200 </p><p>/*******************(检查ram空间)*******************/ </p><p>Uart_SendString("\n\tLinux S3C2410 Nor BOOTLOADER\n"); </p><p>Uart_SendString("\n\tChecking SDRAM 2410loader.c...\n"); </p><p>for(;chkBs&lt;0x33FA0140;chkBs=chkBs+0x4,test++)// </p><p> </p><p>//根据我的经验,最好以一个字节为递增,我们的板子,在256byte递增检测的时候是没问题的,但是 </p><p>//以1byte递增就出错了,第13跟数据线随几的会冒”1”,检测出来是硬件问题,现象如下 </p><p>//用仿真器下代码测试SDRAM，开始没贴28F128A3J FLASH片子，测试结果很好，但在上了FLASH片子//之后，测试数据（data）为0x00000400连续成批写入读出时，操作大约1k左右内存空间就会出错，//而且随机。那个出错数据总是变为0x00002400，数据总线10位和13位又没短路发生。用其他数据//测试比如0x00000200；0x00000800没这问题。dx帮忙。</p><p>//至今没有解决,所以我用不了Flash. </p><p> </p><p>{ </p><p>chkPt1 = chkBs; </p><p>*(u32 *)chkPt1 = test;//写数据 </p><p>if(*(u32 *)chkPt1==1024))//读数据和写入的是否一样? </p><p>{ </p><p>chkPt1 += 4; </p><p>Led_Display(1); </p><p>Led_Display(2); </p><p>Led_Display(3); </p><p>Led_Display(4); </p><p>} </p><p>else </p><p>goto error; </p><p>} </p><p>Uart_SendString("\n\tSDRAM Check Successful!\n\tMemory Maping..."); </p><p>get_memory_map(); </p><p>//获得可用memory 信息,做成列表,后面会作为启动参数传给KERNEL </p><p>//所谓内存映射就是指在4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。 </p><p>Uart_SendString("\n\tMemory Map Successful!\n"); </p><p>//我用仿真器把KERNEL,RAMDISK直接放在SDRAM上,所以下面这段是不需要的,但是如果KERNEL,RAMDISK在FLASH里,那就需要. </p><p>/*******************(copy linux KERNEL)*******************/ </p><p>Uart_SendString("\tLoading KERNEL IMAGE from FLASH... \n "); </p><p>Uart_SendString("\tand copy KERNEL IMAGE to SDRAM at 0x31000000\n"); </p><p>Uart_SendString("\t\tby LEIJUN DONG dongleijun4000@hotmail.com \n"); </p><p>for(k = 0;k &lt; 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M </p><p>* (u32 *)downPt = * (u32 *)fromPt; </p><p>/*******************(load RAMDISK)*******************/ </p><p>Uart_SendString("\t\tloading COMPRESSED RAMDISK...\n"); </p><p>downPt=(RAM_COMPRESSED_RAMDISK_BASE); </p><p>fromPt=(FLASH_RAMDISK_BASE); </p><p>for(k = 0;k &lt; 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M </p><p>* (u32 *)downPt = * (u32 *)fromPt; </p><p>/******jffs2文件系统,在开发中如果用不到FLASH,这段也可以不要********/ </p><p>Uart_SendString("\t\tloading jffs2...\n"); </p><p>downPt=(RAM_JFFS2); </p><p>fromPt=(FLASH_JFFS2); </p><p>for(k = 0;k &lt; (1024*1024/32);k++,downPt += 1,fromPt += 1) </p><p>* (u32 *)downPt = * (u32 *)fromPt; </p><p>Uart_SendString( "Load Success...Run...\n "); </p><p>/*******************(setup param)*******************/ </p><p>setup_start_tag();//开始设置启动参数 </p><p>setup_memory_tags();//内存印象 </p><p>setup_commandline_tag("console=ttyS0,115200n8");//启动命令行 </p><p>setup_initrd2_tag();//root device </p><p>setup_RAMDISK_tag();//ramdisk image </p><p>setup_end_tag(); </p><p>/*关I-cache */ </p><p>asm ("mrc p15, 0, %0, c1, c0, 0": "=r" (i)); </p><p>i &amp;= ~0x1000; </p><p>asm ("mcr p15, 0, %0, c1, c0, 0": : "r" (i)); </p><p>/* flush I-cache */ </p><p>asm ("mcr p15, 0, %0, c7, c5, 0": : "r" (i)); </p><p>//下面这行就跳到了COMPRESSED KERNEL的首地址 </p><p>theKERNEL(0, ARCH_NUMBER, (unsigned long *)(RAM_BOOT_PARAMS)); </p><p>//启动kernel时候,I-cache可以开也可以关,r0必须是0,r1必须是CPU型号 </p><p>(可以从linux/arch/arm/tools/mach-types中找到),r2必须是参数的物理开始地址 </p><p>/*******************END*******************/ </p><p>error: </p><p>Uart_SendString("\n\nPanic SDRAM check error!\n"); </p><p>return 0; </p><p>} </p><p>static void setup_start_tag(void) </p><p>{ </p><p>params = (struct tag *)RAM_BOOT_PARAMS;//启动参数开始的地址 </p><p>params-&gt;hdr.tag = ATAG_CORE; </p><p>params-&gt;hdr.size = tag_size(tag_core); </p><p>params-&gt;u.core.flags = 0; </p><p>params-&gt;u.core.pagesize = 0; </p><p>params-&gt;u.core.rootdev = 0; </p><p>params = tag_next(params); </p><p>} </p><p> </p><p> </p><p>static void setup_memory_tags(void) </p><p>{ </p><p>int i; </p><p> </p><p>for(i = 0; i &lt; NUM_MEM_AREAS; i++) { </p><p>if(memory_map[i].used) { </p><p>params-&gt;hdr.tag = ATAG_MEM; </p><p>params-&gt;hdr.size = tag_size(tag_mem32); </p><p>params-&gt;u.mem.start = memory_map[i].start; </p><p>params-&gt;u.mem.size = memory_map[i].len; </p><p>params = tag_next(params); </p><p>} </p><p>} </p><p>} </p><p> </p><p> </p><p>static void setup_commandline_tag(char *commandline) </p><p>{ </p><p>int i = 0; </p><p>/* skip non-existent command lines so the kernel will still </p><p>* use its default command line. </p><p>*/ </p><p>params-&gt;hdr.tag = ATAG_CMDLINE; </p><p>params-&gt;hdr.size = 8; </p><p>//console=ttyS0,115200n8 </p><p>strcpy(params-&gt;u.cmdline.cmdline, p); </p><p>params = tag_next(params); </p><p>} </p><p> </p><p> </p><p>static void setup_initrd2_tag(void) </p><p>{ </p><p>/* an ATAG_INITRD node tells the kernel where the compressed </p><p>* ramdisk can be found. ATAG_RDIMG is a better name, actually. </p><p>*/ </p><p>params-&gt;hdr.tag = ATAG_INITRD2; </p><p>params-&gt;hdr.size = tag_size(tag_initrd); </p><p>params-&gt;u.initrd.start = RAM_COMPRESSED_RAMDISK_BASE; </p><p>params-&gt;u.initrd.size = 2047;//k byte </p><p>params = tag_next(params); </p><p>} </p><p> </p><p> </p><p>static void setup_ramdisk_tag(void) </p><p>{ </p><p>/* an ATAG_RAMDISK node tells the kernel how large the </p><p>* decompressed ramdisk will become. </p><p>*/ </p><p>params-&gt;hdr.tag = ATAG_RAMDISK; </p><p>params-&gt;hdr.size = tag_size(tag_ramdisk); </p><p>params-&gt;u.ramdisk.start = RAM_DECOMPRESSED_RAMDISK_BASE; </p><p>params-&gt;u.ramdisk.size = 7.8*1024; //k byte </p><p>params-&gt;u.ramdisk.flags = 1; // automatically load ramdisk </p><p>params = tag_next(params); </p><p>} </p><p> </p><p> </p><p>static void setup_end_tag(void) </p><p>{ </p><p>params-&gt;hdr.tag = ATAG_NONE; </p><p>params-&gt;hdr.size = 0; </p><p>} void Uart_Init(int pclk,int baud)//串口是很重要的 </p><p>{ </p><p>int i; </p><p>if(pclk == 0) </p><p>pclk = PCLK; </p><p>rUFCON0 = 0x0; //UART channel 0 FIFO control register, FIFO disable </p><p>rUMCON0 = 0x0; //UART chaneel 0 MODEM control register, AFC disable </p><p> </p><p>//UART0 </p><p>rULCON0 = 0x3; //Line control register : Normal,No parity,1 stop,8 bits </p><p>下面这段samsung好象写的不太对,但是我按照Normal,No parity,1 stop,8 bits算出来的确是0x245 </p><p> </p><p>// [10] [9] [8] [7] [6] [5] [4] [3:2] [1:0] </p><p>// Clock Sel, Tx Int, Rx Int, Rx Time Out, Rx err, Loop-back, Send break, Transmit Mode, Receive Mode </p><p>// 0 1 0 , 0 1 0 0 , 01 01 </p><p>// PCLK Level Pulse Disable Generate Normal Normal Interrupt or Polling </p><p>rUCON0 = 0x245; // Control register </p><p>rUBRDIV0=( (int)(PCLK/16./ baud) -1 ); //Baud rate divisior register 0 </p><p>delay(10); </p><p>} </p><p>经过以上的折腾,接下来就是kernel的活了.能不能启动kernel,得看你编译kernel的水平了. </p><p>这个BOOTLOADER不象blob那样需要交互信息,使用虚拟地址,总的来说非常简洁明了. </p><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19978.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:39 <a href="http://www.cnitblog.com/flutist1225/articles/19978.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>什么是bootloader程序，其功能和特点</title><link>http://www.cnitblog.com/flutist1225/articles/19977.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:38:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19977.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19977.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19977.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19977.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19977.html</trackback:ping><description><![CDATA[
		<p align="left">
				<font face="腩戾,verdana, arial, helvetica" size="2">一、引言 <br /><br />     在专用的嵌入式板子运行 GNU/Linux 系统已经变得越来越流行。一个嵌入式 Linux 系统从软件的角度看通常可以分为四个层次： <br /><br />     1. 引导加载程序。包括固化在固件(firmware)中的 boot 代码(可选)，和 Boot Loader 两大部分。 <br /><br />     2. Linux 内核。特定于嵌入式板子的定制内核以及内核的启动参数。 <br /><br /><br />     3. 文件系统。包括根文件系统和建立于 Flash 内存设备之上文件系统。通常用ram disk 来作为 root fs。 <br /><br />     4. 用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有：MicroWindows 和 MiniGUI 等。 <br /><br /><br />     引导加载程序是系统加电后运行的第一段软件代码。回忆一下 PC 的体系结构我们可以知道，PC 机中的引导加载程序由 BIOS(其本质就是一段固件程序)和位于硬盘MBR中的OS Boot Loader（比如，LILO 和 GRUB 等）一起组成。BIOS 在完成硬件检测和资源分配后，将硬盘MBR中的 Boot Loader 读到系统的RAM 中，然后将控制权交给 OS Boot Loader。Boot Loader 的主要运行任务就是将内核映象从硬盘上读到RAM 中，然后跳转到内核的入口点去运行，也即开始启动操作系统。 <br /><br />     而在嵌入式系统中，通常并没有像BIOS 那样的固件程序（注，有的嵌入式 CPU 也会内嵌一段短小的启动程序），因此整个系统的加载启动任务就完全由 Boot Loader 来完成。比如在一个基于 ARM7TDMI core 的嵌入式系统中，系统在上电或复位时通常都从地址 0x00000000 处开始执行，而在这个地址处安排的通常就是系统的Boot Loader程序。 <br /><br />    本文将从 Boot Loader 的概念、Boot Loader 的主要任务、Boot Loader 的框架结构以及Boot Loader 的安装等四个方面来讨论嵌入式系统的 Boot Loader。 <br /><br />二、 Boot Loader 的概念 <br /><br />      简单地说，Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序，我们可以初始化硬件设备、建立内存空间的映射图，从而将系统的软硬件环境带到一个合适的状态，以便为最终调用操作系统内核准备好正确的环境。 <br /><br />      通常，Boot Loader 是严重地依赖于硬件而实现的，特别是在嵌入式世界。因此，在嵌入式世界里建立一个通用的 Boot Loader 几乎是不可能的。尽管如此，我们仍然可以对 Boot Loader 归纳出一些通用的概念来，以指导用户特定的 Boot Loader 设计与实现。 <br /><br /><br />      1. Boot Loader 所支持的 CPU 和嵌入式板 <br /><br />      每种不同的 CPU 体系结构都有不同的Boot Loader。有些 Boot Loader 也支持多种体系结构的 CPU，比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU的体系结构外，Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说，对于两块不同的嵌入式板而言，即使它们是基于同一种 CPU 而构建的，要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上，通常也都需要修改 Boot Loader 的源程序。 <br /><br />     2. Boot Loader 的安装媒介（Installation Medium） <br /><br />      系统加电或复位后，所有的CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如，基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x00000000 取它的第一条指令。而基于CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如：ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在系统加电后，CPU 将首先执行Boot Loader 程序。 <br /><br />      下图1就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图。 </font>
		</p>
		<p align="center">
				<font face="腩戾,verdana, arial, helvetica" size="2">
						<br />
						<br />
						<img height="112" alt="" src="file:///E:/恒丰锐科公司产品2005/三星S3C44b0x/光盘/pic/0001.gif" width="328" border="0" />
						<br />
						<br />
						<br />图1 固态存储设备的典型空间分配结构</font>
		</p>
		<p align="left">
				<font face="腩戾,verdana, arial, helvetica" size="2">
						<br />      3. 用来控制 Boot Loader 的设备或机制 <br /><br />      主机和目标机之间一般通过串口建立连接，Boot Loader 软件在执行时通常会通过串口来进行 I/O，比如：输出打印信息到串口，从串口读取用户控制字符等。 <br /><br />      4. Boot Loader 的启动过程是单阶段（Single Stage）还是多阶段（Multi-Stage） <br /><br />      通常多阶段的 Boot Loader 能提供更为复杂的功能，以及更好的可移植性。从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程，也即启动过程可以分为 stage 1和 stage 2 两部分。而至于在 stage 1 和 stage 2 具体完成哪些任务将在下面几篇讨论。 <br /><br />      5. Boot Loader 的操作模式 (Operation Mode) <br /><br />      大多数 Boot Loader 都包含两种不同的操作模式："启动加载"模式和"下载"模式，这种区别仅对于开发人员才有意义。但从最终用户的角度看，Boot Loader 的作用就是用来加载操作系统，而并不存在所谓的启动加载模式与下载工作模式的区别。 <br /><br />      启动加载（Boot loading）模式：这种模式也称为"自主"（Autonomous）模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行，整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式，因此在嵌入式产品发布的时侯，Boot Loader 显然必须工作在这种模式下。 <br /><br />      下载（Downloading）模式：在这种模式下，目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机（Host）下载文件，比如：下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中，然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用；此外，以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。 <br /><br />      像 Blob 或 U-Boot 等这样功能强大的 Boot Loader 通常同时支持这两种工作模式，而且允许用户在这两种工作模式之间进行切换。比如，Blob 在启动时处于正常的启动加载模式，但是它会延时 10 秒等待终端用户按下任意键而将 blob 切换到下载模式。如果在 10 秒内没有用户按键，则 blob 继续启动 Linux 内核。 <br /><br />      6. BootLoader 与主机之间进行文件传输所用的通信设备及协议 <br /><br />      最常见的情况就是，目标机上的 Boot Loader 通过串口与主机之间进行文件传输，传输协议通常是 xmodem／ymodem／zmodem 协议中的一种。但是，串口传输的速度是有限的，因此通过以太网连接并借助 TFTP 协议来下载文件是个更好的选择。 <br /><br />      此外，在论及这个话题时，主机方所用的软件也要考虑。比如，在通过以太网连接和 TFTP 协议来下载文件时，主机方必须有一个软件用来的提供 TFTP 服务。在讨论了 BootLoader 的上述概念后，下面我们来具体看看 BootLoader 的应该完成哪些任务。</font>
		</p>
		<p align="left">
				<span class="myp111">
						<font id="zoom">
								<strong>
										<font size="2">三、Boot Loader 的主要任务与典型结构框架</font>
								</strong>
								<font size="2">
										<br />
										<br />    在继续本节的讨论之前，首先我们做一个假定，那就是：假定内核映像与根文件系统映像都被加载到 RAM 中运行。之所以提出这样一个假设前提是因为，在嵌入式系统中内核映像与根文件系统映像也可以直接在 ROM 或 Flash 这样的固态存储设备中直接运行。但这种做法无疑是以运行速度的牺牲为代价的。从操作系统的角度看，Boot Loader 的总目标就是正确地调用内核来执行。 <br /><br />    另外，由于 Boot Loader 的实现依赖于 CPU 的体系结构，因此大多数 Boot Loader 都分为 stage1 和 stage2 两大部分。依赖于 CPU 体系结构的代码，比如设备初始化代码等，通常都放在 stage1 中，而且通常都用汇编语言来实现，以达到短小精悍的目的。而 stage2 则通常用C语言来实现，这样可以实现给复杂的功能，而且代码会具有更好的可读性和可移植性。 <br /><br />    Boot Loader 的 stage1 通常包括以下步骤(以执行的先后顺序)：<br />    ·硬件设备初始化。<br />    ·为加载 Boot Loader 的 stage2 准备 RAM 空间。<br />    ·拷贝 Boot Loader 的 stage2 到 RAM 空间中。<br />    ·设置好堆栈。<br />    ·跳转到 stage2 的 C 入口点。<br />   Boot Loader 的 stage2 通常包括以下步骤(以执行的先后顺序)：<br />    ·初始化本阶段要使用到的硬件设备。<br />    ·检测系统内存映射(memory map)。<br />    ·将 kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中。<br />    ·为内核设置启动参数。<br />    ·调用内核。 </font>
								<br /> </font>
				</span>
		</p>
		<p>
				<span class="myp111">
						<font id="zoom0">
								<font size="2">   3.1 Boot Loader 的 stage1 <br /><br />    3.1.1 基本的硬件初始化 <br /><br />    这是 Boot Loader 一开始就执行的操作，其目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境。它通常包括以下步骤（以执行的先后顺序）： <br /><br />    1．屏蔽所有的中断。为中断提供服务通常是 OS 设备驱动程序的责任，因此在 Boot Loader 的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写 CPU 的中断屏蔽寄存器或状态寄存器（比如 ARM 的 CPSR 寄存器）来完成。 <br /><br />    2．设置 CPU 的速度和时钟频率。 <br /><br />    3．RAM 初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。 <br /><br />    4．初始化 LED。典型地，通过 GPIO 来驱动 LED，其目的是表明系统的状态是 OK 还是 Error。如果板子上没有 LED，那么也可以通过初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息来完成这一点。 <br /><br />    5． 关闭 CPU 内部指令／数据 cache。 <br /><br />    3.1.2 为加载 stage2 准备 RAM 空间 <br /><br />    为了获得更快的执行速度，通常把 stage2 加载到 RAM 空间中来执行，因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。 <br /><br />    由于 stage2 通常是 C 语言执行代码，因此在考虑空间大小时，除了 stage2 可执行映象的大小外，还必须把堆栈空间也考虑进来。此外，空间大小最好是 memory page 大小(通常是 4KB)的倍数。一般而言，1M 的 RAM 空间已经足够了。具体的地址范围可以任意安排，比如 blob 就将它的 stage2 可执行映像安排到从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行。但是，将 stage2 安排到整个 RAM 空间的最顶 1MB(也即(RamEnd-1MB) - RamEnd)是一种值得推荐的方法。 <br /><br />    为了后面的叙述方便，这里把所安排的 RAM 空间范围的大小记为：stage2_size(字节)，把起始地址和终止地址分别记为：stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此： <br /><br /> </font>
								<ccid_nobr>
								</ccid_nobr>
						</font>
				</span>
		</p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>
												<font size="2">stage2_end＝stage2_start＋stage2_size</font>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font size="2">
						<br />
						<br />    另外，还必须确保所安排的地址范围的的确确是可读写的 RAM 空间，因此，必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于 blob 的方法，也即：以 memory page 为被测试单位，测试每个 memory page 开始的两个字是否是可读写的。为了后面叙述的方便，我们记这个检测算法为：test_mempage，其具体步骤如下： <br /><br />    1．先保存 memory page 一开始两个字的内容。 <br /><br />    2．向这两个字中写入任意的数字。比如：向第一个字写入 0x55，第 2 个字写入 0xaa。 <br /><br />    3．然后，立即将这两个字的内容读回。显然，我们读到的内容应该分别是 0x55 和 0xaa。如果不是，则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。 <br /><br />    4．再向这两个字中写入任意的数字。比如：向第一个字写入 0xaa，第 2 个字中写入 0x55。 <br /><br />    5．然后，立即将这两个字的内容立即读回。显然，我们读到的内容应该分别是 0xaa 和 0x55。如果不是，则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。 <br /><br />    6．恢复这两个字的原始内容。测试完毕。 <br /><br />    为了得到一段干净的 RAM 空间范围，我们也可以将所安排的 RAM 空间范围进行清零操作。 <br /><br />    3.1.3 拷贝 stage2 到 RAM 中 <br /><br />拷贝时要确定两点：(1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址；(2) RAM 空间的起始地址。 <br /><br />    3.1.4 设置堆栈指针 sp <br /><br />    堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4)，也即在 3.1.2 节所安排的那个 1MB 的 RAM 空间的最顶端(堆栈向下生长)。此外，在设置堆栈指针 sp 之前，也可以关闭 led 灯，以提示用户我们准备跳转到 stage2。经过上述这些执行步骤后，系统的物理内存布局应该如下图2所示。 <br /><br />    3.1.5 跳转到 stage2 的 C 入口点 <br /><br />    在上述一切都就绪后，就可以跳转到 Boot Loader 的 stage2 去执行了。比如，在 ARM 系统中，这可以通过修改 PC 寄存器为合适的地址来实现。 <br /><br /> </font>
		</p>
		<center>
				<p>
						<font size="2">
								<img height="475" alt="" src="file:///E:/恒丰锐科公司产品2005/三星S3C44b0x/光盘/main/268047.gif" width="609" />
								<br />
								<br />图2 bootloader 的 stage2 可执行映象刚被拷贝到 RAM 空间时的系统内存布局</font>
				</p>
		</center>
		<p>
				<font size="2">
						<br />
						<br />    3.2 Boot Loader 的 stage2 <br /><br />    正如前面所说，stage2 的代码通常用 C 语言来实现，以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通 C 语言应用程序不同的是，在编译和链接 boot loader 这样的程序时，我们不能使用 glibc 库中的任何支持函数。其原因是显而易见的。这就给我们带来一个问题，那就是从那里跳转进 main() 函数呢？直接把 main() 函数的起始地址作为整个 stage2 执行映像的入口点或许是最直接的想法。但是这样做有两个缺点：1)无法通过main() 函数传递函数参数；2)无法处理 main() 函数返回的情况。一种更为巧妙的方法是利用 trampoline(弹簧床)的概念。也即，用汇编语言写一段trampoline 小程序，并将这段 trampoline 小程序来作为 stage2 可执行映象的执行入口点。然后我们可以在 trampoline 汇编小程序中用 CPU 跳转指令跳入 main() 函数中去执行；而当 main() 函数返回时，CPU 执行路径显然再次回到我们的 trampoline 程序。简而言之，这种方法的思想就是：用这段 trampoline 小程序来作为 main() 函数的外部包裹(external wrapper)。 <br /><br />    下面给出一个简单的 trampoline 程序示例(来自blob)： <br /><br /> </font>
				<ccid_nobr>
				</ccid_nobr>
		</p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>
												<font size="2">.text.globl _trampoline_trampoline:	bl	main	/* if main ever returns we just call it again */	b	_trampoline</font>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font size="2">
						<br />
						<br />    可以看出，当 main() 函数返回后，我们又用一条跳转指令重新执行 trampoline 程序――当然也就重新执行 main() 函数，这也就是 trampoline(弹簧床)一词的意思所在。 <br /><br />    3.2.1初始化本阶段要使用到的硬件设备 <br /><br />这通常包括：（1）初始化至少一个串口，以便和终端用户进行 I/O 输出信息；（2）初始化计时器等。在初始化这些设备之前，也可以重新把 LED 灯点亮，以表明我们已经进入 main() 函数执行。 <br /><br />设备初始化完成后，可以输出一些打印信息，程序名字字符串、版本号等。 <br /><br />    3.2.2 检测系统的内存映射（memory map） <br /><br />    所谓内存映射就是指在整个 4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如，在 SA-1100 CPU 中，从 0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间，而在 Samsung S3C44B0X CPU 中，从 0x0c00,0000 到 0x1000,0000 之间的 64M 地址空间被用作系统的 RAM 地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM，但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说，具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上，而让剩下的那部分预留 RAM 地址空间处于未使用状态。由于上述这个事实，因此 Boot Loader 的 stage2 必须在它想干点什么 (比如，将存储在 flash 上的内核映像读到 RAM 空间中) 之前检测整个系统的内存映射情况，也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元，哪些是处于 "unused" 状态的。 <br /><br />   （1) 内存映射的描述 <br /><br />    可以用如下数据结构来描述 RAM 地址空间中的一段连续(continuous)的地址范围： <br /><br /> </font>
				<ccid_nobr>
				</ccid_nobr>
		</p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>
												<font size="2">typedef struct memory_area_struct {	u32 start; /* the base address of the memory region */	u32 size; /* the byte number of the memory region */	int used;} memory_area_t;</font>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font size="2">
						<br />
						<br />    这段 RAM 地址空间中的连续地址范围可以处于两种状态之一：(1)used=1，则说明这段连续的地址范围已被实现，也即真正地被映射到 RAM 单元上。(2)used=0，则说明这段连续的地址范围并未被系统所实现，而是处于未使用状态。 <br /><br />    基于上述 memory_area_t 数据结构，整个 CPU 预留的 RAM 地址空间可以用一个 memory_area_t 类型的数组来表示，如下所示： <br /><br /> </font>
				<ccid_nobr>
				</ccid_nobr>
		</p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>
												<font size="2">memory_area_t memory_map[NUM_MEM_AREAS] = {	[0 ... (NUM_MEM_AREAS - 1)] = {		.start = 0,		.size = 0,		.used = 0	},};</font>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font size="2">
						<br />
						<br />    (2) 内存映射的检测 <br /><br />    下面我们给出一个可用来检测整个 RAM 地址空间内存映射情况的简单而有效的算法： <br /><br /> </font>
				<ccid_nobr>
				</ccid_nobr>
		</p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>
												<font size="2">/* 数组初始化 */for(i = 0; i &lt; NUM_MEM_AREAS; i++)	memory_map[i].used = 0;/* first write a 0 to all memory locations */for(addr = MEM_START; addr &lt; MEM_END; addr += PAGE_SIZE)	* (u32 *)addr = 0;for(i = 0, addr = MEM_START; addr &lt; MEM_END; addr += PAGE_SIZE) {     /*      * 检测从基地址 MEM_START+i*PAGE_SIZE 开始,大小为* PAGE_SIZE 的地址空间是否是有效的RAM地址空间。      */     调用3.1.2节中的算法test_mempage()；     if ( current memory page isnot a valid ram page) {		/* no RAM here */		if(memory_map[i].used )			i++;		continue;	}		/*	 * 当前页已经是一个被映射到 RAM 的有效地址范围	 * 但是还要看看当前页是否只是 4GB 地址空间中某个地址页的别名？	 */	if(* (u32 *)addr != 0) { /* alias? */		/* 这个内存页是 4GB 地址空间中某个地址页的别名 */		if ( memory_map[i].used )			i++;		continue;	}		/*	 * 当前页已经是一个被映射到 RAM 的有效地址范围	 * 而且它也不是 4GB 地址空间中某个地址页的别名。	 */	if (memory_map[i].used == 0) {		memory_map[i].start = addr;		memory_map[i].size = PAGE_SIZE;		memory_map[i].used = 1;	} else {		memory_map[i].size += PAGE_SIZE;	}} /* end of for (…) */</font>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font size="2">
						<br />
						<br />    在用上述算法检测完系统的内存映射情况后，Boot Loader 也可以将内存映射的详细信息打印到串口。 <br /><br />    3.2.3 加载内核映像和根文件系统映像 <br /><br />    (1) 规划内存占用的布局 <br /><br />    这里包括两个方面：(1)内核映像所占用的内存范围；（2）根文件系统所占用的内存范围。在规划内存占用的布局时，主要考虑基地址和映像的大小两个方面。 <br /><br />    对于内核映像，一般将其拷贝到从(MEM_START＋0x8000) 这个基地址开始的大约1MB大小的内存范围内(嵌入式 Linux 的内核一般都不操过 1MB)。为什么要把从 MEM_START 到 MEM_START＋0x8000 这段 32KB 大小的内存空出来呢？这是因为 Linux 内核要在这段内存中放置一些全局数据结构，如：启动参数和内核页表等信息。 <br /><br />    而对于根文件系统映像，则一般将其拷贝到 MEM_START+0x0010,0000 开始的地方。如果用 Ramdisk 作为根文件系统映像，则其解压后的大小一般是1MB。 <br /><br />   （2）从 Flash 上拷贝 <br /><br />    由于像 ARM 这样的嵌入式 CPU 通常都是在统一的内存地址空间中寻址 Flash 等固态存储设备的，因此从 Flash 上读取数据与从 RAM 单元中读取数据并没有什么不同。用一个简单的循环就可以完成从 Flash 设备上拷贝映像的工作： <br /><br /> </font>
				<ccid_nobr>
				</ccid_nobr>
		</p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>
												<font size="2">while(count) {	*dest++ = *src++; /* they are all aligned with word boundary */	count -= 4; /* byte number */};</font>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font size="2">
						<br />
						<br />     3.2.4 设置内核的启动参数 <br /><br />     应该说，在将内核映像和根文件系统映像拷贝到 RAM 空间中后，就可以准备启动 Linux 内核了。但是在调用内核之前，应该作一步准备工作，即：设置 Linux 内核的启动参数。 <br /><br />     Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始，以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中： <br /><br /> </font>
				<ccid_nobr>
				</ccid_nobr>
		</p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>
												<font size="2">/* The list ends with an ATAG_NONE node. */#define ATAG_NONE	0x00000000struct tag_header {	u32 size; /* 注意，这里size是字数为单位的 */	u32 tag;};……struct tag {	struct tag_header hdr;	union {		struct tag_core		core;		struct tag_mem32	mem;		struct tag_videotext	videotext;		struct tag_ramdisk	ramdisk;		struct tag_initrd	initrd;		struct tag_serialnr	serialnr;		struct tag_revision	revision;		struct tag_videolfb	videolfb;		struct tag_cmdline	cmdline;		/*		 * Acorn specific		 */		struct tag_acorn	acorn;		/*		 * DC21285 specific		 */		struct tag_memclk	memclk;	} u;};</font>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font size="2">
						<br />
				</font>
				<br />      在嵌入式 Linux 系统中，通常需要由 Boot Loader 设置的常见启动参数有：ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。比如，设置 ATAG_CORE 的代码如下： <br /><br /> <ccid_nobr></ccid_nobr></p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>params = (struct tag *)BOOT_PARAMS;	params-&gt;hdr.tag = ATAG_CORE;	params-&gt;hdr.size = tag_size(tag_core);	params-&gt;u.core.flags = 0;	params-&gt;u.core.pagesize = 0;	params-&gt;u.core.rootdev = 0;	params = tag_next(params);</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<br />
				<br />     其中，BOOT_PARAMS 表示内核启动参数在内存中的起始基地址，指针 params 是一个 struct tag 类型的指针。宏 tag_next() 将以指向当前标记的指针为参数，计算紧临当前标记的下一个标记的起始地址。注意，内核的根文件系统所在的设备ID就是在这里设置的。 <br /><br />下面是设置内存映射情况的示例代码： <br /><br /> <ccid_nobr></ccid_nobr></p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>for(i = 0; i &lt; NUM_MEM_AREAS; i++) {		if(memory_map[i].used) {			params-&gt;hdr.tag = ATAG_MEM;			params-&gt;hdr.size = tag_size(tag_mem32);			params-&gt;u.mem.start = memory_map[i].start;			params-&gt;u.mem.size = memory_map[i].size;						params = tag_next(params);		}}</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<br />
				<br />     可以看出，在 memory_map［］数组中，每一个有效的内存段都对应一个 ATAG_MEM 参数标记。 <br /><br />     Linux 内核在启动时可以以命令行参数的形式来接收信息，利用这一点我们可以向内核提供那些内核不能自己检测的硬件参数信息，或者重载(override)内核自己检测到的信息。比如，我们用这样一个命令行参数字符串"console=ttyS0,115200n8"来通知内核以 ttyS0 作为控制台，且串口采用 "115200bps、无奇偶校验、8位数据位"这样的设置。下面是一段设置调用内核命令行参数字符串的示例代码： <br /><br /> <ccid_nobr></ccid_nobr></p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>char *p;	/* eat leading white space */	for(p = commandline; *p == ' '; p++)		;	/* skip non-existent command lines so the kernel will still    * use its default command line.	 */	if(*p == '\0')		return;	params-&gt;hdr.tag = ATAG_CMDLINE;	params-&gt;hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) &gt;&gt; 2;	strcpy(params-&gt;u.cmdline.cmdline, p);	params = tag_next(params);</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<br />
				<br />     请注意在上述代码中，设置 tag_header 的大小时，必须包括字符串的终止符'\0'，此外还要将字节数向上圆整4个字节，因为 tag_header 结构中的size 成员表示的是字数。 <br /><br />     下面是设置 ATAG_INITRD 的示例代码，它告诉内核在 RAM 中的什么地方可以找到 initrd 映象(压缩格式)以及它的大小： <br /><br /> <ccid_nobr></ccid_nobr></p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>params-&gt;hdr.tag = ATAG_INITRD2;params-&gt;hdr.size = tag_size(tag_initrd);params-&gt;u.initrd.start = RAMDISK_RAM_BASE;params-&gt;u.initrd.size = INITRD_LEN;	params = tag_next(params);</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<br />
				<br />     下面是设置 ATAG_RAMDISK 的示例代码，它告诉内核解压后的 Ramdisk 有多大（单位是KB）： <br /><br /> <ccid_nobr></ccid_nobr></p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>params-&gt;hdr.tag = ATAG_RAMDISK;params-&gt;hdr.size = tag_size(tag_ramdisk);	params-&gt;u.ramdisk.start = 0;params-&gt;u.ramdisk.size = RAMDISK_SIZE; /* 请注意，单位是KB */params-&gt;u.ramdisk.flags = 1; /* automatically load ramdisk */	params = tag_next(params);</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<br />
				<br />     最后，设置 ATAG_NONE 标记，结束整个启动参数列表： <br /><br /> <ccid_nobr></ccid_nobr></p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>static void setup_end_tag(void){	params-&gt;hdr.tag = ATAG_NONE;	params-&gt;hdr.size = 0;}</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<br />
				<br />    3.2.5 调用内核 <br /><br />     Boot Loader 调用 Linux 内核的方法是直接跳转到内核的第一条指令处，也即直接跳转到 MEM_START＋0x8000 地址处。在跳转时，下列条件要满足： <br /><br />    1． CPU 寄存器的设置：<br />        ·R0＝0；<br />         @R1＝机器类型 ID；关于 Machine Type Number，可以参见 linux/arch/arm/tools/mach-types。<br />         @R2＝启动参数标记列表在 RAM 中起始基地址； <br /><br />    2． CPU 模式：<br />        ·必须禁止中断（IRQs和FIQs）；<br />        ·CPU 必须 SVC 模式； <br /><br />    3． Cache 和 MMU 的设置：<br />        ·MMU 必须关闭；<br />        ·指令 Cache 可以打开也可以关闭；<br />        ·数据 Cache 必须关闭； <br /><br />     如果用 C 语言，可以像下列示例代码这样来调用内核： <br /><br /> <ccid_nobr></ccid_nobr></p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>void (*theKernel)(int zero, int arch, u32 params_addr)             = (void (*)(int, int, u32))KERNEL_RAM_BASE;……theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<br />
				<br />     注意，theKernel()函数调用应该永远不返回的。如果这个调用返回，则说明出错。</p>
		<p>
				<font id="zoom1">
						<strong>
								<font size="2">   四、 关于串口终端</font>
						</strong>
						<font size="2">
								<br />
								<br />    在 boot loader 程序的设计与实现中，没有什么能够比从串口终端正确地收到打印信息能更令人激动了。此外，向串口终端打印信息也是一个非常重要而又有效的调试手段。但是，我们经常会碰到串口终端显示乱码或根本没有显示的问题。造成这个问题主要有两种原因：(1) boot loader 对串口的初始化设置不正确。(2) 运行在 host 端的终端仿真程序对串口的设置不正确，这包括：波特率、奇偶校验、数据位和停止位等方面的设置。 <br /><br />    此外，有时也会碰到这样的问题，那就是：在 boot loader 的运行过程中我们可以正确地向串口终端输出信息，但当 boot loader 启动内核后却无法看到内核的启动输出信息。对这一问题的原因可以从以下几个方面来考虑： <br /><br />    (1) 首先请确认你的内核在编译时配置了对串口终端的支持，并配置了正确的串口驱动程序。 <br /><br />    (2) 你的 boot loader 对串口的初始化设置可能会和内核对串口的初始化设置不一致。此外，对于诸如 s3c44b0x 这样的 CPU，CPU 时钟频率的设置也会影响串口，因此如果 boot loader 和内核对其 CPU 时钟频率的设置不一致，也会使串口终端无法正确显示信息。 <br /><br />    (3) 最后，还要确认 boot loader 所用的内核基地址必须和内核映像在编译时所用的运行基地址一致，尤其是对于 uClinux 而言。假设你的内核映像在编译时用的基地址是 0xc0008000，但你的 boot loader 却将它加载到 0xc0010000 处去执行，那么内核映像当然不能正确地执行了。 <br /><br /><strong>   五、 结束语</strong><br /><br />Boot Loader 的设计与实现是一个非常复杂的过程。如果不能从串口收到那激动人心的内核启动信息，恐怕谁也不能说："嗨，我的 boot loader 已经成功地转起来了！"。  <br /> </font>
						<ccid_nobr>
						</ccid_nobr>
				</font>
		</p>
		<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1">
				<tbody>
						<tr>
								<td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6">
										<pre>
												<ccid_code>
												</ccid_code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.cnitblog.com/flutist1225/aggbug/19977.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:38 <a href="http://www.cnitblog.com/flutist1225/articles/19977.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Bash readline 使用技巧 </title><link>http://www.cnitblog.com/flutist1225/articles/19976.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:36:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19976.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19976.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19976.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19976.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19976.html</trackback:ping><description><![CDATA[很多人会用 Bash，但是很少有人知道 readline 是怎么回事。readline 是一个强大的库，只要使用了它的程序，都可以用同一个配置文件配置，而且用同样的方法操作命令行，让你可以方便的编辑命令行。 <br /><br />使用 readline 的程序现在主要有 Bash, GDB，ftp 等。readline 付予这些程序强大的 Emacs 似的命令行编辑方式，你可以随意绑定你的键盘。 <br /><br />术语解释<br />在下文中，我们经常提到 'C-x r' 这类键操作。'C-x r' 其实就是按Ctrl-x，然后按 r。同理 'C-M-@' 就是按 ctrl-alt-@(M表示meta, 在 PC 上就是 Alt 键)，但是其实 @ 是shift-2 (看看你的键盘)。所以 'C-M-@' 实际上要你按 ctrl-alt-shift-2。 <br /><br />但是在配置文件里的键序列中，我们把 'C-x r' 表示为 '\C-xr', 把 'C-M-@' 表示为 '\C-\M-@'，你自己看看就知道怎么回事了。同理 'Esc a' 别表示成 '\ea'。 <br /><br />这就是 Emacs 里的按键的通常标记方法。EMACS = Esc Meta Alt Ctrl Shift :) <br /><br />技巧篇<br />在自己配置命令行之前，我们先来看看利用缺省的键绑定能够进行的一些巧妙的用法： <br /><br />第一招：使用以前的命令行参数<br />你是否经常出现这种情况？你想把 ~/text-browser/ 目录下的3个.tar.gz文件搬到/usr3/software/，于是你输入： <br /><br />$mv ~/text-browser/*.tar.gz /usr3/software/<br /><br />我想你一定已经知道，打入 ~/text 之后按 TAB 就可以补全text-browser这个长文件名吧？这是Bash 的基本功能。我废话？好了，就当你知道吧。不过今天我要讲的东西比这个复杂一些。 <br /><br />Go on! 刚刚输入到这里，你突然想起，应该在 /usr3/software/ 下先建立一个目录叫browsers，这样放进去的文件比较好管理。 <br /><br />于是你 Ctrl-u，删掉了这行命令。唉呀，这么长的命令一下就没了。是不是有点可惜？这还不算麻烦。然后你 <br /><br />mkdir /usr3/software/browser<br />mv ~/text-browser/*.tar.gz /usr3/software/browser<br /><br />嗯。TAB 是帮了你不少忙。可是你实际上有更好的办法来完成这项工作。好吧，看看 readline 怎样神奇的完成你的任务： <br />我们回到这种情况： <br /><br />$mv ~/text-browser/*.tar.gz /usr3/software/<br /><br />你刚才是按了 Ctrl-u 删除了所有输入的东西。可惜啊！你要是按 M-#(也就是按住 PC 机的 Alt 键，再按 #，实际上就是 Alt-Shift-3)，那么 Bash 就会在这样最开头插入一个 '#'，然后输入这行。这样命令就被作为一行注释载入了历史。 <br />这有什么好处？这样你的这行命令里的内容就可以被再次利用。看着：你接着输入： <br /><br />mkdir ...<br /><br />等等，你是不是想输入 /usr3/software/？你不用再敲一遍了！直接按 M-.(Alt 加句号)，看看， /usr3/software/ 是不是出现在命令行上了？M-. 就是调用了 yank-last-arg 函数，把上一条命令的最后一个参数放在命令行上。好了，回车吧！ <br />你接着输入： <br /><br />mv ...<br /><br />等等，这下是该输入 ~/text-browser/*.tar.gz 了。烦不烦啊？换一种方式吧。请按：'M-1 M-.'(把上一条命令的第一个参数放在命令行上)。这样命令行成为了： <br />mv /usr3/software/browser<br /><br />怎么成这样了？看看你的“上一条命令”是什么吧？是……你自己看。所以这个参数不是你想要的。那么继续再按一次 'M-.'。看到了吧？你的命令行已经成为： <br />mv ~/text-browser/*.tar.gz<br /><br />好。打一个空格。再按一下 'M-.'。命令行变成了： <br />mv ~/text-browser/*.tar.gz /usr3/software/browser<br /><br />这就是你想要的！ <br />是不是看起来你还是花了不少工夫？但是想一想，如果你是要执行这样一个命令呢？ <br /><br />mv /data/ftp/pub/TUG/texmf/tex/latex/CJK/GB/GB.cap \<br />/usr/local/texlive/texmf-local/tex/latex/CJK/GB/<br /><br />嗯。记住这个有用命令：M-. ， 它的前面可以用 M-0, ... 作为数字参数。 <br />第二招：补全命令名，文件名和变量名<br />你知道 TAB 可以补全命令行上很多东西。可是你遇到这种情况的时候怎么办？ <br /><br />man a-very-very-long-command-name<br /><br />你输入了 man a-ver... 之后，按 TAB，什么反应也没有。因为 TAB 执行的是 “按情况补全”(complete)，它看到 man，知道这应该是一个命令，那么它认为： “后面应该是一个文件名参数。” 但是你想要的是命令的名字怎么办？答案：按 'M-!'. <br />再来看：你需要设置 XMODIFIERS='@im=fcitx'。你输入了 <br /><br />export XM...<br /><br />按 TAB? 没有反应。为什么呢？因为 TAB 的补全想要一个文件名，而当前目录没有开头是 'XM...' 的文件。那么你怎么补全？答案：'M-$'。 <br />其实 readline 的补全方式被 Bash 扩充了很多。看看有多少吧！ <br /><br />'TAB': complete<br />'\M-!': complete-command<br />'\M-/': complete-filename<br />'\M-@': complete-hostname<br />'\M-~': complete-username<br />'\M-$': complete-variable<br /><br />自己试试吧！ <br />第三招：扩展命令行<br />你的一个目录里有很多类似的文件，名字叫 T12.txt, T12.log, T23.txt, T23.log, T13.txt, T13.log…… 有后缀 txt 的，也有后缀 log 的。... 你想把其中的某些 T*.txt 都移动到另外一个目录，而T*.log都不动。但是T*.txt 也不是全部都要移动。所以你想把T*.txt 都放在命令行上，然后选择其中一些。你输入： <br /><br />mv T...<br /><br />接着按 'M-*'(insert-completions)。结果 T 开头的文件都被放到命令行上了。嗯。这在某些时候是有用的，可是现在它把 T*.log 的文件也放上去了。不行。我们于是继续输入： <br />mv T*.txt <br />好了，现在我们可以使用 'C-x*'(先按ctrl-x，然后按*)。结果所有名字T*.txt 的文件都被放到了命令行上面。'C-x*' 执行的函数叫做 glob-expand-word. <br />配置篇<br />你是不是觉得那些命令很难记住？不顺手？别怕！它们都是可以改变的，就像Emacs的键绑定那样，可以被任意的改变！ <br /><br />所有使用readline的程序，都使用一个配置文件来决定它的行为和键绑定。这个文件一般是 INPUTRC 环境变量确定的。如果这个环境变量没有值，那么缺省使用 ~/.inputrc。 <br /><br />~/.inputrc 文件很简单，只有4种语句： <br /><br />注释 <br />变量设置语句(set variable value) <br />键绑定('keyseq':function) <br />条件语句($if ... $endif) <br />我们先不说其它的，先来看看键绑定吧！ <br />键绑定<br />绑定语句。 <br />你现在就可以动手设置你喜欢的控制方式。比如，我发现有些时候我需要在命令行上做上 mark(Emacs 术语)，然后把mark 和光标之间的 region(Emacs术语) 删掉，这个操作在 Emacs 里叫做kill-region. 但是我们发现这个函数在 Bash 里缺省是没有绑定的。如果我希望得到跟 Emacs 一样的绑定 C-w 的话，就把这行插入到 ~/.inputrc: <br /><br />'\C-w':kill-region<br /><br />使绑定生效。为了使这个键绑定生效，你需要执行 re-read-init-file 函数。这个函数缺省绑定在了 'C-x C-r'。你修改 ~/.inputrc 之后在 Bash 里输入 'C-x C-r' 就可以使新的配置生效了。 <br />列出可用的函数。 <br />不过你怎么知道那些函数可以被绑定呢？readline 的 info 页列出了很多函数，可是你不会每次都去info里查询吧，很麻烦啊。其实你可以使用bash的 bind 命令来得到所有的键绑定: <br /><br />$bind -p<br /><br />可以显示所有现有的已经绑定和没有绑定的函数。没有被绑定的函数被显示为 '(not bound)'，并被加上了注释。就像这样： <br />'\C-g': abort<br />'\C-x\C-g': abort<br />'\M-\C-g': abort<br />'\C-j': accept-line<br />'\C-m': accept-line<br /># alias-expand-line (not bound)<br /># arrow-key-prefix (not bound)<br /># backward-byte (not bound)<br />'\C-b': backward-char<br />'\M-OD': backward-char<br />'\C-h': backward-delete-char<br />'\C-?': backward-delete-char<br /><br />你可以把这个命令的输出作为一个模板，嵌入到 ~/.inputrc 文件。把你喜欢的函数绑定到方便的按键。 <br />其实 readline 有三个函数可以让你方便的查询函数，变量和宏的绑定情况，它们是： <br /><br />dump-functions<br />dump-variables<br />dump-macros<br /><br />可是它们缺省都没有被绑定到任何按键。你可以为它们分别设置类似 'C-xf', 'C-xv', 'C-xm' 这样容易记忆的绑定。 <br />如果忘了绑定…… <br />这样你就可以设置你需要的绑定啦！但是你还是有可能在需要的时候突然记不起哪些键绑定可以补全。这时候你输入： <br /><br />$bind -p | grep compl<br /><br />得到结果： <br />'\C-i': complete<br />'\M-\e': complete<br />'TAB': complete<br />'\M-!': complete-command<br />'\M-/': complete-filename<br />'\M-@': complete-hostname<br />'\M-{': complete-into-braces<br />'\M-~': complete-username<br />'\M-$': complete-variable<br />'\M-\C-i': dynamic-complete-history<br />'\M-g': glob-complete-word<br />'\M-*': insert-completions<br />.......<br /><br />这样你记不住一个键的时候就可以方便的查询，这样几次之后，你就会把自己需要的按键都记住了。 <br />配置变量<br />体验： <br />Bash 的 readline 有一些变量可以控制它的行为。比如: <br /><br />bell-style 可以控制出错时是 audible(发出响声)，visible(闪动屏幕)，还是none(什么都不做)；editing-mode 可以控制你是用 Emacs 的输入方式还是用 vi 的； <br /><br />completion-query-times 的值控制在补全的个数超过多少N时，bash 提示： “Display all N possibilities? (y or n)”； <br /><br />如果我设置 expand-tilde 为 on，当输入“ls ~/doc”，按 TAB 时，命令行会自动变成 'ls /home/wy/doc'. <br /><br />如果把 visible-stats 设置为 on，那么列出补全的时候，目录，可执行文件，符号连接，会被分别使用 /, *, @ 来标记，就像 ls -F 的到的结果。 <br /><br />设置： <br />设置的方法极其简单，就在 ~/.inputrc 文件里写入类似语句： <br /><br />set visible-stats on<br /><br />然后 'C-x C-r' 使设置生效。 <br />怎样知道有哪些设置？ <br />可以设置的参数是很多的。使用命令 <br /><br />$bind -v<br /><br />就可以得到所有这些可以设置的变量和它们的值了。<br /><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19976.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:36 <a href="http://www.cnitblog.com/flutist1225/articles/19976.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NFS全攻略 </title><link>http://www.cnitblog.com/flutist1225/articles/19975.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:34:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19975.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19975.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19975.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19975.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19975.html</trackback:ping><description><![CDATA[一、NFS简介  <br />NFS-Network FileSystem的缩写，NFS是由Sun开发并发展起来的一项用于在不同机器，不同操作系统之间通过网络互相分享各自的文件。NFS server也可以看作是一个FILE SERVER,它可以让你的PC通过网络将远端得NFS SERVER共享出来的档案MOUNT到自己的系统中，在CLIENT看来使用NFS的远端文件就象是在使用本地文件一样。  <br />NFS协议从诞生到现在为止，已经有多个版本，如NFS V2（rfc1094）,NFS V3（rfc1813）（最新的版本是V4（rfc3010）。  <br />二、各NFS协议版本的主要区别  <br />V3相对V2的主要区别：  <br />1、文件尺寸  <br />V2最大只支持32BIT的文件大小(4G),而NFS V3新增加了支持64BIT文件大小的技术。  <br />2、文件传输尺寸  <br />V3没有限定传输尺寸，V2最多只能设定为8k，可以使用-rsize and -wsize 来进行设定。  <br />3、完整的信息返回  <br />V3增加和完善了许多错误和成功信息的返回，对于服务器的设置和管理能带来很大好处。  <br />4、增加了对TCP传输协议的支持  <br />V2只提供了对UDP协议的支持，在一些高要求的网络环境中有很大限制，V3增加了对TCP协议的支持  <br />*5、异步写入特性  <br />6、改进了SERVER的mount性能  <br />7、有更好的I/O WRITES 性能。  <br />9、更强网络运行效能，使得网络运作更为有效。  <br />10、更强的灾难恢复功能。  <br /><br />异步写入特性（v3新增加）介绍：  <br />NFS V3 能否使用异步写入，这是可选择的一种特性。NFS V3客户端发发送一个异步写入请求到服务器，在给客户端答复之前服务器并不是必须要将数据写入到存储器中（稳定的）。服务器能确定何时去写入数据或者将多个写入请求聚合到一起并加以处理，然后写入。客户端能保持一个数据的copy以防万一服务器不能完整的将数据写入。当客户端希望释放这个copy的时候，它会向服务器通过这个操作过程，以确保每个操作步骤的完整。异步写入能够使服务器去确定最好的同步数据的策略。使数据能尽可能的同步的提交何到达。与V2比较来看，这样的机制能更好的实现数据缓冲和更多的平行（平衡）。而NFS V2的SERVER在将数据写入存储器之前不能再相应任何的写入请求。  <br /><br />V4相对V3的改进：  <br />1：改进了INTERNET上的存取和执行效能  <br />2：在协议中增强了安全方面的特性  <br />3：增强的跨平台特性  <br />三、CLIENT和SERVER的具体操作和设置  <br />在讲NFS SERVER的运作之前先来看一些与NFS SERVER有关的东西：  <br />RPC（Remote Procedure Call）  <br />NFS本身是没有提供信息传输的协议和功能的，但NFS却能让我们通过网络进行资料的分享，这是因为NFS使用了一些其它的传输协议。而这些传输协议勇士用到这个RPC功能的。可以说NFS本身就是使用RPC的一个程序。或者说NFS也是一个RPC SERVER.所以只要用到NFS的地方都要启动RPC服务，不论是NFS SERVER或者NFS CLIENT。这样SERVER和CLIENT才能通过RPC来实现PROGRAM PORT的对应。可以这么理解RPC和NFS的关系：NFS是一个文件系统，而RPC是负责负责信息的传输。  <br /><br />NFS需要启动的DAEMONS  <br />pc.nfsd:主要复杂登陆权限检测等。  <br />rpc.mountd：负责NFS的档案系统，当CLIENT端通过rpc.nfsd登陆SERVER后，对clinet存取server的文件进行一系列的管理  <br />NFS SERVER在REDHAT LINUX平台下一共需要两个套件：nfs-utils和PORTMAP  <br />nfs-utils：提供rpc.nfsd 及 rpc.mountd这两个NFS DAEMONS的套件  <br />portmap:NFS其实可以被看作是一个RPC SERVER PROGRAM,而要启动一个RPC SERVER PROGRAM，都要做好PORT的对应工作，而且这样的任务就是由PORTMAP来完成的。通俗的说PortMap就是用来做PORT的mapping的。  <br /><br />一：服务器端的设定（以LINUX为例）  <br />服务器端的设定都是在/etc/exports这个文件中进行设定的，设定格式如下：  <br />欲分享出去的目录 主机名称1或者IP1(参数1，参数2） 主机名称2或者IP2（参数3，参数4）  <br />上面这个格式表示，同一个目录分享给两个不同的主机，但提供给这两台主机的权限和参数是不同的，所以分别设定两个主机得到的权限。  <br />可以设定的参数主要有以下这些：  <br />rw：可读写的权限；  <br />ro：只读的权限；  <br />no_root_squash：登入到NFS主机的用户如果是ROOT用户，他就拥有ROOT的权限，此参数很不安全，建议不要使用。  <br />root_squash：在登入 NFS 主機使用分享之目錄的使用者如果是 root 時，那麼這個使用者的權限將被壓縮成為匿名使用者，通常他的 UID 與 GID 都會變成 nobody 那個身份；  <br />all_squash：不管登陆NFS主机的用户是什么都会被重新设定为nobody。  <br />anonuid：将登入NFS主机的用户都设定成指定的user id,此ID必须存在于/etc/passwd中。  <br />anongid：同 anonuid ，但是變成 group ID 就是了！  <br />sync：资料同步写入存储器中。  <br />async：资料会先暂时存放在内存中，不会直接写入硬盘。  <br />insecure 允许从这台机器过来的非授权访问。  <br /><br />例如可以编辑/etc/exports为：  <br />/tmp　　　　　*(rw,no_root_squash)  <br />/home/public　192.168.0.*(rw)　　 *(ro)  <br />/home/test　　192.168.0.100(rw)  <br />/home/linux　 *.the9.com(rw,all_squash,anonuid=40,anongid=40)  <br />设定好后可以使用以下命令启动NFS:  <br />/etc/rc.d/init.d/portmap start (在REDHAT中PORTMAP是默认启动的）  <br />/etc/rc.d/init.d/nfs start  <br /><br />exportfs命令：  <br />如果我们在启动了NFS之后又修改了/etc/exports，是不是还要重新启动nfs呢？这个时候我们就可以用exportfs命令来使改动立刻生效，该命令格式如下：  <br />exportfs [-aruv]  <br />-a ：全部mount或者unmount /etc/exports中的内容  <br />-r ：重新mount /etc/exports中分享出来的目录  <br />-u ：umount 目录  <br />-v ：在 export 的時候，将详细的信息输出到屏幕上。  <br />具体例子：  <br />[root @test root]# exportfs -rv &lt;==全部重新 export 一次！  <br />exporting 192.168.0.100:/home/test  <br />exporting 192.168.0.*:/home/public  <br />exporting *.the9.com:/home/linux  <br />exporting *:/home/public  <br />exporting *:/tmp  <br />reexporting 192.168.0.100:/home/test to kernel  <br /><br />exportfs -au &lt;==全部都卸载了。  <br /><br /><br />客户段的操作：  <br />1、showmout命令对于NFS的操作和查错有很大的帮助，所以我们先来看一下showmount的用法  <br />showmout  <br />-a ：这个参数是一般在NFS SERVER上使用，是用来显示已经mount上本机nfs目录的cline机器。  <br />-e ：显示指定的NFS SERVER上export出来的目录。  <br />例如：  <br />showmount -e 192.168.0.30  <br />Export list for localhost:  <br />/tmp *  <br />/home/linux *.linux.org  <br />/home/public (everyone)  <br />/home/test 192.168.0.100  <br />2、mount nfs目录的方法：  <br />mount -t nfs hostname(orIP):/directory /mount/point  <br />具体例子：  <br />Linux: mount -t nfs 192.168.0.1:/tmp /mnt/nfs  <br />Solaris:mount -F nfs 192.168.0.1:/tmp /mnt/nfs  <br />BSD: mount 192.168.0.1:/tmp /mnt/nfs  <br /><br />3、mount nfs的其它可选参数：  <br />HARD mount和SOFT MOUNT：  <br />HARD: NFS CLIENT会不断的尝试与SERVER的连接（在后台，不会给出任何提示信息,在LINUX下有的版本仍然会给出一些提示），直到MOUNT上。  <br />SOFT:会在前台尝试与SERVER的连接，是默认的连接方式。当收到错误信息后终止mount尝试，并给出相关信息。  <br />例如：mount -F nfs -o hard 192.168.0.10:/nfs /nfs  <br />对于到底是使用hard还是soft的问题，这主要取决于你访问什么信息有关。例如你是想通过NFS来运行X PROGRAM的话，你绝对不会希望由于一些意外的情况（如网络速度一下子变的很慢，插拔了一下网卡插头等）而使系统输出大量的错误信息，如果此时你用的是HARD方式的话，系统就会等待，直到能够重新与NFS SERVER建立连接传输信息。另外如果是非关键数据的话也可以使用SOFT方式，如FTP数据等，这样在远程机器暂时连接不上或关闭时就不会挂起你的会话过程。  <br /><br />rsize和wsize：  <br />文件传输尺寸设定：V3没有限定传输尺寸，V2最多只能设定为8k，可以使用-rsize and -wsize 来进行设定。这两个参数的设定对于NFS的执行效能有较大的影响  <br />bg：在执行mount时如果无法顺利mount上时，系统会将mount的操作转移到后台并继续尝试mount，直到mount成功为止。（通常在设定/etc/fstab文件时都应该使用bg，以避免可能的mount不上而影响启动速度）  <br />fg：和bg正好相反，是默认的参数  <br />nfsvers＝n:设定要使用的NFS版本，默认是使用2，这个选项的设定还要取决于server端是否支持NFS VER 3  <br />mountport：设定mount的端口  <br />port：根据server端export出的端口设定，例如如果server使用5555端口输出NFS,那客户端就需要使用这个参数进行同样的设定  <br />timeo=n:设置超时时间，当数据传输遇到问题时，会根据这个参数尝试进行重新传输。默认值是7/10妙（0.7秒）。如果网络连接不是很稳定的话就要加大这个数值，并且推荐使用HARD MOUNT方式，同时最好也加上INTR参数，这样你就可以终止任何挂起的文件访问。  <br />intr 允许通知中断一个NFS调用。当服务器没有应答需要放弃的时候有用处。  <br />udp：使用udp作为nfs的传输协议（NFS V2只支持UDP)  <br />tcp：使用tcp作为nfs的传输协议  <br />namlen=n：设定远程服务器所允许的最长文件名。这个值的默认是255  <br />acregmin=n：设定最小的在文件更新之前cache时间，默认是3  <br />acregmax=n：设定最大的在文件更新之前cache时间，默认是60  <br />acdirmin=n：设定最小的在目录更新之前cache时间，默认是30  <br />acdirmax=n：设定最大的在目录更新之前cache时间，默认是60  <br />actimeo=n：将acregmin、acregmax、acdirmin、acdirmax设定为同一个数值，默认是没有启用。  <br />retry=n：设定当网络传输出现故障的时候，尝试重新连接多少时间后不再尝试。默认的数值是10000 minutes  <br />noac:关闭cache机制。  <br />同时使用多个参数的方法：mount -t nfs -o timeo=3,udp,hard 192.168.0.30:/tmp /nfs  <br />请注意，NFS客户机和服务器的选项并不一定完全相同，而且有的时候会有冲突。比如说服务器以只读的方式导出，客户端却以可写的方式mount,虽然可以成功mount上，但尝试写入的时候就会发生错误。一般服务器和客户端配置冲突的时候，会以服务器的配置为准。  <br /><br /><br />4、/etc/fstab的设定方法  <br />/etc/fstab的格式如下：  <br />fs_spec　　　fs_file　　fs_type　　　fs_options　　fs_dump　fs_pass　  <br />fs_spec:该字段定义希望加载的文件系统所在的设备或远程文件系统,对于nfs这个参数一般设置为这样：192.168.0.1:/NFS  <br />fs_file:本地的挂载点  <br />fs_type：对于NFS来说这个字段只要设置成nfs就可以了  <br />fs_options:挂载的参数，可以使用的参数可以参考上面的mount参数。  <br />fs_dump　-　该选项被"dump"命令使用来检查一个文件系统应该以多快频率进行转储，若不需要转储就设置该字段为0  <br />fs_pass　-　该字段被fsck命令用来决定在启动时需要被扫描的文件系统的顺序，根文件系统"/"对应该字段的值应该为1，其他文件系统应该为2。若该文件系统无需在启动时扫描则设置该字段为0 。  <br /><br />5、与NFS有关的一些命令介绍  <br />nfsstat:  <br />查看NFS的运行状态，对于调整NFS的运行有很大帮助  <br />rpcinfo：  <br />查看rpc执行信息，可以用于检测rpc运行情况的工具。  <br /><br /><br />四、NFS调优  <br />调优的步骤：  <br />1、测量当前网络、服务器和每个客户端的执行效率。  <br />2、分析收集来的数据并画出图表。查找出特殊情况，例如很高的磁盘和CPU占用、已经高的磁盘使用时间  <br />3、调整服务器  <br />4、重复第一到第三步直到达到你渴望的性能  <br /><br /><br />与NFS性能有关的问题有很多，通常可以要考虑的有以下这些选择：  <br /><br />WSIZE,RSIZE参数来优化NFS的执行效能  <br />WSIZE、RSIZE对于NFS的效能有很大的影响。  <br />wsize和rsize设定了SERVER和CLIENT之间往来数据块的大小，这两个参数的合理设定与很多方面有关，不仅是软件方面也有硬件方面的因素会影响这两个参数的设定（例如LINUX KERNEL、网卡，交换机等等）。  <br />下面这个命令可以测试NFS的执行效能，读和写的效能可以分别测试，分别找到合适的参数。对于要测试分散的大量的数据的读写可以通过编写脚本来进行测试。在每次测试的时候最好能重复的执行一次MOUNT和unmount。  <br />time dd if=/dev/zero of=/mnt/home/testfile bs=16k count=16384  <br />用于测试的WSIZE,RSIZE最好是1024的倍数，对于NFS V2来说8192是RSIZE和WSIZE的最大数值，如果使用的是NFS V3则可以尝试的最大数值是32768。  <br />如果设置的值比较大的时候，应该最好在CLIENT上进入mount上的目录中，进行一些常规操作（LS,VI等等），看看有没有错误信息出现。有可能出现的典型问题有LS的时候文件不能完整的列出或者是出现错误信息，不同的操作系统有不同的最佳数值，所以对于不同的操作系统都要进行测试。  <br /><br />设定最佳的NFSD的COPY数目。  <br />linux中的NFSD的COPY数目是在/etc/rc.d/init.d/nfs这个启动文件中设置的，默认是8个NFSD,对于这个参数的设置一般是要根据可能的CLIENT数目来进行设定的，和WSIZE、RSIZE一样也是要通过测试来找到最近的数值。  <br /><br />UDP and TCP  <br />可以手动进行设置，也可以自动进行选择。  <br />mount -t nfs -o sync,tcp,noatime,rsize=1024,wsize=1024 EXPORT_MACHINE:/EXPORTED_DIR /DIR  <br />UDP有着传输速度快，非连接传输的便捷特性，但是UDP在传输上没有TCP来的稳定，当网络不稳定或者黑客入侵的时候很容易使NFS的 Performance 大幅降低甚至使网络瘫痪。所以对于不同情况的网络要有针对的选择传输协议。nfs over tcp比较稳定，nfs over udp速度较快。在机器较少网络状况较好的情况下使用UDP协议能带来较好的性能，当机器较多，网络情况复杂时推荐使用TCP协议（V2只支持UDP协议）。在局域网中使用UDP协议较好，因为局域网有比较稳定的网络保证，使用UDP可以带来更好的性能，在广域网中推荐使用TCP协议，TCP协议能让NFS在复杂的网络环境中保持最好的传输稳定性。可以参考这篇文章：http://www.hp.com.tw/ssn/unix/0212/unix021204.asp  <br /><br />版本的选择  <br />V3作为默认的选择（RED HAT 8默认使用V2,SOLARIS 8以上默认使用V3），可以通过vers= mount option来进行选择。  <br />LINUX通过mount option的nfsvers=n进行选择。  <br /><br />五、NFS故障解决  <br />1、NFSD没有启动起来  <br />首先要确认 NFS 输出列表存在，否则 nfsd 不会启动。可用 exportfs 命令来检查，如果 exportfs 命令没有结果返回或返回不正确，则需要检查 /etc/exports 文件。  <br />2、mountd 进程没有启动  <br />mountd 进程是一个远程过程调用 (RPC) ，其作用是对客户端要求安装（mount）文件系统的申请作出响应。mountd进程通过查找 /etc/xtab文件来获知哪些文件系统可以被远程客户端使用。另外，通过mountd进程，用户可以知道目前有哪些文件系统已被远程文件系统装配，并得知远程客户端的列表。查看mountd是否正常启动起来可以使用命令rpcinfo进行查看，在正常情况下在输出的列表中应该象这样的行：  <br />100005 1 udp 1039 mountd  <br />100005 1 tcp 1113 mountd  <br />100005 2 udp 1039 mountd  <br />100005 2 tcp 1113 mountd  <br />100005 3 udp 1039 mountd  <br />100005 3 tcp 1113 mountd  <br />如果没有起来的话可以检查是否安装了PORTMAP组件。  <br />rpm -qa|grep portmap  <br />3、fs type nfs no supported by kernel  <br />kernel不支持nfs文件系统，重新编译一下KERNEL就可以解决。  <br />4、cant contact portmapper: RPC: Remote system error - Connection refused  <br />出现这个错误信息是由于SEVER端的PORTMAP没有启动。  <br />5、mount clntudp_create: RPC: Program not registered  <br />NFS没有启动起来，可以用showmout -e host命令来检查NFS SERVER是否正常启动起来。  <br />6、mount: localhost:/home/test failed, reason given by server: Permission denied  <br />这个提示是当client要mount nfs server时可能出现的提示，意思是说本机没有权限去mount nfs server上的目录。解决方法当然是去修改NFS SERVER咯。  <br />7、被防火墙阻挡  <br />这个原因很多人都忽视了，在有严格要求的网络环境中，我们一般会关闭linux上的所有端口，当需要使用哪个端口的时候才会去打开。而NFS默认是使用111端口，所以我们先要检测是否打开了这个端口，另外也要检查TCP_Wrappers的设定。  <br /><br /><br />六、NFS安全  <br />NFS的不安全性主要体现于以下4个方面:  <br /><br />1、新手对NFS的访问控制机制难于做到得心应手,控制目标的精确性难以实现  <br />2、NFS没有真正的用户验证机制,而只有对RPC/Mount请求的过程验证机制  <br />3、较早的NFS可以使未授权用户获得有效的文件句柄  <br />4、在RPC远程调用中,一个SUID的程序就具有超级用户权限.  <br /><br />加强NFS安全的方法：  <br />1、合理的设定/etc/exports中共享出去的目录，最好能使用anonuid，anongid以使MOUNT到NFS SERVER的CLIENT仅仅有最小的权限，最好不要使用root_squash。  <br />2、使用IPTABLE防火墙限制能够连接到NFS SERVER的机器范围  <br />iptables -A INPUT -i eth0 -p TCP -s 192.168.0.0/24 --dport 111 -j ACCEPT  <br />iptables -A INPUT -i eth0 -p UDP -s 192.168.0.0/24 --dport 111 -j ACCEPT  <br />iptables -A INPUT -i eth0 -p TCP -s 140.0.0.0/8 --dport 111 -j ACCEPT  <br />iptables -A INPUT -i eth0 -p UDP -s 140.0.0.0/8 --dport 111 -j ACCEPT  <br />3、为了防止可能的Dos攻击，需要合理设定NFSD 的COPY数目。  <br />4、修改/etc/hosts.allow和/etc/hosts.deny达到限制CLIENT的目的  <br />/etc/hosts.allow  <br />portmap: 192.168.0.0/255.255.255.0 : allow  <br />portmap: 140.116.44.125 : allow  <br /><br />/etc/hosts.deny  <br />portmap: ALL : deny  <br />5、改变默认的NFS 端口  <br />NFS默认使用的是111端口，但同时你也可以使用port参数来改变这个端口，这样就可以在一定程度上增强安全性。  <br />6、使用Kerberos V5作为登陆验证系统<br /><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19975.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:34 <a href="http://www.cnitblog.com/flutist1225/articles/19975.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Redboot安装历程（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19973.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:30:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19973.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19973.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19973.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19973.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19973.html</trackback:ping><description><![CDATA[注: 本文第一次发布在 <a href="http://www.embedzone.com/bbs/dispbbs.asp?boardID=7&amp;ID=266" target="_blank">http://www.embedzone.com/bbs/dispbbs.asp?boardID=7&amp;ID=266</a>, 由于补丁问题,在此贴一下,. <br /><br />简介: 本文是本人为工作需要而试验redboot的一个过程,我只是记录了试验的过程,希望能对别人使用redboot有些帮助. 本文没有系统性介绍redboot, 相关知识请参考其他资料.文章中具体修改的内容详细可以看附件patch. <br /><br />一 目标 <br />公司购买了EV40开发板:CPU AT91M40800, 内存2M+2M(扩展), FLASH 2M+4M(扩展), RTl8019AS芯片, FLASH是AM29LV160TE(2M) <br />公司自己开发自用的板子WX10: CPU AT91M40800, 内存4M, FLASH 8M, RTl8019AS芯片, FLASH是AM29LV641(8M)及其他应用功能芯片. <br />我主要的工作是开发系统的驱动软件,因此需要随时修改内核驱动,并调试,因此装入和启动的速度对工作效率是比较关键的.因为用hitool烧录方式启动 uclinux内核方式需要6~10分种一次.听”嵌入式linux群”kingmonkey说可以使用redboot, 因此决定试试. <br /><br />二 环境建立 <br />redboot是ecos操作系统的一部分,也是ecos操作系统最小配置的版本.因此要使用redboot,必须建立ecos操作系统环境. 到网站 <br /><a href="http://sources.redhat.com/ecos/" target="_blank">http://sources.redhat.com/ecos/</a><br />查找其安装方法,按照说明采用了其网络方式安装,使用: <br /># wget --passive-ftp <a href="ftp://ecos.sourceware.org/pub/ecos/ecos-install.tcl" target="_blank">ftp://ecos.sourceware.org/pub/ecos/ecos-install.tcl</a><br />下载安装命令,并运行: <br /># sh ecos-install.tcl <br />安装了ecos 2.0.我把这个安装在/rh80/ecos下.ECOS_REPOSITORY就是/rh80/ecos/ecos-2.0. <br />运行 : <br /># cd /rh80/ecos <br /># . ecosenv.sh <br />设置环境变量. <br />由于linux操作系统中已经安装了 <br /><a href="http://www.uclinux.org/pub/uClinux/m68k-elf-tools/arm-elf-tools-20030314.sh" target="_blank">http://www.uclinux.org/pub/uClinux/m68k-elf-tools/arm-elf-tools-20030314.sh</a><br />因此我安装ecos时,没有选择安装arm-elf GNU tools工具. <br /><br />三 编译redboot <br />由于ecos是个可配置的操作系统,因此下载安装的实际就是一个配置的仓库,要编译redboot就按照需要进行配置.配置的方式使用配置工具ecosconfig, 也有图形方式配置工具的,我没有用,只用字符界面的ecosconfig. <br />由于EV40类似EB40, 因此我的命令是: <br /># mkdir rom <br /># cd rom <br /># ecosconfig new eb40 redboot <br />#ecosconfig import /rh80/ecos/ecos-2.0/packages/hal/arm/at91/eb40/current/misc/redboot_ROMRAM.ecm <br />#ecosconfig tree <br />#make <br />结果编译出现错误. <br />因此怀疑编译器不兼容,于是重新安装ecos,此时选择arm-elf工具.此时运行. Ecosenv.sh 时,新下载的工具的路径包含在PATH中, 重新进行了redboot生成和编译,正确生成了install/bin/redboot.bin等文件. <br />将redboot.bin用hitool烧写到EV40的flash中,启动,没有任何反应.重新选择redboot的ROM版: <br /># ecosconfig new eb40 redboot <br />#ecosconfig import /rh80/ecos/ecos-2.0/packages/hal/arm/at91/eb40/current/misc/redboot_ROM.ecm <br />#ecosconfig tree <br />#make <br />编译,烧录,运行,仍然无反应. <br /><br />四 配置硬件参数 <br />由于EB40和EV40不完全相同,因此首先检查硬件参数的配置,找到配置的文件是: ecos/packages/hal/arm/at91/eb40/current/include/hal_platform_ints.h <br />主要是AT91_EBI配置参数表, EV40是: <br />_InitMemory: <br />.long 0x01002529 @ 0x01000000, 16MB, 2 cycles added after transfer, 16-bit, 6 wait states <br />.long 0x020020a1 @ 0x02000000, 16MB, 0x02002121 0 cycles added after transfer, 16-bit, 1 wait state <br />.long 0x03002529 @ unused <br />.long 0x40000000 @ unused <br />.long 0x02202021 @ unused ,CS 4 <br />.long 0x02302021 @ unused ,CS 5 <br />.long 0x60000000 @ unused <br />.long 0x70000000 @ unused <br />.long 0x00000001 @ REMAP commande <br />.long 0x00000006 @ 7 memory regions, standard read <br />.long AT91_EBI @ EBI address <br />.long 10f // address where to jump <br /><br />WX10的配置是: <br /><br />_InitMemory: <br />.long 0x01002529 // 0x01000000, 16MB, 2 cycles after transfer, 16-bit, 6 wait states <br />.long 0x020020a1 // 0x02000000, 16MB, 0 cycles after transfer, 16-bit, 1 wait state <br />.long 0x03002529 // unused <br />.long 0x30000000 // unused <br />.long 0x40000000 // unused <br />.long 0x50000000 // unused <br />.long 0x60000000 // unused <br />.long 0x70000000 // unused <br />.long 0x00000001 // REMAP command <br />.long 0x00000000 // 7 memory regions, standard read <br />.long AT91_EBI // External Bus Interface address <br />.long 10f // address where to jump <br />这里我要说明的一点是EV40和WX10的区别, EV40是用到CS6的,而WX10是不用CS6的, EV40有扩展内存,由CS4和CS5配置. EV40网卡地址是0x40010000, 而WX10的网卡地址是0x03210000. <br />修改了配置后, 编译,烧录,运行,仍然无反应. <br /><br />向kingmonkey讨叫,kingmonkey认为可能是ecos版本不是最新的缘故,建议用cvs下载最新的版本. 用ecos-install.tcl是最新的稳定版,但不是最新的. <br /><br />五 安装cvs版ecos <br />安装方法参考网站中Anonymous CVS: <br /># cd /rh80/ecos <br /># cvs -d <img alt="" src="http://www.linuxforum.net/forum/images/icons/tongue.gif" />server:anoncvs@ecos.sourceware.org:/cvs/ecos login <br />口令任意 <br /># cvs -z3 -d <img alt="" src="http://www.linuxforum.net/forum/images/icons/tongue.gif" />server:anoncvs@ecos.sourceware.org:/cvs/ecos co -P ecos <br />这样呢就下载的最新的ecos,目录是/rh80/ecos/ecos, 修改ecosenv.sh中 <br />ECOS_REPOSITORY=/rh80/ecos/ecos/packages ; export ECOS_REPOSITORY <br />这样呢,就使用最新用cvs下载的ecos了.原来的ecos-2.0仍然保留,并使用其下面的ecosconfig等工具,不用重新去下载ecosconfig工具了. <br /><br />六 重新编译redboot <br />安装第三节的方法重新编译redboot,但仍然无法工作.因此只好去看资料和代码.并且下载了网站上预编译好的reboot.bin来试,仍然没有任何反应. <br />仔细阅读了ecos参考手册: <br /><a href="http://ecos.sourceware.org/docs-latest/ref/ecos-ref.html" target="_blank">http://ecos.sourceware.org/docs-latest/ref/ecos-ref.html</a><br />中关于Installation and Testing部分中ARM/ARM7 Atmel AT91 Evaluation Boards (EBXX)的资料,它运行redboot的方式是通过angel和arm-elf-gdb的方式的,因此我就想先按照其方式试一下. <br />把angel烧录到EV40板上,然后编译出redboot的RAM板(上面命令中redboot_ROM.ecm改成 redboot_RAM.ecm就是).然后安装手册,成功启动了redboot! 结合前面看了redboot的一些代码,怀疑缺省的EV40配置是只能在angel方式下启动的. <br /><br />七 修改redboot配置 <br />经过阅读其文件, 发现编译命令文件install/lib/target.ld中: <br />__reserved_bootmon = 0x01000000; . = __reserved_bootmon + 0x10000; <br />将运行开始位置后移了一个0x10000, 这个可能是造成不能直接flash启动的原因.经过检查,修改了多处跟这个有关的地方: <br />1. ecos/packages/hal/arm/at91/eb40/current/include/hal_platform_ints.h中0x1010000 à 0x100000 <br />2. ecos/packages/hal/arm/at91/eb40/current/include/pkgconf/mlt_arm_at91_eb40_rom.h中 <br />#define CYGMEM_SECTION_reserved_bootmon_SIZE (0x10000) 改成 <br />#define CYGMEM_SECTION_reserved_bootmon_SIZE (0x00000) <br />3. ecos/packages/hal/arm/at91/eb40/current/include/pkgconf/mlt_arm_at91_eb40_rom.ldi中 <br /><br />CYG_LABEL_DEFN(__reserved_bootmon) = 0x01000000; . = CYG_LABEL_DEFN(__reserved_bootmon) + 0x10000; <br />改成: <br />CYG_LABEL_DEFN(__reserved_bootmon) = 0x01000000; . = CYG_LABEL_DEFN(__reserved_bootmon) + 0x00000; <br />[mlt_arm_at91_eb40_rom.ldi就是生成rom版redboot中target.ld的依据.] <br />然后重新配置redboot和编译,运行,烧录到EV40,正常启动了redboot. ^_^ <br />[此时出现一个非常讨厌的问题,就是EV40板子一运行redboot,蜂鸣器不停的叫.我没有去检查为什么这样!后来只好在自己公司的板子上试了.] <br /><br />八 配置flash <br />由于EB40采用的flash芯片和EV40采用的芯片是不一样的,因此很正常的结果是我们第七节编译处理的redboot是不能正确识别EV40的flash芯片.因此就必须考虑修改flash驱动. <br />此时本人对如何修改还不是太清楚,因此只要乖乖地去看ecos的资料.正好同事买了一本ecos的书,这样就省了看英文资料的麻烦. <br />从资料上,可以知道修改配置,主要是修改cdl文件. 跟EB40有关的flash包是: <br />Package CYGPKG_DEVS_FLASH_EB40 (FLASH memory support for Atmel AT91/EB40): <br />Package CYGPKG_DEVS_FLASH_ATMEL_AT29CXXXX (Support for Atmel AT29Cxxxx flash memory): <br />我就把CYGPKG_DEVS_FLASH_EB40包中采用的ATMEL芯片的包改成: <br />CYGPKG_DEVS_FLASH_AMD_AM29XXXXX <br />具体修改的文件是ecos/packages/devs/flash/arm/eb40/current/cdl/flash_eb40.cdl: <br />修改 requires CYGPKG_DEVS_FLASH_ATMEL_AT29CXXXX 为 <br /><br />requires CYGPKG_DEVS_FLASH_AMD_AM29XXXXX <br />修改 cdl_interface CYGINT_DEVS_FLASH_ATMEL_AT29CXXXX_REQUIRED { <br />display "Generic Atmel AT29CXXXX driver required" <br />为 cdl_interface CYGINT_DEVS_FLASH_AMD_AM29XXXXX_REQUIRED { <br />display "Generic Amd AM29XXXXX driver required" <br />修改 implements CYGINT_DEVS_FLASH_ATMEL_AT29CXXXX_REQUIRED <br />为 implements CYGINT_DEVS_FLASH_AMD_AM29XXXXX_REQUIRED <br /><br />增加 requires CYGHWR_DEVS_FLASH_AMD_AM29LV160. <br />这里特别是要说明的是最后这一行, 因为CYGPKG_DEVS_FLASH_AMD_AM29XXXXX支持多种芯片,在配置的时候,需要指定哪些芯片可以识别,刚开始的时候ecoscofing tree和编译后,就是没有看到驱动程序包含进去,弄了半天才搞明白,如果一种芯片也没有选,则驱动程序就不包含进去.因此需要加入至少一种芯片的选择,最后一行就是选择AM29LV160的芯片. <br />同时修改 ecos/packages/ecos.db 的target eb40中: <br />修改 CYGPKG_DEVS_FLASH_ATMEL_AT29CXXXX <br />为 CYGPKG_DEVS_FLASH_AMD_AM29XXXXX <br /><br />再修改ecos/packages/devs/flash/arm/eb40/current/src/eb40_flash.c: <br />增加 #define CYGNUM_FLASH_WIDTH 16 <br />修改 #include "cyg/io/flash_at29cxxxx.inl" <br />为 #include "cyg/io/flash_am29xxxxx.inl" <br /><br />此时,由于EV40板子乱叫的原因,我是用我们自己的板子WX10来调试了,而WX10采用的flash是AM29LV641,与AM29LV160是有区别的,因此我打开了flash调试,自己增加了一些调试语句,运行后,然后增加了AM29LV641的驱动,具体文件是 ecos/packages/devs/flash/amd/am29xxxxx/current/include/flash_am29xxxxx_parts.inl, 增加了AM29LV641配置: <br />+ { // MBM29LV641 <br />+ device_id : FLASHWORD(0x22d7), <br />+ block_size : 0x10000 * CYGNUM_FLASH_INTERLEAVE, <br />+ block_count: 32, <br />+ device_size: 0x200000 * CYGNUM_FLASH_INTERLEAVE, <br />+ base_mask : ~(0x200000 * CYGNUM_FLASH_INTERLEAVE - 1), <br />+ bootblock : true, <br />+ bootblocks : { 0x000000 * CYGNUM_FLASH_INTERLEAVE, <br />+ 0x004000 * CYGNUM_FLASH_INTERLEAVE, <br />+ 0x002000 * CYGNUM_FLASH_INTERLEAVE, <br />+ 0x002000 * CYGNUM_FLASH_INTERLEAVE, <br />+ 0x008000 * CYGNUM_FLASH_INTERLEAVE, <br />+ _LAST_BOOTBLOCK <br />+ }, <br />+ banked : false, <br />+ bufsiz : 1 <br />+ }, <br />具体还是放在CYGHWR_DEVS_FLASH_AMD_AM29LV160下,这样上面的cdl不用修改.[上面的修改中我没有修改flash的容量,因为AM29LV641是8M的,我还弄清楚如何改!我就先把它当成2M使用.] <br />经过上面的修改,重新编译运行后,redboot能够正确识别了flash的. ^_^ <br /><br />九 网卡驱动 <br />google了”ecos 8019as driver”,查到了二个结果,我把二个驱动包都下载下来了.然后分别测试了一下,但都没有成功. 我最后选择dp83902a这种方式重点研究. <br />首先下载其软件包,解压到相应的目录. 然后增加了CYGPKG_DEVS_ETH_RLTK_ISA8019AS 定义. <br />由于EB40没有网卡驱动,因此只好自己增加配置.具体是redboot_ROM.ecm中增加: <br />package -hardware CYGPKG_DEVS_ETH_RLTK_ISA8019AS current ; <br />package -hardware CYGPKG_DEVS_ETH_NS_DP83902A current <br />package CYGPKG_IO_ETH_DRIVERS current ; <br />然后在文件ecos/packages/devs/eth/rltk/isa8019as/current/include/devs_eth_rltk_isa8019as.inl中修改网卡的起始地址和中断: <br />static dp83902a_priv_data_t dp83902a_eth0_priv_data = { <br />base: (cyg_uint8*) 0x03210000, <br />interrupt: 17, <br />tx_buf1: 0x40, <br />tx_buf2: 0x48, <br />rx_buf_start: 0x50, <br />rx_buf_end: 0x80, <br />hardwired_esa: false, <br />}; <br />然后编译,烧录,运行.但运行到网卡时就没有反应了.网卡能检测到. 此时只好调试原代码,打开了dp83902a驱动的开关.发现发送数据包时,就不动了.经过检查其代码,发现mac地址有二种方式,一种是从网卡eprom中取,另一种就是指定,于是我就把配置改成了指定: <br />static dp83902a_priv_data_t dp83902a_eth0_priv_data = { <br />base: (cyg_uint8*) 0x03210000, <br />interrupt: 17, <br />tx_buf1: 0x40, <br />tx_buf2: 0x48, <br />rx_buf_start: 0x50, <br />rx_buf_end: 0x80, <br />hardwired_esa: true, <br />esa: {0x00, 0x05, 0x0c, 0x04, 0x05, 0x06}, <br />}; <br />但效果仍然一样. 通过多次调试和分析, 想起了寄存器偏移量的问题. 我在EV40板子上用uclinux驱动网卡时也是同样的问题,后来把 所有寄存器的偏移量*2就可以了.于是动手将全部寄存器偏移量*2.这样呢, 网卡驱动就可以了, 并能发送和接收数据包了.【这个呢,我自己也不太明白,是不是跟硬件的设置有关?】 <br />但redboot启动时, 经过很长时间才到redboot提示符出来.原来是redboot启动时,自动通过bootp去取的IP地址,由于没有bootp服务器,因此要等待一段时间才出现超时, 让我误以为死机了. <br />建立好bootp服务器, redboot就正常启动,并且配置了ip地址. 然后ping也通了. <br />此时大功告成.!!! <br /><br />十 启动uclinux <br />uclinux编译时必须注意的是: 由于 redboot运行时,必须要是使用部分内存,你可以用version命令看出使用了什么内存, 因此uclinux的入口地址就不能是0x2000000, 我选择了0x2010000, 前面留了64K. <br />将linux.elf拷贝到bootp和tftp服务器的/tftpboot下. 运行: <br />&gt; load –m tftp linux.elf <br />&gt; go 0x2010000 <br />注意: 由于redboot串口使用38400波特率,而uclinux采用9600波特率,因此uclinux启动后出现乱码,没有关系,把波特率改成9600, 然后重新连接终端就可以了. <br /><br />附: 修改的patch. Patch是针对WX10板子的. 由于我修改的ecos是cvs版本,每次checkout的不一定一样.因此patch就不一定能够直接使用, 另外呢,我的patch中也有一些我增加的调试语句,如果你要产品中使用,建议删除好了.<br /><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19973.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:30 <a href="http://www.cnitblog.com/flutist1225/articles/19973.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于ARM核的Intel XScale嵌入式系统（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19974.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:30:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19974.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19974.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19974.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19974.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19974.html</trackback:ping><description><![CDATA[
		<font color="#333333">　1 简 介<br /><br />　　Intel XScale微体系结构提供了一种全新的、高性价比、低功耗且基于ARMv5TE体系结构的解决方案，支持16位Thumb指令和DSP扩充。基于XScale技术开发的微处理器，可用于手机、便携式终端(PDA)、网络存储设备、骨干网(BackBone)路由器等。Intel PXA250微处理器芯片就是一款集成了32位Intel XScale 处理器核、多通信信道、LCD控制器、增强型存储控制器和PCMCIA/CF控制器以及通用I/O口的高度集成的应用处理器。 <br /><br />　　Intel XScale处理器的处理速度是Intel StrongARM处理速度的两倍，其内部结构也有了相应的变化： <br /><br /></font>　　数据Cache的容量从8KB增加到32KB； 
<p>　　指令Cache的容量从16KB增加到32KB；</p><p>　　微小数据Cache的容量从512B增加到2KB；</p><p>　　为了提高指令的执行速度，超级流水线结构由5级增至7级；</p><p>　　新增乘/加法器MAC和特定的DSP型协处理器CP0，以提高对多媒体技术的支持；</p><p>　　动态电源管理，使XScale处理器的时钟可达1GHz、功耗1.6W，并能达到1200MIPS。 <br />　　<br />　　XScale微处理器架构经过专门设计，核心采用了英特尔先进的0.18μm工艺技术制造；具备低功耗特性，适用范围从0.1mW～1.6W。同时，它的时钟工作频率将接近1GHz。 XScale与StrongARM相比，可大幅降低工作电压并且获得更高的性能。具体来讲，在目前的StrongARM中，在1.55V下可以获得133MHz的工作频率，在2.0V下可以获得206MHz的工作频率；而采用XScale后，在0.75V时工作频率达到150MHz，在1.0V时工作频率可以达到400MHz，在1.65V下工作频率则可高达800MHz。超低功率与高性能的组合使Intel XScale适用于广泛的互联网接入设备，在因特网的各个环节中，从手持互联网设备到互联网基础设施产品，Intel XScale都表现出了令人满意的处理性能。</p><p>　　2 PXA250的结构及特点</p><p>　　Intel XScale PXA250结构框图如图1所示。 <br /></p><a href="http://www.developboard.com/upload/article/2004329135938.jpg" target="_blank"></a><a href="http://www.developboard.com/upload/article/2004329135938.jpg" target="_blank"></a><p align="center"><img height="350" alt="" src="http://www.gd-emb.org/UserFiles/Image/tech_doc/js176.jpg" width="400" /></p><p><br /><br />　　2.1 PXA250处理器的主要特点</p><p>　　(1)高性能<br /><br />　　低功耗，高性能的32位Intel XScale处理器内核，工作频率高达400MHz；</p><p>　　兼容ARMv5TE架构；</p><p>　　采用7级超级流水线结构；</p><p>　　支持多媒体处理技术，采用40位累加器和16位乘法器,以增强对音频和视频的解码能力；</p><p>　　提供支持同步Intel StrataFlash存储器的高性能分帧和分页模式接口。 <br /><br />　　(2)低功耗<br /><br />　　多电源管理模式；</p><p>　　32KB数据和32KB指令Cache；</p><p>　　2KB的微小数据Cache；</p><p>　　支持2.5V和3.3V的存储器。 <br /><br />　　(3)I/O扩展<br /><br />　　100MHz存储器总线，6个静态存储空间(16或32位ROM(SMROM)/Flash/SRAM)，4个动态存储分区(16或32位SDRAM)；</p><p>　　支持2个PCMCIA 或 Compact Flash槽。 <br /><br />　　(4)外围控制模块<br /><br />　　16通道可配置DMA控制器；</p><p>　　LCD控制器，独有的支持对快变彩屏的DMA方式；</p><p>　　920kbps Bluetooth接口；</p><p>　　串行端口(IrDA、I2C、I2S、AC97、3个UARTs、SPI 和SSP)；</p><p>　　USB接口；</p><p>　　支持MMC/SD卡。 <br /><br />　　(5)时钟控制<br /><br />　　五种时钟源：<br /><br />　　32.768kHz振荡器；</p><p>　　3.6864MHz振荡器；</p><p>　　可编程的内核锁相环；</p><p>　　95.85MHz外围固定频率锁相环；</p><p>　　147.46MHz固定频率锁相环。 <br /><br />　　(6)电源管理<br /><br />　　运行模式(正常处理模式)、Turbo模式(运行于400MHZ)、空闲模式(下电)、睡眠模式(下电)。</p><p>　　(7)封装形式<br /><br />　　17mm×17 mm 256脚PBGA封装。</p><p>　　2.2 Intel XScale内核</p><p>　　Intel XScale CPU内核采用带有一个增强型存储器管道的超级流水线RISC处理器架构的体系结构。这款新型高性能、低功耗的微构架兼容ARMv5TE ISA指令集(不支持浮点指令集)。这种微构架在ARM核的周围提供了指令与数据存储器管理单元，指令、数据和微小数据Cache，写缓冲、全缓冲、挂起缓冲和分支目标缓冲器，电源管理，性能监控、调试和JTAG单元以及协处理器接口，MAC协处理器和内核存储总线。</p><p>　　超级流水线结构是由整型管道、存储器管道和MAC管道构成。整型管道包括7级流水线结构，取指令1(分支目标缓冲器)→取指令2→译码→寄存/移位→ALU实现→状态执行→回复；存储器管道除包括整型管道的前5级外，后接3个高速缓存，数据Cache1、数据Cache2和数据回复Cache，共8级流水线结构；MAC管道是6~9级的流水线结构，包括整型管道的前4级和4级MAC段，以及一个数据回复Cache，其中MAC2-4的选通由数据决定。流水线结构级数越多越能提高指令的执行速度，使用分支目标缓冲器的目的在于成功的预知分支指令的 结果。128个入口的分支目标缓冲器的每个入口都包含了分支指令的地址、与分支指令相联系的目标地址以及该分支的执行情况，它由协处理器15使能。分支目标缓冲器的使用旨在避免超级流水线结构中的分支延迟。</p><p>　　PXA250 CPU的MM(IMMU和DMMU)均提供了一个32项的转换旁路缓存器(ITLB和DTLB)，它们的每一项均可映射存储器中的段、大页和小页。为了保证内核周期的存取指令和数据，PXA250包含了1个32KB的指令Cache和1个32KB的数据Cache。另外，为了避免数据Cache内数据流存取的频繁变化，还提供了1个2KB的微小数据Cache。指令和数据Cache都是具有32个入口和32路相联的Cache，每路均包含1个标志地址，32字节的高速缓存队列和1个有效位，采用循环方式进行刷新存储。微小数据Cache是1个具有32个入口和2路相联的Cache，同样采用循环方式进行刷新存储。</p><p>　　PXA250内核还提供了4个入口的全缓冲和挂起缓冲，用于提升内核性能，与数据Cache和微小数据Cache协同工作。此外，1个8入口的写缓冲，每个入口可保存16字节，它从内核、数据Cache或微小数据Cache中得到数据，在系统总线选通前，暂存数据。</p><p>　　2.3 系统控制功能</p><p>　　PXA250的系统控制模块提供了实时时钟、看门狗及间隔定时器、功率管理控制器、中断控制器、复位控制器和2个片上振荡器。该系统定时器支持源自SA-11x0处理器的定时器单元，OS定时器使用3.6864MHz振荡器，包含了4个定时匹配寄存器(OSMR)、1个定时状态寄存器(OSSR)和1个定时中断使能寄存器(OIER)。看门狗定时中断可以通过激活OS定时看门狗使能寄存器(OWER)来实现。<br />中断控制器处理的所有中断源，有两个中断类型：中断请求(IRQ)和快速中断请求(FIQ)。中断控制器可以根据掩码寄存器的值，允许CPU被中断或保持预中断。中断控制器中的每一个寄存器都是1比特映射，并且每一比特均被预先分配给不同的中断源。</p><p>　　2.4 时钟和电源管理</p><p>　　为了达到处理性能和能量消耗之间比例的最优化，用时钟和电源管理器来控制不同模块的时钟频率并处理不同能量管理操作模式之间的转化。时钟和电源管理器为每一个外设提供了固定的时钟，并且为LCD控制器、存储器控制器和CPU提供了可编程的频率时钟，这些时钟均来自内部锁相环时钟源。时钟管理器还可通过关闭不用设备的时钟来减少功率损耗。</p><p>　　电源管理提供了四种工作模式：Turbo模式、运行模式、空闲模式和睡眠模式。Turbo模式下，CPU核运行在峰值频率，为避免内核对外部存储器的等待时间，在该模式下，很少对外部存储器进行存取；运行模式下，CPU核运行于正常标准频率，可以假定内核不断地对外部存储器进行存取，运行速率的减慢对于性能与功耗的最佳平衡是有利的；在空闲模式下，暂停到CPU的时钟，但是使能到外围器件的时钟；睡眠模式下，整个系统将处于最低功耗状态，要唤醒睡眠状态必须重新启动系统。</p><p>　　2.5 存储器和PCMCIA/Compact Flash控制模块</p><p>　　PXA250处理器的外部存储器总线接口支持同步动态存储器(SDRAM)、同步和异步分页模式段、页模式闪存、同步掩码只读存储器(SMROM)、页模式ROM、SRAM、静态段支持可变等待时间的I/O设备(VLIO)、16位的PC卡扩展存储器和Compact Flash。存储器的类型可通过存储器接口配置寄存器决定。</p><p>　　2.6 外围控制模块</p><p>　　PXA250处理器定义了16个通道的DMA控制器。它可响应内部和外部设备的请求，完成数据从主存储器中读出与写入。DMAC用于外围设备与存储系统之间的数据传输。</p><p>　　LCD控制器提供了支持双扫描无源阵列彩显(DSTN，俗称伪彩)或有源阵列彩显(TFT，俗称真彩)屏的接口，并支持单色和多色素格式。它拥有自己独立的双通道DMA控制器，两路通道分别用于单面板和双面板显示。最大支持显示分辨率为1024×1024像素，推荐最高分辨率为800×600像素。在无源单色模式下，最高支持256级灰度。对于彩色显示，不管有源还是无源模式，最高均支持65536种颜色。LCD控制器将帧缓存中的像素编码值，对应于16位宽的256个入口的调色板RAM，根据数据宽度决定彩色的数量。</p><p>　　PXA250处理器支持的串口包括：基于通用串行总线1.1版本的USB客户服务模块接口，它最高支持16个端点外挂，并提供了1个48MHz的内部时钟；3个通用异步收发口(UART)，最高速率230Kbps的全功能UART(完备的握手信号)，最高速率921Kbps蓝牙UART和标准UART；高速红外通信口(FICP)半双工，速率4Mbps，执行4PPM标准；AC97控制器支持AC97 2.0修订版本的多媒体数字信号编解码器，AC97控制器对于立体PCM输入输出，Modem输入输出和单一的麦克风输入都提供了单独的16位通道；I2S控制器为数字立体声标准I2S多媒体数字信号编解码器提供了串行连接，复用AC97控制器引脚；I2C总线接口提供了2个引脚的通用串行通信端口，2个引脚分别用于数据地址和时钟；另外，提供了2个支持MMC或SPI协议，高达20Mbps串行数据传输的MMC卡接口和一个SSP接口。SSP逻辑接口支持National Microwire协议、Texas Instruments协议、同步串行协议(SSP)和Motorola SPI协议，所有这些协议都用于A/D转换、音频和电信多媒体数字信号编解码器和其它满足串行数据传输协议的设备。</p><p>　　3 研华最新推出的XScale单板计算机PCM-7210</p><p>　　PCM-7210是一款集成Intel XScale低功耗RISC处理器PXA250的单板计算机。它由一块支撑板和一块CPU板构成，在CPU板上集成了处理器PXA250、64MB的SDRAM和32MB的Flash存储器，其它的外围器件均置于支撑板之上，包括10Mbps以太网接口，4个全功能RS-232和1个RS-485串行接口，AC97音频接口，2个USB主机端和1个客户端，数字I/O引脚和CF/PCMCIA扩展插槽。此外，还有支持LCD/CRT显示的接口以及智能电源接口。PCM-7210功能框图如图2所示。</p><a href="http://www.developboard.com/upload/article/2004329140021.jpg" target="_blank"></a><p align="center"><img alt="" src="http://www.gd-emb.org/UserFiles/Image/tech_doc/js177.jpg" /></p><a href="http://www.developboard.com/upload/article/2004329140021.jpg" target="_blank"></a><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19974.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:30 <a href="http://www.cnitblog.com/flutist1225/articles/19974.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>嵌入式系统开发（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19972.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:26:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19972.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19972.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19972.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19972.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19972.html</trackback:ping><description><![CDATA[
		<font face="Verdana">如果您刚接触嵌入式开发，那么大量可用的引导装载程序（bootloader）、规模缩小的分发版（distribution）、文件系统和 GUI 看起来可能太多了。但是这些丰富的选项实际上是一种恩赐，允许您调整开发或用户环境以完全符合您的需要。对 Linux 嵌入式开发的概述将帮助您理解所有这些选项。<br />Linux 正在嵌入式开发领域稳步发展。因为 Linux 使用 GPL（请参阅本文后面的参考资料），所以任何对将 Linux 定制于 PDA、掌上机或者可佩带设备感兴趣的人都可以从因特网免费下载其内核和应用程序，并开始移植或开发。许多 Linux 改良品种迎合了嵌入式／实时市场。它们包括 RTLinux（实时 Linux）、uclinux（用于非 MMU 设备的 Linux）、Montavista Linux（用于 ARM、MIPS、PPC 的 Linux 分发版）、ARM-Linux（ARM 上的 Linux）和其它 Linux 系统（请参阅参考资料以链接到本文中提到的这些和其它术语及产品。）<br /><br />嵌入式 Linux 开发大致涉及三个层次：引导装载程序、Linux 内核和图形用户界面（或称 GUI）。在本文中，我们将集中讨论涉及这三层的一些基本概念；深入了解引导装载程序、内核和文件系统是如何交互的；并将研究可用于文件系统、GUI 和引导装载程序的众多选项中的一部分。<br /><br />引导装载程序<br />引导装载程序通常是在任何硬件上执行的第一段代码。在象台式机这样的常规系统中，通常将引导装载程序装入主引导记录（Master Boot Record，(MBR)）中，或者装入 Linux 驻留的磁盘的第一个扇区中。通常，在台式机或其它系统上，BIOS 将控制移交给引导装载程序。这就提出了一个有趣的问题：谁将引导装载程序装入（在大多数情况中）没有 BIOS 的嵌入式设备上呢？<br /><br />解决这个问题有两种常规技术：专用软件和微小的引导代码（tiny bootcode）。<br /><br />专用软件可以直接与远程系统上的闪存设备进行交互并将引导装载程序安装在闪存的给定位置中。闪存设备是与存储设备功能类似的特殊芯片，而且它们能持久存储信息 — 即，在重新引导时不会擦除其内容。<br /><br />这个软件使用目标（在嵌入式开发中，嵌入式设备通常被称为目标）上的 JTAG 端口，它是用于执行外部输入（通常来自主机机器）的指令的接口。JFlash-linux 是一种用于直接写闪存的流行工具。它支持为数众多的闪存芯片；它在主机机器（通常是 i386 机器 — 本文中我们把一台 i386 机器称为主机）上执行并通过 JTAG 接口使用并行端口访问目标的闪存芯片。当然，这意味着目标需要有一个并行接口使它能与主机通信。Jflash-linux 在 Linux 和 Windows 版本中都可使用，可以在命令行中用以下命令启动它：<br /><br />Jflash-linux &lt;bootloader&gt;<br /><br />某些种类的嵌入式设备具有微小的引导代码 — 根据几个字节的指令 — 它将初始化一些 DRAM 设置并启用目标上的一个串行（或者 USB，或者以太网）端口与主机程序通信。然后，主机程序或装入程序可以使用这个连接将引导装载程序传送到目标上，并将它写入闪存。<br /><br />在安装它并给予其控制后，这个引导装载程序执行下列各类功能：<br /><br />初始化 CPU 速度<br />初始化内存，包括启用内存库、初始化内存配置寄存器等<br />初始化串行端口（如果在目标上有的话）<br />启用指令／数据高速缓存<br />设置堆栈指针<br />设置参数区域并构造参数结构和标记（这是重要的一步，因为内核在标识根设备、页面大小、内存大小以及更多内容时要使用引导参数）<br />执行 POST（加电自检）来标识存在的设备并报告任何问题<br />为电源管理提供挂起／恢复支持<br />跳转到内核的开始<br /><br /><br /><br />带有引导装载程序、参数结构、内核和文件系统的系统典型内存布局可能如下所示：<br /><br />清单 1. 典型内存布局 /* Top Of Memory */<br /><br />Bootloader<br />Parameter Area<br />Kernel<br />Filesystem<br /><br />/* End Of Memory */<br /><br /><br /><br /><br />嵌入式设备上一些流行的并可免费使用的 Linux 引导装载程序有 Blob、Redboot 和 Bootldr（请参阅参考资料获得链接）。所有这些引导装载程序都用于基于 ARM 设备上的 Linux，并需要 Jflash-linux 工具用于安装。<br /><br />一旦将引导装载程序安装到目标的闪存中，它就会执行我们上面提到的所有初始化工作。然后，它准备接收来自主机的内核和文件系统。一旦装入了内核，引导装载程序就将控制转给内核。<br /><br />设置工具链<br />设置工具链在主机机器上创建一个用于编译将在目标上运行的内核和应用程序的构建环境 — 这是因为目标硬件可能没有与主机兼容的二进制执行级别。<br /><br />工具链由一套用于编译、汇编和链接内核及应用程序的组件组成。 这些组件包括：<br /><br />Binutils — 用于操作二进制文件的实用程序集合。它们包括诸如 ar、as、objdump、objcopy 这样的实用程序。<br />Gcc — GNU C 编译器。<br />Glibc — 所有用户应用程序都将链接到的 C 库。避免使用任何 C 库函数的内核和其它应用程序可以在没有该库的情况下进行编译。<br /><br /><br /><br />构建工具链建立了一个交叉编译器环境。本地编译器编译与本机同类的处理器的指令。交叉编译器运行在某一种处理器上，却可以编译另一种处理器的指令。重头设置交叉编译器工具链可不是一项简单的任务：它包括下载源代码、修补补丁、配置、编译、设置头文件、安装以及很多很多的操作。另外，这样一个彻底的构建过程对内存和硬盘的需求是巨大的。如果没有足够的内存和硬盘空间，那么在构建阶段由于相关性、配置或头文件设置等问题会突然冒出许多问题。<br /><br />因此能够从因特网上获得已预编译的二进制文件是一件好事（但不太好的一点是，目前它们大多数只限于基于 ARM 的系统，但迟早会改变的）。一些比较流行的已预编译的工具链包括那些来自 Compaq（Familiar Linux ）、LART（LART Linux）和 Embedian（基于 Debian 但与它无关）的工具链 — 所有这些工具链都用于基于 ARM 的平台。<br /><br />内核设置<br />Linux 社区正积极地为新硬件添加功能部件和支持、在内核中修正错误并且及时地进行常规改进。这导致大约每 6 个月（或 6 个月不到）就有一个稳定的 Linux 树的新发行版。不同的维护者维护针对特定体系结构的不同内核树和补丁。当为一个项目选择了一个内核时，您需要评估最新发行版的稳定性如何、它是否符合项目要求和硬件平台、从编程角度来看它的舒适程度以及其它难以确定的方面。还有一点也非常重要：找到需要应用于基本内核的所有补丁，以便为特定的体系结构调整内核。<br /><br />内核布局<br />内核布局分为特定于体系结构的部分和与体系结构无关的部分。内核中特定于体系结构的部分首先执行，设置硬件寄存器、配置内存映射、执行特定于体系结构的初始化，然后将控制转给内核中与体系结构无关的部分。系统的其余部分在这第二个阶段期间进行初始化。内核树下的目录 arch/ 由不同的子目录组成，每个子目录用于一个不同的体系结构（MIPS、ARM、i386、SPARC、PPC 等）。每一个这样的子目录都包含 kernel/ 和 mm/ 子目录，它们包含特定于体系结构的代码来完成象初始化内存、设置 IRQ、启用高速缓存、设置内核页面表等操作。一旦装入内核并给予其控制，就首先调用这些函数，然后初始化系统的其余部分。<br /><br />根据可用的系统资源和引导装载程序的功能，内核可以编译成 vmlinux、Image 或 zImage。vmlinux 和 zImage 之间的主要区别在于 vmlinux 是实际的（未压缩的）可执行文件，而 zImage 是或多或少包含相同信息的自解压压缩文件 — 只是压缩它以处理（通常是 Intel 强制的）640 KB 引导时间的限制。有关所有这些的权威性解释，请参阅 Linux Magazine 的文章“Kernel Configuration: dealing with the unexpected”（请参阅参考资料）。<br /><br />内核链接和装入<br />一旦为目标系统编译了内核后，通过使用引导装载程序（它已经被装入到目标的闪存中），内核就被装入到目标系统的内存（在 DRAM 中或者在闪存中）。通过使用串行、USB 或以太网端口，引导装载程序与主机通信以将内核传送到目标的闪存或 DRAM 中。在将内核完全装入目标后，引导装载程序将控制传递给装入内核的地址。<br /><br />内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节，如文本、数据、init 数据、bass 等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中；换句话说，它将所有输入对象文件都链接到单一的可执行文件中，将该可执行文件的各节装入到指定地址处。vmlinux.lds 是存在于 arch/&lt;target&gt;/ 目录中的内核链接器脚本，它负责链接内核的各个节并将它们装入内存中特定偏移量处。典型的 vmlinux.lds 看起来象这样：<br /><br />清单 2. 典型的 vmlinux.lds 文件 OUTPUT_ARCH(&lt;arch&gt;) /* &lt;arch&gt; includes architecture type */<br />ENTRY(stext) /* stext is the kernel entry point */<br />SECTIONS /* SECTIONS command describes the layout<br />of the output file */<br />{<br />. = TEXTADDR; /* TEXTADDR is LMA for the kernel */<br />.init : { /* Init code and data*/<br />_stext = .; /* First section is stext followed<br />by __init data section */<br />__init_begin = .;<br />*(.text.init)<br />__init_end = .;<br />}<br />.text : { /* Real text segment follows __init_data section */<br />_text = .;<br />*(.text)<br />_etext = .; /* End of text section*/<br />}<br />.data :{<br />_data=.; /* Data section comes after text section */<br />*(.data)<br />_edata=.;<br />} /* Data section ends here */<br />.bss : { /* BSS section follows symbol table section */<br />__bss_start = .;<br />*(.bss)<br />_end = . ; /* BSS section ends here */<br />}<br />}<br /><br /><br /><br /><br />LMA 是装入模块地址；它表示将要装入内核的目标虚拟内存中的地址。TEXTADDR 是内核的虚拟起始地址，并且在 arch/&lt;target&gt;/ 下的 Makefile 中指定它的值。这个地址必须与引导装载程序使用的地址相匹配。<br /><br />一旦引导装载程序将内核复制到闪存或 DRAM 中，内核就被重新定位到 TEXTADDR — 它通常在 DRAM 中。然后，引导装载程序将控制转给这个地址，以便内核能开始执行。<br /><br />参数传递和内核引导<br />stext 是内核入口点，这意味着在内核引导时将首先执行这一节下的代码。它通常用汇编语言编写，并且通常它在 arch/&lt;target&gt;/ 内核目录下。这个代码设置内核页面目录、创建身份内核映射、标识体系结构和处理器以及执行分支 start_kernel（初始化系统的主例程）。<br /><br />start_kernel 调用 setup_arch 作为执行的第一步，在其中完成特定于体系结构的设置。这包括初始化硬件寄存器、标识根设备和系统中可用的 DRAM 和闪存的数量、指定系统中可用页面的数目、文件系统大小等等。所有这些信息都以参数形式从引导装载程序传递到内核。<br /><br />将参数从引导装载程序传递到内核有两种方法：parameter_structure 和标记列表。在这两种方法中，不赞成使用参数结构，因为它强加了限制：指定在内存中，每个参数必须位于 param_struct 中的特定偏移量处。最新的内核期望参数作为标记列表的格式来传递，并将参数转化为已标记格式。param_struct 定义在 include/asm/setup.h 中。它的一些重要字段是：<br /><br />清单 3. 样本参数结构 struct param_struct {<br />unsigned long page_size; /* 0: Size of the page */<br />unsigned long nr_pages; /* 4: Number of pages in the system */<br />unsigned long ramdisk /* 8: ramdisk size */<br />unsigned long rootdev; /* 16: Number representing the root device */<br />unsigned long initrd_start; /* 64: starting address of initial ramdisk */<br />/* This can be either in flash/dram */<br />unsigned long initrd_size; /* 68: size of initial ramdisk */<br />}<br /><br /><br /><br /><br />请注意：这些数表示定义字段的参数结构中的偏移量。这意味着如果引导装载程序将参数结构放置在地址 0xc0000100，那么 rootdev 参数将放置在 0xc0000100 + 16，initrd_start 将放置在 0xc0000100 + 64 等等 — 否则，内核将在解释正确的参数时遇到困难。<br /><br />正如上面提到的，因为从引导装载程序到内核的参数传递会有一些约束条件，所以大多数 2.4.x 系列内核期望参数以已标记的列表格式传递。在已标记的列表中，每个标记由标识被传递参数的 tag_header 以及其后的参数值组成。标记列表中标记的常规格式可以如下所示：<br /><br />清单 4. 样本标记格式。内核通过 &lt;ATAG_TAGNAME&gt; 头来标识每个标记。 #define &lt;aTAG_TAGNAME&gt; &lt;Some Magic number&gt;<br /><br />struct &lt;tag_tagname&gt; {<br />u32 &lt;tag_param&gt;;<br />u32 &lt;tag_param&gt;;<br />};<br /><br />/* Example tag for passing memory information */<br /><br />#define ATAG_MEM 0x54410002 /* Magic number */<br /><br />struct tag_mem32 {<br />u32 size; /* size of memory */<br />u32 start; /* physical start address of memory*/<br />};<br /><br /><br /><br /><br />setup_arch 还需要对闪存存储库、系统寄存器和其它特定设备执行内存映射。一旦完成了特定于体系结构的设置，控制就返回到初始化系统其余部分的 start_kernel 函数。这些附加的初始化任务包含：<br /><br />设置陷阱<br />初始化中断<br />初始化计时器<br />初始化控制台<br />调用 mem_init，它计算各种区域、高内存区等内的页面数量<br />初始化 slab 分配器并为 VFS、缓冲区高速缓存等创建 slab 高速缓存<br />建立各种文件系统，如 proc、ext2 和 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2<br />创建 kernel_thread，它执行文件系统中的 init 命令并显示 lign 提示符。 如果在 /bin、/sbin 或 /etc 中没有 init 程序，那么内核将执行文件系统的 /bin 中的 shell。<br /><br /><br /><br />设备驱动程序<br />嵌入式系统通常有许多设备用于与用户交互，象触摸屏、小键盘、滚动轮、传感器、RA232 接口、LCD 等等。除了这些设备外，还有许多其它专用设备，包括闪存、USB、GSM 等。内核通过所有这些设备各自的设备驱动程序来控制它们，包括 GUI 用户应用程序也通过访问这些驱动程序来访问设备。本节着重讨论通常几乎在每个嵌入式环境中都会使用的一些重要设备的设备驱动程序。<br /><br />帧缓冲区驱动程序<br />这是最重要的驱动程序之一，因为通过这个驱动程序才能使系统屏幕显示内容。帧缓冲区驱动程序通常有三层。最底层是基本控制台驱动程序 drivers/char/console.c，它提供了文本控制台常规接口的一部分。通过使用控制台驱动程序函数，我们能将文本打印到屏幕上 — 但图形或动画还不能（这样做需要使用视频模式功能，通常出现在中间层，也就是 drivers/video/fbcon.c 中）。这个第二层驱动程序提供了视频模式中绘图的常规接口。<br /><br />帧缓冲区是显卡上的内存，需要将它内存映射到用户空间以便可以将图形和文本能写到这个内存段上：然后这个信息将反映到屏幕上。帧缓冲区支持提高了绘图的速度和整体性能。这也是顶层驱动程序引人注意之处：顶层是非常特定于硬件的驱动程序，它需要支持显卡不同的硬件方面 — 象启用／禁用显卡控制器、深度和模式的支持以及调色板等。所有这三层都相互依赖以实现正确的视频功能。与帧缓冲区有关的设备是 /dev/fb0（主设备号 29，次设备号 0）。<br /><br />输入设备驱动程序<br />可触摸板是用于嵌入式设备的最基本的用户交互设备之一 — 小键盘、传感器和滚动轮也包含在许多不同设备中以用于不同的用途。<br /><br />触摸板设备的主要功能是随时报告用户的触摸，并标识触摸的坐标。这通常在每次发生触摸时，通过生成一个中断来实现。<br /><br />然后，这个设备驱动程序的角色是每当出现中断时就查询触摸屏控制器，并请求控制器发送触摸的坐标。一旦驱动程序接收到坐标，它就将有关触摸和任何可用数据的信号发送给用户应用程序，并将数据发送给应用程序（如果可能的话）。然后用户应用程序根据它的需要处理数据。<br /><br />几乎所有输入设备 — 包括小键盘 — 都以类似原理工作。<br /><br />闪存 MTD 驱动程序<br />MTD 设备是象闪存芯片、小型闪存卡、记忆棒等之类的设备，它们在嵌入式设备中的使用正在不断增长。<br /><br />MTD 驱动程序是在 Linux 下专门为嵌入式环境开发的新的一类驱动程序。相对于常规块设备驱动程序，使用 MTD 驱动程序的主要优点在于 MTD 驱动程序是专门为基于闪存的设备所设计的，所以它们通常有更好的支持、更好的管理和基于扇区的擦除和读写操作的更好的接口。Linux 下的 MTD 驱动程序接口被划分为两类模块：用户模块和硬件模块。<br /><br />用户模块<br />这些模块提供从用户空间直接使用的接口：原始字符访问、原始块访问、FTL（闪存转换层，Flash Transition Layer — 用在闪存上的一种文件系统）和 JFS（即日志文件系统，Journaled File System — 在闪存上直接提供文件系统而不是模拟块设备）。用于闪存的 JFS 的当前版本是 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2（稍后将在本文中描述）。<br /><br />硬件模块<br />这些模块提供对内存设备的物理访问，但并不直接使用它们。通过上述的用户模块来访问它们。这些模块提供了在闪存上读、擦除和写操作的实际例程。<br /><br />MTD 驱动程序设置<br />为了访问特定的闪存设备并将文件系统置于其上，需要将 MTD 子系统编译到内核中。这包括选择适当的 MTD 硬件和用户模块。当前，MTD 子系统支持为数众多的闪存设备 — 并且有越来越多的驱动程序正被添加进来以用于不同的闪存芯片。<br /><br />有两个流行的用户模块可启用对闪存的访问：MTD_CHAR 和 MTD_BLOCK。<br /><br />MTD_CHAR 提供对闪存的原始字符访问，而 MTD_BLOCK 将闪存设计为可以在上面创建文件系统的常规块设备（象 IDE 磁盘）。与 MTD_CHAR 关联的设备是 /dev/mtd0、mtd1、mtd2（等等），而与 MTD_BLOCK 关联的设备是 /dev/mtdblock0、mtdblock1（等等）。由于 MTD_BLOCK 设备提供象块设备那样的模拟，通常更可取的是在这个模拟基础上创建象 FTL 和 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 那样的文件系统。<br /><br />为了进行这个操作，可能需要创建分区表将闪存设备分拆到引导装载程序节、内核节和文件系统节中。样本分区表可能包含以下信息：<br /><br />清单 5. MTD 的简单闪存设备分区 struct mtd_partition sample_partition = {<br />{<br />/* First partition */<br />name : bootloader, /* Bootloader section */<br />size : 0x00010000, /* Size */<br />offset : 0, /* Offset from start of flash- location 0x0*/<br />mask_flags : MTD_WRITEABLE /* This partition is not writable */<br />},<br />{ /* Second partition */<br />name : Kernel, /* Kernel section */<br />size : 0x00100000, /* Size */<br />offset : MTDPART_OFS_APPEND, /* Append after bootloader section */<br />mask_flags : MTD_WRITEABLE /* This partition is not writable */<br />},<br />{ /* Third partition */<br />name : <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2, /* <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 filesystem */<br />size : MTDPART_SIZ_FULL, /* Occupy rest of flash */<br />offset : MTDPART_OFS_APPEND /* Append after kernel section */<br />}<br />}<br /><br /><br /><br /><br />上面的分区表使用了 MTD_BLOCK 接口对闪存设备进行分区。这些分区的设备节点是：<br /><br />简单闪存分区的设备节点 User device node Major number Minor number<br /><br />Bootloader /dev/mtdblock0 31 0<br />Kernel /dev/mtdblock1 31 1<br />Filesystem /dev/mtdblock2 31 2<br /><br /><br /><br /><br />在本例中，引导装载程序必须将有关 root 设备节点（/dev/mtdblock2）和可以在闪存中找到文件系统的地址（本例中是 FLASH_BASE_ADDRESS + 0x04000000）的正确参数传递到内核。一旦完成分区，闪存设备就准备装入或挂装文件系统。<br /><br />Linux 中 MTD 子系统的主要目标是在系统的硬件驱动程序和上层，或用户模块之间提供通用接口。硬件驱动程序不需要知道象 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 和 FTL 那样的用户模块使用的方法。所有它们真正需要提供的就是一组对底层闪存系统进行 read、 write 和 erase 操作的简单例程。<br /><br />嵌入式设备的文件系统<br />系统需要一种以结构化格式存储和检索信息的方法；这就需要文件系统的参与。Ramdisk（请参阅参考资料）是通过将计算机的 RAM 用作设备来创建和挂装文件系统的一种机制，它通常用于无盘系统（当然包括微型嵌入式设备，它只包含作为永久存储媒质的闪存芯片）。<br /><br />用户可以根据可靠性、健壮性和／或增强的功能的需求来选择文件系统的类型。下一节将讨论几个可用选项及其优缺点。<br /><br />第二版扩展文件系统（Ext2fs）<br />Ext2fs 是 Linux 事实上的标准文件系统，它已经取代了它的前任 — 扩展文件系统（或 Extfs）。Extfs 支持的文件大小最大为 2 GB，支持的最大文件名称大小为 255 个字符 — 而且它不支持索引节点（包括数据修改时间标记）。Ext2fs 做得更好；它的优点是：<br /><br />Ext2fs 支持达 4 TB 的内存。<br />Ext2fs 文件名称最长可以到 1012 个字符。<br />当创建文件系统时，管理员可以选择逻辑块的大小（通常大小可选择 1024、2048 和 4096 字节）。<br />Ext2fs 了实现快速符号链接：不需要为此目的而分配数据块，并且将目标名称直接存储在索引节点（inode）表中。这使性能有所提高，特别是在速度上。<br /><br /><br /><br />因为 Ext2 文件系统的稳定性、可靠性和健壮性，所以几乎在所有基于 Linux 的系统（包括台式机、服务器和工作站 — 并且甚至一些嵌入式设备）上都使用 Ext2 文件系统。然而，当在嵌入式设备中使用 Ext2fs 时，它有一些缺点：<br /><br />Ext2fs 是为象 IDE 设备那样的块设备设计的，这些设备的逻辑块大小是 512 字节，1 K 字节等这样的倍数。这不太适合于扇区大小因设备不同而不同的闪存设备。<br />Ext2 文件系统没有提供对基于扇区的擦除／写操作的良好管理。在 Ext2fs 中，为了在一个扇区中擦除单个字节，必须将整个扇区复制到 RAM，然后擦除，然后重写入。考虑到闪存设备具有有限的擦除寿命（大约能进行 100,000 次擦除），在此之后就不能使用它们，所以这不是一个特别好的方法。<br />在出现电源故障时，Ext2fs 不是防崩溃的。<br />Ext2 文件系统不支持损耗平衡，因此缩短了扇区／闪存的寿命。（损耗平衡确保将地址范围的不同区域轮流用于写和／或擦除操作以延长闪存设备的寿命。）<br />Ext2fs 没有特别完美的扇区管理，这使设计块驱动程序十分困难。<br /><br /><br /><br />由于这些原因，通常相对于 Ext2fs，在嵌入式环境中使用 MTD/<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 组合是更好的选择。<br /><br />用 Ramdisk 挂装 Ext2fs<br />通过使用 Ramdisk 的概念，可以在嵌入式设备中创建并挂装 Ext2 文件系统（以及用于这一目的的任何文件系统）。<br /><br />清单 6. 创建一个简单的基于 Ext2fs 的 Ramdisk mke2fs -vm0 /dev/ram 4096<br />mount -t ext2 /dev/ram /mnt<br />cd /mnt<br />cp /bin, /sbin, /etc, /dev ... files in mnt<br />cd ../<br />umount /mnt<br />dd if=/dev/ram bs=1k count=4096 of=ext2ramdisk<br /><br /><br /><br /><br />mke2fs 是用于在任何设备上创建 ext2 文件系统的实用程序 — 它创建超级块、索引节点以及索引节点表等等。<br /><br />在上面的用法中，/dev/ram 是上面构建有 4096 个块的 ext2 文件系统的设备。然后，将这个设备（/dev/ram）挂装在名为 /mnt 的临时目录上并且复制所有必需的文件。一旦复制完这些文件，就卸装这个文件系统并且设备（/dev/ram）的内容被转储到一个文件（ext2ramdisk）中，它就是所需的 Ramdisk（Ext2 文件系统）。<br /><br />上面的顺序创建了一个 4 MB 的 Ramdisk，并用必需的文件实用程序来填充它。<br /><br />一些要包含在 Ramdisk 中的重要目录是：<br /><br />/bin — 保存大多数象 init、busybox、shell、文件管理实用程序等二进制文件。<br />/dev — 包含用在设备中的所有设备节点<br />/etc — 包含系统的所有配置文件<br />/lib — 包含所有必需的库，如 libc、libdl 等<br /><br /><br /><br />日志闪存文件系统，版本 2（<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2）<br />瑞典的 Axis Communications 开发了最初的 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>，Red Hat 的 David Woodhouse 对它进行了改进。 第二个版本，<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2，作为用于微型嵌入式设备的原始闪存芯片的实际文件系统而出现。<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 文件系统是日志结构化的，这意味着它基本上是一长列节点。每个节点包含有关文件的部分信息 — 可能是文件的名称、也许是一些数据。相对于 Ext2fs，<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 因为有以下这些优点而在无盘嵌入式设备中越来越受欢迎：<br /><br /><font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 在扇区级别上执行闪存擦除／写／读操作要比 Ext2 文件系统好。<br /><font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 提供了比 Ext2fs 更好的崩溃／掉电安全保护。当需要更改少量数据时，Ext2 文件系统将整个扇区复制到内存（DRAM）中，在内存中合并新数据，并写回整个扇区。这意味着为了更改单个字，必须对整个扇区（64 KB）执行读／擦除／写例程 — 这样做的效率非常低。要是运气差，当正在 DRAM 中合并数据时，发生了电源故障或其它事故，那么将丢失整个数据集合，因为在将数据读入 DRAM 后就擦除了闪存扇区。<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 附加文件而不是重写整个扇区，并且具有崩溃／掉电安全保护这一功能。<br />这可能是最重要的一点：<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 是专门为象闪存芯片那样的嵌入式设备创建的，所以它的整个设计提供了更好的闪存管理。<br /><br /><br /><br />因为本文主要是写关于闪存设备的使用，所以在嵌入式环境中使用 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 的缺点很少：<br /><br />当文件系统已满或接近满时，<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 会大大放慢运行速度。这是因为垃圾收集的问题（更多信息，请参阅参考资料）。<br /><br /><br /><br />创建 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 文件系统<br />在 Linux 下，用 mkfs.<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 命令创建 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 文件系统（基本上是使用 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 的 Ramdisk）。<br /><br />清单 7. 创建 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 文件系统 mkdir <font style="BACKGROUND-COLOR: #00ff00">jffs</font>file<br />cd <font style="BACKGROUND-COLOR: #00ff00">jffs</font>file<br /><br />/* copy all the /bin, /etc, /usr/bin, /sbin/ binaries and /dev entries<br />that are needed for the filesystem here */<br /><br />/* Type the following command under <font style="BACKGROUND-COLOR: #00ff00">jffs</font>file directory to create the <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 Image */<br /><br />./mkfs.<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 -e 0x40000 -p -o ../<font style="BACKGROUND-COLOR: #00ff00">jffs</font>.image<br /><br /><br /><br /><br />上面显示了 mkfs.<font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 的典型用法。-e 选项确定闪存的擦除扇区大小（通常是 64 千字节）。-p 选项用来在映像的剩余空间用零填充。-o 选项用于输出文件，通常是 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 文件系统映像 — 在本例中是 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>.image。一旦创建了 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 文件系统，它就被装入闪存中适当的位置（引导装载程序告知内核查找文件系统的地址）以便内核能挂装它。<br /><br />tmpfs<br />当 Linux 运行于嵌入式设备上时，该设备就成为功能齐全的单元，许多守护进程会在后台运行并生成许多日志消息。另外，所有内核日志记录机制，象 syslogd、dmesg 和 klogd，会在 /var 和 /tmp 目录下生成许多消息。由于这些进程产生了大量数据，所以允许将所有这些写操作都发生在闪存是不可取的。由于在重新引导时这些消息不需要持久存储，所以这个问题的解决方案是使用 tmpfs。<br /><br />tmpfs 是基于内存的文件系统，它主要用于减少对系统的不必要的闪存写操作这一唯一目的。因为 tmpfs 驻留在 RAM 中，所以写／读／擦除的操作发生在 RAM 中而不是在闪存中。因此，日志消息写入 RAM 而不是闪存中，在重新引导时不会保留它们。tmpfs 还使用磁盘交换空间来存储，并且当为存储文件而请求页面时，使用虚拟内存（VM）子系统。<br /><br />tmpfs 的优点包括：<br /><br />动态文件系统大小 — 文件系统大小可以根据被复制、创建或删除的文件或目录的数量来缩放。使得能够最理想地使用内存。<br />速度 — 因为 tmpfs 驻留在 RAM，所以读和写几乎都是瞬时的。即使以交换的形式存储文件，I/O 操作的速度仍非常快。<br /><br /><br /><br />tmpfs 的一个缺点是当系统重新引导时会丢失所有数据。因此，重要的数据不能存储在 tmpfs 上。<br /><br />挂装 tmpfs<br />诸如 Ext2fs 和 <font style="BACKGROUND-COLOR: #00ff00">jffs</font>2 等大多数其它文件系统都驻留在底层块设备之上，而 tmpfs 与它们不同，它直接位于 VM 上。因而，挂装 tmpfs 文件系统是很简单的事：<br /><br />清单 8. 挂装 tmpfs /* Entries in /etc/rc.d/rc.sysinit for creating/using tmpfs */<br /><br /># mount -t tmpfs tmpfs /var -o size=512k<br /># mkdir -p /var/tmp<br /># mkdir -p /var/log<br /># ln -s /var/tmp /tmp<br /><br /><br /><br /><br />上面的命令将在 /var 上创建 tmpfs 并将 tmpfs 的最大大小限制为 512 K。同时，tmp/ 和 log/ 目录成为 tmpfs 的一部分以便在 RAM 中存储日志消息。<br /><br />如果您想将 tmpfs 的一个项添加到 /etc/fstab，那么它可能看起来象这样：<br /><br />tmpfs /var tmpfs size=32m 0 0<br /><br />这将在 /var 上挂装一个新的 tmpfs 文件系统。<br /><br />图形用户界面（GUI）选项<br />从用户的观点来看，图形用户界面（GUI）是系统的一个最至关重要的方面：用户通过 GUI 与系统进行交互。所以 GUI 应该易于使用并且非常可靠。但它还需要是有内存意识的，以便在内存受限的、微型嵌入式设备上可以无缝执行。所以，它应该是轻量级的，并且能够快速装入。<br /><br />另一个要考虑的重要方面涉及许可证问题。一些 GUI 分发版具有允许免费使用的许可证，甚至在一些商业产品中也是如此。另一些许可证要求如果想将 GUI 合并入项目中则要支付版税。<br /><br />最后，大多数开发人员可能会选择 XFree86，因为 XFree86 为他们提供了一个能使用他们喜欢的工具的熟悉环境。但是市场上较新的 GUI，象 Century Software 的 Microwindows（Nano-X）和 Trolltech 的 QT/Embedded，与 X 在嵌入式 Linux 的竞技舞台中展开了激烈竞争，这主要是因为它们占用很少的资源、执行的速度很快并且具有定制窗口构件的支持。<br /><br />让我们看一看这些选项中的每一个。<br /><br />Xfree86 4.X（带帧缓冲区支持的 X11R6.4）<br />XFree86 Project, Inc. 是一家生产 XFree86 的公司，该产品是一个可以免费重复分发、开放源码的 X Window 系统。X Window 系统（X11）为应用程序以图形方式进行显示提供了资源，并且它是 UNIX 和类 UNIX 的机器上最常用的窗口系统。它很小但很有效，它运行在为数众多的硬件上，它对网络透明并且有良好的文档说明。X11 为窗口管理、事件处理、同步和客户机间通信提供强大的功能 — 并且大多数开发人员已经熟悉了它的 API。它具有对内核帧缓冲区的内置支持，并占用非常少的资源 — 这非常有助于内存相对较少的设备。X 服务器支持 VGA 和非 VGA 图形卡，它对颜色深度 1、2、4、8、16 和 32 提供支持，并对渲染提供内置支持。最新的发行版是 XFree86 4.1.0。<br /><br />它的优点包括：<br /><br />帧缓冲区体系结构的使用提高了性能。<br />占用的资源相对很小 — 大小在 600 K 到 700 K 字节的范围内，这使它很容易在小型设备上运行。<br />非常好的支持：在线有许多文档可用，还有许多专用于 XFree86 开发的邮递列表。<br />X API 非常适合扩展。<br /><br /><br /><br />它的缺点包括：<br /><br />比最近出现的嵌入式 GUI 工具性能差。<br />此外，当与 GUI 中最新的开发 — 象专门为嵌入式环境设计的 Nano-X 或 QT/Embedded — 相比时，XFree86 似乎需要更多的内存。<br /><br /><br /><br />Microwindows<br />Microwindows 是 Century Software 的开放源代码项目，设计用于带小型显示单元的微型设备。它有许多针对现代图形视窗环境的功能部件。象 X 一样，有多种平台支持 Microwindows。<br /><br />Microwindows 体系结构是基于客户机／服务器的并且具有分层设计。最底层是屏幕和输入设备驱动程序（关于键盘或鼠标）来与实际硬件交互。在中间层，可移植的图形引擎提供对线的绘制、区域的填充、多边形、裁剪以及颜色模型的支持。<br /><br />在最上层，Microwindows 支持两种 API：Win32/WinCE API 实现，称为 Microwindows；另一种 API 与 GDK 非常相似，它称为 Nano-X。Nano-X 用在 Linux 上。它是象 X 的 API，用于占用资源少的应用程序。<br /><br />Microwindows 支持 1、2、4 和 8 bpp（每像素的位数）的 palletized 显示，以及 8、16、24 和 32 bpp 的真彩色显示。Microwindows 还支持使它速度更快的帧缓冲区。Nano-X 服务器占用的资源大约在 100 K 到 150 K 字节。<br /><br />原始 Nano-X 应用程序的平均大小在 30 K 到 60 K。由于 Nano-X 是为有内存限制的低端设备设计的，所以它不象 X 那样支持很多函数，因此它实际上不能作为微型 X（Xfree86 4.1）的替代品。<br /><br />可以在 Microwindows 上运行 FLNX，它是针对 Nano-X 而不是 X 进行修改的 FLTK（快速轻巧工具箱(Fast Light Toolkit)）应用程序开发环境的一个版本。本文中描述 FLTK。<br /><br />Nano-X 的优点包括：<br /><br />与 Xlib 实现不同，Nano-X 仍在每个客户机上同步运行，这意味着一旦发送了客户机请求包，服务器在为另一个客户机提供服务之前一直等待，直到整个包都到达为止。这使服务器代码非常简单，而运行的速度仍非常快。<br />占用很小的资源<br /><br /><br /><br />Nano-X 的缺点包括：<br /><br />联网功能部件至今没有经过适当地调整（特别是网络透明性）。<br />还没有太多现成的应用程序可用。<br />与 X 相比，Nano-X 虽然近来正在加速开发，但仍没有那么多文档说明而且没有很好的支持，但这种情形会有所改变。<br /><br /><br /><br />Microwindows 上的 FLTK API<br />FLTK 是一个简单但灵活的 GUI 工具箱，它在 Linux 世界中赢得越来越多的关注，它特别适用于占用资源很少的环境。它提供了您期望从 GUI 工具箱中获得的大多数窗口构件，如按钮、对话框、文本框以及出色的“赋值器”选择（用于输入数值的窗口构件）。还包括滑动器、滚动条、刻度盘和其它一些构件。<br /><br />针对 Microwindows GUI 引擎的 FLTK 的 Linux 版本被称为 FLNX。FLNX 由两个组件构成：Fl_Widget 和 FLUID。Fl_Widget 由所有基本窗口构件 API 组成。FLUID（快速轻巧的用户界面设计器(Fast Light User Interface Designer, FLUID)）是用来产生 FLTK 源代码的图形编辑器。总的来说，FLNX 是能用来为嵌入式环境创建应用程序的一个出色的 UI 构建器。<br /><br />Fl_Widget 占用的资源大约是 40 K 到 48 K，而 FLUID（包括了每个窗口构件）大约占用 380 K。这些非常小的资源占用率使 Fl_Widget 和 FLUID 在嵌入式开发世界中非常受欢迎。<br /><br />优点包括：<br /><br />习惯于在象 Windows 这样已建立得较好的环境中开发基于 GUI 的应用程序的任何人都会非常容易地适应 FLTK 环境。<br />它的文档包括一本十分完整且编写良好的手册。<br />它使用 LGPL 进行分发，所以开发人员可以灵活地发放他们应用程序的许可证。<br />FLTK 是一个 C++ 库（Perl 和 Python 绑定也可用）。面向对象模型的选择是一个好的选择，因为大多数现代 GUI 环境都是面向对象的；这也使将编写的应用程序移植到类似的 API 中变得更容易。<br />Century Software 的环境提供了几个有用的工具，诸如 ScreenToP 和 ViewML 浏览器。<br /><br /><br /><br />它的缺点是：<br /><br />普通的 FLTK 可以与 X 和 Windows API 一同工作，而 FLNX 不能。它与 X 的不兼容性阻碍了它在许多项目中的使用。<br /><br /><br /><br />Qt/Embedded<br />Qt/Embedded 是 Trolltech 新开发的用于嵌入式 Linux 的图形用户界面系统。Trolltech 最初创建 Qt 作为跨平台的开发工具用于 Linux 台式机。它支持各种有 UNIX 特点的系统以及 Microsoft Windows。KDE — 最流行的 Linux 桌面环境之一，就是用 Qt 编写的。<br /><br />Qt/Embedded 以原始 Qt 为基础，并做了许多出色的调整以适用于嵌入式环境。Qt Embedded 通过 Qt API 与 Linux I/O 设施直接交互。那些熟悉并已适应了面向对象编程的人员将发现它是一个理想环境。而且，面向对象的体系结构使代码结构化、可重用并且运行快速。与其它 GUI 相比，Qt GUI 非常快，并且它没有分层，这使得 Qt/Embedded 成为用于运行基于 Qt 的程序的最紧凑环境。<br /><br />Trolltech 还推出了 Qt 掌上机环境（Qt Palmtop Environment，俗称 Qpe）。Qpe 提供了一个基本桌面窗口，并且该环境为开发提供了一个易于使用的界面。Qpe 包含全套的个人信息管理（Personal Information Management (PIM)）应用程序、因特网客户机、实用程序等等。然而，为了将 Qt/Embedded 或 Qpe 集成到一个产品中，需要从 Trolltech 获得商业许可证。（原始 Qt 自版本 2.2 以后就可以根据 GPL 获得 。）<br /><br />它的优点包括：<br /><br />面向对象的体系结构有助于更快地执行<br />占用很少的资源，大约 800 K<br />抗锯齿文本和混合视频的象素映射<br /><br /><br /><br />它的缺点是：<br /><br />Qt/Embedded 和 Qpe 只能在获得商业许可证的情况下才能使用。<br /><br /><br /><br />结束语<br />嵌入式 Linux 开发正迅速地发展着。您必须学习并从引导装载程序和分发版到文件系统和 GUI 中的每一个事物的各种选项中作出选择。但是要感谢有这种选择自由度以及非常活跃的 Linux 社区，Linux 上的嵌入式开发已经达到了新的境界，并且调整模块以适合您的规范从未比现在更简单。这已经导致出现了许多时新的手持和微型设备作为开放盒，这是件好事 — 因为事实是您不必成为一个专家从这些模块中进行选择来调整您的设备以满足您自己的要求和需要。<br /><br />我们希望这篇对嵌入式 Linux 领域的介绍性概述能激起您进行试验的欲望，并且希望您将体会摆弄微型设备的乐趣以满足您的爱好。为进一步有助于您的项目，请参阅下面的“参考资料”，链接到有关我们这里已经概述的技术的更深入的信息。 </font>
		<br />
		<br />
<img src ="http://www.cnitblog.com/flutist1225/aggbug/19972.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:26 <a href="http://www.cnitblog.com/flutist1225/articles/19972.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux网络接口源码导读（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19971.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:23:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19971.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19971.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19971.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19971.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19971.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: BSD是UNIX系统中通用的网络接口，它不仅支持各种不同的网络类型，而且也是一种内部进程之间的通信机制。两个通信进程都用一个套接口来描述通信链路的两端。套接口可以认为是一种特殊的管道，但和管道不同的是，套接口对于可以容纳的数据的大小没有限制。    Linux支持多种类型的套接口，也叫做套接口寻址族，这是因为每种类型的套接口都有自己的寻址方法。Linux支持以下的套接口类型：        UNI...&nbsp;&nbsp;<a href='http://www.cnitblog.com/flutist1225/articles/19971.html'>阅读全文</a><img src ="http://www.cnitblog.com/flutist1225/aggbug/19971.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:23 <a href="http://www.cnitblog.com/flutist1225/articles/19971.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Install Gentoo From Stage 3 （转）</title><link>http://www.cnitblog.com/flutist1225/articles/19970.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:19:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19970.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19970.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19970.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19970.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19970.html</trackback:ping><description><![CDATA[
		<p>Install Gentoo From Stage 3<br />bladebai 2005.12.28</p>
		<p>
				<a href="http://www.gentoo.org/doc/zh_tw/handbook/2005.0/handbook-x86.xml?style=printable&amp;full=1">http://www.gentoo.org/doc/zh_tw/handbook/2005.0/handbook-x86.xml?style=printable&amp;full=1</a>
		</p>passwd<br />输入root密码<br /><br />/etc/init.d/sshd start<br /><br /><br />fdisk分区<br />/dev/hda1 ext2 32M 開機分割區（Boot partition） <br />/dev/hda2 (swap) 512M Swap 分割區 <br />/dev/hda3 ext3 磁碟剩下的空間 根 或是 主分割區（Root partition） <br /><br /><br />mke2fs /dev/hda1<br />mke2fs -j /dev/hda3<br /><br />mkswap /dev/hda2<br />swapon /dev/hda2<br /><br />mount -t ext3 /dev/hda3 /mnt/gentoo<br />mkdir /mnt/gentoo/boot<br />mount /dev/hda1 /mnt/gentoo/boot<br /><br />date<br />设置时间<br /><br />cd /mnt/gentoo<br />tar -xvjpf /mnt/cdrom/stages/stage3-x86-2005.1-r1.tar.bz2<br />tar -xvjf /mnt/cdrom/snapshots/portage-20050709.tar.bz2 -C /mnt/gentoo/usr<br /><br />mkdir /mnt/gentoo/usr/portage/distfiles<br />cp /mnt/cdrom/distfiles/* /mnt/gentoo/usr/portage/distfiles/<br /><br />mount -t proc none /mnt/gentoo/proc<br />cp -L /etc/resolv.conf /mnt/gentoo/etc/resolv.conf<br /><br />chroot /mnt/gentoo /bin/bash<br />env-update<br />source /etc/profile<br /><br />ln -sf /usr/share/zoneinfo/Asia /etc/localtime<br /><br />emerge gentoo-sources<br /><br />rm /usr/src/linux<br />cd /usr/src<br />ln -s linux-2.6.12-gentoo-r10 linux<br /><br />emerge genkernel<br />zcat /proc/config.gz &gt; /usr/share/genkernel/x86/kernel-config-2.6<br />genkernel all<br />emerge -k coldplug<br />rc-update add coldplug boot<br /><br />{<br />cd /usr/src/linux<br />make menuconfig<br /><br />General setup  ---&gt;<br />    [*] Support for hot-pluggable devices<br />Processor type and features ---&gt;<br />    Subarchitecture Type (PC-compatible)  ---&gt;<br /> （依照您的處理器所更換）<br /> (Athlon/Duron/K7) Processor family<br /><br />File systems ---&gt;<br />  Pseudo Filesystems ---&gt;<br />    &lt;*&gt; /proc file system support<br />    &lt; &gt; /dev file system support (OBSOLETE)<br />    &lt;*&gt;   Automatically mount at boot<br />    &lt;*&gt; Virtual memory file system support (former shm fs)<br /><br />（按照您系統所需要的檔案系統開啟以下選項）<br />  &lt;*&gt; Reiserfs support<br />  &lt;*&gt; Ext3 journalling file system support<br />  &lt;*&gt; JFS filesystem support<br />  &lt;*&gt; Second extended fs support<br />  &lt;*&gt; XFS filesystem support<br /><br />Device Drivers ---&gt;<br />  ATA/IDE/MFM/RLL support ---&gt; <br />    [*] Generic PCI bus-master DMA support<br />    [*]   Use PCI DMA by default when available<br /><br />Device Drivers ---&gt;<br />  Networking support ---&gt;<br />    &lt;*&gt; PPP (point-to-point protocol) support<br />    &lt;*&gt;   PPP support for async serial ports<br />    &lt;*&gt;   PPP support for sync tty ports<br /><br />Device Drivers ---&gt;<br />  USB Support ---&gt;<br />  &lt;*&gt;   USB Human Interface Device (full HID) support<br />  [*]   HID input layer support <br /><br /><br />make &amp;&amp; make modules_install<br /><br />cp arch/i386/boot/bzImage /boot/kernel-2.6.12-gentoo-r10<br />cp .config /boot/config-2.6.12-gentoo-r10<br />}<br /><br />nano -w /etc/fstab<br /><br />/dev/hda1   /boot     ext2    defaults,noatime     1 2<br />/dev/hda2   none      swap    sw                0 0<br />/dev/hda3   /         ext3    noatime           0 1<br />none        /proc     proc    defaults          0 0<br />none        /dev/shm  tmpfs   nodev,nosuid,noexec  0 0<br />/dev/cdroms/cdrom0    /mnt/cdrom    auto      noauto,user    0 0<br /><br />echo bladebai &gt; /etc/hostname<br /><br />echo homenetwork &gt; /etc/dnsdomainname<br />rc-update add domainname default<br /><br />nano -w /etc/conf.d/net<br /><br />iface_eth0="dhcp"<br />dhcpcd_eth0="-HD"<br /># rp-pppoe<br />#iface_eth0="up"<br /><br />rc-update add net.eth0 default<br /><br />nano -w /etc/hosts<br /><br />127.0.0.1     localhost<br /><br />passwd<br />输入root密码<br /><br />echo "tts/0" &gt;&gt; /etc/securetty<br /><br />nano -w /etc/rc.conf<br /><br />emerge syslog-ng<br />rc-update add syslog-ng default<br /><br />emerge vixie-cron<br />rc-update add vixie-cron default<br /><br />emerge slocate<br /><br />檔案系統     工具         安裝指令 <br />XFS         xfsprogs     emerge xfsprogs <br />ReiserFS     reiserfsprogs     emerge reiserfsprogs <br />JFS         jfsutils     emerge jfsutils <br /><br />emerge dhcpcd<br /><br />USE="-X" emerge rp-pppoe<br /><br />emerge grub<br /><br />nano -w /boot/grub/grub.conf<br /><br />default 0<br />timeout 30<br />splashimage=(hd0,0)/grub/splash.xpm.gz<br /><br />title=Gentoo Linux 2.6.12-r10<br />root (hd0,0)<br />kernel /kernel-genkernel-x86-2.6.12-gentoo-r10 root=/dev/ram0 init=/linuxrc ramdisk=8192 real_root=/dev/hda3 udev<br />initrd /initramfs-genkernel-x86-2.6.12-gentoo-r10<br /><br />title=Windows XP<br />root (hd0,5)<br />makeactive<br />chainloader +1<br /><br />cp /proc/mounts /etc/mtab<br />grub-install /dev/hda<br /><br />{<br /># 將為開機預設。0為第一，1為第二，以此類推。<br />default 0<br /># 使用預設前等待的開機秒數。<br />timeout 30<br /># 將漂亮又肥大的 splash-image 啟用：）<br /># 如果您沒有安裝顯示卡，請註解這行<br />splashimage=(hd0,0)/grub/splash.xpm.gz<br /><br />title=Gentoo Linux 2.6.11-r3<br /># 儲存核心檔的分割區（或是作業系統）<br />root (hd0,0)<br />kernel /kernel-2.6.11-gentoo-r3 root=/dev/hda3<br /><br /># 以下三行為 Windows 系統適合的雙重開機。<br /># 在我們的例子，Windows是放在 /dev/hda6<br />title=Windows XP<br />rootnoverify (hd0,5)<br />makeactive<br />chainloader +1<br /><br />grub<br /><br />grub&gt; root (hd0,0)          (指定你的 /boot 分割區位置) <br />grub&gt; setup (hd0)           (安裝 GRUB 到 MBR) <br />grub&gt; quit                  (離開 GRUB shell) <br />}<br /><br />exit<br />cd<br />umount /mnt/gentoo/boot /mnt/gentoo/proc /mnt/gentoo<br />reboot<br /><br />ln -s /usr/lib/gcc-lib/i686-linux-gnu /usr/lib/gcc-lib/i386-linux-gnu<br /><br />emerge --sync<br />emerge kde<br /><br />{<br />mount /mnt/cdrom<br />export PKGDIR="/mnt/cdrom"<br /><br />emerge --usepkg kde<br />}<br /><br />reboot<br /><br />echo "startkde" &gt; ~/.xinitrc<br />startx<img src ="http://www.cnitblog.com/flutist1225/aggbug/19970.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:19 <a href="http://www.cnitblog.com/flutist1225/articles/19970.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HOWTO Custom Stage4 （转）</title><link>http://www.cnitblog.com/flutist1225/articles/19968.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:16:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19968.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19968.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19968.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19968.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19968.html</trackback:ping><description><![CDATA[
		<p align="justify">
		</p>
		<table class="toc" id="toc">
				<tbody>
						<tr>
								<td>
										<div id="toctitle">
												<h2>Contents</h2>
												<span class="toctoggle">[<a class="internal" id="togglelink" href="javascript:toggleToc()"><font color="#002c99">hide</font></a>]</span>
										</div>
										<ul>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Introduction">
																<font color="#002c99">
																		<span class="tocnumber">1</span>
																		<span class="toctext">Introduction</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Step1">
																<font color="#002c99">
																		<span class="tocnumber">2</span>
																		<span class="toctext">Step1</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Step2">
																<font color="#002c99">
																		<span class="tocnumber">3</span>
																		<span class="toctext">Step2</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Step3">
																<font color="#002c99">
																		<span class="tocnumber">4</span>
																		<span class="toctext">Step3</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Step4">
																<font color="#002c99">
																		<span class="tocnumber">5</span>
																		<span class="toctext">Step4</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Step5">
																<font color="#002c99">
																		<span class="tocnumber">6</span>
																		<span class="toctext">Step5</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Install_from_a_stage4">
																<font color="#002c99">
																		<span class="tocnumber">7</span>
																		<span class="toctext">Install from a stage4</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Partition_table_tip">
																<font color="#002c99">
																		<span class="tocnumber">8</span>
																		<span class="toctext">Partition table tip</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#LILO_tip">
																<font color="#002c99">
																		<span class="tocnumber">9</span>
																		<span class="toctext">LILO tip</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#GRUB_tip">
																<font color="#002c99">
																		<span class="tocnumber">10</span>
																		<span class="toctext">GRUB tip</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Privacy_tip">
																<font color="#002c99">
																		<span class="tocnumber">11</span>
																		<span class="toctext">Privacy tip</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Credits">
																<font color="#002c99">
																		<span class="tocnumber">12</span>
																		<span class="toctext">Credits</span>
																</font>
														</a>
												</li>
												<li class="toclevel-1">
														<a href="http://gentoo-wiki.com/HOWTO_Custom_Stage4#Feedback">
																<font color="#002c99">
																		<span class="tocnumber">13</span>
																		<span class="toctext">Feedback</span>
																</font>
														</a>
												</li>
										</ul>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<script type="text/javascript"><![CDATA[f (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); }]]&gt;</script>
		</p>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=1"><font color="#002c99">edit</font></a>]</div>
		<a name="Introduction">
		</a>
		<h2>Introduction</h2>
		<p>This article will demonstrate how to create a custom stage4 archive. A stage4 archive is an image of your entire root partition. The primary reason to create a stage4 archive is to provide for quick recovery in the event of disk failure. A stage4 archive is the same as a stage3, only you can select the CFLAGS you want to use and what other software you want installed. You can adapt this method to suit your personal uses. </p>
		<p>If you want a generic stage4 that you can install on multiple systems, use <a title="Genkernel" href="http://gentoo-wiki.com/Genkernel"><font color="#002c99">genkernel</font></a> to set up your kernel. This will ensure that on boot, the kernel will work like the one on the livecd. You may also want to use less restrictive CFLAGS (i.e., MCPU instead of MARCH). You may still have to modify <font color="#008000">/etc/fstab</font> and the USE flags after extracting the stage4, to suit the user. </p>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=2"><font color="#002c99">edit</font></a>]</div>
		<a name="Step1">
		</a>
		<h2>Step1</h2>
		<p>
				<a class="external text" title="http://www.gentoo.org/doc/en/handbook/index.xml" href="http://www.gentoo.org/doc/en/handbook/index.xml" rel="nofollow">
						<font color="#002c99">Install Gentoo</font>
				</a>. </p>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=3"><font color="#002c99">edit</font></a>]</div>
		<a name="Step2">
		</a>
		<h2>Step2</h2>
		<p>Configure all drivers (i.e. sound, video and USB) and install any software you want to be included on your stage4. For example, I would install X, Xfce4, Sun's JDK, CVS, Emacs, Thunderbird, and Firefox. </p>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=4"><font color="#002c99">edit</font></a>]</div>
		<a name="Step3">
		</a>
		<h2>Step3</h2>
		<p>Make a copy of your <font color="#008000">/boot</font> partition: </p>
		<pre>root# mount /bootroot# cp -R /boot /bootcpyroot# umount /boot</pre>
		<p>Alternatively, you can just mount <font color="#008000">/boot</font> before you tar everything up. However, there may be problems with this alternate method if you use the stage4 on systems with different hardware configurations. </p>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=5"><font color="#002c99">edit</font></a>]</div>
		<a name="Step4">
		</a>
		<h2>Step4</h2>
		<p>Clear out temporary files in <font color="#008000">/usr/portage/distfiles</font> and <font color="#008000">/var/tmp</font> to save space. </p>
		<p align="justify">
		</p>
		<table cellspacing="0" cellpadding="0" width="75%">
				<tbody>
						<tr>
								<td style="BORDER-RIGHT: #ff7070 2px solid; PADDING-RIGHT: 4px; BORDER-TOP: #ff7070 2px solid; PADDING-LEFT: 4px; PADDING-BOTTOM: 0px; BORDER-LEFT: #ff7070 2px solid; PADDING-TOP: 0px; BORDER-BOTTOM: #ff7070 2px solid; BACKGROUND-COLOR: #ffc1c1">
										<font size="-1">
												<b>Warning:</b> Do NOT remove "/var/tmp/portage/homedir"! It will result in problems with portage.</font>
								</td>
						</tr>
				</tbody>
		</table>
		<p>I would use caution in clearing <font color="#008000">/var/tmp</font>. On my system, config information was stored in there for sound and portage. This caused problems later, when I restored my system and realized that I was missing some important information. It may be better to have a slightly larger tarball than to find that the restored system is incomplete. </p>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=6"><font color="#002c99">edit</font></a>]</div>
		<a name="Step5">
		</a>
		<h2>Step5</h2>
		<p>After everything is set up correctly from the previous sections, we will create the archive: </p>
		<pre>root# tar cjpf /path/to/save/at/stage4.tar.bz2 / --exclude=stage4.tar.bz2 --exclude=/proctar options we used: c - create archive j - use bzip2 compression p - preserve file attributes (don't leave this out!!) f - specify file name</pre>
		<p>Be sure to look at the tar manpage </p>
		<p>It will probably take a long time to create the archive depending on how much you have installed. I usually save the archive on a spare disk I have in my system that I use for backups. You can also burn it to a cd or dvd. </p>
		<p>If it is too large to fit on a cd you will have to split it up. Large tar files can be split up with 'split'. The parts can be joined later with 'cat'. ('man split', and 'man cat' for more info). </p>
		<p>
				<a class="external text" title="http://forums.gentoo.org/viewtopic.php?p=1035688#1035688" href="http://forums.gentoo.org/viewtopic.php?p=1035688#1035688" rel="nofollow">
						<font color="#002c99">fdavid</font>
				</a> suggested: </p>
		<ul>
				<li>Large tar files can be split with a small utility called split. The parts can be joined later with cat. 
</li>
				<li>The exclude option of tar must be heavily utilized, because there are many directories which must be excluded when backing up your root. Exclude can also go for /usr/portage, if you want a smaller archive. Excluding this might be prudent, since the contents of /usr/portage will be outdated when you restore the archive. </li>
		</ul>
		<p>The directories I exclude when backing up my root: </p>
		<ul>
				<li>
						<font color="#008000">/mnt</font>
				</li>
				<li>
						<font color="#008000">/proc</font>
				</li>
				<li>
						<font color="#008000">/sys</font>
				</li>
				<li>
						<font color="#008000">/tmp</font>
				</li>
				<li>+ any directory, which is backed up separately. </li>
		</ul>
		<p>Small script, which I use for making backup of my <font color="#008000">/home</font>. (The /dev/hdX corresponds to the /mnt/tarbackup directory in /etc/fstab.) </p>
		<p align="justify">
		</p>
		<table style="BORDER-RIGHT: #ffbfbf 1px solid; BORDER-TOP: #ffbfbf 1px solid; BORDER-LEFT: #ffbfbf 1px solid; BORDER-BOTTOM: #ffbfbf 1px solid; BACKGROUND-COLOR: #fff2f2" cellspacing="0" cellpadding="0" width="75%">
				<tbody>
						<tr>
								<td style="BORDER-BOTTOM: #888 1px solid; BACKGROUND-COLOR: #ffafaf">
										<font size="-1">
												<b>File:</b> backupHome.sh</font>
								</td>
						</tr>
						<tr>
								<td>
										<pre>#! /bin/bash# Backup script for Gentoo Linux# Author: fdavid# Date: 2003.11.29.# Making backup of home partition to cds# options for the archivetarOptions="--create --absolute-names --preserve-permissions --gzip --file"# name of the archivearchive=/mnt/tarbackup/$(date +%Y%m%d)home.tar.gz# mount the backup partitionmount /dev/hdaXsleep 5# create the archivetar ${tarOptions} ${archive} /home/;echo archive is done# split the archive to cd size (use: "cat ${archive}.* &gt;&gt; ${archive}" to join the parts)split --bytes=700000000 ${archive} ${archive}.echo splitting is done# unmountsleep 5umount /dev/hdaX</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<a class="external text" title="http://forums.gentoo.org/profile.php?mode=viewprofile&amp;amp;u=35737&amp;amp;sid=1d3fd63f34aa46db117bc344a42c3759" href="http://forums.gentoo.org/profile.php?mode=viewprofile&amp;u=35737&amp;sid=1d3fd63f34aa46db117bc344a42c3759" rel="nofollow">
						<font color="#002c99">BrianW</font>
				</a> added: </p>
		<ul>
				<li>Cleaned up the script a bit. 
</li>
				<li>Made it so the --exclude= for the archive doesn't have to be edited if you modify $stage4Location 
</li>
				<li>Added --exclude=/var/tmp/* 
</li>
				<li>Added --verbose to the tar options 
</li>
				<li>Added code to remove /bootcpy 
</li>
				<li>Moved the --exclude directories/files out of $tarOptions into their own variable's (thanks <a class="external text" title="http://forums.gentoo.org/profile.php?mode=viewprofile&amp;amp;u=23381" href="http://forums.gentoo.org/profile.php?mode=viewprofile&amp;u=23381" rel="nofollow"><font color="#002c99">kamilian </font></a>) </li>
		</ul>
		<p align="justify">
		</p>
		<table style="BORDER-RIGHT: #ffbfbf 1px solid; BORDER-TOP: #ffbfbf 1px solid; BORDER-LEFT: #ffbfbf 1px solid; BORDER-BOTTOM: #ffbfbf 1px solid; BACKGROUND-COLOR: #fff2f2" cellspacing="0" cellpadding="0" width="75%">
				<tbody>
						<tr>
								<td style="BORDER-BOTTOM: #888 1px solid; BACKGROUND-COLOR: #ffafaf">
										<font size="-1">
												<b>File:</b> mkstage4.sh</font>
								</td>
						</tr>
						<tr>
								<td>
										<pre>#! /bin/bash##  Backup script for Gentoo Linux##  Author: BrianW##  Date: 2004.10.26.##  Adapted from backupHome.sh by fdavid##  Adapted from mkstage4.sh by nianderson##  This is a script to create a custom stage 4 tarball (System and boot backup)##  I use this script to make a snapshot of my system. Meant to be done weekly in my case##  Please check the options and adjust to your specifics.echo -=- Starting the Backup Script...echo -=-echo -=- Setting the variables...##  The location of the stage 4 tarball.##  Be sure to include a trailing /stage4Location=/##  The name of the stage 4 tarball.archive=$stage4Location$(hostname)-stage4.tar.bz2##  Directories/files that will be exluded from the stage 4 tarball.####  Add directories that will be recursively excluded, delimited by a space.##  Be sure to omit the trailing /dir_excludes="/dev /proc /sys /tmp /usr/portage /var/tmp"####  Add files that will be excluded, delimited by a space.##  You can use the * wildcard for multiple matches.##  There should always be $archive listed or bad things will happen.file_excludes="$archive"####  Combine the two *-excludes variables into the $excludes variableexcludes="$(for i in $dir_excludes; do if [ -d $i ]; then \    echo -n " --exclude=$i/*"; fi; done) $(for i in $file_excludes; do \    echo -n " --exclude=$i"; done)"##  The options for the stage 4 tarball.tarOptions="$excludes --create --absolute-names --preserve-permissions --bzip2 --verbose --totals --file"echo -=- Done!echo -=-##  Mounting the boot partitionecho -=- Mounting boot partition, then sleeping for 5 seconds...mount /bootsleep 5echo -=- Done!echo -=-##  Creating a copy of the boot partition (copy /boot to /bootcpy).##  This will allow the archiving of /boot without /boot needing to be mounted.##  This will aid in restoring the system.echo -=- Copying /boot to /bootcpy ...cp -R /boot /bootcpyecho -=- Done!echo -=-##  Unmounting /bootecho -=- Unmounting /boot then sleeping for 5 seconds...umount /bootsleep 5echo -=- Done!echo -=-##  Creating the stage 4 tarball.echo -=- Creating custom stage 4 tarball \=\=\&gt; $archiveecho -=-echo -=- Running the following command:echo -=- tar ${tarOptions} ${archive} /tar ${tarOptions} ${archive} /;echo -=- Done!##  Split the stage 4 tarball in cd size tar files.##  To combine the tar files after copying them to your##  chroot do the following: "cat *.tar.bz2 &gt;&gt; stage4.tar.bz2".##  Uncomment the following lines to enable this feature.#echo -=- Splitting the stage 4 tarball into CD size tar files...#split --bytes=700000000 ${archive} ${archive}.#echo -=- Done!##  Removing the directory /bootcpy.##  You may safely uncomment this if you wish to keep /bootcpy.echo -=- Removing the directory /bootcpy ...rm -rf /bootcpyecho -=- Done!echo -=-##  This is the end of the line.echo -=- The Backup Script has completed!</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>Here is a little script to install from a stage4 created by either of the above two scripts. (Limited testing reported by rpcyan thanks please continue testing) should work though. A few changes were made by rpcyan to get the install script working correctly Please contact me with comments or improvements. <a class="external text" title="http://forums.gentoo.org/profile.php?mode=viewprofile&amp;amp;u=19862" href="http://forums.gentoo.org/profile.php?mode=viewprofile&amp;u=19862" rel="nofollow"><font color="#002c99">nianderson</font></a></p>
		<p align="justify">
		</p>
		<table style="BORDER-RIGHT: #ffbfbf 1px solid; BORDER-TOP: #ffbfbf 1px solid; BORDER-LEFT: #ffbfbf 1px solid; BORDER-BOTTOM: #ffbfbf 1px solid; BACKGROUND-COLOR: #fff2f2" cellspacing="0" cellpadding="0" width="75%">
				<tbody>
						<tr>
								<td style="BORDER-BOTTOM: #888 1px solid; BACKGROUND-COLOR: #ffafaf">
										<font size="-1">
												<b>File:</b> installstage4.sh</font>
								</td>
						</tr>
						<tr>
								<td>
										<pre>#! /bin/bash# Backup script for Gentoo Linux# Author: nianderson# Date: 2004.09.15.##Used to install a stage4 created with mkstage4.sh#assumes you have already partitioned and formated your drive#assumes your booted from gentoo live cd#Define Disk layout#if using ide disks use hdax if using scsi use sdaxrootPartition=/dev/sda3bootPartition=/dev/sda1#where to mount the disk partitionsmntRootPartition=/mnt/gentoomntBootPartition=/mnt/gentoo/boot#URL of stage4#I put a copy of the tar on a webserver so i can#easily get it when a reinstall is neededurlToStage4=http://domain.com/stage4=hostname-stage4.tar.bz2#mount root partitionecho mounting root partition $rootPartition to $mntRootPartitionmount $rootPartition $mntRootPartitionsleep 5echo#not sure about this part yet#wget the stage4 to the mounted root partitioncd $mntRootPartitionecho wget $urlToStage4$stage4 to $mntRootPartitionwget $urlToStage4$stage4sleep 5#untar the stage4echo extract stage4tar xjpf $stage4sleep 5echo#mount boot partitonecho mounting $bootPartition to $mntBootPartitionmkdir $mntbootPartitionmount $bootPartition $mntBootPartitionsleep 5echo#copy boot copy back to bootecho copy bootcpy back to bootcp -R $mntRootPartition/bootcpy $mntBootPartitionsleep 5#remove stage4 filerm -rf $mntRootPartition/$stage4echo you need to check your fstab and install grub or lilo thenecho all should be wellecho Removing bootcpyrm -rf /bootcpyecho Enjoy</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<br />For explanation and further infos refer to the <a class="external text" title="http://forums.gentoo.org/viewtopic-t-312817-highlight-stage4.html" href="http://forums.gentoo.org/viewtopic-t-312817-highlight-stage4.html" rel="nofollow"><font color="#002c99">Gentoo Post</font></a>. A howto (Wiki) of the script can be found <a class="external text" title="http://blinkeye.ch/mediawiki/index.php/GNU/Linux System Backup Script (stage4)" href="http://blinkeye.ch/mediawiki/index.php/GNU/Linux_System_Backup_Script_%28stage4%29" rel="nofollow"><font color="#002c99">here</font></a>. </p>
		<p align="justify">
		</p>
		<table style="BORDER-RIGHT: #ffbfbf 1px solid; BORDER-TOP: #ffbfbf 1px solid; BORDER-LEFT: #ffbfbf 1px solid; BORDER-BOTTOM: #ffbfbf 1px solid; BACKGROUND-COLOR: #fff2f2" cellspacing="0" cellpadding="0" width="75%">
				<tbody>
						<tr>
								<td style="BORDER-BOTTOM: #888 1px solid; BACKGROUND-COLOR: #ffafaf">
										<font size="-1">
												<b>File:</b> mkstage4.sh</font>
								</td>
						</tr>
						<tr>
								<td>
										<pre>#!/bin/bash# Backup script for Gentoo Linux# Author: Reto Glauser aka blinkeye# Homepage: http://blinkeye.ch# Mailto: stage4 at blinkeye dot ch# Date: 23.03.2005# If you need further infos check out this post: http://forums.gentoo.org/viewtopic.php?p=1751698#1751698version=v1.2# these are the commands we actually need for the backupcommand_list="echo tar hostname date split"# verify that each command we use existsfor command in $command_list; do	path=`which $command | grep "no $command in"`		if [ ! -x `which $command` -a "$path" ]; then		echo -e "\n\nERROR: $command not found! Check your commands and/or your \$PATH"		exit -1	fidone# options for the tar commandtarOptions="--create --absolute-names --preserve-permissions --totals --bzip2 --ignore-failed-read --verbose --file"# where to put the stage4stage4Location=/mnt/backups/stage4# name prefixstage4prefix=$(hostname)-stage4-`date +\%d.\%m.\%Y`# these files/directories are always excludeddefault_exclude_list="--exclude=/tmp/*--exclude=/var/tmp/*--exclude=/lost+found/*--exclude=/dev/*--exclude=/proc/*--exclude=/mnt/*--exclude=/sys/*--exclude=/usr/portage/*--exclude=/var/log/*--exclude=$stage4Location"# depending on your choice these files or directories will additionally be excludedcustom_exclude_list="--exclude=/usr/src/*--exclude=/opt/mathematica--exclude=/usr/share/smssend--exclude=/home/*"# check the folder/files stored in $default_exclude_list existfor exclude in $default_exclude_list; do	if [ ! -e "`echo "$exclude" | cut -d'=' -f2 | cut -d'*' -f1`"  ]; then		echo -e "\n\nERROR: `echo "$exclude" | cut -d'=' -f2` not found! Check your \$default_exclude_list"	fidone# check the folder/files stored in $custom_exclude_list existfor exclude in $custom_exclude_list; do	if [ ! -e "`echo "$exclude" | cut -d'=' -f2 | cut -d'*' -f1`"  ]; then		echo -e "\n\nERROR: `echo "$exclude" | cut -d'=' -f2` not found! Check your \$custom_exclude_list"	fidone# print out the version echo -e "\nBackup script $version" echo -e "==================="# how do you want to backup?echo -e "\nWhat do you want to do? (Use CONTROL-C to abort)\n(1) Minimal backup(2) Interactive backup"while [ "$option" != '1' -a "$option" != '2'  ]; do	echo -en "\nPlease enter your option: "	read optiondonecase $option in1)	stage4Name=$stage4Location/$stage4prefix-minimal	final_command="tar $default_exclude_list $custom_exclude_list $tarOptions $stage4Name.tar.bz2 / /var/log/emerge.log"	;;2)	for folder in $custom_exclude_list; do		echo -en "Do you want to backup" `echo "$folder" | cut -d'=' -f2`"? (y/n) "		read answer		while [ "$answer" != 'y' -a "$answer" != 'n' ]; do			echo "please enter y or n"			read answer		done		if [ "$answer" == 'n' ]; then			default_exclude_list="$default_exclude_list $folder"		fi	done		stage4Name=$stage4Location/$stage4prefix-custom	final_command="tar $default_exclude_list $tarOptions $stage4Name.tar.bz2 /  /var/log/emerge.log"	;;esac# show what will be doneecho -e "\n* creating the stage4 at $stage4Location with the following options:\n\n"$final_command# everything is set, are you sure to continue?echo -ne "\nDo you want to continue? (y/n) "read answerwhile [ "$answer" != 'y' ] &amp;&amp; [ "$answer" != 'n' ]; do			echo "please enter y or n"			read answerdoneif [ "$answer" == 'y' ]; then	# mount boot	echo -e "\n* mount boot"	mount /boot &gt;/dev/null 2&gt;&amp;1			# if necessary, create the stage4Location	if [ ! -d "$stage4Location" ] ; then		echo "* creating directory $stage4Location"		mkdir -p $stage4Location	fi		# check whether the file already exists	if [ -a "$stage4Name.tar.bz2" ]; then		echo -en "\nDo you want to overwrite $stage4Name.tar.bz2? (y/n) "		read answer		while [ "$answer" != 'y' ] &amp;&amp; [ "$answer" != 'n' ]; do			echo "please enter y or n"			read answer		done		if [ "$answer" == 'n' ]; then			echo -e "\n* There's nothing to do ... Exiting"			exit 0;		fi	fi		# do the backup	time $final_command	# copy the current world file to the stage4 location	echo -e "\n* creating stage4 overview $stage4Name.txt"	cp /var/lib/portage/world $stage4Name.txt &gt;/dev/null 2&gt;&amp;1		# we finished, clean up	echo "* stage4 is done"	echo "* umounting boot"	umount /bootelse	echo -e "\n* There's nothing to do ... Exiting"fi#Uncomment the following command if you want to split the archive in cd size chunks:#split --suffix-length=1 --bytes=670m $stage4Name.tar.bz2 "$stage4Name".tar.bz2_ &amp;&amp; echo "* splitting is done"</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=7"><font color="#002c99">edit</font></a>]</div>
		<a name="Install_from_a_stage4">
		</a>
		<h2>Install from a stage4</h2>
		<ol>
				<li>Boot live CD 
</li>
				<li>Create partitions, make filesystems and mount filesystems <pre>root# cd /mnt/gentoo</pre></li>
				<li>Copy your stage4 archive(s) to disk (if it is on another CD type "gentoo docache" at the boot prompt. Then you'll be able to umount/mount other CDs.)<pre>tar -xvjpf stage4.tar.bz2</pre></li>
				<li>
						<pre>root# cp -R bootcpy /mnt/gentoo/boot</pre>(<b>Double check the boot dir after you copy the files over!</b>) 
</li>
				<li>
						<pre>root# rm -rf bootcpy</pre>
				</li>
				<li>Check /etc/fstab 
</li>
				<li>You must "link" /dev to /mnt/gentoo/dev with following command (given outside chroot):<pre>mount -o bind /dev /mnt/gentoo/dev</pre></li>
				<li>Chroot into /mnt/gentoo:<pre>chroot /mnt/gentoo /bin/bash</pre></li>
				<li>grub or lilo </li>
		</ol>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=8"><font color="#002c99">edit</font></a>]</div>
		<a name="Partition_table_tip">
		</a>
		<h2>Partition table tip</h2>
		<p align="justify">
		</p>
		<table style="BORDER-RIGHT: #bfffbf 1px solid; BORDER-TOP: #bfffbf 1px solid; BORDER-LEFT: #bfffbf 1px solid; BORDER-BOTTOM: #bfffbf 1px solid; BACKGROUND-COLOR: #f2fff2" cellspacing="0" cellpadding="0" width="75%">
				<tbody>
						<tr>
								<td style="BORDER-RIGHT: #a0ffa0 1px solid; BORDER-TOP: #a0ffa0 1px solid; BORDER-LEFT: #a0ffa0 1px solid; BORDER-BOTTOM: #888 1px solid; BACKGROUND-COLOR: #c1ffc1">
										<font size="-1">
												<b>Code:</b> To store partition table info</font>
								</td>
						</tr>
						<tr>
								<td>
										<pre>dd if=/dev/(your_disk) of=mbr.save count=1 bs=512sfdisk -d /dev/(your_disk) &gt; partitions.save</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>The first of those saves the mbr and the second will store all partition info (including logical partitions, which aren't part of the mbr). </p>
		<p align="justify">
		</p>
		<table style="BORDER-RIGHT: #bfffbf 1px solid; BORDER-TOP: #bfffbf 1px solid; BORDER-LEFT: #bfffbf 1px solid; BORDER-BOTTOM: #bfffbf 1px solid; BACKGROUND-COLOR: #f2fff2" cellspacing="0" cellpadding="0" width="75%">
				<tbody>
						<tr>
								<td style="BORDER-RIGHT: #a0ffa0 1px solid; BORDER-TOP: #a0ffa0 1px solid; BORDER-LEFT: #a0ffa0 1px solid; BORDER-BOTTOM: #888 1px solid; BACKGROUND-COLOR: #c1ffc1">
										<font size="-1">
												<b>Code:</b> To restore partition info</font>
								</td>
						</tr>
						<tr>
								<td>
										<pre>dd if=mbr.save of=/dev/(your_disk)sfdisk /dev/(your_disk) &lt; partitions.save</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=9"><font color="#002c99">edit</font></a>]</div>
		<a name="LILO_tip">
		</a>
		<h2>LILO tip</h2>
		<p>To install LILO in the MBR after untaring stage, mount it as: </p>
		<pre>mount -o bind /dev /mnt/gentoo/dev</pre>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=10"><font color="#002c99">edit</font></a>]</div>
		<a name="GRUB_tip">
		</a>
		<h2>GRUB tip</h2>
		<p>After chrooting: </p>
		<pre>root# grubgrub&gt; root (hd0,0)grub&gt; setup (hd0)grub&gt; quit</pre>
		<p>(hd may need to be changed depending on your setup) </p>
		<div class="editsection" style="FLOAT: right; MARGIN-LEFT: 5px">[<a title="HOWTO Custom Stage4" href="http://gentoo-wiki.com/index.php?title=HOWTO_Custom_Stage4&amp;action=edit&amp;section=11"><font color="#002c99">edit</font></a>]</div>
		<a name="Privacy_tip">
		</a>
		<h2>Privacy tip</h2>
		<p>I recommend deleting every <b>"~/.bash_history"</b> before executing the stage4 commands/scripts. This can be done by simply typing:<br /></p>
		<p>Singleuser (root) environments:<br /><b>root@gentoo# rm ~/.bash_history &amp;&amp; tar cpjf ...</b><br />or<br /><b>root@gentoo# rm ~/.bash_history &amp;&amp; sh &lt;your-script&gt;.sh</b><br /></p>
		<p>Multiuser (&gt;root) environments:<br />Just add <b>".bash_history"</b> to the excluded files list in your stage4 scripts or <b>"--exclude='.bash_history'"</b> to command line.<br /></p>
		<p align="justify">
				<br /> </p>
<img src ="http://www.cnitblog.com/flutist1225/aggbug/19968.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:16 <a href="http://www.cnitblog.com/flutist1225/articles/19968.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux 系统内核的调试 （转）</title><link>http://www.cnitblog.com/flutist1225/articles/19967.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:13:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19967.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19967.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19967.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19967.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19967.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr valign="top">
								<td width="10">
								</td>
								<td width="100%">
										<table class="no-print" cellspacing="0" cellpadding="0" width="160" align="right" border="0">
												<tbody>
														<tr>
																<td width="10">
																		<img height="1" alt="" src="http://www.ibm.com/i/c.gif" width="10" />
																</td>
																<td>
																		<table cellspacing="0" cellpadding="0" width="150" border="0">
																				<tbody>
																						<tr>
																								<td class="v14-header-1-small">文档选项</td>
																						</tr>
																				</tbody>
																		</table>
																		<table class="v14-gray-table-border" cellspacing="0" cellpadding="0" border="0">
																				<tbody>
																						<tr>
																								<td class="no-padding" width="150">
																										<table cellspacing="0" cellpadding="0" width="143" border="0">
																												<tbody>
																														<tr valign="top">
																																<td width="8">
																																		<img height="1" alt="" src="http://www.ibm.com/i/c.gif" width="8" />
																																</td>
																																<td width="16">
																																		<img height="16" alt="将此页作为电子邮件发送" src="http://www.ibm.com/i/v14/icons/em.gif" width="16" vspace="3" />
																																</td>
																																<td width="122">
																																		<p>
																																				<a class="smallplainlink" href="javascript:document.email.submit();">
																																						<strong>
																																								<font color="#5c81a7" size="2">将此页作为电子邮件发送</font>
																																						</strong>
																																				</a>
																																		</p>
																																</td>
																														</tr>
																														<noscript>
																														</noscript>
																												</tbody>
																										</table>
																								</td>
																						</tr>
																				</tbody>
																		</table>
																		<br />
																		<table cellspacing="0" cellpadding="0" width="150" border="0">
																				<tbody>
																						<tr>
																								<td class="v14-header-1-small">对此页的评价</td>
																						</tr>
																				</tbody>
																		</table>
																		<table class="v14-gray-table-border" cellspacing="0" cellpadding="0" border="0">
																				<tbody>
																						<tr>
																								<td class="no-padding" width="150">
																										<table cellspacing="0" cellpadding="0" width="143" border="0">
																												<tbody>
																														<tr valign="top">
																																<td width="8">
																																		<img height="1" alt="" src="http://www.ibm.com/i/c.gif" width="8" />
																																</td>
																																<td>
																																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/d_bold.gif" width="16" vspace="3" border="0" />
																																</td>
																																<td width="125">
																																		<p>
																																				<a class="smallplainlink" href="http://www-128.ibm.com/developerworks/cn/linux/l-kdb/#rate">
																																						<strong>
																																								<font color="#996699" size="2">帮助我们改进这些内容</font>
																																						</strong>
																																				</a>
																																		</p>
																																</td>
																														</tr>
																												</tbody>
																										</table>
																								</td>
																						</tr>
																				</tbody>
																		</table>
																		<br />
																</td>
														</tr>
												</tbody>
										</table>
										<p>2005 年 12 月 18 日</p>
										<blockquote>本文将首先介绍 Linux 内核上的一些内核代码监视和错误跟踪技术，这些调试和跟踪方法因所要求的使用环境和使用方法而各有不同，然后重点介绍三种 Linux 内核的源代码级的调试方法。</blockquote>
										<p>调试是软件开发过程中一个必不可少的环节，在 Linux 内核开发的过程中也不可避免地会面对如何调试内核的问题。但是，Linux 系统的开发者出于保证内核代码正确性的考虑，不愿意在 Linux 内核源代码树中加入一个调试器。他们认为内核中的调试器会误导开发者，从而引入不良的修正[1]。所以对 Linux 内核进行调试一直是个令内核程序员感到棘手的问题，调试工作的艰苦性是内核级的开发区别于用户级开发的一个显著特点。 </p>
										<p>尽管缺乏一种内置的调试内核的有效方法，但是 Linux 系统在内核发展的过程中也逐渐形成了一些监视内核代码和错误跟踪的技术。同时，许多的补丁程序应运而生，它们为标准内核附加了内核调试的支持。尽管这些补丁有些并不被 Linux 官方组织认可，但他们确实功能完善，十分强大。调试内核问题时，利用这些工具与方法跟踪内核执行情况，并查看其内存和数据结构将是非常有用的。</p>
										<p>本文将首先介绍 Linux 内核上的一些内核代码监视和错误跟踪技术，这些调试和跟踪方法因所要求的使用环境和使用方法而各有不同，然后重点介绍三种 Linux 内核的源代码级的调试方法。</p>
										<p>
												<a name="IDAPSYMB">
														<span class="atitle">
																<font size="4">1. Linux 系统内核级软件的调试技术</font>
														</span>
												</a>
										</p>
										<p>printk() 是调试内核代码时最常用的一种技术。在内核代码中的特定位置加入printk() 调试调用，可以直接把所关心的信息打打印到屏幕上，从而可以观察程序的执行路径和所关心的变量、指针等信息。 Linux 内核调试器（Linux kernel debugger，kdb）是 Linux 内核的补丁，它提供了一种在系统能运行时对内核内存和数据结构进行检查的办法。Oops、KDB在文章掌握 <a href="http://www-128.ibm.com/developerworks/cn/linux/l-kdbug/index.html"><font color="#5c81a7">Linux 调试技术</font></a>有详细介绍，大家可以参考。 Kprobes 提供了一个强行进入任何内核例程，并从中断处理器无干扰地收集信息的接口。使用 Kprobes 可以轻松地收集处理器寄存器和全局数据结构等调试信息，而无需对Linux内核频繁编译和启动，具体使用方法，请参考<a href="http://www-128.ibm.com/developerworks/cn/linux/l-kprobes.html"><font color="#5c81a7">使用 Kprobes 调试内核</font></a>。</p>
										<p>以上介绍了进行Linux内核调试和跟踪时的常用技术和方法。当然，内核调试与跟踪的方法还不止以上提到的这些。这些调试技术的一个共同的特点在于，他们都不能提供源代码级的有效的内核调试手段，有些只能称之为错误跟踪技术，因此这些方法都只能提供有限的调试能力。下面将介绍三种实用的源代码级的内核调试方法。</p>
										<br />
										<table cellspacing="0" cellpadding="0" width="100%" border="0">
												<tbody>
														<tr>
																<td>
																		<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
																</td>
														</tr>
												</tbody>
										</table>
										<table class="no-print" cellspacing="0" cellpadding="0" align="right">
												<tbody>
														<tr align="right">
																<td>
																		<table cellspacing="0" cellpadding="0" border="0">
																				<tbody>
																						<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-128.ibm.com/developerworks/cn/linux/l-kdb/#main">
																												<strong>
																														<font color="#996699">回页首</font>
																												</strong>
																										</a>
																								</td>
																						</tr>
																				</tbody>
																		</table>
																</td>
														</tr>
												</tbody>
										</table>
										<br />
										<br />
										<p>
												<a name="IDA5SYMB">
														<span class="atitle">
																<font size="4">2. 使用KGDB构建Linux内核调试环境</font>
														</span>
												</a>
										</p>
										<p>kgdb提供了一种使用 gdb调试 Linux 内核的机制。使用KGDB可以象调试普通的应用程序那样，在内核中进行设置断点、检查变量值、单步跟踪程序运行等操作。使用KGDB调试时需要两台机器，一台作为开发机（Development Machine）,另一台作为目标机（Target Machine），两台机器之间通过串口或者以太网口相连。串口连接线是一根RS-232接口的电缆，在其内部两端的第2脚（TXD）与第3脚（RXD）交叉相连，第7脚（接地脚）直接相连。调试过程中，被调试的内核运行在目标机上，gdb调试器运行在开发机上。</p>
										<p>目前，kgdb发布支持i386、x86_64、32-bit PPC、SPARC等几种体系结构的调试器。有关kgdb补丁的下载地址见参考资料[4]。</p>
										<p>
												<a name="IDAGTYMB">
														<span class="smalltitle">
																<strong>
																		<font size="3">2．1 kgdb的调试原理</font>
																</strong>
														</span>
												</a>
										</p>
										<p>安装kgdb调试环境需要为Linux内核应用kgdb补丁，补丁实现的gdb远程调试所需要的功能包括命令处理、陷阱处理及串口通讯3个主要的部分。kgdb补丁的主要作用是在Linux内核中添加了一个调试Stub。调试Stub是Linux内核中的一小段代码，提供了运行gdb的开发机和所调试内核之间的一个媒介。gdb和调试stub之间通过gdb串行协议进行通讯。gdb串行协议是一种基于消息的ASCII码协议，包含了各种调试命令。当设置断点时，kgdb负责在设置断点的指令前增加一条trap指令，当执行到断点时控制权就转移到调试stub中去。此时，调试stub的任务就是使用远程串行通信协议将当前环境传送给gdb，然后从gdb处接受命令。gdb命令告诉stub下一步该做什么，当stub收到继续执行的命令时，将恢复程序的运行环境，把对CPU的控制权重新交还给内核。</p>
										<br />
										<a name="IDANTYMB">
												<strong>
												</strong>
										</a>
										<br />
										<img height="224" alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-kdb/images/image001.gif" width="450" border="0" />
										<br />
										<p>
												<a name="IDAZTYMB">
														<span class="smalltitle">
																<strong>
																		<font size="3">2．2 Kgdb的安装与设置</font>
																</strong>
														</span>
												</a>
										</p>
										<p>下面我们将以Linux 2.6.7内核为例详细介绍kgdb调试环境的建立过程。</p>
										<p>
												<strong>2.2.1软硬件准备</strong>
										</p>
										<p>以下软硬件配置取自笔者进行试验的系统配置情况：</p>
										<br />
										<a name="IDADUYMB">
												<strong>
												</strong>
										</a>
										<br />
										<img height="215" alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-kdb/images/table1.gif" width="589" border="0" />
										<br />
										<p>kgdb补丁的版本遵循如下命名模式：Linux-A-kgdb-B，其中A表示Linux的内核版本号，B为kgdb的版本号。以试验使用的kgdb补丁为例，linux内核的版本为linux-2.6.7，补丁版本为kgdb-2.2。</p>
										<p>物理连接好串口线后，使用以下命令来测试两台机器之间串口连接情况，stty命令可以对串口参数进行设置：</p>
										<p>在development机上执行：</p>
										<br />
										<a name="IDASUYMB">
												<strong>
												</strong>
										</a>
										<br />
										<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
												<tbody>
														<tr>
																<td>
																		<pre>
																				<code class="section">
																						<font face="Lucida Console">stty ispeed 115200 ospeed 115200 -F /dev/ttyS0</font>
																				</code>
																		</pre>
																</td>
														</tr>
												</tbody>
										</table>
										<br />
										<p>在target机上执行：</p>
										<br />
										<a name="IDA0UYMB">
												<strong>
												</strong>
										</a>
										<br />
										<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
												<tbody>
														<tr>
																<td>
																		<pre>
																				<code class="section">
																						<font face="Lucida Console">stty ispeed 115200 ospeed 115200 -F /dev/ttyS0</font>
																				</code>
																		</pre>
																</td>
														</tr>
												</tbody>
										</table>
										<br />
										<p>在developement机上执行：</p>
										<br />
										<a name="IDACVYMB">
												<strong>
												</strong>
										</a>
										<br />
										<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
												<tbody>
														<tr>
																<td>
																		<pre>
																				<code class="section">
																						<font face="Lucida Console">echo hello &gt; /dev/ttyS0</font>
																				</code>
																		</pre>
																</td>
														</tr>
												</tbody>
										</table>
										<br />
										<p>在target机上执行：</p>
										<br />
										<a name="IDAKVYMB">
												<strong>
												</strong>
										</a>
										<br />
										<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
												<tbody>
														<tr>
																<td>
																		<pre>
																				<code class="section">
																						<font face="Lucida Console">cat /dev/ttyS0</font>
																				</code>
																		</pre>
																</td>
														</tr>
												</tbody>
										</table>
										<br />
										<p>如果串口连接没问题的话在将在target机的屏幕上显示"hello"。</p>
										<p>
												<strong>2．2．2 安装与配置</strong>
										</p>
										<p>下面我们需要应用kgdb补丁到Linux内核，设置内核选项并编译内核。这方面的资料相对较少，笔者这里给出详细的介绍。下面的工作在开发机（developement）上进行，以上面介绍的试验环境为例，某些具体步骤在实际的环境中可能要做适当的改动：</p>
										<p>I、内核的配置与编译</p>
										<br />
										<a name="IDAWVYMB">
												<strong>
												</strong>
										</a>
										<br />
										<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
												<tbody>
														<tr>
																<td>
																		<pre>
																				<code class="section">
																						<font face="Lucida Console">[root@lisl tmp]# tar -jxvf linux-2.6.7.tar.bz2[root@lisl tmp]#tar -jxvf linux-2.6.7-kgdb-2.2.tar.tar[root@lisl tmp]#cd inux-2.6.7</font>
																				</code>
																		</pre>
																</td>
														</tr>
												</tbody>
										</table>
										<br />
										<p>请参照目录补丁包中文件README给出的说明，执行对应体系结构的补丁程序。由于试验在i386体系结构上完成，所以只需要安装一下补丁：core-lite.patch、i386-lite.patch、8250.patch、eth.patch、core.patch、i386.patch。应用补丁文件时，请遵循kgdb软件包内series文件所指定的顺序，否则可能会带来预想不到的问题。eth.patch文件是选择以太网口作为调试的连接端口时需要运用的补丁</p>。 
<p>应用补丁的命令如下所示：</p><br /><a name="IDAAWYMB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl tmp]#patch -p1 &lt;../linux-2.6.7-kgdb-2.2/core-lite.patch </font></code></pre></td></tr></tbody></table><br /><p>如果内核正确，那么应用补丁时应该不会出现任何问题（不会产生*.rej文件）。为Linux内核添加了补丁之后，需要进行内核的配置。内核的配置可以按照你的习惯选择配置Linux内核的任意一种方式。</p><br /><a name="IDAIWYMB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl tmp]#make menuconfig</font></code></pre></td></tr></tbody></table><br /><p>在内核配置菜单的Kernel hacking选项中选择kgdb调试项，例如：</p><br /><a name="IDAQWYMB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">  [*] KGDB: kernel debugging with remote gdb                                                                    Method for KGDB communication (KGDB: On generic serial port (8250))  ---&gt;    [*] KGDB: Thread analysis                                                                              [*] KGDB: Console messages through gdb[root@lisl tmp]#make  </font></code></pre></td></tr></tbody></table><br /><p>编译内核之前请注意Linux目录下Makefile中的优化选项，默认的Linux内核的编译都以-O2的优化级别进行。在这个优化级别之下，编译器要对内核中的某些代码的执行顺序进行改动，所以在调试时会出现程序运行与代码顺序不一致的情况。可以把Makefile中的-O2选项改为-O,但不可去掉-O，否则编译会出问题。为了使编译后的内核带有调试信息，注意在编译内核的时候需要加上-g选项。</p><p>不过，当选择"Kernel debugging-&gt;Compile the kernel with debug info"选项后配置系统将自动打开调试选项。另外，选择"kernel debugging with remote gdb"后，配置系统将自动打开"Compile the kernel with debug info"选项。</p><p>内核编译完成后，使用scp命令进行将相关文件拷贝到target机上(当然也可以使用其它的网络工具，如rcp)。</p><br /><a name="IDA0WYMB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl tmp]#scp arch/i386/boot/bzImage root@192.168.6.13:/boot/vmlinuz-2.6.7-kgdb[root@lisl tmp]#scp System.map root@192.168.6.13:/boot/System.map-2.6.7-kgdb</font></code></pre></td></tr></tbody></table><br /><p>如果系统启动使所需要的某些设备驱动没有编译进内核的情况下，那么还需要执行如下操作：</p><br /><a name="IDACXYMB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl tmp]#mkinitrd /boot/initrd-2.6.7-kgdb 2.6.7[root@lisl tmp]#scp initrd-2.6.7-kgdb root@192.168.6.13:/boot/ initrd-2.6.7-kgdb</font></code></pre></td></tr></tbody></table><br /><p>II、kgdb的启动</p><p>在将编译出的内核拷贝的到target机器之后，需要配置系统引导程序，加入内核的启动选项。以下是kgdb内核引导参数的说明：</p><br /><a name="IDAMXYMB"><strong></strong></a><br /><img height="309" alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-kdb/images/table2.gif" width="584" border="0" /><br /><p>如表中所述，在kgdb 2.0版本之后内核的引导参数已经与以前的版本有所不同。使用grub引导程序时，直接将kgdb参数作为内核vmlinuz的引导参数。下面给出引导器的配置示例。</p><br /><a name="IDAZXYMB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">title 2.6.7 kgdbroot (hd0,0)kernel /boot/vmlinuz-2.6.7-kgdb ro root=/dev/hda1 kgdbwait kgdb8250=1,115200</font></code></pre></td></tr></tbody></table><br /><p>在使用lilo作为引导程序时，需要把kgdb参放在由append修饰的语句中。下面给出使用lilo作为引导器时的配置示例。</p><br /><a name="IDAEAAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">image=/boot/vmlinuz-2.6.7-kgdblabel=kgdb    read-only    root=/dev/hda3append="gdb gdbttyS=1 gdbbaud=115200"</font></code></pre></td></tr></tbody></table><br /><p>保存好以上配置后重新启动计算机，选择启动带调试信息的内核，内核将在短暂的运行后在创建init内核线程之前停下来，打印出以下信息，并等待开发机的连接。</p><p>Waiting for connection from remote gdb...</p><p>在开发机上执行：</p><br /><a name="IDAOAAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">gdbfile vmlinuxset remotebaud 115200target remote /dev/ttyS0</font></code></pre></td></tr></tbody></table><br /><p>其中vmlinux是指向源代码目录下编译出来的Linux内核文件的链接，它是没有经过压缩的内核文件，gdb程序从该文件中得到各种符号地址信息。</p><p>这样，就与目标机上的kgdb调试接口建立了联系。一旦建立联接之后，对Linux内的调试工作与对普通的运用程序的调试就没有什么区别了。任何时候都可以通过键入ctrl+c打断目标机的执行，进行具体的调试工作。</p><p>在kgdb 2.0之前的版本中，编译内核后在arch/i386/kernel目录下还会生成可执行文件gdbstart。将该文件拷贝到target机器的/boot目录下，此时无需更改内核的启动配置文件，直接使用命令：</p><br /><a name="IDAYAAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl boot]#gdbstart -s 115200 -t /dev/ttyS0</font></code></pre></td></tr></tbody></table><br /><p>可以在KGDB内核引导启动完成后建立开发机与目标机之间的调试联系。</p><p><strong>2．2．3 通过网络接口进行调试</strong></p><p>kgdb也支持使用以太网接口作为调试器的连接端口。在对Linux内核应用补丁包时，需应用eth.patch补丁文件。配置内核时在Kernel hacking中选择kgdb调试项，配置kgdb调试端口为以太网接口，例如：</p><br /><a name="IDADBAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[*]KGDB: kernel debugging with remote gdbMethod for KGDB communication (KGDB: On ethernet)  ---&gt; ( ) KGDB: On generic serial port (8250)(X) KGDB: On ethernet</font></code></pre></td></tr></tbody></table><br /><p>另外使用eth0网口作为调试端口时，grub.list的配置如下：</p><br /><a name="IDALBAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">title 2.6.7 kgdbroot (hd0,0)kernel /boot/vmlinuz-2.6.7-kgdb ro root=/dev/hda1 kgdbwait kgdboe=@192.168.5.13/,@192.168. 6.13/ </font></code></pre></td></tr></tbody></table><br /><p>其他的过程与使用串口作为连接端口时的设置过程相同。</p><p>注意：尽管可以使用以太网口作为kgdb的调试端口，使用串口作为连接端口更加简单易行，kgdb项目组推荐使用串口作为调试端口。</p><p><strong>2．2．4 模块的调试方法</strong></p><p>内核可加载模块的调试具有其特殊性。由于内核模块中各段的地址是在模块加载进内核的时候才最终确定的，所以develop机的gdb无法得到各种符号地址信息。所以，使用kgdb调试模块所需要解决的一个问题是，需要通过某种方法获得可加载模块的最终加载地址信息，并把这些信息加入到gdb环境中。</p><p>I、在Linux 2.4内核中的内核模块调试方法</p><p>在Linux2.4.x内核中，可以使用insmod -m命令输出模块的加载信息，例如：</p><br /><a name="IDAZBAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl tmp]# insmod -m hello.ko &gt;modaddr</font></code></pre></td></tr></tbody></table><br /><p>查看模块加载信息文件modaddr如下：</p><br /><a name="IDABCAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">.this           00000060  c88d8000  2**2.text           00000035  c88d8060  2**2.rodata         00000069  c88d80a0  2**5…….data           00000000  c88d833c  2**2.bss            00000000  c88d833c  2**2……</font></code></pre></td></tr></tbody></table><br /><p>在这些信息中，我们关心的只有4个段的地址:.text、.rodata、.data、.bss。在development机上将以上地址信息加入到gdb中,这样就可以进行模块功能的测试了。</p><br /><a name="IDAJCAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">(gdb) Add-symbol-file hello.o 0xc88d8060 -s .data 0xc88d80a0 -s .rodata 0xc88d80a0 -s .bss 0x c88d833c</font></code></pre></td></tr></tbody></table><br /><p>这种方法也存在一定的不足，它不能调试模块初始化的代码，因为此时模块初始化代码已经执行过了。而如果不执行模块的加载又无法获得模块插入地址，更不可能在模块初始化之前设置断点了。对于这种调试要求可以采用以下替代方法。</p><p>在target机上用上述方法得到模块加载的地址信息，然后再用rmmod卸载模块。在development机上将得到的模块地址信息导入到gdb环境中，在内核代码的调用初始化代码之前设置断点。这样，在target机上再次插入模块时，代码将在执行模块初始化之前停下来，这样就可以使用gdb命令调试模块初始化代码了。</p><p>另外一种调试模块初始化函数的方法是：当插入内核模块时，内核模块机制将调用函数sys_init_module(kernel/modle.c)执行对内核模块的初始化，该函数将调用所插入模块的初始化函数。程序代码片断如下：</p><br /><a name="IDATCAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">……	……	if (mod-&gt;init != NULL)		ret = mod-&gt;init();……	……</font></code></pre></td></tr></tbody></table><br /><p>在该语句上设置断点，也能在执行模块初始化之前停下来。</p><p>II、在Linux 2.6.x内核中的内核模块调试方法</p><p>Linux 2.6之后的内核中，由于module-init-tools工具的更改，insmod命令不再支持-m参数，只有采取其他的方法来获取模块加载到内核的地址。通过分析ELF文件格式，我们知道程序中各段的意义如下：</p><p>.text（代码段）：用来存放可执行文件的操作指令，也就是说是它是可执行程序在内存种的镜像。</p><p>.data（数据段）：数据段用来存放可执行文件中已初始化全局变量，也就是存放程序静态分配的变量和全局变量。</p><p>.bss（BSS段）：BSS段包含了程序中未初始化全局变量，在内存中 bss段全部置零。</p><p>.rodata（只读段）：该段保存着只读数据，在进程映象中构造不可写的段。</p><p>通过在模块初始化函数中放置一下代码，我们可以很容易地获得模块加载到内存中的地址。</p><br /><a name="IDACDAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">……int bss_var;static int hello_init(void){printk(KERN_ALERT "Text location .text(Code Segment):%p\n",hello_init);static int data_var=0;printk(KERN_ALERT "Data Location .data(Data Segment):%p\n",&amp;data_var);printk(KERN_ALERT "BSS Location: .bss(BSS Segment):%p\n",&amp;bss_var);……}Module_init(hello_init);</font></code></pre></td></tr></tbody></table><br /><p>这里，通过在模块的初始化函数中添加一段简单的程序，使模块在加载时打印出在内核中的加载地址。.rodata段的地址可以通过执行命令readelf -e hello.ko，取得.rodata在文件中的偏移量并加上段的align值得出。</p><p>为了使读者能够更好地进行模块的调试，kgdb项目还发布了一些脚本程序能够自动探测模块的插入并自动更新gdb中模块的符号信息。这些脚本程序的工作原理与前面解释的工作过程相似，更多的信息请阅读参考资料[4]。</p><p><strong>2．2．5 硬件断点</strong></p><p>kgdb提供对硬件调试寄存器的支持。在kgdb中可以设置三种硬件断点：执行断点（Execution Breakpoint）、写断点（Write Breakpoint）、访问断点（Access Breakpoint）但不支持I/O访问的断点。目前，kgdb对硬件断点的支持是通过宏来实现的，最多可以设置4个硬件断点，这些宏的用法如下：</p><br /><a name="IDAPDAZB"><strong></strong></a><br /><img height="222" alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-kdb/images/table3.gif" width="513" border="0" /><br /><p>在有些情况下，硬件断点的使用对于内核的调试是非常方便的。有关硬件断点的定义和具体的使用说明见参考资料[4]</p>。 
<p><a name="IDA3DAZB"><span class="smalltitle"><strong><font size="3">2．3．在VMware中搭建调试环境</font></strong></span></a></p><p>kgdb调试环境需要使用两台微机分别充当development机和target机，使用VMware后我们只使用一台计算机就可以顺利完成kgdb调试环境的搭建。以windows下的环境为例，创建两台虚拟机，一台作为开发机，一台作为目标机。</p><p><strong>2．3．1虚拟机之间的串口连接</strong></p><p>虚拟机中的串口连接可以采用两种方法。一种是指定虚拟机的串口连接到实际的COM上，例如开发机连接到COM1，目标机连接到COM2，然后把两个串口通过串口线相连接。另一种更为简便的方法是：在较高一些版本的VMware中都支持把串口映射到命名管道，把两个虚拟机的串口映射到同一个命名管道。例如，在两个虚拟机中都选定同一个命名管道 \\.\pipe\com_1,指定target机的COM口为server端，并选择"The other end is a virtual machine"属性；指定development机的COM口端为client端，同样指定COM口的"The other end is a virtual machine"属性。对于IO mode属性，在target上选中"Yield CPU on poll"复选择框，development机不选。这样，可以无需附加任何硬件，利用虚拟机就可以搭建kgdb调试环境。即降低了使用kgdb进行调试的硬件要求，也简化了建立调试环境的过程。</p><br /><a name="IDAHEAZB"><strong></strong></a><br /><img height="424" alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-kdb/images/image002.jpg" width="600" border="0" /><br /><p><strong>2．3．2 VMware的使用技巧</strong></p><p>VMware虚拟机是比较占用资源的，尤其是象上面那样在Windows中使用两台虚拟机。因此，最好为系统配备512M以上的内存，每台虚拟机至少分配128M的内存。这样的硬件要求，对目前主流配置的PC而言并不是过高的要求。出于系统性能的考虑，在VMware中尽量使用字符界面进行调试工作。同时，Linux系统默认情况下开启了sshd服务，建议使用SecureCRT登陆到Linux进行操作，这样可以有较好的用户使用界面。</p><p><strong>2．3．3 在Linux下的虚拟机中使用kgdb</strong></p><p>对于在Linux下面使用VMware虚拟机的情况，笔者没有做过实际的探索。从原理上而言，只需要在Linux下只要创建一台虚拟机作为target机，开发机的工作可以在实际的Linux环境中进行，搭建调试环境的过程与上面所述的过程类似。由于只需要创建一台虚拟机，所以使用Linux下的虚拟机搭建kgdb调试环境对系统性能的要求较低。（vmware已经推出了Linux下的版本）还可以在development机上配合使用一些其他的调试工具，例如功能更强大的cgdb、图形界面的DDD调试器等，以方便内核的调试工作。</p><br /><a name="IDA0EAZB"><strong></strong></a><br /><img height="463" alt="" src="http://www-128.ibm.com/developerworks/cn/linux/l-kdb/images/image004.jpg" width="527" border="0" /><br /><p><a name="IDAGFAZB"><span class="smalltitle"><strong><font size="3">2．4 kgdb的一些特点和不足</font></strong></span></a></p><p>使用kgdb作为内核调试环境最大的不足在于对kgdb硬件环境的要求较高，必须使用两台计算机分别作为target和development机。尽管使用虚拟机的方法可以只用一台PC即能搭建调试环境，但是对系统其他方面的性能也提出了一定的要求，同时也增加了搭建调试环境时复杂程度。另外，kgdb内核的编译、配置也比较复杂，需要一定的技巧，笔者当时做的时候也是费了很多周折。当调试过程结束后时，还需要重新制作所要发布的内核。使用kgdb并不能进行全程调试，也就是说kgdb并不能用于调试系统一开始的初始化引导过程。</p><p>不过，kgdb是一个不错的内核调试工具，使用它可以进行对内核的全面调试，甚至可以调试内核的中断处理程序。如果在一些图形化的开发工具的帮助下，对内核的调试将更方便。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><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-128.ibm.com/developerworks/cn/linux/l-kdb/#main"><strong><font color="#996699">回页首</font></strong></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="IDANFAZB"><span class="atitle"><font size="4">3. 使用SkyEye构建Linux内核调试环境</font></span></a></p><p>SkyEye是一个开源软件项目（OPenSource Software）,SkyEye项目的目标是在通用的Linux和Windows平台上模拟常见的嵌入式计算机系统。SkyEye实现了一个指令级的硬件模拟平台，可以模拟多种嵌入式开发板，支持多种CPU指令集。SkyEye 的核心是 GNU 的 gdb 项目，它把gdb和 ARM Simulator很好地结合在了一起。加入ARMulator 的功能之后，它就可以来仿真嵌入式开发板，在它上面不仅可以调试硬件驱动，还可以调试操作系统。Skyeye项目目前已经在嵌入式系统开发领域得到了很大的推广。</p><p><a name="IDATFAZB"><span class="smalltitle"><strong><font size="3">3．1 SkyEye的安装和μcLinux内核编译</font></strong></span></a></p><p><strong>3．1.1 SkyEye的安装</strong></p><p>SkyEye的安装不是本文要介绍的重点，目前已经有大量的资料对此进行了介绍。有关SkyEye的安装与使用的内容请查阅参考资料[11]。由于skyeye面目主要用于嵌入式系统领域，所以在skyeye上经常使用的是μcLinux系统，当然使用Linux作为skyeye上运行的系统也是可以的。由于介绍μcLinux 2.6在skyeye上编译的相关资料并不多，所以下面进行详细介绍。</p><p><strong>3．1.2 μcLinux 2.6.x的编译</strong></p><p>要在SkyEye中调试操作系统内核，首先必须使被调试内核能在SkyEye所模拟的开发板上正确运行。因此，正确编译待调试操作系统内核并配置SkyEye是进行内核调试的第一步。下面我们以SkyEye模拟基于Atmel AT91X40的开发板，并运行μcLinux 2.6为例介绍SkyEye的具体调试方法。</p><p>I、安装交叉编译环境</p><p>先安装交叉编译器。尽管在一些资料中说明使用工具链arm-elf-tools-20040427.sh ,但是由于arm-elf-xxx与arm-linux-xxx对宏及链接处理的不同，经验证明使用arm-elf-xxx工具链在链接vmlinux的最后阶段将会出错。所以这里我们使用的交叉编译工具链是：arm-uclinux-tools-base-gcc3.4.0-20040713.sh，关于该交叉编译工具链的下载地址请参见[6]。注意以下步骤最好用root用户来执行。</p><br /><a name="IDAAGAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl tmp]#chmod +x  arm-uclinux-tools-base-gcc3.4.0-20040713.sh[root@lisl tmp]#./arm-uclinux-tools-base-gcc3.4.0-20040713.sh</font></code></pre></td></tr></tbody></table><br /><p>安装交叉编译工具链之后，请确保工具链安装路径存在于系统PATH变量中。</p><p>II、制作μcLinux内核</p><p>得到μcLinux发布包的一个最容易的方法是直接访问uClinux.org站点[7]。该站点发布的内核版本可能不是最新的，但你能找到一个最新的μcLinux补丁以及找一个对应的Linux内核版本来制作一个最新的μcLinux内核。这里，将使用这种方法来制作最新的μcLinux内核。目前（笔者记录编写此文章时），所能得到的发布包的最新版本是uClinux-dist.20041215.tar.gz。</p><p>下载uClinux-dist.20041215.tar.gz，文件的下载地址请参见[7]。</p><p>下载linux-2.6.9-hsc0.patch.gz，文件的下载地址请参见[8]。</p><p>下载linux-2.6.9.tar.bz2，文件的下载地址请参见[9]。</p><p>现在我们得到了整个的linux-2.6.9源代码，以及所需的内核补丁。请准备一个有2GB空间的目录里来完成以下制作μcLinux内核的过程。</p><br /><a name="IDAOGAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl tmp]# tar -jxvf uClinux-dist-20041215.tar.bz2[root@lisl uClinux-dist]# tar -jxvf  linux-2.6.9.tar.bz2[root@lisl uClinux-dist]# gzip -dc linux-2.6.9-hsc0.patch.gz | patch -p0 </font></code></pre></td></tr></tbody></table><br /><p>或者使用：</p><br /><a name="IDAWGAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl uClinux-dist]# gunzip linux-2.6.9-hsc0.patch.gz [root@lisl uClinux-dist]patch -p0 &lt; linux-2.6.9-hsc0.patch</font></code></pre></td></tr></tbody></table><br /><p>执行以上过程后，将在linux-2.6.9/arch目录下生成一个补丁目录－armnommu。删除原来μcLinux目录里的linux-2.6.x(即那个linux-2.6.9-uc0)，并将我们打好补丁的Linux内核目录更名为linux-2.6.x。</p><br /><a name="IDA4GAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl uClinux-dist]# rm -rf linux-2.6.x/[root@lisl uClinux-dist]# mv linux-2.6.9 linux-2.6.x</font></code></pre></td></tr></tbody></table><br /><p>III、配置和编译μcLinux内核</p><p>因为只是出于调试μcLinux内核的目的，这里没有生成uClibc库文件及romfs.img文件。在发布μcLinux时，已经预置了某些常用嵌入式开发板的配置文件，因此这里直接使用这些配置文件，过程如下：</p><br /><a name="IDAHHAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl uClinux-dist]# cd linux-2.6.x[root@lisl linux-2.6.x]#make ARCH=armnommu CROSS_COMPILE=arm-uclinux- atmel_deconfig</font></code></pre></td></tr></tbody></table><br /><p>atmel_deconfig文件是μcLinux发布时提供的一个配置文件，存放于目录linux-2.6.x /arch/armnommu/configs/中。</p><br /><a name="IDAPHAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl linux-2.6.x]#make ARCH=armnommu CROSS_COMPILE=arm-uclinux-oldconfig</font></code></pre></td></tr></tbody></table><br /><p>下面编译配置好的内核：</p><br /><a name="IDAXHAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">[root@lisl linux-2.6.x]# make ARCH=armnommu CROSS_COMPILE=arm-uclinux- v=1</font></code></pre></td></tr></tbody></table><br /><p>一般情况下，编译将顺利结束并在Linux-2.6.x/目录下生成未经压缩的μcLinux内核文件vmlinux。需要注意的是为了调试μcLinux内核，需要打开内核编译的调试选项-g，使编译后的内核带有调试信息。打开编译选项的方法可以选择：</p><p>"Kernel debugging-&gt;Compile the kernel with debug info"后将自动打开调试选项。也可以直接修改linux-2.6.x目录下的Makefile文件，为其打开调试开关。方法如下：。</p><br /><a name="IDADIAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">CFLAGS  += -g </font></code></pre></td></tr></tbody></table><br /><p>最容易出现的问题是找不到arm-uclinux-gcc命令的错误，主要原因是PATH变量中没有包含arm-uclinux-gcc命令所在目录。在arm-linux-gcc的缺省安装情况下，它的安装目录是/root/bin/arm-linux-tool/，使用以下命令将路径加到PATH环境变量中。</p><br /><a name="IDALIAZB"><strong></strong></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">Export PATH＝$PATH:/root/bin/arm-linux-tool/bin</font></code></pre></td></tr></tbody></table><br /><p>IV、根文件系统的制作</p><p>Linux内核在启动的时的最后操作之一是加载根文件系统。根文件系统中存放了嵌入式系统使用的所有应用程序、库文件及其他一些需要用到的服务。出于文章篇幅的考虑，这里不打算介绍根文件系统的制作方法，读者可以查阅一些其他的相关资料。值得注意的是，由配置文件skyeye.conf指定了装载到内核中的根文件系统。</p><p><a name="IDAUIAZB"><span class="smalltitle"><strong><font size="3">3．2 使用SkyEye调试</font></strong></span></a></p><p>编译完μcLinux内核后，就可以在SkyEye中调试该ELF执行文件格式的内核了。前面已经说过利用SkyEye调试内核与使用gdb调试运用程序的方法相同。</p><p>需要提醒读者的是，SkyEye的配置文件－skyeye.conf记录了模拟的硬件配置和模拟执行行为。该配置文件是SkyEye系统中一个及其重要的文件，很多错误和异常情况的发生都和该文件有关。在安装配置SkyEye出错时，请首先检查该配置文件然后再进行其他的工作。此时，所有的准备工作已经完成，就可以进行内核的调试工作了。</p><p><a name="IDA1IAZB"><span class="smalltitle"><strong><font size="3">3．3使用SkyEye调试内核的特点和不足</font></strong></span></a></p><p>在SkyEye中可以进行对Linux系统内核的全程调试。由于SkyEye目前主要支持基于ARM内核的CPU，因此一般而言需要使用交叉编译工具编译待调试的Linux系统内核。另外，制作SkyEye中使用的内核编译、配置过程比较复杂、繁琐。不过，当调试过程结束后无需重新制作所要发布的内核。</p><p>SkyEye只是对系统硬件进行了一定程度上的模拟，所以在SkyEye与真实硬件环境相比较而言还是有一定的差距，这对一些与硬件紧密相关的调试可能会有一定的影响，例如驱动程序的调试。不过对于大部分软件的调试，SkyEye已经提供了精度足够的模拟了。</p><p>SkyEye的下一个目标是和eclipse结合，有了图形界面，能为调试和查看源码提供一些方便。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><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-128.ibm.com/developerworks/cn/linux/l-kdb/#main"><strong><font color="#996699">回页首</font></strong></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="IDADJAZB"><span class="atitle"><font size="4">4. 使用UML调试Linux内核</font></span></a></p><p>User-mode Linux（UML）简单说来就是在Linux内运行的Linux。该项目是使Linux内核成为一个运行在 Linux 系统之上单独的、用户空间的进程。UML并不是运行在某种新的硬件体系结构之上，而是运行在基于 Linux 系统调用接口所实现的虚拟机。正是由于UML是一个将Linux作为用户空间进程运行的特性，可以使用UML来进行操作系统内核的调试。有关UML的介绍请查阅参考资料[10]、[12]。</p><p><a name="IDAJJAZB"><span class="smalltitle"><strong><font size="3">4．1 UML的安装与调试</font></strong></span></a></p><p>UML的安装需要一台运行Linux 2.2.15以上，或者2.3.22以上的I386机器。对于2.6.8及其以前版本的UML，采用两种形式发布：一种是以RPM包的形式发布，一种是以源代码的形式提供UML的安装。按照UML的说明，以RPM形式提供的安装包比较陈旧且会有许多问题。以二进制形式发布的UML包并不包含所需要的调试信息，这些代码在发布时已经做了程度不同的优化。所以，要想利用UML调试Linux系统内核，需要使用最新的UML patch代码和对应版本的Linux内核编译、安装UML。完成UML的补丁之后，会在arch目录下产生一个um目录，主要的UML代码都放在该目录下。</p><p>从2.6.9版本之后（包含2.6.9版本的Linux），User-Mode Linux已经随Linux内核源代码树一起发布，它存放于arch/um目录下。</p><p>编译好UML的内核之后，直接使用gdb运行已经编译好的内核即可进行调试。</p><p><a name="IDARJAZB"><span class="smalltitle"><strong><font size="3">4．2使用UML调试系统内核的特点和不足</font></strong></span></a></p><p>目前，用户模式 Linux 虚拟机也存在一定的局限性。由于UML虚拟机是基于Linux系统调用接口的方式实现的虚拟机，所以用户模式内核不能访问主机系统上的硬件设备。因此，UML并不适合于调试那些处理实际硬件的驱动程序。不过，如果所编写的内核程序不是硬件驱动，例如Linux文件系统、协议栈等情况，使用UML作为调试工具还是一个不错的选择。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><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-128.ibm.com/developerworks/cn/linux/l-kdb/#main"><strong><font color="#996699">回页首</font></strong></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="IDAXJAZB"><span class="atitle"><font size="4">5. 内核调试配置选项</font></span></a></p><p>为了方便调试和测试代码，内核提供了许多与内核调试相关的配置选项。这些选项大部分都在内核配置编辑器的内核开发(kernel hacking)菜单项中。在内核配置目录树菜单的其他地方也还有一些可配置的调试选项，下面将对他们作一定的介绍。</p><p>Page alloc debugging ：CONFIG_DEBUG_PAGEALLOC: </p><p>不使用该选项时，释放的内存页将从内核地址空间中移出。使用该选项后，内核推迟移出内存页的过程，因此能够发现内存泄漏的错误。</p><p>Debug memory allocations ：CONFIG_DEBUG_SLAB: </p><p>该打开该选项时，在内核执行内存分配之前将执行多种类型检查，通过这些类型检查可以发现诸如内核过量分配或者未初始化等错误。内核将会在每次分配内存前后时设置一些警戒值，如果这些值发生了变化那么内核就会知道内存已经被操作过并给出明确的提示，从而使各种隐晦的错误变得容易被跟踪。</p><p>Spinlock debugging ：CONFIG_DEBUG_SPINLOCK:</p><p>打开此选项时，内核将能够发现spinlock未初始化及各种其他的错误,能用于排除一些死锁引起的错误。</p><p>Sleep-inside-spinlock checking：CONFIG_DEBUG_SPINLOCK_SLEEP: </p><p>打开该选项时，当spinlock的持有者要睡眠时会执行相应的检查。实际上即使调用者目前没有睡眠，而只是存在睡眠的可能性时也会给出提示。</p><p>Compile the kernel with debug info ：CONFIG_DEBUG_INFO:</p><p>打开该选项时，编译出的内核将会包含全部的调试信息，使用gdb时需要这些调试信息。</p><p>Stack utilization instrumentation ：CONFIG_DEBUG_STACK_USAGE:</p><p>该选项用于跟踪内核栈的溢出错误，一个内核栈溢出错误的明显的现象是产生oops错误却没有列出系统的调用栈信息。该选项将使内核进行栈溢出检查，并使内核进行栈使用的统计。</p><p>Driver Core verbose debug messages：CONFIG_DEBUG_DRIVER:</p><p>该选项位于"Device drivers-&gt; Generic Driver Options"下，打开该选项使得内核驱动核心产生大量的调试信息，并将他们记录到系统日志中。</p><p>Verbose SCSI error reporting (kernel size +=12K) ：CONFIG_SCSI_CONSTANTS:</p><p>该选项位于"Device drivers/SCSI device support"下。当SCSI设备出错时内核将给出详细的出错信息。</p><p>Event debugging：CONFIG_INPUT_EVBUG: </p><p>打开该选项时，会将输入子系统的错误及所有事件都输出到系统日志中。该选项在产生了详细的输入报告的同时，也会导致一定的安全问题。</p><p>以上内核编译选项需要读者根据自己所进行的内核编程的实际情况，灵活选取。在使用以上介绍的三种源代码级的内核调试工具时，一般需要选取CONFIG_DEBUG_INFO选项，以使编译的内核包含调试信息。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><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-128.ibm.com/developerworks/cn/linux/l-kdb/#main"><strong><font color="#996699">回页首</font></strong></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="IDAQKAZB"><span class="atitle"><font size="4">6. 总结</font></span></a></p><p>上面介绍了一些调试Linux内核的方法，特别是详细介绍了三种源代码级的内核调试工具，以及搭建这些内核调试环境的方法，读者可以根据自己的情况从中作出选择。</p><p>调试工具（例如gdb）的运行都需要操作系统的支持，而此时内核由于一些错误的代码而不能正确执行对系统的管理功能，所以对内核的调试必须采取一些特殊的方法进行。以上介绍的三种源代码级的调试方法，可以归纳为以下两种策略：</p><p>I、为内核增加调试Stub，利用调试Stub进行远程调试，这种调试策略需要target及development机器才能完成调试任务。</p><p>II、将虚拟机技术与调试工具相结合，使Linux内核在虚拟机中运行从而利用调试器对内核进行调试。这种策略需要制作适合在虚拟机中运行的系统内核。</p><p>由不同的调试策略决定了进行调试时不同的工作原理，同时也形成了各种调试方法不同的软硬件需求和各自的特点。</p><p>另外，需要说明的是内核调试能力的掌握很大程度上取决于经验和对整个操作系统的深入理解。对系统内核的全面深入的理解，将能在很大程度上加快对Linux系统内核的开发和调试。</p><p>对系统内核的调试技术和方法绝不止上面介绍所涉及的内容，这里只是介绍了一些经常看到和听到方法。在Linux内核向前发展的同时，内核的调试技术也在不断的进步。希望以上介绍的一些方法能对读者开发和学习Linux有所帮助。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><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-128.ibm.com/developerworks/cn/linux/l-kdb/#main"><strong><font color="#996699">回页首</font></strong></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="IDA2KAZB"><span class="atitle"><font size="4">参考资料</font></span></a></p><p>[1] <a href="http://oss.sgi.com/projects/kdb/"><font color="#5c81a7">http://oss.sgi.com/projects/kdb/</font></a></p><p>[2] <a href="http://www-128.ibm.com/developerworks/cn/linux/sdk/l-debug/index.html"><font color="#5c81a7">http://www-128.ibm.com/developerworks/cn/linux/sdk/l-debug/index.html</font></a></p><p>[3] <a href="http://www-128.ibm.com/developerworks/cn/linux/l-kdbug"><font color="#5c81a7">http://www-128.ibm.com/developerworks/cn/linux/l-kdbug</font></a>/</p><p>[4] <a href="http://www-128.ibm.com/developerworks/cn/linux/l-kprobes.html"><font color="#5c81a7">http://www-128.ibm.com/developerworks/cn/linux/l-kprobes.html</font></a></p><p>[5] <a href="http://kgdb.linsyssoft.com/downloads.htm"><font color="#5c81a7">http://kgdb.linsyssoft.com/downloads.htm</font></a></p><p>[6] <a href="ftp://166.111.68.183/"><font color="#5c81a7">ftp://166.111.68.183</font></a></p><p>[8] <a href="http://www.uclinux.org/pub/uClinux/dist/"><font color="#996699">http://www.uclinux.org/pub/uClinux/dist/</font></a></p><p>[9] <a href="http://opensrc.sec.samsung.com/download/linux-2.6.9-hsc0.patch.gz"><font color="#5c81a7">http://opensrc.sec.samsung.com/download/linux-2.6.9-hsc0.patch.gz</font></a></p><p>[10] <a href="http://www.kernel.org/"><font color="#996699">http:// www.kernel.org</font></a></p><p>[11] <a href="http://user-mode-linux.sourceforge.net/"><font color="#5c81a7">http://user-mode-linux.sourceforge.net/</font></a></p><p>[12] <a href="http://www-128.ibm.com/developerworks/cn/linux/l-skyeye/part1/"><font color="#5c81a7">http://www-128.ibm.com/developerworks/cn/linux/l-skyeye/part1/</font></a></p><p>[13] <a href="http://www-128.ibm.com/developerworks/cn/views/linux/tutorials.jsp?cv_doc_id=84978"><font color="#5c81a7">http://www-128.ibm.com/developerworks/cn/views/linux/tutorials.jsp?cv_doc_id=84978</font></a></p><br /></td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.cnitblog.com/flutist1225/aggbug/19967.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:13 <a href="http://www.cnitblog.com/flutist1225/articles/19967.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用GProf来优化你的C/C++程序（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19966.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:10:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19966.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19966.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19966.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19966.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19966.html</trackback:ping><description><![CDATA[
		<h2>使用GProf来优化你的C/C++程序</h2>
		<img height="63" alt="Profiling with GProf" hspace="10" src="http://www.tldp.org/linuxfocus/common/images2/article371/profilingpicture.png" width="400" />
		<!-- ABSTRACT OF THE ARTICLE -->
		<br />
		<!-- HEAD_OF_THE_ARTICLE_STOP -->
		<!-- BODY_OF_THE_ARTICLE_START -->
		<a name="371lfindex0"> </a>
		<h2>Profiling in a nutshell</h2>程序概要分析的概念非常简单：通过记录各个函数的调用和结束时间，我们可以计算出程序的最大运行时的程序段。这种方法听起来似乎要花费很多气力——幸运的是，我们其实离真理并不远！我们只需要在用 gcc编译时加上一个额外的参数('-pg')，运行这个（编译好的）程序（来搜集程序概要分析的有关数据），然后运行'gprof'以更方便的分析这些结果。<a name="371lfindex1"> </a><h2>案例分析: Pathalizer</h2>我使用了一个现实中使用的程序来作为例子，是 <a href="http://pathalizer.bzzt.net/">pathalizer</a>的一部分: 即<code>event2dot</code>，一个将路径“事件”描述文件转化为图形化“dot”文件的工具（executable which translates a pathalizer 'events' file to a graphviz 'dot' file）。 
<p>简单的说，它从一个文件里面读取各种事件，然后将它们分别保存为图像(以页为节点，且将页与页之间的转变作为边），然后将这些图像整合为一张大的图形，并保存为图形化的'dot'格式文件。<a name="371lfindex2"> </a></p><h3>给程序计时</h3>先让我们给我们未经优化的程序计一下时，看看它们的运行要多少时间。在我的计算机上使用<code>event2dot</code>并用源码里的例子作为输入（大概55000的数据），大致要三分多钟： 
<p></p><pre class="code">real    3m36.316s<br />user    0m55.590s<br />sys     0m1.070s<br /></pre><a name="371lfindex3"> </a><h3>程序分析</h3>要使用gprof 作概要分析，在编译的时候要加上'-pg' 选项，我们就是如下重新编译源码如下：<pre class="code">g++ -pg dotgen.cpp readfile.cpp main.cpp graph.cpp config.cpp -o event2dot<br /></pre><p>现在我们可以再次运行<code>event2dot</code>，并使用我们前面使用的测试数据。这次我们运行的时候，<code>event2dot</code>运行的分析数据会被搜集并保存在'gmon.out'文件中，我们可以通过运行'gprof <code>event2dot</code> | less'来查看结果。</p><p>gprof 会显示出如下的函数比较重要：</p><pre class="code"> % cumulative  self              self     total<br /> time seconds  seconds  calls s/call s/call name<br />43.32   46.03  46.03 339952989  0.00  0.00 CompareNodes(Node *,Node *)<br />25.06   72.66  26.63    55000   0.00  0.00 getNode(char *,NodeListNode *&amp;)<br />16.80   90.51  17.85 339433374  0.00  0.00 CompareEdges(Edge *,AnnotatedEdge *)<br />12.70  104.01  13.50    51987   0.00  0.00 addAnnotatedEdge(AnnotatedGraph *,Edge *)<br /> 1.98  106.11   2.10    51987   0.00  0.00 addEdge(Graph *,Node *,Node *)<br /> 0.07  106.18   0.07        1   0.07  0.07 FindTreshold(AnnotatedEdge *,int)<br /> 0.06  106.24   0.06        1   0.06 28.79 getGraphFromFile(char *,NodeListNode *&amp;,Config *)<br /> 0.02  106.26   0.02        1   0.02 77.40 summarize(GraphListNode *,Config *)<br /> 0.00  106.26   0.00    55000   0.00  0.00 FixName(char *)<br /></pre>可以看出，第一个函数比较重要: 程序里面绝大部分的运行时都被它给占据了。<a name="371lfindex4"> </a><h3>优化</h3>上面结果可以看出，这个程序大部分的时间都花在了<code>CompareNodes</code>函数上，用 grep 查看一下则发现CompareNodes 只是被<code>CompareEdges</code>调用了一次而已, 而CompareEdges则只被<code>addAnnotatedEdge</code>调用——它们都出现在了上面的清单中。这儿就是我们应该做点优化的地方了吧！ 
<p>我们注意到<code>addAnnotatedEdge</code>遍历了一个链表。虽然链表是易于实现，但是却实在不是最好的数据类型。我们决定将链表 g-&gt;edges 用二叉树来代替: 这将会使得查找更快。<a name="371lfindex5"> </a></p><h3>结果</h3>现在我们看一下优化后的运行结果:<pre class="code">real    2m19.314s<br />user    0m36.370s<br />sys     0m0.940s<br /></pre><a name="371lfindex6"> </a><h3>第二遍</h3>再次运行 gprof 来分析:<pre class="code">%   cumulative self           self    total<br /> time   seconds seconds calls  s/call  s/call name<br />87.01     25.25  25.25  55000    0.00    0.00 getNode(char *,NodeListNode *&amp;)<br />10.65     28.34   3.09  51987    0.00    0.00 addEdge(Graph *,Node *,Node *)<br /></pre>看起来以前占用大量运行时的函数现在已经不再是占用运行时的大头了！我们试一下再优化一下呢：用节点哈希表来取代节点树。 
<p>这次简直是个巨大的进步:</p><pre class="code">real    0m3.269s<br />user    0m0.830s<br />sys     0m0.090s<br /></pre><a name="371lfindex7"> </a><h2>其他 C/C++ 程序分析器</h2><img height="337" alt="" src="http://www.tldp.org/linuxfocus/common/images2/article371/shot.png" width="565" align="right" />还有其他很多分析器可以使用gprof 的数据, 例如<a href="http://kprof.sf.net/">KProf</a> (截屏) 和 <a href="http://mvertes.free.fr/">cgprof</a>。虽然图形界面的看起来更舒服，但我个人认为命令行的gprof 使用更方便。<br clear="all" /><a name="371lfindex8"> </a><h2>对其他语言的程序进行分析</h2>我们这里介绍了用gprof 来对C/C++ 的程序进行分析，对其他语言其实一样可以做到: 对 Perl,我们可以用Devel::DProf 模块。你的程序应该以<code>perl -d:DProf mycode.pl</code>来开始，并使用<code>dprofpp</code>来查看并分析结果。如果你可以用gcj 来编译你的Java 程序，你也可以使用gprof，然而目前还只支持单线程的Java 代码。<a name="371lfindex9"> </a><h2>结论</h2>就像我们已经看到的，我们可以使用程序概要分析快速的找到一个程序里面值得优化的地方。在值得优化的地方优化，我们可以将一个程序的运行时从 3分36秒 减少到少于 5秒，就像从上面的例子看到的一样。<a name="371lfindex10"> </a><h2>References</h2><ul><li>Pathalizer: <a href="http://pathalizer.sf.net/">http://pathalizer.sf.net</a><br /><br /></li><li>KProf: <a href="http://kprof.sf.net/">http://kprof.sf.net</a><br /><br /></li><li>cgprof: <a href="http://mvertes.free.fr/">http://mvertes.free.fr</a><br /><br /></li><li>Devel::DProf <a href="http://www.perldoc.com/perl5.8.0/lib/Devel/DProf.html">http://www.perldoc.com/perl5.8.0/lib/Devel/DProf.html</a><br /><br /></li><li>gcj: <a href="http://gcc.gnu.org/java">http://gcc.gnu.org/java</a><br /><br /></li><li>: pathalizer example files: <a href="http://www.tldp.org/linuxfocus/common/src2/article371/index.html">download for article371</a><br /></li></ul><img src ="http://www.cnitblog.com/flutist1225/aggbug/19966.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:10 <a href="http://www.cnitblog.com/flutist1225/articles/19966.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>自己写 Netfilter 匹配器（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19965.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:08:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19965.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19965.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19965.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19965.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19965.html</trackback:ping><description><![CDATA[
		<h2>
				<a name="367lfindex0">概述</a>
		</h2>
		<a name="367lfindex0">写一个 iptables/netfilter 匹配模块的大体步骤如下：</a>
		<ul>
				<li>
						<a name="367lfindex0">找到你要匹配的具体情况。</a>
				</li>
				<li>
						<a name="367lfindex0">写用于接受参数的用户空间部分程序。</a>
				</li>
				<li>
						<a name="367lfindex0">写用于分析包信息，得出是否匹配结论的内核空间部分程序。</a>
				</li>
		</ul>
		<a name="367lfindex1"> </a>
		<h2>1.0 iptables 模块</h2>iptables 库的用途基本上讲就是和用户交互，它捕获用户要传送给核心态程序的参数。<a name="367lfindex2"> </a><h2>1.1 可用的数据结构和函数</h2>首先是一些基本数据结构。&lt;<i>iptables/include/iptables.h</i>&gt;<br />稍后文中就将可以看到这些结构的用途了。 
<table width="70%"><tbody><tr><td><pre class="code">/* Include file for additions: new matches and targets. */<br />struct iptables_match<br />{<br />   struct iptables_match *next;<br /><br />   ipt_chainlabel name;<br /><br />   const char *version;<br /><br />   /* Size of match data. */<br />   size_t size;<br /><br />   /* Size of match data relevent for userspace comparison purposes */<br />   size_t userspacesize;<br /><br />   /* Function which prints out usage message. */<br />   void (*help)(void);<br /><br />   /* Initialize the match. */<br />   void (*init)(struct ipt_entry_match *m, unsigned int *nfcache);<br /><br />   /* Function which parses command options; returns true if it<br />           ate an option */<br />   int (*parse)(int c, char **argv, int invert, unsigned int *flags,<br />           const struct ipt_entry *entry,<br />           unsigned int *nfcache,<br />           struct ipt_entry_match **match);<br /><br />   /* Final check; exit if not ok. */<br />   void (*final_check)(unsigned int flags);<br /><br />   /* Prints out the match iff non-NULL: put space at end */<br />   void (*print)(const struct ipt_ip *ip,<br />            const struct ipt_entry_match *match, int numeric);<br /><br />   /* Saves the match info in parsable form to stdout. */<br />   void (*save)(const struct ipt_ip *ip,<br />           const struct ipt_entry_match *match);<br /><br />   /* Pointer to list of extra command-line options */<br />   const struct option *extra_opts;<br /><br />   /* Ignore these men behind the curtain: */<br />   unsigned int option_offset;<br />   struct ipt_entry_match *m;<br />   unsigned int mflags;<br />#ifdef NO_SHARED_LIBS<br />   unsigned int loaded; /* simulate loading so options are merged properly */<br />#endif<br />};<br /></pre></td></tr></tbody></table><a name="367lfindex3"> </a><h2>1.2 深入骨架程序</h2><a name="367lfindex4"> </a><h3>1.2.1 初始化</h3>我们首先初始化 'iptables_match' 结构中的常用字段：<pre class="code">static struct iptables_match ipaddr<br />= {<br /></pre>'Name' 是你的函数库的文件名（也就是 libipt_ipaddr）。<br />你不能在这个位置放其它的东西，这是用来自动加载你的库的。<pre class="code">    .name            = "ipaddr",<br /></pre>下一个字段 'version' 是 iptables 的版本。后面的两个字段都是用于保持用户态程序和核心态共享结构的大小一致性的。<pre class="code">    .version         = IPTABLES_VERSION,<br />    .size            = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)),<br />    .userspacesize   = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)),<br /></pre>'Help' 是用户输入 'iptables -m module -h' 的时候要调用的函数。'Parse' 是用户输入一条新规则的时候调用的，用于验证参数的合法性。'print' 就是使用 'iptables -L' 的时候显示前面添加的规则的。<pre class="code">    .help            = &amp;help,<br />    .init            = &amp;init,<br />    .parse           = &amp;parse,<br />    .final_check     = &amp;final_check,<br />    .print           = &amp;print,<br />    .save            = &amp;save,<br />    .extra_opts      = opts<br />};<br /></pre>iptables 架构能够支持多个共享库。每个共享库必须使用 &lt;<i>iptables/iptables.c</i>&gt; 中定义的 'register_match()' 向 iptables 注册。这个函数将在模块被 iptables 加载的时候调用。更多信息请参考:'man dlopen'。<pre class="code">void _init(void)<br />{<br />   register_match(&amp;ipaddr);<br />}<br /></pre><a name="367lfindex5"> </a><h3>1.2.2 save 函数</h3>如果我们有一个需要保存的规则集，可以利用 iptables 提供的工具 'iptables-save'，它可以保存下所有的规则。显然你需要扩展这个工具来保存下来这些规则。这个扩展通过 save 函数完成。<pre class="code">static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)<br />{<br />   const struct ipt_ipaddr_info *info = (const struct ipt_ipaddr_info *)match-&gt;data;<br /></pre>如果源地址是规则的一部分的话，打印它。<pre class="code">   if (info-&gt;flags &amp; IPADDR_SRC) {<br />      if (info-&gt;flags &amp; IPADDR_SRC_INV)<br />         printf("! ");<br />      printf("--ipsrc ");<br />      print_ipaddr((u_int32_t *)&amp;info-&gt;ipaddr.src);<br />   }<br /></pre>如果目的地址是规则的一部分的话就打印目的地址。<pre class="code">   if (info-&gt;flags &amp; IPADDR_DST) {<br />      if (info-&gt;flags &amp; IPADDR_DST_INV)<br />         printf("! ");<br />      printf("--ipdst ");<br />      print_ipaddr((u_int32_t *)&amp;info-&gt;ipaddr.dst);<br />   }<br />}<br /></pre><a name="367lfindex6"> </a><h3>1.2.3 print 函数</h3>和上面的 save 所蕴含的哲学一样，也有一个 print 函数用于打印规则。它在 'iptables -L' 的时候被调用。我们将在下文里看到参数 'ipt_entry_match *match' 的用途，不过我们已经对它有了一点概念了，是吧？<pre class="code">static void print(const struct ipt_ip *ip,<br />                  const struct ipt_entry_match *match,<br />                  int numeric)<br />{<br />   const struct ipt_ipaddr_info *info = (const struct ipt_ipaddr_info *)match-&gt;data;<br /><br />   if (info-&gt;flags &amp; IPADDR_SRC) {<br />         printf("src IP ");<br />      if (info-&gt;flags &amp; IPADDR_SRC_INV)<br />         printf("! ");<br />      print_ipaddr((u_int32_t *)&amp;info-&gt;ipaddr.src);<br />   }<br /><br />   if (info-&gt;flags &amp; IPADDR_DST) {<br />      printf("dst IP ");<br />      if (info-&gt;flags &amp; IPADDR_DST_INV)<br />         printf("! ");<br />      print_ipaddr((u_int32_t *)&amp;info-&gt;ipaddr.dst);<br />   }<br />}<br /></pre><a name="367lfindex7"> </a><h3>1.2.4 final check 函数</h3>这个函数是最后一次正确性检查的机会。它在用户输入完规则之后、参数解析刚刚完成的时候被调用。<pre class="code">static void final_check(unsigned int flags)<br />{<br />   if (!flags)<br />      exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Invalid parameters.");<br />}<br /></pre><a name="367lfindex8"> </a><h3>1.2.5 parse 函数</h3>parse 是最重要的一个函数，因为这里要检查参数的正确性，并写入我们将共享给核心态程序的信息。它在每次参数被发现的时候被调用，也就是说，如果用户输入了两个参数，这个函数就将被以不同的参数代码 c 调用两次。<pre class="code">static int parse(int c, char **argv, int invert, unsigned int *flags,<br />                 const struct ipt_entry *entry,<br />                 unsigned int *nfcache,<br />                 struct ipt_entry_match **match)<br />{<br /></pre>我们使用特殊结构来保存我们要传递给核心态程序的信息。'match' 指针被传递给多个函数，我们可以每次使用同样的数据结构。一旦规则被加载了，这个指针就被复制到了核心态程序里。通过这个方式，内核模块可以知道用户想要分析什么（这正是问题的关键，不是么?）。<pre class="code">   struct ipt_ipaddr_info *info = (struct ipt_ipaddr_info *)(*match)-&gt;data;<br /></pre>每个参数对应着一个单独的值，于是我们能根据进入的参数决定采取何种行动。下文中我们将看到我们如何把参数变成数值。<br /><pre class="code">   switch(c) {<br /></pre>首先，我们检查参数是否被使用了多次。如果使用了多次的话，调用 &lt;<i>iptables/iptables.c</i>&gt; 中定义的 'exit_error()' 函数，这样程序会立刻带着 &lt;<i>iptables/include/iptables_common.h</i>&gt; 中定义的 'PARAMETER_PROBLEM' 的错误状态推出。否则，我们在我们的头文件中定义的 'IPADDR_SRC' 中设置 'flags' 和 'info-&gt;flags'。稍后我们将介绍这个头文件。<br /><br />虽然这两个标志看起来差不多，但是是完全不同的。'flag' 的作用域就是这个函数，而 'info-&gt;flags' 是我们用于和核心态程序共享信息的结构的一部分。<pre class="code">      case '1':<br />         if (*flags &amp; IPADDR_SRC)<br />            exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Only use --ipsrc once!");<br />         *flags |= IPADDR_SRC;<br />         info-&gt;flags |= IPADDR_SRC;<br /></pre>检查如果取反标志 '!' 是否存在，如果有的话，在 'info-&gt;flags' 中写相应的值。<br />之后调用为这个骨架程序所写的内部函数 'parse_ipaddr' 来把 IP 地址从字符串转化为 32 位值。<br /><pre class="code">         if (invert)<br />            info-&gt;flags |= IPADDR_SRC_INV;<br /><br />         parse_ipaddr(argv[optind-1], &amp;info-&gt;ipaddr.src);<br />         break;<br /></pre>同样考虑，我们检查是否存在多次设置，置恰当的标志。<pre class="code">      case '2':<br />         if (*flags &amp; IPADDR_DST)<br />            exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Only use --ipdst once!");<br />         *flags |= IPADDR_DST;<br />         info-&gt;flags |= IPADDR_DST;<br />         if (invert)<br />            info-&gt;flags |= IPADDR_DST_INV;<br /><br />         parse_ipaddr(argv[optind-1], &amp;info-&gt;ipaddr.dst);<br />         break;<br /><br />      default:<br />         return 0;<br />   }<br /><br />   return 1;<br />}<br /></pre><a name="367lfindex9"> </a><h3>1.2.6 options 结构</h3>前文中，我们已经谈到了要将每个参数映射到一个值。 'struct option' 就是一个达到这个目的的好办法。要想得到关于这个结构的进一步信息，强烈建议阅读 'man 3 getopt'。<pre class="code">static struct option opts[] = {<br />   { .name = "ipsrc",   .has_arg = 1,   .flag = 0,   .val = '1' },<br />   { .name = "ipdst",   .has_arg = 1,   .flag = 0,   .val = '2' },<br />   { .name = 0 }<br />};<br /><br /></pre><a name="367lfindex10"> </a><h3>1.2.7 init 函数</h3>init 函数用于初始化一些特定的东西，比如 netfilter 的 cache 系统。现在不必过多考虑这个函数的具体用途。<pre class="code">static void init(struct ipt_entry_match *m, unsigned int *nfcache)<br />{<br />   /* Can't cache this */<br />   *nfcache |= NFC_UNKNOWN;<br />}<br /><br /></pre><a name="367lfindex11"> </a><h3>1.2.7 help 函数</h3>这个函数通过 'iptables -m match_name -h' 被调用，用于显示可用的参数。<pre class="code">static void help(void)<br />{<br />   printf (<br />            "IPADDR v%s options:\n"<br />            "[!] --ipsrc <ip>\t\t The incoming ip addr matches.\n"<br />            "[!] --ipdst <ip>\t\t The outgoing ip addr matches.\n"<br />            "\n", IPTABLES_VERSION<br />         );<br />}<br /><br /></ip></ip></pre><a name="367lfindex12"> </a><h3>1.2.8 头文件 'ipt_ipaddr.h'</h3>这个文件定义了我们需要的一些东西。<pre class="code">#ifndef _IPT_IPADDR_H<br />#define _IPT_IPADDR_H<br /></pre>我们已经在上文中使用了这些特定的值了。<pre class="code">#define IPADDR_SRC   0x01     /* Match source IP addr */<br />#define IPADDR_DST   0x02     /* Match destination IP addr */<br /><br />#define IPADDR_SRC_INV  0x10  /* Negate the condition */<br />#define IPADDR_DST_INV  0x20  /* Negate the condition */<br /></pre>结构 'ipt_ipaddr_info' 是将要被拷贝到核心态程序的那个数据结构。<pre class="code">struct ipt_ipaddr {<br />   u_int32_t src, dst;<br />};<br /><br />struct ipt_ipaddr_info {<br /><br />   struct ipt_ipaddr ipaddr;<br /><br />   /* Flags from above */<br />   u_int8_t flags;<br /><br />};<br /><br />#endif<br /></pre><a name="367lfindex13"> </a><h2>1.3 第一章小结</h2>第一部分中，我们讨论了 iptables 库的作用。我们记述了每个函数的内容和 'ipt_ipaddr_info' 这个用于保存信息的将要被拷贝到核心态程序来做进一步处理的重要结构。我们也看到了 iptables 结构和如何注册一个新的库。<br />应该注意，这仅仅是一个用于演示框架如何工作的骨架程序。而且，'ipt_ipaddr_info' 和其他类似的东西并不是 iptables/netfilter 的一部分，而仅仅是这个例子的一部分。<a name="367lfindex14"> </a><h2>2.0 netfilter 模块</h2>一个匹配模块的工作就是察看每一个收到的包并决定是否符合某个判决准则。这个模块要做如下工作： 
<ul><li>接收每个包，并察看匹配模块相关的表 
</li><li>告知 netfilter，我们的模块是否匹配上了这个包</li></ul><a name="367lfindex15"> </a><h2>2.1 可用的函数与数据结构</h2>首先是一些基本数据结构，这些数据结构定义在 &lt;<i>linux/netfilter_ipv4/ip_tables.h</i>&gt;。<br />如果你对这个结构以及前面的 iptables 部分还有兴趣的话，你可以看看 Rusty Russell 和 Harald Welte 写的 <a href="http://www.netfilter.org/documentation/HOWTO/netfilter-hacking-HOWTO.html">netfilter hacking howto</a> 。<pre class="code">struct ipt_match<br />{<br />   struct list_head list;<br /><br />   const char name[IPT_FUNCTION_MAXNAMELEN];<br /><br />   /* Return true or false: return FALSE and set *hotdrop = 1 to<br />           force immediate packet drop. */<br />   /* Arguments changed since 2.4, as this must now handle<br />           non-linear skbs, using skb_copy_bits and<br />           skb_ip_make_writable. */<br />   int (*match)(const struct sk_buff *skb,<br />           const struct net_device *in,<br />           const struct net_device *out,<br />           const void *matchinfo,<br />           int offset,<br />           int *hotdrop);<br /><br />   /* Called when user tries to insert an entry of this type. */<br />   /* Should return true or false. */<br />   int (*checkentry)(const char *tablename,<br />           const struct ipt_ip *ip,<br />           void *matchinfo,<br />           unsigned int matchinfosize,<br />           unsigned int hook_mask);<br /><br />   /* Called when entry of this type deleted. */<br />   void (*destroy)(void *matchinfo, unsigned int matchinfosize);<br /><br />   /* Set this to THIS_MODULE. */<br />   struct module *me;<br />};<br /></pre><a name="367lfindex16"> </a><h2>2.2 深入骨架程序</h2><a name="367lfindex17"> </a><h3>2.2.1 初始化</h3><p>首先,我们初始化 'ipt_match' 数据结构中的常用域。</p><pre class="code">static struct ipt_match ipaddr_match<br />= {<br /></pre>'name' 是你的模块的文件名字符串(也就是说 ipt_ipaddr)。<pre class="code">	.name       = "ipaddr",<br /></pre>下面的字段是框架将要使用的回调函数.'match'是当一个包传送给你的模块的时候要调用的函数.<pre class="code">	.match      = match,<br />	.checkentry = checkentry,<br />	.me         = THIS_MODULE,<br />};<br /></pre>你的内核模块的 init 函数需要通过指向一个 'struct ipt_match' 的指针调用 'ipt_register_match()' 来向 netfilter 框架注册.这个函数在模块被加载的时候调用.<pre class="code">static int __init init(void)<br />{<br />	printk(KERN_INFO "ipt_ipaddr: init!\n");<br />	return ipt_register_match(&amp;ipaddr_match);<br />}<br /></pre>当把模块从内核中移出的时候这个函数会被调用.这里我们进行的工作是注销匹配器。<pre class="code">static void __exit fini(void)<br />{<br />	printk(KERN_INFO "ipt_ipaddr: exit!\n");<br />	ipt_unregister_match(&amp;ipaddr_match);<br />}<br /></pre>设置让这两个函数在模块装入和移出的时候被调用。<pre class="code">module_init(init);<br />module_exit(fini);<br /><br /></pre><a name="367lfindex18"> </a><h3>2.2.2 match 函数</h3>Linux 的 TCP/IP 协议栈包括5个 netfilter 钩子。这样，一个包近来之后，协议栈把包送到相应的钩子，依次进入每个表，再依次叠带每条规则。当你的模块得到包的时候，你的模块就可以进行它的工作了。<pre class="code">static int match(const struct sk_buff *skb,<br />                 const struct net_device *in,<br />                 const struct net_device *out,<br />                 const void *matchinfo,<br />                 int offset,<br />                 const void *hdr,<br />                 u_int16_t datalen,<br />                 int *hotdrop)<br />{<br /></pre>希望你还记着我们在用户态程序里面做了些什么！ :)。现在把用户态程序拷贝过来的数据结构映射到我们这里<pre class="code">	const struct ipt_skeleton_info *info = matchinfo;<br /></pre>'skb' 包含了我们想要处理的包。想要得到关于这个在 linux 的 TCP/IP 协议栈中到处都是功能强大的数据结构的信息，可以看看 Harald Welte 写的一出色的文章 <a href="ftp://ftp.gnumonks.org/pub/doc/skb-doc.html">article (ftp://ftp.gnumonks.org/pub/doc/skb-doc.html)</a> 。<pre class="code">   struct iphdr *iph = skb-&gt;nh.iph;<br /></pre>这里，我们就是打印一些有趣的东西来看看他们长成什么样子。宏 'NIPQUAD' 用于以可读的方式显示一个 IP 地址，它是在 &lt;<i>linux/include/linux/kernel.h</i>&gt; 中定义的。<pre class="code">   printk(KERN_INFO "ipt_ipaddr: IN=%s OUT=%s TOS=0x%02X "<br />                    "TTL=%x SRC=%u.%u.%u.%u DST=%u.%u.%u.%u "<br />                    "ID=%u IPSRC=%u.%u.%u.%u IPDST=%u.%u.%u.%u\n",<br /><br />                    in ? (char *)in : "", out ? (char *)out : "", iph-&gt;tos,<br />                    iph-&gt;ttl, NIPQUAD(iph-&gt;saddr), NIPQUAD(iph-&gt;daddr),<br />                    ntohs(iph-&gt;id), NIPQUAD(info-&gt;ipaddr.src), NIPQUAD(info-&gt;ipaddr.dst)<br />         );<br /></pre>如果输入了 '--ipsrc' 参数，我们察看源地址是否和规则指定的地址相匹配。别忘了考虑反标志 '!'。如果没有匹配，我们返回 0.<pre class="code">   if (info-&gt;flags &amp; IPADDR_SRC) {<br />      if ( (ntohl(iph-&gt;saddr) != ntohl(info-&gt;ipaddr.src)) ^ !!(info-&gt;flags &amp; IPADDR_SRC_INV) ) {<br /><br />         printk(KERN_NOTICE "src IP %u.%u.%u.%u is not matching %s.\n",<br />                            NIPQUAD(info-&gt;ipaddr.src),<br />                            info-&gt;flags &amp; IPADDR_SRC_INV ? " (INV)" : "");<br />         return 0;<br />      }<br />   }<br /></pre>这里，我们进行完全相同的工作，只是察看 '--ipdst' 参数。<pre class="code">   if (info-&gt;flags &amp; IPADDR_DST) {<br />      if ( (ntohl(iph-&gt;daddr) != ntohl(info-&gt;ipaddr.dst)) ^ !!(info-&gt;flags &amp; IPADDR_DST_INV) )  {<br /><br />         printk(KERN_NOTICE "dst IP %u.%u.%u.%u is not matching%s.\n",<br />                            NIPQUAD(info-&gt;ipaddr.dst),<br />                            info-&gt;flags &amp; IPADDR_DST_INV ? " (INV)" : "");<br />         return 0;<br />      }<br />   }<br /></pre>如果都不成功，返回 1，表明我们匹配了这个包。<pre class="code">   return 1;<br />}<br /></pre><a name="367lfindex19"> </a><h3>2.2.3 checkentry 函数</h3>checkentry 通常是最后一次合法性检查的机会。关于它何时被调用有些难以理解。看看 <a href="http://www.mail-archive.com/netfilter-devel@lists.samba.org/msg00625.html">post (http://www.mail-archive.com/netfilter-devel@lists.samba.org/msg00625.html)</a> 作为一个解释吧。这篇文章也是一篇 netfilter hacking howto。<pre class="code">static int checkentry(const char *tablename,<br />                             const struct ipt_ip *ip,<br />                             void *matchinfo,<br />                             unsigned int matchsize,<br />                             unsigned int hook_mask)<br />{<br />   const struct ipt_skeleton_info *info = matchinfo;<br /><br />   if (matchsize != IPT_ALIGN(sizeof(struct ipt_skeleton_info))) {<br />      printk(KERN_ERR "ipt_skeleton: matchsize differ, you may have forgotten to recompile me.\n");<br />      return 0;<br />   }<br /><br />   printk(KERN_INFO "ipt_skeleton: Registered in the %s table, hook=%x, proto=%u\n",<br />                    tablename, hook_mask, ip-&gt;proto);<br /><br />   return 1;<br />}<br /><br /></pre><a name="367lfindex20"> </a><h2>2.3 第二章小结</h2>在第二部分，我们讲了 netfilter 模块以及如何使用特定结构注册它。另外我们还讨论了如何根据用户空间部分给出的判据匹配特定的情况。<a name="367lfindex21"> </a><h2>3.0 运行 iptables/netfilter</h2>我们已经看到了如何写一个新的 iptables/netfilter 匹配模块。现在我们将把它添加到内核中来运行它。这里，我假设你知道如何编译内核。首先把骨架匹配文件从<a href="http://www.tldp.org/linuxfocus/common/src2/article367/index.html">本文下载页面</a>下载下来。<a name="367lfindex22"> </a><h3>3.1 iptables</h3>现在，如果你还没有 iptables 的源代码的话，可以从 <a href="ftp://ftp.netfilter.org/pub/iptables/">ftp://ftp.netfilter.org/pub/iptables/</a> 下载。然后拷贝 'libipt_ipaddr.c' 到 &lt;<i>iptables/extensions/</i>&gt;。<br /><br />这是 &lt;<i>iptables/extensions/Makefile</i>&gt; 中的一行，你应该加上 'ipaddr'。<pre class="code">PF_EXT_SLIB:=ah addrtype comment connlimit connmark conntrack dscp ecn<br />esp hashlimit helper icmp iprange length limit <b>ipaddr</b> mac mark<br />multiport owner physdev pkttype realm rpc sctp standard state tcp tcpmss<br />tos ttl udp unclean CLASSIFY CONNMARK DNAT DSCP ECN LOG MARK MASQUERADE<br />MIRROR NETMAP NOTRACK REDIRECT REJECT SAME SNAT TARPIT TCPMSS TOS TRACE<br />TTL ULOG<br /></pre><a name="367lfindex23"> </a><h3>3.2 内核</h3>首先，你应该拷贝 'ipt_ipaddr.c' 到 &lt;<i>linux/net/ipv4/netfilter/</i>&gt;，拷贝 ' ipt_ipaddr.h' 到 &lt;<i>linux/net/ipv4/netfilter/</i>&gt;。有些读者可能还在使用 2.4 内核，所以我同时提供了 2.4和 2.6 的文件。<br /><br />对于 2.4 内核，编辑 &lt;<i>linux/net/ipv4/netfilter/Config.in</i>&gt; ，加入下面加重的行。<pre class="code"># The simple matches.<br />  dep_tristate '  limit match support' CONFIG_IP_NF_MATCH_LIMIT $CONFIG_IP_NF_IPTABLES<br /><b>  dep_tristate '  ipaddr match support' CONFIG_IP_NF_MATCH_IPADDR $CONFIG_IP_NF_IPTABLES</b></pre>然后，编辑 &lt;<i>linux/Documentation/Configure.help</i>&gt; 加入加重的行。我复制了一些文本来帮助你找到要加入内容的地方。<pre class="code">limit match support<br />CONFIG_IP_NF_MATCH_LIMIT<br />  limit matching allows you to control the rate at which a rule can be<br />  ...<b><br />ipaddr match support<br />CONFIG_IP_NF_MATCH_IPADDR<br />  ipaddr matching. etc etc.</b></pre>最后，你必须把加重的行加入到 &lt;<i>linux/net/ipv4/netfilter/Makefile</i>&gt; 之中。<pre class="code"># matches<br />obj-$(CONFIG_IP_NF_MATCH_HELPER) += ipt_helper.o<br />obj-$(CONFIG_IP_NF_MATCH_LIMIT) += ipt_limit.o<br /><b>obj-$(CONFIG_IP_NF_MATCH_IPADDR) += ipt_ipaddr.o</b></pre>Now for 2.6, files to edit are &lt;<i>linux/net/ipv4/netfilter/Kconfig</i>&gt; and &lt;<i>linux/net/ipv4/netfilter/Makefile</i>&gt;.对 2.6 内核，编辑的文件应该是 &lt;<i>linux/net/ipv4/netfilter/Kconfig</i>&gt;和 &lt;<i>linux/net/ipv4/netfilter/Makefile</i>&gt;。<a name="367lfindex24"> </a><img src ="http://www.cnitblog.com/flutist1225/aggbug/19965.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:08 <a href="http://www.cnitblog.com/flutist1225/articles/19965.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>通过网络完全复制你的电脑（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19964.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:06:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19964.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19964.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19964.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19964.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19964.html</trackback:ping><description><![CDATA[
		<h2>通过网络完全复制你的电脑</h2>
		<img height="150" alt="[Illustration]" hspace="10" src="http://www.tldp.org/linuxfocus/common/images2/article370.png" width="136" />
		<!-- ABSTRACT OF THE ARTICLE -->
		<br />
		<!-- HEAD_OF_THE_ARTICLE_STOP -->
		<!-- BODY_OF_THE_ARTICLE_START -->
		<a name="370lfindex0"> </a>
		<h2>提要</h2>尽管克隆动物（“多莉羊“）甚至人类胚胎的研究依然是一个充满争议和风险的领域，而掌握一些关于“克隆”电脑的知识不但没有害（正确的操作为前提），反而会使你仔细书写配置脚本的能力得到提升。根据摩尔定律以及计算机生产的快速进程，我们在使用电脑时很可能会遇到需要“克隆”我们的电脑的情况——不管是台式机被笔记本取代还是换一台更快的机子。而我们就需要将电脑A 上的<i>所有文件分区</i>复制到电脑B上而且使它正常工作。通常有两种做法：一种就是直接打开机箱，然后将硬盘换掉就可以了——但是但打开机箱通常意味着失去质保，而且这通常很危险——一个没有经验的用户可能会给硬件带来机械的或者电子上的损伤。另一种方法要求两台电脑都有网卡（现在即使是在家用电脑中通常也满足），这种在下面会详细介绍的方法更安全。 
<p>下面描述的所有方法都是建立在网络连接的基础上，即，需要在“<i>源</i>”电脑（就是那台有数据要被复制的电脑）和“<i>目标</i>”电脑（就是需要数据的那台电脑）之间有网络连接。可以直接通过集线器连接，或者通过<i>特殊连接线（crossover cable）</i>将 两张网卡连接起来（注意：一般的网线是不行的）。对目标电脑来说，需要一张Live-CD（如Knoppix 或 LNX-BBC）或一个最小安装，以保证可对网卡进行操作而且使<tt>ssh</tt> 和／或 <tt>netcat</tt>能用。甚至有的软盘上的系统（就像我用<tt>tomsrtb</tt>也能很好工作），而且如果你想安装另一个全新的发行版，这是一个很不错的选择。两台电脑的IP 要在同一个网段，以便他们能相互进行“会话”，如上图。</p><a name="370lfindex1"> </a><h2>可能的方案 </h2><p>通过基本的准备，有几种方法来进行复制：</p><ul><li>通过 <tt>dd</tt>复制 
</li><li><tt>tar / cpio</tt> 管道 
</li><li><tt>rsync </tt></li><li><tt>dump</tt> 和 <tt>restore</tt></li></ul>如果你的两块硬盘不是同样的型号和大小，第一种方法是不可行的或非常复杂的（复制iso 镜像(<tt>dd if=/dev/cdrom of=the.iso</tt>)或者软盘通过<tt>dd</tt>很不错。<a href="http://www.tldp.org/linuxfocus/common/src2/article370/diskcopy.txt">这里 (diskcopy 脚本)</a>是一个使用<tt>dd</tt>的脚本<tt>diskcopy</tt>）。使用<tt>dd</tt>的另一个缺点是：你未使用的空间也会被复制，从而浪费很多无谓的时间。通过<tt>tar</tt> 和 <tt>cpio</tt>的管道会花费很长的时间（长达数小时），而且对文件名和符号连接会有一些限制，在<tt>/dev</tt>时会被阻塞，等等。因此，不推荐此法。如果你的源电脑和目标电脑上的文件系统不一样，那么<b><tt>rsync(1)</tt></b>可能是最好的选择。这只需要有<tt>ssh</tt>正常的运行且文件传输协议正常。而且它还有针对设备文件的<tt>-D</tt>选项以及其他很多针对各种场合各种需求的选项。这是对每天的备份、作镜像或其他任务来说很有用的一个工具，它的手册上还有很多值得学习的例子。通过<tt>rsync</tt>复制的例子在[<a href="http://www.tldp.org/linuxfocus/ChineseGB/March2005/article370.shtml#key-2">1</a>]可以找到。<br /><br />这里，我们使用<tt>dump</tt> 和 <tt>restore</tt>，这样还可以重新分配整个文件系统。这是一个快速、有效且可以通过最少的努力达到我们的目的——简直是理想的解决方法。我需要操作这个过程两次，因为有两台目标电脑。两台目标电脑都能很好的启动、工作，而且复制那些成G 的数据大概只花了我一个小时。这个方法要求源电脑和目标电脑有同样的文件系统。在此，我们假定是<tt>ext2</tt> 或<tt>ext3</tt>,因为这是现在最广泛的使用的两种文件系统（）(参见 <a href="http://www.tldp.org/linuxfocus/ChineseGB/March2005/article370.shtml#sec:Other-systems">下面</a>).<a name="370lfindex2"> </a><h2>配置ssh</h2>一旦配置好最小安装的系统或者Live-CD,下一步就是配置<tt>ssh</tt>(如果你没有像下面描述的使用<tt>netcat</tt>来传送文件)。这需要源电脑运行<tt>sshd</tt>(the secure shell daemon)。如果不确定，请检查 <tt>/etc/init.d/</tt>。在目标电脑上输入（root帐户）：<pre class="code">ssh-keygen -t rsa</pre>为简便起见，不要输入密码。公钥就会保存在<tt>/root/.ssh/id_rsa.pub</tt>文件里。复制此文件到源电脑上<pre class="code">scp /root/.ssh/id_rsa   SourcePC:/tmp</pre>在此 <tt>SourcePC</tt> 是你源电脑的IP 地址，当提示你是否确认时，输入完整的“<tt>yes</tt>”(单独的“<tt>y</tt>” 有时会不行)。在源电脑上你还会被要求输入root 的密码。现在把目标电脑加入你源电脑的可信任网络节点里去<pre class="code">cat /tmp/id_rsa.pub &gt;&gt; /root/.ssh/authorized_keys</pre>为检查是否成功完成，重复上面的复制命令，应该不会再要求你输入密码了！<a name="370lfindex3"> </a><h2>在目标电脑上创建文件系统</h2><p>通常第一步都是对你的硬盘分区，然后创建<tt>ext2/ext3</tt>文件系统。ext3 的需要在<tt>mke2fs</tt> 命令里加一个<tt>-j</tt> (journalling)的选项(需要内核对<tt>ext3</tt>的支持)。你甚至可以将<tt>ext2</tt>的分区转化为 <tt>ext3</tt>的, 参见<b><tt>tune2fs(8</tt>)</b>。假设我们的源电脑上有如下的分区：</p><table cellspacing="2" cellpadding="2" width="56%" align="center" bgcolor="#000000" border="0"><tbody><tr><th valign="top" bgcolor="#ffffff">Filesystem</th><th valign="top" bgcolor="#ffffff">Size</th><th valign="top" bgcolor="#ffffff">Used</th><th valign="top" bgcolor="#ffffff">Use%</th><th valign="top" bgcolor="#ffffff">Mounted on</th></tr><tr><td valign="top" bgcolor="#ffffff">/dev/hda3 </td><td valign="top" bgcolor="#ffffff">2.7G</td><td valign="top" bgcolor="#ffffff">552M</td><td valign="top" bgcolor="#ffffff">22%</td><td valign="top" bgcolor="#ffffff">/</td></tr><tr><td valign="top" bgcolor="#ffffff">/dev/hda5 </td><td valign="top" bgcolor="#ffffff">7.8G <br /></td><td valign="top" bgcolor="#ffffff">1.6G</td><td valign="top" bgcolor="#ffffff">22%</td><td valign="top" bgcolor="#ffffff">/usr</td></tr><tr><td valign="top" bgcolor="#ffffff">/dev/hda7 </td><td valign="top" bgcolor="#ffffff">6.3G</td><td valign="top" bgcolor="#ffffff">1.7G</td><td valign="top" bgcolor="#ffffff">28%</td><td valign="top" bgcolor="#ffffff">/usr/share</td></tr><tr><td valign="top" bgcolor="#ffffff">/dev/hda8 </td><td valign="top" bgcolor="#ffffff">3.4G</td><td valign="top" bgcolor="#ffffff">601M</td><td valign="top" bgcolor="#ffffff">19%</td><td valign="top" bgcolor="#ffffff">/home</td></tr><tr><td valign="top" bgcolor="#ffffff">/dev/hda12 </td><td valign="top" bgcolor="#ffffff">5.3G</td><td valign="top" bgcolor="#ffffff">1.9G</td><td valign="top" bgcolor="#ffffff">37%</td><td valign="top" bgcolor="#ffffff">/opt</td></tr><tr><td valign="top" bgcolor="#ffffff">/dev/hda1 </td><td valign="top" bgcolor="#ffffff">587M</td><td valign="top" bgcolor="#ffffff">70M</td><td valign="top" bgcolor="#ffffff">13% <br /></td><td valign="top" bgcolor="#ffffff">/var/backup</td></tr></tbody></table><p>我推荐大家一定要做一些分区，否则，文件系统的错误使用或者硬盘磁道的一点损坏就会完全破坏掉你<em>所有的</em>数据。而且根据Murphy 定律，在事先没有做好硬盘分区而是直接使用整块硬盘的时候，这通常就会发生。我最近就遇到过这样的情况，如果事先没有做好分区的话，就会由于主分区的一点问题而丢失掉我的所有数据。上面的文件表明<tt>/usr</tt> 分区增长的太大了，所以<tt>/usr/share</tt>必须被加入。是该换一个大点的硬盘的时候了。</p>在目标电脑上，使用<tt>parted</tt> (推荐) 或者你喜爱的分区工具(Qtparted 是一个很好的图形界面的工具，据说是Partition Magic 克隆版)。创建的分区不能比源电脑上对应的分区小。另外，别忘了 swap 分区。保存好分区表，在刚创建的分区上创建文件系统，可以使用<pre class="code">mke2fs -j -L &lt;label&gt; /dev/xxx</pre>这里 <tt>xxx</tt> 是你的分区名，然后用<tt>&lt;label&gt;</tt>设置卷标号。我经常使用如 “<tt>/usr</tt>”一类的作为卷标。你也可以通过<tt><b>tune2fs(8)</b></tt>来设置各种任务，比如周期性的文件系统检查。 
<h1>传送文件系统 </h1>首先你需要加载所有新创建的分区，我们先从主文件系统(“/”)开始，其他的按顺序依次进行。当然可以将源电脑上的两个分区整合到目标电脑上的一个分区上去，事实上，这正是我们要做的——将上例中<tt>/usr/</tt> 和 <tt>/usr/share</tt>合并为一个分区。我们加载未来的主文件系统<pre class="code">mount /dev/xxx   /mnt</pre>在复制的时候，转到目标目录里面去是很必要的<pre class="code">cd /mnt</pre>在目标电脑上键入<pre class="code">ssh sourcePC 'dump -0 -f - /' | restore -r -f -</pre>这里 <tt>targetPC</tt> 是你目标电脑的 IP 地址。参数“<tt>-0</tt>” 表示完全备份，“<tt>-f -</tt>”表示使用<tt>stdin</tt>/<tt>stdout</tt>做为文件描述符，而“<tt>-r</tt>”意思是指示 restore 去重新创建通过网络传送的文件系统到目标电脑上去。更多内容请参考<b><tt>dump(8)</tt></b> 和 <b><tt>restore(8)</tt></b>。下面你看到的是传送主文件系统的输出。<br /><pre class="code">$ ssh 10.42.3.42 'dump -0 -f - /' | restore -r -f -<br />DUMP: Date of this level 0 dump: Tue Feb 22 15:50:12 2005<br />DUMP: Dumping /dev/hda3 (/) to standard output<br />DUMP: Label: debian<br />DUMP: Writing 10 Kilobyte records<br />DUMP: mapping (Pass I) [regular files]<br />DUMP: mapping (Pass II) [directories]<br />DUMP: estimated 547312 blocks.<br />DUMP: Volume 1 started with block 1 at: Tue Feb 22 15:50:14 2005<br />DUMP: dumping (Pass III) [directories]<br />DUMP: dumping (Pass IV) [regular files]<br />DUMP: Volume 1 completed at: Tue Feb 22 15:51:43 2005<br />DUMP: Volume 1 546590 blocks (533.78MB)<br />DUMP: Volume 1 took 0:01:29<br />DUMP: Volume 1 transfer rate: 6141 kB/s<br />DUMP: 546590 blocks (533.78MB)<br />DUMP: finished in 89 seconds, throughput 6141 kBytes/sec<br />DUMP: Date of this level 0 dump: Tue Feb 22 15:50:12 2005<br />DUMP: Date this dump completed: Tue Feb 22 15:51:43 2005<br />DUMP: Average transfer rate: 6141 kB/s<br />DUMP: DUMP IS DONE<br /></pre>Restore 通常会创建一个名叫 <tt>restoresymtable</tt>的文件，如果你确信在文件系统重建过程中没有错误发生，你可以将这个文件删掉。完成主文件系统的复制，下面我们依次完成其他子分区的复制。从 <tt>/usr</tt>开始吧(假定你现在的工作目录是未来的主文件系统)。<pre class="code">mount /dev/xxx  ./usr<br /><br />cd ./usr<br /><br />ssh targetPC 'dump -0 -f - /usr' | restore -r -f -<br /></pre>这个 mount-cd-dump/restore 循环现在可以对你的所有目录重复进行操作。上面提到的对 <tt>/usr/share</tt>(在源电脑上是个独立的分区)的处理, 可以简单的通过切换目录到 <tt>./usr/share</tt> (注意这个“.”)，然后简单的重复<pre class="code">ssh targetPC 'dump -0 -f - /usr/share' | restore -r -f -</pre>在目标文件系统有你要 restore 的文件时，Restore 会报错。通过 ssh 复制一整台电脑的数据会花费大概一个小时和100MB 网卡（也许还要特殊数据线crossover cable）。 
<p><strong>注意： </strong>转储文件系统时，并不需要加载，你可以仅仅通过给定 <i>分区名</i>, 如 <big><tt>/dev/hda6</tt></big>,而非加载后的目录名。</p><h1>另一个选择  <tt>netcat</tt></h1><p>另一个种方法不使用 ssh，而使用 <tt>netcat(1)</tt>, netcat 简称 nc. Netcat 是一把非常简单易用的基于 TCP/IP 协议(C/S模型的)的“瑞士军刀”，它能允许我们通过网络创建管道（pipe）。上面的例子只需像下面一样改一点点东西就可以用了。我们假设加载在 <big><tt>/var/backup</tt></big> 上的分区是我们要通过<big><tt>dump/restore</tt></big> 来传送的。</p>在接收端(<em>目标电脑</em>)，创建一个 <tt>netcat</tt> 的监听例程（<tt>-l</tt>），这个监听例程将管道输出到 <tt>restore</tt>。<pre class="code">nc -l -p 2000 -q 1 | restore -r -f -</pre>在 <em>源电脑</em>, 创建另一个 <tt>netcat</tt> 的例程，这个例程将它从管道里得到的输入发给目标电脑，这里 <tt>target-IP</tt> 是目标电脑的 IP 地址。<pre class="code">dump -0 -f - /var/backup | nc &lt;target-ip&gt;   2000</pre><tt>-q</tt> 选项是让 <tt>nc</tt> 在到达文件结束（EOF）时停止运行，但我是手动结束 nc 的。不过，仍然建议大家使用<tt>ssh</tt> 。 
<h1>后期工作 </h1>恭喜你！到目前为止，你已经成功的复制了你的系统。剩下的问题就是让它好好的工作起来。首先，就是更新你的 <tt>/etc/fstab</tt> 文件。如果你的目标电脑的 IP 地址变了，那么网络配置文件 (Debian 里面是<tt>/etc/hosts</tt>和<tt>/etc/network/interfaces</tt> 两个文件)。然后就是非常重要的 <i>启动配置</i> 文件，这个是无论如何都需要更新的。对<tt>lilo</tt> 来说, 就是需要修改 <tt>/etc/lilo.conf</tt>文件(特别是 <tt>root=...</tt> 选项) 然后运行 <tt>lilo -v</tt>）。 对 grub 来说, 编辑 <tt>/boot/grub/menu.lst</tt>(或 <tt>/boot/grub/grub.conf</tt>，取决于哪个是符号链接)然后运行 <tt>grub</tt>,<pre class="code">grub&gt; root (hd0,xxx)<br /><br />... filesystem is ...<br /><br />grub&gt; setup (hd0)<br /><br />... lots of output here<br /><br />grub&gt; quit<br /></pre>或运行 <tt>grub-install /dev/xxx</tt> 其中 <tt>xxx</tt> 是你的硬盘。在此，检查你的<tt>root (hdn,xx)</tt> ，并加上 <tt>root=/dev/xxx</tt> 的设置。 
<p>可能的情况是，你现在复制好的电脑有了一些更好的硬件，因此可能需要修改你 <i>内核的配置 </i>。如果你的系统有很多预先配置的好的模块(如 RedHat, SuSe, Mandrake, Fedora ...) ，那么很可能已经有了合适的模块（module）了。否则, <tt>lspci -vv</tt> 并自己重新编译内核。如果你的显卡不同了，更新 <tt>/etc/X11/XF86Config-4</tt>(或者在 RH/Fedora 中<tt>xorg.conf</tt> )。如果可能，启动到运行级别 3 并使用工具来配置你的 X。在 debian 里,一些调查是必要的，我就很幸运的发现我的驱动从<tt>r128</tt> 变为了<tt>radeon</tt>。</p><a name="sec:Other-systems"></a><a name="370lfindex4"> </a><h2>其他系统</h2><p>这篇文章讲解了克隆 <tt>ext2/ext3</tt> 文件系统的全过程。很多类似的命令可以在很多其他的 *nix 系统上，诸如 FreeBSD, HP-UX, IRIX 等也提供 <tt>dump/restore</tt> 这些命令; 在 Solaris 中，这被称作 <tt>ufsdump/ufsrestore</tt>。当然也有的文件系统不提供 dump 功能，例如 ReiserFS，这种情况就最好使用 <tt>rsync</tt>了。关于使用 <tt>rsync</tt> 成功复制Linux 系统的问题，参见 [<a href="http://www.tldp.org/linuxfocus/ChineseGB/March2005/article370.shtml#key-2">1</a>]。</p><a name="370lfindex5"> </a><h2>References </h2><dl compact=""><dt><a name="key-2">[1]</a><i>''<a href="http://www.linuxgazette.com/issue83/okopnik.html">Replicating a Linux System - Yet Another Method</a></i>.'' Ben Okopnik, Linux Gazette Issue 83, October 2002.</dt></dl><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19964.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:06 <a href="http://www.cnitblog.com/flutist1225/articles/19964.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>LINUX系统中动态链接库的创建与使用 </title><link>http://www.cnitblog.com/flutist1225/articles/19963.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:03:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19963.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19963.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19963.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19963.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19963.html</trackback:ping><description><![CDATA[
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">大家都知道，在WINDOWS系统中有很多的动态链接库(以.DLL为后缀的文件，DLL即Dynamic LinkLibrary)。这种动态链接库，和静态函数库不同，它里面的函数并不是执行程序本身的一部分，而是根据执行程序需要按需装入，同时其执行代码可在多个执行程序间共享，节省了空间，提高了效率，具备很高的灵活性，得到越来越多程序员和用户的青睐。那么，在LINUX系统中有无这样的函数库呢？<br />答案是肯定的，LINUX的动态链接库不仅有，而且为数不少。在/lib目录下，就有许多以.so作后缀的文件，这就是LINUX系统应用的动态链接库，只不过与WINDOWS叫法不同，它叫so，即Shared Object，共享对象。(在LINUX下，静态函数库是以.a作后缀的)X-WINDOW作为LINUX下的标准图形窗口界面，它本身就采用了很多的动态链接库(在/usr/X11R6/lib目录下)，以方便程序间的共享，节省占用空间。著名的APACHE网页服务器，也采用了动态链接库，以便扩充程序功能。你只需将PHP动态链接库拷到其共享目录，修改一下配置，APACHE就可以支持PHP网页了。如果你愿意，可以自己编写动态链接库，让APACHE支持你自己定义的网页格式。这就是动态链接的好处。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">
										<b>1、LINUX下动态链接库的创建</b>
								</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">在LINUX系统下，创建动态链接库是件再简单不过的事情。只要在编译函数库源程序时加上-shared选项即可，这样所生成的执行程序即为动态链接库。从某种意义上来说，动态链接库也是一种执行程序。按一般规则，程序名应带.so后缀。下面举个例子说说。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">我准备编写两个函数，一个用于查询当前日期getdate，一个用于查询当前时间gettime，并将这两个函数存于动态链接库my.so中。为此，需要做以下几项工作。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">1.1 编写用户接口文件datetime.h，内容如下(每行前面的数字为行号)：</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">1 /* datetime.h : 纵横软件制作中心雨亦奇编写, 2001-06-28. */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">2 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">3 #ifndef __DATETIME_H</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">4 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">5 #define __DATETIME_H</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">6 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">7 /* 日期结构 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">8 typedef struct</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">9 {</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">10 int year;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">11 int mon;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">12 int day;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">13 }DATETYPE;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">14 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">15 /* 时间结构 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">16 typedef struct</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">17 {</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">18 char hour;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">19 char min;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">20 char sec;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">21 }TIMETYPE;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">22 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">23 /* 函数原型说明 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">24 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">25 #ifdef SHARED</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">26 int (*getdate)(DATETYPE *d);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">27 #else</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">28 int getdate(DATETYPE *d);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">29 #endif</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">30 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">31 #ifdef SHARED</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">32 int (*gettime)(TIMETYPE *t);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">33 #else</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">34 int gettime(TIMETYPE *t);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">35 #endif</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">36 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">37 #endif</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">38 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">这个用户接口文件中，先定义了日期与时间结构，接着定义一下函数的原型。动态函数与静态函数的原型说明不同的是，动态函数应使用(*函数名)的形式，以便引用其指针。若要引用文件中的动态函数说明，用户应该定义一下SHARED宏，这样才能使用。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">1.2 编写getdate.c，源程序如下：</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">1 /* getdate.c : 纵横软件制作中心雨亦奇编写, 2001-06-28. */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">2 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">3 #include "time.h"</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">4 #include "datetime.h"</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">5 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">6 int getdate(DATETYPE *d)</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">7 {</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">8 long ti;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">9 struct tm *tm;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">10 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">11 time(&amp;ti);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">12 tm=localtime(&amp;ti);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">13 d-&gt;year=tm-&gt;tm_year+1900;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">14 d-&gt;mon=tm-&gt;tm_mon+1;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">15 d-&gt;day=tm-&gt;tm_mday;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">16 }</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">17 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">在getdate函数中，先调用time取得以秒计的系统时间，再用localtime函数转换一下时间结构，最后调整得到正确的日期。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">1.3 编写gettime.c，源程序如下：</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">1 /* gettime.c : 纵横软件制作中心雨亦奇编写, 2001-06-28. */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">2 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">3 #include "time.h"</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">4 #include "datetime.h"</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">5 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">6 int gettime(TIMETYPE *t)</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">7 {</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">8 long ti;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">9 struct tm *tm;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">10</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">11 time(&amp;ti);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">12 tm=localtime(&amp;ti);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">13 t-&gt;hour=tm-&gt;tm_hour;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">14 t-&gt;min=tm-&gt;tm_min;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">15 t-&gt;sec=tm-&gt;tm_sec;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">16 }</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">17 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">gettime函数与getdate函数相仿，先用time函数取得以秒计的系统时间，再用localtime函数转换一下时间结构，最后返回当前的时间(不需调整)。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">1.4 编写维护文件makefile-lib，内容如下：</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">1 # makefile-lib : 纵横软件制作中心雨亦奇编写, 2001-06-28.</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">2 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">3 all : my.so</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">4 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">5 SRC = getdate.c gettime.c</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">6 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">7 TGT = $(SRC:.c=.o)</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">8 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">9 $(SRC) : datetime.h</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">10 @touch $@</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">11 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">12 %.o : %.c</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">13 cc -c $?</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">14 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">15 # 动态函数库(my.so)生成</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">16 my.so : $(TGT)</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">17 cc -shared -o $@ $(TGT)</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">18 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">编写维护文件的目的，在于方便程序员维护程序，尤其是维护比较大的工程项目。一个素质良好的程序员应该学会熟练地编写维护文件makefile。定义了文件间的依赖关系后，一旦源文件发生变化，仅需make一下，其目标文件维护代码会自动执行，从而自动更新目标文件，减少了许多工作量。注意:每行维护代码必须以TAB(跳格键)开始，不是的话make时将出错。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">本维护文件第1行是注释行，以#号开头；文件第3行定义所有需要维护的函数库；第5行定义相关源程序文件；第7行定义目标文件；第9-10行说明所有源程序依赖于datetime.h头文件，并有相应维护代码，即touch一下，更新一下源文件的时间；第12-13行定义.o文件依赖于相应的.c文件，并指定了维护代码，即用cc编译一下；第16-17行定义共享库my.so依赖的目标文件，维护代码中用-shared编译选项，以生成动态链接库my.so。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">1.5 运行make -f makefile-lib 命令</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">make运行后，动态链接库my.so就产生了，我们就可以在程序中调用了。如果想让系统所有用户都可以使用，则应以root用户登录系统，将这个库拷贝到/lib目录下(命令：cp my.so /lib)，或者在/lib目录下建个符号连接即可(命令：ln -s `pwd`/my.so/lib)。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">
										<b>2、LINUX下动态链接库的使用</b>
								</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2.1 重要的dlfcn.h头文件</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">LINUX下使用动态链接库，源程序需要包含dlfcn.h头文件，此文件定义了调用动态链接库的函数的原型。下面详细说明一下这些函数。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2.1.1 dlerror</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">原型为: const char *dlerror(void);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">当动态链接库操作函数执行失败时，dlerror可以返回出错信息，返回值为NULL时表示操作函数执行成功。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2.1.2 dlopen</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">原型为: void *dlopen (const char *filename, int flag);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">dlopen用于打开指定名字(filename)的动态链接库，并返回操作句柄。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">filename: 如果名字不以/开头，则非绝对路径名，将按下列先后顺序查找该文件。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">(1) 用户环境变量中的LD_LIBRARY值；</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">(2) 动态链接缓冲文件/etc/ld.so.cache</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">(3) 目录/lib，/usr/lib</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">flag表示在什么时候解决未定义的符号(调用)。取值有两个:</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">1) RTLD_LAZY : 表明在动态链接库的函数代码执行时解决。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2) RTLD_NOW : 表明在dlopen返回前就解决所有未定义的符号，一旦未解决，dlopen将返回错误。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">dlopen调用失败时，将返回NULL值，否则返回的是操作句柄。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2.1.3 dlsym : 取函数执行地址</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">原型为: void *dlsym(void *handle, char *symbol);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">dlsym根据动态链接库操作句柄(handle)与符号(symbol)，返回符号对应的函数的执行代码地址。由此地址，可以带参数执行相应的函数。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">如程序代码: void (*add)(int x,int y); /* 说明一下要调用的动态函数add */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">add=dlsym("xxx.so","add"); /* 打开xxx.so共享库,取add函数地址 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">add(89,369); /* 带两个参数89和369调用add函数 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2.1.4 dlclose : 关闭动态链接库</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">原型为: int dlclose (void *handle);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">dlclose用于关闭指定句柄的动态链接库，只有当此动态链接库的使用计数为0时,才会真正被系统卸载。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2.2 在程序中使用动态链接库函数</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2.2.1 程序范例</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">下面的程序装载了动态链接库my.so，并用getdate,gettime取得当前日期与时间后输出。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">1 /************************************/</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">2 /* 文件名称: dy.c */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">3 /* 功能描述: 动态链接库应用示范程序 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">4 /* 程序编写: 纵横软件制作中心雨亦奇 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">5 /* 编写时间: 2001-06-28 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">6 /************************************/</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">7 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">8 #include "stdio.h" /* 包含标准输入输出文件 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">9 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">10 #include "dlfcn.h" /* 包含动态链接功能接口文件 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">11 #define SOFILE "./my.so" /* 指定动态链接库名称 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">12 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">13 #define SHARED /* 定义宏,确认共享,以便引用动态函数 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">14 #include "datetime.h" /* 包含用户接口文件 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">15 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">16 main()</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">17 {</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">18 DATETYPE d;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">19 TIMETYPE t;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">20 void *dp;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">21 char *error;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">22 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">23 puts("动态链接库应用示范");</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">24 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">25 dp=dlopen(SOFILE,RTLD_LAZY); /* 打开动态链接库 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">26 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">27 if (dp==NULL) /* 若打开失败则退出 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">28 {</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">29 fputs(dlerror(),stderr);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">30 exit(1);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">31 }</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">32 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">33 getdate=dlsym(dp,"getdate"); /* 定位取日期函数 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">34 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">35 error=dlerror(); /* 检测错误 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">36 if (error) /* 若出错则退出 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">37 {</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">38 fputs(error,stderr);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">39 exit(1);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">40 }</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">41 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">42 getdate(&amp;d); /* 调用此共享函数 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">43 printf("当前日期: %04d-%02d-%02d\n",d.year,d.mon,d.day);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">44 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">45 gettime=dlsym(dp,"gettime"); /* 定位取时间函数 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">46 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">47 error=dlerror(); /* 检测错误 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">48 if (error) /* 若出错则退出 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">49 {</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">50 fputs(error,stderr);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">51 exit(1);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">52 }</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">53 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">54 gettime(&amp;t); /* 调用此共享函数 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">55 printf("当前时间: %02d:%02d:%02d\n",t.hour,t.min,t.sec);</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">56 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">57 dlclose(dp); /* 关闭共享库 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">58 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">59 exit(0); /* 成功返回 */</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">60 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">61 } </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">---------------------------------------------------------------------- </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">程序说明:</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第8行: 包含标准输入输出头文件,因为程序中使用了printf,puts,fputs等标准输入输出函数,需要让编译器根据头文件中函数的原型,检查一下语法;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第10-11行: 包含动态链接库功能头文件,并定义动态链接库名称;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第13-14行: 定义宏SHARED以便引用14行的头文件datetime.h中的动态函数说明;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第25行: 用dlopen打开SOFILE共享库,返回句柄dp;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第27-31行: 检测dp是否为空,为空则显示错误后退出;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第33行: 用dlsym取得getdate函数动态地址;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第35-40行: 如果dlerror返回值不为空,则dlsym执行出错,程序显示错误后退出;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第42-43行: 执行getdate调用,输出当前日期;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第45行: 用dlsym取得gettime函数动态地址;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第47-52行: 如果dlerror返回值不为空,则dlsym执行出错,程序显示错误后退出;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第54-55行: 执行gettime调用,输出当前时间;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第57行: 用dlclose关闭dp所指示的动态链接库;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第59行: 程序退出,返回0值。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2.2.2 编写维护文件</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">维护文件makefile内容如下:</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">----------------------------------------------------------------------</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">1 # makefile : 纵横软件制作中心雨亦奇编写, 2001-06-28.</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">2 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">3 all : dy</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">4 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">5 DYSRC = dy.c</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">6 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">7 DYTGT = $(DYSRC:.c=.o)</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">8 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">9 %.o : %.c</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">10 cc -c $?</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">11 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">12 # 动态库应用示范程序</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">13 dy : $(DYTGT)</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">14 cc -rdynamic -s -o $@ $(DYTGT) -ldl</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14" bgcolor="#c0c0c0">15 </td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">----------------------------------------------------------------------</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">维护文件说明:</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第3行: 定义所有需要维护的模块;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第5行: 定义源程序;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第7行: 定义目标文件;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第9-10行: 定义.o文件依赖于.c文件,维护代码为“cc -c 变动的源文件名”;</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">第13-14行: 定义dy依赖于变量DYTGT指示的值,维护代码中采用-rdynamic选项以指定输出文件为动态链接的方式，选项-s指定删除目标文件中的符号表,最后的选项-ldl则指示装配程序ld需要装载dl函数库。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">2.2.3 运行make命令</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">运行make后将产生执行文件dy，运行后将产生如下类似信息：</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">动态链接库应用示范</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">当前日期: 2001-06-28</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">当前时间: 10:06:21</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">当删除my.so文件时,将出现以下信息:</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">动态链接库应用示范</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">my.so: cannot open shared object file: 文件或目录不存在</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">
										<b>3、小结</b>
								</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">LINUX创建与使用动态链接库并不是一件难事。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">编译函数源程序时选用-shared选项即可创建动态链接库，注意应以.so后缀命名，最好放到公用库目录(如/lib,/usr/lib等)下面，并要写好用户接口文件，以便其它用户共享。</td>
						</tr>
				</tbody>
		</table>
		<table width="620" align="center">
				<tbody>
						<tr>
								<td class="a14">使用动态链接库，源程序中要包含dlfcn.h头文件，写程序时注意dlopen等函数的正确调用，编译时要采用-rdynamic选项与-ldl选项，以产生可调用动态链接库的执行代码。</td>
						</tr>
				</tbody>
		</table>点击这里<a href="http://www.ccw.com.cn/htm/app/down/linuxdllsrc.tgz"><font color="#0000ff">下载源程序</font></a>。<br /><br /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19963.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:03 <a href="http://www.cnitblog.com/flutist1225/articles/19963.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>LINUX动态链接库高级应用（转）</title><link>http://www.cnitblog.com/flutist1225/articles/19962.html</link><dc:creator>Flutist</dc:creator><author>Flutist</author><pubDate>Sun, 03 Dec 2006 12:02:00 GMT</pubDate><guid>http://www.cnitblog.com/flutist1225/articles/19962.html</guid><wfw:comment>http://www.cnitblog.com/flutist1225/comments/19962.html</wfw:comment><comments>http://www.cnitblog.com/flutist1225/articles/19962.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/flutist1225/comments/commentRss/19962.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/flutist1225/services/trackbacks/19962.html</trackback:ping><description><![CDATA[在《<a href="http://www.ccw.com.cn/htm/app/linux/develop/01_8_6_2.asp" target="_blank"><font color="#0000ff">LINUX下动态链接库的创建与应用</font></a>》一文中,我介绍了LINUX动态链接库的基本知识.其要点是:用户根据实际情况需要,利用dlopen,dlsym,dlclose等动态链接库操作函数,装入指定的动态链接库中指定的函数,然后加以执行.程序中使用很少的动态函数时,这样的做法尚可.如果程序需要调用大量的动态函数,那么采用这样的编程手段将是非常繁复的,所以我们必须使用一种更为聪明的办法,以减少代码量,提高工作效率.这就是现在我要举例介绍的《LINUX动态链接库高级应用》. 
<table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注:本文举的例子类似上篇文章,只是文件的内容已做相应修改,裁减了不少.示例程序ady.c和两个动态函数的源程序getdate.c与gettime.c仅修改了头文件的名字,其内容不再列出.本文使用头文件为adatetime.h.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">要想高效地应用LINUX动态链接库(尤其是用户自己编写的),需要做以下工作:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><b>一、编写合格的动态链接库头文件</b></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">C语言的头文件,可供一个或多个程序引用,里面一般定义程序所需的常量,自定义类型及函数原型说明等.其中的函数原型说明,则供编译器检查语法,用于排除引用参数时类型不一致的错误.只有编写合格的动态链接库头文件,程序员才能正确使用动态链接库内的函数.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">动态链接库头文件要采用C语言标准格式,其中的动态函数原型定义,不必象上文介绍的那样用(*动态函数名)的描述形式.请看下面的例子:(每行开始的数字为所在行行号,为笔者添加,供注解使用)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">1 /* adatetime.h : 纵横软件制作中心雨亦奇(zhsoft@371.net)编写, 2002-03-06. */</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">2 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">3 #ifndef __DATETIME_H</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">4 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">5 #define __DATETIME_H</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">6 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">7 /* 日期结构 */</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">8 typedef struct</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">9 {</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">10 int year;</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">11 int mon;</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">12 int day;</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">13 }DATETYPE;</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">14 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">15 /* 时间结构 */</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">16 typedef struct</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">17 {</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">18 char hour;</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">19 char min;</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">20 char sec;</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">21 }TIMETYPE;</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">22 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">23 int getdate(DATETYPE *d); /* 取当前日期 */</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">24 int gettime(TIMETYPE *t); /* 取当前时间 */</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">25 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">26 #endif</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">27 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注:与上文的datetime.h文件比较,从该头文件第23,24行可以看到,动态函数getdate,gettime的原型定义改变了,不再使用(*getdate),(*gettime)的格式了(这种格式使用较为罗嗦).</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><b>二、正确编译与命名动态链接库</b></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">为了让GCC编译器生成动态链接库,编译时须加选项-shared.(这点须牢记)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">LINUX系统中,为了让动态链接库能被系统中其它程序共享,其名字应符合“lib*.so*”这种格式.如果某个动态链接库不符合此格式,则LINUX的动态链接库自动装入程序(ld.so)将搜索不到此链接库,其它程序也无法共享之.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">格式中,第一个*通常表示为简写的库名,第二个*通常表示为该库的版本号.如:在我的系统中,基本C动态链接库的名字为libc.so.6,线程pthread动态链接库的名字为libpthread.so.0等等.本文例子所生成的动态链接库的名字为libmy.so,虽没有版本号,但也符合所要求的格式.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">生成该动态链接库的维护文件makefile-lib内容如下:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">1 # makefile : 纵横软件制作中心雨亦奇编写, 2002-03-07.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">2 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">3 all : libmy.so</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">4 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">5 SRC = getdate.c gettime.c</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">6 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">7 TGT = $(SRC:.c=.o)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">8 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">9 $(SRC) : adatetime.h</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">10 @touch $@</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">11 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">12 %.o : %.c</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">13 cc -c $?</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">14 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">15 # 动态链接库(libmy.so)生成</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">16 libmy.so : $(TGT)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">17 cc -s -shared -o $@ $(TGT)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">18 </td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">运行命令:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">$ make -f makefile-lib</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">$</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">即生成libmy.so库.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注: 维护文件中,第17行用-shared选项以生成动态链接库,用-s选项以去掉目标文件中的符号表,从而减小文件长度.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><b>三、共享动态链接库</b></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">3.1 动态链接库配置文件</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">为了让动态链接库为系统所使用,需要维护动态链接库的配置文件--/etc/ld.so.conf.此文件内,存放着可被LINUX共享的动态链接库所在目录的名字(系统目录/lib,/usr/lib除外),各个目录名间以空白字符(空格,换行等)或冒号或逗号分隔.一般的LINUX发行版中,此文件均含一个共享目录/usr/X11R6/lib,为X window窗口系统的动态链接库所在的目录.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">下面看看我的系统中此文件的内容如何:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># cat /etc/ld.so.conf</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/usr/X11R6/lib</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/usr/zzz/lib</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">由上可以看出,该动态库配置文件中,增加了一个/usr/zzz/lib目录.这是我自己新建的共享库目录,下面存放我新开发的可供系统共享的动态链接库.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">3.2 动态链接库管理命令</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">为了让动态链接库为系统所共享,还需运行动态链接库的管理命令--ldconfig.此执行程序存放在/sbin目录下.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ldconfig命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ldconfig命令行用法如下:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ldconfig [-v|--verbose] [-n] [-N] [-X] [-f CONF] [-C CACHE] [-r ROOT][-l] [-p|--print-cache] [-c FORMAT] [--format=FORMAT] [-V][-?|--help|--usage] path...</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ldconfig可用的选项说明如下:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(1) -v或--verbose : 用此选项时,ldconfig将显示正在扫描的目录及搜索到的动态链接库,还有它所创建的连接的名字.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(2) -n : 用此选项时,ldconfig仅扫描命令行指定的目录,不扫描默认目录(/lib,/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(3) -N : 此选项指示ldconfig不重建缓存文件(/etc/ld.so.cache).若未用-X选项,ldconfig照常更新文件的连接.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(4) -X : 此选项指示ldconfig不更新文件的连接.若未用-N选项,则缓存文件正常更新.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(5) -f CONF : 此选项指定动态链接库的配置文件为CONF,系统默认为/etc/ld.so.conf.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(6) -C CACHE : 此选项指定生成的缓存文件为CACHE,系统默认的是/etc/ld.so.cache,此文件存放已排好序的可共享的动态链接库的列表.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(7) -r ROOT :此选项改变应用程序的根目录为ROOT(是调用chroot函数实现的).选择此项时,系统默认的配置文件/etc/ld.so.conf,实际对应的为ROOT/etc/ld.so.conf.如用-r/usr/zzz时,打开配置文件/etc/ld.so.conf时,实际打开的是/usr/zzz/etc/ld.so.conf文件.用此选项,可以大大增加动态链接库管理的灵活性.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(8) -l : 通常情况下,ldconfig搜索动态链接库时将自动建立动态链接库的连接.选择此项时,将进入专家模式,需要手工设置连接.一般用户不用此项.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(9) -p或--print-cache : 此选项指示ldconfig打印出当前缓存文件所保存的所有共享库的名字.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(10) -c FORMAT 或 --format=FORMAT : 此选项用于指定缓存文件所使用的格式,共有三种:old(老格式),new(新格式)和compat(兼容格式,此为默认格式).</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(11) -V : 此选项打印出ldconfig的版本信息,而后退出.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(12) -? 或 --help 或 --usage : 这三个选项作用相同,都是让ldconfig打印出其帮助信息,而后退出.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">举三个例子:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">例1:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># ldconfig -p</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">793 libs found in cache `/etc/ld.so.cache'</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libzvt.so.2 (libc6) =&gt; /usr/lib/libzvt.so.2</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libzvt.so (libc6) =&gt; /usr/lib/libzvt.so</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libz.so.1.1.3 (libc6) =&gt; /usr/lib/libz.so.1.1.3</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libz.so.1 (libc6) =&gt; /lib/libz.so.1</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">......</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注:有时候用户想知道系统中有哪些动态链接库,或者想知道系统中有没有某个动态链接库,这时,可用-p选项让ldconfig输出缓存文件中的动态链接库列表,从而查询得到.例子中,ldconfig命令的输出结果第1行表明在缓存文件/etc/ld.so.cache中找到793个共享库,第2行开始便是一系列共享库的名字及其全名(绝对路径).因为实际输出结果太多,为节省篇幅,以......表示省略的部分.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">例2:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># ldconfig -v</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/lib:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">liby.so.1 -&gt; liby.so.1</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libnss_wins.so -&gt; libnss_wins.so</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">......</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/usr/lib:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libjscript.so.2 -&gt; libjscript.so.2.0.0</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libkspell.so.2 -&gt; libkspell.so.2.0.0</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">......</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/usr/X11R6/lib:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libmej-0.8.10.so -&gt; libmej-0.8.10.so</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libXaw3d.so.7 -&gt; libXaw3d.so.7.0</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">......</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注:ldconfig命令在运行正常的情况下,默认不输出什么东西.本例中用了-v选项,以使ldconfig在运行时输出正在扫描的目录及搜索到的共享库,用户可以清楚地看到运行的结果.执行结束后,ldconfig将刷新缓存文件/etc/ld.so.cache.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">例3:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># ldconfig /usr/zhsoft/lib</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table>注: 当用户在某个目录下面创建或拷贝了一个动态链接库,若想使其被系统共享,可以执行一下"ldconfig目录名"这个命令.此命令的功能在于让ldconfig将指定目录下的动态链接库被系统共享起来,意即:在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库.本例让系统共享了/usr/zhsoft/lib目录下的动态链接库.需要说明的是,如果此目录不在/lib,/usr/lib及/etc/ld.so.conf文件所列的目录里面,则再度运行ldconfig时,此目录下的动态链接库可能不被系统共享了.<br /><br />3.3 动态链接库如何共享 
<table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">了解了以上知识,我们可以采用以下三种方法来共享动态链接库:(注:均须在超级用户状态下操作,以我的动态链接库libmy.so共享过程为例)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(1)拷贝动态链接库到系统共享目录下,或在系统共享目录下为该动态链接库建立个连接(硬连接或符号连接均可,常用符号连接).这里说的系统共享目录,指的是LINUX动态链接库存放的目录,它包含/lib,/usr/lib以及/etc/ld.so.conf文件内所列的一系列目录.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># cp libmy.so /lib</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># ldconfig</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">或:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># ln -s `pwd`/libmy.so /lib</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># ldconfig</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(2)将动态链接库所在目录名追加到动态链接库配置文件/etc/ld.so.conf中.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># pwd &gt;&gt; /etc/ld.so.conf</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># ldconfig</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(3)利用动态链接库管理命令ldconfig,强制其搜索指定目录,并更新缓存文件,便于动态装入.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># ldconfig `pwd`</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">需要说明的是,这种操作方法虽然有效,但效果是暂时的,供程序测试还可以,一旦再度运行ldconfig,则缓存文件内容可能改变,所需的动态链接库可能不被系统共享了.与之相比较,前两种方法是可靠的方法,值得业已定型的动态链接库共享时采用.前两种方法还有一个特点,即最后一条命令都是ldconfig,也即均需要更新一下缓存文件,以确保动态链接库的共享生效.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><b>四、含有动态函数的程序的编译</b></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">4.1 防止编译因未指定动态链接库而出错</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">当一个程序使用动态函数时,编译该程序时就必须指定含所用动态函数的动态链接库,否则编译将会出错退出.如本文示例程序ady.c的编译(未明确引用动态链接库libmy.so):</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># cc -o ady ady.c</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/tmp/ccL4FsJp.o: In function `main':</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/tmp/ccL4FsJp.o(.text+0x43): undefined reference to `gettime'</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">collect2: ld returned 1 exit status</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注: 因为ady.c所含的动态函数getdate,gettime不在系统函数库中,所以连接时出错.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">4.2 编译时引用动态链接库的几种方式</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(1)当所用的动态链接库在系统目录(/lib,/usr/lib)下时,可用编译选项-l来引用.即:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># cc -lmy -o ady ady.c</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注:编译时用-l选项引用动态链接库时,库名须使用其缩写形式.本例的my,表示引用libmy.so库.若引用光标库libncurses.so,须用-lncurses.注意,-l选项与参数之间不能有空格,否则会出错.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(2)当所用的动态链接库在系统目录(/lib,/usr/lib)以外的目录时,须用编译选项-L来指定动态链接库所在的目录(供编译器查找用),同时用-l选项指定缩写的动态链接库名.即:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># cc -L/usr/zzz/lib -lmy -o ady ady.c</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(3)直接引用所需的动态链接库.即:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># cc -o ady ady.c libmy.so</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">或</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"># cc -o ady ady.c /lib/libmy.so</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">#</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">等等.其中,动态链接库的库名可以采用相对路径形式(文件名不以/开头),也可采用绝对路径形式(文件名以/开头).</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><b>五、动态链接程序的运行与检查</b></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">5.1 运行</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">编译连接好含动态函数的程序后,就可以运行它了.动态链接程序因为共享了系统中的动态链接库,所以其空间占用很小.但这并不意味功能的减少,它的执行与静态连接的程序执行,效果完全相同.在命令提示符下键入程序名及相关参数后回车即可,如下例:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">$ ady</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">动态链接库高级应用示范</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">当前日期: 2002-03-11</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">当前时间: 19:39:06</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">$</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">5.2 检查</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">检查什么?检查动态链接程序究竟需要哪些共享库,系统中是否已有这些库,没有的话,用户好想办法把这些库装上.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">怎么检查呢?这里,告诉你一个实用程序--ldd,这个程序就是专门用来检查动态链接程序依赖哪些共享库的.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ldd命令行用法如下:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ldd [--version] [-v|--verbose] [-d|--data-relocs] [-r|--function-relocs] [--help] FILE...</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">各选项说明如下:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(1) --version : 此选项用于打印出ldd的版本号.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(2) -v 或 --verbose : 此选项指示ldd输出关于所依赖的动态链接库的尽可能详细的信息.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(3) -d 或 --data-relocs : 此选项执行重定位,并且显示不存在的函数.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(4) -r 或 --function-relocs : 此选项执行数据对象与函数的重定位,同时报告不存在的对象.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">(5) --help : 此选项用于打印出ldd的帮助信息.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注: 上述选项中,常用-v(或--verbose)选项.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ldd的命令行参数为FILE...,即一个或多个文件名(动态链接程序或动态链接库).</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">例1:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">$ ldd ady</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libmy.so =&gt; ./libmy.so (0x40026000)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libc.so.6 =&gt; /lib/libc.so.6 (0x40028000)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/lib/ld-linux.so.2 =&gt; /lib/ld-linux.so.2 (0x40000000)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">$</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注:每行=&gt;前面的,为动态链接程序所需的动态链接库的名字,而=&gt;后面的,则是运行时系统实际调用的动态链接库的名字,所需的动态链接库在系统中不存在时,=&gt;后面将显示"notfound",括号所括的数字为虚拟的执行地址.本例列出ady所需的三个动态链接库,其中libmy.so为自己新建的动态链接库,而libc.so.6与/lib/ld-linux.so.2均为系统的动态链接库,前一个为基本C库,后一个动态装入库(用于动态链接库的装入及运行).</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">例2:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">$ ldd -v ady</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libmy.so =&gt; ./libmy.so (0x40026000)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libc.so.6 =&gt; /lib/libc.so.6 (0x40028000)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/lib/ld-linux.so.2 =&gt; /lib/ld-linux.so.2 (0x40000000)</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">Version information:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">./ady:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libc.so.6 (GLIBC_2.1.3) =&gt; /lib/libc.so.6</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libc.so.6 (GLIBC_2.0) =&gt; /lib/libc.so.6</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">./libmy.so:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libc.so.6 (GLIBC_2.1.3) =&gt; /lib/libc.so.6</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">libc.so.6 (GLIBC_2.0) =&gt; /lib/libc.so.6</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">/lib/libc.so.6:</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ld-linux.so.2 (GLIBC_2.1.1) =&gt; /lib/ld-linux.so.2</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ld-linux.so.2 (GLIBC_2.2.3) =&gt; /lib/ld-linux.so.2</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ld-linux.so.2 (GLIBC_2.1) =&gt; /lib/ld-linux.so.2</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ld-linux.so.2 (GLIBC_2.2) =&gt; /lib/ld-linux.so.2</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">ld-linux.so.2 (GLIBC_2.0) =&gt; /lib/ld-linux.so.2</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">$</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14">注:本例用-v选项以显示尽可能多的信息,所以例中除列出ady所需要的动态链接库外,还列出了程序所需动态链接库版本方面的信息.</td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><br type="_moz" /></td></tr></tbody></table><table width="100%"><tbody><tr><td class="a14"><b>小结: </b>在LINUX动态链接库的高级应用中,关键有两点,一是如何让动态链接库为LINUX系统所共享,二是编译连接程序时如何做.让动态链接库为系统所共享,主要是用ldconfig管理命令,维护好系统共享库的缓存文件/etc/ld.so.cache.编译连接时如何做?注意连接上所用的动态链接库就可以了.LINUX动态链接库的高级应用,用一用就明白:其实,就是这么简单!</td></tr><tr><td class="a14">点击这里<a href="http://www.ccw.com.cn/htm/app/down/ld-source.tgz"><font color="#0000ff">下载示例程序</font></a>。</td></tr></tbody></table><br type="_moz" /><img src ="http://www.cnitblog.com/flutist1225/aggbug/19962.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/flutist1225/" target="_blank">Flutist</a> 2006-12-03 20:02 <a href="http://www.cnitblog.com/flutist1225/articles/19962.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>