Linux驱动---Linux 中断系统及其上与下半部的介绍与阻塞IO实现按键检测
目录一. Linux 的中断系统1.1 中断概念1.2 回顾裸机中中断处理方法1.3 Linux 中断相关API函数1.3.1 request_irq 函数1.3.2 free_irq 函数1.3.3 中断处理函数1.3.4 中断使能与禁止函数二. 中断的上半部与下半部2.1 简介2.2 下半部的实现方式2.2.1 软中断2.2.2 tasklet1、定义初始化宏2、调用2.2.3 工作队列 Workqueue1、初始化宏2、调度函数三. 设备树中的中断信息3.1 .dtsi 文件3.2 设备树中断函数3.2.1 获取中断号四. 按键中断示例代码4.1 修改设备树4.2 驱动代码4.3 应用层代码4.4 结果五. 阻塞IO 与非阻塞 IO5.1 区别5.2 等待队列5.3 相关函数5.3.1 init_waitqueue_head5.3.2 等待唤醒5.3.3 等待事件5.4 修改示例代码一. Linux 的中断系统1.1 中断概念中断是指CPU在处理事件的过程中遇到了突发事情需要去处理CPU暂停当前程序转去处理突发事件处理完之后又返回原程序被打断的地方继续执行这个过程就叫做中断。中断分为了内部中断和外部中断内部中断来源于CPU内部外部中断来源于CPU外部1.2 回顾裸机中中断处理方法1、使能中断初始化相应的中断2、注册中断处理函数也就是向中断向量表的指定下标处写入中断处理函数3、中断发生后执行相应的中断处理函数1.3 Linux 中断相关API函数1.3.1 request_irq 函数在 Linux 内核中要想使用某个中断是需要申请的request_irq 函数用于申请中断request_ irq 函数可能会导致睡眠因此不能在中断上下文或者其他禁止睡眠的代码段中使用request_irq 函数。request_irq 函数会激活(使能)中断所以不需要手动去使能中断作用注册中断处理函数绑定中断号与处理函数参数irq要申请中断的中断号。handler中断处理函数当中断发生以后就会执行此中断处理函数类型如下typedef irqreturn_t (*irq_handler_t)(int, void *);flags中断标志可以在文件include/linux/interrupt.h里面查看所有的中断标志这里我们 介绍几个常用的中断标志如图所示name中断名字设置以后可以在/proc/interrupts文件中看到对应的中断名字proc下都是与进程相关的dev如果将 flags 设置为 IRQF_SHARED 的话dev 用来区分不同的中断一般情况下将 dev 设置为设备结构体dev 会传递给中断处理函数 irq_handler_t 的第二个参数也可设置为 NULL返回值0 表示中断申请成功其他负值中断申请失败如果返回-EBUSY的话表示中断已经被申请了1.3.2 free_irq 函数使用中断的时候需要通过 request_irq 函数申请使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的那么 free_irq 会删除中断处理函数并且禁止中断作用释放已经注册的中断资源参数irq要释放的中断。dev一般为 NULL返回值无1.3.3 中断处理函数使用 request_irq 函数申请中断的时候需要设置中断处理函数中断处理函数格式如下irqreturn_t (*irq_handler_t) (int, void *)参数第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针也就是个通用指针需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备 dev也可以指向设备数据结构。返回值中断处理函数的返回值为 irqreturn_t 类型irqreturn_t 类型定义如下所示enum irqreturn { IRQ_NONE (0 0), IRQ_HANDLED (1 0), IRQ_WAKE_THREAD (1 1), }; typedef enum irqreturn irqreturn_t;代表的意义IRQ_NONE:中断触发了但是不是由本函数处理因为中断可以共IRQ_NANDLED与上面相反处理中断IRQ_WAKE_THREAD处理中断放在另一线程中且唤醒线程在中断函数中唤醒等待的队列并且是条件变量置一1.3.4 中断使能与禁止函数作用enable_irq 和 disable_irq 用于使能和禁止指定的中断参数irq就是要禁止的中断号。disable_irq返回值无注意disable_irq 函数要等到当前正在执行的中断处理函数执行完才返回作用禁止中断但是与 disable_irq 不同的是不会等待中断处理程序结束后返回会立即返回作用local_irq_enable 用于使能当前处理器中断系统local_irq_disable 用于禁止当前处理器中断系统。上面两个函数不足之处假如 A 任务调用local_irq_disable关闭全局中断 10S当关闭了 2S 的时候 B 任务开始运行B 任务也调用local_irq_disable关闭全局中断 3S3 秒以后 B 任务调用local_irq_enable函数将全局中断打开了。此时才过去 235 秒的时间然后全局中断就被打开了但是 A 任务要关闭 10S 全局中断可能系统会崩溃。作用local_irq_save函数用于禁止中断并且将中断状态保存在 flags 中。local_irq_restore用于恢复中断将中断到 flags 状态二. 中断的上半部与下半部2.1 简介在使用request_irq申请中断的时候注册的中断服务函数属于中断处理的上半部只要中断触发那么中断处理函数就会执行。中断处理函数一定要快点执行完毕越短越好但是现实中有些中断处理过程就是比较费时间必须要对其进行处理缩小中断处理函数的执行时间这个时候中断处理过程就分为 了两部分上半部上半部就是中断处理函数那些处理过程比较快不会占用很长时间的处理就可以放在上半部完成比如置一个标志位置一个硬件寄存器的标志位等等通常设置为不可被新中断打断上半部使用的场景要处理的任务不希望被其他中断打断要处理的任务对时间敏感处理与硬件相关的事物下半部如果中断处理过程比较耗时那么就将这些比较耗时的代码提出来交给下半部去执行这样中断处理函数就会快进快出下半部使用的场景处理并不是紧急的耗时任务可以被打断2.2 下半部的实现方式2.2.1 软中断软中断系统资源有限常用于系统级的任务系统中使用软中断的 enum { HI_SOFTIRQ0, TIMER_SOFTIRQ, /普通定时器 NET_TX_SOFTIRQ, /网络发送软中断 NET_RX_SOFTIRQ, /网络接收软中断 BLOCK_SOFTIRQ, /块设备 BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, /tasklet SCHED_SOFTIRQ, /调度器 HRTIMER_SOFTIRQ, /高精度定时器 RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS };2.2.2 tasklettasklet 是利用软中断来实现的另外一种下半部机制软中断调度tasklet执行时机是通常是上半部返回的时候软中断和tasklet运行于中断上下文不允许休眠非则系统会崩溃相关的函数或者宏1、定义初始化宏参数name为要定义的 tasklet 名字这个名字就是一个tasklet_struct 类型的时候变量struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; //处理函数 #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif }; typedef void (*work_func_t)(struct work_struct *work);func就是 tasklet 的处理函数类型如下:void (*func)(unsigned long);data是传递给 func 函数的参数2、调用作用在上半部也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行参数t要调度的 tasklet也就是 DECLARE_TASKLET 宏里面的 name2.2.3 工作队列 Workqueue工作队列运行于进程上下文允许休眠工作队列是另外一种下半部执行方式工作队列在进程上下文执行工作队列将要推后的 工作交给一个内核线程去执行因为工作队列工作在进程上下文因此工作队列允许睡眠Linux 内核使用work_struct 结构体表示一个工作相关的宏或者函数1、初始化宏参数_work表示要初始化的工作(struct work_struct *类型)struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); //对应的处理函数 unsigned long data; };_func 是工作对应的处理函数类型如下void workque_handler(struct work_struct *work); //工作队列处理函数2、调度函数参数work要调度的工作。返回值0成功其他值失败。三. 设备树中的中断信息如果使用设备树的话就需要在设备树中设置好中断属性信息Linux 内核通过读取设备树中的中断属性信息来配置中断3.1 .dtsi 文件打开 imx6ull.dtsi 文件其中的 intc 节点就是 I.MX6ULL 的中断控制器节点节点内容如下所示intc: interrupt-controller00a01000 { compatible arm,cortex-a7-gic; #interrupt-cells 3; interrupt-controller; reg 0x00a01000 0x1000, 0x00a02000 0x100; };1、compatible 属性值为“arm,cortex-a7-gic”在 Linux 内核源码中搜索“arm,cortex-a7 -gic”即可找到 GIC 中断控制器驱动文件2、#interrupt-cells和#address-cells、#size-cells 一样。表示此中断控制器下设备的 cells 大小对于设备而言会使用 interrupts 属性描述中断信息#interrupt-cells 描述了 interrupts 属性的 cells 大小也就是一条信息有几个 cells。每个 cells 都是 32 位整形值对于 ARM 处理的 GIC 来说一共有 3 个 cells这三个 cells 的含义如下第一个 cells中断类型0 表示 SPI 中断1 表示 PPI 中断。第二个 cells中断号对于 SPI 中断来说中断号的范围为 0~987对于 PPI 中断来说中断 号的范围为 0~15。第三个 cells中断触发类型为 1 的时候表示上升沿触发为 2 的时候 表示下降沿触发为 4 的时候表示高电平触发为 8 的时候表示低电平触发。3、interrupt-controller 节点为空表示当前节点是中断控制器对于 gpio 来说gpio 节点也可以作为中断控制器比如 imx6ull.dtsi 文件中的 gpio5 节点内 容如下所示gpio5: gpio020ac000 { compatible fsl,imx6ul-gpio, fsl,imx35-gpio; reg 0x020ac000 0x4000; interrupts GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH, GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH; gpio-controller; #gpio-cells 2; interrupt-controller; #interrupt-cells 2; };1、interrupts 描述中断源信息对于 gpio5 来说一共有两条信息中断类型都是SPI 触发电平都是 IRQ_TYPE_LEVEL_HIGH。不同之处在于中断源一个是 74一个是 752、interrupt-controller表明了 gpio5 节点也是个中断控制器用于控制 gpio5 所有 IO 的中断3、将#interrupt-cells 修改为 23.2 设备树中断函数3.2.1 获取中断号参数dev设备节点。index索引号interrupts 属性可能包含多条中断信息通过 index 指定要获取的信息。返回值中断号。如果使用 GPIO 的话可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号如下参数gpio要获取的 GPIO 编号。返回值GPIO 对应的中断号四. 按键中断示例代码4.1 修改设备树putekey { #address-cells 1; #size-cells 1; compatible pt,pute-key; status okay; pinctrl-names default; pinctrl-0 pinctrl_ptkey; gpio-key gpio1 18 GPIO_ACTIVE_LOW; interrupt-parent gpio1; interrupts 18 IRQ_TYPE_EDGE_FALLING; };4.2 驱动代码中断处理函数//中断处理函数 static irqreturn_t key_irq_handler(int irq_num, void * arg) { if(led_stauts 0) { led_stauts 1; } else { led_stauts 0; } gpio_set_value(led_gpio, led_stauts); return IRQ_HANDLED; }//杂项设备结构体 static struct miscdevice misc_dev { .minor MISC_DYNAMIC_MINOR, //这个宏为255但是次设备号不一定是255;自动获取次设备号 .name DEV_NAME, //设备节点名 和 /sys/class/misc/xxx 两者名称相同 .fops fops //杂项设备文件操作函数集合 }; static int __init mydrv_init(void) { int ret 0; /注册杂项设备 ret misc_register(misc_dev); if(ret) { pr_err(misc_register error, errno:%d\n, ret); return ret; } pnode_key of_find_node_by_path(/putekey); if(!pnode_key) { pr_err(of_find_node_by_path error\n); ret -ENODEV; goto del_misc; } //参数三是0在设备树中代表数组0就代表下标是0对应的GPIO //返回值成功正数 失败负数错误号 key_gpio of_get_named_gpio(pnode_key, gpio-key, 0); if(key_gpio 0) { ret key_gpio; pr_err(of_get_named_gpio error, errno:%d\n, ret); goto del_misc; } //参数二标签随便赋值 ret gpio_request(key_gpio, gpio_for_key); if(ret) { pr_err(gpio_request error, errno:%d\n, ret); goto del_misc; } ret gpio_direction_input(key_gpio); if(ret) { pr_err(gpio_direction_input error, errno:%d\n, ret); goto del_gpio; } /获取中断号 /key_irq_num of_irq_get(pnode_key, 0); key_irq_num gpio_to_irq(key_gpio); if(key_irq_num 0) { ret key_irq_num; pr_err(of_irq_get error, errno:%d\n, ret); goto del_gpio; } pr_info(key_irq_num is %d\n, key_irq_num); //申请中断 ret request_irq(key_irq_num, key_irq_handler, IRQF_TRIGGER_FALLING, key_interrupt, NULL); if(ret 0) { pr_err(request_irq error, errno:%d\n, ret); goto del_gpio; } //要实现按键控制 LED 就得初始化LED以下是LED部分 pnode_led of_find_node_by_path(/puteled); if(!pnode_led) { ret -ENODEV; pr_err(of_find_node_by_path error, errno:%d\n, ret); goto del_gpio; } led_gpio of_get_named_gpio(pnode_led, gpio-led, 0); if(led_gpio 0) { ret led_gpio; pr_err(of_get_named_gpio error, errno:%d\n, ret); goto del_gpio; } ret gpio_request(led_gpio, led-gpio); if(ret) { pr_err(gpio_request error, errno:%d\n, ret); goto del_gpio; } ret gpio_direction_output(led_gpio, 1); if(ret) { pr_err(gpio_direction_output error, errno:%d\n, ret); goto del_led_gpio; } pr_info(key_irq drv init finish!\n); return 0; del_led_gpio: gpio_free(led_gpio); del_gpio: gpio_free(key_gpio); del_misc: misc_deregister(misc_dev); return ret; } static void __exit mydrv_exit(void) { gpio_free(led_gpio); free_irq(key_irq_num, NULL); gpio_free(key_gpio); //卸载杂项设备 misc_deregister(misc_dev); pr_info(hello, bbb\n); return; } module_init(mydrv_init); module_exit(mydrv_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(this drv is key test\n); MODULE_AUTHOR(dl);4.3 应用层代码int main(int argc, char **argv) { int fd 0; fd open(/dev/mykey0, O_RDWR); if (fd 0) { perror(open); return -1; } char buffer[10] {0}; int sw 0; int key_status 0; while (1) { read(fd, key_status, sizeof(key_status)); if(key_status 1) { sw 1; printf(key status is on\n); } else if(key_status 0) { sw 0; printf(key status is off\n); } } close(fd); return 0; }4.4 结果可以看到应用层一直在轮询按键的状态这样一直占用 CPU 资源很不好故可将其修改为阻塞 IO五. 阻塞IO 与非阻塞 IO5.1 区别阻塞IO:非阻塞IO5.2 等待队列阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态这样可以将 CPU 资源让出来。但是当设备文件可以操作的时候就必须唤醒进程一般在中断函数里面完成唤醒工作。Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作如果要在驱动中使用等待队列必须创建并初始化一个等待队列头等待队列头使用结构体wait_queue _head_t表示5.3 相关函数5.3.1 init_waitqueue_head作用初始化等待队列头参数参数 q 就是要初始化的等待队列头返回值无5.3.2 等待唤醒当设备可以使用的时候就要唤醒进入休眠态的进程参数 q 就是要唤醒的等待队列头这两个函数会将这个等待队列头中的所有进程都唤醒。 wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进 程而 wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程5.3.3 等待事件5.4 修改示例代码读函数ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *off) { unsigned long ret 0; wait_condition 0; wait_event_interruptible(wq, wait_condition); ret gpio_get_value(key_gpio); if( ret 0 ) { key_status KEY_ON; } else { key_status KEY_OFF; } ret copy_to_user(userdata, key_status, sizeof(key_status)); if(ret) { pr_err(copy_to_user error\n); return ret; } pr_info(read caller\n); return sizeof(key_status); }中断处理函数//中断处理函数 static irqreturn_t key_irq_handler(int irq_num, void * arg) { wait_condition 1; //不是自动唤醒必须加上 wake up wake_up(wq); if(led_stauts 0) { led_stauts 1; } else { led_stauts 0; } gpio_set_value(led_gpio, led_stauts); return IRQ_HANDLED; }在 drv_init 中新增//等待队列头 static wait_queue_head_t wq; //等待条件 static int wait_condition 0; //初始化等待队列头 init_waitqueue_head(wq);结果

相关新闻

云原生 AI 调度开发短记:本地环境如何复现

云原生 AI 调度开发短记:本地环境如何复现

云原生 AI 调度开发短记:本地环境如何复现 对于调度器,任务提交、队列选择和执行状态回写比抽象架构更值得先检查。本文把“本地开发环境与可复现实验脚手架”限定为可由配置、代码和测试记录交叉验证的事项。 云原生 AI 平台搭建与智能调度系统设计&…

2026/8/11 5:59:00 阅读更多 →
AE 3D图层零基础入门:一键打造立体PV画面的核心技法

AE 3D图层零基础入门:一键打造立体PV画面的核心技法

1. 背景与核心概念:为什么需要3D化PV画面?在视频制作和后期特效领域,After Effects(简称AE)是当之无愧的行业标准工具之一。无论是影视包装、广告片头,还是如今流行的短视频和PV(Promotion Vide…

2026/8/11 5:59:00 阅读更多 →
OpenClaw与Nextcloud Talk整合:智能企业通讯解决方案

OpenClaw与Nextcloud Talk整合:智能企业通讯解决方案

1. 项目背景与核心价值OpenClaw人人养虾这个项目名称乍看有些趣味性,实际上揭示了两个关键技术方向:OpenClaw开源框架与Nextcloud Talk的深度整合。作为一款新兴的AI智能体开发平台,OpenClaw正在改变传统企业通讯工具的交互方式。我最近在部署…

2026/8/11 5:59:00 阅读更多 →

最新新闻

电力系统调度中的自适应鲁棒优化方法研究

电力系统调度中的自适应鲁棒优化方法研究

1. 项目背景与核心挑战在电力系统调度领域,单元承诺问题(Unit Commitment, UC)一直是运营优化的核心难题。传统UC问题需要在满足负荷需求的前提下,确定发电机组的启停状态和出力水平,以最小化运行成本。然而&#xff0…

2026/8/11 6:52:18 阅读更多 →
Unity虚拟键盘插件全解析:从架构设计到实战集成与性能优化

Unity虚拟键盘插件全解析:从架构设计到实战集成与性能优化

1. 项目概述:为什么我们需要一个专门的虚拟键盘插件? 在Unity项目里,尤其是面向移动端、WebGL或者需要触屏交互的桌面应用时,输入文本是个绕不开的坎。Unity自带的 TouchScreenKeyboard 类,用过的开发者都知道&#…

2026/8/11 6:52:18 阅读更多 →
管道检测设备的 WiFi 无线传输方案

管道检测设备的 WiFi 无线传输方案

管道检测设备有一个长期被忽视的体验瓶颈。 屏幕。 不是屏幕分辨率不够。是整个检测系统被一根视频线绑在屏幕旁边。操作人员在井口推线缆,屏幕得放在旁边地上,太阳反光看不清要拿手挡,下雨天得找东西盖。客户或者监理想看画面,得…

2026/8/11 6:52:18 阅读更多 →
Unity资源逆向实战:UABEA工具深度解析与资源提取指南

Unity资源逆向实战:UABEA工具深度解析与资源提取指南

1. 项目概述:为什么我们需要UABEA?在Unity游戏开发或者逆向分析、Mod制作的过程中,我们常常会遇到一个核心需求:如何从游戏或应用的最终发布包(如.apk、.ipa、.exe或.assetbundle文件)里,把那些…

2026/8/11 6:52:18 阅读更多 →
Unity动态天空盒:AIGC生成全景图序列实现低成本环境氛围

Unity动态天空盒:AIGC生成全景图序列实现低成本环境氛围

1. 项目概述:当AIGC的想象力遇见Unity3D的实时渲染 最近在捣鼓一个开放世界风格的小项目,场景搭建到一半,卡在了天空盒上。传统的静态天空盒贴图看久了总觉得死板,云不会动,光线没变化,氛围感差了一大截。而…

2026/8/11 6:52:18 阅读更多 →
低成本网络存储!iSCSI 从 0 搭建 + 多路径高可用实操

低成本网络存储!iSCSI 从 0 搭建 + 多路径高可用实操

CentOS7 iSCSI IP-SAN 完整实战博客 前言 传统服务器本地硬盘容量有限、无法多机共享,光纤FC SAN存储成本高昂。iSCSI基于TCP/IP以太网实现块级远程存储共享,俗称IP-SAN,普通千兆/万兆网线即可搭建共享存储,中小企业、机房实训首选…

2026/8/11 6:51:18 阅读更多 →

日新闻

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南 【免费下载链接】video2x A machine learning-based video super resolution and frame interpolation framework. Est. Hack the Valley II, 2018. 项目地址: https://gitcode.com/GitHub_Trending/vi/v…

2026/8/11 0:00:02 阅读更多 →
前后端分离项目中控制台与接口工具数据差异排查指南

前后端分离项目中控制台与接口工具数据差异排查指南

1. 问题现象解析:控制台与Apifox的数据差异 最近在调试一个前后端分离项目时,遇到了一个典型问题:后端服务在本地开发环境控制台能正常输出查询数据,但通过Apifox测试时却返回空结果。这种"控制台有数据,接口工具…

2026/8/11 0:00:03 阅读更多 →
AI编程实战:从Claude Code踩坑到游戏开发入门

AI编程实战:从Claude Code踩坑到游戏开发入门

1. 从“AI能帮我做游戏”到“AI让我重新学编程”最近身边不少朋友,尤其是一些非技术背景、但对游戏开发有浓厚兴趣的朋友,都在问我同一个问题:“听说现在用Claude Code这种AI编程工具,小白也能做游戏了,是真的吗&#…

2026/8/11 0:00:03 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/11 1:08:05 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/11 1:08:05 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/11 1:08:05 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/10 17:07:33 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/11 1:08:06 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

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