2013年12月17日 星期二

2013年11月16日 星期六

UltraEdit的菜單欄亂碼問題

    一直以來都是用notepad++作為修改代碼的編輯器,開源並且的確很好用,但是在使用時發現一個嚴重bug,若一個文件同時被notepad++和另 外的程序打開,而另外的程序修改了這個文件,notepad++不能及時顯示,只能多點一次「重新讀取文件」的菜單,估計作者已經知道這個bug,但是程 序底層無法修改,所以就採用了這種補救方法,而這個問題在ultraedit下是完全不存在的,於是從官網下載了一個繁體版,安裝之後,發現菜單欄的字符 很奇怪,但是沒反應過來其實就是亂碼,於是卸載又安裝英文版,結果菜單欄還是亂碼,於是Google解決方案,大概是因為我的電腦是中文版的,與 ultraedit的字符不兼容,而之前安裝過繁體版的ultraedit,雖然卸載,但是配置文件並沒有刪除,於是在C盤的用戶名目錄下的還存在。
    解決方案,刪除配置文件即可,在Windows 7下是C:\Users\用戶名\AppData\Roaming,話說Windows 7為了兼容大部分的xp程序,於是就有Roaming這個文件,而系統盤目錄下的Document and Setting其實是無法訪問的,我想直接就是映射到Roaming這個目錄裡吧。

以上文字引自該網站http://ordinarysky.cn/?tag=菜單欄亂碼


—————————————————————————————————————————————————

    今天在網上搜了一天  儘是些說UltraEdit顯示中文什麼的出現亂碼的,天知道我會碰到菜單欄亂碼的問題,好不容易找到一個是在Win7環境下的。不過以上方法說的我個人感覺有點不準確。

    我還參考了另外一種方法,之前在找這個問題的時候,發現XP環境下說的是刪除一個IDMComp文件夾就行了,不是IDMComputer文件夾,我記得我找到過一個叫這個的文件夾,結果還含有系統文件刪不掉。

    後來我發現,IDMComp就在Roaming目錄下C:\Users\Administrator\AppData\Roaming\IDMComp
或者C:\Documents and Settings\Administrator\AppData\Roaming\IDMComp兩種方法一樣的。

    就是刪除掉Roaming下的IDNComp文件夾,然後點回收站清空,搞定。重新打開你的UltraEdit,絕對OK!

[轉] linux中select()函數分析

Select在Socket編程中還是比較重要的,可是對於初學Socket的人來說都不太愛用Select寫程序,他們只是習慣寫諸如connect、accept、recv或recvfrom這樣的阻塞程序(所謂阻塞方式block,顧名思義,就是進程或是線程執行到這些函數時必須等待某個事件的發生,如果事件沒有發生,進程或線程就被阻塞,函數不能立即返回)。可是使用Select就可以完成非阻塞(所謂非阻塞方式non-block,就是進程或線程執行此函數時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函數的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,所以效率較高)方式工作的程序,它能夠監視我們需要監視的文件描述符的變化情況——讀寫或是異常。下面詳細介紹一下!

Select的函數格式(我所說的是Unix系統下的伯克利socket編程,和windows下的有區別,一會兒說明):

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

先說明兩個結構體:

第一,struct fd_set可以理解為一個集合,這個集合中存放的是文件描述符(file descriptor),即文件句柄,這可以是我們所說的普通意義的文件,當然Unix下任何設備、管道、FIFO等都是文件形式,全部包括在內,所以毫無疑問一個socket就是一個文件,socket句柄就是一個文件描述符。fd_set集合可以通過一些宏由人為來操作,比如清空集合FD_ZERO(fd_set *),將一個給定的文件描述符加入集合之中FD_SET(int ,fd_set *),將一個給定的文件描述符從集合中刪除FD_CLR(int ,fd_set*),檢查集合中指定的文件描述符是否可以讀寫FD_ISSET(int ,fd_set* )。一會兒舉例說明。

第二,struct timeval是一個大家常用的結構,用來代表時間值,有兩個成員,一個是秒數,另一個是毫秒數。

具體解釋select的參數:

int maxfdp是一個整數值,是指集合中所有文件描述符的範圍,即所有文件描述符的最大值加1,不能錯!在Windows中這個參數的值無所謂,可以設置不正確。

fd_set *readfds是指向fd_set結構的指針,這個集合中應該包括文件描述符,我們是要監視這些文件描述符的讀變化的,即我們關心是否可以從這些文件中讀取數據了,如果這個集合中有一個文件可讀,select就會返回一個大於0的值,表示有文件可讀,如果沒有可讀的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0,若發生錯誤返回負值。可以傳入NULL值,表示不關心任何文件的讀變化。

fd_set *writefds是指向fd_set結構的指針,這個集合中應該包括文件描述符,我們是要監視這些文件描述符的寫變化的,即我們關心是否可以向這些文件中寫入數據了,如果這個集合中有一個文件可寫,select就會返回一個大於0的值,表示有文件可寫,如果沒有可寫的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0,若發生錯誤返回負值。可以傳入NULL值,表示不關心任何文件的寫變化。

fd_set *errorfds同上面兩個參數的意圖,用來監視文件錯誤異常。

struct timeval* timeout是select的超時時間,這個參數至關重要,它可以使select處於三種狀態,第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,一定等到監視文件描述符集合中某個文件描述符發生變化為止;第二,若將時間值設為0秒0毫秒,就變成一個純粹的非阻塞函數,不管文件描述符是否有變化,都立刻返回繼續執行,文件無變化返回0,有變化返回一個正值;第三,timeout的值大於0,這就是等待的超時時間,即select在timeout時間內阻塞,超時時間之內有事件到來就返回了,否則在超時後不管怎樣一定返回,返回值同上述。

返回值:

負值:select錯誤 正值:某些文件可讀寫或出錯 0:等待超時,沒有可讀寫或錯誤的文件

在有了select後可以寫出像樣的網絡程序來!舉個簡單的例子,就是從網絡上接受數據寫入一個文件中。

例子:

main()

{

int sock;

FILE *fp;

struct fd_set fds;

struct timeval timeout={3,0}; //select等待3秒,3秒輪詢,要非阻塞就置0

char buffer[256]={0}; //256字節的接收緩衝區

/* 假定已經建立UDP連接,具體過程不寫,簡單,當然TCP也同理,主機ip和port都已經給定,要寫的文件已經打開

sock=socket(...);

bind(...);

fp=fopen(...); */

while(1)

{

FD_ZERO(&fds); //每次循環都要清空集合,否則不能檢測描述符變化

FD_SET(sock,&fds); //添加描述符

FD_SET(fp,&fds); //同上

maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1

switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用

{

case -1: exit(-1);break; //select錯誤,退出程序

case 0:break; //再次輪詢

default:

if(FD_ISSET(sock,&fds)) //測試sock是否可讀,即是否網絡上有數據

{

recvfrom(sock,buffer,256,.....);//接受網絡數據

if(FD_ISSET(fp,&fds)) //測試文件是否可寫

fwrite(fp,buffer...);//寫入文件

buffer清空;

}// end if break;

}// end switch

}//end while

}//end main
參考資料:http://cuijinbird.blogchina.com/cuijinbird/1921117.html 
Part 2:
select()的機制中提供一fd_set的數據結構,實際上是一long類型的數組,
每一個數組元素都能與一打開的文件句柄(不管是Socket句柄,還是其他
文件或命名管道或設備句柄)建立聯繫,建立聯繫的工作由程序員完成,
當調用select()時,由內核根據IO狀態修改fd_set的內容,由此來通知執
行了select()的進程哪一Socket或文件可讀,下面具體解釋:

#include <sys/types.h>
#include <sys/times.h>
#include <sys/select.h>

int select(nfds, readfds, writefds, exceptfds, timeout)
int nfds;
fd_set *readfds, *writefds, *exceptfds;
struct timeval *timeout;

ndfs:select監視的文件句柄數,視進程中打開的文件數而定,一般設為呢要監視各文件
中的最大文件號加一。
readfds:select監視的可讀文件句柄集合。
writefds: select監視的可寫文件句柄集合。
exceptfds:select監視的異常文件句柄集合。
timeout:本次select()的超時結束時間。(見/usr/sys/select.h,
可精確至百萬分之一秒!)

當readfds或writefds中映像的文件可讀或可寫或超時,本次select()
就結束返回。程序員利用一組系統提供的宏在select()結束時便可判
斷哪一文件可讀或可寫。對Socket編程特別有用的就是readfds。
幾隻相關的宏解釋如下:

FD_ZERO(fd_set *fdset):清空fdset與所有文件句柄的聯繫。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd與fdset的聯繫。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd與fdset的聯繫。
FD_ISSET(int fd, fdset *fdset):檢查fdset聯繫的文件句柄fd是否
可讀寫,>0表示可讀寫。
(關於fd_set及相關宏的定義見/usr/include/sys/types.h)

這樣,你的socket只需在有東東讀的時候才讀入,大致如下:

...
int sockfd;
fd_set fdR;
struct timeval timeout = ..;
...
for(;;) {
FD_ZERO(&fdR);
FD_SET(sockfd, &fdR);
switch (select(sockfd + 1, &fdR, NULL, &timeout)) {
case -1:
error handled by u;
case 0:
timeout hanled by u;
default:
if (FD_ISSET(sockfd)) {
now u read or recv something;
/* if sockfd is father and
server socket, u can now
accept() */
}
}
}

所以一個FD_ISSET(sockfd)就相當通知了sockfd可讀。
至於struct timeval在此的功能,請man select。不同的timeval設置
使使select()表現出超時結束、無超時阻塞和輪詢三種特性。由於
timeval可精確至百萬分之一秒,所以Windows的SetTimer()根本不算
什麼。你可以用select()做一個超級時鐘。

FD_ACCEPT的實現?依然如上,因為客戶方socket請求連接時,會發送
連接請求報文,此時select()當然會結束,FD_ISSET(sockfd)當然大
於零,因為有報文可讀嘛!至於這方面的應用,主要在於服務方的父
Socket,你若不喜歡主動accept(),可改為如上機制來accept()。

至於FD_CLOSE的實現及處理,頗費了一堆cpu處理時間,未完待續。

--
討論關於利用select()檢測對方Socket關閉的問題:

仍然是本地Socket有東東可讀,因為對方Socket關閉時,會發一個關閉連接
通知報文,會馬上被select()檢測到的。關於TCP的連接(三次握手)和關
閉(二次握手)機制,敬請參考有關TCP/IP的書籍。

不知是什麼原因,UNIX好像沒有提供通知進程關於Socket或Pipe對方關閉的
信號,也可能是cpu所知有限。總之,當對方關閉,一執行recv()或read(),
馬上回返回-1,此時全局變量errno的值是115,相應的sys_errlist[errno]
為"Connect refused"(請參考/usr/include/sys/errno.h)。所以,在上
篇的for(;;)...select()程序塊中,當有東西可讀時,一定要檢查recv()或
read()的返回值,返回-1時要作出關斷本地Socket的處理,否則select()會
一直認為有東西讀,其結果曾幾令cpu傷心欲斷針腳。不信你可以試試:不檢
查recv()返回結果,且將收到的東東(實際沒收到)寫至標準輸出...
在有名管道的編程中也有類似問題出現。具體處理詳見拙作:發佈一個有用
的Socket客戶方原碼。

至於主動寫Socket時對方突然關閉的處理則可以簡單地捕捉信號SIGPIPE並作
出相應關斷本地Socket等等的處理。SIGPIPE的解釋是:寫入無讀者方的管道。
在此不作贅述,請詳man signal。

以上是cpu在作tcp/ip數據傳輸實驗積累的經驗,若有錯漏,請狂炮擊之。

唉,昨天在hacker區被一幫孫子轟得差點兒沒短路。ren cpu(奔騰的心) z80

補充關於select在異步(非阻塞)connect中的應用,剛開始搞socket編程的時候
我一直都用阻塞式的connect,非阻塞connect的問題是由於當時搞proxy scan
而提出的呵呵
通過在網上與網友們的交流及查找相關FAQ,總算知道了怎麼解決這一問題.同樣
用select可以很好地解決這一問題.大致過程是這樣的:

1.將打開的socket設為非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完
成(有的系統用FNEDLAY也可).

2.發connect調用,這時返回-1,但是errno被設為EINPROGRESS,意即connect仍舊
在進行還沒有完成.

3.將打開的socket設進被監視的可寫(注意不是可讀)文件集合用select進行監視,
如果可寫,用
getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));
來得到error的值,如果為零,則connect成功.

在許多unix版本的proxyscan程序你都可以看到類似的過程,另外在solaris精華
區->編程技巧中有一個通用的帶超時參數的connect模塊.

2013年11月12日 星期二

C - Function Pointer


今天在研究 C Function Pointer 的部分,原本有些誤解,後來查了一些資料後,終於釐清 Function Pointer 的觀念了!

Function Pointer 顧名思義,就是指向 Function 的指標

在 C 語言中,不論是 variable、array、struct、或是 function(一段程式碼),都有所屬的啟始記憶體位置

由此可知,main function 也是有其啟始記憶體位置。

而 function pointer 的宣告跟使用 function 時所要注意的地方是相同的,有以下幾點必須注意:
  1. 回傳值型態(return type)
  2. 參數數量(augument count)
  3. 參數型態(argument type)

以下直接用一個簡單範例來說明 function pointer 的使用:
#include <stdio.h>

//function宣告 
int doAdd(int, int);
int doMinus(int, int);

int main(void) {
   //宣告 function pointer
   //注意所設定的參數數量與型態
   int (*my_func_ptr)(int, int);

   //function pointer 指向 doAdd
   my_func_ptr = doAdd;
   printf("function pointer 指向 doAdd => %d\n", (*my_func_ptr)(5, 3));    //結果:8

   //function pointer 指向 doMinus
   my_func_ptr = doMinus;
   printf("function pointer 指向 doMinus => %d\n", (*my_func_ptr)(5, 3));  //結果:2 

   return 0;
}   //end main


int doAdd(int a, int b) {
   return a + b;
}   //end doAdd

int doMinus(int a, int b) {
   return a - b;
}   //end doMinus
從上面的範例可以看出,doAdd() 與 doMinus() 兩個 function 的回傳值型態、參數數量、參數型態都是相同的,只是名稱不同而已。

而名稱不同,卻不影響 function pointer 的使用,因為我們所用的是 function 的啟始記憶體位置

而 function pointer 的使用,有一點相當重要,即是 function pointer 的宣告;而 function pointer 的宣告,即是要注意到「回傳值型態」、「參數數量」、「參數型態」這三個部分。

當 function pointer 的宣告完成後,另外一個需要注意的就是每個 function 的啟始記憶體位置;而每個function 的啟始記憶體位置,即為 function 的名稱

在程式中,就是透過將 function pointer 指向不同 function 的啟始記憶體位置,來執行不同的 function。

2013年11月6日 星期三

第10章 ARM與DSP的架構與流程

http://www.docin.com/p-49044186.html

bss,data,text,rodata,heap,stack,常量段

bss段:
BSS段(bsssegment)通常是指用來存放程序中未初始化的全局變量的一塊內存區域BSS是英文BlockStarted by Symbol的簡稱。BSS段屬於靜態內存分配。
data段:
數據段(datasegment)通常是指用來存放程序中已初始化的全局變量的一塊內存區域。數據段屬於靜態內存分配。
text段:
代碼段(codesegment/textsegment)通常是指用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經確定,並且內存區域通常屬於只讀,某些架構也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。
rodata
存放C中的字符串和#define定義的常量
heap堆:
堆是用於存放進程運行中被動態分配的內存段,它的大小並不固定,可動態擴張或縮減。當進程調用malloc等函數分配內存時,新分配的內存就被動態添加到堆上(堆被擴張);當利用free等函數釋放內存時,被釋放的內存從堆中被剔除(堆被縮減)
stack棧:
是用戶存放程序臨時創建的局部變量,也就是說我們函數括弧「{}」中定義的變量(但不包括static聲明的變量,static意味著在數據段中存放變量)。除此以外,在函數被調用時,其參數也會被壓入發起調用的進程棧中,並且待到調用結束後,函數的返回值也會被存放回棧中。由於棧的先進先出特點,所以棧特別方便用來保存/恢復調用現場。從這個意義上講,我們可以把堆棧看成一個寄存、交換臨時數據的內存區。

常量段:
常量段一般包含編譯器產生的數據(與只讀段包含用戶定義的只讀數據不同)。比如說由一個語句a=2+3編譯器把2+3編譯期就算出5,存成常量5在常量段中

一般情況下,一個程序本質上都是由 bss段、data段、text段三個組成的——本概念是當前的計算機程序設計中是很重要的一個基本概念。而且在嵌入式系統的設計中也非常重要,牽涉到嵌入式系統運行時的內存大小分配,存儲單元佔用空間大小的問題。
在採用段式內存管理的架構中(比如intel的80x86系統),bss段(Block Started by Symbol segment)通常是指用來存放程序中未初始化的全局變量的一塊內存區域,一般在初始化時bss 段部分將會清零(bss段屬於靜態內存分配,即程序一開始就將其清零了)。
比如,在C語言程序編譯完成之後,已初始化的全局變量保存在.data 段中,未初始化的全局變量保存在.bss 段中。   
l          text和data段都在可執行文件中(在嵌入式系統裡一般是固化在鏡像文件中),由系統從可執行文件中加載;
l          而bss段不在可執行文件中,由系統初始化

編譯兩個小程序如下:
程序1:
int ar[30000];
void main()
{
    ......
}

程序2:
int ar[300000] =  {1, 2, 3, 4, 5, 6 };
void main()
{
    ......
}
    發現程序2編譯之後所得的.exe文件比程序1的要大得多。 為什麼?
區別很明顯,一個位於.bss段,而另一個位於.data段,兩者的區別在於
l          全局的未初始化變量存在於.bss段中,具體體現為一個佔位符;全局的已初始化變量存於.data段中;
l          而函數內的自動變量都在棧上分配空間。
l          .bss是不佔用.exe文件空間的,其內容由操作系統初始化(清零);
l          而.data卻需要佔用,其內容由程序初始化,因此造成了上述情況。

注意:
l          bss段(未手動初始化的數據)並不給該段的數據分配空間,只是記錄數據所需空間的大小。
l          data(已手動初始化的數據)段則為數據分配空間,數據保存在目標文件中。
l          DATA段包含經過初始化的全局變量以及它們的值。
l          BSS段的大小從可執行文件中得到,然後鏈接器得到這個大小的內存塊,緊跟在數據段後面。當這個內存區進入程序的地址空間後全部清零。包含DATA和BSS段的整個區段此時通常稱為數據區。

Program / Process / Thread 的差別

Program:放在二次儲存裝置中,尚沒有被Load到記憶體的一堆Code
         稱之為「程式」。  (也就是還是死的)


Process:已經被Load到記憶體中,任何一行Code隨時會被CPU執行,且其宣告的在記憶體
         的變數的值會隨著需求而不斷變動。
         稱之為「程序」。 (也就是活的Program) => 恐龍本第三章
         一個多工作業系統(Multitasking Operating System)可以同時運行多個Process
         然而一個CPU一次只能做一件事情,但CPU的數量永遠少於運行中的Process數,
         因此每個Process使用的時間需要被排程(Scheduling) => 恐龍本第五章
         又每個Process間在記憶體中,如果擺放的方式不當,就會在記憶體中產生很多
         沒辦法用到的碎片,因此MemoryManagement是一個問題 => 恐龍本第八章
         另外,每個Process所需要的記憶體總合,也可能大於實體記憶體,因此需要另
         外用二次儲存裝置充當虛擬記憶體(Virtual Memory),但是二次儲存裝置的速
         度肯定很慢,因此如何做到對虛擬記憶體最小的依賴,盡量避免Page Fault(電
         腦在主記憶體中找不到資料,而要去二次記憶體找,就稱為Page Fault)
         防止Thrashing的發生(因為Virtual Memory演算法不當,造成幾乎每次存取都要
         依賴二次記憶體,就是Thrashing),以達到效能最佳化,也是個學問 => 第九章




Thread :在同一個Process底下,有許多自己的分身,就是Thread,中文又翻成執行緒。
         以往一個Process一次只能做一件事情,因此要一面輸入文字,一面計算字數,
         這種事情是不可能的。但是有了Thread之後,可以在同一個Process底下,讓輸
         入文字是一個Thread,計算文字又是另外一個Thread,對CPU來說兩個都是類似
         一個Process,因此兩個可以同時做。
         又一個Process底下有數個Thread,而一個Process的Global Variable可以讓
         它的所有Thread共享,也就是所有Thread都可以存取同一個Process的Global
         Variable。而每個Thread自己也有自己的專屬Variable。 => 恐龍本第四章
         但是,如果有兩個Thread要存取同一個Global Variable,有可能發生問題,
         也就是說可能會存取到錯的值(例如兩個Thread同時要對一個Variable做加減,
         最後那個答案可能會是錯的),這就是Synchronization問題 =>恐龍本第六章
         又,每一個Thread之間可能會互搶資源,而造成死結(Deadlock),只要以下四
         個條件都滿足就有死結。(1)這個資源不能同時給兩個人用 (2)有一個人拿了一
         個資源,又想拿別人的資源 (3)如果一個人占了茅坑不拉屎,占用資源很久,仍
         不能趕他走 (4)A等B,B等C,C等D,D又等A 等成一圈。 要解決這種狀況有
         Avoid(預防) 或 避免(Prevent)兩種方式,破除以上四種其中一種即可。
         => 恐龍本第七章

2013年11月5日 星期二

認識 IRQ 及資源分配問題

常見的問題 
  你要求任何一個電腦技術人員列舉所有常見的電腦問題,其中資源(PC' system resources,電腦系統資源)衝突定必是這個問題清單的頭一二位。這些令人煩惱的衝突主要是由於電腦系統資源不足而最終導致死機、間歇性系統故障及資料流失等情 況。 
  要解決這些問題,其中一個要辦法就是從IRQ 〈Interrupt Request Lines,中斷請求線)入手。當然,你要知道甚麼是 IRQ 及 IRQ 如何運作。認識以後,便能一步步地解決 IRQ 的衝突及電腦系統的問題。 

甚麼是電腦系統資源 
  電腦系統資源可被理解成為電腦系統內的一部份,但不是指物理層次上的(因為我們眼不能見,手也觸摸不到),而是概念上的。這些資源對電腦系統非常重要,必須小心地分配給系統內各驅動器使用。這些資源的主要用途有二:第一,用作驅動器之間的溝通及訊息傳送;第二,用作管理各驅動器如何存取記憶體。某些電腦資源是供不應求的, 換句話說,不斷增加電腦系統的週邊設備,只會令這些資源更顯缺乏,無法滿足所有設備的要求。這些資源主要有四種,第一當然是IRQ,其餘三個分別是直接記憶體取存通道(Direct Memory Access (DMA) Channels)、輸入輸出地址 (Input/output(I/O)Addresses)及記憶體地址 ( Memory Address)。 

多工處理 
  中央處理器是電腦系統的心臟,本是被設計成為只能在一個時間處理一個工作。但大家的經驗都是,我們可以同時要求電腦處理超過一個工作,特別是使用到一些能夠多工處理(multitasking 〉的操作系統(operating system),例如WIN95或以上等,我們確是可以一邊下載軟件、一邊聽音樂、一 邊使用文書處理器。是何道理呢?那是因為處理器 不停地快速轉換工作而造成的錯覺,或是速度之高使我們未能察覺。最終,處理器還是只能"專注地工作"。 
  對於電腦各部份不停地發出對處理器的要求又如何排遣?就好像地方各區不斷地要求中央政府工作,怎樣的安排才能平衡各界呢?這就是中斷(intcrrupt)的基本概念。 
當一個裝置要求做資料的傳輸,它就會發出一個中斷訊號給處理器,比方說:“我需要你的注意。”這時處理器就會停止手頭上的工作,處理新的要求。 

中斷的運作 
  電腦各裝置就是利用IRQ產生中斷訊號要求被處理器注意。每一個裝置會有一個或更多的IRQ。當裝置要求被注意時,它就會將中斷訊號放進IRQ,然後中斷控制器(interrupt controller,下稱控制器)會識別這些中斷訊號並將之傳送到處理器。 控制器同時會告訴處理器這些訊號的優先次序,好讓處理器再安排工作,這叫做“中斷服務”。 

IRQ分配至控制器 
  最早期的個人電腦,只有一個控制器,管理八個IRQ。後來、IBM於1984年在PC/AT的機種中加多一個控制器,為連接起兩個控制器,第二個控制器的記號將會透過IRQ2連接到第一個控制器,至於原使用IRQ2的訊號則會在主機板上“被導向”改用IRQ9,此格式一直沿用至今。所以今日電腦最多有16個lIRQ,平均分配給兩個控制器負責。即第一個控制器負責IRQ0至IRQ7,第二個控制器負責IRQ8至IRQ15。 

IRQ的優先次序 
  不同IRQ的用途與優先次序使得電腦系統內的不同裝置不重復地產生中斷訊號。各IRQ的優次由高至低分別是0、1、8、9、 10、 11、12、 13、 14、 15、3、4、5、6及7。 
  因為第二個控制器使用IRQ2傳送訊號至第一個控制器,所以它所負責的IRQ8至IRQ15的優次介乎IRQ1至IRQ3中間。 

IRQ的分享 
  傳統的中斷訊號是單一裝置所發出。因為系統總線(system bus)的設計所限,多於一個裝置同時使用同一條IRQ是不可能的。這只會令處理器混亂造成錯誤的回應。不過,多於一個裝置分享同一條IRQ在有限制的條件下卻是可能的,例如兩個裝置很少使用或同時使用,實例是電腦系統內的四個通訊連接端口(communications port,COM)共分享二個IRQ(IRQ4及IRQ3〉。但這做法只會傾向產生問題多於解決問題,不是最好的。新一代的電 腦,多個週邊元件互連(Peripheral Component Interconnect,PCI)透過PCI總線及個別元件的控制器,則能夠有效他分享IRQ避免衝突。 

IRQ的工作 
  因為IRQ的數量有限,所以IRQ的工作分配一定要清晰及準確。正常情況下,每一個IRQ都有內設或標準的用途,其中部份的IRQ是保留給電腦系統內部使用,包括IRQO、1、8及13。而大家不是善忘的話,該記得IRQ2是已不復存在的了。 

更改IRQ的設定 
  某些週邊器材會指定使用哪一條IRQ及其用途,是無法更改的。但其餘大部份的IRQ仍可按用戶需要更改選擇,避免衝突。以下就是其中五個可使用的方法: 
a. 更改硬件設定。 
某些較舊的硬件可透過設定跨接線(jumpers)及開關(switch)更改IRQ的選擇,但由於此方法並不方便、所以新的硬件已不備有這個功能。 
b. 使用配置程式。 
不少新的裝置會備有個別獨特的配置程式,用戶可以透過這些程式更改IRQ的選擇。 
c. 使用視窗的裝置管理員。 
有些裝置是可以透過視窗的裝置管理員更改資源運用。 
d. 隨插隨用。 
在備有隨插隨用特性的操作系統及特定的裝置,用戶或可在安裝時選擇IRQ的安排。 
e. 檢查系組內IRQ的使用。 
視窗95或以上的操作系統,可以簡單地檢查關於IRQ的使用分配。 用戶可循以下途徑找到: 控制台→系統→裝置管理員→內容(選取電腦的情況下)→確定是IRQ的選項。 

衝突與病徵 
  現在我們算是初步認識了IRQ,接著就是認識關於IRQ的衝突。當兩個或以上的裝置嘗試同時使用同一個資源時就會發生資源衝突。而當上述所指的資源是IRQ時,就是IRQ衝突了。有些衝突是容易被識別,但有些衝突因為故障的出現不直接甚至出現一些不似是由裝置問題造成的“症狀”,故難以被發現及更正,以下就是部份常見的“病徵”: 
1. 當使用某些裝置時“當機”; 
2. 音效卡出現雜音; 
3. 打印時輸出不正確或出現其他圖文; 
4. 滑鼠的指標拒絕移動或出現“口吃”; 
5. 視窗顯示錯誤訊息又或突然以安全模式(safe mode)運作; 
6. 應用程式衝突並沒有提供原因; 
7. 新的週邊加入以後,電腦系統出現奇怪行徑。 
  當然發生IRQ衝突時會出現以上情況,但有以上情況的又未必一定是IRQ衝突。而且“病發”與被病毒感染的情況很相似,所以當你懷疑是電腦資源衝突時,宜先檢查電腦系統內有沒有病毒。 

如何解決衝突呢 
IRQ衝突通常是意外的錯誤設定所造成。所以要解決衝突,理論上,就只是以下二個簡單的步驟: ヾ 
a. 檢視lIRQ與裝置之間的合作情況; ヾ 
b. 確定哪個是衝突的裝置及 ヾ 
c. 改變資源設定,解決衝突。 
  第一步可以按上文所示從控制台內的系統檢視。第二步就需要做一點研究的功夫。除了解IRQ的使用情況外,用戶可以到裝置管理員,有沒有裝置出現“黃圈內的感嘆號”,那些裝置就是最大的“嫌疑犯”。用戶當然可以請教朋友,哪些是常見的資源衝突。其中IRQ2、3、4、5、7、9、 12及15是比較多出現問題的。最後,當然是更改資源設定。但這不是易做而且冒險的工作,有些裝置是不容許你更改設定的,你要首先取消“使用自動的設定”一項的選擇。以下是一些解決衝突的意見,不妨參考參考: 
a. 使用一些電腦系統的診斷工具,例如Norton Diagnostics,這些軟件會提供資源運用的分析。 但這並不是代表完美的解決方法。 ヾ 
b. 如上文所述,IRQ2與IRQ9最好不要同時使用,或當作是一個IRQ看待。 ヾ 
c. 新增Modem同時而系統又擁有COM2,衝突就會出現,除非更改Modem的設定。如果只是單單由 COM2改用COM 4、問題仍未解決,必須從IRQ 著手。 
d. 音效卡與第二個並聯端口(second parallel port) 內設同是使用IRQ5,所以必先更改其中一個設定。小心別將第一個並聯端口改用IRQ5。 

2013年11月1日 星期五

C-反轉鏈結串列(單鏈)

C-反轉鏈結串列(單鏈)
因為每次都要重新推
所以乾脆PO在這邊好了
這個用的演算法是直接一個一個更改"下一個的指標(ptr->nl)"來達成
至於各個步驟詳解
等我想不開了再來製作
struct listNode
{
    data a;
    listNode *nl;  //nl:Next Link
}
listNode * Inverse(listNode *str)//鏈結串列反轉範例  str 為鏈結串列的起始端
{
    listNode *ptr=str,*tmp=NULL;  // tmp儲存"上一個的位置"
    while(ptr->nl!=NULL)
    {
        str=ptr->nl;
        ptr->nl=tmp;
        tmp=ptr;
        ptr=str;
    }
    ptr->nl=tmp;
    return str;
}

2013年10月30日 星期三

面試題目分享

從4/1以來, 面試過不少公司, 還記得一些面試的考題, 在此分享給有需要的人

1. 給定一個single linked list, 如下 A-->B-->C-->D-->.....
現在給你B的pointer, 要如何讓 linked list 變成 A-->C-->D-->... 跳過B
註: 沒有給你A的pointer

2. 給一個十進位 string 如: 566 轉成 integer.
寫一個function 可以做這件事, 類似 atoi, 要考慮字串前面有負號 "-"
提示: 不用ascii table, 用literal char '0'

3. 給兩個 single linked lists A and B, 要怎樣判斷這兩個list有相交 intersection.
請給出computation complexity order, 有更快的方法嗎? 請多想幾個.

4. 請寫出一個function, 可以把一個 single linked list 反轉
例如: A-->B-->C--->D 改成 D-->C-->B-->A

5. 請寫出一個function, 可以反轉字串, 例如輸入book, 可以輸出 koob
請寫出一個function, 可以反轉句子, 例如 this is a book, 輸出 book a is this
方法應該有很多, 有沒有什麼好方法省記憶體, 速度又快?

6. 請寫出一個function, 可以得知binary tree的深度depth.

7. 請寫出一個function, 可以判斷字串是否對稱
如 "aasddsaa", "asa" 是對稱, "aabbcc" 不對稱

8. 請寫出一個function, 可以把一個二維的image buffer, 做順時鐘旋轉90度
參考prototype: void rotate(int w, int h, unsigned char* src, unsigned char* dst)

9. 請寫一個function計算:
有一個n個階梯的梯子, 假如每次只能往上1或者2階, 會有幾種不同的組合?

10. 請寫一個function, 計算二進位數中, 1的個數, complexity愈低愈好, 能想出多種解法更好.

11. 有兩個threads, A, and B. 他們的thread body 如下
global int sum = 0;
void threadProc(void) { int c = 100; while(c--) { sum ++; } }
請問 sum 的最大值與最小值是多少, 為什麼?

12. 請寫一個function, 可以對一個 double linked list 做 bubble sorting

13. 給一個正整數數列, 此數列滿足以下條件:
a. 未排序
b. 此數列可排序為連續整數
c. 此數列中有一個數有重複
請想個方法可以快速有效的找出重複的數
例如: [2, 5, 4, 3, 6, 2, 7], 此例中為 2

大家可以動動腦:D

2013年10月5日 星期六

Ubuntu 11.10 手動安裝 Sun Java 6 JDK


手動安裝JDK


sudo apt-get install python-software-properties
sudo add-apt-repository ppa:ferramroberto/java
sudo apt-get update
sudo apt-get install sun-java6-jdk

==================================================
手動安裝JRE


sudo apt-get install python-software-properties
sudo add-apt-repository ppa:ferramroberto/java
sudo apt-get update
sudo apt-get install sun-java6-jre sun-java6-plugin sun-java6-fonts

Wallpaper

http://foundwalls.com/

2013年10月1日 星期二

ISR之不能做什麼


  1.7 __interrupt double isr(double r)
      {
         double area = PI*r*r ;
         printf("%f\n",area) ;
         return area ;
      }
      說明並解釋上述之interrupt service routine 之錯誤處?


中斷嵌入式系統中重要組成部分,很多編譯器開發商都讓標準c支持中斷,並引入關鍵字_interrupt.但是:
1、ISR不能有返回值
2、ISR不能傳遞參數;
3、ISR應該是短而高效的,在ISR中做浮點運算是不明智的;
4、ISR中不應該有重入和性能上的問題,因此不應該使用pintf()函數。




1.ISR 不能有返回值。為什麼?
2.ISR 不能傳遞參數。為什麼? 

裸奔的系統:硬件中斷響應程序的運行插入時機是隨機的,程序中不存在這樣的調用語句:「value=interrupter( )」, 所以,即使有返回值也不知返回給誰。  同理,如果中斷函數有形參,但因沒有調用者,也就沒有實參對形參賦值。所以,不可能有參數傳遞。
裸奔系統中,中斷程序由硬件觸發執行。這意味著中斷函數沒有具體的調用者,所以,中斷函數無法將值返回給任何對象

  非裸奔系統:操作系統需要進行各種調度安排,所以接管了中斷的入、出口;另外,還增加了許多軟件中斷。這些中斷函數的運行插入時機已經不再是隨機了。一個中斷申請發生後,其運行時機取決於操作系統的確定安排和調用。也就是說,有了調用者,所以可以有返回值和參數傳遞。

2013年9月27日 星期五

C++ 入門指南 - 封裝

http://pydoing.blogspot.tw/2012/10/cpp-encapsulation.html

Loda's blog

http://loda.hala01.com/

米歐的學習筆記

http://miox.cc/

AT Command

http://miox.cc/search/label/AT%20COMMAND

[轉] Android Toolchain與Bionic Libc


Android所用的Toolchain(即交叉編譯工具鏈)可從下面的網址下載:
http://android.kernel.org/pub/android-toolchain-20081019.tar.bz2。如果下載了完整的Android項目的源代碼,則可以在「/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin」目錄下找到交叉編譯工具,比如Android所用的arm-eabi-gcc-4.2.1。
Android並沒有採用glibc作為C庫,而是採用了Google自己開發的Bionic Libc,它的官方Toolchain也是基於Bionic Libc而並非glibc的。這使得使用或移植其他Toolchain來用於Android要比較麻煩:在Google公佈用於Android的官方Toolchain之前,多數的Android愛好者使用的Toolchain是在http://www.codesourcery.com/gnu_toolchains/arm/download.html 下載的一個通用的Toolchain,它用來編譯和移植Android 的Linux內核是可行的,因為內核並不需要C庫,但是開發Android的應用程序時,直接採用或者移植其他的Toolchain都比較麻煩,其他Toolchain編譯的應用程序只能採用靜態編譯的方式才能運行於Android模擬器中,這顯然是實際開發中所不能接受的方式。目前尚沒有看到說明成功移植其他交叉編譯器來編譯Android應用程序的資料。
與glibc相比,Bionic Libc有如下一些特點:
- 採用BSD License,而不是glibc的GPL License;
- 大小只有大約200k,比glibc差不多小一半,且比glibc更快;
- 實現了一個更小、更快的pthread;
- 提供了一些Android所需要的重要函數,如」getprop」, 「LOGI」等;
- 不完全支持POSIX標準,比如C++ exceptions,wide chars等;
- 不提供libthread_db 和 libm的實現
另外,Android中所用的其他一些二進制工具也比較特殊:
- 加載動態庫時使用的是/system/bin/linker而不是常用的/lib/ld.so;
- prelink工具不是常用的prelink而是apriori,其源代碼位於」 /build/tools/apriori」
- strip工具也沒有採用常用的strip,即「/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin」目錄下的arm-eabi-strip,而是位於/out/host/linux-x86/bin/的soslim工具。


Android Code Review Gerrit

https://android-review.googlesource.com/#/q/status:open,n,z

2013年9月25日 星期三

何謂udev?

udev 是Linux kernel 2.6系列的裝置管理器。它主要的功能是管理/dev目錄底下的裝置節點。它同時也是用來接替devfs及hotplug的功能,這意味著它要在添加/刪除硬體時處理/dev目錄以及所有使用者空間的行為,包括載入firmware時。

udev的最新版本依賴於升級後的Linux kernel 2.6.13的uevent介面的最新版本。使用新版本udev的系統不能在2.6.13以下版本啟動,除非使用noudev參數來禁用udev並使用傳統的/dev來進行裝置讀取



概要

在傳統的Linux系統中,/dev目錄下的裝置節點為一系列靜態存在的檔案,而udev則動態提供了在系統中實際存在的裝置節點。雖然devfs提供了類似功能,udev的支援者也給出了很多udev實作比devfs好的理由[1]
  • udev支援裝置的固定命名,而並不依賴於裝置插入系統的順序。預設的udev設定提供了儲存裝置的固定命名。任何硬碟都根據其唯一的檔案系統id、磁碟名稱及硬體連線的物理位置來進行識別。
  • udev完全在使用者空間執行,而不是像devfs在核心空間一樣執行。結果就是udev將命名策略從核心中移走,並可以在節點創建前用任意程式在裝置屬性中為裝置命名。

執行方式

udev是一個通用的核心裝置管理器。它以守護行程的方式執行於Linux系統,並監聽在新裝置初始化或裝置從系統中移除時核心(透過netlink socket)發出的uevent。
系統提供了一套規則用於匹配可發現的裝置事件和屬性的匯出值。匹配規則可能命名並創建裝置節點,並執行配置程式來對裝置進行設定。udev規則可以 匹配像核心子系統、核心裝置名稱、裝置的物理等屬性,或裝置序列號的屬性。規則也可以請求外部程式提供資訊來命名裝置,或指定一個永遠一樣的自訂名稱來命 名裝置,而不管裝置什麼時候被系統發現。

Linux 驅動程式觀念解析

http://www.jollen.org/blog/2006/05/

touch panel驅動程式實做

http://zh.scribd.com/doc/19234558/touch-panel

Android Booting

http://elinux.org/Android_Booting

The Android boot process from power on

http://www.androidenea.com/2009/06/android-boot-process-from-power-on.html

Android/Linux Source Code Reference

http://hala01.com/
http://androidxref.com/
https://android.googlesource.com/

2013年9月24日 星期二

stack vs heap:執行時期儲存兩大要角


現代電腦系統大多依照Von Neumann Architecture設計而成,其中一特色stored programming乃指『程式執行一定要將欲執行的指令跟資料放入記憶體方可執行』,由此可知執行過程中記憶體所佔的地位厥偉之處。但許多工程師卻搞不清楚記憶體中的stack跟heap space到底有何居別,下面簡單針對兩者加以論述,希望對讀者有所幫助~

三分天下。程式執行過程中其實主要分成三大區塊:global、stack、heap三塊。其中global區塊最最易理解,主要存放全域變數或宣告為static的靜態變數在此就不多做贅述;另外兩個區塊分別為stack跟heap這兩者往往混淆不清,尤其在java中有時候會出現stack overflow或heap overflow到底兩者差異在哪,若工程師連這都不清楚那以後怎麼去調整JVM中的heap memory space跟stack memory space的大小呢?


貼心的系統全自動化管理區塊:Stack Memory Space!在記憶體中不外乎就是要存放變數、函式相關資訊等資料,使運作過程可以順利取得所需的變數或函式所在地。要讓系統可以全自動化管理,代表需可被預期此變數或函數資訊的生命週期,一旦完全可預測代表可以安心的交由系統管理,這些資訊也將在執行過程中被存放在stack空間。Stack中常見的存放資訊如下:區域變數(local variable)、函式參數(function/method parameter)、函數的返回位址(function/method return address)等資訊。為何上述資訊會放於stack之中,簡單來說:

void method1() {

  int x = 100;

}

上述的int x = 100,系統會在stack中找一個區塊給x,另外裡面的內容為100。然而,x會被放入stack主要是因為在編譯時期系統已經可以預知x從何時開始配置跟何時結束回收(當然就是看所屬block結束就跟著回收),由於配置跟回收的規則明確,當然就往stack擺囉。

在舉一例子:

void method2() {

  method1();

}

上述當呼叫method1()時,系統會先把method2的返回位址存到stack當中,為何是存放在stack呢,因為函式的呼叫有後進先出的概念,當method1()被呼叫而開始執行,待結束時必定會查找該返回何處,故最後一定會讀取函式的返回位址,既然如此明確而有條理,當然也是往stack放!

可預測性外加後進先出的生存模式,令stack無疑是最佳的存放策略。由於程式語言中變數跟函式的生命週期皆為後進先出的概念,也就是越晚產生的會越先被回收或銷毀。正因如此只要是可預測性的相關資訊都是往stack存放。此外,由於stack中的資料之存活週期規律故由系統自行產生與回收其空間即可,就不勞工程師們費心啦!

天啊!程式中竟然有不可預測其存活時間的資料存在。在程式中,有部分的需求總是在執行中依據實際情況才會動態增減,這些資訊是難以被預測哪時候開始有?量有多少?何時該回收?…這些不可預測的因素造成上述的stack區塊不適合運用於此。當資訊為動態配置產生,系統會存放在另外一塊空間,稱之為『Heap』(注意這裡的Heap跟資料結構中的Heap不相關,可別會錯意!)。Heap的區塊專收執行期間動態產生的資料,由於為動態產生故結束點無法由系統來掌握,故需使用者自行回收空間。在C++或Java中利用new語法產生的就是動態配置的物件,需存放於heap中。

奇怪跑越久記憶體用越多的怪現象。許多時候執行的程式都沒有改變,但卻常出現隨時間執行越久程式所耗用的空間將越多,最後造成out of memory。工程師也不知為何如此,就是定期在out of memory之前restart程式即可。這中現象層出不窮,一般大多是因為工程師沒有正確將記憶體回收所導致。Heap中的資料如果沒有正常的回收,將會逐步成長到將記憶體消耗殆盡,下次發生上述問題的實後,切記自己檢查一下heap空間的資料有無正常回收。論述到此有些讀者可能會覺得納悶:為何在寫Java都不需要注意回收空間的問題?~答案是因為Java中會採用Garbage Collection(垃圾回收)的機制自動檢查Heap中哪些資料已經沒有被使用,當確認資料已經沒有使用會自動將空間回收,如此工程師就專注撰寫程式即可,不用擔心記憶體回收不當等問題。

The conclusion is…。當產生stack overflow一般是因為過多的函式呼叫(例如:遞迴太深)、或區域變數使用太多,此時請試著將stack size調大一點,另外檢查看看函式的呼叫跟變數的使用量。反之,當發生heap overflow請檢查是否都有正確將heap space的資料回收,另外採行的動態配置是否合理,不要過渡濫用而new出無謂的空間,若真的是程式過於複雜造成,請將heap size調大一些。

程式的撰寫,其實跟製造業的加工廠很相似,套句郭董常說的:『魔鬼藏在細節中』~~

2013年9月23日 星期一

TI DM320/270 DSP+ARM處理器的解決方案

  Ti通過與Ingenient合作,提供了最成熟的MP4設計方案。愛可視推出的世界上第一款MP4產品——愛可視 Jukebox便是採用的Ingenient方案。目前,包括愛可視、iRiver、微星等眾多著名廠商都採用了Ingenient的方案。
硬解碼才是王道即拖即放型閃存MP4導購(2)
圖:IIngenien技術公司基於TI DM2X及DM3X的多媒體硬件參考設計
  Ti的解決方案採用ARM處理器搭載DSP的方式,利用DM320/270 DSP芯片進行音視頻編解碼,ARM處理器負責系統以及支持外圍設備接口。TI DM320為純DSP芯片,因此必須配合ARM處理器才能組成完整的解決方案。該方案所支持的媒體類型非常豐富,包括MPEG4 SP/ASP、pX、MPEG1/2、WMV、WMA、QuickTime 6、H.264、AAC-LC、MP3等格式。其中除了H.264格式外的分辨率只有CIF(352×288)的水平外,其它均可實現D1分辨率(720×576)視頻(30fps)文件的實時解碼。另外,此方案的編碼能力也是相當強大,DM320方案錄製MPEG4 SP視頻可達到D1的質量(30fps),而DM270方案可錄製VGA(640×480)質量的視頻。該方案更可具備PVR的功能(即錄製和播放可雙工同時進行)。
  所有Ti方案均可支持USB2.0 OTG接口、支持HDD、SD、CF、MS等。
優點:
支持的媒體類型豐富,編解碼能力強
缺點:
  • 必須配合ARM處理器,成本不佔優勢,功耗較大;
  • 不支持網絡視頻格式RM、RMVB;
  • 屬於低性能應用處理器(ARM 80-160MHZ),軟件解決方案有限,需要客戶做大量的軟件編程工作。

[轉] 清楚了解 android.os 源碼中的Looper,Handler,Message,MessageQueue

Hi All:
Handler, Message, Looper, MessageQueue 是 android.os 中的class
也是深度開發 Application 時,必須具備的基本觀念,若清楚了解,
便可運用的當。
因為網路有太多模糊不清的文章,大家說法看起來也都不太一樣,
很容易讓人猜東猜西,想東想西的。至於,不想瞎猜的話,就不如直接把source code都讀懂吧。
因此本篇文章,目地在於,快速引導大家快速「正確」的了解,重點在於「正確性」
並且透過靜態 trace code  的方式,跟大家解釋它 source code 的運作原理。
因此,對於四個 class沒信心的時候,
就直接將文章看下去,文章會完整交代 trace source code的部份。
關於這四個 class 的結論:
========================================================
整個Handler, Message, MessageQueue, Looper 它們四個 class 只有一個共同的目標
就是讓程式碼,可以丟到其它 thread 去執行。這麼作有什麼好處呢 ??
例如 android 的 GUI 元件是 thread safe的 (意思是,元件的使用,無法multi-thread執行)
Activity 的畫面顯示是由 UI Thread所負責的,若是你寫了 mutlti-thread 程式時
又想更新畫面,就必須要將 Thread 內部的一段程式碼,交由 UI Thread 來執行才行。
OK, 上面四個 class 的共同目地已經說明完畢了,那麼這四個 class有其分工方式。
因此每個 class 的設計又有不同目地。說明如下 …
Handler 的目地,在於提供 callback function,預其給其它 Thread 作執行
但Handler又要如何 transfer 至其它 Thread 呢 ?  於是有了 Message
Message 的目地,將 Handler 包裝起來,傳送給其它 Thread
但是同時有多條 thread 不斷的在系統中傳遞 Message 那麼如何緩衝呢 ?
MessageQueue 的目地,是為了讓 Message 能夠作緩衝,好讓Message先暫存起來。
因此,當Message 已經被放在其它 Thread上的MessageQueue 之後,
它裡面包著 Handler,而 Handler上的 callback function 總得有人來執行吧 ??
Looper 的目地 :
就是為了將 Message 由 Thread 所對應的 MessageQueue 取出來,並且拿出 Handler
來執行它上面的 callback function.
當 Looper.java 中的 loop() 被呼叫起來之後,它就是在反覆作這件事
不斷將Handler由Message拆包出來,並且執行Handler上的callback function。
                                                                              
======================================================================
以上,已經將這四個class的關係完整說明了。看到這邊您還有疑慮嗎 ?
接下來小弟就直接講 trace source 的部份,
教你快速 trace 懂這些 code,迅速驗證出這四個 class 的用途。
以下開始 trace source code .. Follow Me ^____^
Looper 中的 mThread, mQueue 只有在 Ctor 建立,並且”不會再更改”
mThread = Thread.currentThread() //紀綠此Looper由那條Thread建立
mQueue = new MessageQueue() //每個Looper只有唯一的Queue
主要的執行函式為
Looper.java: loop()  {
  MessageQueue queue = myLooper().mQueue //取得CurrentThread下Looper的MsgQueue
  while(true) {
    Message msg = queue.next() //跳到msg一個message
    msg.target.dispatchMessage(msg)
    //target 被almost設定的方式,是透過Message.obtain(Handler h)設 h 為target
    msg.recycle(); //In Message class, 只有recycle()與obtain() 作sync同步
  }
}
                                                                              
上面程式中,所提到的東西,在以下深入探討。
(1) dispatchMessage(msg) 是如何重要呢 ?
它呼叫 Handler 上的 handleMessage().
———————————————
PS: 一般來說,我們會寫個 EHandler extends Handler,
並且重寫handleMessage()function 好讓 Handler 上的 handlerMessage()
被 UI Thread呼叫,來更新畫面。
——————————
(2) 至於 loop() 是如何被使用的呢 ?
typical example 大約是這樣子的
class LooperThread extends Thread {
  public Handler mHandler;
  public void run() {
    Looper.prepare();
    mHandler = new Handler() {
      public void handleMessage(Message msg) {
        // process incoming messages here
      }
    };
    Looper.loop();
}
類似的 typical example 在 Android 系統中的 ActivityThread.java :: main()
public static final void main(String[] args) {
  Looper.prepareMainLooper();
  ActivityThread thread = new ActivityThread();
  Looper.loop();
}
                                                                              
額外話 …
此範例trace下去將發現, Looper.mMainLooper 變數被設定為
(Looper)sThreadLocal.get()
許多重要的 android source code 皆會透過 getMainLooper() 函數取出
Looper.mMainLooper
(3) msg.target 是個 Handler 類別,  又是從何而來的呢 ?
直接copy高煥堂網路文章中的example code過來 …
講義摘錄之28:Anrdroid 的Message Queue(3/3) 的example code如下
class AnyClass implements Runnable {
 public void run() {
         Looper.prepare();
         h = new Handler(){
               public void handleMessage(Message msg) {
                  EventHandler ha = new EventHandler(Looper.getMainLooper());
                    String obj = (String)msg.obj + ”, myThread”;
                     Message m = ha.obtainMessage(1, 1, 1, obj);
                     ha.sendMessage(m); //sendMessage的原理,請見(4)的說明
         }
       };
  Looper.loop();
  }
}
                                                                              
我們直接由此來作解釋,
追蹤當中的 obtainMssage 可發現 target的由來。原理如下
Handler.java: Message obtainMessage(int what, int arg1, int arg2)
Message.java:
static Message obtain(Handler h, int what, int arg1, int arg2) {
        Message m = obtain();
        m.target = h;  // 這邊就是 msg.target 的由來
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
}
而 Message m = obtain() 是執行下面這段程式
    public static Message obtain() {
        synchronized (mPoolSync) { //與 recycle() 共用 mPoolSync
            if (mPool != null) {
                Message m = mPool;
                mPool = m.next;
                m.next = null;
                return m;
            }
        }
        return new Message();
    }
因此你可從 sample code 知道
Handler 呼叫 obtainMessage 的時候,其實是由 mPool 取出 Message來
將 msg.target 設為原 Handler. 並且設定好 what, arg1, arg2 等參數
好讓 Looper 來執行它 …
———————————————–
(4) 接續 (3) 中的 example code, 它的 sendMessage() 又作了什麼事呢 ?class AnyClass implements Runnable {
 public void run() {
         Looper.prepare();
         h = new Handler(){
               public void handleMessage(Message msg) {
                  EventHandler ha = new EventHandler(Looper.getMainLooper());
                    String obj = (String)msg.obj + ”, myThread”;
                     Message m = ha.obtainMessage(1, 1, 1, obj);
                     ha.sendMessage(m); //sendMessage 作什麼事呢?
         }
       };
  Looper.loop();
  }
}
                                                                              
以這個例子,簡單來說,Looper.getMainLooper() 會回傳一個ActivityThread的
Looper object, 即為 Looper.mMainLooper. 而mMainLooper有自己的mQueue
==================================================
在此穿插一小段 sendMessage() 的作用
Handler本身在暫存一個mQueue, 當Handler的成員函數sendMessage 被呼叫時,即是把帶著 Handler ha 的 Message m,enqueue 至 Handler自己存存的mQueue中。而mQueue的設置,通常是在建構子就被決定好的。因此你得特別注意 Handler 的建構子。
==================================================
像上面的例子中 sendMessage 即是把帶著 Handler ha 的 Message m,enqueue 至 mMainLooper.mQueue
sendMessage 即是把帶著 Handler ha 的 Message m,enqueue 至 mMainLooper.mQueue
好讓 mMainLooper.loop() 函數把 m 由這個 mMainLooper.mQueue取出(取出時名為msg)
來dispatchMessage,因此就會執行到 msg.target.handleMessage(0
也就是 exmaple code 中的 ha.handleMessage();
因為在 Handler ha = new Handler(Looper looper) 這 Ctor 時,
ha.mLooper = looper 便被紀錄下來,而且ha.mQueue=looper.mQueue也被紀錄下來
也就是 looper.mQueue    (PS:若是用 new Handler(), 則looper取Looper.myLooper())
當 ha.sendMessage 被執行時,便將 msg 塞入 looper.mQueue
—————————————————————–
                                                                                
(5) 所以整個 Looper, Message, MessageQueue, Handler 的運作原理是什麼?
因此你的 ha = JohnHandler(MaryLooper) 就像信紙一樣,上面寫著Dear MaryLooper:
上面寫著要執行的程式碼 handleMessage(msg)
透過信封(Message),以Handler.java 的 sendMessage 將信紙(Handler)傳出去
傳到 MaryLooper 的個人信箱 MessageQueue (也就是MaryLooper.mQueue)
在 MaryLooper 中,有個有個固定的 loop() 會不斷被執行
(假設當初宣告此looper的thread, 有 去running 此 function loop 的話 )
那麼 loop 會收到 Message msg. 而 msg.target (Handler) 即為 JohnHandler這封信紙
看著 JohnHandler 上有 handleMessage() 的信紙內容,
故對 Handler 執行了 dipsatchMessage(),因此執行了 JohnHandler
當初信紙內容的交辦事項。
======================================================================

[轉] 深入理解Android消息處理系統——Looper、Handler、Thread

熟悉Windows編程的朋友可能知道Windows程序是消息驅動的,並且有全局的消息循環系統。而Android應用程序也是消息驅動的,按道理來說也應該提供消息循環機制。實際上谷歌參考了Windows的消息循環機制,也在Android系統中實現了消息循環機制。Android通過Looper、Handler來實現消息循環機制,Android消息循環是針對線程的(每個線程都可以有自己的消息隊列和消息循環)。本文深入介紹一下Android消息處理系統原理。
Android系統中Looper負責管理線程的消息隊列和消息循環,具體實現請參考Looper的源碼。 可以通過Loop.myLooper()得到當前線程的Looper對象,通過Loop.getMainLooper()可以獲得當前進程的主線程的Looper對象。
前面提到Android系統的消息隊列和消息循環都是針對具體線程的,一個線程可以存在(當然也可以不存在)一個消息隊列和一個消息循環(Looper),特定線程的消息只能分發給本線程,不能進行跨線程,跨進程通訊。但是創建的工作線程默認是沒有消息循環和消息隊列的,如果想讓該線程具有消息隊列和消息循環,需要在線程中首先調用Looper.prepare()來創建消息隊列,然後調用Looper.loop()進入消息循環。如下例所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  class LooperThread extends Thread {
      public Handler mHandler;
 
      public void run() {
          Looper.prepare();
 
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
 
          Looper.loop();
      }
  }
這樣你的線程就具有了消息處理機制了,在Handler中進行消息處理。
Activity是一個UI線程,運行於主線程中,Android系統在啟動的時候會為Activity創建一個消息隊列和消息循環(Looper)。詳細實現請參考ActivityThread.java文件。
Handler的作用是把消息加入特定的(Looper)消息隊列中,並分發和處理該消息隊列中的消息。構造Handler的時候可以指定一個Looper對象,如果不指定則利用當前線程的Looper創建。詳細實現請參考Looper的源碼。
Activity、Looper、Handler的關係如下圖所示:


一個Activity中可以創建多個工作線程或者其他的組件,如果這些線程或者組件把他們的消息放入Activity的主線程消息隊列,那麼該消息就會在主線程中處理了。因為主線程一般負責界面的更新操作,並且Android系統中的weget不是線程安全的,所以這種方式可以很好的實現Android界面更新。在Android系統中這種方式有著廣泛的運用。
那麼另外一個線程怎樣把消息放入主線程的消息隊列呢?答案是通過Handle對象,只要Handler對象以主線程的Looper創建,那麼調用Handler的sendMessage等接口,將會把消息放入隊列都將是放入主線程的消息隊列。並且將會在Handler主線程中調用該handler的handleMessage接口來處理消息。
這裡面涉及到線程同步問題,請先參考如下例子來理解Handler對象的線程模型:
1、首先創建MyHandler工程。
2、在MyHandler.java中加入如下的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.simon;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;
import android.os.Handler;
 
public class MyHandler extends Activity {
 static final String TAG = "Handler";
 Handler h = new Handler(){
     public void handleMessage (Message msg)
     {
      switch(msg.what)
      {
      case HANDLER_TEST:
       Log.d(TAG, "The handler thread id = " + Thread.currentThread().getId() + "\n");
       break;
      }
     }
    };
 
 static final int HANDLER_TEST = 1;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "The main thread id = " + Thread.currentThread().getId() + "\n");
 
        new myThread().start();
        setContentView(R.layout.main);
    }
 
    class myThread extends Thread
    {
     public void run()
     {
      Message msg = new Message();
      msg.what = HANDLER_TEST;
      h.sendMessage(msg);
      Log.d(TAG, "The worker thread id = " + Thread.currentThread().getId() + "\n");
     }
    }
}
在這個例子中我們主要是打印,這種處理機制各個模塊的所處的線程情況。如下是我的機器運行結果:
09-10 23:40:51.478: DEBUG/Handler(302): The main thread id = 1
09-10 23:40:51.569: DEBUG/Handler(302): The worker thread id = 8
09-10 23:40:52.128: DEBUG/Handler(302): The handler thread id = 1
我們可以看出消息處理是在主線程中處理的,在消息處理函數中可以安全的調用主線程中的任何資源,包括刷新界面。工作線程和主線程運行在不同的線程中,所以必須要注意這兩個線程間的競爭關係。
上例中,你可能注意到在工作線程中訪問了主線程handler對象,並在調用handler的對象向消息隊列加入了一個消息。這個過程中會不會出現消息隊列數據不一致問題呢?答案是handler對象不會出問題,因為handler對象管理的Looper對象是線程安全的,不管是加入消息到消息隊列和從隊列讀出消息都是有同步對象保護的,具體請參考Looper.java文件。上例中沒有修改handler對象,所以handler對象不可能會出現數據不一致的問題。
通過上面的分析,我們可以得出如下結論:
1、如果通過工作線程刷新界面,推薦使用handler對象來實現。
2、注意工作線程和主線程之間的競爭關係。推薦handler對象在主線程中構造完成(並且啟動工作線程之後不要再修改之,否則會出現數據不一致),然後在工作線程中可以放心的調用發送消息SendMessage等接口。
3、除了2所述的hanlder對象之外的任何主線程的成員變量如果在工作線程中調用,仔細考慮線程同步問題。如果有必要需要加入同步對象保護該變量。
4、handler對象的handleMessage接口將會在主線程中調用。在這個函數可以放心的調用主線程中任何變量和函數,進而完成更新UI的任務。
5、Android很多API也利用Handler這種線程特性,作為一種回調函數的變種,來通知調用者。這樣Android框架就可以在其線程中將消息發送到調用者的線程消息隊列之中,不用擔心線程同步的問題。
深入理解Android消息處理機制對於應用程序開發非常重要,也可以讓你對線程同步有更加深刻的認識。以上是最近Simon學習Android消息處理機制的一點兒總結,如有錯誤之處請不吝指教。
參考資料:
原文作者:Simon_fu