• 《驱动学习


    内核版本: 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 etworkingIfenslave.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/downifconfig 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
    ",
                              ifname, strerror(saved_errno));
             } else {
                      v_print("Interface '%s': flags set to %04X.
    ", ifname, flags);
             }
             return res;
    }

    3.3 寻找SIOCSIFFLAGS宏,看看内核那里在实现它

    找到位于netcoreDev_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,即可看到内核已经帮我设置好了:

  • 相关阅读:
    C# winform窗体自动停靠控件
    C# winform treeview checkbox 选中 循环 遍历
    解决WinForm(C#)中MDI子窗体最大化的问题
    tabcontrol的alignment属性设置成Left或Right时,tabPage的text内
    用伪随机数生成器Random生成随机数序列
    C#限制MDI子窗体重复打开——C#判断窗体是否已经打开 多种方法 20120626更新
    管理软件中融入“人、时间、流程”维度,提升软件社会价值
    如何让管理软件提升企业战略执行力
    2007年11月17日上午 ITIL与ITSM QQ群聊天记录: 欢迎加入QQ群:48132184
    Sharepoint 站点下应用程序虚拟路径下出现"不能进行输出缓存处理",错误ID 5787 解决方法
  • 原文地址:https://www.cnblogs.com/zhuangquan/p/11707262.html
Copyright © 2020-2023  润新知