顯示具有 communication 標籤的文章。 顯示所有文章
顯示具有 communication 標籤的文章。 顯示所有文章

2012年2月28日 星期二

API Select怎麼用


前言
常在寫網路程式的人對select()這個API應該不莫生才對,或常寫伺服器端程式也應該很熟悉才對,它可以同時監看多個檔案的讀寫允許,而且檔案包含各種週邊元件以及網路的socket,這裏我將會介紹它經常被用的應用,以及我個人經驗中覺有點特別的用法分享給其他人參考參考。
Select函式原型
int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);
這個函數主要是用來同時監看數個檔案(包含socket),其中的事件在一定時間內是否有發生,而其中的事件有是否有資料可取得,是否可以寫入,以及其它錯誤事件,若在一段時間內沒有任何事件產生,則會回覆一個timeout的訊息,這個函數好用在它可以同時監看很多個檔案,以及時間,如此一來可以讓電腦更有效率以及可以同時處理多個檔案。
其中輸入的參數說明如下:
int nfds
對所要監看的所有file handle中最大的file handle號碼加1
fd_set *restrict readfds
相對監看該檔案是否有資料已收到或者已有資料可讀,只是確認有資可讀,但不知有多少資料可讀。可以使用FD_SET來設定相對需要確認的檔案,以及使用FD_ISSET來檢查確認的結果。
fd_set *restrict writefds
相對確認該檔案是否可以寫入或者有空間可寫入,只是確認可寫入,但是有多少空間可寫入,並不知道。可以使用FD_SET來設定相對需要確認的檔案,以及使用FD_ISSET來檢查確認的結果。
fd_set *restrict errorfds
相對確認該檔案是否有任何錯誤產生,可以使用FD_SET來設定相對需要確認的檔案,以及使用FD_ISSET來檢查確認的結果。
struct timeval *restrict timeout
這是設定timeout時間,若為NULL則是無限制時間監看及確認,其宣告如下:
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
}
以上最小時間單位為usec,但是在Linux中系統tick設定通常為10ms之下,通常最小時間可細分也只能到10ms,比這個小的時間其實是無法達到的。
其回傳值如下:
> 0 表示所以監看的檔案中有幾個檔案已符合條件。
= 0 表示未任何事件發生並且已經timeout。
< 0 表示有錯誤產生。
其它會用到的巨集指令
void FD_CLR(int fd, fd_set *set);
清除掉該檔案相對檔案的設定,也就是取消對該檔案的監看。
int  FD_ISSET(int fd, fd_set *set);
檢查相對該檔案是否為真,就是對該檔案所監看的結果,確認是否為真,若是為真即表示該事件有發生,若為否則表示該事件未發生。
void FD_SET(int fd, fd_set *set);
設定監看該檔案行為。
void FD_ZERO(fd_set *set);
清除所有檔案的監看及設定。
正常使用
在Linux中任何週邊都是使用檔案來使用,所以在select中的所使用file handle可以是網路socket open來的,也可以是open device node所得來的,也可以是真正的檔案,所以以下為一個正常使用的範例:
int fd1=soekcet(…)
int fd2=open(“device-node-name”, …);
int maxfd;
fd_set readfds, writerfds;
struct timeval timev;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
if ( fd1 > fd2 )
maxfd = fd1;
else
maxfd = fd2;
FD_SET(fd1, &readfds);
FD_SET(fd2, &readfds);
FD_SET(fd1, &writefds);
FD_SET(fd2, &writefds);
timev.tv_sec = 3;
timev.tv_usec = 0;
if ( select(maxfd, &readfds, &writefds, NULL, &timev) ) {
if ( FD_ISSET(fd1, &readfds) ) {
// fd1有資料可讀
}
if ( FD_ISSET(fd2, &readfds) ) {
// fd2 有資料可讀
}
if ( FD_ISSET(fd1, &writefds) ) {
// fd1 可寫入
}
if (FD_ISSET(fd2, &writefds) ) {
// fd2 可寫入
}
} else {
// timeout or error 
}
以上的寫法在應用有許多好處,一是可以同時處理許多檔案,並且可以隨時加入或退出,二是使用事作觸發讓系統的負載不會過重,並且也可兼顧時效性。
短時間休眠用
前面所提是一般正常的使用方式,有一種另外的使用場合,那是因為在Linux的sleep function中只有秒級的單位,若我們想要實現msec的sleep則無法做到(注:現在在real time中可以有usleep),則可以利用select的timeout機制來作sleep的msec等級,其寫法如下:
struct timeval timev;
timev.tv_sec = 0;
timev.tv_usec = 10000; // 10msec
select(1, NULL, NULL, NULL, &timev);

2009年10月28日 星期三

如何撰寫一個ethernet實體層的driver

ethernet現在已成為很多設備的主要通訊介面,因為它除了快速以及便宜之外,更可以讓它可以很容易架設網路,使多個設備可以互相通訊,這是網路通訊七層中最為底層的device driver, 它必須和硬體相關聯, 而現在的SoC中通常都會將這個通用元件SoC進來, 而如果你將這個driver完成將會讓網路的其它通訊堆疊馬上可以使用, 因以從Link層開始都會是屬於軟體的事情和硬體無關, 所以這部分也是Linux中最強最穩定的程式, 所以你就可以不用撰寫這部分的程式, 你只要專心地將ethernet完成即可。首先我們先來看網路的軟體架構:















以下為這個驅動程式的撰寫重點 :
  • 驅動程式的進入點, 則依照Linux驅動程式的標準寫法, 若是PCI bus則在初始化時依照PCI client驅程式的登錄方式, 若是SoC則依照platform driver的登錄方式。以下是一個範例說明:
static struct platform_driver victor_mac_driver = {
.driver.name = DRV_NAME,
.probe = victor_mac_probe,
.remove = victor_mac_remove,
};

static int __init victor_mac_module_init(void)
{
printk("%s \n",__FUNCTION__);
return platform_driver_register(&victor_mac_driver);
}
module_init(victor_mac_module_init);
  • 當你登錄完畢後則會進入probe的進入點, 在這裏我們需要初始化ethernet device driver。而在probe callback function中會做以下幾件事:
  1. 取得硬體資源 (memory & IRQ no), 並且將其做ioremap以取得virtual address
  2. 初始化硬體, 並註冊interrupt service routine
  3. allocate ethernet device structure, register a ethernet device
以下是範例程式:
static const struct net_device_ops victor_netdev_mac_ops = {
.ndo_init = victor_mac_init,
.ndo_open = victor_mac_open,
.ndo_stop = victor_mac_stop,
.ndo_start_xmit = victor_mac_start_xmit,
.ndo_get_stats = victor_mac_get_stats,
.ndo_set_multicast_list = victor_mac_set_mcast_list,
.ndo_do_ioctl = victor_mac_ioctl,
.ndo_change_mtu = eth_change_mtu,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
};
struct net_device *ndev;
struct resource *res;
ndev = alloc_etherdev(sizeof(victor_gmac_priv_t));
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ndev->netdev_ops = (struct net_device_ops *)&victor_netdev_mac_ops;
retval = register_netdev(ndev);

而以上在註冊ethernet device driver時會有幾個call back function需要給予撰寫, 其說明如下:

ndo_init
這是在註冊ethernet driver時馬上會被呼叫的call back function, 在此可以做一些初始化的動作

ndo_open
當使用者up該網路介面時會呼叫這個call back function, 通常會在此啓動該ethernet的硬體動作

ndo_stop
當使用者down該網路介面時會呼叫這個call back function, 通常會在此停止該ethernet的硬體動作

ndo_start_xmit
這是網路最後要將資料在硬體線路傳輸, 在這裏該driver不用對內容做任何更動, 而source MAC address通常是由硬體自動填入, 另外有一個很重要的事, 就是網路傳輸由開始者 (TCP or UDP) allocate skb_buff來儲存資料, 而在ethernet driver在送至硬體時需要free該skb_buff, 若沒有做這個動作將會由系統的記憶體越來越少

ndo_get_stats
由此來取得系統所定統計資料, 統計資籵必須由driver自行記錄, 並非由系統協助記錄

ndo_set_multicast_list
設定multicast MAC位址,驅動程式必須將這些位址設至硬體

ndo_do_ioctl
這是對於該元件的ioctl,通常一些運用程式會使用,如bridge程式就會使用,又如設定phy時也會使用。

ndo_change_mtu
設定網路的mtu值,通常是不會更動。

ndo_set_mac_address
設定該網路介面的MAC位址,通常MAC位址都是在初始化時讀取設定在硬體中,所以不會是任意更改,因為MAC位址是全世界上有所管制的,必須是唯一性。

ndo_validate_addr
設定validate MAC address

撰寫好以上各個call back function之後就是interrupt service routine, 而在interrupt service routine中就是要處理收和送的資料, 這部分和IC硬體有很大的關連, 隨著硬體而有所不同, 所以必須要好好地研讀硬體的軟體說明書.