rk3588 驅動開發(一)字符設備開發

3.字符設備驅動開發

3.1 什么是字符設備驅動

字符設備:就是一個個字節,按照字節流進行讀寫操作的設備,讀寫是按照先后順序的。
舉例子:IIC 按鍵 LED SPI LCD 等
Linux 應用程序調用驅動程序流程:
Linux中驅動加載成功后會在/dev 中生成一個/dev/xxx(xxx是驅動文件名字)文件 應用程序對/dev/xxx進行open write read close 等操作
在這里插入圖片描述

在這里插入圖片描述

應用程序open函數調用c庫函數,c庫中的open函數調用系統內核態的open函數,然后調用驅動中的open函數。

3.2字符設備驅動開發

3.2.1 驅動模塊的加載和卸載

驅動運行方式有兩種:
1、將驅動編譯到內核中啟動時自動運行驅動程序
2、在 Linux 內核啟動以后使用“modprobe”或者“insmod”命令加載驅動模塊

這里有兩個函數:

module_init(xxx_init); //注冊模塊加載函數
module_exit(xxx_exit); //注冊模塊卸載函數

使用modprobe時module_init函數被調用

使用rmmod時module_exit函數被調用

入口函數:

static int __init xxx_init(void)
{/* 入口函數具體內容 */return 0;
}

出口函數:

static void __exit xxx_exit(void)
{
/*出口函數內容*/
}

insmod和modprobe的關系:

insmod 命令不能解決模塊的依賴關系,比如 drv.ko 依賴 first.ko 這個模塊,就必須先使用insmod 命令加載 first.ko 這個模塊,然后再加載 drv.ko 這個模塊。
modprobe 就不會存在這個問題,modprobe 會分析模塊的依賴關系,然后會將所有的依賴模塊都加載到內核中,因此 modprobe 命令相比 insmod 要智能一些。
modprobe 命令主要智能在提供了模塊的依賴性分
析、錯誤檢查、錯誤報告等功能,推薦使用 modprobe 命令來加載驅動。modprobe 命令默認會去/lib/modules/<kernel-version>目錄中查找模塊,kernel-version為內核版本號。

驅動模塊卸載
驅動模塊的卸載使用命令“rmmod”即可,比如要卸載 drv.ko,使用如下命令即可:
rmmod drv.ko
也可以使用“modprobe -r”命令卸載驅動,比如要卸載 drv.ko,命令如下:
modprobe -r drv
使用 modprobe 命令可以卸載掉驅動模塊所依賴的其他模塊,前提是這些依賴模塊已經沒有被其他模塊所使用,否則就不能使用 modprobe 來卸載驅動模塊。所以對于模塊的卸載,還是推薦使用 rmmod 命令。

3.2.2 字符設備注冊與注銷

當驅動模塊加載成功后,需要注冊字符設備。當驅動模塊卸載成時,需要將字符設備注銷。
注冊和注銷設備驅動函數原型如下:

static inline int register_chrdev(unsigned int major, 
const char *name,
const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, 
const char *name)

register_chrdev 函數用于注冊字符設備,此函數一共有三個參數,這三個參數的含義如下:
major:主設備號,Linux 下每個設備都有一個設備號,設備號分為主設備號和次設備號兩部分,
name:設備名字,指向一串字符串。
fops:結構體 file_operations 類型指針,指向設備的操作函數集合變量。
unregister_chrdev 函數用戶注銷字符設備,此函數有兩個參數,這兩個參數含義如下:
major:要注銷的設備對應的主設備號。
name:要注銷的設備對應的設備名。

查看已使用的設備號:cat /proc/devices

在這里插入圖片描述

3.2.3 實現設備的具體操作函數

file_operation結構體就是具體的操作函數,如open read write release

29 static struct file_operations test_fops = {
30 .owner = THIS_MODULE, 
31 .open = chrtest_open,
32 .read = chrtest_read,
33 .write = chrtest_write,
34 .release = chrtest_release,
35 };

指定初始化器(designated initializer),.成員名 = 值 的語法格式在 C99 標準中被引入。

3.2.4 添加LICENSE和作者信息

LICENSE 是必須添加的,
否則的話編譯的時候會報錯,作者信息可以添加也可以不添加。LICENSE 和作者信息的添加
使用如下兩個函數:
MODULE_LICENSE() //添加模塊 LICENSE 信息
MODULE_AUTHOR() //添加模塊作者信息
GPL(GNU General Public License,GNU通用公共許可證)是一種開源軟件許可協議,由自由軟件基金會(FSF)制定,旨在保護軟件自由的使用、修改和再發布權利。
GPL 是“你用了我的代碼,你就得開源你的代碼” 的開源協議。

3.3設備號

3.3.1什么是設備號

設備號(Device Number)是 Linux 內核中用于標識一個設備的數字標識,它由兩個部分組成:

🧩 1. 主設備號(Major Number)
表示這個設備屬于哪一個驅動程序。
也就是說,哪個內核驅動模塊來處理這個設備的請求。

🧩 2. 次設備號(Minor Number)
表示由同一個驅動程序管理的多個設備中的哪一個。
相當于:驅動負責整條生產線,次設備號表示哪個產品。
Linux 提供了一個名為 dev_t 的數據類型表示設備號,dev_t 定義在文件 include/linux/types.h 里面,定義
如下:

13 typedef u32 __kernel_dev_t;
......
16 typedef __kernel_dev_t dev_t;

可以看出 dev_t 是 u32 類型的,也就是 unsigned int,所以 dev_t 其實就是 unsigned int 類型,是一個 32 位的數據類型。這 32 位的數據構成了主設備號和次設備號兩部分,其中高 12位為主設備號,低 20 位為次設備號。因此 Linux 系統中主設備號范圍為 0~4095,所以大家在選擇主設備號的時候一定不要超過這個范圍。

在文件 include/linux/kdev_t.h 中提供了幾個關于設備號的操作函數(本質是宏),如下所示:

7 #define MINORBITS 20
8 #define MINORMASK ((1U << MINORBITS) - 1)
9 
10 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
11 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
12 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

第 7 行,宏 MINORBITS 表示次設備號位數,一共是 20 位。
第 8 行,宏 MINORMASK 表示次設備號掩碼。
第 10 行,宏 MAJOR 用于從 dev_t 中獲取主設備號,將 dev_t 右移 20 位即可。
第 11 行,宏 MINOR 用于從 dev_t 中獲取次設備號,取 dev_t 的低 20 位的值即可。
第 12 行,宏 MKDEV 用于將給定的主設備號和次設備號的值組合成 dev_t 類型的設備
號。

3.3.2 設備號的分配

設備號分配為:靜態分配和動態分配

1、靜態分配
cat /proc/devices 查看當前設備中使用的設備號如果沒有被使用的設備號就能注冊
2、動態分配
在注冊字符設備之前先申請一個設備號,系統會自動給你一個沒有被使用的設備號,這樣就避免了沖突。卸載驅動的時候釋放掉這個設備號即可,設備號的申請函數如下

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

函數 alloc_chrdev_region 用于申請設備號,此函數有 4 個參數:
dev:保存申請到的設備號。
baseminor:次設備號起始地址,alloc_chrdev_region 可以申請一段連續的多個設備號,這
些設備號的主設備號一樣,但是次設備號不同,次設備號以 baseminor 為起始地址地址開始遞
增。一般 baseminor 為 0,也就是說次設備號從 0 開始。
count:要申請的設備號數量。
name:設備名字。

注銷字符設備之后要釋放掉設備號,設備號釋放函數如下:

void unregister_chrdev_region(dev_t from, unsigned count)

此函數有兩個參數:
from:要釋放的設備號。
count:表示從 from 開始,要釋放的設備號數量。

3.4 chrdevbase 字符設備驅動開發實驗

我們就以 chrdevbase 這個虛擬設備為
例,完整的編寫一個字符設備驅動模塊。chrdevbase 不是實際存在的一個設備,是為了方便講解字符設備的開發而引入的一個虛擬設備。chrdevbase 設備有兩個緩沖區,一個讀緩沖區,一個寫緩沖區,這兩個緩沖區的大小都為 100 字節。在應用程序中可以向 chrdevbase 設備的寫緩沖區中寫入數據,從讀緩沖區中讀取數據。
驅動文件

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>#define CHRDEVBASE_MAJOR	200				/* 主設備號 */
#define CHRDEVBASE_NAME		"chrdevbase" 	/* 設備名     */static char readbuf[100];		/* 讀緩沖區 */
static char writebuf[100];		/* 寫緩沖區 */
static char kerneldata[] = {"kernel data!"};/** @description		: 打開設備* @param - inode 	: 傳遞給驅動的inode* @param - filp 	: 設備文件,file結構體有個叫做private_data的成員變量* 					  一般在open的時候將private_data指向設備結構體。* @return 			: 0 成功;其他 失敗*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{//printk("chrdevbase open!\r\n");return 0;
}/** @description		: 從設備讀取數據 * @param - filp 	: 要打開的設備文件(文件描述符)* @param - buf 	: 返回給用戶空間的數據緩沖區* @param - cnt 	: 要讀取的數據長度* @param - offt 	: 相對于文件首地址的偏移* @return 			: 讀取的字節數,如果為負值,表示讀取失敗*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 向用戶空間發送數據 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}//printk("chrdevbase read!\r\n");return 0;
}/** @description		: 向設備寫數據 * @param - filp 	: 設備文件,表示打開的文件描述符* @param - buf 	: 要寫給設備寫入的數據* @param - cnt 	: 要寫入的數據長度* @param - offt 	: 相對于文件首地址的偏移* @return 			: 寫入的字節數,如果為負值,表示寫入失敗*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 接收用戶空間傳遞給內核的數據并且打印出來 */retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("kernel recevdata:%s\r\n", writebuf);}else{printk("kernel recevdata failed!\r\n");}//printk("chrdevbase write!\r\n");return 0;
}/** @description		: 關閉/釋放設備* @param - filp 	: 要關閉的設備文件(文件描述符)* @return 			: 0 成功;其他 失敗*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{//printk("chrdevbase release!\r\n");return 0;
}/** 設備操作函數結構體*/
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,	.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};/** @description	: 驅動入口函數 * @param 		: 無* @return 		: 0 成功;其他 失敗*/
static int __init chrdevbase_init(void)
{int retvalue = 0;/* 注冊字符設備驅動 */retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(retvalue < 0){printk("chrdevbase driver register failed\r\n");}printk("chrdevbase init!\r\n");return 0;
}/** @description	: 驅動出口函數* @param 		: 無* @return 		: 無*/
static void __exit chrdevbase_exit(void)
{/* 注銷字符設備驅動 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase exit!\r\n");
}/* * 將上面兩個函數指定為驅動的入口和出口函數 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);/* * LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("123");
MODULE_INFO(intree, "Y");

驅動程序書寫流程:
1、寫入口函數 出口函數

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

2、實現入口函數出口函數
入口函數中注冊字符設備
出口函數注銷字符設備

static int __init chrdevbase_init(void)
{int retvalue = 0;/* 注冊字符設備驅動 */retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(retvalue < 0){printk("chrdevbase driver register failed\r\n");}printk("chrdevbase init!\r\n");return 0;
}/** @description	: 驅動出口函數* @param 		: 無* @return 		: 無*/
static void __exit chrdevbase_exit(void)
{/* 注銷字符設備驅動 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase exit!\r\n");
}

3、實現file_operations 結構體 和open read write release函數

/** @description		: 打開設備* @param - inode 	: 傳遞給驅動的inode* @param - filp 	: 設備文件,file結構體有個叫做private_data的成員變量* 					  一般在open的時候將private_data指向設備結構體。* @return 			: 0 成功;其他 失敗*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{//printk("chrdevbase open!\r\n");return 0;
}/** @description		: 從設備讀取數據 * @param - filp 	: 要打開的設備文件(文件描述符)* @param - buf 	: 返回給用戶空間的數據緩沖區* @param - cnt 	: 要讀取的數據長度* @param - offt 	: 相對于文件首地址的偏移* @return 			: 讀取的字節數,如果為負值,表示讀取失敗*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 向用戶空間發送數據 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}//printk("chrdevbase read!\r\n");return 0;
}/** @description		: 向設備寫數據 * @param - filp 	: 設備文件,表示打開的文件描述符* @param - buf 	: 要寫給設備寫入的數據* @param - cnt 	: 要寫入的數據長度* @param - offt 	: 相對于文件首地址的偏移* @return 			: 寫入的字節數,如果為負值,表示寫入失敗*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 接收用戶空間傳遞給內核的數據并且打印出來 */retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("kernel recevdata:%s\r\n", writebuf);}else{printk("kernel recevdata failed!\r\n");}//printk("chrdevbase write!\r\n");return 0;
}/** @description		: 關閉/釋放設備* @param - filp 	: 要關閉的設備文件(文件描述符)* @return 			: 0 成功;其他 失敗*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{//printk("chrdevbase release!\r\n");return 0;
}/** 設備操作函數結構體*/
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,	.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};

APP文件

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"static char usrdata[] = {"usr data!"};/** @description		: main主程序* @param - argc 	: argv數組元素個數* @param - argv 	: 具體參數* @return 			: 0 成功;其他 失敗*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;char readbuf[100], writebuf[100];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打開驅動文件 */fd  = open(filename, O_RDWR);if(fd < 0){printf("Can't open file %s\r\n", filename);return -1;}if(atoi(argv[2]) == 1){ /* 從驅動文件讀取數據 */retvalue = read(fd, readbuf, 50);if(retvalue < 0){printf("read file %s failed!\r\n", filename);}else{/*  讀取成功,打印出讀取成功的數據 */printf("read data:%s\r\n",readbuf);}}if(atoi(argv[2]) == 2){/* 向設備驅動寫數據 */memcpy(writebuf, usrdata, sizeof(usrdata));retvalue = write(fd, writebuf, 50);if(retvalue < 0){printf("write file %s failed!\r\n", filename);}}/* 關閉設備 */retvalue = close(fd);if(retvalue < 0){printf("Can't close file %s\r\n", filename);return -1;}return 0;
}

Makefile

KERNELDIR := /home/lk/rk3588_linux_sdk/kernel
CURRENT_PATH := $(shell pwd)
# obj-m 是內核模塊編譯規則中的一個特殊變量。
# obj-m 定義了要生成的模塊目標文件(即 .ko 文件)。
# obj-m 表示編譯時將 chrdevbase.o 作為模塊(module)對象,最終會生成 chrdevbase.ko。
# chrdevbase.o# chrdevbase.o 是將 chrdevbase.c 文件編譯為目標文件(.o 文件)的名稱。
# 生成的目標文件會自動鏈接成內核模塊 chrdevbase.ko。
obj-m := chrdevbase.o
# make 會首先檢查 kernel_modules 目標。
# 如果 kernel_modules 目標沒有生成或需要更新,make 會執行 kernel_modules 的命令。
# 執行完 kernel_modules 后,build 目標就算完成了。
build : kernel_modules# kernel_modules# 定義一個名為 kernel_modules 的目標。
# 當執行 make kernel_modules 時,會觸發后面的命令。# $(MAKE)# $(MAKE) 是一個特殊的變量,表示 make 命令本身。
# 使用 $(MAKE) 而不是直接調用 make 可以在嵌套調用時保持參數一致性。
# -C $(KERNELDIR)# -C 選項表示切換到 $(KERNELDIR) 目錄下執行命令。
# $(KERNELDIR) 是一個變量,通常指定為 Linux 內核源碼的構建目錄。
# 在內核源碼目錄中調用 make 會使用內核的構建系統。
# M=$(CURRENT_PATH)# M= 選項告訴內核構建系統,當前模塊的源代碼位于 $(CURRENT_PATH) 目錄下。
# modules# modules 是內核構建系統的一個目標,表示要構建模塊(.ko 文件)。
# 當傳入 modules 目標時,內核會根據 obj-m 定義的模塊進行編譯。
# 總結  使用make buil 就會檢查kernel_modules是否存在或者更新 ,kernel_modules會執行$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
#也就是 make 內核路徑 當前文件路徑 生成modules即obj-m 對應的 chrdevbase.o生成chrdevbase.ko文件
kernel_modules :$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

Makefile 用法 根據配置
KERNELDIR := /home/lk/rk3588_linux_sdk/kernel
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o
運行build 然后運行kernel_modules 后的
其中:= 是簡單賦值 :是目標依賴
M= 是 Make 運行參數,不是 Makefile 自身定義變量的語法
-C:切換目錄到內核源碼目錄
modules:告訴它“我要構建模塊”

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

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

相關文章

設計模式 --- 外觀模式

外觀模式是一種結構型設計模式&#xff0c;為復雜子系統提供??統一的高層接口??&#xff0c;通過定義一個外觀類來??簡化客戶端與子系統的交互??&#xff0c;降低系統耦合度。這種模式隱藏了子系統的復雜性&#xff0c;將客戶端與子系統的實現細節隔離開來&#xff0c;…

我的gittee倉庫

日常代碼: 日常代碼提交https://gitee.com/xinxin-pingping/daily-code 有需要的寶子們可自行讀取。

微服務調用中的“大對象陷阱”:CPU飆高問題解析與優化

背景 對幾十萬條用戶歷史存量數據寫入&#xff0c;且存在大對象的基礎上。kafka消費進行消費寫mysql超時。導致上游服務調用時異常&#xff0c;CPU飆高異常。 大對象解釋 大對象的定義與危害 1. 什么是大對象&#xff1f; JVM 內存分配機制&#xff1a;Java 中對象優先分配…

代碼隨想錄算法訓練營day6(字符串)

華子目錄 反轉字符串思路 反轉字符串II思路 替換數字思路 反轉字符串 https://leetcode.cn/problems/reverse-string/ 思路 使用雙指針&#xff0c;初始化時&#xff0c;left指向下標0的位置&#xff0c;right指向最后一個元素的下標當while left<right時&#xff0c;交換…

Oracle 19c新特性:OCP認證考試與職業躍遷的關鍵?

在數字化轉型的浪潮中&#xff0c;Oracle 19c作為數據庫領域的旗艦版本&#xff0c;不僅承載著技術革新的使命&#xff0c;更成為IT從業者職業進階的“黃金跳板”。無論是企業級應用的高可用性需求&#xff0c;還是云原生架構的快速迭代&#xff0c;Oracle 19c的智能化與多模型…

【MySQL數據庫入門到精通】

文章目錄 一、SQL分類二、DDL-數據庫操作1.查詢2.創建數據庫3.刪除數據庫4.使用數據庫 三、DDL-表操作1.查詢 一、SQL分類 根據功能主要分為DDL DML DQL DCL DDL:Date Definition Language數據定義語言&#xff1a;定義數據庫&#xff0c;表和字段 DML:Date Manipulatin Lan…

MCP服務端開發

MCP(Memory, Context, Planning)是一種增強AI系統認知能力的框架,通過整合記憶管理、上下文理解和規劃能力,可以顯著提升AI系統的表現。下面我將為您開發一個完整的MCP服務端。 概述 我們將使用Python開發一個基于FastAPI的MCP服務端,包含以下核心組件: Memory Manager…

前端:uniapp中uni.pageScrollTo方法與元素的overflow-y:auto之間的關聯

在uniapp中&#xff0c;uni.pageScrollTo方法與元素的overflow-y:auto屬性之間存在以下關聯和差異&#xff1a; 一、功能定位差異 ?uni.pageScrollTo? 屬于?頁面級滾動控制?&#xff0c;作用于整個頁面容器?34。要求頁面內容高度必須超過屏幕高度&#xff0c;且由根元素下…

基礎知識-指針

1、指針的基本概念 1.1 什么是指針 1.1.1 指針的定義 指針是一種特殊的變量&#xff0c;與普通變量存儲具體數據不同&#xff0c;它存儲的是內存地址。在計算機程序運行時&#xff0c;數據都被存放在內存中&#xff0c;而指針就像是指向這些數據存放位置的 “路標”。通過指針…

VS遠程Linux_CMake項目搭建

VS遠程Linux CMake項目搭建 準備工作 遠程計算機上安裝 gcc: 一個開源的編譯器集合, GCC支持多種編程語言的編譯&#xff0c;包括C、C、Objective-C、Fortran、Ada、Go、D和Javagdb: GDB&#xff08;GNU Debugger&#xff09;是一個功能強大的調試工具&#xff0c;主要用于調…

替代升級VMware | 云軸科技ZStack構建山西證券一云多芯云平臺

通過云軸科技ZStack Cloud云平臺&#xff0c;山西證券打造了敏捷部署、簡單運維的云平臺&#xff0c;不僅兼容x86、海光、鯤鵬三種異構服務器實現一云多芯&#xff0c;還通過云平臺虛擬化納管模塊納管原有VMware虛擬化資源&#xff0c;并對接第三方集中式存儲&#xff0c;在保護…

MATLAB - 模型預測控制器(MPC)的穩定性和魯棒性問題

系列文章目錄 目錄 系列文章目錄 前言 一、被控對象模型 二、初始控制器設計 三、改進初始設計 五、查看軟約束 七、參考 前言 您可以檢查模型預測控制器設計是否存在潛在的穩定性和魯棒性問題。具體操作如下 在命令行中&#xff0c;使用審查功能。在 MPC Designer 中&a…

《GPT-4.1深度解析:AI進化新標桿,如何重塑行業未來?》

一、GPT-4.1:AI 領域的 “全能戰士” 降臨 1.1 發布背景與戰略意義 在 OpenAI 的技術迭代版圖中,GPT-4.1 被賦予了 “承前啟后” 的關鍵角色。它不僅是 GPT-4o 的全面升級版,更被視為向 GPT-5 過渡的重要橋梁。2025 年 4 月 15 日的發布會上,OpenAI 宣布 GPT-4.1 系列模型…

MySQL+Redis實戰教程:從Docker安裝部署到自動化備份與數據恢復20250418

MySQLRedis實戰教程&#xff1a;從Docker安裝部署到自動化備份與數據恢復 一、前言 在企業應用中&#xff0c;對MySQL和Redis運維的要求越來越高&#xff1a; 不能僅是啟動就算部署運行穩定、隔離、訪問控制、備份恢復、安全可靠&#xff0c;才是 企業級的基本功能 本文將手…

Linux系統編程之守護進程與調試技術

在Linux系統編程中&#xff0c;守護進程&#xff08;Daemon&#xff09;是非常重要的一種概念。它允許程序在后臺運行&#xff0c;不受用戶交互的影響&#xff0c;并且可以持續長時間地運行。通過了解如何創建和管理守護進程&#xff0c;我們能夠開發出更加穩定、高效的系統應用…

Linux中的管道

管道的概念 管道是一種進程間通信的方式。 管道是一種半雙工通信機制&#xff0c;數據只能讀或寫&#xff0c;如果要讀寫同時進行就要創建兩個管道 管道的類型 1、匿名管道PIPE&#xff1a;通常在親緣進程中使用&#xff08;兄弟、父子&#xff09; 函數參考&#xff1a;匿名管…

深度學習2.4 微積分

2.4.1 導數和微分 2.4.2 偏導數 ![在這里插入圖片描述](https://i-blog.csdnimg.cn/direct/17227e00adb14472902baba4da675aed.png 2.4.3 梯度 具體證明&#xff0c;矩陣-向量積

《軟件設計師》復習筆記(11.3)——需求獲取、分析、定義、驗證、管理

目錄 一、軟件需求概述 真題示例&#xff1a; 二、質量功能部署&#xff08;QFD&#xff09; 三、需求開發流程 需求獲取 需求分析 需求定義&#xff08;SRS&#xff09; 需求驗證 真題示例&#xff1a; 四、需求管理 真題示例&#xff1a; 一、軟件需求概述 軟件…

Spring Boot 依賴注入與Bean管理:JavaConfig如何取代XML?

大家好呀&#xff01;今天我們來聊一個超級實用的技術話題 —— Spring Boot 中的依賴注入和Bean管理&#xff0c;特別是JavaConfig是如何一步步取代XML配置的。我知道很多小伙伴一聽到"依賴注入"、"Bean管理"這些詞就頭大&#xff0c;別擔心&#xff01;我…

全志H5,NanopiKP1lus移植QT5.12記錄

移植步驟 機器環境下載QT5.12.0源碼安裝交叉編譯器修改qmake.conf文件配置編譯選項qt5的configure選項說明基本配置選項編譯器和鏈接器選項功能模塊配置第三方庫集成注意事項 配置過程報錯解決配置完成編譯過程報錯解決編譯完成將arm-qt文件夾傳送到開發板配置板子環境變量運行…