摘要: 我們首先要學習的是在計算機科學中信息如何編碼和存儲。第一步,我們要討論計算機數據存儲設備的基礎知識,然后進一步研究如何進行信息編碼并存儲到系統內部。我們還將探討現如今數據存儲系統的各個分支,以及如何用數據壓縮、糾錯等技術來克服其不足。
閱讀全文
原文 volatile提醒編譯器它后面所定義的變量隨時都有可能改變,因此編譯后的程序每次需要存儲或讀取這個變量的時候,都會直接從變量地址中讀取數 據。如果沒有volatile關鍵字,則編譯器可能優化讀取和存儲,可能暫時使用寄存器中的值,如果這個變量由別的程序更新了的話,將出現不一致的現象。 下面舉例說明。在DSP開發中,經常需要等待某個事件的觸發,所以經常會寫出這樣的程序:
short flag;
void test()
{
do1();
while(flag==0);
do2();
}
這段程序等待內存變量flag的值變為1(懷疑此處是0,有點疑問,)之后才運行do2()。變量flag的值由別的程序更改,這個程序可能是某個硬件中 斷服務程序。例如:如果某個按鈕按下的話,就會對DSP產生中斷,在按鍵中斷程序中修改flag為1,這樣上面的程序就能夠得以繼續運行。但是,編譯器并 不知道flag的值會被別的程序修改,因此在它進行優化的時候,可能會把flag的值先讀入某個寄存器,然后等待那個寄存器變為1。如果不幸進行了這樣的 優化,那么while循環就變成了死循環,因為寄存器的內容不可能被中斷服務程序修改。為了讓程序每次都讀取真正flag變量的值,就需要定義為如下形 式:
volatile short flag;
需要注意的是,沒有volatile也可能能正常運行,但是可能修改了編譯器的優化級別之后就又不能正常運行了。因此經常會出現debug版本正常,但是 release版本卻不能正常的問題。所以為了安全起見,只要是等待別的程序修改某個變量的話,就加上volatile關鍵字。
volatile的本意是“易變的”
由于訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化。比如:
static int i=0;
int main(void)
{
...
while (1)
{
if (i) do_something();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中斷產生時,在main當中調用do_something函數,但是,由于編譯器判斷在main函數里面沒有修改過i,因此可 能只執行一次對從i到某寄存器的讀操作,然后每次if判斷都只使用這個寄存器里面的“i副本”,導致do_something永遠也不會被調用。如果變量 加上volatile修飾,則編譯器保證對此變量的讀寫操作都不會被優化(肯定執行)。此例中i也應該如此說明。
一般說來,volatile用在如下的幾個地方:
1、中斷服務程序中修改的供其它程序檢測的變量需要加volatile;
2、多任務環境下各任務間共享的標志應該加volatile;
3、存儲器映射的硬件寄存器通常也要加volatile說明,因為每次對它的讀寫都可能由不同意義;
另外,以上這幾種情況經常還要同時考慮數據的完整性(相互關聯的幾個標志讀了一半被打斷了重寫),在1中可以通過關中斷來實現,2中可以禁止任務調度,3中則只能依靠硬件的良好設計了。
二、volatile 的含義
volatile總是與優化有關,編譯器有一種技術叫做數據流分析,分析程序中的變量在哪里賦值、在哪里使用、在哪里失效,分析結果可以用于常量合并,常量傳播等優化,進一步可以死代碼消除。但有時這些優化不是程序所需要的,這時可以用volatile關鍵字禁止做這些優化,volatile的字面含義是易變的,它有下面的作用:
1 不會在兩個操作之間把volatile變量緩存在寄存器中。在多任務、中斷、甚至setjmp環境下,變量可能被其他的程序改變,編譯器自己無法知道,volatile就是告訴編譯器這種情況。
2 不做常量合并、常量傳播等優化,所以像下面的代碼:
volatile int i = 1;
if (i > 0) ...
if的條件不會當作無條件真。
3 對volatile變量的讀寫不會被優化掉。如果你對一個變量賦值但后面沒用到,編譯器常常可以省略那個賦值操作,然而對Memory Mapped IO的處理是不能這樣優化的。
前面有人說volatile可以保證對內存操作的原子性,這種說法不大準確,其一,x86需要LOCK前綴才能在SMP下保證原子性,其二,RISC根本不能對內存直接運算,要保證原子性得用別的方法,如atomic_inc。
對于jiffies,它已經聲明為volatile變量,我認為直接用jiffies++就可以了,沒必要用那種復雜的形式,因為那樣也不能保證原子性。
你可能不知道在Pentium及后續CPU中,下面兩組指令
inc jiffies
;;
mov jiffies, %eax
inc %eax
mov %eax, jiffies
作用相同,但一條指令反而不如三條指令快。
三、編譯器優化 → C關鍵字volatile → memory破壞描述符zz
“memory”比較特殊,可能是內嵌匯編中最難懂部分。為解釋清楚它,先介紹一下編譯器的優化知識,再看C關鍵字volatile。最后去看該描述符。
1、編譯器優化介紹
內存訪問速度遠不及CPU處理速度,為提高機器整體性能,在硬件上引入硬件高速緩存Cache,加速對內存的訪問。另外在現代CPU中指令的執行并不一 定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。以上是硬件級別的優化。再看軟件一級的優化:一種是在 編寫代碼時由程序員優化,另一種是由編譯器進行優化。編譯器優化常用的方法有:將內存變量緩存到寄存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對 常規內存進行優化的時候,這些優化是透明的,而且效率很好。由編譯器優化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須 以特定順序執行的操作之間設置內存屏障(memory barrier),linux 提供了一個宏解決編譯器的執行順序問題。
void Barrier(void)
這個函數通知編譯器插入一個內存屏障,但對硬件無效,編譯后的代碼會把當前CPU寄存器中的所有修改過的數值存入內存,需要這些數據的時候再重新從內存中讀出。
2、C語言關鍵字volatile
C語言關鍵字volatile(注意它是用來修飾變量而不是上面介紹的__volatile__)表明某個變量的值可能在外部被改變,因此對這些變量的 存取不能緩存到寄存器,每次使用時需要重新存取。該關鍵字在多線程環境下經常使用,因為在編寫多線程的程序時,同一個變量可能被多個線程修改,而程序通過 該變量同步各個線程,例如:
DWORD __stdcall threadFunc(LPVOID signal)
{
int* intSignal=reinterpret_cast<int*>(signal);
*intSignal=2;
while(*intSignal!=1)
sleep(1000);
return 0;
}
該線程啟動時將intSignal 置為2,然后循環等待直到intSignal 為1 時退出。顯然intSignal的值必須在外部被改變,否則該線程不會退出。但是實際運行的時候該線程卻不會退出,即使在外部將它的值改為1,看一下對應 的偽匯編代碼就明白了:
mov ax,signal
label:
if(ax!=1)
goto label
對于C編譯器來說,它并不知道這個值會被其他線程修改。自然就把它cache在寄存器里面。記住,C 編譯器是沒有線程概念的!這時候就需要用到volatile。volatile 的本意是指:這個值可能會在當前線程外部被改變。也就是說,我們要在threadFunc中的intSignal前面加上volatile關鍵字,這時 候,編譯器知道該變量的值會在外部改變,因此每次訪問該變量時會重新讀取,所作的循環變為如下面偽碼所示:
label:
mov ax,signal
if(ax!=1)
goto label
3、Memory
有了上面的知識就不難理解Memory修改描述符了,Memory描述符告知GCC:
1)不要將該段內嵌匯編指令與前面的指令重新排序;也就是在執行內嵌匯編代碼之前,它前面的指令都執行完畢
2)不要將變量緩存到寄存器,因為這段代碼可能會用到內存變量,而這些內存變量會以不可預知的方式發生改變,因此GCC插入必要的代碼先將緩存到寄存器的變量值寫回內存,如果后面又訪問這些變量,需要重新訪問內存。
如果匯編指令修改了內存,但是GCC 本身卻察覺不到,因為在輸出部分沒有描述,此時就需要在修改描述部分增加“memory”,告訴GCC 內存已經被修改,GCC 得知這個信息后,就會在這段指令之前,插入必要的指令將前面因為優化Cache 到寄存器中的變量值先寫回內存,如果以后又要使用這些變量再重新讀取。
使用“volatile”也可以達到這個目的,但是我們在每個變量前增加該關鍵字,不如使用“memory”方便。
摘要: 在用CCD相機拍照的圖像進行實時成像的時候遇到這么一個問題: 1: RunCamera(); 2: while (1) { 3: GetImg(); 4: ShowImg(); 5: } 6: CloseCamera();
想要對獲取的圖像進行實時成像,最先想到的是采用while(1)的方式,但是這樣的方式會帶來一些問題,除了終止程序,沒有辦法使得循環...
閱讀全文
拾起才一個月的時間,codelite在今晚這個時候開機突然間無法打開了,開啟了后在任務管理器中會出現codelite,然后過一會兒就自動消失了,悲劇呀,算了還是用code::block了,最討厭的是莫名奇妙的錯誤,讓人不知所措誒
摘要: 上一篇文章中介紹了關于CCD camera實現的一般流程CCD camera的一般控制流程及些許困惑,現在想用類來實現這個過程。該類設計以及實現的目的是用于相機拍照,成像,存儲。目前僅考慮拍照與實時成像兩個過程。由于從簡單上進行設計,實現的一般流程中的一些步驟能夠省略。具體代碼如下: 1: // ccd_class.h 2: class CCDClass 3: { 4: pri...
閱讀全文
1. 聲明變量
PCO_General strGeneral;
PCO_CameraType strCamType;
PCO_Sensor strSensor;
PCO_Description strDescription;
PCO_Timing strTiming;
PCO_Storage strStorage;
PCO_Recording strRecording;
2. 設置變量大小
strGeneral.wSize = sizeof(strGeneral);
strGeneral.strCamType.wSize = sizeof(strGeneral.strCamType);
strCamType.wSize = sizeof(strCamType);
strSensor.wSize = sizeof(strSensor);
strSensor.strDescription.wSize = sizeof(strSensor.strDescription);
strSensor.strDescription2.wSize = sizeof(strSensor.strDescription2);
strDescription.wSize = sizeof(strDescription);
strTiming.wSize = sizeof(strTiming);
strStorage.wSize = sizeof(strStorage);
strRecording.wSize = sizeof(strRecording);
3. 打開相機,填寫變量結構
PCO_OPENCAMERA(&hCam, iBoardNumber)
PCO_GETGENERAL(hCam, &strGeneral)
PCO_GETCAMERATYPE(hCam, &strCamType)
PCO_GETSENSORSTRUCT(hCam, &strSensor)
PCO_GETCAMERADESCRIPTION(hCam, &strDescription)
PCO_GETTIMINGSTRUCT(hCam, &strTiming)
PCO_GETRECORDINGSTRUCT(hCam, &strRecording)
4. 設置相機相關參數,曝光時間,觸發模式,ROI區域大小等
5. arm the camera使相機準備好
6. 獲取圖片大小,分配buffer
PCO_GETSIZES(hCam, &actualsizex, &actualsizey, &ccdsizex, &ccdsizey)
PCO_ALLOCATEBUFFER(hCam, &bufferNr, actualsizex*actualsizey*sizeof(WORD), &data, &hEvent)
其中bufferNr是buffer的編號,actualsizex*actualsizey*sizeof(WORD)為data的大小,data用來存儲圖片數據。
7. 開始進行記錄,并將圖片數據添加到指定的buffer中
PCO_SetRecordingState(hCam,0x0001);
PCO_AddBufferEx(hCam,0,0,bufferNr,actualsizex,actualsizey,bitres);
原來不清楚data中的圖片數據是怎么獲取的,本來想將這個困惑寫到這里,不過寫著寫著突然發現原來如此,它是通過bufferNr來制定第六步中分配相應大小的data的。寫博客還是能夠把問題給理清楚一點的。
8. 顯示圖像
這里可以采用原來的文章中提到的方法使用wxWidget中遇到的圖片存儲問題一二
9. 停止記錄
PCO_SetRecordingState(hCam,0x000);
10. 從CamRAM中讀取圖片
PCO_GetNumberOfImagesInSegment(hCam, wActSeg, &dwValidImageCnt, &dwMaxImageCnt);
PCO_GetImageEx(hCam,wActSeg,dw1swImage,dwLastImage,bufferNr,actualsizex,actualsizey,bitres)
上述函數的應該與AddBufferEx有類似的功能。AddBufferEx直接在記錄過程中將數據存儲在data中,而GetImageEx是從ccd的寄存器中讀取相應的數據賦值到data中。
11. 釋放用于存儲圖像數據的buffer,關閉相機
PCO_FreeBuffer(hCamera,sBufNr)
PCO_CloseCamera(hCamera);
上面的過程是獲取單個圖像的整個流程,如果要連續進行拍攝呢?思考中。。。。。。
這本書對于入門級的人來說是一個不錯的選擇,它囊括了很多關于計算機方面的知識,能夠使你對這方面的知識有個廣泛的了解。
對于小數的存儲常用的是浮點記數法。
浮點記數法簡介
對于知識的掌握,通過例子的學習是一個很好的方式。為了考慮到例子的簡易型,用一個字節來存儲浮點數。那么如01101011這樣的二進制序列表示的是什么小數呢?
一個字節中浮點記數法有如下成分:
在01101011這么一個串中,符號位是0表示非負,1則表示負,指數為110,表示位數將乘以2^(指數),也就是將小數點從尾數位的最左邊移動指數個位,如果指數為正,向右移動;反之,向左移動指數位。110是用余碼的方式進行記錄的。對于余碼,我采用的解析的方式是,將最高為取反,按照補碼的方式進行讀取。
110--->010,表示2;0100--->1100,表示-4;
既然提到了補碼,就解釋一下補碼,最高位為零的補碼其值直接讀取,0110--->6;最高為為1的補碼,其值的讀取方式,從右往左遇到第一個1之后的所有位取反,讀出值后加個負號,1101--->0011,則-3。當然對于0和最小的負數,不適合上述的方式,但是也很容易就能給出其值。
再讓我們回到0,110,1011這個串中,尾數.1011最左斷含有一個小數點,由于110--->2,因此小數點向右移動兩位則10.11,此時讀取值2+1/2+1/4=2又3/4。
至此,我們已經知道小數是如何存儲在二進制串中,也知道了如何將其轉換為10進制數值,那么該提出注意事項的時候了。
1. 規范化形式
尾數最左端的一位必須是1,否則會出現多意的現象。如00111100,01000110,都可以解碼為3/8。
2. 截斷誤差
整型值會出現溢出現象,同樣浮點數也會出現類似的現象。如利用一字節浮點記數法存儲值2又5/8,先用二進制進行表示10.101,而尾數只有4位,因此會出現截斷。
還有一個需要注意的是浮點記數法表示的數值加法中,它們相加的順序很重要。
如,2+1/4+1/8+1/8+1/8+1/8,當到2(5/8)+1/8時,2(5/8)用二進制表示為10.101,這將發生截斷,變成10.10,繼續加1/8則會持續階段。當反過來進行加法運算時,則不會出現截斷的現象。
由于實際浮點數具有較長的位數,能夠較好的運用于一般的浮點數運算中。但是這樣的問題在一些應用中是很嚴重的(如航海系統),小的誤差在不斷的加法運算中累加,最終產生嚴重的后果,因此對于精度要求較好的運算中,可以選擇采用分數表示法,分裝一個表示分數的類。
工作學習上由于需要編寫一個能夠控制CCD進行拍照的簡易軟件,因此需要對CCD照相機的基本原理有一個較為清楚的認識,于是參考DICAM-PRO Operating Instructions寫下了這篇文章。
上圖顯示了CCD相機不同的功能模塊,其中主要的是Image Intensifier,CCD,12 Bit A/D,parallel/serial Converter,Logic Control。
那么在計算機內部相對應的軟件是如何對從CCD相機傳輸進來的數據進行處理的呢。如下圖所示:
可見對于圖片數據進行存儲的時候可以采用16bit的形式進行無損存儲,但是在pc機的屏幕上進行顯示的時候,一般需將16bit的數組轉化為8bit的數組,然后采用8bit,24bit或32bit的形式將圖形成像到屏幕上。由于屏幕成像的方式是采用RGB或加一個alpha通道進行成像的,即使采用8bit的方式進行成像,也是將其轉化成了R=G=B的灰度圖進行成像。
下面主要從CCD相機圖像增強,曝光、觸發方式兩個方面進行闡述。
1. 圖像增強
光子打到光電陰極上產生電子,通過MCP進行增益而后打到熒光粉上發出光。
該處需要注意的是:光子打到光電陰極上與CCD的使用壽命直接相關,另外,不允許強光照射到光電陰極板,這樣有可能損壞光電陰極板,從而使CCD相機損壞。由于對光強有一定的要求,因此對于曝光時間也要小心設置,過長的曝光時間也有可能引起光電陰極板的損壞。
2. 曝光、觸發
觸發模式分為:Single Trigger Mode, Multi Trigger Mode, Double Trigger Mode;
a. single trigger mode
系統延時與脈沖延時是硬件上的延時是無法避免的,它們總共的時間在100ns以內,延時時間(Delay)(0-1000s)與曝光(Exposure)(x-1000s,x脈沖時序相關)時間是人為設定的。因為它是單觸發模式,每一次觸發都帶有系統與脈沖上的延遲。在每次循環之間也需要額外的時間。
b. multi trigger mode
延遲時間0-999ms,曝光時間在20ns-999ms之間。
c. double trigger mode

需求:
要運用番茄時間管理,沒有像文中所說的定時器,用手機也怎么方便,因為本身就經常是在計算機前工作學習的。那么下一個軟件呢?有那么一款PomoTime,既包含了定時的功能,又含有添加Todolist,統計當天的番茄時間等的功能,而我僅僅需要一個定時功能,因為對于Todolist還是希望采用紙筆的方式進行記錄。原來嘗試看桌面右下角的時間,還判斷一個番茄時間是否結束,發現工作中,往往因為過于集中注意力或分散注意力瀏覽網頁去了而忘記對時間的查看,同時又想將自己所學的知識進行運用,于是想到了這么個方式,自己編寫一個簡易的定時器吧。
功能描述:
1. 顯示分,秒,處于桌面最前面;
2. 設定分,秒;
3. 當設定的時間消耗完后會彈出對話框,進行提醒;
(這里沒有采用聲音提醒的原因是若采用這種方式,會影響到別人的辦公與學習。)
語言選擇:
由于這僅僅是一個很簡單的功能實現,對于語言沒有很大的要求,選擇了C++,庫為wxWidget。
界面設計:
使用wxFormBuilder
關鍵點描述:
在整個過程中遇到的問題的地方時,如何進行倒計時?最先想到的就是onTimer這樣類似的功能,在wxWidget庫中找到了具有該功能的wxTimer類,在手冊中這么一句話 its precision is platform-dependent, but in general will not be better than 1ms nor worse than 1s. 也就是說采用該方法進行時間設定可能每一次都會造成1ms-1s時間上的誤差,這個誤差不是很大,對于整個的設計目的并沒有太大的影響。
全部源碼在Simple timer source code
關于時間處理的部分代碼如下:
1: // timer_app.h
2: ////////////////////////////////////////////////////////////////////////////////
3: // main application frame declaration
4: ////////////////////////////////////////////////////////////////////////////////
5:
6: class TimerFrame : public MainDialogBase
7: {
8: public:
9: TimerFrame( wxWindow *parent );
10: virtual ~TimerFrame();
11:
12: protected:
13: // protected event handlers
14: virtual void OnCloseDialog( wxCloseEvent& event );
15: virtual void OnSetButtonClick( wxCommandEvent& event );
16: virtual void OnStartButtonClick( wxCommandEvent& event );
17:
18: public:
19: void OnTimer( wxTimerEvent& event );
20:
21: private:
22: int m_minute;
23: int m_second;
24: wxTimer *m_clock;
25: wxDECLARE_EVENT_TABLE();
26: };
27:
28: // timer_app.cpp
29: // event list, combine timer event with OnTimer function
30: wxBEGIN_EVENT_TABLE(TimerFrame, MainDialogBase)
31: EVT_TIMER(TIMER_ID, TimerFrame::OnTimer)
32: wxEND_EVENT_TABLE()
33:
34: //......some code......
35:
36: TimerFrame::TimerFrame(wxWindow *parent) : MainDialogBase( parent )
37: {
38: m_clock = new wxTimer(); // create a new wxTimer
39: m_clock->SetOwner(this, TIMER_ID); // set owner, #define TIMER_ID 1000
40: m_minute = 25;
41: m_second = 0;
42: }
43:
44: //......some code......
45:
46: // count down the time and show
47: void TimerFrame::OnTimer(wxTimerEvent& event)
48: {
49: // determine the minute and second wait to show
50: if (m_second == 0 && m_minute == 0) {
51: m_clock->Stop();
52: wxMessageBox(wxT("Time over"),wxT("Timer"));
53: return;
54: } else if (m_second ==0) {
55: m_second = 59;
56: m_minute -= 1;
57: } else {
58: m_second -= 1;
59: }
60: wxString sTmp;
61: sTmp.Printf(wxT("%d"),m_minute);
62: m_minuteTextCtrl->SetValue(sTmp);
63: sTmp.Printf(wxT("%d"),m_second);
64: m_secondTextCtrl->SetValue(sTmp);
65: }
66: