正點原子【第四期】Linux之驅動開發學習筆記-2.1LED燈驅動實驗(直接操作寄存器)

?前言:

本文是根據嗶哩嗶哩網站上“正點原子【第四期】手把手教你學Linux系列課程之 Linux驅動開發篇”視頻的學習筆記,該課程配套開發板為正點原子alpha/mini Linux開發板。在這里會記錄下正點原子 I.MX6ULL 開發板的配套視頻教程所作的實驗和學習筆記內容。本文大量引用了正點原子教學視頻和鏈接中的內容。

引用:

正點原子IMX6U倉庫 (GuangzhouXingyi) - Gitee.com

正點原子【第四期】手把手教你學 Linux之驅動開發篇_嗶哩嗶哩_bilibili

《【正點原子】I.MX6U嵌入式Linux驅動開發指南V1.5.2.pdf》

正點原子資料下載中心 — 正點原子資料下載中心 1.0.0 文檔

正點原子imx6ull-mini-Linux驅動之Linux I2C 驅動實驗(21)-CSDN博客

uboot移植(4)--在NXP官方uboot適配ALPHA開發板網絡_uboot sr8201f-CSDN博客

?

正文:

本文是 “正點原子【第四期】手把手教你學 Linux之驅動開發篇-1.1 Linux驅動開發與裸機開發的區別”。本節將參考正點原子的視頻教程和配套的正點原子開發指南文檔進行學習。

?

0. 概述

上一章我們詳細的講解了字符設備驅動開發步驟,并且用一個虛擬的 chrdevbase 設備為例帶領大家完成了第一個字符設備驅動的開發。本章我們就開始編寫第一個真正的 Linux 字符設備驅動。在 I.MX6U-ALPHA 開發板上有一個 LED 燈,我們在裸機篇中已經編寫過此 LED 燈的裸機驅動,本章我們就來學習一下如何編寫 Linux 下的 LED 燈驅動。


1 Linux 下 LED 燈驅動原理

Linux 下的任何外設驅動,最終都是要配置相應的硬件寄存器。所以本章的 LED 燈驅動最終也是對 I.MX6ULL 的 IO 口進行配置,與裸機實驗不同的是,在 Linux 下編寫驅動要符合 Linux的驅動框架。I.MX6U-ALPHA 開發板上的 LED 連接到 I.MX6ULL 的 GPIO1_IO03 這個引腳上,因此本章實驗的重點就是編寫 Linux 下 I.MX6UL 引腳控制驅動。關于 I.MX6ULL 的 GPIO 詳細講解請參考第八章。

1.1 地址映射

在編寫驅動之前,我們需要先簡單了解一下 MMU 這個神器, MMU 全稱叫做 MemoryManage Unit,也就是內存管理單元。在老版本的 Linux 中要求處理器必須有 MMU,但是現在Linux 內核已經支持無 MMU 的處理器了。 MMU 主要完成的功能如下:

①、完成虛擬空間到物理空間的映射。
②、內存保護,設置存儲器的訪問權限,設置虛擬存儲空間的緩沖特性。

我們重點來看一下第①點,也就是虛擬空間到物理空間的映射,也叫做地址映射。首先了解兩個地址概念:虛擬地址(VA,Virtual Address)、物理地址(PA, Physcical Address)。對于 32 位的處理器來說,虛擬地址范圍是 2^32=4GB,我們的開發板上有 512MB 的 DDR3,這 512MB 的內存就是物理內存,經過 MMU 可以將其映射到整個 4GB 的虛擬空間,如圖 41.1.1 所示

?

物理內存只有 512MB,虛擬內存有 4GB,那么肯定存在多個虛擬地址映射到同一個物理地址上去,虛擬地址范圍比物理地址范圍大的問題處理器自會處理,這里我們不要去深究,因為MMU 是很復雜的一個東西,后續有時間的話正點原子 Linux 團隊會專門做 MMU 專題教程。

Linux 內核啟動的時候會初始化 MMU,設置好內存映射,設置好以后 CPU 訪問的都是虛擬 地 址 。 比 如 I.MX6ULL 的 GPIO1_IO03 引 腳 的 復 用 寄 存 器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03? 的地址為 0X020E0068。如果沒有開啟 MMU 的直接向 0X020E0068 這個寄存器地址寫入數據就可以配置 GPIO1_IO03 的復用功能。現在開啟了 MMU,并且設置了內存映射,因此就不能直接向 0X020E0068 這個地址寫入數據了。我們必須得到 0X020E0068 這個物理地址在 Linux 系統里面對應的虛擬地址,這里就涉及到了物理內存和虛擬內存之間的轉換,需要用到兩個函數: ioremap 和 iounmap。

1、 ioremap 函數

ioremap 函 數 用 于 獲 取 指 定 物 理 地 址 空 間 對 應 的 虛 擬 地 址 空 間 , 定 義 在arch/arm/include/asm/io.h 文件中,定義如下:

arch/arm/include/asm/io.h

ioremap 是個宏,有兩個參數: cookie 和 size,真正起作用的是函數__arm_ioremap,此函數有三個參數和一個返回值,這些參數和返回值的含義如下:

  • phys_addr:要映射的物理起始地址。
  • size:要映射的內存空間大小。
  • mtype: ioremap 的類型,可以選擇 MT_DEVICE、 MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC, ioremap 函數選擇 MT_DEVICE。

返回值: __iomem 類型的指針,指向映射后的虛擬空間首地址。

假如我們要獲取 I.MX6ULL 的 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器對應的虛擬地址,使用如下代碼即可:

#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
static void __iomem* SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);

宏 SW_MUX_GPIO1_IO03_BASE 是寄存器物理地址, SW_MUX_GPIO1_IO03 是映射后的虛擬地址。對于 I.MX6ULL 來說一個寄存器是 4 字節(32 位)的,因此映射的內存長度為 4。映射完成以后直接對 SW_MUX_GPIO1_IO03 進行讀寫操作即可。

2、 iounmap 函數

卸載驅動的時候需要使用 iounmap 函數釋放掉 ioremap 函數所做的映射, iounmap 函數原型如下:

void iounmap (volatile void __iomem *addr)

1.2 I/O 內存訪問函數

這里說的 I/O 是輸入/輸出的意思,并不是我們學習單片機的時候講的 GPIO 引腳。這里涉及到兩個概念: I/O 端口和 I/O 內存

當外部寄存器或內存映射到 IO 空間時,稱為 I/O 端口。當外部寄存器或內存映射到內存空間時,稱為 I/O 內存

。但是對于 ARM 來說沒有 I/O 空間這個概念,因此 ARM 體系下只有 I/O 內存(可以直接理解為內存)。使用 ioremap 函數將寄存器的物理地址映射到虛擬地址以后,我們就可以直接通過指針訪問這些地址,但是 Linux 內核不建議這么做,而是推薦使用一組操作函數來對映射后的內存進行讀寫操作。

1、讀操作函數

讀操作函數有如下幾個:

readb、 readw 和 readl 這三個函數分別對應 8bit、 16bit 和 32bit 讀操作,參數 addr 就是要讀取寫內存地址,返回值就是讀取到的數據。

2、寫操作函數

寫操作函數有如下幾個:

writeb、 writew 和 writel 這三個函數分別對應 8bit、 16bit 和 32bit 寫操作,參數 value 是要寫入的數值, addr 是要寫入的地址。

2.硬件原理圖分析

本章實驗硬件原理圖參考 8.3 小節即可
?

3.實驗程序編寫

本章實驗編寫 Linux 下的 LED 燈驅動,可以通過應用程序對 I.MX6U-ALPHA 開發板上的LED 燈進行開關操作。

新建名為“2_led”文件夾,然后在 2_led 文件夾里面創建 VSCode 工程,工作區命名為“led”。工程創建好以后新建 led.c 文件,此文件就是 led 的驅動文件,在 led.c 里面輸入如下內容:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <asm/io.h>#define LED_MAJOR 200
#define LED_NAME "led"#define CCM_CCGR1_BASE                          (0x020C406C)
#define SW_MUX_CTL_PAD_GPIO1_IO03_BASE          (0x020E0068)
#define SW_PAD_CTL_PAD_GPIO1_IO03_BASE          (0x020E02F4)
#define GPIO1_DR_BASE                           (0x0209C000)
#define GPIO1_GDIR_BASE                         (0X0209C004)void __iomem * CCM_CCGR1 = NULL;
void __iomem * SW_MUX_CTL_GPIO1_IO03 = NULL;
void __iomem * SW_PAD_CTL_GPIO1_IO03 = NULL;
void __iomem * GPIO1_GDIR = NULL;
void __iomem * GPIO1_DR = NULL;static int led_open(struct inode *inode, struct file *filep) {return 0;
}static int led_release(struct inode *inode, struct file *filep) {return 0;
}static ssize_t led_read(struct file *filep, char __user *buf,size_t count, loff_t *ppos) {return 0;
}#define LED_OFF 0 
#define LED_ON 1void led_switch(uint8_t sta) {u32 val = sta;if (sta == LED_ON) {val = readl(GPIO1_DR);val &= ~(1 << 3);writel(val, GPIO1_DR);}else if (sta == LED_OFF) {val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);}
}static ssize_t led_write(struct file *filep, const char __user *buf,size_t count, loff_t *ppos)
{int retvalue;unsigned char databuf[1];retvalue = copy_from_user(databuf, buf, 1);if(retvalue < 0 ){printk("kernel write failed\n\n");return -EFAULT;}if (databuf[0] == 0) {led_switch(LED_OFF);}else if (databuf[0] == 1) {led_switch(LED_ON);}return 0;
}//
static const struct file_operations led_fopes = {.owner = THIS_MODULE,.read =  led_read,.write = led_write,.open = led_open,.release = led_release,
};// entry 
static int __init led_init(void) {int ret = 0;u32 val = 0;printk("led init\n");/**/// map gpio physical memory address to vitual addressCCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_CTL_GPIO1_IO03 = ioremap(SW_MUX_CTL_PAD_GPIO1_IO03_BASE, 4);SW_PAD_CTL_GPIO1_IO03 = ioremap(SW_PAD_CTL_PAD_GPIO1_IO03_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);//gpio init//CCGR0 clockval = readl(CCM_CCGR1);val |= (3 << 26);writel(val, CCM_CCGR1);//GPIO1_IO03 MUXwritel(0x5, SW_MUX_CTL_GPIO1_IO03);writel(0x10B0, SW_PAD_CTL_GPIO1_IO03);//GPOI1 directionval = readl(GPIO1_GDIR);val |= (1 << 3);writel(val, GPIO1_GDIR);//GPIO1 dataval = readl(GPIO1_DR);val &= ~(1 << 3);writel(val, GPIO1_DR);// register chrdeviceret = register_chrdev(LED_MAJOR, LED_NAME, &led_fopes);if(ret < 0){printk("register chardev fail\r\n");return -EIO;}return 0;
}//exit 
static void __exit led_exit(void) {u32 val;unregister_chrdev(LED_MAJOR, LED_NAME);//GPIO1 dataval = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);iounmap(CCM_CCGR1);iounmap(SW_MUX_CTL_GPIO1_IO03);iounmap(SW_PAD_CTL_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);printk("led exit\n");
}//retister .ko load and deload function
module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("dimon.chen@163.com");


編寫測試 APP

編寫測試 APP, led 驅動加載成功以后手動創建/dev/led 節點,應用 APP 通過操作/dev/led文件來完成對 LED 設備的控制。向/dev/led 文件寫 0 表示關閉 LED 燈,寫 1 表示打開 LED 燈。新建 ledApp.c 文件,在里面輸入如下內容:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>/** ./ledApp <filename> <0|1>  , 0:trun off led, 1:turn on led* ./ledApp /dev/led 0 * ./ledApp /dev/led 1*/
int main(int argc, char *argv[]) 
{int ret = 0;int fd = -1;char *filename = NULL;char read_data[100];char write_data[100];if(argc < 3) {printf("usate:  %s <dev> <opt>\r\n", argv[0]);return -1;}filename = argv[1];fd = open(filename, O_RDWR);if(fd < 0) {printf("open %s fail\r\n");return -1;}char databuf[1];databuf[0] = atoi(argv[2]);ret = write(fd, databuf, sizeof(databuf));if(ret < 0 ){printf("write fail \n");close(fd);return -1;}close(fd);return 0;
}


4 運行測試

4.1 編譯驅動程序和測試 APP

1、編譯驅動程序
編寫 Makefile 文件,本章實驗的 Makefile 文件和第四十章實驗基本一樣,只是將 obj-m 變量的值改為 led.o, Makefile 內容如下所示:


obj-m := led.oPWD=$(shell pwd)
KDERDIR=/home/dimon/I.MX6ULL/linux_altek_drivermodules:$(MAKE) -C $(KDERDIR) M=$(PWD) modulesclear:$(MAKE) -C $(KDERDIR) M=$(PWD) clear

2、編譯測試 APP

輸入如下命令編譯測試 ledApp.c 這個測試程序:

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

編譯成功以后就會生成 ledApp 這個應用程序。

4.2 運行測試

注意! 如果大家使用的正點原子出廠系統來做本實驗,那么會發現 LED 燈會一直閃爍。這是因為正點原子出廠系統默認將 LED 燈作為了心跳燈,因此系統啟動以后 LED 燈就會自動閃爍,這樣會影響大家做實驗。如果是完全按照本教程自行移植的內核和根文件系統,那么就不會遇到此問題。 如果直接使用出廠系統來做實驗,我們需要關閉 LED 燈的心跳功能,關閉方法參考《【正點原子】 I.MX6U 用戶快速體驗》第 3.1 小節,或者輸入如下命令即可:

echo none > /sys/class/leds/sys-led/trigger // 改變 LED 的觸發模式

創建設備節點

mknod /dev/led c 200 0

加載內核模塊

執行ledApp測試程序,控制LED亮滅

./ledApp /dev/led 1 //打開 LED 燈

輸入上述命令以后觀察 I.MX6U-ALPHA 開發板上的紅色 LED 燈是否點亮,如果點亮的話說明驅動工作正常。

在輸入如下命令關閉 LED 燈

./ledApp /dev/led 0 //關閉 LED 燈

輸入上述命令以后觀察 I.MX6U-ALPHA 開發板上的紅色 LED 燈是否熄滅,如果熄滅的話說明我們編寫的 LED 驅動工作完全正常!至此,我們成功編寫了第一個真正的 Linux 驅動設備程序。

如果要卸載驅動的話輸入如下命令即可:

rmmod led.ko

5. 總結

Linux中開啟了MMU則ARM 處理器訪問的所有地址都是虛擬內存地址,當在led.ko中需要對寄存器物理地址進行讀操作和寫操作時,需要先把寄存器物理內存地址映射為虛擬內存物理地址。從物理內存地址映射為虛擬物理內存地址使用的是 ioremap() 和 iounmap(),在Linux中類似這種ioremap()/iounmap()的函數必須成對的使用,有ioremap,在退出內核模塊時就需要做iounmap()。

在內核里對映射到虛擬內存地址的 void __iomem * xxx_addr 的地址做讀寫的時,需要使用Linux內核提供 void __iomem * 的讀寫函數,內核提供的讀函數有 readb(), readw(), readl() 分別是讀8bit, 16bit, 32bit,內核提供的寫函數有 writeb(), writew(), writel() 分別是寫 8bit, 16bit, 32bit。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/94322.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/94322.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/94322.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【GM3568JHF】FPGA+ARM異構開發板 測試命令

本章節的命令操作均在板卡的終端執行 1 初探/sys目錄 與/proc目錄類似&#xff0c;/sys目錄下的文件/文件夾向用戶提供了一些關于設備、內核模塊、文件系統以及其他內核組件的信息&#xff0c; 如子目錄block中存放了所有的塊設備&#xff1b;子目錄bus中存放了系統中所有的總…

【Win】Motrix+Aria2瀏覽器下載加速

系統安裝Motrix Motrix官網下載&#xff0c;推薦下載NSIS Installer 安裝版 瀏覽器安裝Aria2 下載Aria2插件&#xff0c;然后開發者模式安裝到瀏覽器 Aria2擴展選項的配置如下&#xff1a; 端口號需要改成Motrix的&#xff0c;默認是16800

SpringBoot applicationContext.getBeansOfType獲取某一接口所有實現類,應用于策略模式

本文介紹了如何在Springboot項目中通過ApplicationContext獲取接口的實現類&#xff0c;并通過枚舉策略模式避免if/else&#xff0c;展示了如何使用getBeansOfType獲取TrafficModeService的實現&#xff0c;以及如何在實際場景中應用&#xff0c;如查詢交通方式費用 1 在實際工…

大模型問題:幻覺分類+原因+各個訓練階段產生幻覺+幻覺的檢測和評估基準

1. 什么是幻覺&#xff1f;大模型出現幻覺&#xff0c;簡而言之就是“胡說八道”。 用《A Survey on Hallucination in Large Language Models》1文中的話來講&#xff0c;是指模型生成的內容與現實世界事實或用戶輸入不一致的現象。 研究人員將大模型的幻覺分為事實性幻覺&…

智慧冷庫物聯網解決方案——實現降本增效與風險可控的冷庫管理新范式

一、冷庫管理痛點設備孤島化&#xff1a;冷庫品牌、型號分散&#xff0c;缺乏統一接入標準&#xff0c;數據互通難&#xff0c;依賴人工巡檢&#xff0c;故障響應滯后。能耗黑洞&#xff1a;制冷系統能耗占冷庫總運營成本的60%以上&#xff0c;傳統管理粗放&#xff0c;缺乏動態…

太空生活的八種要素

數代以來&#xff0c;科學家們一直在銀河系中搜尋地外行星存在生命的證據。他們試圖找到一組特定的環境條件與化學物質&#xff0c;在恰當的時間、恰當的地點交匯融合。 通過研究人類、植物、動物及微生物在地球上的生存與繁衍方式&#xff0c;科學家們已識別出生命演化所需的關…

Flutter 小技巧之有趣的 UI 骨架屏框架 skeletonizer

很久沒有更新過小技巧系列&#xff0c;今天簡單介紹一個非常好用的骨架屏框架 skeletonizer &#xff0c;它主要是通過將你現有的布局自動簡化為簡單的骨架&#xff0c;并添加動畫效果來實現加載過程&#xff0c;而使用成本則是簡單的添加一個 Skeletonizer 作為 parent &…

基于SpringBoot的寵物用品系統【2026最新】

作者&#xff1a;計算機學姐 開發技術&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源碼”。 專欄推薦&#xff1a;前后端分離項目源碼、SpringBoot項目源碼、Vue項目源碼、SSM項目源碼、微信小程序源碼 精品專欄&#xff1a;…

MongoDB 分片集群修改管理員密碼

記得關注一下博主&#xff0c;博主每天都會更新IT技術&#xff0c;讓你有意想不到的小收獲哦^_^ 文章目錄*記得關注一下博主&#xff0c;博主每天都會更新IT技術&#xff0c;讓你有意想不到的小收獲哦^_^*一、注釋MongoDB分片集群認證參數&#xff08;三臺主機都要操作&#xf…

C++函數重載與引用詳解

一、函數重載&#xff1a;同名函數的 “差異化生存”?1. 概念定義?函數重載&#xff08;Function Overloading&#xff09;是 C 的重要特性&#xff0c;指在同一作用域內&#xff0c;允許存在多個同名函數&#xff0c;但要求這些函數的參數列表必須不同。&#xff08;參數個數…

2025-08-17 李沐深度學習16——目標檢測

文章目錄1 介紹1.1 實際應用1.2 邊界框1.3 數據集2 錨框2.1 什么是錨框2.2 交并比2.3 分配標簽2.4 非極大值抑制3 經典目標檢測網絡3.1 R-CNN3.1.1 R-CNN (原始版本)3.1.2 Fast R-CNN3.1.3 Faster R-CNN3.1.4 Mask R-CNN3.2 單階段檢測器&#xff1a;SSD 和 YOLO3.2.1 SSD (Sin…

Bluedroid vs NimBLE

&#x1f539; 對比&#xff1a;Bluedroid vs NimBLE 1. 協議棧體積 & 內存占用 Bluedroid&#xff1a;體積大&#xff0c;RAM 占用也大&#xff08;幾十 KB 到上百 KB&#xff09;。NimBLE&#xff1a;輕量級&#xff0c;內存占用大概是 Bluedroid 的一半甚至更少。 &…

(純新手教學)計算機視覺(opencv)實戰八——四種邊緣檢測詳解:Sobel、Scharr、Laplacian、Canny

邊緣檢測詳解&#xff1a;Sobel、Scharr、Laplacian、Canny邊緣檢測是圖像處理和計算機視覺中的重要步驟&#xff0c;主要用于發現圖像中亮度變化劇烈的區域&#xff0c;即物體的輪廓、邊界或紋理特征。OpenCV 提供了多種常用的邊緣檢測算子&#xff0c;本教程將通過四種方法帶…

PyTorch 環境配置

目錄一、安裝 CUDA二、安裝 PyTorch1. 創建虛擬環境2. 安裝 PyTorch三、在 PyCharm 上創建一個 PyTorch 項目參考文章&#xff1a; 【2025年最新PyTorch環境配置保姆級教程&#xff08;附安裝包&#xff09;】 【超詳細 CUDA 安裝與卸載教程&#xff08;圖文教程&#xff09;】…

鴻蒙中冷啟動分析:Launch分析

啟動的分類&#xff08;熱身環節&#xff09; 啟動動類型觸發條件系統開銷 & 速度主要優化方向冷啟動應用進程不存在&#xff08;首次啟動或進程被殺后啟動&#xff09;最高&#xff0c;需創建進程、加載資源、初始化所有組件主要優化目標&#xff0c;減少主線程任務&…

告別盲目排查,PolarDB+DAS Agent智能運維新突破

1.概述 周五下午6點正準備下班&#xff0c;數據庫CPU突然爆滿&#xff0c;業務告警響成一片&#xff0c;DBA卻要手動翻查CPU/內存/負載等多個監控指標&#xff0c;還要查詢是否有新增慢SQL&#xff0c;死鎖等問題&#xff1f;” 這可能是數據庫DBA最鬧心的場景了&#xff0c;…

Linux------《零基礎到聯網:CentOS 7 在 VMware Workstation 中的全流程安裝與 NAT 網絡配置實戰》

&#xff08;一&#xff09;Linux的發行版Centos安裝與配置 下載Linux發行版本Centos:centos-7-isos-x86_64安裝包下載_開源鏡像站-阿里云點擊CentOS-7-x86_64-DVD-2009.torrent &#xff0c;CentOS-7-x86_64-DVD-2009.torrent是官方提供的 BT 種子文件&#xff08;176.1 KB&a…

iOS App 混淆工具實戰,教育培訓類 App 的安全保護方案

隨著在線教育、企業培訓、知識付費平臺的興起&#xff0c;越來越多的 iOS 應用需要保護自己的課程資源和核心邏輯。然而&#xff0c;教育類 App 面臨的最大風險并非傳統的外掛或刷分&#xff0c;而是 視頻盜鏈、題庫數據泄露、源碼邏輯被二次利用。 在這種場景下&#xff0c;合…

RabbitMQ:SpringAMQP Topic Exchange(主題交換機)

目錄一、案例需求二、基礎配置三、代碼實現TopicExchange與DirectExchange類似&#xff0c;區別在于RoutingKey可以是多個單次的列表&#xff0c;并且以.分割。 Queue與Exchange指定BindingKey時可以使用通配符&#xff1a; #&#xff1a;代指0個或多個單詞。*&#xff1a;代…

(純新手教學)計算機視覺(opencv)實戰六——圖像形態學(腐蝕、膨脹、開運算、閉運算、梯度、頂帽、黑帽)

圖像形態學在圖像處理中&#xff0c;形態學&#xff08;Morphology&#xff09; 是一種基于圖像中物體形狀的處理方法&#xff0c;通常用于二值圖像和灰度圖像。它通過腐蝕、膨脹等基本操作&#xff0c;結合開運算、閉運算、梯度運算、頂帽、黑帽等派生操作&#xff0c;來實現去…