前面文章讲了Linux系统的ethtool框架的一些东西,是从用户空间可以直观认识到的地方入手。同样,本文从Linux系统绝大部分人都熟悉的“ifconfig eth0 up”命令来跟踪一下此命令在内核中的发生了什么事情。由于ifconfig启动(up)和禁止(down)网络设备很相似,就放到一起讲了。

首先从ifconfig的源码入手,我下载的源码地址是http://www.tazenda.demon.co.uk/phil/net-tools/。这个网站上还有大量很有用的工具的源码,源码分布符合Linux的系统目录,有兴趣的可以去看看。

在我们输入up或down时,对应的代码如下:

  1. main()
  2. {
  3. if (!strcmp(*spp, "up")) {
  4. goterr |= set_flag(ifr.ifr_name, (IFF_UP | IFF_RUNNING));
  5. spp++;
  6. continue;
  7. }
  8. if (!strcmp(*spp, "down")) {
  9. goterr |= clr_flag(ifr.ifr_name, IFF_UP);
  10. spp++;
  11. continue;
  12. }
  13. }

很简单,就是根据用户的输入来标志IFF_UP参考。当up时,使用set_flag置位IFF_UP和IFF_RUNNING,当down时,使用clr_flag清除IFF_UP。Linux的这种思想值得学习,其实对于内核来讲,真的就是通过IFF_UP标志来判断网卡的使能和禁止的。

来看设置标志的set_flag函数:

  1. static int set_flag(char *ifname, short flag)
  2. {
  3. struct ifreq ifr;
  4. safe_strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
  5. if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) {
  6. fprintf(stderr, _("%s: unknown interface: %s\n"),
  7. ifname,     strerror(errno));
  8. return (-1);
  9. }
  10. safe_strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
  11. ifr.ifr_flags |= flag;
  12. if (ioctl(skfd, SIOCSIFFLAGS, &ifr) < 0) {
  13. perror("SIOCSIFFLAGS");
  14. return -1;
  15. }
  16. return (0);
  17. }

以及清除标志的clr_flag函数:

  1. static int clr_flag(char *ifname, short flag)
  2. {
  3. struct ifreq ifr;
  4. int fd;
  5. if (strchr(ifname, ':')) {
  6. /* This is a v4 alias interface.  Downing it via a socket for
  7. another AF may have bad consequences. */
  8. fd = get_socket_for_af(AF_INET);
  9. if (fd < 0) {
  10. fprintf(stderr, _("No support for INET on this system.\n"));
  11. return -1;
  12. }
  13. } else
  14. fd = skfd;
  15. safe_strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
  16. if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
  17. fprintf(stderr, _("%s: unknown interface: %s\n"),
  18. ifname, strerror(errno));
  19. return -1;
  20. }
  21. safe_strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
  22. ifr.ifr_flags &= ~flag;
  23. if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) {
  24. perror("SIOCSIFFLAGS");
  25. return -1;
  26. }
  27. return (0);
  28. }

观察这两个函数,最后都是使用SIOCSIFFLAGS命令和内核交互。我们找到这个命令的使用地方,它位于net/core/dev.c文件,如下:

  1. int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)
  2. {
  3. case SIOCSIFFLAGS:
  4. case SIOCSIFMETRIC:
  5. case SIOCSIFMTU:
  6. case SIOCSIFMAP:
  7. case SIOCSIFHWADDR:
  8. case SIOCSIFSLAVE:
  9. case SIOCADDMULTI:
  10. case SIOCDELMULTI:
  11. case SIOCSIFHWBROADCAST:
  12. case SIOCSIFTXQLEN:
  13. case SIOCSMIIREG:
  14. case SIOCBONDENSLAVE:
  15. case SIOCBONDRELEASE:
  16. case SIOCBONDSETHWADDR:
  17. case SIOCBONDCHANGEACTIVE:
  18. case SIOCBRADDIF:
  19. case SIOCBRDELIF:
  20. case SIOCSHWTSTAMP:
  21. if (!capable(CAP_NET_ADMIN))
  22. return -EPERM;
  23. /* fall through */
  24. case SIOCBONDSLAVEINFOQUERY:
  25. case SIOCBONDINFOQUERY:
  26. dev_load(net, ifr.ifr_name);
  27. rtnl_lock();
  28. ret = dev_ifsioc(net, &ifr, cmd);
  29. rtnl_unlock();
  30. return ret;
  31. }

SIOCSIFFLAGS会调用到dev_ifsioc函数:

  1. static int dev_ifsioc(struct net *net, struct ifreq *ifr, unsigned int cmd)
  2. {
  3. switch (cmd) {
  4. case SIOCSIFFLAGS:     /* Set interface flags */
  5. return dev_change_flags(dev, ifr->ifr_flags);
  6. }

继续跟进dev_change_flags函数:

  1. int dev_change_flags(struct net_device *dev, unsigned flags)
  2. {
  3. int ret, changes;
  4. int old_flags = dev->flags;
  5. ret = __dev_change_flags(dev, flags); // 打开设备
  6. if (ret < 0)
  7. return ret;
  8. changes = old_flags ^ dev->flags;
  9. if (changes)
  10. rtmsg_ifinfo(RTM_NEWLINK, dev, changes); // 暂未了解
  11. __dev_notify_flags(dev, old_flags); // 向通道链netdev_chain发出通知
  12. return ret;
  13. }

真正干活的是__dev_change_flags函数:

  1. int __dev_change_flags(struct net_device *dev, unsigned int flags)
  2. {
  3. if ((old_flags ^ flags) & IFF_UP) {     /* Bit is different  ? */
  4. ret = ((old_flags & IFF_UP) ? __dev_close : __dev_open)(dev);
  5. }

根据标志选择打开设备__dev_open或关闭__dev_close。在同一文件还有dev_open或dev_close,我发现它们使用了EXPORT_SYMBOL导出,供给其它模块使用,但在这里,是使用了__dev_XX函数的。

打开函数如下:

  1. static int __dev_open(struct net_device *dev)
  2. {
  3. const struct net_device_ops *ops = dev->netdev_ops;
  4. int ret;
  5. ASSERT_RTNL();
  6. /*
  7. *  Is it even present?
  8. */
  9. if (!netif_device_present(dev))
  10. return -ENODEV;
  11. ret = call_netdevice_notifiers(NETDEV_PRE_UP, dev);
  12. ret = notifier_to_errno(ret);
  13. if (ret)
  14. return ret;
  15. /*
  16. *  Call device private open method
  17. */
  18. set_bit(__LINK_STATE_START, &dev->state);
  19. if (ops->ndo_validate_addr)
  20. ret = ops->ndo_validate_addr(dev);
  21. if (!ret && ops->ndo_open)
  22. ret = ops->ndo_open(dev);
  23. /*
  24. *  If it went open OK then:
  25. */
  26. if (ret)
  27. clear_bit(__LINK_STATE_START, &dev->state);
  28. return ret;
  29. }

在真正调用具体驱动的接口前,先发NETDEV_PRE_UP给到通知链,再置__LINK_STATE_START,然后才调用驱动的ndo_open接口,最后需要清除__LINK_STATE_START标志。

关闭函数如下:

  1. static int __dev_close(struct net_device *dev)
  2. {
  3. const struct net_device_ops *ops = dev->netdev_ops;
  4. ASSERT_RTNL();
  5. might_sleep();
  6. /*
  7. *  Tell people we are going down, so that they can
  8. *  prepare to death, when device is still operating.
  9. */
  10. call_netdevice_notifiers(NETDEV_GOING_DOWN, dev);
  11. clear_bit(__LINK_STATE_START, &dev->state);
  12. /* Synchronize to scheduled poll. We cannot touch poll list,
  13. * it can be even on different cpu. So just clear netif_running().
  14. *
  15. * dev->stop() will invoke napi_disable() on all of it's
  16. * napi_struct instances on this device.
  17. */
  18. smp_mb__after_clear_bit(); /* Commit netif_running(). */
  19. dev_deactivate(dev);
  20. /*
  21. *  Call the device specific close. This cannot fail.
  22. *  Only if device is UP
  23. *
  24. *  We allow it to be called even after a DETACH hot-plug
  25. *  event.
  26. */
  27. if (ops->ndo_stop)
  28. ops->ndo_stop(dev);
  29. /*
  30. *  Device is now down.
  31. */
  32. dev->flags &= ~IFF_UP;
  33. return 0;
  34. }

在真正调用具体驱动的接口前,先发NETDEV_GOING_DOWN给到通知链,表示网络设备准备挂了,再清除__LINK_STATE_START标志,然后才调用驱动的ndo_stop接口,最后需要清除IFF_UP。

经过内核的层层结构,条条框框,终于到了具体的驱动了,上面的函数使用的接口实际上是net_device_ops结构体的函数指针,还是以igb驱动为例,赋值如下:

  1. static const struct net_device_ops igb_netdev_ops = {
  2. .ndo_open       = igb_open,
  3. .ndo_stop       = igb_close,
  4. .ndo_start_xmit     = igb_xmit_frame_adv,
  5. .ndo_get_stats64    = igb_get_stats64,
  6. .ndo_set_rx_mode    = igb_set_rx_mode,
  7. .ndo_set_multicast_list = igb_set_rx_mode,
  8. .ndo_set_mac_address    = igb_set_mac,
  9. .ndo_change_mtu     = igb_change_mtu,
  10. .ndo_do_ioctl       = igb_ioctl,
  11. .ndo_tx_timeout     = igb_tx_timeout,
  12. .ndo_validate_addr  = eth_validate_addr,
  13. .ndo_vlan_rx_register   = igb_vlan_rx_register,
  14. .ndo_vlan_rx_add_vid    = igb_vlan_rx_add_vid,
  15. .ndo_vlan_rx_kill_vid   = igb_vlan_rx_kill_vid,
  16. .ndo_set_vf_mac     = igb_ndo_set_vf_mac,
  17. .ndo_set_vf_vlan    = igb_ndo_set_vf_vlan,
  18. .ndo_set_vf_tx_rate = igb_ndo_set_vf_bw,
  19. .ndo_get_vf_config  = igb_ndo_get_vf_config,
  20. #ifdef CONFIG_NET_POLL_CONTROLLER
  21. .ndo_poll_controller    = igb_netpoll,
  22. #endif
  23. };

我们看到最开始的2个函数就是打开和关闭。在igb_probe函数对igb_netdev_ops进行赋值:

  1. netdev->netdev_ops = &igb_netdev_ops;

至此,整个过程分析完毕。

文中涉及到通知链,网络设备通知链netdev_chain,这个还没研究过,这里简单列一下:

  1. // 声明通知链表
  2. static RAW_NOTIFIER_HEAD(netdev_chain);
  3. //注册
  4. int register_netdevice_notifier(struct notifier_block *nb)
  5. {
  6. raw_notifier_chain_register(&netdev_chain, nb);
  7. }
  8. 注销:
  9. int unregister_netdevice_notifier(struct notifier_block *nb)
  10. {
  11. int err;
  12. err = raw_notifier_chain_unregister(&netdev_chain, nb);
  13. }

31号的PS:

写完后想一想,感觉没到分析彻底,因为到具体的驱动后干了些什么还没跟踪,于是又花了点时间跟踪一下。我跟踪的是ti的网卡驱动,主要实现代码在cpsw.c文件,在http://lxr.oss.org.cn/source/drivers/net/ethernet/ti/?v=3.17可以找到。下面列出启动网卡时的过程的重要函数调用:

  1. > cpsw_ndo_open
  2. > cpsw_slave_open
  3. > phy_connect (传递cpsw_adjust_link)
  4. > phy_connect_direct (PHY_READY)
  5. >  phy_prepare_link (赋值cpsw_adjust_link为adjust_link)
  6. > phy_start_machine
  7. > phy_start (PHY_READY变成PHY_UP)

phy_start之后进入了phy驱动重要的状态判断函数phy_state_machine,phy驱动有一个工作队列就是调用这个函数的,这个函数判断了网络各种状态:PHY_DOWN、PHY_UPPHY_AN、PHY_NOLINK,等,并做出对应的动作。我们设置了PHY_UP,则函数过程如下:

  1. phy_state_machine
  2. > phy_start_aneg
  3. > config_aneg
  4. > genphy_config_aneg(实际上是这个函数,由phy驱动赋值的)

到了genphy_config_aneg这个函数,就是直接和phy芯片打交道了,读phy寄存器、写phy寄存器。默认情况下会进行自动协商(其实就是写phy的第0个寄存器),当然,如果不是,即phy_device成员变量autoneg是AUTONEG_DISABLE,则会强制设置指定的速率、双工模式。

至此,就不再继续分析、跟踪了。

原文:https://blog.csdn.net/subfate/article/details/44755679

Linux系统网络设备启动和禁止“ifconfig eth0 up/down”命令的跟踪的更多相关文章

  1. Linux系统的启动过程

    Linux 系统启动过程 Linux系统的启动过程可以分为5个阶段: BIOS自检 内核的引导. 运行init. 系统初始化. 用户登录系统. BIOS自检: BIOS是英文"Basic I ...

  2. Linux系统基本网络配置之ifconfig命令

    Linux系统基本网络配置之ifconfig命令 一.Linux系统的网络配置基本包括:网络IP的配置.网关路由的配置.主机DNS的配置.主机名的配置等,本篇注重介绍网络IP的配置. 二.Linux系 ...

  3. 探索 Linux 系统的启动过程

    引言 之所以想到写这些东西,那是因为我确实想让大家也和我一样,把 Linux 桌面系统打造成真真正正日常使用的工具,而不是安装之后试用几把再删掉.我是真的在日常生活和工作中都使用 Linux,比如在 ...

  4. 腾讯云Linux系统中启动自己安装的tomcat

    腾讯云Linux系统中启动自己安装的tomcat 首先通过工具查看一下安装的tomcat的位置 进入命令行之后输入以下指令: 此时,tomcat已经启动了.

  5. [转] 浅谈Linux系统的启动流程

    原文:http://blog.csdn.net/justdb/article/details/9621271 版权声明:本文为博主原创文章. Linux系统的启动时通过读取不同的配置文件,执行相应的S ...

  6. 详解linux系统的启动过程及系统初始化

    一.linux系统的启动流程 关于linux系统的启动流程我们可以按步进行划分为如下: POST加电自检 -->BIOS(Boot Sequence)-->加载对应引导上的MBR(boot ...

  7. Linux系统快速启动方案

    =========================        基本常识       ========================= Linux系统基本启动流程: 1. CPU从ROM(如果有的 ...

  8. CentOS(四)--Linux系统的启动级别

    对于绝大多数Linux程序员来说,进入Linux系统后一般看到的是黑乎乎的界面(开发模式),因为系统如果启动选择开发模式,会减少启动时间,优化内存等.但是通常我们刚安装完Linux系统,然后进去以后是 ...

  9. 浅谈Linux系统的启动流程

    Linux系统的启动时通过读取不同的配置文件,执行相应的Shell脚本完成的.当然本文只是简单的从文件的角度分析,更深层次的本文没涉及. 主要读取了以下文件:  /boot/grub/grub.con ...

随机推荐

  1. JS-对象查找父级

    之前在寻找两个以上的父级,一直傻傻的用parent().parent()... 今天,需要写五个,当然以前也是写过五个的,但是今天总想着换个简单的方式,至少不要.parent().parent().p ...

  2. openstack 爬坑日记

    这个问题官方文档应该付全部责任.​ 关于 endpoint regionOne 问题 ​官方文档的所有endpoint 创建命令都是使用的regionOne,但是这个配置项必须和相关的组件ini 文件 ...

  3. 使用log4net记录日志到数据库(含有自定义属性)

    记录日志是管理系统中对用户行为的一种监控与审核,asp.net中记录日志的方式有很多种,这里我只介绍一下最近用到的log4net,关于他的具体介绍网上有很多,我讲一下他的用法. 第一步:在配置文件中的 ...

  4. 微信小程序 --- 绘画

    cavans及context详解 绘画API的使用 游戏的制作

  5. 树链剖分+离散+扫描(HDU5044)

    Tree Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Subm ...

  6. VC编译选项 多线程(/MT)

    VC编译选项 多线程(/MT)多线程调试(/MTd)多线程 DLL (/MD)多线程调试 DLL (/MDd)C 运行时库                        库文件Single threa ...

  7. Oracle Schema Objects——伪列ROWID Pseudocolumn(ROWNUM、ROWID)

    Oracle Schema Objects Oracle Schema Objects——Tables——Oracle Data Types Oracle伪列 在Oracle数据库之中为了实现完整的关 ...

  8. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect.

    Spring Framework Overview https://www.tutorialspoint.com/spring/spring_overview.htm Aspect Oriented ...

  9. SpringCloud 进阶之Zuul(路由网关)

    1. Zuul(路由网关) Zuul 包含了对请求的路由和过滤两个最主要的功能; 路由功能:负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础; 过滤功能:负责对请求的处理过程进行干 ...

  10. Git学习-->GitLab如何屏蔽掉注册功能?

    一.背景 最近发现我在公司内部搭建的GitLab平台上关于账号出现了以下几个问题: 莫名其妙出现很多陌生人的账号 团队成员的很多账户注册填写的Email和UserName都不符合规范 本来一开始是打算 ...