Linux字符设备驱动开发:从内核注册到/dev节点创建的完整实践

news2026/5/24 2:34:29
1. 项目概述从零到一理解Linux内核的“门牌号”管理在Linux的世界里一切皆文件。这个哲学理念不仅体现在我们熟悉的普通文件上更深刻地内嵌于设备管理中。当你敲下ls -l /dev命令看到那些tty、null、random等文件时你是否好奇过它们是如何诞生的它们背后并非真实的磁盘数据而是一扇通往内核驱动功能的“门”。今天我们就来深入拆解这扇“门”——字符设备——在内核中的创建全过程。这不仅仅是调用一个API那么简单它涉及内核对象管理、文件系统抽象、用户空间交互等一系列精妙的协作。理解这个过程对于从事驱动开发、系统调优乃至深入理解操作系统原理都至关重要。无论你是刚接触驱动的新手还是想巩固内核知识的老兵这篇从一线实践中总结的笔记都将带你走一遍完整的“造门”之旅。2. 核心概念与设计思路拆解在动手写代码之前我们必须先厘清几个核心概念和整个框架的设计思路。字符设备驱动的创建本质上是向内核“注册”一个能力并让用户空间能够通过文件操作接口来使用这个能力。2.1 字符设备 vs. 块设备内核的两种“服务生”首先明确我们讨论的对象。Linux设备主要分两类字符设备和块设备。字符设备提供流式访问数据像字节流一样顺序读写不支持随机存取寻址。典型的例子就是键盘、鼠标、串口、声卡。你从键盘读数据只能按顺序读按键事件不能跳到“第100个按键”去读。驱动直接响应read,write,ioctl等系统调用。块设备提供块式访问数据被组织成固定大小的块如512字节、4KB支持随机存取。硬盘、U盘、SSD就是典型。内核为块设备提供了复杂的缓存、调度机制驱动主要与“请求队列”打交道。我们聚焦字符设备。它的创建过程核心目标是建立一条从用户空间open(“/dev/mydev”)到内核空间my_driver_read()函数之间的通路。2.2 核心数据结构struct cdev与struct file_operations内核用两个关键结构体来抽象一个字符设备struct cdev这是字符设备的内核对象。它包含了设备的核心元信息最重要的是一个指向file_operations的指针和一个设备号。你可以把它理解为设备的“身份证”和“能力目录”。struct file_operations这是一个函数指针集合定义了设备能做什么。里面包含了诸如.open,.read,.write,.release对应close,.unlocked_ioctl等函数的指针。驱动开发者的主要工作就是实现这个结构体里需要的函数。它就像是设备的“服务菜单”。创建过程就是分配并初始化这两个结构体然后将cdev“添加”到内核系统中使其生效。2.3 设备号主设备号与次设备号这是字符设备的“门牌号系统”是内核寻址设备的依据。主设备号标识设备类型或者说关联到具体的驱动。例如历史上3代表tty4代表ttyS串口。内核通过主设备号找到对应的驱动。次设备号由驱动自行解释用于标识同一驱动下的不同设备实例。例如第一个串口ttyS0和第二个串口ttyS1主设备号相同次设备号不同。设备号用一个dev_t类型通常是32位整数表示高12位为主设备号低20位为次设备号。内核提供了MAJOR(dev_t)和MINOR(dev_t)宏来提取以及MKDEV(major, minor)宏来合成。注意设备号的管理申请与释放是驱动开发中容易出错的地方。静态申请指定数字容易冲突动态申请是更推荐的做法。2.4 设计思路总览三步走策略一个稳健的字符设备创建流程通常遵循以下三步准备阶段分配设备号动态或静态、初始化cdev结构体、实现file_operations函数集。注册阶段调用cdev_add()将初始化好的cdev正式添加到内核。此后设备便“激活”了。呈现阶段在/dev目录下创建设备文件节点通常使用device_create()或mknod。这一步建立了设备号与文件名的关联。对应的清理过程则是逆序删除设备节点、注销cdev、释放设备号。3. 实操过程手把手创建一个简单的字符设备理论说得再多不如动手一试。我们创建一个名为my_char_dev的虚拟字符设备它实现一个简单的读写缓冲区。以下代码基于 Linux 5.x 内核版本。3.1 模块初始化分配资源与设备注册驱动通常以内核模块形式开发。初始化函数是入口。#include linux/module.h #include linux/fs.h #include linux/cdev.h #include linux/device.h #include linux/slab.h #include linux/uaccess.h #define DEVICE_NAME my_char_dev #define CLASS_NAME my_char_class #define BUFFER_SIZE 1024 static int major_num; // 主设备号动态分配 static struct class *char_class; static struct cdev my_cdev; static dev_t dev_num; // 简单的设备内存缓冲区 static char device_buffer[BUFFER_SIZE]; static int buffer_offset; // file_operations 函数实现 static int dev_open(struct inode *inodep, struct file *filep) { printk(KERN_INFO my_char_dev: Device opened.\n); buffer_offset 0; // 每次打开重置偏移 return 0; } static ssize_t dev_read(struct file *filep, char __user *buffer, size_t len, loff_t *offset) { int bytes_to_read; int ret; // 计算还能读多少字节 bytes_to_read BUFFER_SIZE - buffer_offset; if (bytes_to_read len) bytes_to_read len; if (bytes_to_read 0) return 0; // EOF // 将内核缓冲区数据拷贝到用户空间 ret copy_to_user(buffer, device_buffer buffer_offset, bytes_to_read); if (ret) { printk(KERN_ERR my_char_dev: Failed to send %d bytes to user.\n, ret); return -EFAULT; } buffer_offset bytes_to_read; printk(KERN_INFO my_char_dev: Sent %d bytes to user.\n, bytes_to_read); return bytes_to_read; } static ssize_t dev_write(struct file *filep, const char __user *buffer, size_t len, loff_t *offset) { int bytes_to_write; int ret; // 计算还能写多少字节 bytes_to_write BUFFER_SIZE - buffer_offset; if (bytes_to_write len) bytes_to_write len; if (bytes_to_write 0) return -ENOSPC; // 设备缓冲区已满 // 将用户空间数据拷贝到内核缓冲区 ret copy_from_user(device_buffer buffer_offset, buffer, bytes_to_write); if (ret) { printk(KERN_ERR my_char_dev: Failed to receive %d bytes from user.\n, ret); return -EFAULT; } buffer_offset bytes_to_write; printk(KERN_INFO my_char_dev: Received %d bytes from user.\n, bytes_to_write); return bytes_to_write; } static int dev_release(struct inode *inodep, struct file *filep) { printk(KERN_INFO my_char_dev: Device closed.\n); return 0; } // 定义 file_operations 结构体 static struct file_operations fops { .owner THIS_MODULE, .open dev_open, .read dev_read, .write dev_write, .release dev_release, }; static int __init char_dev_init(void) { int ret; struct device *dev_ret; printk(KERN_INFO my_char_dev: Initializing module.\n); // 1. 动态申请一个设备号主次 ret alloc_chrdev_region(dev_num, 0, 1, DEVICE_NAME); if (ret 0) { printk(KERN_ERR my_char_dev: Failed to allocate device number.\n); return ret; } major_num MAJOR(dev_num); printk(KERN_INFO my_char_dev: Allocated major number %d.\n, major_num); // 2. 初始化 cdev 结构体并将其与 fops 关联 cdev_init(my_cdev, fops); my_cdev.owner THIS_MODULE; // 3. 将 cdev 添加到内核系统设备号从 dev_num 开始数量为1 ret cdev_add(my_cdev, dev_num, 1); if (ret 0) { printk(KERN_ERR my_char_dev: Failed to add cdev to system.\n); goto err_cdev_add; } // 4. 创建设备类在/sys/class/下可见 char_class class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(char_class)) { printk(KERN_ERR my_char_dev: Failed to create device class.\n); ret PTR_ERR(char_class); goto err_class_create; } // 5. 在 /dev 目录下创建设备文件节点 // 这一步会自动在/dev下生成 DEVICE_NAME 文件并绑定设备号 dev_ret device_create(char_class, NULL, dev_num, NULL, DEVICE_NAME); if (IS_ERR(dev_ret)) { printk(KERN_ERR my_char_dev: Failed to create the device.\n); ret PTR_ERR(dev_ret); goto err_device_create; } printk(KERN_INFO my_char_dev: Device created successfully at /dev/%s\n, DEVICE_NAME); return 0; // 错误处理逆序清理已分配的资源 err_device_create: class_destroy(char_class); err_class_create: cdev_del(my_cdev); err_cdev_add: unregister_chrdev_region(dev_num, 1); return ret; }3.2 模块退出资源清理有初始化就必须有清理这是编写稳健内核代码的铁律。static void __exit char_dev_exit(void) { // 1. 销毁 /dev 下的设备节点 device_destroy(char_class, dev_num); // 2. 销毁设备类 class_destroy(char_class); // 3. 从系统中删除 cdev cdev_del(my_cdev); // 4. 释放设备号 unregister_chrdev_region(dev_num, 1); printk(KERN_INFO my_char_dev: Module removed, device /dev/%s destroyed.\n, DEVICE_NAME); } module_init(char_dev_init); module_exit(char_dev_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(A simple character device driver example);3.3 编译、加载与测试编写对应的Makefileobj-m my_char_dev.o KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) clean操作流程实录编译make加载模块sudo insmod my_char_dev.ko查看内核日志dmesg | tail -20。你应该能看到类似“my_char_dev: Device created successfully at /dev/my_char_dev”和分配的主设备号比如247。检查设备节点ls -l /dev/my_char_dev。你会看到类似crw------- 1 root root 247, 0 ...的输出。c表示字符设备247, 0就是主设备号和次设备号。测试读写# 写入数据 echo Hello, Char Device! | sudo tee /dev/my_char_dev # 读取数据 sudo cat /dev/my_char_dev再次查看dmesg可以看到驱动打印的读写日志。卸载模块sudo rmmod my_char_dev。检查/dev/my_char_dev文件应被自动删除。4. 关键环节深度解析与避坑指南上面的代码跑通了但里面每个步骤都藏着细节和“坑”。我们来逐一拆解。4.1 设备号申请静态与动态的抉择静态注册 (register_chrdev)这是老式接口一次申请一个主设备号下的所有次设备号通常是256个。它内部会自动调用cdev_init和cdev_add。代码简单但不够灵活且主设备号可能冲突。不推荐在新驱动中使用。动态注册 (alloc_chrdev_region)如上例所示内核自动分配一个空闲的主设备号。这是推荐做法。alloc_chrdev_region(dev, firstminor, count, name)中firstminor通常是0count是你需要的设备数量支持多个次设备。实操心得在生产驱动中如果设备数量可能变化使用alloc_chrdev_region并预留足够的count是更安全的选择。通过cat /proc/devices可以查看已分配的设备号避免冲突。4.2cdev_add的时机与并发风险cdev_add()是关键转折点。调用它之后设备就对内核可见用户空间的open系统调用就可能找到并调用你的驱动函数。这意味着你必须在cdev_add()之前完成所有初始化工作包括cdev_init、内存分配、硬件初始化等。一旦cdev_add成功你的file_operations中的函数尤其是.open就必须准备好被并发调用。即使你的模块初始化函数还没返回另一个进程也可能已经尝试打开设备。常见坑点在.open函数里做大量耗时的初始化如硬件探测。这会导致用户进程卡住甚至可能因为依赖未完成的模块初始化而出错。正确的做法是把必要的、耗时的初始化放在模块的init函数中在cdev_add之前完成。4.3device_create与 udev/mdev 的协作device_create()不仅创建了/dev下的节点更关键的是它在sysfs通常是/sys/class/下创建了对应的设备信息。现代Linux发行版使用udev或嵌入式系统用的mdev来管理/dev动态节点。device_create(class, parent, devt, drvdata, “name”)会在/sys/class/CLASS_NAME/下创建一个以name命名的目录里面包含dev文件记录设备号等属性。udev守护进程监控sysfs事件当它发现这个新设备时会根据规则/etc/udev/rules.d/在/dev下创建具有特定权限、所有者和名字的节点。注意事项如果你发现/dev下节点权限不对比如不是crw-rw----或者想给设备一个固定的名字如/dev/myapp/tty1就需要编写udev规则而不是在驱动里硬编码。驱动只负责提供原始的devt和基础名称。4.4 文件操作函数实现中的核心细节copy_to_user/copy_from_user这是用户空间和内核空间数据交换的唯一安全通道。它们会检查用户空间指针的有效性。绝对不要直接对用户空间指针进行解引用操作那是致命的安全漏洞和崩溃源头。返回值语义read/write返回成功传输的字节数。返回0对于read表示EOF对于write通常可以接受。返回负值表示错误-EIO,-EFAULT,-ENOMEM等。loff_t *offset这个参数在普通文件操作中表示文件偏移。但在很多简单的字符设备驱动中如我们的例子我们可能选择忽略它自己维护一个像buffer_offset这样的内部状态。更规范的做法是使用*offset这样能更好地支持pread/pwrite等系统调用。并发控制如果多个进程同时打开你的设备并进行读写你需要考虑竞争条件。简单的设备可能不需要锁但复杂的、有共享状态的设备必须使用内核提供的同步机制如信号量 (semaphore)、互斥锁 (mutex)或自旋锁 (spinlock)。锁通常放在设备私有数据结构中在.open时初始化在.release时销毁。5. 进阶话题与扩展思考掌握了基础创建流程后我们可以看看更复杂的场景。5.1 支持多个次设备号一个驱动管理多个同类型设备非常常见如多个串口。你需要在alloc_chrdev_region时申请多个设备号count 1。为每个设备实例分配一个私有数据结构包含其特定状态如缓冲区、锁、硬件寄存器地址等。在file_operations的函数中通过struct inode参数获取次设备号iminor(inode)然后索引到对应的私有数据结构进行操作。关键代码片段示例static int dev_open(struct inode *inodep, struct file *filep) { int minor iminor(inodep); struct my_device_data *dev_data device_data_array[minor]; filep-private_data dev_data; // 将设备私有数据存入file结构 // ... 其他操作 return 0; }5.2 使用file-private_data传递上下文struct file中有一个void *private_data成员专门用于驱动存储每个“打开文件”的上下文信息。这是一个极其重要的技巧。在.open中将指向该设备实例私有数据的指针赋值给file-private_data。在.read,.write,.release等其他操作中通过file-private_data直接获取上下文避免了每次都要通过inode计算的麻烦代码更清晰高效。5.3 自动创建设备节点的旧式方法mknod在device_create和udev普及之前驱动需要手动或通过脚本调用mknod命令创建设备节点。命令格式为mknod /dev/name c major minor。虽然现在不推荐在驱动模块中直接调用但理解它有助于调试。当你的设备没有自动出现在/dev时可以用它手动创建来测试驱动本身是否工作正常。6. 常见问题排查与调试技巧实录驱动开发三分写七分调。以下是我踩过坑后总结的排查清单。6.1 模块加载失败insmod: ERROR: could not insert module: Invalid parameters检查1MODULE_LICENSE是否正确定义没有或非GPL兼容许可可能导致某些符号无法使用。检查2内核版本是否匹配用uname -r确认编译用的内核头文件路径KDIR是否正确。insmod: ERROR: could not insert module: Device or resource busy检查1设备号冲突。可能是之前模块未完全卸载或静态分配的主设备号已被占用。用cat /proc/devices查看并确保在模块退出函数中正确调用了unregister_chrdev_region。检查2/dev下的节点文件仍被某个进程占用着。用lsof /dev/your_device查看并关闭相应进程。6.2 设备节点未出现或权限不对/dev下没有设备文件检查1dmesg看device_create是否成功。IS_ERR()判断了吗检查2udev服务是否运行sysfs中是否有设备信息检查/sys/class/CLASS_NAME/下是否存在以你设备命名的目录。如果有说明驱动部分成功了是用户空间udev的问题。可以尝试手动触发udev规则sudo udevadm trigger。设备文件权限不是crw--w----(600)这是udev规则管理的。默认由驱动创建的节点权限是root:root 600。如果需要改变必须编写udev规则文件。6.3 用户空间操作设备失败open: No such device驱动模块未加载或cdev_add失败导致内核找不到对应设备号的驱动。write: Operation not permitted文件权限问题。确保测试进程有读写权限通常需要用sudo。read/write: Bad address几乎肯定是驱动中copy_to/from_user失败了返回了-EFAULT。检查用户空间缓冲区指针在你调用copy_*时是否有效。用户空间传递的缓冲区可能在系统调用过程中被换出或失效但copy_*函数会处理这些情况如果还报错可能是你计算的长度或偏移超出了合理范围。系统调用卡住不返回驱动可能在某个操作如.read中阻塞了。检查是否有未正确处理的等待队列、锁未释放或死循环。使用printk加KERN_DEBUG级别日志追踪函数执行流程。6.4 内核崩溃 (Oops)这是最严重的情况。dmesg会打印详细的调用栈。常见原因1空指针解引用。检查所有从private_data、inode-i_cdev等地方获取的指针是否在.open中正确赋值。常见原因2内存访问越界。检查所有数组索引、缓冲区长度计算。常见原因3在原子上下文如中断处理程序、自旋锁持有期间中执行了可能睡眠的操作如kmalloc(GFP_KERNEL)、copy_from_user。此时应使用GFP_ATOMIC标志分配内存。调试技巧尽可能简化驱动先实现一个空的.open和.release确保基础框架稳定再逐步添加read/write功能。字符设备是Linux驱动世界的基石。从简单的内存缓冲区到复杂的硬件控制器其核心创建流程万变不离其宗。理解cdev,file_operations, 设备号以及它们在内核对象模型中的生命周期是打开驱动开发大门的钥匙。记住稳健的驱动始于清晰的错误处理和资源管理在init函数中分配的资源必须在exit函数中逆序释放。多读内核源码中经典的字符设备驱动如drivers/char/mem.c结合动手实践你会对这套精妙的抽象机制有更深刻的体会。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…