Thursday, December 8, 2011

WEP Key: OPEN V.S. SHARED System


AP的“open key”和“share key”有啥不同?



簡單說,就是AP與HOST都設成OPEN KEY的話,HOST可以跟AP連接上,可是當要傳送封包時,如果HOST沒有設對WEP碼,那就無法傳送成功

如果AP與HOST都設成SHARE KEY的話,那當一開始HOST要跟AP連接時,AP就會要檢查HOST的WEP碼.如果不對,就無法連上AP

而當封包傳送時,不管是OPEN或SHARE,都是會加上WEP碼的

兩者的差別,就是一開始要連接AP時的檢查(或者叫認證吧)

而不管是OPEN或SHARE,傳送封包時都一樣會加上WEP碼,所以只要收集到封包,就可以破解



本圖取用自 Microsoft 802.11 無線區域網路通訊協定及應用 一書

Monday, November 14, 2011

What is different between fork() & vfork()?

Ref:  http://csie-tw.blogspot.com/2009/04/vforkuclinux.html

節錄自:大黑狗 之 嵌入式系統專案實務 與 產業觀察

  fork()和vfork()這兩個系統功能都可以複製出和呼叫者﹙parent﹚完全相同processchild﹚,但呼叫vfork()後的parent process會被暫停,直到被複製出來的child process執行exec()exit();而呼叫fork()後的parent process會和新產生的child process平行(concurrent﹚執行。
  接下來我們必須約略解釋一下fork()Linux中的實現方式,旨在讓讀者知道為什麼這個系統功能沒法直接移植到沒有MMUCPU上;首先我們必須先介紹一下”copy-on-write”這個觀念:
    一個程式在執行時會佔據記憶體空間,粗略可分為程式段、資料段、堆疊段與常數段,其中程式段與堆疊段是唯讀的,資料段與堆疊段的內容則有可能在執行時期被改變。Linux中,當某個process呼叫fork()產生child process時,系統只會為新的process配置堆疊段,其他的記憶體區段都是共用的;實際上在child process呼叫exec()去執行另一個程式前,諸如程式段以及常數段這些內容不可以被改變的記憶體區段始終可以共用。可是資料段就不能一直共用,如果parentchild process同時去操作某個變數勢必會引起混亂。
    Child processfork出來後馬上呼叫exec()去執行其他程式是最常用的流程,以此說來,雖然每個process都必須有獨立的資料段,但馬上為child process配置資料段是很不經濟的,因為在大部分的狀況下child process並不會去對資料段作寫入的動作,在執行exec()後,之前的資料段就沒用了。
為了解決這個問題,Linux(fork)採用”copy-on-write”的技術,在child process尚未對資料段作寫入的動作之前,parentchild process共用資料段;當child process對資料段記憶體作出寫入的要求時,系統會配置一塊實體記憶體﹙一個page﹚給child process,並將原本資料段中被要求寫入之page的內容複製到這塊新的page;接著系統會更改child process的page table,使要被寫入資料的虛擬位址可以對應到上述新配置的實體記憶體位址。
此時child processparent process的資料段大部分都還是功用的,不同的地方只是這次要被寫入的page;這種演算法的好處很多,在最節省記憶體的前提下使得parentchild process不致互相影響。要達到這種效果,CPU沒有支援MMU是做不到的,所以uClinux無法直接支援fork()這個系統功能。
uClinux無法作到安全的資料段分享機制,產生child process後複製整塊資料段也顯得有點笨拙,於是只好讓parent process停止執行,直到child process結束執行或有了自己的資料段之後才能恢復執行,前者表示child process出現例外或呼叫了_exit(),而後者則表示child process呼叫了exec()去執行其他的程式。這樣妥協出來的功能,就是原本Linux中的vfork()系統呼叫。
如果讀者對copy-on-write的原理不清楚也沒關係,讀者在使用uClinux時只需知道一般Linux在實現fork()這個系統功能時必須用到MMU的機制,而uClinux執行在沒有MMUCPU之上,所以fork()無法直接移植到uClinux上;uClinux提供vfork()以達到多工的效果。
必須注意的是,使用vfork()產生的child process很可能會破壞parent process原本的資料段,所以程式設計師在uClinux上使用vfork()時必須格外小心;而且沒有fork()系統功能的事實使得許多原本運行在Linux上的應用程式無法完全不經修改救執行於uClinux之上。
我的心得:在早期的fork會複製整個parent的address space,造成時間的浪費,使用vfork則不會複製parent的address space。近來的fork則使用copy-on-write的技術,但是這卻需要MMU的幫忙,才能將child process的page table修改成正確的physcial address,而在嵌入式系統上,uClinux並不支援MMU,所以只能使用vfork。


Read more: http://csie-tw.blogspot.com/2009/04/vforkuclinux.html#ixzz1djWY034l

Tuesday, November 8, 2011

Port Forwarding,Port Triggering及UPnP/NAT-PMP


Tomato Port Forwarding / Port Triggering 設定

簡單介紹有關頻寬分享器常會有的功能:Port Forwarding,Port Triggering及UPnP/NAT-PMP


當頻寬分享器安裝好後,通常會有一個功能,叫Port Forwarding,但是手冊上不一定會說明怎麼使用,比如ASUS RT-N10 / RT-N16二本手冊,我根本沒看到在哪設定? 以下介紹在Tomato Firmware上有的Port Forwarding功能:

  • Port Forwarding
  • Port Triggering
  • UPnP/NAT-PMP

Port Forwarding
這是最早出現的轉Port方式,比如如果你的PC,IP為192.168.0.1,在這台機器上有一個應用程式開啟了1234這個埠(Port),負責接收網際網路的需求,但是由於頻寬分享器本身的防火牆功能,並不允許外面的連線連到192.168.0.1:1234這個端點,故需要設定Port Forwarding,告訴頻寬分享器,如果收到要到Port 1234的要求,就轉到192.168.0.1的Port 1234。Port Forwarding最不方便的是,要設定內部轉到哪台IP,不能動態設定。典型用Port Forwarding的例子是:架在內部的Web Server,要能讓外部可以連接時,都會用Port Forwarding。



 

 Port Triggering
Port Triggering和Port Forward很類似,差別在於不用指定IP Address,試想想如果PC端是用DHCP,那麼指定IP的話,不是很麻煩? Port Triggering可說是Port Forwarding的進化版,只要指示Router,收到外部對哪個Port要求Request時,送到內部哪個Port即可。但是它應該也無法同時處理二台PC上同一個Port的要求,我沒有實驗過是不是真的不行。





 UPnP / NAT-PMP

UPnP是為了解決上面二種麻煩的設定而產生的,在有支援UPnP的軟硬體下,通常不用做任何設定,程式就可以直接設作業系統及軟體設備溝通,並建立連結。
NAT-PMP (NAT Port Mapping Protocol)是架構在UPnP下的更進階的協定,要用這功能,UPnP一定要打開,NAT-PMP神奇的地方是,它可以直接和Router溝通,可以把它想成自動在Router上設Port Forwarding,主要是由Apple提出,應用方面也以Apple的設備為主,但是不少軟體也有支援(比如BitComet)。

在Tomato Firmware上設定Port Forwarding、Port Triggering和UPnP


下圖表示如何在Tomato Firmware上設定Port Forwarding:


 範例--設定Port Forwarding:下面的例子為指定192.168.0.1:12185 TCP Port和192.168.0.1:9663 UDP 二個Port Forwarding,請注意畫面上的設定是沒打開(On)的,另外,設好後,右下角有個Save的按鈕,要按了以後才生效。




範例--設定Port Trigger:下面的例子為指定12185 TCP Port,9663 UDP Port,7263 TCP三個Port Triggering,這種設定方式是不用指定IP的,請注意畫面上的設定是沒打開(On)的,另外,設好後,右下角有個Save的按鈕,要按了以後才生效。


 


範例--設定UPnP/NAT-PMP:這個設定沒什麼好設的,只要確定有開啟就好,要用NAT-PMP的話,要先開UPnP,反正二個都打開就行,畫面上會顯示目前運作中的UPnP


 

理論上設有UPnP後,PC端應該就可以自動設定了,可是有時卻發現還是無效,原因可能是:
  • 你用Windows XP:UPnP需要手動開啟,請自行Google,Windows 7內定UPnP是開的
  • 軟體本身支援UPnP,但是Router卻沒開對應的Port:這可以手動設定


如何手動設定Router的UPnP對應 

以Bitcomet和eMule來說,二者都支援UPnP,但是打開UPnP後,BotComet可以連出去,但是eMule卻出現Low ID,在用Tomato前,用RT-10還出現二個程式即使是Port Triggering設好了,都不能動的狀況,應該可以想成這二支程式在當時,只設定好作業系統的UPnP映射?

手動設定Router的Port Mapping
以Windows 7為例,其中一個設定方式如下:




  • 打開網路和共用中心,點開下圖中的網路圖示:

 
網路結構下,應該會看到分享器(UPnP Router):




  • 按右鍵,選內容後,按下設定:


上面列出已經設定好的Mapping,其中BitComet二個是程式自己設的,eMule是我設的:


 

    為何eMule在它的程式中設定用UPnP後,還是出現Low ID? 因為它不支援NAT-PMP,無法主動設定Router上的Mapping,而BitComet有支援NAT-PMP,它會告訴Router相關的設定,這就是為何同樣支援UPnP,但是eMule設了卻沒用的原因。
     
  • 如果要新增的話,按下新增按鈕即可:





eMule和Port Forwarding / Port Triggering / UPnP
eMule雖然有UPnP的設定,可惜的是並不能正常運作,就連Port Triggering都有非常不穩定,所以最安全的方法,還是用最古老的Port Forwarding吧!

 

 

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;
    }
    =================================================