Ⅰ 任務管理的其他
4.4.1任務狀態及轉換時序
在上面的章節中,描述了任務的三種基本狀態,一般在實現時會基於這三種轉態添加新的狀態。圖4-4描述了實際實現的任務狀態轉換圖。在給定的時刻,任務的狀態一定處在這六種狀態之一,下面的論述只是對本系統實現的描述,不同的內核對這些部分的實現有很大差異,但基本原理不變。
圖4-4在描述任務狀態遷移的同時,也描述了任務的生存周期,任務的生命期從新建態時開始直到結束態時結束。在不同的操作系統中,這些狀態的實現是有差異的,有的內核還有其他狀態。新建狀態是指任務被創建的過程,在這個過程中主要工作有:為任務分配TCB和棧空間以及其他資源。當任務創建完成以後,任務就具備運行的能力了,與此同時,任務進入就緒狀態,並等待調度器為它分配運行的機會。當任務得到運行的機會,任務開始執行。處於運行態的任務會在任意時刻由運行態進入休眠態、就緒態或結束狀態。其中進入休眠態是任務的主動過程,這主要是任務調用了內核提供的休眠函數,任務在休眠狀態,如果沒有其他任務喚醒它,它將永遠休眠下去直到系統關閉,這種方式也可用於任務同步。等待狀態主要由兩種原因引起,一種是等待某事件的發生,如等待信號量;第二種為任務主動等待多少個tick。最後,任務可以將自己殺死進入結束態。
4.4.2任務控制
任務控制塊(TCB)唯一地描述了一個任務的屬性。一旦任務建立了,任務控制塊中的各個值將被賦值。任務控制塊是一個數據結構,當任務的CPU使用權被剝奪時,TCB保存了該任務的狀態和其他信息。當任務重新得到CPU使用權時,TCB能確保任務從被中斷的點絲毫不差地繼續執行。TCB全部駐留在RAM中。TCB在任務初始化的時候被建立。任務控制塊數據結構如下所示:
typedef struct task_ctrl_blk{
stk_t *pstack;
stk_t *pstk;
list_t link;
uword_t id;
uword_t prio;
uword_t slice_time;
uword_t exe_time;
word_t delay_time;
uword_t status;
list_t task_link;
}tcb_t;
其中:
·pstack:指向當前任務的棧頂。每個任務有自己的棧,尤為重要的是,每個任務的棧的容量可以是任意的。有些商業內核要求所有任務棧的容量都一樣,除非用戶寫一個復雜的介面函數來改變之。這種限制浪費了RAM,當各任務需要的棧空間不同時,也得按任務中預期棧容量需求最多的分配棧空間。pstack是TCB數據結構中唯一一個能用匯編語言來處置的變數(在任務切換段的代碼之中使用)把pstack放在數據結構的最前面,使得從匯編語言中處理這個變數時較為容易;
·pstk:指向任務的棧頂,在任務結束而回收任務棧空間時使用,這主要由內存管理部分的缺陷所引起的;
·link:用於連接任務控制塊。內核在運行時,除了任務控制塊外,系統中存在很多類型的鏈表,比如信號量鏈表。為了對這些鏈表有一個統一的操作,所以定義了list_t類型來統一這些操作。如果不使用list_t,TCB鏈表操作需要實現一組鏈表操作函數,信號量需要另外一組鏈表操作函數,這樣使程序變得冗長;
·id:任務的ID號,用於唯一標識一個任務。每個任務都有一個唯一的ID號,需要在任務創建的時候指定ID,如果指定的ID號已經存在,則此任務不能被創建;
·prio:任務的優先順序,此值范圍為0~63,值越小代表優先順序越高。內核將盡力保證高優先順序的任務優先運行,並且允許任務可以是相同的優先順序;
·slice_time:表示任務應該運行的時間片數。雖然內核保證高優先順序的任務優先得到運行的機會,但對於相同優先順序的任務來說,時間片方式是比較好的調度策略;
·exe_time:保存了任務已經運行的時間片個數。這個變數在每次系統時鍾中斷產生時被累加1,如果exe_time的值達到slice_time,則說明該任務已經運行了給定時間片的時間,這時,內核將把運行機會讓給其他的,且優先順序等於此任務的其他任務。如果此優先順序上沒有其他任務,且此任務沒有自己放棄運行機會,此任務將繼續運行;
·delay_time:用於記錄任務等待的時間片數,每個系統時鍾中斷產生時,此值自減1,如果delay_time的值為0,說明該任務的等待時間已經超時。內核將此任務從等待隊列中刪除,並移動就緒隊列中,這樣該任務就會被調度器在適當的時候調度;
·status:指示了任務的運行狀態,目前,此值表示的含義有就緒,休眠,等待和阻塞,在任務狀態轉換圖4-4中的運行態未能表示出來,這是因為在實現時,就緒態同時也表示了運行態;
·task_link:用於將系統中所有的任務連接成循環雙鏈表。
4.4.3 ByCore中的各種隊列
在圖4-4中描述的每個狀態都對應一個或一組隊列。如處於就緒狀態中的就緒隊列,處於等待態中的等待隊列等等。
4.4.3.1 就緒隊列
就緒隊列中的任務已經得到除CPU以外的所有資源。調度器也將在它們中按照優先順序和時間片結合的策略選擇一個就緒任務獲得CPU。在實現中,任務被分成64(0~63)種優先順序,且不同的任務又會有相同優先順序。內核將相同優先順序的任務組成一個雙鏈表。為了在調度過程中能快速的檢索出最高優先順序的任務隊列,將整個就緒隊列用一個全局數組list_t ptask[MAX_PRIO](其中MAX_PRIO=64)來作為不同優先順序就緒隊列的隊頭,如ptask為優先順序是i的就緒隊列的隊頭。整個就緒隊列如圖4-5所示。
4.4.3.2 等待和休眠隊列
當任務處於等待或休眠態時,內核必須將該任務的TCB從就緒隊列中刪除,然後插入到等待或者休眠隊列。在當前的實現中,內核只分別維持一個等待隊列和休眠隊列,這兩個隊列不像就緒隊列按照優先順序的高低被分組,換句話說,等待隊列和休眠隊列將所有的任務TCB連成一個雙鏈表。
pdelay和psleep分別為等待隊列和休眠隊列的對頭指針。這兩個隊列的組織雖然一樣,但是它們各自隊列中的任務被激活的時機卻不同,pdelay所指隊列中的任務會被內核的tick激活,而處在psleep隊列中的任務只能由其他的任務將其喚醒。利用這兩種隊列配和信號量等任務同步、通信機制可以實現較為復雜、靈活的任務控制機制。
當任務處在等待態時,任務還可能處在另外的隊列中,這個隊列就是為等待某個信號量而組織成的隊列。這個隊列將在信號量實現的內容中論述。
4.4.4調度器實現
在整個任務管理中,任務調度無疑是系統的核心,任務調度通常由內核中的調度器實現。調度器的實現與任務運行狀態遷移,任務隊列有密切的聯系,可以說任務運行狀態遷移和任務隊列決定了調度器的實現。調度器的主要作用是在就緒隊列中選擇優先順序最高的任務運行,如果優先順序最高的任務不止一個,則選擇隊頭的任務運行。雖然整個調度器的功能可以用上面的幾句話概括,但調度器的實現遠遠沒有那麼簡單,主要困難來源下面的原因:
1.確定調度器運行的時機;
2.中斷處理程序完了後,是執行當前任務,還是馬上調度;
3.調度器的性能;
4.調度中伴隨著任務上下文的切換,尤其對處理器架構有關的上下文,應該設計良好的介面以便移植。
以上這些基本問題都是應該考慮的,隨著內核功能的擴充和完善,調度器可能會在原先沒涉及到的地方被調用,雖然在這些新地方不要求能正確調度,但至少不能引起系統崩潰。對於實時系統來說,中斷處理程序執行完畢後,應該馬上執行調度,這是因為中斷常常伴隨著有新的任務處於就緒隊列中,在這些任務中可能會有高優先順序的任務就緒,所以在實時內核中要求必須支持在中斷後馬上進行任務調度。不管是在實時系統,還是在其他系統中,調度器性能顯得非常重要,常常要求調度器的時間復雜度至少應該為線性,當然常數是最好的。對於不同的處理器架構,其提供的寄存器,狀態寄存器都有很大的區別,調度器應該留出良好的介面給不同的處理器,以便以後方便移植。
在實現調度器時,基本上考慮了上面的幾個基本問題。根據上兩節論述的任務狀態遷移、內核隊列等方面的內容,在byCore中實現了一個叫scheler( )的調度程序。在scheler( )中調用幾個與硬體相關的函數,這幾個函數主要用於實現任務硬體上下文的切換,這部分代碼用匯編完成,並且與處理器有關。在現代操作系統中,會有很少一部分使用匯編語言實現,這是因為各種處理器架構的寄存器都沒有被映射到可見的位置,也即象C這樣的高級語言不能直接對其操作,然而,在任務切換時,硬體上下文會保存到任務堆棧中,這種操作使得高級語言無能為力。
該調度程序的演算法非常簡單,首先,在允許調度的情況下,如果有高優先順序任務就緒,則進行任務切換。任務切換會發生在兩種處理器模式下,一種是處理器處於正常的運行態,另一種發生在中斷態中。因此,內核使用兩組函數分別處理這兩種情況。在兩種處理器狀態下都有「啟動新任務」和「新舊任務切換」函數介面實現最後的任務切換工作,這兩組函數與處理器有關,並由匯編實現。在後面的內核移植一節將詳細論述這些函數介面的實現。
啟動新任務的主要功能是將任務的初始上下文復制給處理器的各個寄存器,這包括通用寄存器、堆棧指針寄存器、狀態寄存器和指令指針寄存器等。這些初始值在新任務創建時被初始化。啟動新任務發生的時機有兩種情況,第一種情況是內核初始化完畢後,啟動第一個任務;第二種情況為任務主動結束後,當前任務指針被置位NULL時。
任務切換發生在兩個任務之間,一個是被換切換出去的任務,另一個是將要執行的任務。任務切換函數也由匯編代碼實現。它所要完成的工作主要有兩個,第一是將舊任務(被換切換出去的任務)的上下文保存到自己的棧中,第二是新任務(將要執行的任務)將保存在棧中的上下文復制到處理器的相關寄存器中。任務切換的發生時機有:
· 當前任務執行時間到;
· 當前任務被高優先順序任務搶占;
· 當前任務休眠,或等待某事件發生。
由於任務切換與處理器關系緊密,本章只介紹與處理器無關部分的實現,與處理器有關的部分將在內核移植一章中詳細論述。
4.4.5 內核時鍾實現
在內核時鍾一節中,論述了內核時鍾的作用以及功能。但在當前實現中,根據實際的情況對內核時鍾的功能做了裁減,內核時鍾功能主要由systick( )函數實現。
4.4.6 任務管理API實現
任何內核都應該提供一組豐富的API函數供用戶使用。像UNIX、Linux、Windows這些大型操作系統提供了大量的API。當然這些API的數量、種類,用法等都會隨著系統的不同而不同。但在任務管理方面下面幾個API是必不可少的:任務創建、撤銷、休眠、等待和喚醒等操作。下面將描述各個API的實現演算法。
4.4.6.1 任務創建
當用戶調用任務創建函數時,內核應該完成哪些工作呢?這和內核的實現方式,復雜程度密切相關。當前任務管理實現中,提供兩個任務創建函數osInitTask( )和osCreateTask( )。這兩個函數的原型如下所示:
void osInitTask(void (*pTask)(), uword_t TaskID, uword_t Prio, uword_t Time, uword_t StkSize);
void osCreateTask(void (*pTask)(), tcb_t *pTcb, uword_t TaskID, uword_t Prio, uword_t Time, stk_t *pStk, uword_t StkSize);
這兩個函數的主要區別為任務需要的TCB和棧空間是否為動態創建。osInitTask( )函數只需要傳遞任務起始地址((*pTask)()),任務ID(TaskID),優先順序(Prio),運行時間片(Time)和棧大小(StkSize),任務的棧和TCB空間都為動態創建,棧和TCB空間處於系統的堆區。osCreateTask( )函數除了以上的參數外還格外需要*ptcb和*pstk兩個參數,這兩個參數分別指向任務的TCB起始地址和棧起始地址,這個函數的空間需要在編譯時制定,棧和TCB空間屬於內核區。雖然它們需要的參數不同,但它們的實現演算法是相同的。
在描述演算法之前需要對任務棧做簡單的論述,棧的作用是保證任務正常運行,它保存了任務中各個函數的調用軌跡和返回地址。對於處理器來說都提供一個獨立的寄存器或者其他空間保存著棧頂的位置,各種處理器架構對棧頂和棧底的定義也不相同,這主要有兩種,一是棧頂的地址值大於棧底,其二相反。第一種伴隨著棧往下增長,第二種棧往上增長。為了便於移植內核,內核應該處理這兩種情況。除了這兩種情況,棧還分為滿棧和空棧兩種,所以內核必須考慮這幾種棧方式。因此在實現中提供一組宏來應對這些情況,如下所示:
#define UP 1
#define DOWN 0
#define FULL 1
#define EMPTY 0
#define STACK DOWN
#define STACK_STYLE FULL
UP和DOWN定義了棧的增長方向,FULL和EMPTY說明了是滿棧還是空棧。最後用STACK和STACK_STYLE聯合說明真正的棧工作方式。
論述完了任務創建方面需要注意的一些問題,下面論述任務創建的演算法。任務創建過程主要包含初始化TCB和棧區,如果調用osCreateTask( )函數,在初始化前還需要向內核申請TCB和棧空間。圖4-9為osInitTask( )函數創建新任務的流程圖。
4.4.6.2 任務撤銷
每個任務都有一個生命周期,包括任務創建、運行與撤銷。任務撤銷也可稱為在多任務系統中,任務也可以被任何用戶殺死,也可以有特殊用戶殺死。比如,殺死任務。任務撤銷的方式有很多種實現方式。一般情況下,任務可以被內核殺死。在Linux下有些任務可以被任何用戶殺死,有些則只能由root用戶殺死。在單用戶系統中,用戶任務能被內核殺死,也可以被其他用戶任務殺死,但後種情況不多見。根據實際的情況,當前對任務撤銷的實現為只有任務自己主動殺死自己。
在當前實現中,任務撤銷的函數為osKill( ),如果當前任務完成了自己的使命,可以調用該函數。osKill( )會釋放掉該任務的相關資源,如TCB和棧空間等。osKill( )只釋放掉內核分配的資源,如果任務的運行過程中申請了其他資源,應該在調用osKill( )前釋放掉這些資源。任務在創建時有兩個創建函數osInitTask( )和osCreateTask( ),osKill( )只能釋放osInitTask( )的資源,而osCreateTask( )的資源會被保留下來。這是因為osCreateTask( )所使用的空間屬於內核空間,而不屬於系統動態內存管理的堆區,這部分區域沒有相關的數據結構管理,一旦釋放系統就會崩潰。根據上面的描述可以設計出osKill( )的演算法,該演算法如圖4-10所示。
4.4.6.3 任務休眠與喚醒
當任務需要等待某些資源的時候,可以將自己設為休眠狀態,把運行的機會讓給其他任務,當所等待的資源或者事件發生時,任務再被喚醒繼續運行。這種方式也是解決任務同步的一種辦法,如任務A與任務B合作完成某項任務,且A完成後B才能運行,休眠與喚醒機制可以很容易地解決此問題。內核實現了兩個函數分別完成這兩項工作,他們是osSleep( )和osWakeUp( ),osSleep( )是任務的主動行為,因此不需要參數,osWakeUp( )需要一個參數TaskID,該參數指定了需喚醒任務的ID號。
當任務調用osSleep( )後,該任務的TCB從就緒隊列中刪除,並插入到休眠隊列(如圖4-6所示),然後重新調度。如果任務A需要喚醒正在休眠的任務B,那麼A可以調用osWakeUp( )函數,並傳入B的ID。osWakeUp( )就會查找休眠隊列,如果找到任務B,則將它的狀態置為就緒,並從休眠隊列刪除插入就緒隊列。
4.4.6.4 任務等待
任務等待與任務休眠的實現原理都一樣。任務在等待一段時間後再獲得運行的機會,這個時候它所等待的事件或者資源有可能不可用,這點和任務休眠是有差異的。例如任務A需要與串口I/O通信,由於串口速度相對較慢,任務A大部分時間都需要等待,如果任務A在沒有數據傳輸的時候進入等待狀態,將會顯著提高CPU利用率。
內核提供了osWait( )函數來實現此功能,該函數接受一個時間參數,該參數說明當前任務等待時間長短,該時間以系統tick為單位。當前任務調用此函數後,任務狀態被置為等待態,TCB從就緒隊列中刪除,並插入到等待隊列,最後調度scheler( )。等待隊列與休眠隊列相同,見圖6-7所示。osWait( )函數的流程圖與osSleep( )演算法相似,這里不再贅述。
每次系統tick發生中斷時,內核時鍾中斷處理程序更新等待隊列上任務的等待時間域,也就是任務控制塊TCB的delay_time域作減1操作,當此域減少到0時,表示該任務的等待時間已到,這時它將從等待隊列中刪除,並插入到就緒隊列中。這些工作也是內核時鍾中斷當前唯一需要做的事情。
Ⅱ 求大神啊!!使用api實現 快速排序演算法最好用c++
C/C++ 中的快速排序 API 就是 qsort。
#include<iostream>
usingnamespacestd;
intcmp(constvoid*a,constvoid*b)
{
inti1=*((int*)a);
inti2=*((int*)b);
returni1-i2;
}
intmain()
{
intn=16;
intarr[]={3,12,1,999,56,77,32,4,99,12,67,65,43,22,2000,88};
cout<<"排序前:"<<endl;
for(inti=0;i<n;++i)
{
cout<<arr[i]<<"";
}
cout<<endl;
qsort(arr,n,sizeof(arr[0]),cmp);
cout<<"排序後:"<<endl;
for(inti=0;i<n;++i)
{
cout<<arr[i]<<"";
}
cout<<endl;
return0;
}
編譯運行:
Ⅲ C#怎麼來判斷2張圖片相似度
很麻煩,而且計算量很大,這個屬於人工智慧的范疇。
如果這「兩張相似圖片」可以規定很多前提,比如相同解析度,黑白,簡單幾何圖形。。。那麼可以用基本的演算法去算一下「相似度」, 也就是樓上說的,讀取兩張照片的像素點,然後遍歷去對比灰度差值。這些有很多現成的演算法,也有很多網站提供這方面的計算(直接調用API即可),但是只能得出數字化的「相似度」。
如果你要的就是兩張圖片像素點之間的差異,那麼就去找演算法即可實現。
看一參考這個網站:www.aforgenet.com 這個是國外比較知名的圖像處理的網站。
但是,兩張圖片如果尺寸不一呢? 如果比例不一樣呢? 如果有留白呢?彩色的呢?
所以目前最成熟的編程演算法也就是識別一下字母和數字(比如谷歌可以識別照片上的門牌號和街道號),人臉識別也只是拿幾個標本部位來大致判斷相似度(眼睛的大小,鼻樑的高度,臉頰的寬瘦和比例), 以人眼的標准完整的去比較兩張圖片是否一樣是很難的,目前應該還沒有這方面成熟的技術。
Ⅳ 編程開發一個網路應用系統——圖譜分析系統,用java語言實現,小女無知,還望各位編程高手慷慨出手相助
我做好了 嘿嘿
Ⅳ 請教SAPI
可以的。他有一個相似度的,一般是負數。越靠近0,相似度越大。
Ⅵ 正在在畢設,請問用VC++處理圖像相似度的演算法
建議你找一本vc++圖形圖像編程的書,在前面的幾章都會講怎麼取圖像數據,都有事例代碼,其實說起來也很簡單,就是通過api把數據部分從圖像文件里取出來,然後根據圖像的調色板轉成顏色數據,我推薦你找周長發的vc++圖形圖像處理,所有事例都有代碼並且可以直接使用,裡面的代碼雖然執行效率不高,但是結構非常清晰,適合學習使用。
Ⅶ 有沒有好的判斷圖片相似度的庫
很麻煩,而且計算量很大,這個屬於人工智慧的范疇。
如果這逗兩張相似圖片地可以規定很多前提,比如相同解析度,黑白,簡單幾何圖形。。。那麼可以用基本的演算法去算一下逗相似度地, 也就是樓上說的,讀取兩張照片的像素點,然後遍歷去對比灰度差值。這些有很多現成的演算法,也有很多網站提供這方面的計算(直接調用API即可),但是只能得出數字化的逗相似度地。
如果你要的就是兩張圖片像素點之間的差異,那麼就去找演算法即可實現。
看一參考這個網站: 這個是國外比較知名的圖像處理的網站。
但是,兩張圖片如果尺寸不一呢看 如果比例不一樣呢看 如果有留白呢看彩色的呢看
所以目前最成熟的編程演算法也就是識別一下字母和數字(比如谷歌可以識別照片上的門牌號和街道號),人臉識別也只是拿幾個標本部位來大致判斷相似度(眼睛的大小,鼻樑的高度,臉頰的寬瘦和比例), 以人眼的標准完整的去比較兩張圖片是否一樣是很難的,目前應該還沒有這方面成熟的技術。
Ⅷ 用Java 8 增加的 Stream API 能實現哪些優雅的演算法
Java 8引入了全新的Stream API。這里的Stream和I/O流不同,它更像具有Iterable的集合類,但行為和集合類又有所不同。
Stream API引入的目的在於彌補Java函數式編程的缺陷。對於很多支持函數式編程的語言,map()、rece()基本上都內置到語言的標准庫中了,不過,Java 8的Stream API總體來講仍然是非常完善和強大,足以用很少的代碼完成許多復雜的功能。
創建一個Stream有很多方法,最簡單的方法是把一個Collection變成Stream。我們來看最基本的幾個操作:
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream = numbers.stream();
stream.filter((x) -> {
return x % 2 == 0;
}).map((x) -> {
return x * x;
}).forEach(System.out::println);
}
集合類新增的stream()方法用於把一個集合變成Stream,然後,通過filter()、map()等實現Stream的變換。Stream還有一個forEach()來完成每個元素的迭代。
為什麼不在集合類實現這些操作,而是定義了全新的Stream API?Oracle官方給出了幾個重要原因:
一是集合類持有的所有元素都是存儲在內存中的,非常巨大的集合類會佔用大量的內存,而Stream的元素卻是在訪問的時候才被計算出來,這種「延遲計算」的特性有點類似Clojure的lazy-seq,佔用內存很少。
二是集合類的迭代邏輯是調用者負責,通常是for循環,而Stream的迭代是隱含在對Stream的各種操作中,例如map()。
要理解「延遲計算」,不妨創建一個無窮大小的Stream。
如果要表示自然數集合,顯然用集合類是不可能實現的,因為自然數有無窮多個。但是Stream可以做到。
自然數集合的規則非常簡單,每個元素都是前一個元素的值+1,因此,自然數發生器用代碼實現如下:
class NaturalSupplier implements Supplier<Long> {
long value = 0;
public Long get() {
this.value = this.value + 1;
return this.value;
}
}
反復調用get(),將得到一個無窮數列,利用這個Supplier,可以創建一個無窮的Stream:
public static void main(String[] args) {
Stream<Long> natural = Stream.generate(new NaturalSupplier());
natural.map((x) -> {
return x * x;
}).limit(10).forEach(System.out::println);
}
對這個Stream做任何map()、filter()等操作都是完全可以的,這說明Stream API對Stream進行轉換並生成一個新的Stream並非實時計算,而是做了延遲計算。
當然,對這個無窮的Stream不能直接調用forEach(),這樣會無限列印下去。但是我們可以利用limit()變換,把這個無窮Stream變換為有限的Stream。
利用Stream API,可以設計更加簡單的數據介面。例如,生成斐波那契數列,完全可以用一個無窮流表示(受限Java的long型大小,可以改為BigInteger):
class FibonacciSupplier implements Supplier<Long> {
long a = 0;
long b = 1;
@Override
public Long get() {
long x = a + b;
a = b;
b = x;
return a;
}
}
public class FibonacciStream {
public static void main(String[] args) {
Stream<Long> fibonacci = Stream.generate(new FibonacciSupplier());
fibonacci.limit(10).forEach(System.out::println);
}
}
如果想取得數列的前10項,用limit(10),如果想取得數列的第20~30項,用:
List<Long> list = fibonacci.skip(20).limit(10).collect(Collectors.toList());
最後通過collect()方法把Stream變為List。該List存儲的所有元素就已經是計算出的確定的元素了。
用Stream表示Fibonacci數列,其介面比任何其他介面定義都要來得簡單靈活並且高效。