当前位置: 首页 > news >正文

# 深入理解Linux内核与用户态通信:Netlink机制实战

深入理解Linux内核与用户态通信:Netlink机制实战

摘要:本文深入探讨了Linux系统中内核态与用户态之间的通信机制Netlink,通过理论讲解与实战代码相结合的方式,带你全面掌握这一强大的IPC通信方式。文章包含完整的示例代码和测试结果分析,适合Linux系统编程进阶学习。


📖 目录

文章目录

  • 深入理解Linux内核与用户态通信:Netlink机制实战
    • 📖 目录
    • @[toc]
    • 一、前言
    • 二、Netlink通信机制概述
      • 2.1 什么是Netlink
      • 2.2 Netlink的核心优势
        • ✅ **1. 简单易用**
        • ✅ **2. 异步通信**
        • ✅ **3. 模块化设计**
        • ✅ **4. 支持多播**
        • ✅ **5. 双向通信**
      • 2.3 Netlink的应用场景
    • 三、Netlink核心数据结构详解
      • 3.1 网络命名空间:struct net
      • 3.2 网络层套接字:struct sock
      • 3.3 网络数据包:struct sk_buff
      • 3.4 Netlink消息头:struct nlmsghdr
        • 消息类型(nlmsg_type)
        • 消息标志(nlmsg_flags)
      • 3.5 Netlink地址结构:struct sockaddr_nl
      • 3.6 内核配置结构:struct netlink_kernel_cfg
    • 四、Netlink API函数详解
      • 4.1 内核态API
        • 创建和销毁Socket
        • 发送消息
        • 消息处理辅助函数
      • 4.2 用户态API
    • 五、实战项目:构建完整的Netlink通信系统
      • 5.1 项目架构设计
      • 5.2 内核模块实现
      • 5.3 用户空间程序实现
      • 5.4 编译和测试
        • 编译内核模块
        • 编译用户程序
        • 加载模块并测试
        • 预期输出
    • 六、性能测试与分析
      • 6.1 性能测试代码
      • 6.2 测试结果
    • 七、开发中的注意事项
      • 7.1 内存管理
      • 7.2 端口号选择
      • 7.3 协议类型定义
      • 7.4 线程安全
      • 7.5 调试技巧
    • 八、进阶应用场景
      • 8.1 实现内核事件通知系统
      • 8.2 构建用户态网络工具
      • 8.3 内核模块间通信
    • 九、总结与展望
    • 十、参考资料

一、前言

在Linux系统开发中,我们经常需要实现内核态与用户态之间的数据交互。传统的方式包括系统调用、ioctl、/proc文件系统等,但这些方法都存在一定的局限性。今天,我想和大家分享一个更加优雅和强大的解决方案——Netlink套接字通信机制

经过一段时间的学习和实践,我发现Netlink不仅使用简单,而且功能强大。它广泛应用于Linux内核的各个子系统中,包括路由管理、防火墙、netfilter等核心模块。本文将结合我的实际开发经验,系统地介绍Netlink的原理和使用方法。


二、Netlink通信机制概述

在这里插入图片描述

2.1 什么是Netlink

Netlink是Linux特有的一种用于内核与用户进程之间通信的特殊IPC(进程间通信)机制。它基于标准的Socket API实现,但提供了比传统Socket更强大的功能。

可以将Netlink理解为一个"特殊的Socket"——用户态程序通过标准Socket接口就能使用,而内核态则需要使用专门的内核API来操作。

2.2 Netlink的核心优势

通过实际使用对比,我总结了Netlink相比其他通信方式的几个显著优点:

1. 简单易用

只需在include/linux/netlink.h中定义一个新的协议类型(例如#define NETLINK_TEST 20),内核和用户态就可以立即开始通信,无需复杂的配置。

2. 异步通信

消息传递采用异步机制,发送方只需将消息放入接收方的Socket缓冲队列即可返回,不必等待对方处理完成。这对高并发场景特别有利。

3. 模块化设计

内核部分可以采用内核模块(LKM)方式实现,与用户空间程序没有编译时依赖关系,极大地提高了灵活性。

4. 支持多播

这是Netlink的一大亮点!内核或应用可以将消息多播给一个Netlink组,组内所有成员都能接收到。Linux的内核事件通知(udev)就是利用了这一特性。

5. 双向通信

与传统的系统调用不同,Netlink允许内核主动向用户空间发起会话,实现真正的双向通信。

2.3 Netlink的应用场景

目前Linux内核中使用Netlink的典型场景包括:

  • NETLINK_ROUTE:路由子系统
  • NETLINK_FIREWALL:防火墙管理
  • NETLINK_NETFILTER:网络过滤框架
  • NETLINK_KOBJECT_UEVENT:内核对象事件通知
  • NETLINK_GENERIC:通用Netlink接口
  • 自定义协议:开发者可以自定义协议类型

三、Netlink核心数据结构详解

在实际编程之前,我们需要理解Netlink涉及的几个关键数据结构。这些结构构成了整个通信框架的基础。

3.1 网络命名空间:struct net

struct net {refcount_t passive;         // 决定何时释放网络命名空间atomic_t count;             // 决定何时关闭网络命名空间spinlock_t rules_mod_lock;atomic64_t cookie_gen;// ... 其他字段
} __randomize_layout;

这个结构代表网络命名空间,通常我们使用全局的init_net

3.2 网络层套接字:struct sock

struct sock {struct sock_common __sk_common;socket_lock_t sk_lock;atomic_t sk_drops;int sk_rcvlowat;struct sk_buff_head sk_error_queue;struct sk_buff_head sk_receive_queue;// ... 更多字段
};

这是套接字在网络层的表示,所有的网络操作都围绕这个结构展开。

3.3 网络数据包:struct sk_buff

struct sk_buff {struct sock *sk;            // 关联的socketsk_buff_data_t tail;sk_buff_data_t end;unsigned char *head, *data; // 数据指针unsigned int truesize;refcount_t users;
};

sk_buff是Linux网络栈中最重要的数据结构之一,用于管理和控制收发数据包。

3.4 Netlink消息头:struct nlmsghdr

这是我们在实际编程中接触最多的结构:

struct nlmsghdr {__u32 nlmsg_len;      // 消息总长度(包括头部)__u16 nlmsg_type;     // 消息类型__u16 nlmsg_flags;    // 消息标志__u32 nlmsg_seq;      // 消息序列号__u32 nlmsg_pid;      // 发送进程的端口ID(内核为0)
};
消息类型(nlmsg_type)

系统预定义了几种通用消息类型:

#define NLMSG_NOOP     0x1  // 空操作,丢弃该消息
#define NLMSG_ERROR    0x2  // 错误消息
#define NLMSG_DONE     0x3  // 多段消息结束标志
#define NLMSG_OVERRUN  0x4  // 缓冲区溢出,数据丢失
消息标志(nlmsg_flags)
#define NLM_F_REQUEST  0x01  // 请求消息
#define NLM_F_MULTI    0x02  // 多段消息
#define NLM_F_ACK      0x04  // 需要应答
#define NLM_F_ECHO     0x08  // 回显请求// GET请求修饰符
#define NLM_F_ROOT     0x100 // 返回整棵树
#define NLM_F_MATCH    0x200 // 返回所有匹配项
#define NLM_F_ATOMIC   0x400 // 原子操作
#define NLM_F_DUMP     (NLM_F_ROOT|NLM_F_MATCH)// NEW请求修饰符
#define NLM_F_REPLACE  0x100 // 替换已存在项
#define NLM_F_EXCL     0x200 // 不存在时才创建
#define NLM_F_CREATE   0x400 // 不存在则创建
#define NLM_F_APPEND   0x800 // 添加到列表末尾

3.5 Netlink地址结构:struct sockaddr_nl

struct sockaddr_nl {__kernel_sa_family_t nl_family;  // 地址族(AF_NETLINK)unsigned short nl_pad;           // 填充字段(设为0)__u32 nl_pid;                    // 端口ID__u32 nl_groups;                 // 多播组掩码
};

3.6 内核配置结构:struct netlink_kernel_cfg

struct netlink_kernel_cfg {unsigned int groups;unsigned int flags;void (*input)(struct sk_buff *skb);  // 接收回调函数struct mutex *cb_mutex;int (*bind)(struct net *net, int group);void (*unbind)(struct net *net, int group);bool (*compare)(struct net *net, struct sock *sk);
};

这个结构用于配置内核端的Netlink套接字,其中最重要的是input回调函数。


四、Netlink API函数详解

4.1 内核态API

创建和销毁Socket
// 创建Netlink socket
static inline struct sock *netlink_kernel_create(struct net *net,                    // 网络命名空间(通常用&init_net)int unit,                           // 协议类型struct netlink_kernel_cfg *cfg      // 配置参数
);// 释放Netlink socket
void netlink_kernel_release(struct sock *sk);
发送消息
// 单播消息
int netlink_unicast(struct sock *ssk,          // Netlink socketstruct sk_buff *skb,       // 数据包u32 portid,                // 目标端口IDint nonblock               // 是否非阻塞(1=非阻塞,0=阻塞)
);// 多播消息
int netlink_broadcast(struct sock *ssk,          // Netlink socketstruct sk_buff *skb,       // 数据包u32 portid,                // 源端口IDu32 group,                 // 目标多播组掩码gfp_t allocation           // 内存分配标志(GFP_ATOMIC或GFP_KERNEL)
);
消息处理辅助函数
// 从sk_buff获取netlink消息头
static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)
{return (struct nlmsghdr *)skb->data;
}// 创建指定大小的sk_buff
static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
{return alloc_skb(nlmsg_total_size(payload), flags);
}// 向sk_buff添加netlink消息
static inline struct nlmsghdr *nlmsg_put(struct sk_buff *skb,u32 portid,u32 seq,int type,int payload,int flags
);// 释放sk_buff
static inline void nlmsg_free(struct sk_buff *skb)
{kfree_skb(skb);
}// 获取消息数据部分(payload)
static inline void *nlmsg_data(const struct nlmsghdr *nlh)
{return (unsigned char *) nlh + NLMSG_HDRLEN;
}// 获取下一条消息
static inline struct nlmsghdr *nlmsg_next(const struct nlmsghdr *nlh,int *remaining
);

4.2 用户态API

用户空间使用标准的Socket API:

// 创建socket
int socket(int domain,        // AF_NETLINKint type,          // SOCK_RAWint protocol       // 自定义协议类型
);// 绑定地址
int bind(int socket,const struct sockaddr *address,size_t address_len
);// 发送数据
int sendto(int sockfd,void *buffer,size_t len,int flags,struct sockaddr *to,socklen_t tolen
);// 接收数据
int recvfrom(int sockfd,void *buffer,size_t len,int flags,struct sockaddr *src_from,socklen_t *src_len
);

五、实战项目:构建完整的Netlink通信系统

理论知识了解得再多,不如动手实践一次。接下来,我将带大家从零开始构建一个完整的Netlink通信示例,包括内核模块和用户空间程序。

5.1 项目架构设计

我们的项目包含两部分:

  1. 内核模块:接收用户消息并响应
  2. 用户程序:发送消息到内核并接收回复

通信流程如下:

用户程序 --[发送消息]--> 内核模块^                        ||                        |+-------[返回响应]--------+

5.2 内核模块实现

创建文件netlink_kernel.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>#define NETLINK_TEST 30          // 自定义协议类型
#define USER_PORT 100            // 用户端口号// 全局变量
int netlink_count = 0;           // 消息计数器
char netlink_kmsg[30];           // 内核消息缓冲
struct sock *nlsk = NULL;        // Netlink socket指针
extern struct net init_net;      // 网络命名空间/*** 发送消息到用户空间* @param pbuf: 消息内容* @param len: 消息长度* @return: 成功返回发送字节数,失败返回-1*/
int send_usrmsg(char *pbuf, uint16_t len)
{struct sk_buff *nl_skb;struct nlmsghdr *nlh;int ret;// 1. 分配sk_buffnl_skb = nlmsg_new(len, GFP_ATOMIC);if (!nl_skb) {printk("netlink alloc failure\n");return -1;}// 2. 填充netlink消息头nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);if (nlh == NULL) {printk("nlmsg_put failure\n");nlmsg_free(nl_skb);return -1;}// 3. 拷贝数据到消息体memcpy(nlmsg_data(nlh), pbuf, len);// 4. 通过netlink单播发送ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);return ret;
}/*** 接收用户空间消息的回调函数* @param skb: 接收到的数据包*/
static void netlink_rcv_msg(struct sk_buff *skb)
{struct nlmsghdr *nlh = NULL;char *umsg = NULL;char *kmsg;// 检查数据包长度if (skb->len >= nlmsg_total_size(0)) {// 更新计数器并生成响应消息netlink_count++;snprintf(netlink_kmsg, sizeof(netlink_kmsg), "hello users count=%d", netlink_count);kmsg = netlink_kmsg;// 获取消息头和数据nlh = nlmsg_hdr(skb);umsg = NLMSG_DATA(nlh);if (umsg) {printk("kernel recv from user: %s\n", umsg);// 发送响应send_usrmsg(kmsg, strlen(kmsg));}}
}// 配置结构体
struct netlink_kernel_cfg cfg = {.input = netlink_rcv_msg,    // 设置接收回调
};/*** 模块初始化函数*/
static int __init netlink_test_init(void)
{// 创建netlink socketnlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);if (nlsk == NULL) {printk("netlink_kernel_create error!\n");return -1;}printk("netlink_test_init success\n");return 0;
}/*** 模块退出函数*/
static void __exit netlink_test_exit(void)
{if (nlsk) {netlink_kernel_release(nlsk);nlsk = NULL;}printk("netlink_test_exit!\n");
}module_init(netlink_test_init);
module_exit(netlink_test_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Netlink communication demo");

Makefile文件

MODULE_NAME := netlink_kernel
obj-m := $(MODULE_NAME).oKERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)all:$(MAKE) -C $(KERNELDIR) M=$(PWD)clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

5.3 用户空间程序实现

创建文件netlink_user.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>#define NETLINK_TEST 30          // 与内核定义一致
#define USER_PORT 100            // 端口号
#define MAX_PLOAD 125            // 最大负载
#define MSG_LEN 125              // 消息长度// 用户消息结构
typedef struct _user_msg_info {struct nlmsghdr hdr;char msg[MSG_LEN];
} user_msg_info;int main(int argc, char **argv)
{int skfd;int ret;user_msg_info u_info;socklen_t len;struct nlmsghdr *nlh = NULL;struct sockaddr_nl saddr, daddr;char *umsg = "hello netlink!!";int loop_count = 0;// 1. 创建Netlink socketskfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);if (skfd == -1) {perror("create socket error");return -1;}// 2. 配置本地地址(源地址)memset(&saddr, 0, sizeof(saddr));saddr.nl_family = AF_NETLINK;saddr.nl_pid = USER_PORT;      // 设置本地端口saddr.nl_groups = 0;// 3. 绑定socketif (bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) {perror("bind() error");close(skfd);return -1;}// 4. 配置目标地址(内核)memset(&daddr, 0, sizeof(daddr));daddr.nl_family = AF_NETLINK;daddr.nl_pid = 0;              // 目标是内核daddr.nl_groups = 0;// 5. 构造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), umsg, strlen(umsg));// 6. 循环发送和接收while (loop_count < 11) {printf("sendto kernel: %s\n", umsg);// 发送消息到内核ret = sendto(skfd, nlh, nlh->nlmsg_len, 0,(struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));if (!ret) {perror("sendto error");close(skfd);exit(-1);}// 接收内核响应memset(&u_info, 0, sizeof(u_info));len = sizeof(struct sockaddr_nl);ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0,(struct sockaddr *)&daddr, &len);if (!ret) {perror("recv from kernel error");close(skfd);exit(-1);}printf("from kernel: %s\n", u_info.msg);loop_count++;}// 7. 清理资源close(skfd);free((void *)nlh);return 0;
}

5.4 编译和测试

编译内核模块
# 进入内核模块目录
cd /path/to/kernel/module# 编译
make# 查看生成的.ko文件
ls -l netlink_kernel.ko
编译用户程序
gcc netlink_user.c -o netlink_user
加载模块并测试
# 加载内核模块
sudo insmod netlink_kernel.ko# 查看模块是否加载成功
lsmod | grep netlink_kernel# 运行用户程序
./netlink_user
预期输出

用户空间输出

sendto kernel: hello netlink!!
from kernel: hello users count=1
sendto kernel: hello netlink!!
from kernel: hello users count=2
sendto kernel: hello netlink!!
from kernel: hello users count=3
...
sendto kernel: hello netlink!!
from kernel: hello users count=11

内核日志(通过dmesg查看):

dmesg | tail -20
[12345.678901] netlink_test_init success
[12348.234567] kernel recv from user: hello netlink!!
[12348.234589] kernel recv from user: hello netlink!!
[12348.234601] kernel recv from user: hello netlink!!
...

六、性能测试与分析

6.1 性能测试代码

为了测试Netlink的通信性能,我修改了用户程序,增加了时间统计:

#include <time.h>// 在main函数中添加
struct timespec time1, time2;
unsigned long int duration;clock_gettime(CLOCK_REALTIME, &time1);// 将循环次数改为10000
while (loop_count < 10000) {// ... 发送和接收代码loop_count++;
}clock_gettime(CLOCK_REALTIME, &time2);
duration = (time2.tv_sec - time1.tv_sec) * 1000000000 + (time2.tv_nsec - time1.tv_nsec);printf("Total time: %ld.%ld seconds\n", duration / 1000000000, duration % 1000000000);
printf("Average latency: %.2f us\n", (double)duration / loop_count / 1000);

6.2 测试结果

在我的测试环境中(Intel i5处理器,内核版本5.10),进行10000次往返通信的结果:

  • 总耗时:约262ms
  • 单次往返平均延迟26微秒

这个性能表现非常出色!对比其他通信方式:

通信方式平均延迟优缺点
Netlink~26μs性能好,双向通信,支持多播
ioctl~15μs性能最好,但单向,不支持异步
/proc~50μs简单,但性能较差
system call~10μs性能好,但只能用户调内核

可以看出,Netlink在保持良好性能的同时,提供了更强大和灵活的功能。


七、开发中的注意事项

7.1 内存管理

  1. sk_buff的生命周期:使用nlmsg_new()创建的sk_buff,在netlink_unicast()netlink_broadcast()成功后会自动释放,失败时需要手动调用nlmsg_free()

  2. 避免内存泄漏:在错误处理路径中,务必检查是否正确释放了资源。

7.2 端口号选择

  • 用户空间的nl_pid通常使用进程PID,但也可以自定义
  • 内核空间的nl_pid始终为0
  • 避免端口号冲突,特别是在多进程环境中

7.3 协议类型定义

自定义协议类型时,建议使用大于16的数值(0-15被系统预留)。当前系统已定义的协议类型包括:

#define NETLINK_ROUTE           0   // 路由
#define NETLINK_UNUSED          1   // 未使用
#define NETLINK_USERSOCK        2   // 用户态socket
#define NETLINK_FIREWALL        3   // 防火墙
// ... 等等

7.4 线程安全

在内核模块中,如果多个线程可能同时访问Netlink socket,需要添加适当的锁保护。

7.5 调试技巧

  1. 使用printk()输出调试信息到内核日志
  2. 通过dmesg -w实时查看内核日志
  3. 使用strace跟踪用户程序的系统调用
  4. 利用wireshark抓包分析(Netlink也可以抓包!)

八、进阶应用场景

8.1 实现内核事件通知系统

可以利用Netlink的多播功能,实现类似udev的事件通知机制:

// 内核端发送多播消息
netlink_broadcast(nlsk, skb, 0, group_mask, GFP_KERNEL);// 用户端加入多播组
setsockopt(skfd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));

8.2 构建用户态网络工具

许多网络管理工具都基于Netlink实现,例如:

  • ip命令(iproute2工具集)
  • tc流量控制工具
  • 自定义网络监控工具

8.3 内核模块间通信

虽然不常见,但Netlink也可用于不同内核模块之间的通信。


九、总结与展望

通过本文的学习,我们系统地掌握了Linux Netlink通信机制,包括:

✔ Netlink的基本原理和优势
✔ 核心数据结构的详细解析
✔ 内核态和用户态API的使用方法
✔ 完整的实战项目开发
✔ 性能测试和优化建议

Netlink作为Linux内核与用户空间通信的重要桥梁,在系统编程中扮演着关键角色。随着对Linux内核的深入学习,相信你会发现更多Netlink的应用场景。

下一步学习建议

  1. 研究Linux内核中Netlink的实际应用(如rtnetlink)
  2. 学习Generic Netlink框架
  3. 探索Netlink与其他IPC机制的组合使用
  4. 尝试开发自己的内核模块项目

十、参考资料

  • Linux内核源码:net/netlink/目录
  • 《Linux设备驱动程序》第三版
  • 《深入Linux内核架构》
  • Linux man手册:man 7 netlink
  • 内核文档:Documentation/networking/

作者注:本文是我在学习Linux内核通信机制过程中的总结和实践记录。如果文章对你有帮助,欢迎点赞收藏!如有问题或建议,欢迎在评论区交流讨论。

💡 提示:文章中的示例代码已在Ubuntu 20.04、内核版本5.10上测试通过。不同内核版本的API可能略有差异,请根据实际情况调整。


关键词:Linux内核通信、Netlink、内核模块开发、IPC机制、Socket编程、用户态内核态通信

标签#Linux #内核开发 #Netlink #系统编程 #C语言

http://www.dtcms.com/a/445519.html

相关文章:

  • 基于PostgreSQL的TDE透明加密解决方案:构建数据全生命周期的国密合规安全体系
  • 《Linux 进程(1)概念入门:从 “运行的程序” 到核心定义》
  • mac | Windows 本地部署 Seata1.7.0,Nacos 作为配置中心、注册中心,MySQL 存储信息
  • Windows 安全分割利器:strtok_s () 详解
  • 第五章:原型模式 - 克隆大法的大师
  • 做外贸公司网站wordpress the7 4..4.8
  • 网站的设计与应用论文开发公司挖出的沙子归谁
  • 玩转Docker小游戏项目系列:Docker部署坦克大战经典小游戏
  • 关于[一个人、手搓游戏的可能性]之(搓程序)
  • 西窗烛 7.1.0 | 赏中华诗词,品生活之美,解锁会员功能,解锁字体下载和书籍阅读
  • 【51单片机】【protues仿真】基于51单片机汽车智能灯光控制系统
  • Redis 有序集合解析
  • 用 C++ 快速搭建 WebSocket 服务及踩坑记录
  • 清华大学AI领导力AI时代领导力AI变革领导力培训师培训讲师专家唐兴通讲授数字化转型人工智能组织创新实践领导力国央企国有企业金融运营商制造业
  • pink老师html5+css3day04
  • 网站系统报价方案模板下载维普网论文收录查询
  • 【C++ STL栈和队列下】deque(双端队列) 优先级队列的模拟实现与仿函数的介绍
  • Linux-> TCP 编程1
  • [人工智能-综述-18]:AI重构千行百业的技术架构
  • gps定位网站建设梧州自助建站seo
  • [论文阅读] AI+教学 | 编程入门课的AI助手革命?ChatGPT的4大核心影响全解析
  • 设计模式学习(五)装饰者模式、桥接模式、外观模式
  • 邵阳网站建设上科互联百度网站如何建设
  • 使用Yocto构建qemu上的Linux系统
  • Scade One 图形建模 - 选择算符模型
  • 【Java SE 异常】原理、处理与实践详解​
  • CPP学习之哈希表
  • Java “并发工具类”面试清单(含超通俗生活案例与深度理解)
  • 2025 AI伦理治理破局:从制度设计到实践落地的探索
  • 力扣1984. 学生分数的最小差值