【openwrt学习笔记】dnsmasq源码阅读

news2025/1/10 10:16:37

目录

    • 一、DHCP(Dynamic Host Configuration Protocol)
      • 1.1 前置知识
      • 1.2 参考链接
      • 1.3 IP地址分配代码分析
        • rfc2131.c
        • dhcp-common.c
        • dhcp.c
      • 1.4 几个小问题
        • 1.4.1 连续IP模式(sequential_ip)
        • 1.4.2 重新连接使用IP地址
        • 1.4.3 续约租期
        • 1.4.4 不同的MAC地址分配到相同IP

一、DHCP(Dynamic Host Configuration Protocol)

1.1 前置知识

之前也学习了一下,总结了一些概念和抓包分析,此处不赘述。
DHCP和PPPoE协议以及抓包分析

1.2 参考链接

24-Openwrt dnsmasq
DNS and DHCP configuration
rfc2131文档
DHCP协议详解

1.3 IP地址分配代码分析

本次着重看了这一块代码,其它部分再后续补充。
吐槽一下:源代码的格式真是一言难尽,缩进乱七八糟,而且有的空格有的tab看着也难受,格式不标准,有的都不能正确缩放,还是让GPT转化了一下再看的。

rfc2131.c

if (mess_type == 0 && !pxe) {
    /* BOOTP request */
    struct dhcp_netid id, bootp_id;
    struct in_addr *logaddr = NULL;

    /* must have a MAC addr for bootp */
    if (mess->htype == 0 || mess->hlen == 0 || (context->flags & CONTEXT_PROXY))
        return 0;

    if (have_config(config, CONFIG_DISABLE))
        message = _("disabled");

    end = mess->options + 64; /* BOOTP vend area is only 64 bytes */

//如果配置中设置了 CONFIG_NAME 标志,则设置主机名(hostname)和域名(domain)。
    if (have_config(config, CONFIG_NAME)) {
        hostname = config->hostname;
        domain = config->domain;
    }
//遍历配置中的网络标识列表(netid),并将它们添加到当前的 netid 列表中
    if (config) {
        struct dhcp_netid_list *list;
        
        for (list = config->netid; list; list = list->next) {
            list->list->next = netid;
            netid = list->list;
        }
    }

    /* Match incoming filename field as a netid. */
    if (mess->file[0]) {
        memcpy(daemon->dhcp_buff2, mess->file, sizeof(mess->file));
        daemon->dhcp_buff2[sizeof(mess->file) + 1] = 0; /* ensure zero term. */
        id.net = (char *)daemon->dhcp_buff2;
        id.next = netid;
        netid = &id;
    }

    /* Add "bootp" as a tag to allow different options, address ranges etc for BOOTP clients */
    bootp_id.net = "bootp";
    bootp_id.next = netid;
    netid = &bootp_id;

//运行标识的处理函数(run_tag_if)以获取最终的 netid 列表。
    tagif_netid = run_tag_if(netid);

//遍历 dhcp_ignore 列表,如果与 netid 列表中的标识匹配,则将消息设置为 "ignored"。
    for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next) {
        if (match_netid(id_list->list, tagif_netid, 0)) {
            message = _("ignored");
            break;
        }
    }

    if (!message) {
        int nailed = 0;
//检查是否已配置 IP 地址(have_config(config, CONFIG_ADDR))。如果配置了,尝试分配指定的 IP 地址。
        if (have_config(config, CONFIG_ADDR)) {
            nailed = 1;
            logaddr = &config->addr;
            mess->yiaddr = config->addr;
            if ((lease = lease_find_by_addr(config->addr)) &&
                (lease->hwaddr_len != mess->hlen ||
                lease->hwaddr_type != mess->htype ||
                memcmp(lease->hwaddr, mess->chaddr, lease->hwaddr_len) != 0))
            {
                message = _("address in use");
            }
        }
        else {
//如果没有配置 IP 地址,尝试从已分配的地址中查找与客户端 MAC 地址匹配的租约(lease_find_by_client)
            if (!(lease = lease_find_by_client(mess->chaddr, mess->hlen, mess->htype, NULL, 0)) ||
                !address_available(context, lease->addr, tagif_netid))
            {
                if (lease) {
                    /* lease exists, wrong network. */
                    lease_prune(lease, now);
                    lease = NULL;
                }
//如果没有找到匹配的租约或该地址不可用(address_available),则尝试分配一个新的 IP 地址
                if (!address_allocate(context, &mess->yiaddr, mess->chaddr, mess->hlen, tagif_netid, now, loopback)) {
                    message = _("no address available");
                }
            }
            else {
                mess->yiaddr = lease->addr;
            }
        }
        
//如果分配了 IP 地址,检查该地址是否在正确的网络上(narrow_context)。如果不在正确的网络上,设置错误消息为 "wrong network"。
        if (!message && !(context = narrow_context(context, mess->yiaddr, netid))) {
            message = _("wrong network");
        }
        //更新 netid 列表并重新运行标识处理函数(run_tag_if)。
        else if (context->netid.net) {
            context->netid.next = netid;
            tagif_netid = run_tag_if(&context->netid);
        }

        log_tags(tagif_netid, ntohl(mess->xid));

//遍历 bootp_dynamic 列表,检查是否配置了与 netid 列表匹配的地址。如果没有找到,则设置错误消息为 "no address configured"。
        if (!message && !nailed) {
            for (id_list = daemon->bootp_dynamic; id_list; id_list = id_list->next) {
                if ((!id_list->list) || match_netid(id_list->list, tagif_netid, 0)) {
                    break;
                }
            }
            
            if (!id_list) {
                message = _("no address configured");
            }
        }

//如果没有错误消息,并且没有租约可用(!lease),尝试为分配的 IP 地址创建一个租约(lease4_allocate)。
        if (!message && !lease && !(lease = lease4_allocate(mess->yiaddr))) {
            message = _("no leases left");
        }

//如果成功分配租约,设置租约的硬件地址、主机名、租约到期时间等信息。
        if (!message) {
            logaddr = &mess->yiaddr;

            lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0, now, 1);
            
            if (hostname) {
                lease_set_hostname(lease, hostname, 1, get_domain(lease->addr), domain);
            }

            /* infinite lease unless nailed in dhcp-host line. */
            lease_set_expires(lease,  
                              have_config(config, CONFIG_TIME) ? config->lease_time : 0xffffffff, 
                              now);

            lease_set_interface(lease, int_index, now);

//清空消息中的选项(do_options),为客户端提供配置选项。
            clear_packet(mess, end);
            do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr), 
                       netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0);
        }
    }

    daemon->metrics[METRIC_BOOTP]++;
    log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, NULL, message, mess->xid);

    return message ? 0 : dhcp_packet_size(mess, agent_id, real_end);
}

然后看一个各个函数的具体实现:

dhcp-common.c

//根据匹配条件更新给定的网络标识列表,并重新运行标识处理逻辑。
struct dhcp_netid *run_tag_if(struct dhcp_netid *tags)
{
  struct tag_if *exprs;
  struct dhcp_netid_list *list;

  // 遍历服务器中定义的标识处理表达式(tag_if)
  for (exprs = daemon->tag_if; exprs; exprs = exprs->next) {
    // 检查当前标识处理表达式是否与当前网络标识列表匹配
    if (match_netid(exprs->tag, tags, 1)) {
      // 对于匹配的表达式,遍历操作集合(set)
      for (list = exprs->set; list; list = list->next) {
        // 将操作列表中的操作添加到网络标识列表(tags)的开头
        list->list->next = tags;
        tags = list->list;
      }
    }
  }

  // 返回更新后的网络标识列表(tags)
  return tags;
}

dhcp.c

struct dhcp_context *address_available(struct dhcp_context *context,
                                       struct in_addr taddr,
                                       struct dhcp_netid *netids)
{
  /* 检查地址是否适用于此网络,检查所有可能的范围。
     确保该地址没有被服务器自身使用。 */
  
  unsigned int start, end, addr = ntohl(taddr.s_addr);
  struct dhcp_context *tmp;

  // 检查提供的地址是否与服务器的路由器地址匹配
  for (tmp = context; tmp; tmp = tmp->current) {
    if (taddr.s_addr == context->router.s_addr) {
      return NULL; // 服务器的路由器地址,不可用
    }
  }
  
  // 遍历每个上下文及其地址范围,以查找可用的地址
  for (tmp = context; tmp; tmp = tmp->current) {
    start = ntohl(tmp->start.s_addr);
    end = ntohl(tmp->end.s_addr);

    // 检查地址是否在当前上下文的范围内且匹配给定的网络标识
    if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) &&
        addr >= start &&
        addr <= end &&
        match_netid(tmp->filter, netids, 1)) {
      return tmp; // 地址在此上下文中可用
    }
  }

  return NULL; // 地址不可用
}
int address_allocate(struct dhcp_context *context,
                     struct in_addr *addrp, unsigned char *hwaddr, int hw_len,
                     struct dhcp_netid *netids, time_t now, int loopback)
{
  /* 寻找一个可用的地址:排除正在使用的地址和分配给特定硬件地址/客户端标识/主机名的地址
     首先尝试返回与netids匹配的上下文。 */

  struct in_addr start, addr;
  struct dhcp_context *c, *d;
  int i, pass;
  unsigned int j;

  /* 对hwaddr进行哈希:使用SDBM哈希算法。
     即使在具有类似值的“字符串”上,似乎也能得到良好的分散效果。 */
  for (j = 0, i = 0; i < hw_len; i++)
    j = hwaddr[i] + (j << 6) + (j << 16) - j;

  /* j == 0 是标记 */
  if (j == 0)
    j = 1;

  for (pass = 0; pass <= 1; pass++)
    for (c = context; c; c = c->current) {
      if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
        continue;
      else if (!match_netid(c->filter, netids, pass))
        continue;
      else {
        if (option_bool(OPT_CONSEC_ADDR))
          /* 种子是此上下文中最大的现存租约地址 */
          start = lease_find_max_addr(c);
        else
          /* 基于hwaddr选择种子 */
          start.s_addr = htonl(ntohl(c->start.s_addr) +
                               ((j + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr))));

        /* 循环,直到找到一个可用的地址。 */
        addr = start;

        do {
          /* 排除服务器正在使用的地址。 */
          for (d = context; d; d = d->current) {
            if (addr.s_addr == d->router.s_addr) {
              break;
            }
          }

          /* 以.255和.0结尾的地址在Windows上有问题,即使使用超网也是如此。
             例如,dhcp-range=192.168.0.1,192.168.1.254,255,255,254.0
             那么192.168.0.255是有效的IP地址,但在Windows上却不是,因为它在C类范围内。
             请参阅KB281579。因此,我们不分配这些地址,以避免难以诊断的问题。感谢Bill。 */
          if (!d &&
              !lease_find_by_addr(addr) &&
              !config_find_by_address(daemon->dhcp_conf, addr) &&
              (!IN_CLASSC(ntohl(addr.s_addr)) ||
               ((ntohl(addr.s_addr) & 0xff) != 0xff && ((ntohl(addr.s_addr) & 0xff) != 0x0)))) {
            /* 在连续IP模式下,跳过等于客户端拒绝的地址数量的地址。
               这应该避免同一客户端在拒绝地址后再次被分配相同的地址。 */
            if (option_bool(OPT_CONSEC_ADDR) && c->addr_epoch) {
              c->addr_epoch--;
            } else {
              struct ping_result *r;

              if ((r = do_icmp_ping(now, addr, j, loopback))) {
                /* 连续IP模式:我们最近为另一个客户端提供了此地址(不同的哈希),
                   不要再次提供给此客户端。 */
                if (!option_bool(OPT_CONSEC_ADDR) || r->hash == j) {
                  *addrp = addr;
                  return 1;
                }
              } else {
                /* 地址正在使用中:扰动地址选择,以便不太可能再次尝试此地址。 */
                if (!option_bool(OPT_CONSEC_ADDR)) {
                  c->addr_epoch++;
                }
              }
            }
          }

          addr.s_addr = htonl(ntohl(addr.s_addr) + 1);

          if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1)) {
            addr = c->start;
          }
        } while (addr.s_addr != start.s_addr);
      }
    }

  return 0; // 无可用地址
}

上述算法过程举例如下,假设有一个 DHCP 上下文 c,其中包含以下信息:

  • c->start.s_addr 表示分配地址的起始地址,假设为 192.168.1.100(以网络字节序表示)。
  • c->end.s_addr 表示分配地址的结束地址,假设为 192.168.1.200(以网络字节序表示)。
  • c->addr_epoch 是一个地址时代值,假设为 5。
  • j 是之前计算得到的 SDBM 哈希值,假设为 12345。

现在我们来演示如何通过上述代码生成一个新的分配地址:

  1. 计算地址范围内的总地址数:1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr) = 1 + 192.168.1.200 - 192.168.1.100 = 101

  2. 计算 SDBM 哈希值和地址时代的影响:(j + c->addr_epoch) % (1 + 101) = (12345 + 5) % 102 = 12350 % 102 = 46

  3. 计算新的分配地址的偏移量:ntohl(c->start.s_addr) + 46 = 3232235876 + 46 = 3232235922

  4. 将计算得到的偏移量转换为网络字节序:htonl(3232235922) = 192.168.1.122

因此,根据上述计算,生成的新分配地址将是 192.168.1.122。这个过程确保了生成的地址在指定的地址范围内,同时通过 SDBM 哈希值和地址时代进行了调整。

struct dhcp_context *narrow_context(struct dhcp_context *context,
                                    struct in_addr taddr,
                                    struct dhcp_netid *netids)
{
  /* 我们从一组可能的上下文开始,所有这些上下文都在当前的物理接口上。
     这些上下文通过 ->current 进行链接。
     在这里,我们有一个地址,并返回与该地址对应的实际上下文。
     请注意,如果地址来自dhcp-host,并且位于任何dhcp-range之外,
     则可能没有匹配的上下文。在这种情况下,如果可能的话,我们会返回静态范围,
     或者如果失败的话,返回正确子网上的任何上下文。
     (如果有多个上下文,这是一个不稳定的配置:也许应该有一个警告。) */

  struct dhcp_context *tmp;

  if (!(tmp = address_available(context, taddr, netids))) {
    for (tmp = context; tmp; tmp = tmp->current) {
      if (match_netid(tmp->filter, netids, 1) &&
          is_same_net(taddr, tmp->start, tmp->netmask) &&
          (tmp->flags & CONTEXT_STATIC)) {
        break;
      }
    }

    if (!tmp) {
      for (tmp = context; tmp; tmp = tmp->current) {
        if (match_netid(tmp->filter, netids, 1) &&
            is_same_net(taddr, tmp->start, tmp->netmask) &&
            !(tmp->flags & CONTEXT_PROXY)) {
          break;
        }
      }
    }
  }

  /* 现在只允许一个上下文 */
  if (tmp) {
    tmp->current = NULL;
  }

  return tmp;
}

narrow_context 的函数,用于根据给定的地址和网络标识来缩小上下文的范围。它首先尝试根据给定的地址和网络标识来查找可用的上下文。如果找不到匹配的可用上下文,则会尝试在同一子网中查找静态范围,或者如果没有静态范围,则查找任何与正确子网匹配的上下文。最后,函数会将选定的上下文链表中的 current 指针设置为 NULL,以确保只返回一个上下文。函数将返回选定的上下文,如果找不到合适的上下文,则返回 NULL。

1.4 几个小问题

1.4.1 连续IP模式(sequential_ip)

上述代码中有提到这个模式,openwrt中对应的配置字段就是sequential_ip。对应的描述如下:

NameTypeDefaultOptionDescription
sequential_ipboolean0–dhcp-sequential-ipDnsmasq is designed to choose IP addresses for DHCP clients using a hash of the client’s MAC address. This normally allows a client’s address to remain stable long-term, even if the client sometimes allows its DHCP lease to expire. In this default mode IP addresses are distributed pseudo-randomly over the entire available address range. There are sometimes circumstances (typically server deployment) where it is more convenient to have IP addresses allocated sequentially, starting from the lowest available address, and setting this parameter enables this mode. Note that in the sequential mode, clients which allow a lease to expire are much more likely to move IP address; for this reason it should not be generally used.
Dnsmasq用于使用客户端MAC地址的哈希为DHCP客户端选择IP地址。这通常允许客户端的地址长期保持稳定,即使客户端有时允许其DHCP租约到期。在此默认模式下,IP地址在整个可用地址范围内伪随机分布。有时在某些情况下(通常是服务器部署),从最低可用地址开始按顺序分配IP地址更方便,并且设置此参数可以启用此模式。请注意,在顺序模式中,允许租约到期的客户端更有可能移动IP地址;由于这个原因,它不应该被普遍使用。

在这里插入图片描述

可以看到按顺序分配,设置成功
在这里插入图片描述

1.4.2 重新连接使用IP地址

在RFC 2131文档第3.2节描述了客户端重新使用先前分配的网络地址时的客户端-服务器交互过程:

根据文档中的描述,如果客户端希望重新连接并使用先前分配的网络地址,客户端可以选择省略先前部分描述的一些步骤。客户端可以通过向服务器发送DHCPREQUEST消息来请求使用先前分配的网络地址。在DHCPREQUEST消息中,客户端将其网络地址填入“请求的IP地址”选项中,并且不填写“ciaddr”字段。服务器收到DHCPREQUEST消息后,如果具有客户端的配置参数信息,则会向客户端发送DHCPACK消息服务器不应检查客户端的网络地址是否已被使用。客户端在收到DHCPACK消息后,将配置参数应用于自身,并记录DHCPACK消息中指定的租约持续时间。此时,客户端已经重新连接并配置完成。

请注意,根据文档中的描述,如果客户端在DHCPACK消息中检测到分配的IP地址已经被使用,则客户端必须发送DHCPDECLINE消息给服务器,并重新启动配置过程以请求新的网络地址。如果客户端收到DHCPNAK消息,则表示无法重新使用先前分配的网络地址,客户端必须重新启动配置过程,并按照文档中描述的完整流程进行操作。

在这里插入图片描述
重用以前分配的网络地址时,DHCP客户端和服务器之间交换的消息时间轴图

注:客户端通常不会在正常关闭期间放弃其租约。只有在客户端明确需要放弃其租期的情况下,例如,客户端即将移动到另一个子网,客户端才会发送DHCPRELEASE消息。

1.4.3 续约租期

在RFC 2131文档的第4.4.5节中描述了租期续约的过程:

  1. 客户端在T1之前选择续约或延长租期:客户端可以选择在T1之前续约或延长租期。这意味着客户端可以在租期即将到期之前主动向服务器发送DHCPREQUEST消息来请求续约。

  2. 服务器根据网络管理员设置的策略来决定是否延长租期:服务器可以根据网络管理员设置的策略来决定是否延长客户端的租期。这意味着服务器可以根据自己的策略来决定是否接受客户端的续约请求。

  3. 服务器应返回调整后的T1和T2的值:服务器应返回调整后的T1和T2的值,以考虑租期剩余的时间。这意味着服务器应根据租期剩余时间来调整T1和T2的值,并将其返回给客户端。

  4. 在续约和重新绑定状态下,如果客户端收不到DHCPREQUEST消息的响应,客户端应等待剩余时间的一半(在续约状态下是T2的一半,在重新绑定状态下是租期剩余时间的一半),直到最少等待60秒,然后重新发送DHCPREQUEST消息。

具体有以下三种情况:
(1)当clientIP地址已经用到50%的时间,续租一下,client端就会以单播形式向服务端发送一个DHCP Request包,当server响应时就会回应一个ACK包,会重新约定一个时间。

(2)当clientIP地址已经用到50%的时间,续租一下,client端就会以单播形式向服务端发送一个DHCP Request包,server没有响应,client会继续使用,当使用到87.5%时,会在续租一次,同时就以广播的方式是发送一个request包,server这时收到响应以后,就会回应一个ACK包,重新约定一个时间。

(3)当clientIP地址已经用到50%的时间,续租一下,client端就会以单播形式向服务端发送一个DHCP Request包,server没有响应,client会继续使用,当使用到87.5%时,会在续租一次,同时就以广播的方式是发送一个request包,如果server还是没有响应,client那就直接使用到过期。

在这里插入图片描述
DHCP客户端的状态转换图

1.4.4 不同的MAC地址分配到相同IP

这是无意间收到的一个提问,可以参考学习。
multiple offers with same IP to different MAC addresses

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/876490.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Power Automate:筛选查找列表中的项并删除

目的&#xff1a;筛选出列表中所有符合条件的项并删除&#xff0c;如果一项都没有&#xff0c;就发邮件通知自己 1、首先获取多个项&#xff0c;添加筛选条件&#xff0c;比如设备列为1的项&#xff0c;无需加引号。也可以添加筛选条件 2、接下来不能直接循环刚得到的多个项并…

NuGet包离线安装方法

在某项情况下&#xff0c;我们的计算机是无法直接连接外网的&#xff0c;这个时候就只能用离线安装的方法了。 一、直接区NUGET.org网页下载&#xff1a; 二、先下载nuget.exe工具&#xff0c;然后用这个工具下载 把下载的nuget.exe放在任意目录下&#xff0c;然后在此目录用…

MongoDB升级经历(4.0.23至5.0.19)

MongoDB从4.0.23至5.0.19升级经历 引子&#xff1a;为了解决MongoDB的两个漏洞决定把MongoDB升级至最新版本&#xff0c;期间也踩了不少坑&#xff0c;在这里分享出来供大家学习与避坑~ 1、MongoDB的两个漏洞 漏洞1&#xff1a;MongoDB Server 安全漏洞(CVE-2021-20330) 漏洞2…

如何在Stream流中分组统计

上面是今天碰到需求,之前就做过类似的分组统计,这个相对来说比较简单,统计的也少,序号和总预约人数这两部分交给前端了,不需要由后端统计,后端统计一下预约日期和检查项目和预约人数就行; Overridepublic List<ItemStatisticsVo> statistics(ItemStatisticsModel itemSta…

checkbox post参数接收

checkbox 定义 <div class"check-box"> <label for"ck1">batchInsert:</label><input type"checkbox" id"ck1" checkedname"ckFn" value"batchInsert" > </div> <div class&qu…

Python爬虫获取美女头像并保存本地(观山篇一)

Python爬虫获取美女头像并保存本地&#xff08;观山篇一&#xff09; 前言步骤一步骤二步骤三步骤四步骤五最终效果完整代码结言 前言 最近某短视频平台上经常刷到&#xff0c;人生四大雅事&#xff1a;“品茗、抚琴、观山、听雨”。那么今天我们就利用python观山所看到的美景给…

如何做好一名网络工程师?具备的技能有哪些?

支持属于网络工程师的工作范围的企业网络&#xff0c;此网络与支持它的铜或光纤基础架构一样性能良好。网络工程师及其布线厂区需要为支持最新网络技术做好准备。网络工程师作为任何性能问题的解决者&#xff0c;需要拥有必要的工具来确定问题所在 — 在网络中还是在其他地方。…

在众多单片机市场中STM32系列为何能脱颖而出?

回顾单片机市场&#xff0c;除了传统的51系列外&#xff0c;早些年主要有PIC、TI、Nxp、ATMEL、Freescale等厂商。然而&#xff0c;这些厂商普遍存在一些问题&#xff1a;资料难以获取&#xff0c;文档数量有限且大多是英文的&#xff0c;开发板价格昂贵&#xff0c;调试器成本…

Mac 新手10个小窍门

即便你是 Mac 新手&#xff0c;也会发现它易学好用。你可以点按程序坞上的访达&#xff0c;快速查看到所有文件&#xff1b;把你喜爱的文件夹拖入边栏&#xff1b;你可以自定义查看文件的方式&#xff0c;甚至可以按下空格键&#xff0c;就能一键预览文档&#xff1b;还能在多台…

Linux 复制进程fork

一、父进程和子进程 当前的一个进程在fork的时候可以复制当前的进程产生一个进程&#xff0c;这时产生出来的这个进程就是子进程&#xff0c;被复制的进程叫做父进程。子进程会将环境变量从父进程继承过来&#xff0c;或者说被拷贝过来。父进程也会有它的父进程&#xff0c;一…

【动态规划基础】数字三角形(IOI1994)

题目描述 数字三角形 输入输出样例 输入样例#1&#xff1a; 5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5输出样例#1&#xff1a; 30思路&#xff1a; 这题可能看到的第一眼——直接贪心然后一层一层判断呀&#xff01;&#xff01;&#xff01;不过很快又会发现&#xff0c;额___好…

小白如何轻松制作产品帮助中心页面?

产品帮助中心是每个网站/产品必不可少的页面&#xff0c;产品帮助中心页面成为了企业提供客户支持和解决方案的重要组成部分。对于初次接触建立帮助中心页面的小白来说&#xff0c;也许会感到一些困惑和无从下手。本文将为小白介绍如何轻松制作产品帮助中心页面&#xff0c;帮助…

16 dplsys GAN

和有监督的分类工作不同&#xff0c;生成任务的目标更不明确。难以评价生成结果的好坏。 Oracle discriminator 假设我们有一个先知判别器oracle discriminator可以分辨我们生成的内容是真还是假。 我们想让生产成的结果足够真实&#xff0c;所以要 fool Oracle discriminato…

将el-table中的展开列(expand)修改成slots自定义插槽

用过element-ui的有知道&#xff0c;展开这个箭头无法自定义&#xff0c;一点办法都没有&#xff0c;官方根本就没提供预留任何位置给你操作。 从下面图中&#xff0c;可以看到有两个插槽&#xff0c;默认插槽和表头插槽。 我们来扩展一个自定义插槽来实现我们想要的功能。…

【dubbo】自定义filter打印接口请求日志

目标 在应用调用dubbo接口或提供的dubbo接口被调用时&#xff0c;通过自定义的filter打印接口请求时的入参信息、服务名、方法名及返回值 实现filter 1. 自定义filter实现类 Slf4j Activate(group {"provider", "consumer"}) public class DubboProvi…

展会动态 | 迪捷软件邀您参加2023ATC汽车电子软件与软件技术周

论坛背景 随着汽车智能化和电动化的发展&#xff0c;汽车电子与软件技术已经成为汽车产业的重点方向。为促进行业创新发展&#xff0c;各大汽车厂商、供应链企业和研究机构都在积极探索新的技术和应用。 2023ATC汽车电子与软件技术周&#xff08;以下简称“技术周”&#xff…

pytest的fixture梳理

fixture特性 可以重复使用&#xff0c;多个用例可以使用同一个fixture一个测试用例可以使用多个装置 import pytest # Arrange pytest.fixture def first_entry():return "a"# Arrange pytest.fixture def second_entry():return 2# Arrange pytest.fixture def or…

Linux常用命令——dmidecode命令

在线Linux命令查询工具 dmidecode 在Linux系统下获取有关硬件方面的信息 补充说明 dmidecode命令可以让你在Linux系统下获取有关硬件方面的信息。dmidecode的作用是将DMI数据库中的信息解码&#xff0c;以可读的文本方式显示。由于DMI信息可以人为修改&#xff0c;因此里面…

【数据结构与算法】二叉搜索树

文章目录 二叉搜索树的结构二叉搜索树的实现节点的定义二叉搜索树的框架构造函数拷贝构造函数赋值运算符重载析构函数搜索操作插入操作删除操作 二叉搜索树的应用二叉搜索树的效率 二叉搜索树的结构 在浅学一下二叉树链式存储结构的遍历_链式存储二叉树按层次遍历_LeePlace的博…

【SpringBoot】| 接口架构风格—RESTful

目录 一&#xff1a;接口架构风格—RESTful 1. 认识RESTful 2. RESTful 的注解 一&#xff1a;接口架构风格—RESTful 1. 认识RESTful &#xff08;1&#xff09;接口 ①接口&#xff1a; API&#xff08;Application Programming Interface&#xff0c;应用程序接口&…