Linux: Netlink 简介

文章目录

  • 1. 前言
  • 2. Netlink 范例
  • 3. Netlink 简析
    • 3.1 Netlink 协议簇注册
    • 3.2 创建 用户空间 Netlink 套接字
    • 3.3 用户空间 Netlink 套接字 的 绑定
    • 3.4 向 内核空间 Netlink 套接字 发消息
    • 3.5 从 内核空间 Netlink 套接字 读消息
      • 3.5.1 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息
      • 3.5.2 用户空间 Netlink 套接字 读取 内核空间 Netlink 套接字 发送的消息
  • 4. 参考资料

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. Netlink 范例

Netlink 通信包括 内核空间用户空间 两部分,具体来讲,是两个分别位于 内核空间用户空间AF_NETLINK 协议簇套接字。来看一个具体的例子,先看 内核空间 部分 netlink_kern_test.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>extern struct net init_net;#define NETLINK_TEST	30
#define MSG_LEN			100
#define USER_PORT		66static struct sock *test_nlsk;int send_usrmsg(char *buf, uint16_t len)
{struct sk_buff *nl_skb;struct nlmsghdr *nlh;nl_skb = nlmsg_new(len, GFP_ATOMIC);if (!nl_skb) {pr_err("netlink alloc failure\n");return -1;}nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);if (nlh == NULL) {pr_err("nlmsg_put failure\n");nlmsg_free(nl_skb);return -1;}memcpy(nlmsg_data(nlh), buf, len);return netlink_unicast(test_nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);
}static void netlink_rcv_msg(struct sk_buff *skb)
{   struct nlmsghdr *nlh;char *umsg = NULL;char *kmsg = "Hello user process!";nlh = nlmsg_hdr(skb);umsg = NLMSG_DATA(nlh);if (umsg) {printk("kernel recv msg from user: %s\n", umsg);printk("port id: %d\n", NETLINK_CB(skb).portid);send_usrmsg(kmsg, strlen(kmsg));}
}static int __init test_netlink_init(void)
{struct netlink_kernel_cfg cfg = {.input = netlink_rcv_msg,};test_nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);return test_nlsk ? 0 : -1;
}static void __exit test_netlink_exit(void)
{netlink_kernel_release(test_nlsk);
}module_init(test_netlink_init);
module_exit(test_netlink_exit);MODULE_LICENSE("GPL");

内核 Netlink 示例模块的逻辑很简单,调用接口 netlink_kernel_create() 创建一个 Netlink 内核空间套接字,且其协议类型为自定的 NETLINK_TEST,其数据接收接口为 netlink_rcv_msg() 。当其它 Netlink 套接字向它发送数据时,内核调用 netlink_rcv_msg() 处理数据接收,此时可通过调用 netlink_unicast() 向数据发送套接字回送数据。
接下来构建一个 Makefile 来编译这个内核模块,并安装到系统。Makefile 内容如下:

ifneq ($(KERNELRELEASE),)obj-m	:= netlink_kern_test.oelseKERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendifclean:rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions .cache.mk modules.order Module.symvers
$ make
$ sudo insmod netlink_kern_test.ko

再看 Netlink 示例的用户控件部分 netlink_user_test.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>#define NETLINK_TEST 30
#define USER_PORT 66#define MSG_LEN 100
#define MAX_PLOAD 200struct netlink_user_msg {struct nlmsghdr hdr;char msg[MSG_LEN];
};int main(int argc,char **argv)
{int sockfd;struct sockaddr_nl saddr, daddr;struct nlmsghdr *nlh;struct netlink_user_msg u_info;char *msg = "Hello kernel, I am a user process!";socklen_t len;// 创建 Netlink 用户空间套接字sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);if (sockfd < 0) {perror("socket");return -1;}// 数据 源 Netlink 套接字 地址memset(&saddr, 0, sizeof(saddr));saddr.nl_family = AF_NETLINK;saddr.nl_pad = 0;saddr.nl_pid = USER_PORT; // 端口号saddr.nl_groups = 0;if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {perror("socket");return -1;}// Netlink 消息数据nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));memset(nlh, 0, sizeof(struct nlmsghdr));nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);nlh->nlmsg_flags = 0;nlh->nlmsg_type = 0;nlh->nlmsg_seq = 0;nlh->nlmsg_pid = saddr.nl_pid;memcpy(NLMSG_DATA(nlh), msg, strlen(msg));// 数据 目的 Netlink 套接字 地址memset(&daddr, 0, sizeof(daddr));daddr.nl_family = AF_NETLINK;daddr.nl_pid = 0; /* 0 表示数据发往 内核空间 Netlink 套接字 */daddr.nl_groups = 0;sendto(sockfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));printf("send kernel: %s", msg);// 从内核 Netlink 套接字接收数据memset(&daddr, 0, sizeof(daddr));memset(&u_info, 0, sizeof(u_info));len = sizeof(struct sockaddr_nl);recvfrom(sockfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);printf("\n");printf("from kernel(%u): %s\n", daddr.nl_pid, u_info.msg);close(sockfd);return 0;
}

编译执行用户空间测试程序 netlink_user_test

$ make netlink_user_test
$ ./netlink_user_test

本文示例是 内核空间用户空间 Netlink 套接字通信,事实上,内核空间的 Netlink 相互之间也可以通信,而且 Netlink 不仅支持单播通信,还支持广播通信,本文对此不做展开。

3. Netlink 简析

本文就示例中 内核空间用户空间 Netlink 套接字通信做简单分析。

3.1 Netlink 协议簇注册

static struct proto netlink_proto = {.name	  = "NETLINK",.owner	  = THIS_MODULE,.obj_size = sizeof(struct netlink_sock),
};static const struct net_proto_family netlink_family_ops = {.family = PF_NETLINK,.create = netlink_create, /* 创建 用户空间 netlink 套接字 */.owner	= THIS_MODULE,	/* for consistency 8) */
};static int __init netlink_proto_init(void)
{int err = proto_register(&netlink_proto, 0);...nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);...// Netlink 协议簇 PF_NETLINK 注册sock_register(&netlink_family_ops);...
}core_initcall(netlink_proto_init);

3.2 创建 用户空间 Netlink 套接字

sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); /* net/socket.c */retval = sock_create(family, type, protocol, &sock);__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);sock = sock_alloc();...sock->type = type; // SOCK_RAW...pf = rcu_dereference(net_families[family]);...err = pf->create(net, sock, protocol, kern);netlink_create() /* net/netlink/af_netlink.c */...err = __netlink_create(net, sock, cb_mutex, protocol, kern);...sock->ops = &netlink_ops;sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto, kern);...sk->sk_destruct = netlink_sock_destruct;sk->sk_protocol = protocol; // NETLINK_TESTretval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));

3.3 用户空间 Netlink 套接字 的 绑定

struct sockaddr_nl saddr;memset(&saddr, 0, sizeof(saddr));
saddr.nl_family = AF_NETLINK;
saddr.nl_pad = 0;
saddr.nl_pid = USER_PORT;
saddr.nl_groups = 0;
bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr))struct socket *sock;sock = sockfd_lookup_light(fd, &err, &fput_needed);.../* 拷贝用户参数到内核空间: umyaddr ==> address */err = move_addr_to_kernel(umyaddr, addrlen, &address);...err = sock->ops->bind(sock, (struct sockaddr *)&address, addrlen);netlink_bind() /* net/netlink/af_netlink.c */bool bound;...bound = nlk->bound;...if (!bound) { /* 还没有对 netlink 套接字进行过绑定 */...netlink_insert(sk, nladdr->nl_pid)...nlk_sk(sk)->portid = portid; /* 设定 netlink 套接字绑定的端口号 */.../** 将 @sk 插入到 netlink 协议类型 @sk->sk_protocol* 对应表项 nl_table[sk->sk_protocol] 的 sock 列表。* @sk->sk_protocol => {NETLINK_ROUTE, ...}*/err = __netlink_insert(table, sk);.../** 标记 netlink 套接字 @sk 已经进行了 地址 和 端口 的绑定, * 即已经调用过了 bind().*/smp_wmb();nlk_sk(sk)->bound = portid;}

3.4 向 内核空间 Netlink 套接字 发消息

struct sockaddr_nl daddr;
struct nlmsghdr *nlh;nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD)); // 分配 头部 + 内容 空间 并对齐到 4 字节memset(nlh, 0, sizeof(struct nlmsghdr));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
nlh->nlmsg_flags = 0;
nlh->nlmsg_type = 0;
nlh->nlmsg_seq = 0;
nlh->nlmsg_pid = saddr.nl_pid; // 指定 源端口号 USER_PORT
memcpy(NLMSG_DATA(nlh), msg, strlen(msg)); // 设置消息内容// 设置消息发往的 目标 内核空间 netlink 套接字 地址
memset(&daddr, 0, sizeof(daddr));
daddr.nl_family = AF_NETLINK;
daddr.nl_pid = 0; // 发往内核 Netlink 套接字
daddr.nl_groups = 0;// 往 目标内核 netlink 套接字 发消息
sendto(sockfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));struct socket *sock;...struct msghdr msg;...err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter);...sock = sockfd_lookup_light(fd, &err, &fput_needed);...msg.msg_name = NULL;msg.msg_control = NULL;msg.msg_controllen = 0;msg.msg_namelen = 0;err = move_addr_to_kernel(addr, addr_len, &address);msg.msg_name = (struct sockaddr *)&address;msg.msg_namelen = addr_len;...msg.msg_flags = flags;err = sock_sendmsg(sock, &msg);sock_sendmsg_nosec(sock, msg);int ret = sock->ops->sendmsg(sock, msg, msg_data_left(msg));netlink_sendmsg() /* net/netlink/af_netlink.c */...if (msg->msg_namelen) {.../** @dst_portid: 数据发往的 目标 netlink 套接字端口号* @dst_group : 数据发往的 目标 netlink 套接字 组*/dst_portid = addr->nl_pid;dst_group = ffs(addr->nl_groups);...netlink_skb_flags |= NETLINK_SKB_DST;} else {...}...skb = netlink_alloc_large_skb(len, dst_group); /* 分配 skb, 准备用来接收来自 @sock 的数据 */...NETLINK_CB(skb).portid	= nlk->portid; /* 设定 skb 的数据 源端口号 */NETLINK_CB(skb).dst_group = dst_group; /* 设定 skb 的数据 目标组 */...NETLINK_CB(skb).flags	= netlink_skb_flags; // |= NETLINK_SKB_DST...if (memcpy_from_msg(skb_put(skb, len), msg, len)) { /* 将要发送的数据 @msg 拷贝到 @skb */...}...err = netlink_unicast(sk, skb, dst_portid, msg->msg_flags&MSG_DONTWAIT); /* netlink 单播 */...retry:	/* * 目标 sock (@sk) 是 内核 sock, 如情形:*                               data* 用户空间 netlink 套接字 @ssk ----> 内核空间 netlink 套接字 @sk*/sk = netlink_getsockbyportid(ssk, portid);...if (netlink_is_kernel(sk))netlink_unicast_kernel()nlk->netlink_rcv(skb)recv_msg_from_user_land()

3.5 从 内核空间 Netlink 套接字 读消息

从 内核空间 Netlink 套接字 读消息, 这分为两步:

1. 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息
2. 用户空间 Netlink 套接字 读取 内核空间 Netlink 套接字 发送的消息

3.5.1 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息

#define USER_PORT 66 // 用户空间 netlink 套接字 端口号struct sk_buff *nl_skb;
struct nlmsghdr *nlh;// 构建 skb: 容纳 消息头部 + @len 长度的数据
nl_skb = nlmsg_new(len, GFP_ATOMIC);// 设置源消息头部: 协议类型、端口号、数据长度等
nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);__nlmsg_put(skb, portid, seq, type, payload, flags); // @portid == 0, 指代内核 netlink 套接字struct nlmsghdr *nlh;int size = nlmsg_msg_size(len);nlh = skb_put(skb, NLMSG_ALIGN(size));nlh->nlmsg_type = type;nlh->nlmsg_len = size;nlh->nlmsg_flags = flags;nlh->nlmsg_pid = portid;nlh->nlmsg_seq = seq;// 填充要发送的数据内容
memcpy(nlmsg_data(nlh), buf, len);netlink_unicast(test_nlsk, nl_skb, USER_PORT, MSG_DONTWAIT); // 将数据发送 用户空间 netlink 套接字...
retry:	/* * 用 * {sock_net(@ssk), @ssk->sk_protocol, @portid}* 寻找目标 netlink sock, 数据传输方向: * @ssk (源sock) -> @sk (目标sock)*/sk = netlink_getsockbyportid(ssk, portid);.../* * 目标 sock (@sk) 是 用户空间 sock, 如情形:*                               data* 内核空间 netlink 套接字 @ssk ----> 用户空间 netlink 套接字 @sk*/return netlink_sendskb(sk, skb);int len = __netlink_sendskb(sk, skb);.../* 将数据添加到 目标 @sk 的 接收 skb 队列 */skb_queue_tail(&sk->sk_receive_queue, skb);/* 唤醒 可能的、因等待读取数据 而睡眠的进程 */sk->sk_data_ready(sk);sock_def_readable()

3.5.2 用户空间 Netlink 套接字 读取 内核空间 Netlink 套接字 发送的消息

struct netlink_user_msg {struct nlmsghdr hdr;char msg[MSG_LEN];
};struct sockaddr_nl daddr;
struct netlink_user_msg u_info;memset(&u_info, 0, sizeof(u_info));
len = sizeof(struct sockaddr_nl);
recvfrom(sockfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);...err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);...sock = sockfd_lookup_light(fd, &err, &fput_needed);msg.msg_control = NULL;msg.msg_controllen = 0;/* Save some cycles and don't copy the address if not needed */msg.msg_name = addr ? (struct sockaddr *)&address : NULL;/* We assume all kernel code knows the size of sockaddr_storage */msg.msg_namelen = 0;msg.msg_iocb = NULL;msg.msg_flags = 0;...err = sock_recvmsg(sock, &msg, flags);sock_recvmsg_nosec(sock, msg, flags);sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags);netlink_recvmsg() /* net/netlink/af_netlink.c */.../** 从 @sk 的接收队列 @sk->sk_receive_queue 取就绪的数据 skb.* 阻塞模式下, 可因没有数据而陷入睡眠等待.*/skb = skb_recv_datagram(sk, flags, noblock, &err);...data_skb = skb; /* 返回就绪的 skb */.../* 这里告诉我们, netlink 协议数据位于 传输层(L4) 之上, 即位于 应用层(L5) */skb_reset_transport_header(data_skb);err = skb_copy_datagram_msg(data_skb, 0, msg, copied); /* 从就绪的 skb 读取数据 */if (msg->msg_name) {DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name);addr->nl_family = AF_NETLINK;addr->nl_pad    = 0;addr->nl_pid	= NETLINK_CB(skb).portid;addr->nl_groups	= netlink_group_mask(NETLINK_CB(skb).dst_group);msg->msg_namelen = sizeof(*addr);}...

4. 参考资料

[1] Introduction to Netlink

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

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

相关文章

CSS3(响应式布局)

#过渡# 属性连写&#xff1a; transition: width 2s linear 1s; //前一个时间用于表示过渡效果持续时间&#xff0c;后一个时间用于表示过渡效果的延迟。 #转换# #2D转换# 和 #3D转换# 注意&#xff1a;其中angle对应单位为&#xff1a;deg #圆角# #边框# …

使用Docker部署Jupyter Notebook并结合花生壳的内网穿透实现远程访问(详文)

一、前言 本文主要介绍如何利用宝塔面板中的Docker 3.9.3管理器,使用Docker本地部署Jupyter Notebook,并结合花生壳内网穿透工具实现任意浏览器公网远程访问Jupyter登录界面。 安装完成后在宝塔面板中图例 Jupyter Notebook是一个交互式笔记本,支持运行40多种编程语言。…

会声会影2024破解电脑版下载附带永久免费激活码

会声会影2024是一款功能强大的视频编辑软件&#xff0c;它凭借直观易用的界面、全面的编辑工具以及丰富的特效和转场效果&#xff0c;受到了广大视频编辑爱好者和专业人士的喜爱。以下是对会声会影2024的详细介绍&#xff1a; 会声会影最新一键安装包下载如下&#xff1a; ht…

sqlserver在执行维护计划备份时报错,重新设置维护任务,也同样出错,另一计划的异地备份又正常。

出错提示&#xff1a;已以用户 NT Service\SQLSERVERAGENT 的身份执行。 最大值(1)&#xff0c;因此导致失败。当错误数达到 MaximumErrorCount 中指定的数目时将发生这种情况。请更改 MaximumErrorCount 或纠正这些错误。 警告结束 DTExec: 已返回包执行 DTSER_FAILURE (1)。…

web页面点击右键显示按钮

首先声明一个对象&#xff0c;然后把声明的对象&#xff0c;赋值一个function&#xff0c;在对象的function当中再return一个function&#xff0c;在返回的这个function中第一步就是要把按钮的class先移除&#xff0c;不然到后面取消右键显示按钮的时候会失效&#xff0c;按钮依…

费曼学习法个人总结-1

2024年4月27日 1&#xff0c;知道它叫什么和懂得它是什么是两件事&#xff0c;要了解事物的本质&#xff0c;不应该只是了解名字。 2&#xff0c;输出倒逼输入。 3&#xff0c;实施的5步骤 调整心态&#xff0c;开始行动指定目标&#xff0c;养成习惯找对方法&#xff0c;高…

PotatoPie 4.0 实验教程(23) —— FPGA实现摄像头图像伽马(Gamma)变换

为什么要进行Gamma校正 图像的 gamma 校正是一种图像处理技术&#xff0c;用于调整图像的亮度和对比度&#xff0c;让显示设备显示的亮度和对比度更符合人眼的感知。Gamma 校正主要用于修正显示设备的非线性响应&#xff0c;以及在图像处理中进行色彩校正和图像增强。 以前&am…

《从零开始的Java世界》11网络编程

《从零开始的Java世界》系列主要讲解Javase部分&#xff0c;从最简单的程序设计到面向对象编程&#xff0c;再到异常处理、常用API的使用&#xff0c;最后到注解、反射&#xff0c;涵盖Java基础所需的所有知识点。学习者应该从学会如何使用&#xff0c;到知道其实现原理全方位式…

LangChain入门:24.通过Baby AGI实现自动生成和执行任务

随着 ChatGPT 的崭露头角,我们迎来了一种新型的代理——Autonomous Agents(自治代理或自主代理)。 这些代理的设计初衷就是能够独立地执行任务,并持续地追求长期目标。 在 LangChain 的代理、工具和记忆这些组件的支持下,它们能够在无需外部干预的情况下自主运行,这在真…

冯唐成事心法笔记 —— 知己

系列文章目录 冯唐成事心法笔记 —— 知己 冯唐成事心法笔记 —— 知人 冯唐成事心法笔记 —— 知世 冯唐成事心法笔记 —— 知智慧 文章目录 系列文章目录卷首语 管理是一生的日常&#xff0c;成事是一生的修行PART 1 知己 用好自己的天赋如何管理自我第一&#xff0c;如何…

C++学习随笔(12)—— list

本章我们来了解一下list 目录 1. list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modifiers 1.2.6 list的迭代器失效 1. list的介绍及使用 1.1 list的介绍…

【Unity常用插件】Dotween插件API详解【一】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;UI_…

10万字208道Java经典面试题总结(2024修订版)- JavaWeb篇

&#x1f345; 作者简介&#xff1a;哪吒&#xff0c;CSDN2021博客之星亚军&#x1f3c6;、新星计划导师✌、博客专家&#x1f4aa; &#x1f345; 哪吒多年工作总结&#xff1a;Java学习路线总结&#xff0c;搬砖工逆袭Java架构师 &#x1f345; 技术交流&#xff1a;定期更新…

LabVIEW飞机机电系统综合测试平台

LabVIEW飞机机电系统综合测试平台 在现代航空领域&#xff0c;机电系统的准确性与可靠性对飞行安全至关重要。针对飞机机电管理计算机&#xff08;UMC&#xff09;复杂度增加、测试覆盖率低、效率不高等问题&#xff0c;开发了一套基于LabVIEW的机电系统综合测试平台。平台通过…

常用设计模式简介

设计模式是在软件设计中常见问题的通用可重用解决方案。它们分为三大类&#xff1a;创建型模式、结构型模式和行为型模式。下面是这三类中一些常用设计模式的概述&#xff1a; 创建型模式 单例模式&#xff08;Singleton&#xff09; 保证一个类只有一个实例&#xff0c;并提供…

内网穿透及公网解析说明

内网穿透释义&#xff1a; 自己在本地搭建服务器时&#xff0c;本地网络有多种环境&#xff0c;如没有公网IP、没有路由映射权限、网络被NAT转发等情况。在需要外网访问内网服务器资源时&#xff0c;就需要用到内网穿透。内网穿透&#xff0c;即内网映射&#xff0c;内网IP地址…

PotatoPie 4.0 实验教程(21) —— FPGA实现摄像头图像二值化(RGB2Gray2Bin)

PotatoPie 4.0开发板教程目录&#xff08;2024/04/21&#xff09; 为什么要进行图像的二值化&#xff1f; 当我们处理图像时&#xff0c;常常需要将其转换为二值图像。这是因为在很多应用中&#xff0c;我们只对图像中的某些特定部分感兴趣&#xff0c;而不需要考虑所有像素的…

PostgreSQL的扩展(extensions)-常用的扩展之pgAudit

PostgreSQL的扩展&#xff08;extensions&#xff09;-常用的扩展之pgAudit pgAudit 是一个 PostgreSQL 的扩展&#xff0c;它提供了一种方式来生成详尽的审计日志。这对于需要遵守特定监管要求的企业来说是非常重要的&#xff0c;比如那些必须符合 HIPAA、SOX、PCI DSS 等标准…

如何进行域名解析?如何清理DNS缓存?(附源码)

目录 1、什么是域名&#xff1f; 2、为什么使用域名&#xff1f; 3、域名解析的完整流程 4、调用gethostbyname系统接口将域名解析成IP地址 5、为什么需要清理系统DNS缓存&#xff1f; 6、使用cmd命令清理DNS缓存 7、通过代码去清除系统DNS缓存 C软件异常排查从入门到精…

PeLK: 大卷积核强势回归,高达101 × 101,提出了外围卷积

paper&#xff1a;https://arxiv.org/pdf/2403.07589 code&#xff1a;暂无 目录 0. 摘要 1. 引言 2. 相关工作 2.1. Large Kernel Convolutional Networks 2.2. Peripheral Vision for Machine Learning 3. 密集卷积优于条纹卷积 4. 参数高效的大核卷积神经网络 4.1. …