看了一下相关的实现,总结一下,有不对的请各位指正。
我看的source是kernel 2.6.27,ifplugd-0.28。
问题1:为啥用netlink检测网线插拔只能得到RTM_NEWLINK?
问题2:如何能检测到网卡插拔的信息。
netlink实现主要是在在net/netlink/af_netlink.c下面,但是rtnetlink.c在net/core下面。还有一个netlink.h在include/net下面。
用multicast作为关键字查一下,可以发现一个函数nlmsg_multicast,他的作用是multicast a netlink message。OK,顺着这个往上找。
函数调用关系:
nlmsg_multicast
^
|
nlmsg_notify
^
|
rtnl_notify
^
|
rtmsg_ifinfo
^
|
rtnetlink_event
^
|
rtnetlink_dev_notifier
到这里再往上就是register_netdevice_notifier(&rtnetlink_dev_notifier),到了通知链注册,不用往上看了,再往上就是初始化了。
在rtnetlink_event函数中有如下处理:
switch (event) {
case NETDEV_UNREGISTER:
rtmsg_ifinfo(RTM_DELLINK, dev, ~0U);
break;
case NETDEV_REGISTER:
rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U);
break;
case NETDEV_UP:
case NETDEV_DOWN:
rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP|IFF_RUNNING);
break;
case NETDEV_CHANGE:
case NETDEV_GOING_DOWN:
break;
default:
rtmsg_ifinfo(RTM_NEWLINK, dev, 0);
break;
可以发现NETDEV_UP和NETDEV_DOWN都是同一处理,向rtmsg_ifinfo传的都是
RTM_NEWLINK,只有NETDEV_UNREGISTER传的是RTM_DELLINK。目前实验只有rmmod网卡驱动的时候会触发NETDEV_UNREGISTER。
问题1应该到此结束。
顺便说一下,调rtmsg_ifinfo的还有dev_change_flags函数,在处理ioctl的SIOCSIFFLAGS的时候使用这个函数。
问题2主要是ifplugd的四种方法(没考虑无线),在ifplugd.c中的work函数:
switch (api_mode) {
case API_ETHTOOL: detect_beat_func = interface_detect_beat_ethtool;
break;
case API_MII: detect_beat_func = interface_detect_beat_mii; break;
case API_PRIVATE: detect_beat_func = interface_detect_beat_priv;
break;
case API_WLAN: detect_beat_func = interface_detect_beat_wlan; break;
case API_IFF: detect_beat_func = interface_detect_beat_iff; break;
default:
detect_beat_func = detect_beat_auto;
Ifplugd在缺省配置时候用的是detect_beat_auto,这个函数其实就是把上面几个函数挨个试了一遍,先interface_detect_beat_ethtool再interface_detect_beat_mii。这四个方法都可以得到网卡的状态,但是实现有不同。
下面简单说说他们的实现:
方法1:interface_detect_beat_ethtool
调
ioctl的SIOCETHTOOL实现,net/core/dev.c里面的dev_ethtool函数处理。kernel中的ethtool机制,最
早是98年由David S.
Miller先搞出来的,这可是个牛人啊。主要实现在net/core/ethtool.c里面。Ifplugd使用ETHTOOL_GLINK命令来获
取网卡的状态,对这个命令的处理是ethtool_get_value函数:
case ETHTOOL_GLINK:
rc = ethtool_get_value(dev, useraddr, ethcmd,dev->ethtool_ops->get_link);
ethtool_get_value
里面回调dev->ethtool_ops->get_link来取网卡状态,这个方法需要各个网卡驱动自己实现,以intel
e100为例,他的实现在drivers/net/e100.c中的e100_get_link函数。e100_get_link调
mii_link_ok(deivers/net/mii.c),mii_link_ok的处理:
mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR);
if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
return 1;
return 0;
mdio_read是mii需要各个驱动实现的一个callback,具体实现又回到了具体的网卡驱动,e100的在e100.c中的mdio_read函数。里面就是调体系结构相关的ioread32函数读网卡寄存器了。
方法2:interface_detect_beat_mii
调ioctl的SIOCGMIIPHY和SIOCGMIIREG实现,dev_ifsioc函数(net/core/dev.c)中处理:
if (dev->do_ioctl) {
if (netif_device_present(dev))
err = dev->do_ioctl(dev, ifr,cmd);
else
err = -ENODEV;
}
dev->do_ioctl
是各个网卡驱动自己实现的,以intel
e100为例,他的实现在drivers/net/e100.c中的e100_do_ioctl函数,e100_do_ioctl里面又调
generic_mii_ioctl(deivers/net/mii.c)。处理如下:
switch(cmd) {
case SIOCGMIIPHY:
mii_data->phy_id = mii_if->phy_id;
/* fall through */
case SIOCGMIIREG:
mii_data->val_out =
mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
mii_data->reg_num);
break;
很多网卡驱动都是调generic_mii_ioctl来处理ioctl的。MII定义。看来网卡驱动监视和控制PHY基本都是用这个。关于mdio_read看上面方法1里面写的东东。
方法3:interface_detect_beat_priv
基
本上和方法2一样,调ioctl的SIOCDEVPRIVATE,和SIOCDEVPRIVATE+1实现,dev_ifsioc函数(net/core
/dev.c)中处理同方法2,有一点不同就是SIOCDEVPRIVATE到SIOCDEVPRIVATE +
15是各个网卡自己定义的命令,有的网卡有实现,有的没有。例如e100就没有实现这个ioctl。而rtl8150的usb版本网卡驱动就有实现。
方法4:interface_detect_beat_iff
调ioctl的SIOCGIFFLAGS实现,dev_ifsioc_locked函数(net/core/dev.c)中处理:
case SIOCGIFFLAGS: /* Get interface flags */
ifr->ifr_flags = dev_get_flags(dev);
return 0;
而dev_get_flags函数的处理:
flags = (dev->flags & ~(IFF_PROMISC |
IFF_ALLMULTI |
IFF_RUNNING |
IFF_LOWER_UP |
IFF_DORMANT)) |
(dev->gflags & (IFF_PROMISC |
IFF_ALLMULTI));
if (netif_running(dev)) {
if (netif_oper_up(dev))
flags |= IFF_RUNNING;
if (netif_carrier_ok(dev))
flags |= IFF_LOWER_UP;
if (netif_dormant(dev))
flags |= IFF_DORMANT;
没有由网卡驱动去查硬件,而是查现有的状态标志位来实现。
总结一下,方法1和2应该是最保险的,直接去查硬件寄存器,法3可能会有驱动不支持,法4不是很可靠。
--
阅读(3618) | 评论(0) | 转发(0) |