Linux《線程(上)》

通過之前的學習我們已經了解了操作系統當中的基本的概念包括進程、基礎IO、磁盤文件存儲等,但是到目前為止我們還未了解到線程相關的概念,這就使得當前我們對操作系統的認知還不是完整的,現在我們是還是無法理解一個進程當中是如何同時的執行多個任務的。接下來在本篇當中我們就將來了解到線程的相關概念,包括線程在我們的理解上應該是什么樣的,還有線程在操作系統當中具體是如何實現的,以及具體的在我們使用的Linux當中是如何使用的,還會將線程對比我們之前學習的進程,最后再來了解一些線程控制相關的接口,相信通過本篇的學習能讓你對線程有基本的認知,接下來就開始本篇的學習吧!!!



1.線程概念

1.1 什么是線程

實際上從教材當中解釋的線程和之前我們學習過的進程的概念如下所示:

  • 進程:操作系統分配資源(內存、文件描述符等)的基本單位。

  • 線程:進程中的“執行單元”,它負責具體運行代碼。

但是目前的問題是我們還沒有了解過什么是執行流,那么這時我們就試著不從教程中這些晦澀的文字區理解欸,而是轉為從實際Linux當中區理解試試看。

通過之前進程的學習知道進程=內核數據結構+代碼和數據,每個進程都會有其對應的虛擬地址空間和進行映射的頁表,本質上進程的task_struct就是通過虛擬地址空間來“看到”實際上的物理內存,這時就可以將地址空間看作是一個“窗口”。

那么通過以上的理論就可以說明之前我們創建的進程都是讓一個task_struct享有所有的mm_struct,那么會不會出現一種情況就是進程當中同時擁有多個task_struct,這時不就可以讓多個task_struct共享進程當中的虛擬地址空間了嗎。

其實以上的設想就是Linux當中實現線程的基本邏輯,本質上在Linux當中是沒有真正的線程的,本質上是使用進程來模擬實現進程,我們將這種用于模擬線程的進程稱為“輕量級進程”,在一個進程當中是會可能出現多個task_struct的,本質上一個task_struct就可以代表一個線程,只不過在之前的學習當中使用到的進程都是只有一個tesk_struct的,這時就表示該進程當中是只有一個線程的。

通過以上的說明,那么你其實就可以將執行流簡單的先理解為線程,一個進程當中可以存在多個線程就是可以同時存在多個執行流,相比進程線程創建、銷毀的成本更小,從內核當中就可以理解為線程在銷毀的時候只需要銷毀對應的PCB即可,但是進程則需要將虛擬地址空間和頁表也進行銷毀。

但實際上線程是不完全等同與執行流的,實際上 線程=執行流+必要的運行環境

注:輕量級進程這種概念實際上只是在Linux當中才有的,其他的操作系統當中正常是沒有的,因為例如Windows這些操作系統實現線程的方式是在內核當中實現真正的線程。

這時候你可能就會思考,為什么在Linux當中要使用這種方法來實現線程呢?為什么不在內核當中實現真正的線程的數據結構對象呢?
這個問題其實很好理解,Linux其實也可以像Windows一樣在內核當中實現真正的線程,在Windows當中有專門的數據結構對象TCB,而在Linux當中使用PCB來模擬實現線程是因為使用PCB就意味著線程的內核代碼是可以復用PCB的代碼的,那么這時就可以讓線程基于原來的代碼實現。

通過以上的理解我們就可以得出以下的結論:
1.在Linux當中實際上線程是由輕量級進程來模擬的。
2. 在進程當中線程對于資源的劃分本質上就是對地址空間范圍的劃分。而虛擬地址空間就是資源的代表。
3.在進程當中的虛擬地址空間對于線程來說是如何進行劃分的呢?實際上劃分的操是不需要用戶來執行的,我們知道不同的函數時有不同的入口地址,那么這時候不同的線程實際上就會指向不同虛擬地址空間。

通過以上的理解我們就可以發現實際上在操作系統學科當中和實際上的操作系統是有一定的區別的,在操作系統當中只是提供了基本的思想,而在具體的操作系統當中是按照操作系統給定的基本思想實現的,但不同的實現的思想可能不同。

實際上感性上的理解進程和線程可以將進程理解為一個家庭,而線程是家庭當中的一個成員,那么進行社會資源分配的時候的基本實體就是家庭,而家庭當中的成員又可以具體的劃分是在干什么。
因此
在操作系統當中進程是進行資源分配的基本單位,而線程是系統當中進行調度的最小單位。

1.2 分頁式存儲

在之前的學習當中我們了解過在進程當中是通過頁表將虛擬地址空間二號物理內存完成兩個之間的映射關系的,有了頁表就可以將實際數據存儲的從物理內存當中的無序變成了虛擬地址空間上的有序。這樣在進程的視角當中就可以認為數據的存儲是連續的。

但是目前的問題是實際上虛擬地址和物理地址是如何進行映射的呢?

如果是按照之前的了解其實是虛擬地址空間當中的每天一個虛擬地址都和實際內存當中的一個物理地址進行映射,那么這時就會出現在下x86的系統當中,頁表當中每個物理地址和虛擬地址進行映射的一條頁表就會在內存當中占用8字節,假設虛擬地址空間的大小是4GB,這時將所有虛擬地址和物理地址進行映射頁表的最終大小就為32GB,那么這種情況下在操作系統載入之后就要占用內存當中的32GB,這時不就非常的不合理嗎?
因此實際上在操作系統當中是肯定無法使用一個單獨的頁表來進行虛擬地址和物理內存的映射。因此實際上的頁表是通過多級完成映射的。

大致的結構如下所示:

首先會有一個頁表當中存儲虛擬地址當中的前部分的地址內容,例如虛擬地址的長度為32,那么頁表當中存儲的就是虛擬地址前10位,那么這時就會有2^10=1024個表項,接下來再將虛擬地址當中的后10位的地址再作為頁表的當中的地址,那么在這種設計的情況下就可以使得每個頁表目錄當中的地址都能指向一個大小為1024的空間。
在之前文件的存儲學習當中我們就了解到了,在磁盤當中內存的加載都是按照4kb的大小為基本的單位的,實際上在內存當中進行內存的加載也是按照4kb的單位進行的,這就說明即使實際程序申請內存的大小為4097B,那么在內存當中實際申請的大小也是8kb。那么這就說明在內存當中進行數據管理也是按照4kb進行管理的,在此就將每個4kb的物理空間成為頁框。那么在操作系統當中就需要將這些頁框進行管理,,這樣操作系統就知道哪些頁框是使用的,哪些頁框是空閑的,內核當中實現了以下所示的結構體來表示每個物理頁。

/* include/linux/mm_types.h */
struct page
{/* 原?標志,有些情況下會異步更新 */unsigned long flags;union{struct{/* 換出?列表,例如由zone->lru_lock保護的active_list */struct list_head lru;/* 如果最低為為0,則指向inode* address_space,或為NULL* 如果?映射為匿名內存,最低為置位* ?且該指針指向anon_vma對象*/struct address_space *mapping;/* 在映射內的偏移量 */pgoff_t index;/** 由映射私有,不透明數據* 如果設置了PagePrivate,通常?于buffer_heads* 如果設置了PageSwapCache,則?于swp_entry_t* 如果設置了PG_buddy,則?于表?伙伴系統中的階*/unsigned long private;};struct{ /* slab, slob and slub */union{struct list_head slab_list; /* uses lru */struct{ /* Partial pages */struct page *next;
#ifdef CONFIG_64BITint pages;    /* Nr of pages left */int pobjects; /* Approximate count */
#elseshort int pages;short int pobjects;
#endif};};struct kmem_cache *slab_cache; /* not slob *//* Double-word boundary */void *freelist; /* first free object */union{void *s_mem;            /* slab: first object */unsigned long counters; /* SLUB */struct{                        /* SLUB */unsigned inuse : 16; /* ?于SLUB分配器:對象的數? */unsigned objects : 15;unsigned frozen : 1;};}};...};union{/* 內存管理?系統中映射的?表項計數,?于表??是否已經映射,還?于限制逆向映射搜索*/atomic_t _mapcount;unsigned int page_type;unsigned int active; /* SLAB */int units;           /* SLOB */};...
#if defined(WANT_PAGE_VIRTUAL)/* 內核虛擬地址(如果沒有映射則為NULL,即?端內存) */void *virtual;
#endif /* WANT_PAGE_VIRTUAL */...
}

有了以上的struct page結構體之后就可以在內核當中創建struct page的數組,這樣就內核就可以通過對該數組的管理來完成對實際物理內存的管理。那么這級相比直接對物理內存進行管理的代價小的多了。

所以總的來說Linux 內核里的 struct page 是連接“物理內存頁幀”與“內核/用戶內存管理機制”之間的橋梁

所以實際上在頁表當中每個元素當中存儲的也是物理內存當中每個頁框的地址,那么在要X86當中從虛擬地址定位到物理地址時候最后就只需要將后12位當作該頁框當作的偏移量,這樣要訪問對于的就只需要得到對于都要訪問地址的的頁框地址再加上+頁內偏移即可。

因此總結來說就可以將虛擬地址映射到物理地址上需要進行以下的兩步操作:
1.先找到對于的虛擬地址對應的頁框
2.在x86的條件下,之后再將虛擬地址當作的后12位置作為頁內偏移量,訪問具體的字節

實際上在之前了解的寫實拷貝缺頁中斷等都需要從新建立虛擬地址空間和物理內存之間的映射關系。

1.3 線程周邊

以上我們了解到了實際上在操作系統當中頁表是通過多級的方式來建立虛擬地址和物理內存之間的映射關系的,這樣實現的方式是能減少頁表在內存當中的占用空間,但是事物都是有兩面性的,多級頁表映射的方式雖然確實能減少頁表在內存當中的占用但是也就存在相比直接進行映射的時候查找的效率較低的問題,所以在操作系統當中就TBL來減緩多級頁表在查詢是效率就低的問題,實現的邏輯如下所示:

當中CUP得到對應的虛擬地址之后首先就會將該地址發給MMU,這時MMU就不會像之前一樣直接的在多級頁表當中進行查詢而是先從TLB當中查詢有沒有直接可以得到該虛擬地址映射的物理地址,這樣就能提高虛擬地址到物理地址之間轉換的效率。實際上TLB的本質就是一小塊的緩存,會將訪問頻率較高的虛擬地址和物理地址之間的映射保存下來

一句話進行總結TLB的作用就是:緩存最近常用的虛擬頁到物理頁的映射,讓 CPU 在訪問內存時大多數情況下不必再通過多級頁表查找,從而大幅提升效率

如果我們在TLB和頁表當中都無法找到對應的映射關系話,這時CPU就會給自己發送缺頁異常從而觸發缺頁中斷,那么這時CPU得到對應的中斷號接下來在操作系統當中的中斷向量表中得到中斷處理方法,而這個中斷處理方法的工作就是建立對應的虛擬地址和物理地址之間的頁表映射關系。

1.4?線程優點以及缺點

線程的優點如下所示

1.創建和切換開銷小,創建線程比創建進程輕量

線程切換不需要更換虛擬內存空間 → 避免了 TLB 刷新 和大量緩存失效。資源消耗少

同一進程內的線程共享代碼段、數據段、文件等資源。相比進程更節省系統資源。

2.并行能力強

在多處理器系統上,線程可以并行運行,提高計算效率。

3.提升 I/O 性能

I/O 等待時,其他線程還能繼續執行,避免 CPU 空閑。

多線程可以并發等待多個 I/O,提高吞吐量。

線程的缺點如下所示:

1.性能損失

多線程需要額外的調度和同步開銷。

當線程數多于處理器數時,計算密集型線程之間會爭搶 CPU,效率可能反而下降。

2.健壯性降低

線程共享內存空間,彼此缺乏隔離。

小的時間差或錯誤的共享變量使用,容易導致難以發現的 bug(如競態條件、死鎖)。

3.缺乏訪問控制

進程是操作系統的最小保護單位,而線程不是。

一個線程的錯誤操作可能影響整個進程,導致全部線程出問題。

4.編程難度高

需要處理同步、互斥、死鎖等復雜問題。

調試和維護難度遠高于單線程程序。

通過以上我們就能發現線程的優點很大,但缺點也并不是“小問題”它要求開發者必須非常謹慎地設計和實現,否則很容易出現嚴重問題。

1.5 線程 VS 進程

通過以上的學習再結合之前進程的學習我們就知道了進程是資源分配的基本單位,線程是調度的基本單位。那么除此之外線程相比進程還有哪些的區別呢?

實際上同一個進程當中的所有線程是共用同一份的虛擬地址空間的,那么這時候就可以使得在同一個進程當中的線程在進行切換的時候依然能將原來的頁表和TLB和頁表繼續的保存下來,而在進程當中因為不同的進程都有自己獨立的一份虛擬地址空間,那么這時候就會造成進程切換的時候會將對應的TLB和頁表進行更新。

但線程除了有公共的部分還有獨立的部分,就例如線程有自己獨立的寄存器,用于保存線程的上下文數據;該背后就說明線程是被獨立調度的。除此之外線程還有獨立的,那么這就說明線程是一個動態的概念。

本質上通過以下的圖示就可以解答線程和進程之間的關系:

2.?初識Linux線程控制

2.1 POSIX線程庫

在此接下來我們要進行了解的線程庫是POSIX線程庫,本質上POSIX 線程庫(Portable Operating System Interface for uniX threads,簡稱 Pthreads)是遵循 POSIX 標準的一套 多線程編程接口。主要在類 Unix 系統(Linux、macOS、BSD 等)上使用,用 C 語言接口實現。

實際上除了以上的POSIX線程庫之外還存在其他的線程庫就例如語言當中提供的線程庫,例如在C++11當中提供對應的線程庫。這些庫底層一般還是調用操作系統的線程 API(Pthreads 或 Win32 threads),但對開發者更友好。

注:在此C++11當中線程庫的使用會等到在C++當中進行學習,不過建議將Linux當中的線程學習之后再去學習C++的線程API,那么這時理解起來會很簡單。

2.1 了解線程控制接口

以上我們已經了解到上面是POSIX庫,那么接下來就來了解該庫當中提供的線程創建相關的接口。

1.線程創建

功能:創建?個新的線程
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);參數:
thread:返回線程ID
attr:設置線程的屬性,attr為NULL表?使?默認屬性
start_routine:是個函數地址,線程啟動后要執?的函數
arg:傳給線程啟動函數的參數返回值:成功返回0;失敗返回錯誤碼

通過以上的描述就可以看出該函數是需要通過用戶傳入對應的線程ID以及線程對應對的屬性,還有線程運行起來之后需要執行的方法。該函數在創建線程成功的時候就會返回0,否則就會返回對應的錯誤碼。

接下來來看一下的代碼來看看使用以上的接口進行線程創建是什么樣的:

#include<iostream>
#include<pthread.h>
#include<unistd.h>void* routine(void* args)
{while(1){const char* s=static_cast<const char*>(args);std::cout<<"我是線程:"<<s<<" 正在運行"<<std::endl;sleep(1);}}int main()
{pthread_t t1;pthread_create(&t1,nullptr,routine,(void*)"pthread-1");while(1);return 0;
}

以上代碼當中使用的對應的pthread_create來創建線程,在創建當中給該線程傳一個字符串的參數,那么在線程運行起來的時候就會執行routine方法,在該方法當中就可以通過函數的參數得到對應的字符串,只不過在獲得的過程當中需要將對應的指針進行強制類型轉換。

此時再實現以下的makefile文件之后接下來編譯以上的代碼:

Main:thread.ccg++ -o $@ $^ -lpthread
.PHONY:clean
clean:rm -f Main

注:在此在使用g++的時候需要帶上-lpthread選項,那么程序在執行的時候才能找到對應的pthread庫。

以上程序運行的結果如下所示:

以上我們是讓main函數當中的主要進程只是運行而不輸出內容,如果該線程也向顯示器當中進行打印會出現什么樣的結果呢?

將代碼main函數修改為以下的形式:

這時輸出結果就會變為以下的形式:

這時就會發現兩個線程輸出的內容混雜在了一起,這實際上是我們未進行線程同步的原因,要解決這個我問題就需要了解線程同步之后的概念。

在之前進程的學習當中我知道使用ps指令能查看當中操作系統當中的進程,那么使用什么樣的指令能查看操作系統當中的線程呢?

實際上還是使用ps指令,只不過當前帶的選項是-L,例如以的程序在運行的時候使用?ps -aL?指令就能看到以下的結果。

那么這時就可以看到兩個線程對應的PID是一樣的,但是這里又存在問題了,那就是以上當中的LWP又是什么呢?

實際上LWP就是light weight process 就是對應的輕量級進程,實際上我們也可以將其稱為線程id。

實際上和之前我們學習進程的調度類似,在線程當中也是會給不同的線程分配對應的時間片,那么這時候CPU就可以根據對應的時間片來進行線程的調度。

以上我們就了解到了pthread庫當中提供的線程創建接口pthread_create接口,那么這時候我們就要思考了為什么在此要實現一個pthread的庫,讓用戶直接調用對應的系統調用來進行線程的操作不就可以了嗎?

實際上在Linux操作系統當中確實提供了對應的線程調用接口,但是問題是在Linux當中線程操作的系統調用是和其他操作系統當中不同的,那么如果是使用系統調用來進行操作的話,那么我每在一個不同的操作系統當中就需要學習不同的系統調用,那么這對用戶來說壓力是很大的。因此為了解決該問題pthread在不同的操作系統當中就就將不同的系統調用進行封裝,實現給用戶統一的接口,那么這時就能讓用戶的進行線程的操作在不同的系統當中都是一樣的。

在Linux普通的使用用戶就不再需要LWP等輕量級進程的概念,也就不再需要了解到Linux當中是沒有真正的線程的。

實際上pthread庫的實現是在用戶層當中的,所以將其稱為用戶級線程。

實際上在Linux當中是實現了對應的系統調用來實現來實現線程的創建的,系統調用的形式如下所示:

#include <sys/types.h>
#include <unistd.h>pid_t vfork(void);

vfork和之前我們學習的 fork的區別就是超級出來的子進程是和父進程公用同一個地址空間的,那么這時不就相當于創建了一個線程。

但是以上vfork本質還是封裝了以下的clone函數

 #include <sched.h>int clone(int (*fn)(void *), void *stack, int flags, void *arg, .../* pid_t *parent_tid, void *tls, pid_t *child_tid */ );

實際上除了以上的pthread庫之外在C++11當中也提供了對應的線程庫來實現線程創建的功能。

例如以下的示例:

#include <iostream>
#include <thread>
#include<unistd.h>void hello()
{while (1){std::cout << "我是子線程,正在運行" << std::endl;sleep(1);}
}int main()
{std::thread t(hello);while (1){std::cout << "我是主線程,正在運行" << std::endl;sleep(1);}return 0;
}

那么接下來就需要思考為什么在語言當中也要實現對應的線程庫呢,其實這就要講到語言都是為了提高跨平臺的,提高語言的可移植性,那么如果一個用戶在Linux當中先進行開發之后接下來需要在Window當中進行開發,那么如果C++當中沒有提供對應的接口之后那么這時就無法進行跨平臺
。那么是如何實現的呢?實際上一般來說解決的方法就是將所有要兼容的平臺的代碼都編寫一份之后再進行條件編譯。

本質上實際上C++11當中提供的線程庫在Linux當中是封裝了pthread庫。

2. 線程等待

實際上和之前我們學習進程類似,在線程當中在進行線程的創建之后也是會申請出對應的空間的,只不過相比進程線程申請的資源比較少,但是如果不進行線程的等待還是會出現內存泄漏的問題的,在pthread庫當中提供了以下的接口。

 #include <pthread.h>int pthread_join(pthread_t thread, void **retval);參數:
thread:要等待的線程 ID(pthread_create 返回的)。
retval:用于存放目標線程的返回值指針(如果線程用 pthread_exit 返回值,這里能拿到;如果不需要返回值,可以傳 NULL)。返回值:
成功返回 0
失敗返回錯誤碼(如 ESRCH:線程不存在,EINVAL:線程不可被 join 等)。

那么在了解了以上線程等待的接口之后接下來就可以使用以上的接口對上面實現的代碼進行線程的等待。

實現的代碼如下所示:

#include<iostream>
#include<pthread.h>
#include<unistd.h>void* routine(void* args)
{while(1){const char* s=static_cast<const char*>(args);std::cout<<"我是線程:"<<s<<" 正在運行"<<std::endl;sleep(1);}
}int main()
{pthread_t t1;pthread_create(&t1,nullptr,routine,(void*)"pthread-1");while(1){std::cout<<"我是主線程,真正運行當中……"<<std::endl;sleep(1);};pthread_join(t1,nullptr);return 0;
}

以上就是本篇的全部內容了,接下來在線程概念《下》當中將繼續學習線程的概念,未完待續……

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

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

相關文章

為什么知識復用時缺乏場景化指導影響實用性

知識復用時因缺乏場景化指導而嚴重影響實用性&#xff0c;其根本原因在于知識的價值本質上根植于其應用情境。脫離了場景的“純知識”往往是抽象、片面且難以行動的。這導致了認知鴻溝的產生、隱性知識的流失、決策風險的增加、以及學習遷移效率的低下。當使用者面對一份缺乏“…

擁抱直覺與創造力:走進VibeCoding的新世界

引言 在傳統觀念里&#xff0c;編程是一項高度理性、邏輯嚴密的活動&#xff0c;開發者需要像建筑師一樣&#xff0c;用代碼一行行地精確構建數字世界。然而&#xff0c;隨著人工智能技術的飛速發展&#xff0c;一種全新的編程理念和體驗正在興起——它就是 VibeCoding&#xf…

HTTP的Web服務測試在Python中的實現

在Web開發領域&#xff0c;對HTTP Web服務進行測試是確保服務穩定性和可靠性的關鍵步驟。Python作為一種功能強大的編程語言&#xff0c;提供了多種工具和庫來簡化這一過程。本文將介紹如何在Python中實現HTTP的Web服務測試。首先&#xff0c;Python的requests庫是測試HTTP Web…

Android Studio 構建項目時 Gradle 下載失敗的解決方案

一、問題原因分析根據錯誤日志&#xff1a;下載地址 https://services.gradle.org/distributions/gradle-8.1-bin.zip 連接超時&#xff08;10秒&#xff09;。可能原因&#xff1a;網絡環境限制&#xff08;如公司防火墻、地區網絡屏蔽&#xff09;。代理配置未生效或配置錯誤…

mysql 與 MongoDB 的分片

MySQL 和 MongoDB 作為不同類型數據庫的代表(關系型 vs 文檔型),其分片機制在設計理念、實現方式和適用場景上存在顯著差異。兩者的分片核心目標一致——通過水平擴展(Scale Out)解決單節點存儲容量和性能瓶頸,但因數據模型、事務支持和分布式設計理念的不同,形成了截然…

Coze源碼分析-資源庫-創建知識庫-前端源碼-核心邏輯與接口

創建知識庫邏輯 1. 表單驗證系統 文件位置&#xff1a;frontend/packages/data/knowledge/knowledge-modal-base/src/create-knowledge-modal-v2/features/add-type-content/coze-knowledge/index.tsx 知識庫創建表單的驗證規則&#xff1a; // 知識庫名稱驗證規則 const nameV…

歐拉函數 | 定義 / 性質 / 應用

注&#xff1a;本文為 “歐拉函數” 相關合輯。 略作重排&#xff0c;未整理去重。 如有內容異常&#xff0c;請看原文。 歐拉函數最全總結 jiet07 已于 2024-10-22 10:00:54 修改 一、歐拉函數的引入 首先引入互質關系&#xff1a; 如果兩個正整數&#xff0c;除了 111 以…

ubuntu git push每次都要輸入密碼怎么解決只輸入一次密碼

在 Ubuntu 下使用 Git 時&#xff0c;如果每次 push 都需要重復輸入密碼&#xff0c;可以通過配置 Git 憑證存儲來解決。以下是幾種常用方法&#xff1a; &#x1f511; 方法一&#xff1a;使用 Git 憑證緩存&#xff08;推薦&#xff09; 設置憑證緩存&#xff08;默認 15 分鐘…

【機械故障】使用fir濾波器實現數據擬合

使用fir濾波器實現數據擬合 提示&#xff1a;學習筆記 使用fir濾波器實現數據擬合使用fir濾波器實現數據擬合一、問題建模二、 構建矩陣方程&#xff08;關鍵步驟&#xff09;三、最小二乘解四、重要注意事項4.1 濾波器長度 M4.2 數據的預處理4.3 延遲問題4.4 性能評估一、問題…

STC8H系列-高級PWM-兩相步進電機-細分驅動

兩相步進電機, STC8H系列 用高級PWM實現SPWM細分驅動 /************* 功能說明 ************** 用B組高級PWM細分驅動2相4線小型步進電機, 支持1、2、4、8、16、32、64細分, 比如1.8度的電機4細分到0.45度. 本程序用于演示SPWM多細分直接驅動2相4線小型步進電機…

內網環境下ubuntu 20.04搭建深度學習環境總結

2025年9月更新&#xff0c;隨著人工智能的發展&#xff0c;現在深度學習環境配置越來越簡單了&#xff0c;常用的pytorch、paddle&#xff08;3.x&#xff09;等深度學習庫安裝的時候自帶了cuda和cudnn的python包&#xff0c;不需要在操作系統層面自己安裝&#xff0c;配置環境…

深入 Linux 文件系統:從數據存儲到萬物皆文件

深入 Linux 文件系統&#xff1a;從數據存儲到萬物皆文件 Linux 文件系統是一個精妙而復雜的工程&#xff0c;它像一座圖書館&#xff0c;不僅存放著書籍&#xff08;數據&#xff09;&#xff0c;還有一套高效的卡片索引系統&#xff08;元數據&#xff09;來管理它們。本文將…

C++, ffmpeg, libavcodec-RTSP拉流,opencv實時預覽

文章目錄RTSPStreamPlayer.cppRTSPStreamPlayer.hmain.cpp編譯運行在ffmpeg_rtsp原有的rtsp拉流項目基礎上加入了udp連接rtsp&#xff0c;日志模塊&#xff0c;opencv實施預覽等功能。RTSPStreamPlayer.cpp #include "RTSPStreamPlayer.h" #include <iostream>…

MySQL在Ubuntu 20.04 環境下的卸載與安裝

目錄 前言&#xff1a;學習引入 1、安裝注意事項 2、學習建議 3、MySQL 和 MariaDB 核心概念一&#xff1a;它們是什么&#xff1f; 核心概念二&#xff1a;它們如何工作&#xff1f;&#xff08;“倉庫”比喻&#xff09; 核心概念三&#xff1a;為什么它們如此流行&…

BizDevOps 是什么?如何建設企業 BizDevOps 體系

在數字經濟加速滲透的今天&#xff0c;企業數字化轉型已從 “技術升級” 轉向 “價值重構”&#xff0c;單純的 IT 研發或業務優化已難以適應市場快速變化。業務研發運營一體化&#xff08;BizDevOps&#xff09;作為打通 “業務 - 技術 - 運維” 協同壁壘的核心模式&#xff0…

Mac菜單欄綜合工具FancyTool更新啦

本次更新聚焦「輕量體驗」深度優化&#xff1a;不僅重構了 CPU 占用邏輯與系統喚醒機制&#xff0c;讓后臺運行更高效&#xff1b;更讓動畫交互全程保持絲滑流暢&#xff0c;資源消耗卻低到近乎無感 —— 哪怕它常駐菜單欄&#xff0c;你也幾乎察覺不到它的存在&#xff0c;既不…

ARM匯編 led

1.相關介紹本次用的開發板是IMX6ULLCPU&#xff1a;NXP i.MX 6ULL Cortex-A7單核處理器&#xff0c;主頻 528MHz&#xff08;工業級&#xff09; 或 800MHz&#xff08;商業級&#xff09;467, GBA封裝內存&#xff1a;512MB DDR3L RAM&#xff0c;支持高速數據存取。存儲&…

彈窗分頁保留其他頁面勾選的數據(vue)

如圖所示&#xff0c;這是個常見的多選todolist不過這里多了個要求&#xff0c;彈窗上下頁面切換的時候需要保留勾選結果這其實也不難&#xff0c;但是如果每次都手動寫一遍卻有點惱人&#xff0c;這次捋一下思路&#xff0c;并把核心代碼記錄一下&#xff0c;方便下次翻找核心…

分享:一種為藍牙、WIFI、U段音頻發射設備提供ARC回傳數字音頻橋接功能的方案

隨著智能電視、流媒體設備的普及&#xff0c;用戶對高質量音頻輸出的需求激增。為解決多設備協同、無線化傳輸及ARC高保真音頻傳輸的痛點&#xff0c;納祥科技推出HDMI ARC音頻轉換方案&#xff1a;HDMI ARC音頻轉光纖/同軸/I2S/左右聲道&#xff0c;橋接無線音頻發射設備&…

在WPF項目中使用阿里圖標庫iconfont

使用阿里圖標庫的步驟&#xff1a; 1。從阿里圖標庫官方網站上下載圖標。 2。把阿里圖標庫&#xff08;WPF中支持.ttf字體文件&#xff09;引入 3。在App.xaml中添加圖標的全局樣式。推薦在此處添加全局樣式&#xff0c;為了保證圖標可以在所有窗體中使用。 代碼如下&#x…