Friday, October 28, 2011

Linux網絡驅動程序編寫


Linux作業系統網路驅動程式編寫

一.Linux系統設備驅動程式概述
1.1 Linux設備驅動程式分類
1.2 編寫驅動程式的一些基本概念
二.Linux系統網路設備驅動程式
2.1 網路驅動程式的結構
2.2 網路驅動程式的基本方法
2.3 網路驅動程式中用到的資料結構
2.4 常用的系統支援
三.編寫Linux網路驅動程式中可能遇到的問題
3.1 中斷共用
3.2 硬體發送忙時的處理
3.3 流量控制(flow control)
3.4 調試
四.進一步的閱讀
五.雜項


一.Linux系統設備驅動程式概述
1.1 Linux設備驅動程式分類
  Linux設備驅動程式在Linux的內核源代碼中佔有很大的比例,源代碼的長度日益增加,主要是驅動程式的增加。在Linux內核的不斷升級過程中,驅動程式的結構還是相對穩定。在2.0.xx到2.2.xx的變動裏,驅動程式的編寫做了一些改變,但是從2.0.xx的驅動到2.2.xx的移植只需做少量的工作。

  Linux系統的設備分為字元設備(char device),塊設備(block device)和網路設備(network device)三種。字元設備是指存取時沒有緩存的設備。塊設備的讀寫都有緩存來支援,並且塊設備必須能夠隨機存取(random access),字元設備則沒有這個要求。典型的字元設備包括滑鼠,鍵盤,串列口等。塊設備主要包括硬碟軟碟設備,CD-ROM等。一個檔系統要安裝進入作業系統必須在塊設備上。

  網路設備在Linux裏做專門的處理。Linux的網路系統主要是基於BSD unix的 socket
機制。在系統和驅動程式之間定義有專門的資料結構(sk_buff)進行資料的傳遞。系統裏支援對發送資料和接收資料的緩存,提供流量控制機制,提供對多協定的支援。


1.2 編寫驅動程式的一些基本概念
  無論是什麼作業系統的驅動程式,都有一些通用的概念。作業系統提供給驅動程式的支援也大致相同。下面簡單介紹一下網路設備驅動程式的一些基本要求。

1.2.1 發送和接收
  這是一個網路設備最基本的功能。一塊網卡所做的無非就是收發工作。所以驅動程式裏要告訴系統你的發送函數在哪里,系統在有資料要發送時就會調用你的發送程式。還有驅動程式由於是直接操縱硬體的,所以網路硬體有資料收到最先能得到這個資料的也就是驅動程式,它負責把這些原始資料進行必要的處理然後送給系統。這裏,作業系統必須要提供兩個機制,一個是找到驅動程式的發送函數,一個是驅動程式把收到的資料送給系統。

1.2.2 中斷
  中斷在現代電腦結構中有重要的地位。作業系統必須提供驅動程式回應中斷的能力。一般是把一個中斷處理程式註冊到系統中去。作業系統在硬體中斷發生後調用驅動程式的處理程式。Linux支援中斷的共用,即多個設備共用一個中斷。

1.2.3 時鐘
  在實現驅動程式時,很多地方會用到時鐘。如某些協議裏的超時處理,沒有中斷機制的硬體的輪詢等。作業系統應為驅動程式提供定時機制。一般是在預定的時間過了以後回調註冊的時鐘函數。在網路驅動程式中,如果硬體沒有中斷功能,定時器可以提供輪詢(poll)方式對硬體進行存取。或者是實現某些協定時需要的超時重傳等。

二.Linux系統網路設備驅動程式

2.1 網路驅動程式的結構
  所有的Linux網路驅動程式遵循通用的介面。設計時採用的是面向物件的方法。一個設備就是一個物件(device 結構),它內部有自己的資料和方法。每一個設備的方法被調用時的第一個參數都是這個設備物件本身。這樣這個方法就可以存取自身的資料(類似面向物件程式設計時的this引用)。
  一個網路設備最基本的方法有初始化、發送和接收。
------------------- ---------------------
|deliver packets | |receive packets queue|
|(dev_queue_xmit()) | |them(netif_rx()) |
------------------- ---------------------
| | / \
\ / | |
-------------------------------------------------------
| methods and variables(initialize,open,close,hard_xmit,|
| interrupt handler,config,resources,status...) |
-------------------------------------------------------
| | / \
\ / | |
----------------- ----------------------
|send to hardware | |receivce from hardware|
----------------- ----------------------
| | / \
\ / | |
-----------------------------------------------------
| hardware media |
-----------------------------------------------------

  初始化程式完成硬體的初始化、device中變數的初始化和系統資源的申請。發送程式是在驅動程式的上層協定層有資料要發送時自動調用的。一般驅動程式中不對發送資料進行緩存,而是直接使用硬體的發送功能把資料發送出去。接收資料一般是通過硬體中斷來通知的。在中斷處理程式裏,把硬體幀資訊填入一個skbuff結構中,然後調用netif_rx()傳遞給上層處理。


2.2 網路驅動程式的基本方法
  網路設備做為一個物件,提供一些方法供系統訪問。正是這些有統一介面的方法,掩蔽了硬體的具體細節,讓系統對各種網路設備的訪問都採用統一的形式,做到硬體無關性。下面解釋最基本的方法。 

2.2.1 初始化(initialize)
  驅動程式必須有一個初始化方法。在把驅動程式載入系統的時候會調用這個初始化程式。它做以下幾方面的工作。檢測設備。在初始化程式裏你可以根據硬體的特徵檢查硬體是否存在,然後決定是否啟動這個驅動程式。配置和初始化硬體。在初始化程式裏你可以完成對硬體資源的配置,比如即插即用的硬體就可以在這個時候進行配置(Linux內核對PnP功能沒有很好的支援,可以在驅動程式裏完成這個功能)。配置或協商好硬體佔用的資源以後,就可以向系統申請這些資源。有些資源是可以和別的設備共用的,如中斷。有些是不能共用的,如IO、DMA。接下來你要初始化device結構中的變數。最後,你可以讓硬體正式開始工作。

2.2.2 打開(open)
  open這個方法在網路設備驅動程式裏是網路設備被啟動的時候被調用(即設備狀態由down-->up)。所以實際上很多在initialize中的工作可以放到這裏來做。比如資源的申請,硬體的啟動。如果dev->open返回非0(error),則硬體的狀態還是down。open方法另一個作用是如果驅動程式做為一個模組被裝入,則要防止模組卸載時設備處於打開狀態。在open方法裏要調用MOD_INC_USE_COUNT宏。

2.2.3 關閉(stop)
  close方法做和open相反的工作。可以釋放某些資源以減少系統負擔。close是在設備狀態由up轉為down時被調用的。另外如果是做為模組裝入的驅動程式,close裏應該調用MOD_DEC_USE_COUNT,減少設備被引用的次數,以使驅動程式可以被卸載。另外close方法必須返回成功(0==success)。

2.2.4 發送(hard_start_xmit)
  所有的網路設備驅動程式都必須有這個發送方法。在系統調用驅動程式的xmit 時,發送的資料放在一個sk_buff結構中。一般的驅動程式把數據傳給硬體發出去。也有一些特殊的設備比如loopback把資料組成一個接收資料再回送給系統,或者dummy設備直接丟棄資料。如果發送成功,hard_start_xmit方法裏釋放sk_buff,返回0(發送成功)。如果設備暫時無法處理,比如硬體忙,則返回1。這時如果dev->tbusy置為非0,則系統認為硬體忙,要等到dev->tbusy置0以後才會再次發送。tbusy的置0任務一般由中斷完成。硬體在發送結束後產生中斷,這時可以把tbusy置0,然後用mark_bh()調用通知系統可以再次發送。在發送不成功的情況下,也可以不置dev->tbusy為非0,這樣系統會不斷嘗試重發。如果hard_start_xmit發送不成功,則不要釋放sk_buff。傳送下來的sk_buff中的資料已經包含硬體需要的幀頭。所以在發送方法裏不需要再填充硬體幀頭,資料可以直接提交給硬體發送。sk_buff是被鎖住的(locked),確保其他程式不會存取它。

2.2.5 接收(reception)
  驅動程式並不存在一個接收方法。有資料收到應該是驅動程式來通知系統的。一般設備收到資料後都會產生一個中斷,在中斷處理程式中驅動程式申請一塊sk_buff(skb),從硬體讀出資料放置到申請好的緩衝區裏。接下來填充sk_buff中的一些資訊。skb->dev = dev,判斷收到幀的協議類型,填入skb->protocol(多協議的支持)。把指針skb->mac.raw指向硬體資料然後丟棄硬體幀頭(skb_pull)。還要設置skb->pkt_type,標明第二層(鏈路層)資料類型。可以是以下類型:
PACKET_BROADCAST : 鏈路層廣播
PACKET_MULTICAST : 鏈路層組播
PACKET_SELF : 發給自己的幀
PACKET_OTHERHOST : 發給別人的幀(監聽模式時會有這種幀)
最後調用netif_rx()把資料傳送給協定層。netif_rx()裏資料放入處理佇列然後返回,真正的處理是在中斷返回以後,這樣可以減少中斷時間。調用netif_rx()以後,驅動程式就不能再存取資料緩衝區skb。

2.2.6 硬體幀頭(hard_header)
  硬體一般都會在上層資料發送之前加上自己的硬體幀頭,比如乙太網(Ethernet)就有14位元組的幀頭。這個幀頭是加在上層ip、ipx等數據包的前面的。驅動程式提供一個hard_header方法,協議層(ip、ipx、arp等)在發送資料之前會調用這段程式。
硬體幀頭的長度必須填在dev->hard_header_len,這樣協定層回在資料之前保留好硬體幀頭的空間。這樣hard_header程式只要調用skb_push然後正確填入硬體幀頭就可以了。
在協議層調用hard_header時,傳送的參數包括(2.0.xx):數據的sk_buff,device指針,protocol,目的地址(daddr),源位址(saddr),資料長度(len)。數據長度不要使用sk_buff中的參數,因為調用hard_header時資料可能還沒完全組織好。

  saddr是NULL的話是使用缺省位址(default)。daddr是NULL表明協定層不知道硬體目的地址。如果hard_header完全填好了硬體幀頭,則返回添加的位元組數。如果硬體幀頭中的資訊還不完全(比如daddr為NULL,但是幀頭中需要目的硬體位址。典型的情況是乙太網需要位址解析(arp)),則返回負位元組數。hard_header返回負數的情況下,協議層會做進一步的build header的工作。目前Linux系統裏就是做arp(如果hard_header返回正,dev->arp=1,表明不需要做arp,返回負,dev->arp=0,做arp)。對hard_header的調用在每個協定層的處理程式裏。如ip_output。

2.2.7 地址解析(xarp)
有些網路有硬體位址(比如Ethernet),並且在發送硬體幀時需要知道目的硬體地址。這樣就需要上層協定位址(ip、ipx)和硬體位址的對應。這個對應是通過位址解析完成的。需要做arp的的設備在發送之前會調用驅動程式的rebuild_header方法。調用的主要參數包括指向硬體幀頭的指標,協議層位址。如果驅動程式能夠解析硬體位址,就返回1,如果不能,返回0。對rebuild_header的調用在net/core/dev.c的do_dev_queue_xmit()裏。

2.2.8 參數設置和統計資料
在驅動程式裏還提供一些方法供系統對設備的參數進行設置和讀取資訊。一般只有超級用戶(root)許可權才能對設備參數進行設置。設置方法有:
dev->set_mac_address()
當用戶調用ioctl類型為SIOCSIFHWADDR時是要設置這個設備的mac位址。一般對mac位址的設置沒有太大意義的。
dev->set_config()
當用戶調用ioctl時類型為SIOCSIFMAP時,系統會調用驅動程式的set_config方法。用戶會傳遞一個ifmap結構包含需要的I/O、中斷等參數。
dev->do_ioctl()
如果用戶調用ioctl時類型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之間,系統會調用驅動程式的這個方法。一般是設置設備的專用資料。讀取資訊也是通過ioctl調用進行。除次之外驅動程式還可以提供一個dev->get_stats方法,返回一個enet_statistics結構,包含發送接收的統計資訊。ioctl的處理在net/core/dev.c的dev_ioctl()和dev_ifsioc()裏。


2.3 網路驅動程式中用到的資料結構
  最重要的是網路設備的資料結構。定義在include/linux/netdevice.h裏。它的注釋已經足夠詳盡。
struct device
{

/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* the interface.
*/
char *name;

/* I/O specific fields - FIXME: Merge these and struct ifmap into one */
unsigned long rmem_end; /* shmem "recv" end */
unsigned long rmem_start; /* shmem "recv" start */
unsigned long mem_end; /* shared mem end */
unsigned long mem_start; /* shared mem start */
unsigned long base_addr; /* device I/O address */
unsigned char irq; /* device IRQ number */

/* Low-level status flags. */
volatile unsigned char start, /* start an operation */
interrupt; /* interrupt arrived */

/* 在處理中斷時interrupt設為1,處理完清0。 */
unsigned long tbusy; /* transmitter busy must be long for bitops */

struct device *next;

/* The device initialization function. Called only once. */
/* 指向驅動程式的初始化方法。 */
int (*init)(struct device *dev);

/* Some hardware also needs these fields, but they are not part of the
usual set specified in Space.c. */
/* 一些硬體可以在一塊板上支援多個介面,可能用到if_port。 */
unsigned char if_port; /* Selectable AUI, TP,..*/

unsigned char dma; /* DMA channel */
struct enet_statistics* (*get_stats)(struct device *dev);

/*
* This marks the end of the "visible" part of the structure. All
* fields hereafter are internal to the system, and may change at
* will (read: may be cleaned up at will).
*/

/* These may be needed for future network-power-down code. */
/* trans_start記錄最後一次成功發送的時間。可以用來確定硬體是否工作正常。*/
unsigned long trans_start; /* Time (in jiffies) of last Tx */

unsigned long last_rx; /* Time of last Rx */

/* flags裏面有很多內容,定義在include/linux/if.h裏。*/
unsigned short flags; /* interface flags (a la BSD) */
unsigned short family; /* address family ID (AF_INET) */
unsigned short metric; /* routing metric (not used) */
unsigned short mtu; /* interface MTU value */

/* type標明物理硬體的類型。主要說明硬體是否需要arp。定義在include/linux/if_arp.h裏。 */
unsigned short type; /* interface hardware type */

/* 上層協定層根據hard_header_len在發送資料緩衝區前面預留硬體幀頭空間。*/
unsigned short hard_header_len; /* hardware hdr length */

/* priv指向驅動程式自己定義的一些參數。*/
void *priv; /* pointer to private data */

/* Interface address info. */
unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */

unsigned char pad; /* make dev_addr aligned to 8 bytes */
unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address */
unsigned char addr_len; /* hardware address length */
unsigned long pa_addr; /* protocol address */
unsigned long pa_brdaddr; /* protocol broadcast addr */
unsigned long pa_dstaddr; /* protocol P-P other side addr */
unsigned long pa_mask; /* protocol netmask */
unsigned short pa_alen; /* protocol address length */

struct dev_mc_list *mc_list; /* Multicast mac addresses */
int mc_count; /* Number of installed mcasts */
struct ip_mc_list *ip_mc_list; /* IP multicast filter chain */
__u32 tx_queue_len; /* Max frames per queue allowed */

/* For load balancing driver pair support */
unsigned long pkt_queue; /* Packets queued */
struct device *slave; /* Slave device */
struct net_alias_info *alias_info; /* main dev alias info */
struct net_alias *my_alias; /* alias devs */

/* Pointer to the interface buffers. */
struct sk_buff_head buffs[DEV_NUMBUFFS];

/* Pointers to interface service routines. */
int (*open)(struct device *dev);
int (*stop)(struct device *dev);
int (*hard_start_xmit) (struct sk_buff *skb, struct device *dev);
int (*hard_header) (struct sk_buff *skb, struct device *dev, unsigned short type, void *daddr, void *saddr, unsigned len);
int (*rebuild_header)(void *eth, struct device *dev, unsigned long raddr, struct sk_buff *skb);

#define HAVE_MULTICAST
void (*set_multicast_list)(struct device *dev);
#define HAVE_SET_MAC_ADDR
int (*set_mac_address)(struct device *dev, void *addr);
#define HAVE_PRIVATE_IOCTL
int (*do_ioctl)(struct device *dev, struct ifreq *ifr, int cmd);
#define HAVE_SET_CONFIG
int (*set_config)(struct device *dev, struct ifmap *map);
#define HAVE_HEADER_CACHE
void (*header_cache_bind)(struct hh_cache **hhp, struct device *dev, unsigned short htype, __u32 daddr);
void (*header_cache_update)(struct hh_cache *hh, struct device *dev, unsigned char * haddr);
#define HAVE_CHANGE_MTU
int (*change_mtu)(struct device *dev, int new_mtu);
struct iw_statistics* (*get_wireless_stats)(struct device *dev);
};


2.4 常用的系統支援

2.4.1 記憶體申請和釋放
  include/linux/kernel.h裏聲明了kmalloc()和kfree()。用於在內核模式下申請和釋放記憶體。 

void *kmalloc(unsigned int len,int priority);
void kfree(void *__ptr);

與用戶模式下的malloc()不同,kmalloc()申請空間有大小限制。長度是2的整次方。可以申請的最大長度也有限制。另外kmalloc()有priority參數,通常使用時可以為GFP_KERNEL,如果在中斷裏調用用GFP_ATOMIC參數,因為使用GFP_KERNEL
則調用者可能進入sleep狀態,在處理中斷時是不允許的。kfree()釋放的記憶體必須是kmalloc()申請的。如果知道記憶體的大小,也可以用kfree_s()釋放。

2.4.2 request_irq()、free_irq()
  這是驅動程式申請中斷和釋放中斷的調用。在include/linux/sched.h裏聲明。request_irq()調用的定義:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs
*regs),
unsigned long irqflags,
const char * devname,
void *dev_id);

irq是要申請的硬體中斷號。在Intel平臺,範圍0--15。handler是向系統登記的中斷處理函數。這是一個回調函數,中斷發生時,系統調用這個函數,傳入的參數包括硬體中斷號,device id,寄存器值。dev_id就是下面的request_irq時傳遞給系統的參數dev_id。irqflags是中斷處理的一些屬性。比較重要的有SA_INTERRUPT,標明中斷處理程式是快速處理程式(設置SA_INTERRUPT)還是慢速處理程式(不設置SA_INTERRUPT)。快速處理程式被調用時遮罩所有中斷。慢速處理程式不遮罩。還有一個SA_SHIRQ屬性,設置了以後運行多個設備共用中斷。dev_id在中斷共用時會用到。一般設置為這個設備的device結構本身或者NULL。中斷處理程式可以用dev_id
找到相應的控制這個中斷的設備,或者用irq2dev_map找到中斷對應的設備。
void free_irq(unsigned int irq,void *dev_id);

2.4.3 時鐘
  時鐘的處理類似中斷,也是登記一個時間處理函數,在預定的時間過後,系統會調用這個函數。在include/linux/timer.h裏聲明。 

struct timer_list {
struct timer_list *next;
struct timer_list *prev;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long);
};
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
void init_timer(struct timer_list * timer);
  
  使用時鐘,先聲明一個timer_list結構,調用init_timer對它進行初始化。time_list結構裏expires是標明這個時鐘的週期,單位採用jiffies的單位。jiffies是Linux一個總體變數,代表時間。它的單位隨硬體平臺的不同而不同。系統裏定義了一個常數HZ,代表每秒種最小時間間隔的數目。這樣jiffies的單位就是1/HZ。Intel平臺jiffies的單位是1/100秒,這就是系統所能分辨的最小時間間隔了。所以expires/HZ就是以秒為單位的這個時鐘的週期。function就是時間到了以後的回調函數,它的參數就是timer_list中的data。data這個參數在初始化時鐘的時候賦值,一般賦給它設備的device結構指標。在預置時間到系統調用function,同時系統把這個time_list從定時佇列裏清除。所以如果需要一直使用定時函數,要在function裏再次調用add_timer()把這個timer_list加進定時佇列。

2.4.4 I/O
  I/O埠的存取使用:
inline unsigned int inb(unsigned short port);
inline unsigned int inb_p(unsigned short port);
inline void outb(char value, unsigned short port);
inline void outb_p(char value, unsigned short port);
在include/adm/io.h裏定義。
  inb_p()、outb_p()與inb()、outb_p()的不同在於前者在存取I/O時有等待(pause)一適應慢速的I/O設備。為了防止存取I/O時發生衝突,Linux提供對埠使用情況的控制。在使用埠
之前,可以檢查需要的I/O是否正在被使用,如果沒有,則把埠標記為正在使用,使用完後再釋放。系統提供以下幾個函數做這些工作。
int check_region(unsigned int from, unsigned int extent);
void request_region(unsigned int from, unsigned int extent,const char
*name);
void release_region(unsigned int from, unsigned int extent);
其中的參數from表示用到的I/O埠的起始位址,extent標明從from開始的端口數目。name為設備名稱。

2.4.5 中斷打開關閉
  系統提供給驅動程式開放和關閉回應中斷的能力。是在include/asm/system.h中的兩個定義。
#define cli() __asm__ __volatile__ ("cli"::)
#define sti() __asm__ __volatile__ ("sti"::)

2.4.6 列印資訊
  類似普通程式裏的printf(),驅動程式要輸出資訊使用printk()。在include/linux/kernel.h裏聲明。
int printk(const char* fmt, ...);
其中fmt是格式化字串。...是參數。都是和printf()格式一樣的。

2.4.7 註冊驅動程式
  如果使用模組(module)方式載入驅動程式,需要在模組初始化時把設備註冊到系統設備表裏去。不再使用時,把設備從系統中卸除。定義在drivers/net/net_init.h裏的兩個函數完成這個工作。
int register_netdev(struct device *dev);
void unregister_netdev(struct device *dev); 

  dev就是要註冊進系統的設備結構指標。在register_netdev()時,dev結構一般填寫前面11項,即到init,後面的暫時可以不用初始化。最重要的是name指針和init方法。name指標空(NULL)或者內容為\或者name[0]為空格(space),則系統把你的設備做為乙太網設備處理。乙太網設備有統一的命名格式,ethX。對乙太網這麼特別對待大概和Linux的歷史有關。init方法一定要提供,register_netdev()會調用這個方法讓你對硬體檢測和設置。register_netdev()返回0表示成功,非0不成功。

2.4.8 sk_buff
  Linux網路各層之間的資料傳送都是通過sk_buff。sk_buff提供一套管理緩衝區的方法,是Linux系統網路高效運行的關鍵。每個sk_buff包括一些控制方法和一塊數據緩衝區。控制方法按功能分為兩種類型。一種是控制整個buffer鏈的方法,另一種是控制資料緩衝區的方法。sk_buff組織成雙向鏈表的形式,根據網路應用的特點,對鏈表的操作主要是刪除鏈表頭的元素和添加到鏈表尾。sk_buff的控制方法都很短小以儘量減少系統負荷。(translated from article written by Alan Cox)
常用的方法包括:
.alloc_skb() 申請一個sk_buff並對它初始化。返回就是申請到的sk_buff。
.dev_alloc_skb()類似alloc_skb,在申請好緩衝區後,保留16位元組的幀頭空間。主要用在Ethernet驅動程式。
.kfree_skb() 釋放一個sk_buff。
.skb_clone() 複製一個sk_buff,但不複製資料部分。
.skb_copy()完全複製一個sk_buff。
.skb_dequeue() 從一個sk_buff鏈表裏取出第一個元素。返回取出的sk_buff,如果鏈表空則返回NULL。這是常用的一個操作。
.skb_queue_head() 在一個sk_buff鏈表頭放入一個元素。
.skb_queue_tail() 在一個sk_buff鏈表尾放入一個元素。這也是常用的一個操作。網路資料的處理主要是對一個先進先出佇列的管理,skb_queue_tail()和skb_dequeue()完成這個工作。
.skb_insert() 在鏈表的某個元素前插入一個元素。
.skb_append() 在鏈表的某個元素後插入一個元素。一些協議(如TCP)對沒按順序到達的資料進行重組時用到skb_insert()和skb_append()。

.skb_reserve() 在一個申請好的sk_buff的緩衝區裏保留一塊空間。這個空間一般是用做下一層協定的頭空間的。
.skb_put() 在一個申請好的sk_buff的緩衝區裏為資料保留一塊空間。在alloc_skb以後,申請到的sk_buff的緩衝區都是處於空(free)狀態,有一個tail指標指向free空間,實際上開始時tail就指向緩衝區頭。skb_reserve()在free空間裏申請協定頭空間,skb_put()申請資料空間。見下麵的圖。
.skb_push() 把sk_buff緩衝區裏資料空間往前移。即把Head room中的空間移一部分到Data area。
.skb_pull() 把sk_buff緩衝區裏Data area中的空間移一部分到Head room中。

--------------------------------------------------
| Tail room(free) |
--------------------------------------------------
After alloc_skb()

--------------------------------------------------
| Head room | Tail room(free) |
--------------------------------------------------
After skb_reserve()

--------------------------------------------------
| Head room | Data area | Tail room(free) |
--------------------------------------------------
After skb_put()

--------------------------------------------------
|Head| skb_ | Data | Tail room(free) |
|room| push | | |
| | Data area | |
--------------------------------------------------
After skb_push()

--------------------------------------------------
| Head | skb_ | Data area | Tail room(free) |
| | pull | | |
| Head room | | |
--------------------------------------------------
After skb_pull()


三.編寫Linux網路驅動程式中需要注意的問題

3.1 中斷共用
  Linux系統運行幾個設備共用同一個中斷。需要共用的話,在申請的時候指明共用方式。系統提供的request_irq()調用的定義:
int request_irq(unsigned int irq, void (*handler)(int irq, void *dev_id, struct pt_regs *regs), unsigned long irqflags, const char * devname, void *dev_id);
如果共用中斷,irqflags設置SA_SHIRQ屬性,這樣就允許別的設備申請同一個中斷。需要注意所有用到這個中斷的設備在調用request_irq()都必須設置這個屬性。系統在回調每個中斷處理程式時,可以用dev_id這個參數找到相應的設備。一般dev_id就設為device結構本身。系統處理共用中斷是用各自的dev_id參數依次調用每一個中斷處理程式。

3.2 硬體發送忙時的處理
  主CPU的處理能力一般比網路發送要快,所以經常會遇到系統有資料要發,但上一包資料網路設備還沒發送完。因為在Linux裏網路設備驅動程式一般不做資料緩存,不能發送的資料都是通知系統發送不成功,所以必須要有一個機制在硬體不忙時及時通知系統接著發送下面的資料。
  一般對發送忙的處理在前面設備的發送方法(hard_start_xmit)裏已經描述過,即如果發送忙,置tbusy為1。處理完發送資料後,在發送結束中斷裏清tbusy,同時用mark_bh()調用通知系統繼續發送。但在具體實現我的驅動程式時發現,這樣的處理系統好象並不能及時地知道硬件已經空閒了,即在mark_bh()以後,系統要等一段時間才會接著發送。造成發送效率很低。2M線路只有10%不到的使用率。內核版本為2.0.35。
  我最後的實現是不把tbusy置1,讓系統始終認為硬體空閒,但是報告發送不成功。系統會一直嘗試重發。這樣處理就運行正常了。但是遍循內核源碼中的網路驅動程式,似乎沒有這樣處理的。不知道癥結在哪里。

3.3 流量控制(flow control) 
  網路資料的發送和接收都需要流量控制。這些控制是在系統裏實現的,不需要驅動程式做工作。每個設備資料結構裏都有一個參數dev->tx_queue_len,這個參數標明發送時最多緩存的資料包。在Linux系統裏乙太網設備(10/100Mbps)tx_queue_len一般設置為100,串列線路(非同步串口)為10。實際上如果看源碼可以知道,設置了dev->tx_queue_len並不是為緩存這些資料申請了空間。這個參數只是在收到協定層的資料包時判斷發送佇列裏的資料是不是到了tx_queue_len的限度,以決定這一包資料加不加進發送佇列。發送時另一個方面的流控是更高層協議的發送視窗(TCP協定裏就有發送視窗)。達到了視窗大小,高層協定就不會再發送資料。接收流控也分兩個層次。netif_rx()緩存的資料包有限制。另外高層協定也會有一個最大的等待處理的資料量。發送和接收流控處理在net/core/dev.c的do_dev_queue_xmit()和netif_rx()中。

3.4 調試
  很多Linux的驅動程式都是編譯進內核的,形成一個大的內核檔。但對調試來說,這是相當麻煩的。調試驅動程式可以用module方式載入。支援模組方式的驅動程式必須提供兩個函數:int init_module(void)和void cleanup_module(void)。
  init_module()在載入此模組時調用,在這個函數裏可以register_netdev()註冊設備。init_module()返回0表示成功,返回負表示失敗。cleanup_module()在驅動程式被卸載時調用,清除佔用的資源,調用unregister_netdev()。模組可以動態地載入、卸載。在2.0.xx版本裏,還有kerneld自動載入模組,但是2.2.xx中已經取消了kerneld。手工載入使用insmod命令,卸載用rmmod命令,看內核中的模組用lsmod命令。編譯驅動程式用gcc,主要命令行參數-DKERNEL -DMODULE。並且作為模組載入的驅動程式,只編譯成obj形式(加-c參數)。編譯好的目標檔放在/lib/modules/2.x.xx/misc下,在啟動檔裏用insmod載入。


四.進一步的閱讀
  Linux程式設計資料可以從網上獲得。這就是開放源代碼的好處。並且沒有什麼“未公開的秘密”。我編寫驅動程式時參閱的主要資料包括:
Linux內核源代碼
<> by Michael K. Johnson
<> by Ori Pomerantz
<> by olly in BBS水木清華站
可以選擇一個範本作為開始,內核源代碼裏有一個網路驅動程式的範本,drivers/net/skeleton.c。裏面包含了驅動程式的基本內容。但這個範本是以乙太網設備為物件的,乙太網的處理在Linux系統裏有特殊“待遇”,所以如果不是以太網設備,有些細節上要注意,主要在初始化程式裏。

最後,多參照別人寫的程式,聽聽其他開發者的經驗之談大概是最有效的幫助了。

Thursday, October 27, 2011

Howto: ip iproute


一張網卡 bind 兩個不同網段的 IP

 

環境: 兩條對外網路, 兩個 ATU-R 均接到 Hub, 電腦接 Hub
IP1 100.100.100.100/24 Gateway 100.100.100.254
IP2 200.200.200.200/24 Gateway 200.200.200.254
ifconfig eth0 100.100.100.100 netmask 255.255.255.0
ifconfig eth0:0 200.200.200.200 netmask 255.255.255.0
由於 default gateway 只能設一個, 所以會有其中一個 IP 不通
必需用 ip route2 來解決這個問題
kernel option 中的 IP: policy routing 必需勾選(CONFIG_IP_MULTIPLE_TABLES)
否則在使用 ip rule 時會出現如下錯誤訊息
# ip rule list
RTNETLINK error: Invalid argument
dump terminated
先在 /etc/iproute2/rt_tables 下建兩個 table
echo "100 line1" >> /etc/iproute2/rt_tables 
echo "200 line2" >> /etc/iproute2/rt_tables
# 設定 line1 的 gateway
ip route add default via 100.100.100.254 table line1
# 設定 line2 的 gateway
ip route add default via 200.200.200.254 table line2
# 指定從 100.100.100.100 進來的連線走 rule line1
ip rule add from 100.100.100.100 table line1 
# 指定從 200.200.200.200 進來的連線走 rule line1
ip rule add from 200.200.200.200 table line2
設完後, 從外面連兩個 IP 都可以通
連外負載平衡
kernel option 中的 IP: equal cost multipath 必需勾選(CONFIG_IP_ROUTE_MULTIPATH)
指定 multipath
ip route add default scope global nexthop via 100.100.100.254 dev eth0 weight 1 \
nexthop via 200.200.200.254 dev eth0 weight 1
設完後就達成 line1 line2 路由平衡, 可以調整 weight 參數來決定 line1 或 line2 的比重
ip route list 會看到 default 如下
default 
nexthop via 100.100.100.254 dev eth0 weight 1
nexthop via 200.200.200.254 dev eth0 weight 1


Multi-PATH實作

 

用Linux來作路由器相當方便且效能又高, 可以自訂路由規則,彈性很高.
這次的目標主要是要整合 固定IP區段/ADSL單一IP虛擬, 作成多路由,讓使用者可以自由切換線路,又可保由固定IP的區段.
ADSL為單一IP偽裝區段(192.168.1.0/24),以eth1為出口,  固定IP區段為DMZ(非軍事區210.243.128.0/24), 以eth2為出口.
#!/bin/bash
# 設定 LAN
LAN="192.168.1.0" # subnet
LANM="24" # netmask
# 設定 Wan_1
WAN1="123.0.244.118" # 第一條 ADSL 的固定 IP
GW1="123.0.244.254" # 第一條 ADSL 的 Gateway
# 設定 Wan_2
WAN2="210.243.128.221" # 第二條 ADSL 的固定 IP
GW2="210.243.128.222" # 第二條 ADSL 的 Gateway
# pref 為 priority,值越小 priority 越高
# from 設定來源 IP
# table 設定 table 的編號
# 設定 Lan 的 ip rule  ,內部網路互做路由,不偽裝
ip rule add pref 95 from 192.168.1.0/24 to 192.168.1.0/24 table 95
ip rule add pref 96 from 210.243.128.0/24 to 210.243.128.0/24 table 96
#要去210.243.128.222的封包由table 97處理 , teble 97就指定由eth2出去
ip rule add pref 97 to 210.243.128.222 table 97
ip route replace 210.243.128.222 dev eth2 table 97
#要去210.243.128.0/24的封包由table 98處理 , teble 98就指定由eth0出去
ip rule add pref 98 to 210.243.128.0/24 table 98
ip route replace 210.243.128.0/24 dev eth0 table 98
#要去192.168.1.0/24的封包由table 99處理 , teble 99就指定由eth0出去
ip rule add pref 99 to $LAN/$LANM table 99 
ip route replace $LAN/$LANM dev eth0 table 99
# 設定第一條 ADSL 的 ip rule
ip rule add pref 100 from $WAN1 table 100 
ip route replace default via $GW1 table 100
# 設定第二條 ADSL 的 ip rule
ip rule add pref 101 from $WAN2 table 101 
ip route replace default via $GW2 table 101

# SeedNet ISP Routing (如果由210.243.128.0/24來的封包由table 103處理, teble 103就指定由eth2出去)
ip rule add pref 103 from 210.243.128.0/24 table 103
ip route replace default via $GW2 dev eth2 table 103
# Cable ISP Routing (如果由192.168.1.0/24來的封包由table 104處理, teble 104就指定由eth1出去)
ip rule add pref 104 from 192.168.1.0/24 table 104
ip route replace default via $GW1 dev eth1 table 104
# 清除 route cache
ip route flush cache
#最後將DMZ非軍事區設定ARP偽裝將其導向LINUX轉發路由
arp -i eth2 -s 210.243.128.1 00:01:02:03:04:05 pub
arp -i eth2 -s 210.243.128.2 00:01:02:03:04:05 pub
....... 以下類推
arp -i eth0 -s 210.243.128.222 00:11:22:33:44:55 pub

************2010/2/11 補充
如果ADSL斷線重連, 會發現 ROUTE 表被更動, 無法正常運作
ip route show table  xxx  可觀看此規則的 route gateway
重連後就是這行被清空, 要再重建一次, 否則會失效喔! 注意!


Monday, October 24, 2011

How to trace RX/TX packet from ifconfig

I can sure one thing obviously. The information getting from 'ifconfg' is related to 'cat /proc/net/dev'.


For Example,
#cat /proc/net/dev


Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
  imq0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
  imq1:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
  imq2:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
  eth0:50768275   51792    0    0    0     0          0         0   418280   77908    0    0    0     0       0          0
  eth1:92372765   78651    0    0    0     0          0         0    25581   51484    0    0    0     0       0          0
   br0:   52345     565    0    0    0     0          0         1   417158     768    0    0    0     0       0          0

#ifconfig


/proc/3695/net # ifconfig
br0       Link encap:Ethernet  HWaddr 00:30:AB:00:00:01
          inet addr:192.168.1.1  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::230:abff:fe00:1/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:565 errors:0 dropped:0 overruns:0 frame:0
          TX packets:768 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:52345 (51.1 KiB)  TX bytes:417158 (407.3 KiB)

eth0      Link encap:Ethernet  HWaddr 00:30:AB:00:00:01
          inet6 addr: fe80::230:abff:fe00:1/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:51792 errors:0 dropped:0 overruns:0 frame:0
          TX packets:77908 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:50768275 (48.4 MiB)  TX bytes:418280 (408.4 KiB)


eth1      Link encap:Ethernet  HWaddr 00:30:AB:00:00:02
          inet addr:192.37.73.192  Bcast:192.37.73.255  Mask:255.255.255.0
          inet6 addr: fe80::230:abff:fe00:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:78651 errors:0 dropped:0 overruns:0 frame:0
          TX packets:51484 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:92373618 (88.0 MiB)  TX bytes:25581 (24.9 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


Monday, October 17, 2011

OpenWRT buildroot玩法 (Upgrade code via httpd)

Ref: http://sns.linuxpk.com/blog-25219-8731.html

openwrt 包结构
如果要修改文件系统中的etc/rc.d/rcS文件, 那么要按照一下步骤进行

最终的文件系统位置:
build_mips/root/etc/rc.d/rcS

实际的修改位置
/package/base-files/default/etc/rc.d/rcS

修改后重新编译前要把下面的目录删除
rm -fr build_mips/linux-2.6-ar91xx/base-files

重新编译:
make

重新配置kernel并编译
修改kernel的配置文件,位于:
ap81-openwrt/target/linux/ar91xx-2.6/config

然后将下面的目录删除
rm build_mips/linux-2.6-ar91xx/ -fr
重新编译

固件升级方法:
cat /home/user/ap81-openwrt/bin/uImage /home/user/ap81-openwrt/bin/pad.img | head -c 2031616 > /home/user/ap81-openwrt/bin/linux.pad
cat /home/user/ap81-openwrt/bin/info.pad /home/user/ap81-openwrt/bin/linux.pad /home/user/ap81-openwrt/bin/root.burn > /home/user/ap81-openwrt/bin/WN802Tv2-no-crc.img
/home/user/ap81-openwrt/staging_dir_mips/../tools/appendsum /home/river/ap81-openwrt/bin/WN802Tv2-no-crc.img /home/user/ap81-openwrt/bin/WN802Tv2-V1.0.1_1.0.3"".img

firmware.img结构
============
-------------------
device:
version: 128byte
region:
-------------------
kernel(uboot format)
pad
--------------------
rootfs
pad
checksum


/ # cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00040000 00010000 "u-boot"
mtd1: 00010000 00010000 "u-boot-env"
mtd2: 00400000 00010000 "rootfs"
mtd3: 00010000 00010000 "user-config"
mtd4: 001f0000 00010000 "uImage"
mtd5: 001b0000 00010000 "ART"

在升级前uhttp中的函数会先检查升级文件的合法性,首先检查checksum 然后检查device标志:
device标志在include/image.mk中的MODULE_NAME中定义。
升级函数采用mtd的方法把一个含有kernel和rootfs的文件写到对应的mtd块中
#define UPG_IMAGE_OFFSET 128
lseek(img_fd, UPG_IMAGE_OFFSET, SEEK_CUR);
sleep(5); /* wait for some seconds ... */
mtd_write(img_fd, len, UPG_MTD_LINUX);
mtd_write(img_fd, len, UPG_MTD_ROOTFS);
首先去掉头部信息。
然后将kernel写到/dev/mtd/4中,这块的大小是1f0000,故制作img的时候要pad head -c 2031616
这样的话kernel写完后 文件指针就指向rootfs的首地址,这样就能保证升级成功。
checksum采用perl脚本appendsum填在最后一个字节

'mkimage' V.S. U-Boot headers, 合并uzImage.bin和cramfs.bin的方法

Ref: http://scyangzhu.wordpress.com/page/3/
Ref: http://www.freeors.com/bbs/forum.php?mod=viewthread&tid=11149
Ref: http://blog.chinaunix.net/space.php?uid=13701930&do=blog&cuid=279896
Ref: http://blog.csdn.net/niuniumenghua/article/details/6490832


以下这种方法最后合并出的文件不是通常uClinux“认为”的标准镜像文件,而且一定要修改bootload代码。请慎用此方法。


(一)、为什么要合并uzImage.bin和cramfs.bin?

一、为了升级时安全、方便

安全。当系统升级程序拿到升级文件时,它必然要判断该文件合法性。uzImage.bin有crc检查,判断起来很容易,cramfs.bin是不带的,有难度。如果两个合为一个,然后使用加在uzImage.bin上的crc机制,那合法性检查上就简单了。

方便。两个合为一个,升级时只须一个文件。

二、节省flash占用空间

分为uzImage.bin和cramfs.bin,则意味着必须给flash人为地分出两个空间,而且这两个空间大小被事先固定。像4M字节 flash,uzImage.bin占用从0x040000开始的1M字节,cramfs.bin占用从0x140000开始的2,752K字节。

既分在两个区就使得分配时不得不存在一个权衡问题,代码不可能写得“恰好”占用。像对于uzImage.bin,这个版本可能是恰好占用,但代码写多了都 知道,功能上的删、加、修改是不确定的,下个版块可能就要因一个意外修改而导致不得不修改uzImage.bin。为解决这个问题,可以使用在 bootload环境变量中写上两区边界方法,但这种方法怎么说也没只是一个文件时方便,而且再“恰好”也肯定是有一定字节“空隙”。


(二)、如何合并

合并采用的方法分为宿主机链接时和uClinux的bootload加载时两个部分。

注:image.0000.img表示最终形成的单个镜像文件。

宿主机链接时

当拿到uzImage.bin.gz(这里多了个gz,这个文件不是最后mkimage后的,而是在mkimage之前gzip之后)和cramfs.bin,链接时执行:

cat $(cramfs.bin) $(uzImage.bin.gz) > $(uzImage.gz)
./mkimage -A arm -O linux -T kernel -C gzip -a <加载地址> -e <执行地址> -n "uClinux Kernel for xxxx" -d $(uzImage.gz) $(IMAGEDIR)/image.0000.img

简单来说,就是在mkimage uzImage.bin.gz之前,先用cat命令合并uzImage.bin.gz和cramfs.bin,合并时cramfs.bin必须放在前头。

cramfs.bin为何要放在前头?

cramfs.bin文件的[4h---7h]四字节存储了该cramfs.bin的文件长度。gzip之后的uzImage.bin.gz无法自识别长度。

至此形成的image.0000.img文件结构

image_header_t:mkImage添加的文件头,固定64字节
cramfs.bin:cramfs.bin部分,它的长度可由偏移68--71字节处得到,假设是cramfs_len。
uzImage.bin:uzImage.bin部分。它在image.0000.img开始址址是64+cramfs_len,它的长度则可以由image_head_t中的有效数据长度字段值减去cramfs_len得到。

uClinux的bootload加载时

加载还是要分两次进行,分别加载uzImage.bin和cramfs.bin。

当运行到要加载uzImage.bin时。开始地址:image.0000.img开始地址加上64+cramfs_len;长度:image_head_t中的有效数据长度字段值减去cramfs_len。

当运行到要加载cramfs.bin时。开始地址:image.0000.img开始地址加上64。长度:image.0000.img偏移处68--71字节处得到的值。


(三)、几点补充

一、检查镜像文件合法性上沿用原先给uzImage.bin的crc机制,可以说不增加较验上开销。

二、在flash向ram加载效率上,只是多了几个判断,和原先分两个文件加载方法上可说一样的执行效率。


=================================================
=================================================

一个实例:对bootload改动


bootload准备好flash和sdram可通信后,它接下执行:

1):把uzImage.bin从flash加载到sdram;
2):把cramfs.bin从flash加载到sdram;
3):把矢量表加载到sdram零处(u-boot代码中,该0x0地址是一个向量表,第一条指令跳转branch到复位代码start_codeDepend on Processor)
4):运行uClinux内核

bootcmd环境变量表示以上四个过程:
    bootcmd=cp.l fc040000 01800000 40000; cp.l fc140000 02000000 AC000; cp.l fc3f8000 0 e;bootm 01800000
    bootm命令是用来引导经过u-boot的工具mkimage打包后的kernel image的,什么叫做经过u-boot的工具mkimage打包后的kernel image,这个就要看mkimage的代码,看看它做了些什么,虽然我很希望大家不要偷懒,认真地去看看,但是我知道还是有很多人懒得去做这件,那么我 就j将分析mkimage代码后得到的总结告诉大家,mkimage做了些什么,怎么用这个工具。

    mkimage的用法
    • uboot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。
    • mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置, 入口点在内存的那个位置以及映象名是什么
    root@Glym:/tftpboot# ./mkimage
    Usage: ./mkimage -l image
    -l ==> list image header information
    ./mkimage -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
    -A ==> set architecture to 'arch'
    -O ==> set operating system to 'os'
    -T ==> set image type to 'type'
    -C ==> set compression type 'comp'
    -a ==> set load address to 'addr' (hex)
    -e ==> set entry point to 'ep' (hex)
    -n ==> set image name to 'name'
    -d ==> use image data from 'datafile'
    -x ==> set XIP (execute in place)
    参数说明:
    -A 指定CPU的体系结构:
    取值 表示的体系结构
    alpha Alpha
    arm A RM
    x86 Intel x86
    ia64 IA64
    mips MIPS
    mips64 MIPS 64 Bit
    ppc PowerPC
    s390 IBM S390
    sh SuperH
    sparc SPARC
    sparc64 SPARC 64 Bit
    m68k MC68000
    -O 指定操作系统类型,可以取以下值:
    openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos
    -T 指定映象类型,可以取以下值:
    standalone、kernel、ramdisk、multi、firmware、script、filesystem
    -C 指定映象压缩方式,可以取以下值:
    none 不压缩
    gzip 用gzip的压缩方式
    bzip2 用bzip2的压缩方式
    -a 指定映象在内存中的加载地址,映象下载到内存中时,要按照用mkimage制作映象时,这个参数所指定的地址值来下载
    -e 指定映象运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头)
    -n 指定映象名
    -d 指定制作映象的源文件

    cp.l是个复制命令,cp==>copy;l==>long,数量以4字节单位,以上的bootcmd类似下以命令
    注:以下的flash地址像fc040000,完全大于了4M/8M,那时因为flash被map后它的起始地址就是0xfc000000,所以fc040000对应的其实是flash上的0x40000地址。
    flash-2-mem(0xfc040000, 0x01800000, 0x40000 << 2);
    flash-2-mem(0xfc140000, 0x02000000, 0xac000 << 2);
    flash-2-mem(0xfc3f8000, 0x00000000, 0xe << 2);
    bootm(0x1800000)
    以下就是从flash加载到sdram函数。uzImage.bin、cramfs.bin、矢量表被分次调用。
    int do_mem_cp(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
    {
            ulong addr, dest, count;
            int        size;
            image_header_t* hdr;
            if (argc != 4) {
                    printf ("Usage:\n%s\n", cmdtp->usage);
                    return 1;
            }
            /* Check for size specification.
            */
            if ((size = cmd_get_data_size(argv[0], 4)) < 0)
                    return 1;
            addr = simple_strtoul(argv[1], NULL, 16);
            addr += base_address;
            dest = simple_strtoul(argv[2], NULL, 16);
            dest += base_address;
            count = simple_strtoul(argv[3], NULL, 16);
            if (count == 0) {
                    puts ("Zero length ???\n");
                    return 1;
            }
            //
            // 由于使用了把uzImage.bin和cramfs.bin合并到一个文件,而在命令行上又保持不变,就须要修正一些变量意义。
            //
            if (addr == 0xfc040000) {
                    // fc040000, flash(uzImage.bin)-->ram
                    // dest = 0x01008000;
                    // 读出cramfs.bin文件长度
                    cramfs_len = *(ulong *)(0xfc040000 + sizeof(image_header_t) + 4);
                    printf("addr == 0xfc040000, this is uzImage.bin, base_address: %lu, cramfs_len: %lu\n", base_address, cramfs_len);
                    hdr = (image_header_t*)addr;
                    count = sizeof(image_header_t) / size;        // sizeo = 4, sizeof(image_header_t)一般0x40字节,可以被4整除
                    // 复制头
                    while (count-- > 0) {
                            if (size == 4)
                                    *((ulong  *)dest) = *((ulong  *)addr);
                            else if (size == 2)
                                    *((ushort *)dest) = *((ushort *)addr);
                            else
                                    *((u_char *)dest) = *((u_char *)addr);
                            addr += size;
                            dest += size;
                    }
                    // 从image_header_t中取出数据长度(count = cramfs.bin + uzImage.bin.gz)
                    count = SWAP_LONG(hdr->ih_size); // ntohl(ih_size)
                   
                    // 注意,此时的addr已经变成, 函数参数中的addr + sizeof(image_header_t)
                    addr = addr + cramfs_len;
                    count = (count - cramfs_len) / size + 1; // cp.l时,size是4, uzImage.bin有效长度会由gzip压缩机制保证(4M flash)或只要至少比有效多就行(8M flash),这里多复制四个字节总不会出错
    #ifndef USE_4M_FLASH
                    // 对于uzImage.bin来说,为减少字节占用一般使用gzip压缩(大概可以达到2:1压缩率),使整个软件能约束在4M字节以内。
                    // 因为使用gzip压缩,bootload必须对uzImage.bin进行解压,这个过程有时较耗时间,为减少启动时间宁愿采用不压缩而使用8M字节flash
                    // 这里就是8M情况
                    // 对于8M, uzImage.bin没经过压缩, 这里就直接复制到解压后的uzImage.bin应该放到的地址, 省掉do_bootm()中的memcpy
                    dest = SWAP_LONG(hdr->ih_load);
    #endif
                   
            } else if ((addr == 0xfc140000) && cramfs_len) {
                    // fc140000, flash(cramfs.bin)-->ram
                    // 总是假定uzImage.bin已被下载
                    printf("addr == 0xfc140000, this is cramfs.bin, base_address: %lu, cramfs_len: %lu\n", base_address, cramfs_len);
                    addr = 0xfc040000 + sizeof(image_header_t);        // addr要回到flash开始内核开始处: 0xfc040000
                    count = cramfs_len / size;
            } else if (addr == 0xfc3f8000) {
                    // 矢量表。这个才60字节,直接是以内存数组表示
                    printf("addr == 0xfc3f8000, this is vector, count: %lu\n", count);
                    addr = vectordata;
            }
            while (count-- > 0) {
                    if (size == 4)
                            *((ulong  *)dest) = *((ulong  *)addr);
                    else if (size == 2)
                            *((ushort *)dest) = *((ushort *)addr);
                    else
                            *((u_char *)dest) = *((u_char *)addr);
                    addr += size;
                    dest += size;
            }
            return 0;
    }
    =================================================


    【转】 制作JFFS2文件系统 (including u-boot)


    Ref: http://hi.baidu.com/freshwater2009/blog/item/98515ead5651a0ecfbed50af.html

      JFFS2是Flash上应用最广的一个日志结构文件系统。它提供的垃圾回收机 制,不需要马上对擦写越界的块进行擦写,而只需要将其设置一个标志,标明为脏块,当可用的块数不足时,垃圾回收机制才开始回收这些节点。同时,由于 JFFS2基于日志结构,在意外掉电后仍然可以保持数据的完整性,而不会丢失数据。
      本文阐述如何在nand flash上实现JFFS2根文件文件系统。实验环境是:FS2410开发平台及ubantu7.04主机环境。
      由于使用的ubantu7.04的环境没有安装制作JFFS2文件系统镜像的工具mkfs.jffs2,所以首先在Linux系统中安装mkfs.jffs2工具,安装的过程如下:
      1.下载MTD工具包
      本处使用的是mtd-snapshot-20050519.tar.bz2
      farsight#tar –jxvf mtd-snapshot-20050519.tar.bz2
      farsight#cd mtd
      farsight#./configure
      farsight#make
      farsight#make install
      如果系统中没有安装ZLIB库,那么首先安装ZLIB库。具体的安装过程如下:
      farsight#tar –zxvf zlib-1.2.3.tar.gz
      farsight#cd zlib-1.2.3
      farsight#./configure
      farsight#make
      farsight#make install
      完成此步骤后,系统中就有了mkfs.jffs2的工具。注意:这个工具不同于 mkfs.ext2工具,它只能制作相应的JFFS2文件系统的镜像,而不具有进行格式化的功能,而mkfs.ext2具备这以上两种功能。然后用这个工 具就可以制作JFFS2文件系统的镜像了。

      2、制作JFFS2文件镜像
      实验平台用到的nand flash 是K9F1208,在制作镜像过程添加的参数需要和它对应。
      farsight#mkfs.jffs2 -r /source/rootfs  -o rootfs.jffs2   -e  0x4000 --pad=0x800000  -n
     ==============================================================================================
    实例:
    mkfs.jffs2 -r rootfs -o rootfs.jffs2 -e 0x4000 --pad=0x1000000 -s 0x200 -n

    mkfs.jffs2: Usage: mkfs.jffs2 [OPTIONS]
    Make a JFFS2 file system image from an existing directory tree
    Options:
    -p, --pad[=SIZE]       
    用16進制表示所要輸出檔案的大小,也就是root.jffs2的size。很重要的是,為了不浪費flash空間,這個值最好符合flash driver的區塊大小。如果不足则使用0xff来填充补满。
    -r, -d, --root=DIR      指定要做成image的源資料夾.(默认:当前文件夹)
    -s, --pagesize=SIZE     节点页大小(默认: 4KiB)
    -e, --eraseblock=SIZE   设定擦除块的大小为(默认: 64KiB),一般为(16K + 512)
    -c, --cleanmarker=SIZE  Size of cleanmarker (default 12)
    -m, --compr-mode=MODE   Select compression mode (default: priortiry)
    -x, --disable-compressor=COMPRESSOR_NAME Disable a compressor
    -X, --enable-compressor=COMPRESSOR_NAME Enable a compressor
    -y, --compressor-priority=PRIORITY:COMPRESSOR_NAME Set the priority of a compressor
    -L, --list-compressors Show the list of the avaiable compressors
    -t, --test-compression Call decompress and compare with the original (for test)
    -n, --no-cleanmarkers   指明不添加清楚标记(nand flash 有自己的校检块,存放相关的信息。)如果挂载后会出现类似:CLEANMARKER node found at 0x0042c000
    has totlen 0xc != normal 0x0 的警告,则加上-n 就会消失。
    -o, --output=FILE       指定輸出image檔案的文件名.(default: stdout)
    -l, --little-endian     指定使用小端格式
    -b, --big-endian        指定使用大端格式
    -D, --devtable=FILE     Use the named FILE as a device table file
    -f, --faketime          Change all file times to '0' for regression testing
    -q, --squash            Squash permissions and owners making all files be owned byroot
    -U, --squash-uids       将所有文件的拥有者设为root用户
    -P, --squash-perms      Squash permissions on all files
          --with-xattr      stuff all xattr entries into image
          --with-selinux    stuff only SELinux Labels into jffs2 image
          --with-posix-acl  stuff only POSIX ACL entries into jffs2 image
    -h, --help              显示这些文字
    -v, --verbose           Verbose operation
    -V, --version           显示版本
    -i, --incremental=FILE  Parse FILE and generate appendage output for it
     
     
    生成的rootfs.img可通过u-boot直接烧写至nand flash:
                  nand erase 0x200000 0x3e00000//擦写
                  tftp 0x30000000 rootfs.img//下载
                  nand write.jffs2 0x200000 $(filesize)//烧入
    ==================================================================================================================
      这样就会生成一个8M大小的rootfs.jffs2的镜像,它也正是JFFS2文件系统的镜像,关于这个命令行里的选项的内容,可以用man a mkfs.jffs2命令来查看内容。

      JFFS2 维护了几个链表来管理擦写块,根据擦写块上的内容,一个擦写块会在不同的链表上。具体来说,
    当一个擦写块上都是合法(valid)的节点时,它会在 clean_list 上;
    当一个擦写块包含至少一个过时(obsolete)的节点时,它会在 dirty_list 上;
    当一个擦写块被擦写完毕,并被写入 CLEANMARKER 节点后,它会在 free_list 上。而当你在挂载这个文件系统的时候,如果出现CLEANMARKER node found at 0x0042c000 has totlen 0xc != normal 0x0的警告的时候,可以加一个“-n”的选项,这个主要是由于针对Nand Flash不需要在每个擦除块的开始写入CLEANMARKER 节点。

      3、设置内核启动参数
      本处用的bootloader是U-BOOT.所以在U-BOOT的命令终端设置如下:
    FS2410#setenv bootargs root=/dev/mtdblock/2 rootfstype=jffs2rw console=ttySAC0, 115200  init=/linuxrc mem=64M

      4、配置内核支持JFFS2文件系统
      File systems ---> Miscellaneous filesystems --->
      <*>JournallingFlash File System v2 (JFFS2) support
      [*]JFFS2write-bufferingsupport
      [*]AdvancedcompressionoptionsforJFFS2
      [*]JFFS2ZLIBcompressionsupport
      [*]JFFS2RTIMEcompressionsupport
      [*] JFFS2 RUBIN compression support

      5、下载rootfs.jffs2镜像
      下载到Nand Flash第二个分区。
      FS2410#nand erase 200000 800000
      FS2410#nand write.jffs2 300008000 200000 800000

      这里说明下关于nand flash操作的几个常用命令的含义
      nand write:向Nand Flash写入数据,如果NandFlash相应的区域有坏块,则直接报错。
      nand write.jffs2:向Nand Flash写入数据,如果NandFlash相应的区域有坏块,可以跳过坏块。
      nand read:读取Nand Flash相应区域的数据,如果NandFlash相应的区域有坏块,则直接报错。
      nand read.jffs2s:读取Nand Flash相应区域的数据,如果NandFlash相应的区域有坏块,将对应坏块区域的缓冲填充0xff,然后跳过此坏块继续读取。
      nand read.jffs2:读取Nand Flash相应区域的数据,如果NandFlash相应的区域有坏块,直接跳过坏块。

      具体的参考代码参看U-BOOT源码:common/cmd_nand.c文件。
      下载完JFFS2文件系统镜像后,需要把Linux内核NandFlash的驱动关于第二个分区的大小改为8M(和镜像一样大),否则会出现类似如下错误:
      Freeing init memory: 124K
      Warning: unable to open an initial console.
      Argh. Special inode #171 with mode 0xa1ff had more than one node
      Kernel panic: No init found. Try passing init= option to kernel.
      Argh. Special inode #63 with mode 0xa1ff had more than one node
      Returned error for crccheck of ino #63. Expect badness...
      Argh. Special inode #67 with mode 0xa1ff had more than one node
      Returned error for crccheck of ino #67. Expect badness...
      Argh. Special inode #68 with mode 0xa1ff had more than one node
      到此,一个JFFS2文件系统的镜像制作成功。可以启动系统并测试JFFS2的性能了

      还有一种制作JFFS2文件系统镜像的方法,在制作镜像的参数中可以不加—pad选项,过程如下:
      farsight#mkfs.jffs2-r /source/rootfs -o rootfs.jffs2 -e 0x4000 -n
      // 之前对比:farsight#mkfs.jffs2 -r /source/rootfs  -o rootfs.jffs2   -e  0x4000--pad=0x800000  -n

      启动开发板烧写rootfs.jffs2镜像
      FS2410#nand erase 200000 800000    //(注意把整个存放文件系统的分区全部给擦除)。
      FS2410#nand write.jffs2 30008000 200000 31a28c    //(必须是rootfs.jffs2的实际大小。如果是你写成了4M,那么分区的其余部分JFFS2文件系统将无法识别)。




    基于2440的一个实例:
    =====================================================
    移植文件系统写到nand flash
    =====================================================
    ( 1 ) 通过网络文件系统加载开发板上根文件系统
            把主机上把文件系统打包:
    [root@orat3000 nfs]# tar -cf ../nfsroot.tar *
    ( 2 ) 擦除:nand erase 400000 fc00000
    ( 3 ) 把/dev/mtdblock2 挂载到 /mnt下(会自动格式化)
    [root: /]# mount -t yaffs /dev/mtdblock2 /mnt
    ( 4 ) 在开发板上进入/mnt解包:
    [root: /mnt]# tar -xvf /nfsroot.tar
    ( 5 ) 备份原内核参数环境变量:
    set nfs_boot $bootargs
    ( 6 ) 设置新的内核参数环境变量:
    set nand_boot noinitrd root=/dev/mtdblock2 rw console=ttySAC0,115200 init=/linuxrc mem=64M
    ( 7 ) 把默认内核参数改为新的环境变量:
    set bootargs $nand_boot
    ( 8 ) 保存设置:
    save
    (9)reset
    修改完应该有三个环境变量: 
    nfs_boot=noinitrd root=/dev/nfs rw nfsroot=192.168.1.3:/nfsroot ip=192.168.1.88 console=ttySAC0,115200 mem=64M    //这个只是保存修改前的变量,只是个副本,没有意义
    nand_boot=noinitrd root=/dev/mtdblock2 rw console=ttySAC0,115200 init=/linuxrc mem=64M
    bootargs=noinitrd root=/dev/mtdblock2 rw console=ttySAC0,115200 init=/linuxrc mem=64M

    ==============================改变之前的实例==================================
    bootcmd=nand read 0x30008000 0x100000 0x220000;bootm 30008000
    bootdelay=3
    baudrate=115200
    ethaddr=11:22:33:44:55:66
    ipaddr=10.1.0.222
    serverip=10.1.0.177
    netmask=255.255.255.0
    stdin=serial
    stdout=serial
    stderr=serial
    ethact=dm9000
    bootargs=noinitrd root=/dev/nfs rw nfsroot=10.1.0.177:/nfsroot ip=10.1.0.222 console=ttySAC0,115200 mem=64M
    Environment size: 339/131068 bytes

    制作jffs2文件系统(erase block should be same with flash)

    Ref: http://blog.csdn.net/liyandong1204/article/details/6836860

    jffs2文件系统制作过程
    JFFS2 是一个开放源码的项目(www.infradead.org)它是在闪存上使用非常广泛的读/写文件系统,在嵌入式系统中被普遍的应用。

    1.       安装mkfs工具 MTD主页:http://www.linux-mtd.infradead.org/archive/index.html

    1.1.   配置参数 下载好MTD软件,解压后
    $ make menuconfig
    按需要配置参数,下边是在网上搜索到的一个配置方案:
    进入 Memory Technology Devices (MTD) --->
                <*> Memory Technology Device (MTD) support
                [*] Debugging
                [*] MTD partitioning support
                [*]  Command line partition table parsing
                [*] Direct char device access to MTD devices
                [*] Caching block device access to MTD devices
                RAM/ROM/Flash chip drivers ----->
                <*> Detect non-CFI AMD/JEDEC-compatible flash chips
                <*> Support for AMD/Fujitsu flash chips
                Mapping drivers for chip access --->
                [*] Support non-linear mappings of flash chips
                Self-contained MTD device drivers --->
                [*] Support for AT45... DataFlash
                NAND Flash Device Drivers ---->
                [*] NAND Device Support
                [*] Support for NAND Flash /SmartMedia on AT91
                File systems ---->
                <*> Second extended fs support
                [*] Inotify file change notification support
                [*] Inotify support for user space
                <*> Filesystem in Userspace support
                Miscellaneous filesystems
                <*> Journalling Flash File System v2 (JFFS2) support
                [*] JFFS2 write-buffering support
                <*> Compressed ROM file system support (cramfs)
          以上配置中没有列出的,都没选;其配置仅做参考,可根据自己的需要自行配置。
        $ make all

    1.2.   安装zlib库 由于交叉编译mtd工具时需要zlib.h文件,所以在编译之前先安装zlib库文件。从网上下载zlib-1.2.3.tar.gz解压缩
    $ tar zxvf zlib-1.2.3.tar.gz
    $ cd zlib-1.2.3
    $ ./configure –prefix=/usr/local/arm/arm-linux --shared
    修改Makefile如下:
    CC=gcc(由于我的mkfs.jffs2是在宿主机下制作文件系统用的,因此不需要采用交叉编译。下边的LDSHARED也是一样,不需要采用交叉工具)
    LDSHARED=ld -shared
    $ make all
    $ make install
    注意:这里是安装在/usr/local/arm/arm-linux目录下

    1.3.   安装mtd 从网上下载mtd-snapshot-20050519.tar.bz2 解压缩
    $ tar jxvf mtd-snapshot-*
    $ cd mtd/util
    修改该目录下的Makefile:
    SBINDIR=/usr/sbin
    MANDIR=/usr/man
    INCLUDEDIR=/usr/include
    LDFLAGS=-L/usr/local/arm/arm-linux/lib           #zlib库的库文件所在文件夹
    CROSS=                      #用于宿主机下
    CC := $(CROSS)gcc
    CFLAGS := -I../include -I/usr/local/arm/arm-linux/include -O2 -Wall

    $ make all
    (加上-I/usr/local/arm/arm-linux/include是因为,在编译的过程中出现找不到zlib.h的错误,加上LDFLAGS也还是有同样的错误,所以直接在CFLAG中加上zlib库文件所在的文件夹的位置。)
    然后将该目录下生成的 flash_erase,flash_eraseall, mkfs.jffs2工具放在ramdisk文件系统中(我这里放在/bin目录下)。另外,需要将/arm-linux/lib目录下的libz.so, libz.so.1, libz.so.1.2.3文件拷贝到ramdisk文件系统的/lib目录下,否则mkfs.jffs2工具不能使用。


    2.       挂载、制作jffs2文件系统 在这里,为了避免重新制作文件系统,我采用了英蓓特公司的MBS-SAM9G45开发板自带的jffs2文件系统Angstrom-x11-image-demo-glibc-at91.rootfs.jffs2。在整个制作jffs2文件系统的过程中,我们采用root权限。

    2.1.   挂载文件系统镜像 jffs2文件系统不是块设备,不能直接mount,需要做一些中间步骤。首先,内核必须支持MTD,并且编译了mtdram、mtdblock这两个模块。先先建立一个大于等于要挂载的文件系统的虚拟mtd设备。Angstrom-x11-image-demo-glibc-at91.rootfs.jffs2文件系统为28.2M,那么我先建立一个大于等于28.2M的虚拟mtd设备。(为了避免制作过程当中向文件系统里边添加大文件,我将mtd大小设置为50M*1024=50720K
    $ sudo modprobe mtdram total_size=50720
    其中,total_size的单位是KB,指定mtd的大小。
    加载mtdblock产生虚拟块设备并把Angstrom-x11-image-demo-glibc-at91.rootfs.jffs2的内容写入生成的虚拟设备中:
    $ sudo modprobe mtdblock
    $ sudo dd if=/home/Embest_SAM9G45/Angstrom-x11-image-demo-glibc-at91.rootfs.jffs2 of=/dev/mtdblock0
    (注:dd命令是指定大小的块拷贝文件,并在拷贝的同时进行指定的转换。if=file:输入文件名,缺省为标准输入。of=file:输出文件名,缺省为标准输出。)
    创建挂载点:
    $mkdir /mnt/mtd
    现在就可以mount了:
    $ sudo mount -t jffs2 /dev/mtdblock0 /mnt/mtd
    进入/mnt/mtd之后即可对文件系统进行修改!

    2.2.   制作jffs2文件系统镜像 修改(在后边一步讲)好自己的文件系统后,退到已做好的文件系统目录的上一级。比如我的文件系统的挂载点是/mnt/mtd,则退到/mnt目录下,用mkfs.jffs2工具制作jffs2文件系统,如下:
    #mkfs.jffs2 -r rootfs -o fs.jffs2 -e 0x20000 --pad=0x500000 -s 0x800 –n -l
    即可生成 rootfs.jffs2
    Mkfs.jffs2各参数的意义如下:
    -r:指定要做成image的目录名。
    -o:指定输出image的文件名。
    -e:每一块要擦除的block size,默认是64KB.要注意,不同的flash, 其block size会不一样,三星的K9F2G08U0A的block size为0x20000(从其datasheet里可以找到)。在没有加-e选项是,启动会出现以下错误:at91sam user.warn kernel: Empty flash at 0x00f0fffc ends at 0x00f10000。因此,若有类似的错误,加上-e选项,并配置nandflash的块大小,即可消除。
    --pad(-p):用16进制来表示所要输出文件的大小,也就是fs.jffs2的大小,如果实际大小不足此设定的大小,则用0xFF补足。也可以不用此选项,生成的文件系统的大小跟本身大小一致,暂时还不知道有和妙用,但是加上后会少出现很多错误。
    -n,-no-cleanmarkers:指明不添加清楚标记(nandflash有自己的校检块,存放相关的信息)。如果挂载后会出现类似:CLEANMARKER node found at 0x0042c000 has totlen 0xc != normal 0x0的警告,则加上-n就会消失。
    -l,--little-endian:指定使用小端格式。
    还有的选项,不需要了,可以自己看帮助!用如下命令mkfs.jffs2 –h


    3.       修改文件系统 
    3.1.   需要修改的原因
    1、  系统在启动时,会启动很多的项目,而很多的进程是我们根本不需要的,通过对文件系统的修改,可以减少启动项,加快开机速度。
    2、  由于开发板提供的文件系统很全面,囊括了声卡、显卡、游戏、液晶显示屏等很多驱动,但是这些都是我所不需要的,因此通过修改文件系统,我们可以裁减掉不需要的驱动、库文件以及所有的配置文件。
    3、  在系统启动时,需要加入我们自己启动程序。在这个文件系统中,加入了超级用户自动登录功能、无线网卡驱动自启动以及和FPGA的接口驱动自动加载。
    3.2.   删除多余的启动项 所有的启动项都在init.d中实现,按照不同的runlevel,分布在rc0.d~rc6.d以及rcS.d中。rc?.d和/etc/init.d的关系,在下边这篇文章中叙述的相当详细,可以参考学习:
    rc?.d中都是指向/etc/init.d中脚本的连接。在rc?.d中,可以看到有K和S开头的两种连接。S开头表示启动,K开头表示不启动。在启动时,系统会执行rc?.d中的所有S开头的所指向的脚本文件。因此,我们只需要修改rc?.d中的连接以及/etc/init.d中的脚本文件,就可以修改启动的项目。
    在本文件系统中,我做了如下修改:
    rc2.d:关闭S50usb-gadget
    rc3.d:关闭S50usb-gadget、S10alsa-state、S10dropbear
    rc4.d:关闭S50usb-gadget
    rcS.d:关闭S00psplash(旋转进度条,显示开机的进度)、S02banner
    我用的关闭的方法是mv S50usb-gadget K50usb-gadget,这样就关闭了usb-gadget,在需要启动此项时,也很方便启动。当然,这样的操作并没有大幅度减小启动的时间。

    3.3.   root用户自动登录 在每次设备启动或者复位的时候,都需要手动在启动结束后输入root以登录系统,而在无人值守的情况下,需要root用户能自动登录,并执行程序。在/etc/inittab中做如下修改即可实现root用户自动登录:
    默认启动runlevel为5,即id: 5 :default
    注释掉登录的那行代码,即#S:2345:respawn:/sbin/getty 115200 ttyS0
    添加如下登录代码:S1:2345:respawn:/sbin/getty /usr/bin/autologin 115200 ttyS0。启动autologin程序需要自己完成,/usr/bin/是autologin所在位置,这个位置可以自己任意选取。
    编写autologin.c程序如下:
    #include <stdlib.h>
    #include <stdio.h>

    int main ( )
    {
    execlp(“login”, “login”, “-f”, “root”, 0);
    }
    然后编译autologin.c,注意要使用交叉编译器。
    $ /usr/local/arm-2007q1/bin/arm-none-linux-gnueabi-gcc –o autologin autologin
    将编译好的autologin可执行文件复制到/usr/bin目录下。也可以放到其他目录下,相应的修改/etc/inittab即可。
    至此,重新制作文件系统镜像后,烧写进nandflash,即可自动登录root用户。

    3.4.   添加自己的启动项 因为rcS是每个系统都必须启动的项,因此在rcS中添加启动项是最直接的方法。(在我们的系统的中没有rc.local文件,网上有很多在rc.local中添加自启动的方法)
    我需要自动加载无线网卡驱动、FPGA接口驱动以及开启DHCP服务。
    首先,将无线网卡和FPGA的驱动以及DHCP的可执行文件以及配置文件拷贝进相应的文件夹(可执行放的文件夹可以自己设置,在写执行脚本的时候注意路径,配置文件需要按规则放)。在这里,我将无线网卡驱动rt3070sta.ko拷贝到/usr/src中,rt3070sta.ko驱动加载后需要读取的配置文件RT2870STA.dat放在/etc/Wireless/RT2870STA中。将FPGA的接口驱动fpgadev拷贝到/usr/src。在编译内核时,已经添加了dhcp服务,因此在/sbin和/usr/sbin中分别放有客户端uhdpc和服务端uhdpd,拷贝配置文件udhcpd.conf到/etc下,在/var/lib/misci下新建(touch命令)一个文件udhcpd.leases。修改所有文件的权限为755(chmod 755 xxx)
    其次,需要在/etc/init.d中添加执行的脚本文件在此脚本文件中添加你要执行的代码。编写autoset_sta脚本如下
    #!/bin/sh

    #Auto set wireless card and FPGA driver
    /sbin/insmod /usr/src/rt3070sta.ko
    /sbin/insmod /usr/src/fpgadev.ko\

    /sbin/udhcpc
    最后,修改文件权限并在rcS.d中添加指向init.d/autoset_sta的连接。
    $ chmod 755 autoset_sta
    $ cd ../rcS.d
    $ ln –sf ../nit.d/autoset_sta S99autoset_sta
    制作好文件系统后,烧写重启即可自动加载这些驱动以及服务。

    3.5.   减小文件系统体积 原始文件系统28.2MB,在加上自己的文件后,达到了30MB。嵌入式下存储资源是宝贵的,为了减少所占资源,必须对文件系统进行瘦身。
    进入/etc,删除不必要的启动脚本文件,比如X11的文件。
    进入/lib,删除比需要的库文件。
    进入/usr,删除games
    进入/usr/bin,删除没用的命令。
    进入/usr/lib,删除没用的库文件。这里库文件相当多,达到16M之巨,而里边大多是我们不用的库,例如libaudiofile.so.0、libsoundgen.so.0、libmad*、libICE等,还有关于图像和界面的库等,这些都可以删掉。全部删除后,/usr/lib可以小到3M~6M。当然,这都得看具体应用,对与我来讲,我所用到的就是网络编程、多线程、无线网卡、SPI通信、串口通信等东西,所以能删除很多不必要的库。但是因为启动项删除的不够干净,在启动的过程当中,仍然会用到/usr/lib中的很多库,因此会出现很多错误,但并不影响我的操作。
    进入/usr/share,删除没有用的doc以及applications。。。。。
    基本裁剪已经差不多了,重新编译文件系统,输出的大小为16M左右。
    (还需要好好把系统的整个启动已经程序的调用搞清楚一下,这还可以大大的裁剪)


    4.       问题及解决方法 Q:在启动过程中出现at91sam user.warn kernel: Empty flash at 0x00f0fffc ends at 0x00f10000问题
    A:在mkfs.jffs2的时候,加上-e 0x20000指定擦除块的大小。-e是指定擦除块的大小,我们使用的nandflash的块大小为128K字节,因此-e后的参数为(128*1024)10=(20000)16

    Q:启动的时候出现CLEANMARKER node found at 0x00f10000 has totlen 0xc != normal 0x0问题。
    A:在mkfs.jffs2的时候,加上-n选项。-n, --no-cleanmarkers。指明不添加清楚标记(nand flash 有自己的校检块,存放相关的信息。)如果挂载后会出现类似:CLEANMARKER node found at 0x0042c000 has totlen 0xc != normal 0x0 的警告,则加上-n 就会消失。

    Q:解决jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x01649298: 0xa25e instead问题的方法
    A:在mkfs.jffs2的时候加上-s 2048(页大小,由芯片决定)以及-l(小端模式)两个选项。-s是指明页的大小,我们使用的nandflash的页的大小为2048字节。-l指明为小端模式,一般嵌入式下均为小端模式。

    说明:
    1、  在文件系统制作的过程,均需要使用root用户权限;
    2、  一般嵌入式下只有root用户登录,因此文件系统中的所有文件都需要具有root可执行权限,如果用其他用户登录,请保证文件系统中文件(特别是自己添加的文件)的相应可执行权限。
    3、 Erase block should be same with the flash with defined erase block in SPEC.

    mkfs.jffs2 Options:  (http://linux.die.net/man/1/mkfs.jffs2)
    -e, --eraseblock=SIZE
    Use erase block size SIZE. The default is 64 KiB. If you use a erase block size different than the erase block size of the target MTD device, JFFS2 may not perform optimally. If the SIZE specified is below 4096, the units are assumed to be KiB.

    还不清楚的问题:
    1、  启动过程还不明白
    2、  如何更有效的减少启动时间?