網卡驅動詳解

 

當網絡上一臺計算機準備發送數據時,他的網卡開始工作了,首先網卡的芯片偵聽在網絡上是否有數據在流動,如果沒有,他就把數據發送到網絡上,在偵聽和發送之間有一段極小的時間延遲,在這段時間內,也有可能在網絡上有其他的計算機也準備發送數據,也偵聽到網絡上沒有數據在流動,這就可能兩臺甚至多臺的數據一起發送到網絡上,產生數據的碰撞,發送數據的計算機的網卡芯片當然要在發送完成後再校驗返回的數據,如果發現和發送的數據不一致,那就是說產生了碰撞,所以在一個以太網絡中的計算機數量不宜過多,他不但會增加廣播包在網絡中的數量,也請也會增加數據包的碰撞次數.

  我們的計算機的網卡芯片在接收到一完整的數據包後,芯片的一引腳通知8259中斷控制器,中斷控制器再發出中斷給CPU,由此,CPU隨即調用該網卡的中斷例程,:

DOS是這樣的
屏蔽所有中斷(cli)
push any register
因爲中斷向量在段0
所以xor ax,ax
mov ds,ax
mul ax,
中斷號

  那麼在數據段的[ax]偏移處是該中斷例程的指針了call [ax]就到該中斷例程了...(DOS是比較遙遠的事情了,我所描述的是他的原理,當然不會這麼簡單,如果那位網友有興趣詳細描述一下上面的原理,糾正或替換掉我所寫的就感激不盡了)

  總之,在本例程中,CPU將調用elintr中斷例程,並帶有參數unit即該種網卡的第幾塊(因爲在計算機中,你有可能裝了相同的網卡有幾塊),elintr的作用是把數據從網卡的數據存儲器中讀到我們在該網卡初始化時預先分配好的數據緩衝區中,他調用的函數就只有elread,同樣elread也只調用了elget一個函數.elread函數比較簡單,就是調用elget,elget則相對比較複雜一點,涉及到核心內存分配mbuf,mbuf是比較恐怖的東西,正如STEVEN所寫的,爲了節約當時"巨大"4M內存,犧牲了性能搞出了這個mbuf東東,mbuf是必須要弄懂的,雖然在設備驅動程序中調用他的宏和函數不多,但在後面的IP協議,TCP協議中有不少涉及的地方.

  關於數據發送方面和接收差不多,在上層協議放置好數據到mbuf鏈後,調用el_start函數,該函數把mbuf鏈中的數據放置到本塊網卡的發送隊列緩衝el_pktbuf,然後再調用el_xmit函數,此函數把發送隊列緩衝el_pktbuf中的數據有傳遞到網卡的數據存儲器中.我認爲,這中間的內存拷貝是多於的,應該在el_start函數中直接把mbuf中的數據傳遞到網卡的數據存儲器中,這樣會使性能有較大幅度的提升,因爲在驅動程序設計時,最好減少大量的內存拷貝,他佔用的時間太多了.

*/
/* FreeBSD
3COM以太網設備驅動程序 */
/*
本段頭文件是在編譯核心時產生的*/

#include "el.h" /*此三文件爲編譯時產生的頭文件,內容是定製核心的一些常量*/
#include "opt_inet.h"
#include "opt_ipx.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net/bpf.h>
#include <machine/clock.h>
#include <i386/isa/isa_device.h>
#include <i386/isa/if_elreg.h>/*
此頭文件是3COM卡的寄存器常量*/
/*
爲了調試方便 */
#ifdef EL_DEBUG
#define dprintf(x) printf x /*
如果定義了DEBUG調試,則打印到屏幕*/
#else
#define dprintf(x)
#endif
/* softc
結構,每種網卡的該結構是不同的,主要是該第一個成員必須是一以太網的共用結構arpcom*/
static struct el_softc {
struct arpcom arpcom; /*
以太網公共部分 */
u_short el_base; /*
基本輸入輸出地址 */
char el_pktbuf[EL_BUFSIZ]; /*
幀緩衝大小2048 */
} el_softc[NEL]; /*NEL
el.h中定義,即編譯時產生的頭文件,意思爲支持的網卡數*/
/*
看看arpcom結構吧
*
該結構是以太網設備驅動程序和ARP程序所共享.
struct arpcom {
/*
* ifnet
結構必須在此結構的第一個位置.
/
struct ifnet ac_if;
u_char ac_enaddr[6]; /*
以太網硬件地址/
int ac_multicnt; /*
多播地址列表數 /
void *ac_netgraph; /* netgraph
節點信息,即我們所說的PPPoE,也就是ADSL寬帶所用到的 /
};

*/
/*
一些函數申明 */
static int el_attach(struct isa_device *);/*
第二步:填充相關的數據結構*/
static void el_init(void *); /*
不用說,是初始化,probe,attach之後被調用*/
static int el_ioctl(struct ifnet *,u_long,caddr_t);/*
控制網卡的函樹指針*/
static int el_probe(struct isa_device *);/*
第一步:探測程序.查看是否卡存在和是否在正確的位置.*/
static void el_start(struct ifnet *);/*
把數據包從硬件接口輸出去*/
static void el_reset(void *);/*
該例程重設接口. el_watchdog()中調用*/
static void el_watchdog(struct ifnet *);/*
一般該函數用於包在一定時間內沒發送出去,就調用他,
本驅動程序中並不支持該函數,在我的rtl8139說明中有*/
static void el_stop(void *);/*
停止接口,el_ioctl()el_reset()中調用*/
static int el_xmit(struct el_softc *,int);/*
把數據包放到芯片內,發送到以太網上*/
static ointhand2_t elintr;/*
中斷例程*/
static __inline void elread(struct el_softc *,caddr_t,int);/*
傳遞包到更高一級協議處理,ether_input()例程.elintr()調用 */
static struct mbuf *elget(caddr_t,int,struct ifnet *); /*
從網卡上下載數據包,len是數據的長度,本地以太網頭部被分開*/
static __inline void el_hardreset(void *);/*
這是一個子程序,目的是重設硬件.*/
/* isa_driver
結構 autoconf準備 */
/* isa_driver
結構說明:
該結構來之於文件isa_device.h頭文件

 

 

 

搜狐博客 > ㈧糟dē > 日誌

2009-06-25 | Linux網卡驅動程序詳解(2)

結構成員:
/*
*
通用的設備驅動程序結構.
*
*
沒一設備驅動程序定義一組例程入口,由設置程序在啓動時使用.
struct isa_driver {
int (*probe) __P((struct isa_device *idp));
/*
測試設備是否存在
int (*attach) __P((struct isa_device *idp));
/*
爲設備設置驅動程序
char *name; /*
設備名稱
int sensitive_hw; /*
探測發生有衝突時爲真,ISA設備的老毛病
};
*/
struct isa_driver eldriver = {
el_probe, el_attach, "el"
};

/* 探測程序.查看是否卡存在和是否在正確的位置. */
static int
el_probe(struct isa_device *idev)
{
/*
isa_device
是設備的通用結構,該結構說明在isa_device.h頭文件中,說明如下:
struct isa_device {
int id_id; /*
設備的 id
struct isa_driver *id_driver;
指向設備的驅動程序結構
int id_iobase; /*
基本IO地址
int id_iosize; /*
基本IO地址的長度
u_int id_irq; /*
中斷
int id_drq; /* DMA
caddr_t id_maddr; /*
在總線中的物理IO內存地址(即便要)
int id_msize; /* IO
內存的大小
union {
inthand2_t *id_i;
ointhand2_t *id_oi;
中斷例程指針
} id_iu; /*
中斷接口例程
#define id_intr id_iu.id_i
#define id_ointr id_iu.id_oi
int id_unit; /*
在該類設備中是第幾個
int id_flags; /* flags
int id_enabled; /*
設備激活了嗎
struct isa_device *id_next; /*
userconfig()中用於isa_devlist
struct device *id_device;
};
*/
struct el_softc *sc;
u_short base; /*
僅僅爲了方便 */
u_char station_addr[ETHER_ADDR_LEN];/*
以太網的硬件地址*/
int i;
/*
蒐集一些信息 */
sc = &el_softc[idev->id_unit];/*sc
softc結構,如果你有NELel卡的話就有NELsoftc
結構,當然也有可能同時還有其他的xx_softc結構*/
sc->el_base = idev->id_iobase;/*
該塊卡的基本I/O地址*/
base = sc->el_base;/*
有一點多餘,只是爲了方便下面的引用*/
/*
第一次檢查地址,看看基本地址是否在0X2800X3F0之內 */
if((base < 0x280) || (base > 0x3f0)) {
printf("el%d: ioaddr must be between 0x280 and 0x3f0n",
idev->id_unit);
return(0);
}
/*
現在嘗試從PROM中獲取地址,看看是否包含了3COM供應商的標識代碼.
*/
dprintf(("Probing 3c501 at 0x%x...n",base));/*
在調試時會打印出*/
/*
重置板卡 */
dprintf(("Resetting board...n"));
outb(base+EL_AC,EL_AC_RESET);/*
我們一般定義基地址爲0X300,EL_AC=0E,是輔助命令寄存器*/
DELAY(5);/*
延遲5毫秒*/
outb(base+EL_AC,0);
dprintf(("Reading station address...n"));
/*
讀硬件地址,共六次 */
for(i=0;i<ETHER_ADDR_LEN;i++) {
outb(base+EL_GPBL,i);
station_addr = inb(base+EL_EAW);/*EL_EAW
是該卡的地址口,station_addr是函數內部變量,
下面判斷了生產廠家後就沒用的*/
}
dprintf(("Address is %6Dn",station_addr, ":"));
/*
如果廠商標識代碼正確,那麼返回1.
*/
if((station_addr[0] != 0x02) || (station_addr[1] != 0x60)
|| (station_addr[2] != 0x8c)) {
dprintf(("Bad vendor code.n"));/*3COM
廠商此種卡的代碼爲02608C*/
return(0);
} else {
dprintf(("Vendor code ok.n"));
/*
把地址拷貝到arpcom結構中 */
bcopy(station_addr,sc->arpcom.ac_enaddr,ETHER_ADDR_LEN);
return(1);
}
}
/*
這是一個子程序,目的是重設硬件. el_init()中調用,elintr()中調用,產生中斷,有溢出發生時調用*/
static __inline void
el_hardreset(xsc)
void *xsc;
{
register struct el_softc *sc = xsc;/*
記住在C,寄存器變量只能有三個,可加快速度*/
register int base;
register int j;
base = sc->el_base;
/*
第一步,重設板卡,el_probe中的一樣(前面) */
outb(base+EL_AC,EL_AC_RESET);
DELAY(5);
outb(base+EL_AC,0);
/*
又把地址填回去,爲什麼?沒有爲什麼,就是廠商規定的,一些端口填什麼數據時會怎麼樣,只有廠商知道,我相信,在同一廠商之間的網卡,交換,路由器進行祕密通訊是非常可能的,他可以不返回到CPU*/
for(j=0;j<ETHER_ADDR_LEN;j++)
outb(base+j,sc->arpcom.ac_enaddr[j]);
}
/*
連接該接口到核心數據結構.被調用時,我們已經知道該卡已經存在在給定的I/O
*
地址,我們還假定中斷號是正確的.
*/
static int
el_attach(struct isa_device *idev)
{
struct el_softc *sc;
struct ifnet *ifp;/*
該結構是一個巨大的結構,STEVEN的書中有描述,我也寫了一篇*/
u_short base;/*
沒用上,可以去掉*/
dprintf(("Attaching el%d...n",idev->id_unit));
/*
放置一些指針. */
idev->id_ointr = elintr;/*
放置中斷例程指針,中斷例程在下面*/
sc = &el_softc[idev->id_unit];/*
定位本設備的softc結構指針*/
ifp = &sc->arpcom.ac_if;/*
定位ifnet結構*/
base = sc->el_base;/*
從程序來看,這一句可以去掉,根本沒用,因爲在該函數中沒用到base*/
/*
重設板卡 */
dprintf(("Resetting board...n"));
el_hardreset(sc);/*
該程序在上面*/
/*
初始化ifnet結構,該結構的成員經常用來被ether網子程序,arp,bridge等調用 */
ifp->if_softc = sc;/*
該網卡的IFP(通用接口結構)的專用結構指針(softc結構)*/
ifp->if_unit = idev->id_unit;/*
第幾塊網卡*/
ifp->if_name = "el";/*
網絡卡的名稱*/
ifp->if_mtu = ETHERMTU;/*1500*/
ifp->if_output = ether_output;/*
以太網的輸出子程序指針(不要搞錯了,是向IP層輸出,按我們的理解是數據輸入了,再轉送到上一層協議)*/
ifp->if_start = el_start;/*
把數據包從硬件接口輸出去*/
ifp->if_ioctl = el_ioctl;/*
控制網卡的函樹指針*/
ifp->if_watchdog = el_watchdog;/*
一般該函數用於包在一定時間內沒發送出去,就調用他,在本驅動程序中並不支持該函數,在我的rtl8139說明中有*/
ifp->if_init = el_init; /*
不用說,是初始化,probe,attach之後被調用*/
ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX);/*
支持廣播和單播*/
/*
調用通用以太網初始化例程 */
dprintf(("Attaching interface...n"));
ether_ifattach(ifp, ETHER_BPF_SUPPORTED);
/*
if_ethersubr.c中的ether_ifattach例程
void ether_ifattach(ifp, bpf)
調用時,ETHER_BPF_SUPPORTEDBSD的包過濾器,如果在編譯時設置文件沒有打開包過濾器,那麼代表0,否則是1
register struct ifnet *ifp;
int bpf;
{
register struct ifaddr *ifa;
register struct sockaddr_dl *sdl;
if_attach(ifp);
此例程在if.c
ifp->if_type = IFT_ETHER;
代表以太網
ifp->if_addrlen = 6;
硬件地址長度是6
ifp->if_hdrlen = 14;
包的頭長度是6+6+2=14,其中2是協議類型
ifp->if_mtu = ETHERMTU;
1500,多此一舉,在前面你可看到,已經填充了.
ifp->if_resolvemulti = ether_resolvemulti;
以太網解析多播例程指針
if (ifp->if_baudrate == 0)
波特率
ifp->if_baudrate = 10000000;
ifa = ifnet_addrs[ifp->if_index - 1];
ifnet_addrs[]數組中找到本地址指針
KASSERT(ifa != NULL, ("%s: no lladdr!n", __FUNCTION__));
sdl = (struct sockaddr_dl *)ifa->ifa_addr; ifa->ifa_addr
在此時指向的是sockaddr_dl結構.
sdl->sdl_type = IFT_ETHER;
sdl->sdl_alen = ifp->if_addrlen;
bcopy((IFP2AC(ifp))->ac_enaddr, LLADDR(sdl), ifp->if_addrlen);
把硬件地址拷貝到sdl結構中if (bpf) bpf爲真,即加入了BSD包過濾
bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header));
if (ng_ether_attach_p != NULL)
(*ng_ether_attach_p)(ifp);
}
*/
printf("el%d: 3c501 address %6Dn",idev->id_unit,
sc->arpcom.ac_enaddr, ":");
dprintf(("el_attach() finished.n"));
return(1);
}
/*
該例程重設接口. el_watchdog()中調用,因爲watchdog不在本驅動程序中支持,所以從不被調用*/
static void
el_reset(xsc)/*
上面的一個函數,重設硬件*/
void *xsc;
{
struct el_softc *sc = xsc;
int s;
dprintf(("elreset()n"));
s = splimp();/*
關網絡中斷*/
el_stop(sc);/*
下面的一個函數*/
el_init(sc);/*
重新初始化卡*/
splx(s);/*
開網絡中斷*/
}
/*
停止接口,el_ioctl()el_reset()中調用*/
static void el_stop(xsc)
void *xsc;
{
struct el_softc *sc = xsc;
outb(sc->el_base+EL_AC,0);/*
0寫輔助命令寄存器*/
}
/*
初始化接口. */
static void
el_init(xsc)
void *xsc;
{
struct el_softc *sc = xsc;
struct ifnet *ifp;
int s;
u_short base;
ifp = &sc->arpcom.ac_if;/*
定位ifnet結構*/
base = sc->el_base;/*
網卡基本I/O地址*/
/*
如果地址不知道,什麼也不做. */
if(TAILQ_EMPTY(&ifp->if_addrhead)) /*
if.c中的if_attach例程中已經填充,el_attach調用
ether_attach
時再調用if_attach */
return;
s = splimp();/*
關網絡中斷*/

 

 


搜狐博客 > ㈧糟dē > 日誌

2009-06-25 | Linux網卡驅動程序詳解(3)

/* 重設板卡. */
dprintf(("Resetting board...n"));
el_hardreset(sc);/*
該函數在上面,重設硬件*/
/*
設置接收寄存器 rx */
dprintf(("Configuring rx...n"));
if(ifp->if_flags & IFF_PROMISC) /*
是混雜模式?EL_RXC0X6接收命令寄存器*/
outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
else
outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
outb(base+EL_RBC,0);/*
接收緩衝寄存器清0*/
/*
設置傳輸寄存器 TX */
dprintf(("Configuring tx...n"));
outb(base+EL_TXC,0);
/*
開始接收 */
dprintf(("Starting reception...n"));
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/*EL_AC_IRQE
IRQ enable(可用) EL_AC_RX爲接收寄存器*/
/*
設置一些開始使用的標誌 */
ifp->if_flags |= IFF_RUNNING;/*
加上正在運行標誌*/
ifp->if_flags &= ~IFF_OACTIVE;/*
去掉正在傳輸標誌*/
/*
調用輸出. */
el_start(ifp);
splx(s);/*
開網絡中斷*/
}
/*
開始在接口上輸出.從隊列中得到包並輸出他們,在輸出中,留出接收用一部分時間,即打開中斷再關閉中斷,這樣使接口接到的一些數據包不會丟失.
*/
static void
el_start(struct ifnet *ifp)
{
struct el_softc *sc;
u_short base;
struct mbuf *m, *m0;
int s, i, len, retries, done;
/*
定位softc結構的指針*/
sc = ifp->if_softc;
base = sc->el_base;/*
基地址在輸入輸出指令時常要用到*/
dprintf(("el_start()...n"));
s = splimp();/*
因爲下面涉及到if_flags的操作,所以要關閉網絡中斷*/
/*
如果輸出正在進行,則退出 */
if(sc->arpcom.ac_if.if_flags & IFF_OACTIVE)
return;
sc->arpcom.ac_if.if_flags |= IFF_OACTIVE;/*
加上輸出正在進行傳輸標誌*/
/*
主循環
*/
while(1) {/*
唯一出口是準備傳輸的數據爲空,m0==NULL*/
/*
從隊列中移出下一數據包到m0,請看頭文件if_var.h
#define IF_DEQUEUE(ifq, m) {
(m) = (ifq)->ifq_head; ifq
是一mbuf指針隊列,把第一個mbuf指針放到m
if (m) {
if (((ifq)->ifq_head = (m)->m_nextpkt) == 0)
重排隊列
(ifq)->ifq_tail = 0;
(m)->m_nextpkt = 0;
(ifq)->ifq_len--;
}
}

*/
IF_DEQUEUE(&sc->arpcom.ac_if.if_snd,m0);/* &sc->arpcom.ac_if.if_snd
指向發送隊列,該宏取出第一個mubf的指針放到m0,看上面的說明.
這是數據結構的好教材*/
/*
如果發送緩衝區指針爲空,即已經發送完,則退出,此是該無窮循環的唯一出口. */
if(m0 == NULL) {
sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE;/*
出去前當然要去掉輸出正在進行標誌*/
splx(s);
return;
}
/*
關閉接收 */
outb(base+EL_AC,EL_AC_HOST);/*EL_AC_HOST
爲系統總線可訪問緩衝,即系統總線網卡要用 */
outb(base+EL_RBC,0);/*
接收緩衝寄存器清0*/
/*
拷貝mbuf中的數據到softc結構定義的成員el_pktbuf,緩衝大小是EL_BUFSIZ2048. */
len = 0;
for(m = m0; m != NULL; m = m->m_next) { /* m0
是一mbuf指針,也是一mbuf鏈的第一個,
次要發送的是一mbuf,不是一單個mbuf*/
if(m->m_len == 0)
continue;
bcopy(mtod(m,caddr_t),sc->el_pktbuf+len,m->m_len);/*m->len
是該mbuf鏈的數據長度,sc->el_pktbuf是該卡的發送臨時緩衝,要發送的數據在這集中,然後傳送到網卡上,太費時間了,應該直接放置到網卡的存儲器中.*/
len += m->m_len; /*len
是這次要發送的總數*/
}
m_freem(m0); /*
釋放該mbuf*/
len = max(len,ETHER_MIN_LEN); /*ETHER_MIN_LEN
是發送的最小長度*/
/*
如果有BPF,就交給BPF驗證 */
if(sc->arpcom.ac_if.if_bpf)
bpf_tap(&sc->arpcom.ac_if, sc->el_pktbuf, len);/*
你當然可以在這寫一點自己的驗證過程*/
/*
傳送數據包到板卡 */
dprintf(("el: xfr pkt length=%d...n",len));
i = EL_BUFSIZ - len;/*EL_BUFSIZ=2048
字節*/
outb(base+EL_GPBL,(i & 0xff)); /*
告訴發送的長度*/
outb(base+EL_GPBH,((i>>&0xff));
outsb(base+EL_BUF,sc->el_pktbuf,len);/*
傳輸數據到板卡*/
/*
開始發送數據包 */
retries=0;/*
下面做循環用的,在發不出去時,循環15*/
done=0; /*done=1
時發送成功了*/
while(!done) {
if(el_xmit(sc,len)) { /*
調用發送例程,其實只要傳送base就可以了 */
done = -1;
break;
}
/*
檢查輸出後的狀態,如果你要對watchdog支持,可以在這加上代碼,即在5毫秒沒發送出去,就調用el_watchdog() */
i = inb(base+EL_TXS);
dprintf(("tx status=0x%xn",i));
if(!(i & EL_TXS_READY)) { /*
如果傳輸狀態寄存器不是準備接受新幀就緒 */
dprintf(("el: err txs=%xn",i)); /*
那就是出錯了*/
sc->arpcom.ac_if.if_oerrors++;
if(i & (EL_TXS_COLL|EL_TXS_COLL16)) {/*
網絡數據包碰撞*/
if((!(i & EL_TXC_DCOLL16)) && retries < 15) {/*
做循環的目的是爲了有錯誤時可重傳15*/
retries++;
outb(base+EL_AC,EL_AC_HOST);/*EL_AC_HOST
爲系統總線可訪問緩衝 */
}
}
else
done = 1;
}
else {
sc->arpcom.ac_if.if_opackets++;/*
統計用的,說明該卡成功發送一包*/
done = 1;
}
}
if(done == -1) /*
包沒有傳輸(失敗) */
continue;
/*
現在給板卡一個機會接收.注意:linux中曾經ALEN先生批評此卡好恐怖(他說要進博物館,哈哈),並說在傳輸時會丟失進來的數據包,我查看了LINUX的驅動程序,確實是這樣,但在FreeBSD,給了一個機會,由此可證明他的關於"丟失包的說法不一定成立",但關於一個緩衝和一次只能發送一數據包的說法確實是真的,還有多播方面也不支持,我也希望大家最好不要去買這東西,NE2000,PCI中的RTL8139芯片的網卡一樣,性能太差了.*/
(void)inb(base+EL_AS);/*
讀輔助狀態寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/*
IRQ(中斷)使能和接收 寫輔助命令寄存器*/
splx(s);
/*
這有可能接收到中斷(包到達) */
s = splimp();
}
}
/*
這是真正的傳輸包,el_start()調用
*/
static int
el_xmit(struct el_softc *sc,int len)
{
int gpl;
int i;
gpl = EL_BUFSIZ - len;
dprintf(("el: xmit..."));
outb((sc->el_base)+EL_GPBL,(gpl & 0xff));
outb((sc->el_base)+EL_GPBH,((gpl>>&0xff));
outb((sc->el_base)+EL_AC,EL_AC_TXFRX);/*
真正的傳送指令*/
i = 20000;
while((inb((sc->el_base)+EL_AS) & EL_AS_TXBUSY) && (i>0))/*
如果傳送還在忙,循環20000次等待*/
i--;
if(i == 0) {/*
這裏有一個bug,大家發現沒有,i到了0時也有可能傳送成功,解決辦法是把(i>0)這條件放到前面*/
/*
我稍微講一下C,在編譯C程序時,while ( (a>b) && (i>0) ),是這個樣子
top:if a>b then
if i>0 then
執行體
endif
endif
goto top

 

 

 

 

 

 

也就是說,i=0時候,inb((sc->el_base)+EL_AS)這指令還會執行,也有可能這時候傳送完成了,而下面有給打出一個什麼"tx not ready"的東東,而且返回失敗,有得重新傳送一次.

*/
dprintf(("tx not readyn"));
sc->arpcom.ac_if.if_oerrors++;
return(-1);
}
dprintf(("%d cycles.n",(20000-i)));
return(0);/*
成功*/
}
/*
傳遞包到更高一級協議處理,ether_input()例程.elintr()調用 */
static __inline void
elread(struct el_softc *sc,caddr_t buf,int len)
{
register struct ether_header *eh;
struct mbuf *m;
eh = (struct ether_header *)buf;/*
buf中分出以太網頭部*/
/*
* elget
函數是把包放入一mbuf緩衝鏈中
*/
m = elget(buf,len,&sc->arpcom.ac_if);
if(m == 0)/*
出錯了*/
return;
ether_input(&sc->arpcom.ac_if,eh,m);/*
傳輸給上一層的包括ifnet結構,以太網頭部,mbuf*/
}
/*
中斷例程 */
static void
elintr(int unit)
{
register struct el_softc *sc;
register int base;
int stat, rxstat, len, done;
/*
尋址softcI/O基地址 */
sc = &el_softc[unit];
base = sc->el_base;
dprintf(("elintr: "));
/*
檢查板卡狀態 */
stat = inb(base+EL_AS);
if(stat & EL_AS_RXBUSY) {/*
接收忙*/
(void)inb(base+EL_RXC);/*
讀接收命令寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/*
IRQ(中斷)使能和接收 寫輔助命令寄存器*/
return;
}
done = 0;
while(!done) {
rxstat = inb(base+EL_RXS);
if(rxstat & EL_RXS_STALE) {/*EL_RXS_STALE
代表接受狀態沒有改變*/
(void)inb(base+EL_RXC);/*
讀接收命令寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/*
IRQ(中斷)使能和接收 寫輔助命令寄存器*/
return;
}
/*
如果這有一個溢出發生,重新初始化板卡. */
if(!(rxstat & EL_RXS_NOFLOW)) {
dprintf(("overflow.n"));
el_hardreset(sc);
/*
使板卡回到接收模式 */
if(sc->arpcom.ac_if.if_flags & IFF_PROMISC)
outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
else
outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
(void)inb(base+EL_AS);/*
讀輔助狀態寄存器*/
outb(base+EL_RBC,0);/*
清除接收緩衝*/
(void)inb(base+EL_RXC);/*
讀接收命令寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/*
IRQ(中斷)使能和接收 寫輔助命令寄存器*/
return;
}
/*
到這應該是進來了一數據包 */
len = inb(base+EL_RBL);
len |= inb(base+EL_RBH) << 8;/*
包長度的高低位組合爲該包的長度*/
dprintf(("receive len=%d rxstat=%x ",len,rxstat));
outb(base+EL_AC,EL_AC_HOST);/*EL_AC_HOST
爲系統總線可訪問緩衝 */
/*
如果包太短或太長,回到接收模式並返回
*/
if((len <= sizeof(struct ether_header)) || (len > ETHER_MAX_LEN)) {/*
如果包小於以太網頭部的長度或大於最大長度*/
if(sc->arpcom.ac_if.if_flags & IFF_PROMISC)/*
爲重置硬件準備if_flags*/
outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
else
outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
(void)inb(base+EL_AS);/*
讀輔助狀態寄存器*/
outb(base+EL_RBC,0);/*
清除接收緩衝*/
(void)inb(base+EL_RXC);/*
讀接收命令寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/*
IRQ(中斷)使能和接收 寫輔助命令寄存器*/
return;
}
sc->arpcom.ac_if.if_ipackets++;/*
統計使用,說明接收包總數*/
/*
拷貝數據到我們的緩衝 */
outb(base+EL_GPBL,0);
outb(base+EL_GPBH,0);
insb(base+EL_BUF,sc->el_pktbuf,len);/*
從端口讀一串數據到指定地址()*/
outb(base+EL_RBC,0);
outb(base+EL_AC,EL_AC_RX);
dprintf(("%6D-->",sc->el_pktbuf+6,":"));/*
也放置到el_pktbuf,發送也放在他中,在發送時有一個開中斷接數據包的過程不過那時候el_pktbuf中沒有數據,不會相互影響.*/
dprintf(("%6Dn",sc->el_pktbuf,":"));
/*
把數據傳遞到上一層 */
len -= sizeof(struct ether_header);
elread(sc,(caddr_t)(sc->el_pktbuf),len);
/*
對狀態? */
stat = inb(base+EL_AS);
/*
如果忙不爲真則繼續 */
if(!(stat & EL_AS_RXBUSY))
dprintf(("<rescan> "));
else
done = 1; /*
退出循環*/
}
(void)inb(base+EL_RXC);
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));
return;
}
/*
*
從網卡上下載數據包,len是數據的長度,本地以太網頭部被分開
*/
static struct mbuf *
elget(buf, totlen, ifp)/*
elread調用,buf是指向sc->el_pktbuf緩衝區,並且數據已經存在,
totlen
是整個數據包長度-sizeof(struct ether_header)即以太網頭部的長度*/
caddr_t buf;
int totlen;
struct ifnet *ifp;
{
struct mbuf *top, **mp, *m;
int len;
register caddr_t cp;
char *epkt;
buf += sizeof(struct ether_header);/*
調用前buf指向...請看下圖
|--------------------------------
整個數據----------------------------------------------|
|--ether
頭部14字節----|--------IPARP或其他協議頭部及數據-----------------------------|
^
調用前buf指向這
^
執行之後buf指向這
因爲在向上傳遞數據的過程是一層一層的剝,在本次要剝掉ether_header即以太網頭部
*/
cp = buf;/*
放入寄存器中*/
epkt = cp + totlen;/*epkt
在計算後指向數據的尾部*/
MGETHDR(m, M_DONTWAIT, MT_DATA);/*
得到一標記了頭部的mbuf*/
/*MGETHDR
宏說明
#define MGETHDR(m, how, type) do {
struct mbuf *_mm;
int _mhow = (how);
int _mtype = (type);
int _ms = splimp();
屏蔽中斷
if (mmbfree == NULL) mmbfree
是內存管理初始化時的全局變量,意思是還有可用的內存塊嗎?
(void)m_mballoc(1, _mhow);
沒有就分配一個,1代表分配一個MSIZE大小的塊,該函數調用kmem_malloc核心內存分配函數,返回的可用mbuf指針在mmbfree
_mm = mmbfree;
if (_mm != NULL) {
mmbfree = _mm->m_next;
如果上面的m_mballoc函數執行了,_mm->m_next肯定爲NULL
mbtypes[MT_FREE]--;
_mm->m_type = _mtype;
看上下文可知,_mtypeMT_DATA
mbtypes[_mtype]++;
_mm->m_next = NULL;
從這開始是初始化mbuf一些指針
_mm->m_nextpkt = NULL;
_mm->m_data = _mm->m_pktdat;
_mm->m_flags = M_PKTHDR;
加入mbuf鏈首標誌,即該鏈的第一個包,該宏和MGET的不同之處
_mm->m_pkthdr.rcvif = NULL;
_mm->m_pkthdr.csum_flags = 0;
_mm->m_pkthdr.aux = (struct mbuf *)NULL;
(m) = _mm;
splx(_ms);
恢復中斷
} else {
splx(_ms);
_mm = m_retryhdr(_mhow, _mtype);
再來一次MGETHDR,不過m_retryhdr已經定義爲空,防止死循環
if (_mm == NULL && _mhow == M_WAIT)
還爲空
(m) = m_mballoc_wait(MGETHDR_C, _mtype);
強制用阻塞型
else
(m) = _mm;
}
} while (0)
*/
if (m == 0)
return (0);
m->m_pkthdr.rcvif = ifp;/*
指向接收該包的網絡卡的ifp指針,後面好多協議要用到他*/
m->m_pkthdr.len = totlen;/*
已經把以太網頭部剝離,數據長度沒算他了*/
m->m_len = MHLEN;/*
該出是鏈首,所以該mbuf的長度是MHLEN,而不是MLEN*/
/*
這就是MHLEN
#define MSIZE 256 /* mbuf
的大小 *
#define MLEN (MSIZE - sizeof(struct m_hdr)) /*
普通數據區的長度*
#define MHLEN (MLEN - sizeof(struct pkthdr)) /*
鏈首數據區的長度

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

搜狐博客 > ㈧糟dē > 日誌

2009-06-25 | Linux網卡驅動程序詳解(5)

*/
top = 0;
mp = &
while (totlen > 0) {
if (top) {/*
如果不是鏈的第一個*/
MGET(m, M_DONTWAIT, MT_DATA);/*MGET
MGETHDR差不多,只不過少一個m_flags = M_PKTHDR*/
if (m == 0) {
m_freem(top);
return (0);
}
m->m_len = MLEN;/*
非鏈首mbuf的長度爲MLEN,這個if(top)就代表不是鏈首mbuf*/
}/*
如果跳過了上面哪個if,那肯定是鏈的第一個mbuf,並且m已經在循環外就分配好了.*/
len = min(totlen, epkt - cp);/*epkt
在計算後指向數據的尾部,cp指向首部*/
if (len >= MINCLSIZE) {/*#define MINCLSIZE (MHLEN + 1)
這意味着只要數據大於MHLEN,就要分配一個簇*/
MCLGET(m, M_DONTWAIT);/*
看到宏展開後好恐怖,有空我再說一說*/
if (m->m_flags & M_EXT)/*
mbuf中註明是擴展型mbuf(即帶有簇)*/
m->m_len = len = min(len, MCLBYTES);/*
如果大於2048則先裝2048,裝的語句在下面*/
else
len = m->m_len;
} else {
/*
*
如果到這了,就意味着要麼這個包小於MINCLSIZE,要麼是後面一點尾巴且小於MINCLSIZE.
*/
if (len < m->m_len) {
if (top == 0 && len + max_linkhdr <= m->m_len)
m->m_data += max_linkhdr;
m->m_len = len;
} else
len = m->m_len;
}
bcopy(cp, mtod(m, caddr_t), (unsigned)len);/*
第一次數據移動,費時的操作*/
cp += len;
*mp = m;
mp = &m->m_next;/*
mbuf鏈接起來*/
totlen -= len;
if (cp == epkt)
cp = buf;
}
return (top);/*
返回裝填數據的mbuf鏈首*/
}/*
總結:在該函數中,所做的事情非常費時,主要是做內存的申請,大批數據的拷貝,如果象NFS傳送數據,會出現大量的簇的申請和大量簇的數據的拷貝,一次循環需要拷貝204832位的雙字.如果是發給本機的,那還行,如果是本機做爲橋轉發及防活牆,即數據不上傳到IP層處理,那麼可以直接改寫mbuf的分配方案,根據不同的網絡流量可初始化一定數量的大容量的緩衝鏈(可以以一個以太網的整頁數來分配,如是100M以太網是1514字節,可分配2048字節,是有一點浪費,但性能可提高,sc->el_pktbuf可變爲一隊列,用來和其他網卡的接收隊列進行數據交換.這意味着光數據進入就少拷貝一次,性能將大大提高,目前我正在研究中.)*/
/*
*
處理一個IOCTL請求.
*/
static int
el_ioctl(ifp, command, data)
register struct ifnet *ifp;
u_long command; /*IOCTL
的命令*/
caddr_t data;
{
int s, error = 0;
s = splimp(); /*
先關閉網絡中斷*/
switch (command) {
case SIOCSIFADDR:
case SIOCGIFADDR:
case SIOCSIFMTU:
error = ether_ioctl(ifp, command, data);
break;
case SIOCSIFFLAGS:
/*
*
如果接口已經DOWNFLAG還有RUNNING, 那麼先停止它
*/
if (((ifp->if_flags & IFF_UP) == 0) &&
(ifp->if_flags & IFF_RUNNING)) {
el_stop(ifp->if_softc);
ifp->if_flags &= ~IFF_RUNNING;/*
FALG中去掉IFF_RUNNING標誌*/
} else {
/*
*
如果接口已經DOWN,FLAG沒有RUNNING, 只要調用el_init例程
*/
if ((ifp->if_flags & IFF_UP) &&
((ifp->if_flags & IFF_RUNNING) == 0))
el_init(ifp->if_softc);
}
break;
default:
error = EINVAL;
}
(void) splx(s);
return (error);
}
/*
一般是數據在規定的時間內沒有發出後被調用的程序,目前該驅動程序不支持 */
static void
el_watchdog(struct ifnet *ifp)
{
log(LOG_ERR,"el%d: device timeoutn", ifp->if_unit);
ifp->if_oerrors++;
el_reset(ifp->if_softc);

 

 

 

 

由於操作系統正在變得越來越複雜,所以開機引導和關機下電的過程也越來越智能化。從簡單的DOS系統轉移到Windows

  NT系統,人們已經親身感受到了這些變化——這已不僅僅是核心操作系統的啓動引導和關閉了,還包括必須要同時啓動或者關閉相當數量的服務項目。類似於Windows NTLinux系統啓動過程需要打開的服務項目也是數量極大的。 這裏,我們假設大家已經熟悉其它操作系統的引導過程,瞭解硬件的自檢引導步驟,就只從Linux操作系統的引導加載程序(對個人電腦而言通常是LILO)開始,介紹Linux開機引導的步驟。

  加載內核

  LILO啓動之後,如果你選擇了Linux作爲準備引導的操作系統,第一個被加載的東西就是內核。請記住此時的計算機內存中還不存在任何操作系統,PC(因爲它們天然的設計缺陷)也還沒有辦法存取機器上全部的內存。因此,內核就必須完整地加載到可用RAM的第一個兆字節之內。爲了實現這個目的,

  內核是被壓縮了的。這個文件的頭部包含着必要的代碼,先設置CPU進入安全模式(以此解除內存限制),再對內核的剩餘部分進行解壓縮。

  執行內核

  內核在內存中解壓縮之後,就可以開始運行了。此時的內核只知道它本身內建的各種功能,也就是說被編譯爲模塊的內核部分還不能使用。最基本的是,內核必須有足夠的代碼設置自己的虛擬內存子系統和根文件系統(通常就是ext2文件系統)。一旦內核啓動運行,對硬件的檢測就會決定需要對哪些設備驅動程序進行初始化。從這裏開始,內核就能夠掛裝根文件系統(這個過程類似於Windows識別並存取C盤的過程)。內核掛裝了根文件系統之後,將啓動並運行一個叫做init的程序。

  注意:在這裏我們故意略去了Linux內核啓動的許多細節,這些細節只有內核開發人員才感興趣。如果你好奇的話,可以訪問http//www.redhat.com:8080地址處的 “Kernel Hackers Guide”

  init進程

  init進程是非內核進程中第一個被啓動運行的,因此它的進程編號PID的值總是1init讀它的配置文件/etc/inittab,決定需要啓動的運行級別(Runlevel)。從根本上說,運行級別規定了整個系統的行爲,每個級別(分別由06的整數表示)滿足特定的目的。如果定義了initdefault級別,這個值就直接被選中,否則需要由用戶輸入一個代表運行級別的數值。

  輸入代表運行級別的數字之後,init根據/etc/inittab文件中的定義執行一個命令腳本程序。缺省的運行級別取決於安裝階段對登錄程序的選擇:是使用基於文本的,還是使用基於X-Window的登錄程序。

  rc命令腳本程序我們已經知道,當運行級別發生改變時,將由/etc/inittab文件定義需要運行哪一個命令腳本程序。這些命令腳本程序負責啓動或者停止該運行級別特定的各種服務。由於需要管理的服務數量很多,因此需要使用rc命令腳本程序。其中,最主要的一個是/etc/rc.d/rc,它負責爲每一個運行級別按照正確的順序調用相應的命令腳本程序。我們可以想象,這樣一個命令腳本程序很容易變得難以控制!爲了防止這類事件的發生,需要使用精心設計的方案。

  對每一個運行級別來說,在/etc/rc.d子目錄中都有一個對應的下級目錄。這些運行級別的下級子目錄的命名方法是rcX.d,其中的X就是代表運行級別的數字。比如說,運行級別3的全部命令腳本程序都保存在/etc/rc.d/rc3.d子目錄中。在各個運行級別的子目錄中,都建立有到/etc/rc.d/init.d子目錄中命令腳本程序的符號鏈接,但是,這些符號鏈接並不使用命令腳本程序在/etc/rc.d/init.d子目錄中原來的名字。如果命令腳本程序是用來啓動一個服務的,其符號鏈接的名字就以字母S打頭;如果命令腳本程序是用來關閉一個服務的,其符號鏈接的名字就以字母K打頭。

  許多情況下,這些命令腳本程序的執行順序都很重要。如果沒有先配置網絡接口,就沒有辦法使用DNS服務解析主機名!爲了安排它們的執行順序,在字母S或者K的後面緊跟着一個兩位數字,數值小的在數值大的前面執行。比如:/etc/rc.d/rc3.d/S50inet就會在/etc/rc.d/rc3.d/S55named之前執行(S50inet配置網絡設置,55named啓動DNS服務器)。 存放在/etc/rc.d/init.d子目錄中的、被符號鏈接上的命令腳本程序是真正的實幹家,是它們完成了啓動或者停止各種服務的操作過程。當/etc/rc.d/rc運行通過每個特定的運行級別子目錄的時候,它會根據數字的順序依次調用各個命令腳本程序執行。它先運行以字母K打頭的命令腳本程序,然後再運行以字母S打頭的命令腳本程序。對以字母K打頭的命令腳本程序來說,會傳遞Stop參數;類似地對以字母S打頭的命令腳本程序來說,會傳遞Start參數。編寫自己的rc命令腳本在維護Linux系統運轉的日子裏,肯定會遇到需要系統管理員對開機或者關機命令腳本進行修改的情況。有兩種方法可以用來實現修改的目的:

  如果所做的修改只在引導開機的時候起作用,並且改動不大的話,可以考慮簡單地編輯一下/etc/rc.d/rc.local腳本。這個命令腳本程序是在引導過程的最後一步被執行的。

  如果所做的修改比較細緻,或者還要求關閉進程使之明確地停止運行,則需要在/etc/rc.d/init.d子目錄中添加一個命令腳本程序。這個命令腳本程序必須可以接受StartStop參數並完成相應的操作。

  第一種方法,編輯/etc/rc.d/rc.local腳本,當然是兩種方法中比較簡單的。如果想在這個命令腳本程序中添加內容,只需要使用喜歡的編輯器程序打開它,再把打算執行的命令附加到文件的末尾就可以了。這對一兩行的修改來說的確很便利。

  如果確實需要使用一個命令腳本程序,這時必須選擇第二個方法。編寫一個rc命令腳本程序的過程並不像想象中那麼困難。我們下面就給出一個例子,看看它是怎樣實現的(順便說一句,你可以把我們的例子當作範本,按照自己的需要進行修改和添加)。

  假設你打算每隔60分鐘調用一個特殊的程序來彈出一條消息,提醒自己需要從鍵盤前面離開休息一會兒,命令腳本程序將包括下面幾個部分:

  關於這個命令腳本程序功能的說明(這樣就不會在一年之後忘記它);

  在試圖運行它之前驗證這個命令腳本程序確實存在;

  接受startstop參數並執行要求的動作。

  參數給定後,我們就可以編寫命令的腳本程序。這個程序很簡單,大家可以自己編寫一下,我在這裏就不給出了。

  編寫好新的命令腳本程序之後,再從相關的運行級別子目錄中加上必要的符號鏈接,來控制這個命令腳本程序的啓動或者停止。在我的印象中,只想讓它在運行級別3或者運行級別5中啓動,原因是我認爲只有這兩個運行級別纔是日常工作的地方。最後,希望這個命令腳本程序在進入運行級別6(重啓動)的時候被關閉。

  激活或者禁止服務項目有的時候會發現,在引導的時候並不需要某個特定的服務被啓動。如果你正在考慮使用Linux替換Windows NT的文件和打印服務器,就更是如此。我們已經知道,在特定的運行級別子目錄中給符號鏈接改個名稱,就可以讓該服務不被啓動,如把其名稱的第一個字母由S改爲K。一旦熟練掌握了命令行和符號鏈接,就會發現這是激活或者禁止服務的最快辦法。

  在學習這個改名方法的時候,可能會覺得圖形化的操作界面ksysv比較容易掌握。雖然它原來是設計使用在KDE環境裏的,但在Red HatLinux 7.2下缺省安裝的GNOME環境裏也運行得很好。如果想啓動它,只需簡單地打開一個xterm窗口,並輸入ksysv命令就可以了。屏幕上會出現一個窗口,其中列出了能夠修改的全部參數,需要時還包括在線幫助。警告:如果是在一個現實中的系統上學習本文的知識,要多多運用常識。當試着對啓動腳本程序進行修改的時候,要記住所做的修改可能會造成你的系統不能正常工作,而且無法採用重啓動的方法恢復。不要在正常運轉的系統上實驗新的設置,對你準備修改的文件要全部進行備份。最重要的是,在手邊要準備一張引導盤以防不測

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發佈了8 篇原創文章 · 獲贊 0 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章