杂项设备与虚拟设备:Linux驱动开发的高效捷径
我第一次写字符设备驱动的时候被那套注册流程折腾得够呛。alloc_chrdev_region申请设备号、cdev_init初始化、cdev_add注册、还要class_create建类、device_create创建设备节点一串代码下来三五十行真正的业务逻辑还没写一行。后来有人告诉我这种简单设备直接用杂项设备miscdevice几行代码就把口子开好了。再配合虚拟设备硬件还没到手驱动逻辑已经能跑通了。这篇文章就把这两样“神器”掰开揉碎讲清楚适合刚入门的驱动开发者也能给正在做前期验证的工程师提供一条能直接抄的路径。杂项设备到底解决了我什么问题做内核驱动开发的都知道驱动本质上就是给硬件和上层软件之间搭桥。桥搭得越多重复劳动就越明显。尤其是那些功能单一、结构简单的设备——比如一个 GPIO 控制的 LED、一个温度传感器、一个看门狗——它们需要的只是一个能被用户态打开、读写下数据的接口。字符设备注册流程的重复劳动如果你坚持用标准字符设备框架每次都要走一遍完整的流程dev_t dev_num; struct cdev *cdev; struct class *cls; struct device *dev; alloc_chrdev_region(dev_num, 0, 1, my_device); cdev cdev_alloc(); cdev_init(cdev, my_fops); cdev_add(cdev, dev_num, 1); cls class_create(THIS_MODULE, my_class); dev device_create(cls, NULL, dev_num, NULL, my_device);这段代码眼熟吧几乎每个字符设备驱动里都有复制粘贴的影子。代码本身不难就是烦。而且这还没完——卸载的时候要对应释放顺序还不能搞错。项目里设备一多这种模板代码就成了纯粹的噪音。最要命的是每增加一种设备几乎都会占一个主设备号。系统里字符设备主设备号虽然有那么多但实际可用范围是有限的尤其在大型嵌入式系统中主设备号也是一种资源。为一个只开一个小接口的设备去申请一个主设备号成本偏高。杂项设备的“打包”思路杂项设备的思路就简单粗暴所有杂项设备共用主设备号 10用次设备号区分不同设备。只需要定义一个miscdevice结构体然后调一次misc_register设备节点自动出现在/dev下卸载调一次misc_deregister完事。省的不止是代码量。你不用自己管主设备号申请不用建 class不用管设备节点何时创建。内核的杂项子系统已经把这些流程全部打包好了。这个“打包”的思路恰恰是它价值的核心常见的简单设备需求被抽象成了一套标准流程你只需填自己的差异化部分。miscdevice 的内部机制与使用边界既然说它好用就不能不说清楚它为什么好、什么时候不好。知其然还要知其所以然否则遇到问题会一头雾水。结构体里藏着的关键信息看一下内核头文件include/linux/miscdevice.h里struct miscdevice定义不同内核版本略有差异但大致如下struct miscdevice { int minor; const char *name; const struct file_operations *fops; struct list_head list; struct device *parent; struct device *this_device; const struct attribute_group **groups; const char *nodename; umode_t mode; };几个关键字段minor次设备号。可以显式给定也可以设成MISC_DYNAMIC_MINOR让内核帮你找一个空闲的。name设备名会出现在/dev下面。比如name my_dev注册后就有/dev/my_dev。fops文件操作集合就是 read、write、ioctl 这些回调。groups可选的 sysfs 属性组用来暴露一些运行时信息。misc_register 的调用链misc_register核心是向内核的杂项设备子系统登记一个条目。流程大致这样检查minor是否已经被占用这是通过遍历一个全局链表misc_list完成的。如果用的是MISC_DYNAMIC_MINOR就找一个空闲的次设备号。在内核的miscclass 下创建设备节点配合 devtmpfs 机制这个节点立刻出现在/dev。把设备挂到misc_list链表里。也就是说misc_register内部实际上还是走了字符设备注册的整套流程。只是这套流程已经被杂项设备子系统封装好用户态看到的效果就是简单、直接。动态次设备号的分配策略当设置minor MISC_DYNAMIC_MINOR时内核会从保留用于动态分配的区间向下搜索一个可用的次设备号。这样带来的好处是驱动不需要关注具体的次设备号数值只要保证设备名不冲突就行。设备名冲突比次设备号冲突更常见misc_register会同时检查这两点。这类设备比较适合的场景是单一设备节点、操作逻辑简单、不需要复杂的 sysfs 目录树、不需要自定义 class 和 uevent 控制。当一个驱动需要多个主设备号、多个设备类、复杂的设备拓扑时就不应该用杂项设备了。传统字符设备注册与杂项设备对比对比项传统字符设备杂项设备主设备号动态或固定申请固定 10次设备号自主管理手动指定或动态分配设备类/设备节点自行创建内核统一创建注册代码量30 行以上需要 3 行sysfs 目录完全掌控自由扩展受子系统约束较难自定义适用场景复杂设备、协议栈、多设备实例单一功能、轻量接口零基础也能复现的杂项设备驱动 Demo看再多原理不如动手跑一个。这里用一个简单例子在/dev下创建一个misc_demo设备用户态可以往里写一段字符串然后读出来。功能虽小read/write 的桥接逻辑都有了。完整驱动源码#include linux/module.h #include linux/kernel.h #include linux/miscdevice.h #include linux/fs.h #include linux/uaccess.h #include linux/string.h #define DEV_NAME misc_demo static char data_buf[128] {0}; static ssize_t misc_demo_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos) { if (*ppos sizeof(data_buf)) return 0; if (len sizeof(data_buf) - *ppos) len sizeof(data_buf) - *ppos; if (copy_to_user(buf, data_buf *ppos, len)) return -EFAULT; *ppos len; return len; } static ssize_t misc_demo_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) { if (len sizeof(data_buf)) return -EINVAL; if (copy_from_user(data_buf, buf, len)) return -EFAULT; data_buf[len] \0; return len; } static const struct file_operations misc_demo_fops { .owner THIS_MODULE, .read misc_demo_read, .write misc_demo_write, }; static struct miscdevice misc_demo_dev { .minor MISC_DYNAMIC_MINOR, .name DEV_NAME, .fops misc_demo_fops, }; static int __init misc_demo_init(void) { return misc_register(misc_demo_dev); } static void __exit misc_demo_exit(void) { misc_deregister(misc_demo_dev); } MODULE_LICENSE(GPL); MODULE_DESCRIPTION(misc device demo);这里有三个点需要说明copy_to_user和copy_from_user是内核态与用户态数据交换的标准接口不能直接用memcpy否则地址校验不过。ppos是文件偏移指针read 和 write 需要手动维护否则连续读会出现问题。fops-owner THIS_MODULE保证在使用设备时模块不会被意外卸载避免悬空指针。Makefile 与编译写一个最普通的 Makefileobj-m misc_demo.o KERNEL_DIR ? /lib/modules/$(shell uname -r)/build all: $(MAKE) -C $(KERNEL_DIR) M$(PWD) modules clean: $(MAKE) -C $(KERNEL_DIR) M$(PWD) clean执行make后会生成misc_demo.ko。注意编译内核模块需要安装内核头文件包Ubuntu 系用sudo apt install linux-headers-$(uname -r)就能解决。很多新手在自己的电脑上编译不过多半是头文件没装齐。加载与测试sudo insmod misc_demo.ko ls -l /dev/misc_demo echo hello misc device /dev/misc_demo cat /dev/misc_demo如果一切正常cat会打印之前写入的字符串。用完记得卸载sudo rmmod misc_demo如果insmod失败先用dmesg | tail看内核日志常见原因是设备名已经被占用或者内核版本 API 不兼容。开发阶段习惯性看 dmesg能省很多排查时间。虚拟设备的价值硬件没到手驱动先动工理解了杂项设备的轻量机制接下来聊虚拟设备。很多人一听“虚拟设备”就觉得是玩具——没有真实硬件写出来的驱动能算数吗我的回答是能而且非常能。虚拟设备不是玩具是必要的开发手段驱动开发的瓶颈往往不在代码本身而在对硬件行为的理解。更现实的问题是硬件板卡还没焊接完成或者芯片还处在样片阶段驱动工程师已经需要交付代码了。虚拟设备的思路就是用软件模拟一个设备的“脾气”它有寄存器状态、有数据变化规律、有中断触发的时序。驱动代码访问它时和访问真实硬件在逻辑上是一样的。真实项目中虚虚实实结合的开发模式非常常见。很多设备厂会带着模拟器或者仿真模型做前期软件开发等到真实芯片到位后再切换验证。这种模式不是偷懒而是并行工程的一部分。把硬件访问抽象成接口层虚拟设备能发挥作用的关键在于驱动架构设计。我通常会把驱动分成两层上层是业务逻辑层负责数据解析、状态机、用户态接口。下层是硬件访问层负责对具体硬件的读写。硬件访问层提供一个统一的访问接口例如static unsigned int sensor_reg_read(unsigned int reg);真实硬件版本static unsigned int sensor_reg_read(unsigned int reg) { return readl(base_addr reg); }虚拟实现版本static unsigned int sensor_reg_read(unsigned int reg) { return fake_regs[reg 0xff]; }上层代码完全不需要关心读写的是真实寄存器还是一块内存数组。这就是虚拟设备的核心价值接口先行逻辑先行。等真实硬件回来的那天只需要替换底层实现上层代码一行不用动。虚拟实现的常见形态虚拟设备在驱动开发里可以有很多种形态常见的有用全局数组模拟寄存器块读写就操作数组元素。用内核定时器周期性更新“寄存器”数值模拟真实设备的采样和变化。用中断回调模拟硬件中断测试驱动的中断处理路径。用内存地址映射模拟 MMIO让驱动可以用ioremap和readl/writel访问。这些手段都不复杂却能提前暴露相当多的逻辑问题尤其是状态机、时序竞态、缓冲区溢出这一类跟硬件绑定不深、但影响重大的缺陷。杂项设备 虚拟设备组合实战虚拟环境监测器理论说多了容易飘这里来一个完整可复现的实例做一个“虚拟环境监测器”。硬件不存在但驱动、设备节点、用户态接口全部和真实系统对齐。项目需求与架构设计需求很简单提供一个/dev/virtual_sensor节点。用户态read可以读到当前温度。用户态ioctl可以设置采样周期。温度在“硬件”层面每经过一个周期自动变化。架构上分成三层虚拟硬件层一个数组模拟寄存器区内核定时器周期更新温度值。驱动层用miscdevice注册设备read/ioctl 负责数据传递。用户态测试程序验证整个链路。驱动核心代码#include linux/module.h #include linux/kernel.h #include linux/miscdevice.h #include linux/fs.h #include linux/uaccess.h #include linux/timer.h #include linux/ioctl.h #define SENSOR_DEV_NAME virtual_sensor #define SENSOR_IOCTL_MAGIC 0xA1 #define SENSOR_GET_TEMP _IOR(SENSOR_IOCTL_MAGIC, 1, int) #define SENSOR_SET_PERIOD _IOW(SENSOR_IOCTL_MAGIC, 2, int) static unsigned int sensor_regs[4]; #define REG_STATUS 0 #define REG_TEMP_RAW 1 #define REG_PERIOD_MS 2 #define REG_UPDATE_CNT 3 static struct timer_list sensor_timer; /* 模拟硬件行为温度随时间上升代表真实传感器在读数 */ static void sensor_hw_update(void) { sensor_regs[REG_TEMP_RAW] 37; sensor_regs[REG_UPDATE_CNT]; } static void sensor_timer_cb(struct timer_list *t) { sensor_hw_update(); mod_timer(sensor_timer, jiffies msecs_to_jiffies(sensor_regs[REG_PERIOD_MS])); } /* 硬件访问抽象接口这里直接用虚拟寄存器 */ static unsigned int sensor_reg_read(unsigned int reg) { return sensor_regs[reg 3]; } static ssize_t sensor_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { int temp; if (*ppos 0) return 0; temp (int)sensor_reg_read(REG_TEMP_RAW); if (copy_to_user(buf, temp, sizeof(temp))) return -EFAULT; *ppos sizeof(temp); return sizeof(temp); } static long sensor_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int period; switch (cmd) { case SENSOR_GET_TEMP: if (copy_to_user((void __user *)arg, sensor_regs[REG_TEMP_RAW], sizeof(sensor_regs[REG_TEMP_RAW]))) return -EFAULT; break; case SENSOR_SET_PERIOD: if (copy_from_user(period, (void __user *)arg, sizeof(period))) return -EFAULT; if (period 0 || period 60000) return -EINVAL; sensor_regs[REG_PERIOD_MS] period; mod_timer(sensor_timer, jiffies msecs_to_jiffies(sensor_regs[REG_PERIOD_MS])); break; default: return -ENOTTY; } return 0; } static const struct file_operations sensor_fops { .owner THIS_MODULE, .read sensor_read, .unlocked_ioctl sensor_ioctl, }; static struct miscdevice sensor_miscdev { .minor MISC_DYNAMIC_MINOR, .name SENSOR_DEV_NAME, .fops sensor_fops, }; static int __init sensor_init(void) { int ret; sensor_regs[REG_STATUS] 1; sensor_regs[REG_TEMP_RAW] 25000; sensor_regs[REG_PERIOD_MS] 1000; timer_setup(sensor_timer, sensor_timer_cb, 0); mod_timer(sensor_timer, jiffies msecs_to_jiffies(1000)); ret misc_register(sensor_miscdev); if (ret) { del_timer_sync(sensor_timer); return ret; } return 0; } static void __exit sensor_exit(void) { del_timer_sync(sensor_timer); misc_deregister(sensor_miscdev); } MODULE_LICENSE(GPL); MODULE_DESCRIPTION(Virtual environment sensor);这里有几个设计细节值得注意sensor_regs就是“硬件寄存器”驱动所有读值操作都走sensor_reg_read将来替换为真实硬件时只需改这一层。内核定时器模拟了硬件“主动上报数据”的行为。真实传感器芯片往往是每隔一段时间内部更新一次这个定时器就是在模拟这个过程。ioctl 用_IOR/_IOW宏生成命令号封装了方向与数据大小用户态也必须定义同一组宏。用户态测试程序#include stdio.h #include fcntl.h #include unistd.h #include sys/ioctl.h #include stdlib.h #define SENSOR_IOCTL_MAGIC 0xA1 #define SENSOR_GET_TEMP _IOR(SENSOR_IOCTL_MAGIC, 1, int) #define SENSOR_SET_PERIOD _IOW(SENSOR_IOCTL_MAGIC, 2, int) int main(void) { int fd open(/dev/virtual_sensor, O_RDWR); if (fd 0) { perror(open); return 1; } int temp 0; int period 200; if (ioctl(fd, SENSOR_SET_PERIOD, period) 0) { perror(ioctl set period); close(fd); return 1; } for (int i 0; i 5; i) { if (read(fd, temp, sizeof(temp)) ! sizeof(temp)) { perror(read); break; } printf(temp%d.%03d C\n, temp / 1000, temp % 1000); usleep(300 * 1000); } close(fd); return 0; }测试流程make sudo insmod virtual_sensor.ko ls -l /dev/virtual_sensor gcc -o sensor_test sensor_test.c ./sensor_test sudo rmmod virtual_sensor预期能看到温度数值持续变化说明定时器驱动的虚拟硬件更新、杂项设备接口、用户态数据通道全部工作正常。使用边界与个人避坑经验再好的工具也有边界。杂项设备和虚拟设备帮我们省了不少事但它们并不是万能钥匙。我踩过不少坑总结几条最值得说的经验。杂项设备什么时候不划算杂项设备虽然省事但功能边界也很明确。如果你的设备需要以下特征建议回老实的字符设备流程需要多个设备节点协同工作并希望它们归属同一个自定义 class 和 sysfs 目录树下。需要对每个设备实例保存独立的私有数据并希望在 open 时直接从 cdev 的私有数据里取出来。需要自定义设备热插拔的 uevent 交互逻辑。需要大量的 sysfs 属性文件而且要组织成一个有层级的目录结构。杂项设备做得最极致的地方就是“帮你把 bootstrapping 做了”但它的 class 固定是 misc设备的 sysfs 路径、命名方式都是系统级统一的自定义空间反而很小。另一个常见的坑是杂项设备在 open 回调里拿不到指向你miscdevice结构体的指针。如果多个设备实例用同一个 fops你不能像字符设备那样在 open 里直接用container_of得到私有数据。此时要么通过file-f_op做入口再查找要么在 open 里自己从全局链表里找。虚拟设备无法替代真实验证虚拟设备能帮你把逻辑跑通但有一类问题是它永远模拟不了的真实硬件的时序不稳定读写太快可能丢数据。中断优先级和延迟在真实系统里千变万化。硬件手册不会写清楚的怪癖、坑、勘误。电磁干扰、电源波动、引脚竞争这类物理世界的现象。所以我的习惯是虚拟设备负责把确定性问题提前消灭真实硬件负责把不确定性问题暴露出来。两者不是替代关系是接力关系。虚拟设备最大的成就是让你拿到真实板卡的第一天专注在硬件适配和调试上而不是还在补逻辑漏洞。我常用的开发顺序这套方法经过几个项目检验后我形成了一套比较固定的开发顺序也可以作为你的参考先写虚拟硬件层模拟寄存器、定时器、中断行为把设备的外部表现确定下来。用杂项设备快速搭建用户态接口保证 read / write / ioctl 通道畅通。在虚拟硬件上完成所有业务逻辑、状态机、异常路径的测试。硬件就绪后只替换硬件访问层保留上层逻辑与用户态接口。上板联调时重点观察时序、中断、DMA 相关的行为把虚拟阶段无法模拟的问题集中解决。这套顺序尤其在“硬件和驱动并行开发”的项目里有奇效。驱动工程师不用干等硬件软件组也能提前完成大部分集成测试。最后再分享一个小经验用杂项设备 虚拟设备做原型验证时不要急着把代码写得很精致。先把链路打通再用接口边界去约束实现。很多人一开始就在思考“这个寄存器到底该怎么映射”反而忘了一个朴素的道理——接口先跑通再谈优化。如果你也遇到过“等硬件等得心慌”的情况不妨试试这个方法折腾半天不如动手跑一个虚拟设备出来真的会上瘾。

相关新闻

规范等变图神经网络中的环路几何涌现:原理、复现与可视化实践

规范等变图神经网络中的环路几何涌现:原理、复现与可视化实践

前阵子调研图神经网络的最新进展,翻到一篇关于“规范等变图神经网络中的环路几何涌现”的工作,读完思路挺受启发的。说句实话,这类标题第一眼确实劝退——又“规范等变”又“环路几何”,术语叠满,但拆开看,…

2026/10/17 8:33:23 阅读更多 →
WorldOracle:行动条件世界模型的真实世界执行评测指南

WorldOracle:行动条件世界模型的真实世界执行评测指南

做机器人操作项目的朋友应该都遇过这种场景:模型在离线数据集上的评估曲线一路飘绿,官方视频看着也很丝滑,你满怀信心把策略或规划器部署到真实执行端,结果机械臂要么停在原地不动,要么把目标物体越推越远。这个问题在…

2026/10/17 8:33:23 阅读更多 →
从AI焦虑到AI转型:掌握工作流与提示词,成为超级个体

从AI焦虑到AI转型:掌握工作流与提示词,成为超级个体

最近这半年,我身边的朋友分成了两拨:一拨人被“AI焦虑”压得喘不过气,天天刷各种大模型消息,越刷越慌;另一拨人已经偷偷在用AI干活,有的用AI做设计初稿,有的用AI写周报,甚至还有人靠…

2026/10/17 8:33:23 阅读更多 →

最新新闻

RIP路由协议深度实验:距离矢量与防环机制全解析

RIP路由协议深度实验:距离矢量与防环机制全解析

不管你现在用的是OSPF还是IS-IS,只要你还在跟路由打交道,我都建议你花一晚上把RIP认真玩一遍。我这么说不是让你在现网把路由协议换回RIP,而是因为RIP这个协议太适合用来理解"路由协议到底在做什么"了。它简单到你看完原理就能在模…

2026/10/17 9:12:32 阅读更多 →
C++ Qt4 2048小游戏源码详解:二维数组与信号槽实战

C++ Qt4 2048小游戏源码详解:二维数组与信号槽实战

简介:这是一份基于C与Qt4框架编写的2048小游戏源码,面向初学C及图形界面编程的读者。项目包含完整的游戏逻辑与界面实现,六个文件涵盖两个cpp源文件、一个pro工程文件、一个ui界面文件、一个头文件及一个user配置文件,其中cpp与头…

2026/10/17 9:12:31 阅读更多 →
Cocos Creator 拖拽排序:基于Layout的轻量级实现与避坑指南

Cocos Creator 拖拽排序:基于Layout的轻量级实现与避坑指南

简介:面向 Cocos Creator 开发者的拖拽排序列表项目示例,适合需要在游戏或应用中实现列表项自由拖拽重排的开发者参考。资源包以完整项目工程为主,压缩包共 16 个文件,约 243KB;核心包括 TypeScript 脚本、Fire 场景文…

2026/10/17 9:12:31 阅读更多 →
神经网络与麻雀搜索算法联手:转向架构架焊缝疲劳尺寸优化实战

神经网络与麻雀搜索算法联手:转向架构架焊缝疲劳尺寸优化实战

上个月审某型地铁转向架构架的疲劳计算报告时,我盯着横侧梁连接焊缝的安全系数看了很久。按标准载荷谱算下来,那个节点只有1.12,勉强压线。按以往的经验,这种结果交出去之后,工艺那边大概率会找上门——焊接顺序和探伤…

2026/10/17 9:12:31 阅读更多 →
Servlet+JSP+MySQL成绩管理系统源码实战:ECharts统计图与避坑指南

Servlet+JSP+MySQL成绩管理系统源码实战:ECharts统计图与避坑指南

简介:这是一套面向JavaWeb初学者与课程设计开发者的学生成绩管理系统完整源码,基于Servlet、JSP与MySQL技术栈实现,在原项目基础上新增了ECharts统计图表功能,可用于毕业设计、课程作业或框架入门练习。压缩包共344个文件&#xf…

2026/10/17 9:12:31 阅读更多 →
AI原生开发工具链:Superpowers如何重构程序员能力模型

AI原生开发工具链:Superpowers如何重构程序员能力模型

1. “Superpowers”不是超能力,而是开发者工具链的代际跃迁 最近在技术社区里,“superpowers”这个词出现频率高得有点反常——它既不是 Marvel 新出的漫威剧集,也不是某家初创公司刚融资的 SaaS 产品名,而是一整套正在快速渗透主…

2026/10/17 9:11:31 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →