內核版本: Linux version 3.10.14 1.由於每次開發板開機的網卡eth0的物理地址都是隨機的. 然後在網上找到可以通過命令行實現設置mac物理地址: 然後帶著好奇,想看看命令行ifconfig是如何與內核交互的,想試試如何直接通過內核自動設置MAC. 2.分析介紹 因為ifcon ...
- 內核版本: Linux version 3.10.14
1.由於每次開發板開機的網卡eth0的物理地址都是隨機的.
然後在網上找到可以通過命令行實現設置mac物理地址:
ifconfig eth0 down
ifconfig eth0 hw ether 1234567890ab
ifconfig eth0 up
- 然後帶著好奇,想看看命令行ifconfig是如何與內核交互的,想試試如何直接通過內核自動設置MAC.
2.分析介紹
因為ifconfig是命令,代碼位於busybox,不過我們在內核的documentation目錄下找到了ifconfig介紹,代碼介紹文件位於:
- documentation\networking\Ifenslave.c
2.1 如下圖所示,對應ifconfig eth0 down和ifconfig eth0 up的函數就是:
比如,當我們敲ifconfig eth0 down時,實則就是調用:
set_if_down("eth0", master_flags.ifr_flags);
該文件除了上圖外,還有以下常用函數:
set_if_addr(); //設置地址(包括IP,掩碼,廣播,目的地) set_master_hwaddr(); //設置mac物理地址
- 接下來我們以eth0為例,來跟蹤ifconfig up/down和ifconfig eth0 hw ether如何調用內核的
3.分析set_if_up()函數
3.1 分析set_if_up()
set_if_up()函數將會調用set_if_flags("eth0", flags | IFF_UP), 向添加ifname(eth0) 開啟標誌位
3.2 分析set_if_up()->set_if_flags("eth0", flags | IFF_UP)
該函數如下所示:
static int set_if_flags(char *ifname, short flags) { struct ifreq ifr; int res = 0; ifr.ifr_flags = flags; strncpy(ifr.ifr_name, ifname, IFNAMSIZ); //ifr.ifr_name="eth0" res = ioctl(skfd, SIOCSIFFLAGS, &ifr); //通過ioctl()向內核socket傳遞命令SIOCSIFFLAGS和ifr變數 if (res < 0) { saved_errno = errno; v_print("Interface '%s': Error: SIOCSIFFLAGS failed: %s\n", ifname, strerror(saved_errno)); } else { v_print("Interface '%s': flags set to %04X.\n", ifname, flags); } return res; }
3.3 尋找SIOCSIFFLAGS巨集,看看內核那裡在實現它
找到位於net\core\Dev_ioctl.c的dev_ioctl()函數
該函數重要部分代碼如下:
int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg) { struct ifreq ifr; int ret; char *colon; //… … switch (cmd) { //… … case SIOCSIFFLAGS: //設置標誌,比如ifconfig up/down case SIOCSIFMETRIC: case SIOCSIFMTU: //設置MUT長度 case SIOCSIFHWADDR: //設置mac物理地址 //… … dev_load(net, ifr.ifr_name); //通過ifr.ifr_name(eth0)名字來載入網卡 rtnl_lock(); //對net_device進行加鎖,避免與運行衝突 ret = dev_ifsioc(net, &ifr, cmd); //最終調用該函數 rtnl_unlock(); return ret; //… … }
從上面可以看出,我們設置mac物理地址時的流程也會運行到這裡,最終他們都會調用dev_ifsioc(net, &ifr, cmd)函數
4. 後面的就很簡單了,最終ifconfig eth0 up調用內核過程為:
set_if_up()-> set_if_flags("eth0", flags | IFF_UP)-> dev_ifsioc(net, &ifr, cmd)-> dev_change_flags(dev, ifr->ifr_flags)-> __dev_change_flags(dev, flags);
4.1然後在__dev_change_flags(dev, flags)函數中,通過判斷flag的IFF_UP位上的值是否相反,來實現是調用__dev_close()還是__dev_open()來開關eth0
如下圖所示:
4.2然後__dev_open()則將會調用網卡驅動的net_device_ops結構體下的成員函數實現打開
__dev_open(dev): dev->netdev_ops->ndo_validate_addr(dev); //測試dev->dev_addr(hw addr)是否有效,一般都是調用eth_validate_addr()函數,需要註意hw_addr[0]的最低位不能為1 dev->netdev_ops->ndo_open(dev); //調用open()函數實現ifconfig up
4.3同樣__dev_close()會調用下麵的成員函數實現關閉:
dev->netdev_ops->ndo_stop(dev); //調用stop ()函數實現ifconfig down
4.4尋找net_device_ops結構體的成員函數位於哪裡
上面講的dev 變數是struct net_device類型,而struct net_device在內核中表示我們的一個網卡驅動設備,註冊該變數的文件都處於內核drivers/net目錄下,通過register_netdev() 內核函數來註冊.
我們以我們板卡的dm9000網卡為例,該文件位於drivers/net/Ethernet/davicom/dm9000.c,然後便可以找到它的ndo_open ():
5.而對於ifconfig eth0 hw ether 設置網卡流程如下所示:
set_master_hwaddr(master_ifname,&(slave_hwaddr.ifr_hwaddr))-> ioctl (skfd, SIOCSIFHWADDR, &ifr) -> dev_ifsioc(net, &ifr, cmd)-> dev_set_mac_address(dev, &ifr->ifr_hwaddr) -> //設置網卡MAC地址 dev->netdev_ops->ndo_set_mac_address(dev, &ifr->ifr_hwaddr); //最終調用net_device的ops成員函數實現設置
6.實現內核開機自動設置固定MAC地址
流程分析完後,接下來我們便來實現它.
6.1以我們板卡的dm9000網卡為例
我們找到register_netdev()位置,位於drivers/net/Ethernet/davicom/dm9000.c的dm9000_probe函數里:
6.2 然後在register_netdev()函數下麵添加代碼:
struct sockaddr hwaddr; //用來存儲MAC地址的結構體 rtnl_lock(); ret =dev_close(jz_ndev); //首先需要關閉網卡,以防萬一 rtnl_unlock(); hwaddr.sa_family = ndev->type; hwaddr.sa_data[0]=0x12; //註意,data[0]最低位不能為1,也就是首位不能為奇數 hwaddr.sa_data[1]=0x34; hwaddr.sa_data[2]=0x56; hwaddr.sa_data[3]=0x78; hwaddr.sa_data[4]=0x90; hwaddr.sa_data[5]=0xab; rtnl_lock(); ret = dev_set_mac_address(jz_ndev,&hwaddr); //調用我們分析到的函數,來設置mac地址 rtnl_unlock();
6.3 編譯-試驗
啟動後輸入ifconfig,即可看到內核已經幫我設置好了:
- 總結: 其實實現的代碼很簡單,但是需要去分析才能把東西消化為自己的.