【网络驱动】ifconfigup后内核网络驱动做了什么?

嵌入式艺术 2023-06-07 11:30




背景

最近在排查一个网络问题,ifconfig eth0 up 后,网卡link up比较慢。因此,分析了下从ifconfig up 到网络驱动的调用流程。这里顺便作个记录。

ifconfig eth0 up 调用的是busybox 的命令,因此从busybox 源码入手,逐步分析下调用流程。代码介绍文件位于:networking/ifenslave.c

ifconfig eth0 up

ifconfig eth0 upifconfig eth0 down 分别对应busybox 的set_if_up()set_if_down().

static int set_if_down(char *ifname, int flags)
{
 int res = set_if_flags(ifname, flags & ~IFF_UP);
 if (res)
  bb_perror_msg("%s: can't down", ifname);
 return res;
}
static int set_if_up(char *ifname, int flags)
{
 int res = set_if_flags(ifname, flags | IFF_UP);
 if (res)
  bb_perror_msg("%s: can't up", ifname);
 return res;
}

比如,当我们敲ifconfig eth0 down时,实则就是调用:

set_if_down("eth0", master_flags.ifr_flags);

set_if_flags()会将网卡名,up / down 标志位flags通过ioctl命令SIOCSIFFLAGS 传递给内核网卡驱动。

static int set_if_flags(char *ifname, int flags)
{
 struct ifreq ifr;

 ifr.ifr_flags = flags;
 return set_ifrname_and_do_ioctl(SIOCSIFFLAGS, &ifr, ifname);
}

dev_ifsioc

接着深入到内核代码中,看下SIOCSIFFLAGS命令在哪里实现。代码位于kernel\net\core\dev_ioctl.c

static int dev_ifsioc(struct net *net, struct ifreq *ifr, unsigned int cmd)
{
 int err;
 struct net_device *dev = __dev_get_by_name(netifr->ifr_name);
 const struct net_device_ops *ops;

 if (!dev)
  return -ENODEV;

 ops = dev->netdev_ops;

 switch (cmd) {
 case SIOCSIFFLAGS: /* Set interface flags */
  return dev_change_flags(dev, ifr->ifr_flags);

 case SIOCSIFMETRIC: /* Set the metric on the interface
       (currently unused) */

  return -EOPNOTSUPP;

...................

 }
 return err;
}

dev_ifsioc()会调用__dev_get_by_name()根据 网卡名遍历 net链表,如果匹配到则返回net_device结构体指针。接着,SIOCSIFFLAGS会调用到dev_change_flags(),最后调用到__dev_change_flags()

dev_change_flags

int dev_change_flags(struct net_device *dev, unsigned int flags)
{
 int ret;
 unsigned int changes, old_flags = dev->flags, old_gflags = dev->gflags;

 ret = __dev_change_flags(dev, flags);
 if (ret < 0)
  return ret;

 changes = (old_flags ^ dev->flags) | (old_gflags ^ dev->gflags);
 __dev_notify_flags(dev, old_flags, changes);
 return ret;
}
int __dev_change_flags(struct net_device *dev, unsigned int flags)
{
 unsigned int old_flags = dev->flags;
 int ret;

 ASSERT_RTNL();

 /*
  * Set the flags on our device.
  */


 dev->flags = (flags & (IFF_DEBUG | IFF_NOTRAILERS | IFF_NOARP |
          IFF_DYNAMIC | IFF_MULTICAST | IFF_PORTSEL |
          IFF_AUTOMEDIA)) |
       (dev->flags & (IFF_UP | IFF_VOLATILE | IFF_PROMISC |
        IFF_ALLMULTI));

 /*
  * Load in the correct multicast list now the flags have changed.
  */


 if ((old_flags ^ flags) & IFF_MULTICAST)
  dev_change_rx_flags(dev, IFF_MULTICAST);

 dev_set_rx_mode(dev);

 /*
  * Have we downed the interface. We handle IFF_UP ourselves
  * according to user attempts to set it, rather than blindly
  * setting it.
  */


 ret = 0;
    /* 两个标识有一个是IFF_UP */
 if ((old_flags ^ flags) & IFF_UP)
  ret = ((old_flags & IFF_UP) ? __dev_close : __dev_open)(dev); // 通过flags 判断调用__dev_close 还是 __dev_open

 if ((flags ^ dev->gflags) & IFF_PROMISC) {
  int inc = (flags & IFF_PROMISC) ? 1 : -1;
  unsigned int old_flags = dev->flags;

  dev->gflags ^= IFF_PROMISC;

  if (__dev_set_promiscuity(dev, inc, false) >= 0)
   if (dev->flags != old_flags)
    dev_set_rx_mode(dev);
 }

 /* NOTE: order of synchronization of IFF_PROMISC and IFF_ALLMULTI
    is important. Some (broken) drivers set IFF_PROMISC, when
    IFF_ALLMULTI is requested not asking us and not reporting.
  */

 if ((flags ^ dev->gflags) & IFF_ALLMULTI) {
  int inc = (flags & IFF_ALLMULTI) ? 1 : -1;

  dev->gflags ^= IFF_ALLMULTI;
  __dev_set_allmulti(dev, inc, false);
 }

 return ret;
}

__dev_change_flags(dev, flags)函数中,通过判断flag的IFF_UP位上的值是否相反,来实现是调用__dev_close()还是__dev_open()来开关eth0。

__dev_close

__dev_close中会将当前的net_device加入到等待设备关闭列表中。

static int __dev_close(struct net_device *dev)
{
 int retval;
 LIST_HEAD(single);

 list_add(&dev->close_list, &single);
 retval = __dev_close_many(&single);
 list_del(&single);

 return retval;
}

__dev_close_many

__dev_close_many通知设备正在关闭,等待未发送完的数据发送完,最后清除开启标记。

static int __dev_close_many(struct list_head *head)
{
 struct net_device *dev;

 ASSERT_RTNL();
 might_sleep();

 list_for_each_entry(dev, head, close_list) {
  /* Temporarily disable netpoll until the interface is down */
          /* 禁用netpoll */
  netpoll_poll_disable(dev);
  /* 通知设备正在关闭 */
  call_netdevice_notifiers(NETDEV_GOING_DOWN, dev);
   /* 清除start标志位 */
  clear_bit(__LINK_STATE_START, &dev->state);

  /* Synchronize to scheduled poll. We cannot touch poll list, it
   * can be even on different cpu. So just clear netif_running().
   *
   * dev->stop() will invoke napi_disable() on all of it's
   * napi_struct instances on this device.
   */

  smp_mb__after_atomic(); /* Commit netif_running(). */
 }
  /* 未发送完的数据发送完 */
 dev_deactivate_many(head);

 list_for_each_entry(dev, head, close_list) {
  const struct net_device_ops *ops = dev->netdev_ops;

  /*
   * Call the device specific close. This cannot fail.
   * Only if device is UP
   *
   * We allow it to be called even after a DETACH hot-plug
   * event.
   */

         /* 调用设备关闭操作 */
  if (ops->ndo_stop)
   ops->ndo_stop(dev);
  /* 标记设备关闭 */
  dev->flags &= ~IFF_UP;
        /* 启用netpoll */
  netpoll_poll_enable(dev);
 }

 return 0;
}

ndo_stop

ndo_stop为关闭网卡时,不同网卡驱动注册的不同的关闭函数,我们以海思的网卡驱动为例,分析下ndo_stop函数的实现。代码位于kernel\drivers\net\ethernet\hisilicon\hns\hns_enet.c

hns_nic_net_stop

static int hns_nic_net_stop(struct net_device *ndev)
{
 hns_nic_net_down(ndev);

 return 0;
}

hns_nic_net_down

static void hns_nic_net_down(struct net_device *ndev)
{
 int i;
 struct hnae_ae_ops *ops;
 struct hns_nic_priv *priv = netdev_priv(ndev);
 
 if (test_and_set_bit(NIC_STATE_DOWN, &priv->state))
  return;

 (void)del_timer_sync(&priv->service_timer);
 netif_tx_stop_all_queues(ndev);
 netif_carrier_off(ndev);
 netif_tx_disable(ndev);
 priv->link = 0;

 if (priv->phy)
  phy_stop(priv->phy);

 ops = priv->ae_handle->dev->ops;

 if (ops->stop)
  ops->stop(priv->ae_handle);

 netif_tx_stop_all_queues(ndev);

 for (i = priv->ae_handle->q_num - 1; i >= 0; i--) {
  hns_nic_ring_close(ndev, i);
  hns_nic_ring_close(ndev, i + priv->ae_handle->q_num);

  /* clean tx buffers*/
  hns_nic_tx_clr_all_bufs(priv->ring_data + i);
 }
}

hns_nic_net_down()中会调用netif_carrier_off()通知内核子系统网络断开。下面我们详细分析下netif_carrier_off()的实现。

netif_carrier_off()

void netif_carrier_off(struct net_device *dev)
{
     /* 设置网卡为载波断开状态 即nocarrier状态,上行时软中断下半部读到该状态不会进行网卡收包 */
 if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
  if (dev->reg_state == NETREG_UNINITIALIZED)
   return;
        /* 增加设备改变状态 */
  atomic_inc(&dev->carrier_changes);
        /* 加入事件处理队列进行处理 */
  linkwatch_fire_event(dev);
 }
}

linkwatch_fire_event()

linkwatch_fire_event()函数将设备加入到事件队列,并且进行事件调度,调度中会根据是否为紧急事件做不同处理。

void linkwatch_fire_event(struct net_device *dev)
{
    /* 判断是否是紧急处理的事件 */
 bool urgent = linkwatch_urgent_event(dev);
 /* 判断是否是紧急处理的事件 */
 if (!test_and_set_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) {
         /* 添加事件到事件列表 */
  linkwatch_add_event(dev);
 } else if (!urgent)
         /* 设备以前已经设置了pending标记,不是紧急事件,直接返回 */
  return;
 /* 事件调度 */
 linkwatch_schedule_work(urgent);
}

linkwatch_urgent_event()

linkwatch_urgent_event()判断是否是否需要紧急处理。

static bool linkwatch_urgent_event(struct net_device *dev)
{
    /* 设备未运行,非紧急 */
 if (!netif_running(dev))
  return false;
  /* 设备的索引号与连接索引号不等,紧急 */
 if (dev->ifindex != dev_get_iflink(dev))
  return true;
 /* 设备作为team port,紧急 */
 if (dev->priv_flags & IFF_TEAM_PORT)
  return true;
 /* 连接与否 && 发送队列排队规则改变与否 */
 return netif_carrier_ok(dev) && qdisc_tx_changing(dev);
}

linkwatch_add_event()

linkwatch_add_event()将设备加入到事件处理链表。

static void linkwatch_add_event(struct net_device *dev)
{
 unsigned long flags;

 spin_lock_irqsave(&lweventlist_lock, flags);
    /* 若未添加,则添加设备到事件列表 */
 if (list_empty(&dev->link_watch_list)) {
  list_add_tail(&dev->link_watch_list, &lweventlist);
  dev_hold(dev);
 }
 spin_unlock_irqrestore(&lweventlist_lock, flags);
}

linkwatch_schedule_work()

linkwatch_schedule_work()对事件处理进行调度,紧急事件立即执行,非紧急事件延后执行。

static void linkwatch_schedule_work(int urgent)
{
 unsigned long delay = linkwatch_nextevent - jiffies;
 /* 已经设置了紧急标记,则返回 */
 if (test_bit(LW_URGENT, &linkwatch_flags))
  return;

 /* 需要紧急调度 */
 if (urgent) {
        /* 之前设置了,则返回 */
  if (test_and_set_bit(LW_URGENT, &linkwatch_flags))
   return;
        /* 未设置紧急,则立即执行 */
  delay = 0;
 }

 /* 如果大于1s则立即执行 */
 if (delay > HZ)
  delay = 0;

 /* 如果设置了紧急标记,则立即执行 */
 if (test_bit(LW_URGENT, &linkwatch_flags))
  mod_delayed_work(system_wq, &linkwatch_work, 0);
 else
        /* 未设置紧急标记,则按照delay执行 */
  schedule_delayed_work(&linkwatch_work, delay);
}

__linkwatch_run_queue()

__linkwatch_run_queue()完成对事件调度队列中设备的处理。

static void __linkwatch_run_queue(int urgent_only)
{
 struct net_device *dev;
 LIST_HEAD(wrk);

 /*
  * Limit the number of linkwatch events to one
  * per second so that a runaway driver does not
  * cause a storm of messages on the netlink
  * socket.  This limit does not apply to up events
  * while the device qdisc is down.
  */

    /* 已达到调度时间 */
 if (!urgent_only)
  linkwatch_nextevent = jiffies + HZ;
 /* Limit wrap-around effect on delay. */
    /*
     未到达调度时间,并且下一次调度在当前时间的1s以后 
     那么设置调度时间是当前时间
     */

 else if (time_after(linkwatch_nextevent, jiffies + HZ))
  linkwatch_nextevent = jiffies;
 /* 清除紧急标识 */
 clear_bit(LW_URGENT, &linkwatch_flags);

 spin_lock_irq(&lweventlist_lock);
 list_splice_init(&lweventlist, &wrk);
 /* 遍历链表 */
 while (!list_empty(&wrk)) {
  /* 获取设备 */
  dev = list_first_entry(&wrk, struct net_device, link_watch_list);
        /* 从链表移除设备 */
  list_del_init(&dev->link_watch_list);
  /* 未到达调度时间 &&  不需要紧急处理  */
  if (urgent_only && !linkwatch_urgent_event(dev)) {
            /* 添加到链表尾部 */
   list_add_tail(&dev->link_watch_list, &lweventlist);
            /* 继续处理 */
   continue;
  }
  spin_unlock_irq(&lweventlist_lock);
        /* 处理设备 */
  linkwatch_do_dev(dev);
  spin_lock_irq(&lweventlist_lock);
 }
 /* 链表有未处理事件,则以非紧急状态调度队列 */
 if (!list_empty(&lweventlist))
  linkwatch_schedule_work(0);
 spin_unlock_irq(&lweventlist_lock);
}

linkwatch_do_dev()

linkwatch_do_dev()完成对某个设备的状态改变处理。

static void linkwatch_do_dev(struct net_device *dev)
{
 /*
  * Make sure the above read is complete since it can be
  * rewritten as soon as we clear the bit below.
  */

 smp_mb__before_atomic();

 /* We are about to handle this device,
  * so new events can be accepted
  */

    /* 清除pending标记 */
 clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state);

 rfc2863_policy(dev);
     /* 如果设备启动状态 */
 if (dev->flags & IFF_UP) {
        /* 链路连接 */
  if (netif_carrier_ok(dev))
            /* 启用排队规则 */
   dev_activate(dev);
  else
            /* 关闭排队规则 */
   dev_deactivate(dev);
   /* 设备状态改变处理,执行netdev_chain上设备状态变更回调 */
  netdev_state_change(dev);
 }
 dev_put(dev);
}

phy_stop()

最后,hns_nic_net_down()中会调用phy_stop()将网卡link down。

void phy_stop(struct phy_device *phydev)
{
 mutex_lock(&phydev->lock);

 if (PHY_HALTED == phydev->state)
  goto out_unlock;

 if (phy_interrupt_is_valid(phydev)) {
  /* Disable PHY Interrupts */
  phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);

  /* Clear any pending interrupts */
  phy_clear_interrupt(phydev);
 }

 phydev->state = PHY_HALTED;

out_unlock:
 mutex_unlock(&phydev->lock);

 /* Cannot call flush_scheduled_work() here as desired because
  * of rtnl_lock(), but PHY_HALTED shall guarantee phy_change()
  * will not reenable interrupts.
  */

}

phy_stop()将phydev->state设置为PHY_HALTED,将网卡关闭。

__dev_open

__dev_open为设备启用核心函数,该函数打开eth0,设置启用标记,并且设置接收模式,排队规则等。

static int __dev_open(struct net_device *dev)
{
 const struct net_device_ops *ops = dev->netdev_ops;
 int ret;

 ASSERT_RTNL();
  /* 设备不可用 */
 if (!netif_device_present(dev))
  return -ENODEV;

 /* Block netpoll from trying to do any rx path servicing.
  * If we don't do this there is a chance ndo_poll_controller
  * or ndo_poll may be running while we open the device
  */

     /* 禁用netpoll */
 netpoll_poll_disable(dev);
 /* 设备打开前通知 */
 ret = call_netdevice_notifiers(NETDEV_PRE_UP, dev);
 ret = notifier_to_errno(ret);
 if (ret)
  return ret;
  /* 设置设备打开标记,设备将设置IFF_UP标志位*/
 set_bit(__LINK_STATE_START, &dev->state);
 /* 校验地址 */
 if (ops->ndo_validate_addr)
  ret = ops->ndo_validate_addr(dev);
  /* 执行打开 */
 if (!ret && ops->ndo_open)
  ret = ops->ndo_open(dev);
 /* 启用netpoll */
 netpoll_poll_enable(dev);
 /* 失败,清除打开标记 */
 if (ret)
  clear_bit(__LINK_STATE_START, &dev->state);
    /* 设备打开操作 */
 else {
         /* 设置打开标记 */
  dev->flags |= IFF_UP;
         /* 设置接收模式 */
  dev_set_rx_mode(dev);
         /* 初始化排队规则 */
  dev_activate(dev);
        /* 加入设备数据到熵池 */
  add_device_randomness(dev->dev_addr, dev->addr_len);
 }

 return ret;
}

hns_nic_net_open()

我们以海思的网卡驱动为例,分析下ndo_open()函数的实现。代码位于kernel\drivers\net\ethernet\hisilicon\hns\hns_enet.c

static int hns_nic_net_open(struct net_device *ndev)
{
 struct hns_nic_priv *priv = netdev_priv(ndev);
 struct hnae_handle *h = priv->ae_handle;
 int ret;

 if (test_bit(NIC_STATE_TESTING, &priv->state))
  return -EBUSY;

 priv->link = 0;
 netif_carrier_off(ndev);
 /*设置tx queue的个数*/
 ret = netif_set_real_num_tx_queues(ndev, h->q_num);
 if (ret < 0) {
  netdev_err(ndev, "netif_set_real_num_tx_queues fail, ret=%d!\n",
      ret);
  return ret;
 }
 /*设置rx queue的个数*/
 ret = netif_set_real_num_rx_queues(ndev, h->q_num);
 if (ret < 0) {
  netdev_err(ndev,
      "netif_set_real_num_rx_queues fail, ret=%d!\n", ret);
  return ret;
 }
 /*启动网卡*/
 ret = hns_nic_net_up(ndev);
 if (ret) {
  netdev_err(ndev,
      "hns net up fail, ret=%d!\n", ret);
  return ret;
 }

 return 0;
}

hns_nic_net_up()

static int hns_nic_net_up(struct net_device *ndev)
{
 struct hns_nic_priv *priv = netdev_priv(ndev);
 struct hnae_handle *h = priv->ae_handle;
 int i, j, k;
 int ret;
 /*初始化中断,并设置中断函数为hns_irq_handle,每个rx和tx queue都对应一个中断*/
 ret = hns_nic_init_irq(priv);
 if (ret != 0) {
  netdev_err(ndev, "hns init irq failed! ret=%d\n", ret);
  return ret;
 }

 for (i = 0; i < h->q_num * 2; i++) {
        /*使能中断,使能napi*/
  ret = hns_nic_ring_open(ndev, i);
  if (ret)
   goto out_has_some_queues;
 }

 for (k = 0; k < h->q_num; k++)
  h->dev->ops->toggle_queue_status(h->qs[k], 1);
 /*设置mac地址*/
 ret = h->dev->ops->set_mac_addr(h, ndev->dev_addr);
 if (ret)
  goto out_set_mac_addr_err;
 /*hns的start函数为null*/
 ret = h->dev->ops->start ? h->dev->ops->start(h) : 0;
 if (ret)
  goto out_start_err;

 if (priv->phy)
        /*启动phy*/
  phy_start(priv->phy);

 clear_bit(NIC_STATE_DOWN, &priv->state);
    /*修改time 每一秒到期一次*/
 (void)mod_timer(&priv->service_timer, jiffies + SERVICE_TIMER_HZ);

 return 0;

out_start_err:
 netif_stop_queue(ndev);
out_set_mac_addr_err:
 for (k = 0; k < h->q_num; k++)
  h->dev->ops->toggle_queue_status(h->qs[k], 0);
out_has_some_queues:
 for (j = i - 1; j >= 0; j--)
  hns_nic_ring_close(ndev, j);

 set_bit(NIC_STATE_DOWN, &priv->state);

 return ret;
}

phy_start()

最后会调用到phy_start()启动网卡。

void phy_start(struct phy_device *phydev)
{
 bool do_resume = false;
 int err = 0;

 mutex_lock(&phydev->lock);

 switch (phydev->state) {
 case PHY_STARTING:
  phydev->state = PHY_PENDING;
  break;
 case PHY_READY:
  phydev->state = PHY_UP;
  break;
 case PHY_HALTED:
  /* make sure interrupts are re-enabled for the PHY */
  err = phy_enable_interrupts(phydev);
  if (err < 0)
   break;

  phydev->state = PHY_RESUMING;
  do_resume = true;
  break;
 default:
  break;
 }
 mutex_unlock(&phydev->lock);

 /* if phy was suspended, bring the physical link up again */
 if (do_resume)
  phy_resume(phydev);
}

参考

https://blog.csdn.net/qq_29044159/article/details/118030335

https://www.likecs.com/show-308571259.html

https://blog.csdn.net/Longyu_wlz/article/details/108026902

http://bbs.chinaunix.net/thread-2020457-1-1.html

https://blog.csdn.net/tiantao2012/article/details/75283527

https://blog.csdn.net/sinat_20184565/article/details/104353185

嵌入式艺术
MEET SPRING
万物更生,新岁开启

                               

    

分享

收藏

点赞

在看



嵌入式艺术 分享一些高级嵌入式相关知识,包括:计算机基础、操作系统、Linux驱动、Linux内核、RT-thread等,除此之外,并且会组织一些简单的训练项目,一起成长。 我的创作理念:专注分享高质量的嵌入式文章,让大家读有所得!
评论
  • 临近春节,各方社交及应酬也变得多起来了,甚至一月份就排满了各式约见。有的是关系好的专业朋友的周末“恳谈会”,基本是关于2025年经济预判的话题,以及如何稳定工作等话题;但更多的预约是来自几个客户老板及副总裁们的见面,他们为今年的经济预判与企业发展焦虑而来。在聊天过程中,我发现今年的聊天有个很有意思的“点”,挺多人尤其关心我到底是怎么成长成现在的多领域风格的,还能掌握一些经济趋势的分析能力,到底学过哪些专业、在企业管过哪些具体事情?单单就这个一个月内,我就重复了数次“为什么”,再辅以我上次写的:《
    牛言喵语 2025-01-22 17:10 68浏览
  •  万万没想到!科幻电影中的人形机器人,正在一步步走进我们人类的日常生活中来了。1月17日,乐聚将第100台全尺寸人形机器人交付北汽越野车,再次吹响了人形机器人疯狂进厂打工的号角。无独有尔,银河通用机器人作为一家成立不到两年时间的创业公司,在短短一年多时间内推出革命性的第一代产品Galbot G1,这是一款轮式、双臂、身体可折叠的人形机器人,得到了美团战投、经纬创投、IDG资本等众多投资方的认可。作为一家成立仅仅只有两年多时间的企业,智元机器人也把机器人从梦想带进了现实。2024年8月1
    刘旷 2025-01-21 11:15 502浏览
  • 数字隔离芯片是一种实现电气隔离功能的集成电路,在工业自动化、汽车电子、光伏储能与电力通信等领域的电气系统中发挥着至关重要的作用。其不仅可令高、低压系统之间相互独立,提高低压系统的抗干扰能力,同时还可确保高、低压系统之间的安全交互,使系统稳定工作,并避免操作者遭受来自高压系统的电击伤害。典型数字隔离芯片的简化原理图值得一提的是,数字隔离芯片历经多年发展,其应用范围已十分广泛,凡涉及到在高、低压系统之间进行信号传输的场景中基本都需要应用到此种芯片。那么,电气工程师在进行电路设计时到底该如何评估选择一
    华普微HOPERF 2025-01-20 16:50 76浏览
  •     IPC-2581是基于ODB++标准、结合PCB行业特点而指定的PCB加工文件规范。    IPC-2581旨在替代CAM350格式,成为PCB加工行业的新的工业规范。    有一些免费软件,可以查看(不可修改)IPC-2581数据文件。这些软件典型用途是工艺校核。    1. Vu2581        出品:Downstream     
    电子知识打边炉 2025-01-22 11:12 65浏览
  •  光伏及击穿,都可视之为 复合的逆过程,但是,复合、光伏与击穿,不单是进程的方向相反,偏置状态也不一样,复合的工况,是正偏,光伏是零偏,击穿与漂移则是反偏,光伏的能源是外来的,而击穿消耗的是结区自身和电源的能量,漂移的载流子是 客席载流子,须借外延层才能引入,客席载流子 不受反偏PN结的空乏区阻碍,能漂不能漂,只取决于反偏PN结是否处于外延层的「射程」范围,而穿通的成因,则是因耗尽层的过度扩张,致使跟 端子、外延层或其他空乏区 碰触,当耗尽层融通,耐压 (反向阻断能力) 即告彻底丧失,
    MrCU204 2025-01-17 11:30 186浏览
  • Ubuntu20.04默认情况下为root账号自动登录,本文介绍如何取消root账号自动登录,改为通过输入账号密码登录,使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统,接口丰富,开发评估快人一步!添加新账号1、使用adduser命令来添加新用户,用户名以industio为例,系统会提示设置密码以及其他信息,您可以根据需要填写或跳过,命令如下:root@id
    Industio_触觉智能 2025-01-17 14:14 125浏览
  • 2024年是很平淡的一年,能保住饭碗就是万幸了,公司业绩不好,跳槽又不敢跳,还有一个原因就是老板对我们这些员工还是很好的,碍于人情也不能在公司困难时去雪上加霜。在工作其间遇到的大问题没有,小问题还是有不少,这里就举一两个来说一下。第一个就是,先看下下面的这个封装,你能猜出它的引脚间距是多少吗?这种排线座比较常规的是0.6mm间距(即排线是0.3mm间距)的,而这个规格也是我们用得最多的,所以我们按惯性思维来看的话,就会认为这个座子就是0.6mm间距的,这样往往就不会去细看规格书了,所以这次的运气
    wuliangu 2025-01-21 00:15 190浏览
  • 本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。源码修改修改源码根目录下文件device/rockchip/rk3562/package_performance.xml并添加以下内容,注意"+"号为添加内容,"com.tencent.mm"为AP
    Industio_触觉智能 2025-01-17 14:09 167浏览
  • 日前,商务部等部门办公厅印发《手机、平板、智能手表(手环)购新补贴实施方案》明确,个人消费者购买手机、平板、智能手表(手环)3类数码产品(单件销售价格不超过6000元),可享受购新补贴。每人每类可补贴1件,每件补贴比例为减去生产、流通环节及移动运营商所有优惠后最终销售价格的15%,每件最高不超过500元。目前,京东已经做好了承接手机、平板等数码产品国补优惠的落地准备工作,未来随着各省市关于手机、平板等品类的国补开启,京东将第一时间率先上线,满足消费者的换新升级需求。为保障国补的真实有效发放,基于
    华尔街科技眼 2025-01-17 10:44 221浏览
  • 现在为止,我们已经完成了Purple Pi OH主板的串口调试和部分配件的连接,接下来,让我们趁热打铁,完成剩余配件的连接!注:配件连接前请断开主板所有供电,避免敏感电路损坏!1.1 耳机接口主板有一路OTMP 标准四节耳机座J6,具备进行音频输出及录音功能,接入耳机后声音将优先从耳机输出,如下图所示:1.21.2 相机接口MIPI CSI 接口如上图所示,支持OV5648 和OV8858 摄像头模组。接入摄像头模组后,使用系统相机软件打开相机拍照和录像,如下图所示:1.3 以太网接口主板有一路
    Industio_触觉智能 2025-01-20 11:04 157浏览
  • 高速先生成员--黄刚这不马上就要过年了嘛,高速先生就不打算给大家上难度了,整一篇简单但很实用的文章给大伙瞧瞧好了。相信这个标题一出来,尤其对于PCB设计工程师来说,心就立马凉了半截。他们辛辛苦苦进行PCB的过孔设计,高速先生居然说设计多大的过孔他们不关心!另外估计这时候就跳出很多“挑刺”的粉丝了哈,因为翻看很多以往的文章,高速先生都表达了过孔孔径对高速性能的影响是很大的哦!咋滴,今天居然说孔径不关心了?别,别急哈,听高速先生在这篇文章中娓娓道来。首先还是要对各位设计工程师的设计表示肯定,毕竟像我
    一博科技 2025-01-21 16:17 105浏览
  • 嘿,咱来聊聊RISC-V MCU技术哈。 这RISC-V MCU技术呢,简单来说就是基于一个叫RISC-V的指令集架构做出的微控制器技术。RISC-V这个啊,2010年的时候,是加州大学伯克利分校的研究团队弄出来的,目的就是想搞个新的、开放的指令集架构,能跟上现代计算的需要。到了2015年,专门成立了个RISC-V基金会,让这个架构更标准,也更好地推广开了。这几年啊,这个RISC-V的生态系统发展得可快了,好多公司和机构都加入了RISC-V International,还推出了不少RISC-V
    丙丁先生 2025-01-21 12:10 118浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦