2012年7月18日 星期三

Android之JNI

就如我第一篇android說的,我不是很關心android如何應用這樣的機制去寫作應用程式,但是本身卻對機制的原理相當感興趣,如果想要學會如何使用NDK做JNI可以參考參考資料,步驟大概如這張圖

針對JNI在Android上大概分成三層Java/App<=>JNI Lyaer<=>Native
因為有些動作只能在Native完成,所以不得不使用C語言,但是使用者介面又是以Java構成,要使兩者可以溝通就是透過JNI的機制
首先看Java code,必須使用System.loadLibrary載入相對應的share object(.so),對於native function則使用native關鍵字標示出來
 在java內已經宣告了java這邊的介面,用native表明到時候要透過JNI去呼叫Native程式,這時候用javah來編譯出C++ header file,使用JDK工具以特定格式去產生Native function的介面,這樣JNI才會認得兩邊的接口/介面,JNI這位中間人,就是依賴這樣的規則才能為兩邊牽線,下面是javah產生出來的.h檔案
跟著就是自行產生.cpp檔案,實作.h的介面

當呼叫System.loadLibrary的時候JNI會呼叫JNI_OnLoad函數,並且剖析share object(JNIEXPORT關鍵字做的手腳,export某些symbol),並且動態的將這些method註冊到JNI中,java code呼叫某個native method的時候其實是去尋找JNI中註冊表的method,來做呼叫

由於兩種程式語言的基本type並不相同,在兩者之間傳遞資料變成一件很重要的事情,所以如何對應呢?
<type>=>j<type>,如boolean(java)=>jboolean(c++),支援的type有boolean、byte、char、short、int、long、float、double

那麼如何在Native裡面呼叫java的某個物件的某個方法呢?其實如果熟悉Java Reflection這問題會容易解的多。
JNIEnv包含了JNI_OnLoad的時候做的資訊以及JVM的資訊,透過JNIEnv可以找到對應的class,再透過對應的class找出method與field,建構適當的signature就可執行某個object的method,跟Reflection的工作是大同小異的。


另外有個地方要注意的是,預設的狀況在Native並不會增加reference count,也就是說如果再Native functionm用static variable參考到某個object,並不表示下次來,這個object還會存在。可以使用env->NewGlobalRef(some_java_object)來取得global reference,但是要注意,這個global reference會使得garbage collect無法回收該物件,需要使用env->DeleteGlobalRef(some_java_object)來歸還


Native的exception是由JNI回報的,但是C++的異常蠻明顯不大相容於java,且處理很容易讓程式當掉,這時候JNIEnv可能用ExceptionOccured、ExceptionClear、ThrowNew報告異常

參考文件:
http://cheng-min-i-taiwan.blogspot.tw/2011/05/java-native-interface-jni-androidndk.html
http://blog.csdn.net/nicholas6lee/article/details/7324087
http://gyht0808.iteye.com/blog/763435
http://my.unix-center.net/~Simon_fu/?p=833

Learning OpenCV -- chapter 5

這一章談的是圖形的處理
blurring(平滑):用以去除雜訊或者讓影像模糊,基本原理是平均mask/filter內的pixel values
void cvSmooth(const CvArr* scr, CvArr* dst, int smoothtype=CV_GAUSSIAN,
int param1=3, int param2=0, int param3=0, int param4=0);
平滑的type有: CV_BLUR(簡單模糊)、CV_BLUR_NO_SCALE(簡單無縮放模糊)、CV_MEDIAN(中值法)、CV_GUASSIAN(高斯模糊)、CV_BILATERAL(雙邊濾波)
其中簡單與高斯支援in-place,也就是destination或取代source
對於高斯而前param1與param2分別是mask/filter的width與height,第三個參數為sigma,如果要讓xy方向不同的sigma可以使用第四個參數

形態學(Morphology)
Erode(膨脹)與Dilate(侵蝕)是兩個常見的手法,用以將影像的一個部分與kernel(核)做convolution運算,每個kernel有個anchor point(通常位於中心)。膨脹求取局部最大值,使圖形中明亮的區域擴大,侵蝕則是求取區域最小值,侵蝕可以將一些斑點的noise去除
void cvErode(IplImage *src, IplImage *des, IplConvKernel *B=NULL, int iteration=1);
void cvDilate(IplImage *src, IplImage *des, IplConvKernel *B=NULL, int iteration=1);
這兩個運算都支援in-place,若是kernel不指定,則使用3x3的kernel,iteration則表示執行的次數
建立kernel的方式為
IplConvKernel * cvCreateStructuringElementEx(int cols, inr rows, int anchor_x,int anchor_y, int shape, int* values)
望文生義,前面四個參數就不解說了,第五個參數表示形狀有CV_SHAPE_RECT、CV_SHAPE_CROSS、CV_SHAPE_ELLIPSE、CV_SHAPE_CUSTOM
第五個參數表示特定的形狀
在Morphology往往反覆使用上面兩個運算,達到一些特殊目的

void cvMorphologyEx(
   const CvArr*   src,
   CvArr*         dst,
   CvArr*         temp,
   IplConvKernel* element,
   int            operation,
   int            iterations   = 1
);
OpenCV預設支援幾種常見的operator(CV_MOP_OPEN、CV_MOP_CLOSE、CV_MOP_GRADIENT、CV_MOP_TOPHAT、CV_MOP_BLACKHAT)
Open先Dilate再Erode,去除小型的高亮度區域
Close先Erode再Dilate,去除低亮度區域的孤立點
Gradient=dilate(src)-erode(src),邊界
TopHat=src-open(src)
BlackHat=close(src)-src

Flood Fill,就如小畫家的油漆桶一般

void cvFloodFill(
   IplImage*          img,
   CvPoint            seedPoint,
   CvScalar           newVal,
   CvScalar           loDiff    = cvScalarAll(0),
   CvScalar           upDiff    = cvScalarAll(0),
   CvConnectedComp*   comp      = NULL,
   int                flags     = 4,
   CvArr*             mask      = NULL
);

調整大小

void cvResize(
   const CvArr*   src,
   CvArr*         dst,
   int            interpolation = CV_INTER_LINEAR
);
其中調整的時候是如何處理過多或者刪減的pixels,由最後一個參數指定,有CV_INTER_NN (Nearest neighbor)、CV_INTER_LINEAR(Bilinear)、CV_INTER_AREA(Pixel area re-sampling)、CV_INTER_CUBIC(Bicubic interpolation)


Image Pyramids看不懂,orz


Threshold,運算有

double cvThreshold(
   CvArr*         src,
   CvArr*         dst,
   double         threshold,
   double         max_value,
   int            threshold_type
);


相對應的type為CV_THRESH_BINARY、CV_THRESH_BINARY_INV、CV_THRESH_TRUNC
CV_THRESH_TOZERO_INV、CV_THRESH_TOZERO


取threshold是一種技巧,友人發展出自動化的技巧,透過加權平均的方式自行取得threshold

void cvAdaptiveThreshold(
   CvArr*         src,
   CvArr*         dst,
   double         max_val,
   int            adaptive_method = CV_ADAPTIVE_THRESH_MEAN_C
   int            threshold_type  = CV_THRESH_BINARY,
   int            block_size      = 3,
   double         param1          = 5
);
這個function並不支援in-place

以上的函數都是OpenCV已經幫忙實作完畢,只要直接套用就可以,相當方便,但是重點應該是何時該使用何種運算,以及運算背後代表的意義,才是我們要學習的重點(還是直接去翻閱Digital Image Processing的書XD)



2012年7月17日 星期二

android架構

在Android架構,總共是由5個部份來組成。分別是:
(1)Applications(應用程式)
(2)Application Framework(應用程式架構)
(3)Libraries(函式庫)
(4)Android Runtime(Android執行環境)
(5)Linux Kernel(Linux核心)
大多數的網路資訊引用了類似這樣的圖形
我注意到比較有趣的是Binder (IPC) driver被嵌入了kernel,印象中這是android創建的IPC機制。許多人也知道android上的app開發大多是java,也就是java透過application framework在與底層的JNI以及Native的library做溝通,而我感興趣的就是底層的部分(application framework以下)
為何會對這部分有興致,主要想要操作camera以及最近遇到一個有人release出來的ICS版本,但是SD卡卻無法順利掛載,開始追蹤Vold這個deamon(android藉由uevent實作出來的一個動態掛載volumn的機制),或許就是為了喝牛奶養了一條牛吧XD
最後補上一個camera個架構圖
參考資料:
http://my.opera.com/ozzyyngwie/blog/android-an-open-mobile-platform
http://www.kandroid.org/online-pdk/guide/camera.html

Learning OpenCV -- chapter 3之一

OpenCV的主要四種基本資料結構
CvPoint => CvPoint2D32f, CvPoint3D32f,最前面為兩個整數變數x,y 後者則為浮點數
CvSize => CvSize2D32f,兩個整數變數width, height或者為浮點數的結構
CvRect有成員x, y, width, height
CvScalar,四個double的數值,再不考量記憶體使用量可以用來取代前面三者
通常constructor為cvPoint()或者cvSize,也就是第一個字母為小寫的function,在這裡雖然OpenCV是用c語言寫成,但是其實有不少物件的觀念,所以就借用了constructor一詞,但是並非真的是constructor

圖形的資料結構
CvArr
CvMat
IplImage
如果有個function要求CvArray*,則可以放入CvMat*或者IplImage*,同理要求CvMat*則可放入IplImage*,可以將關係視為繼承,也就是說is-a的關係



CvMat * cvCreateMat(int rows,int cols, int type);
表示這為一個二維陣列,望文生義,rows跟cols表示列跟行,type則是
CV_<bit_depth>(S|U|F)C<number_of_channels>
bit_depth也就是色階,S|U|F表示signed|unsigned|floating-point,C表示channel個數
CV8UC3,表示每個pixels由3個channel構成,每個channel用8bits表示
data則是實際儲存資料的地方
而cvCreateMat實際上呼叫了cvCreateMatHeader()與cvCreateMatData(),前者填充rows/cols/type等等,後者對儲存資料的空間作配置,所以如果不需配置記憶體的時候,或者共享某個區塊的時候,就只要呼叫cvCreateMatHeader就可以了。如果是複製某個區塊,使用cvCloneMat(CvMat*),有配置就必要釋放cvReleaseMat(CvMat*)

存取CvMat資料的方式有用Macro
CvMat *mat=cvCreateMat(5,5,CV_32FC1);
CV_MAT_ELE(*mat,float,3,2);
*((float*)CV_MAT_ELE_PRT(*mat,float,3,2))=5.5
由參數得知,必須要知道data的type才能計算到正確位置,還有就是它是一次性計算,也就是非指標運算,對於連續存取很沒效率

另外一種為cvPtr*D與cvGet*D的方式取得指標,用於各種維度
uchar* cvPtr1D(const CvArr* arr, int idx0, int *type=NULL)
uchar* cvPtr2D(const CvArr* arr, int idx0, int idx1, int *type=NULL)
uchar* cvPtr3D(const CvArr* arr, int idx0, int idx1, int idx2, int *type=NULL)
uchar* cvPtrND(const CvArr* arr, int *idx, int *type=NULL, int create_node=1, unsigned* precalc_hashval=NULL)
或者
double cvGetReal1D(const CvArra *arr, int idx0)
double cvGetReal2D(const CvArra *arr, int idx0, int idx1)
double cvGetReal3D(const CvArra *arr, int idx0, int idx1, int idx2)
double cvGetRealND(const CvArra *arr, int *idx)

CvScalar cvGet1D(const CvArr *arr, int idx0)
CvScalar cvGet2D(const CvArr *arr, int idx0, int idx1)
CvScalar cvGet3D(const CvArr *arr, int idx0, int idx1, int idx2)
CvScalar cvGetND(const CvArr *arr, int *idx)

其中cvPtr*D取得為每個pixel,如果有3個channel,則為rgbrgbrgb...等地排列方式,這部分區自行處理,好處是可以快速地訪問,另外有cvGet*D就有cvSet*D
另外要注意的是CvMat未必是連續空間,最多保證每個row是連續的

從上面可以看到IplImage的成員,大多可以望文生義
重要的有origin,表示原點座標是放置在IPL_ORIGIN_TL左上角(一般電腦圖形)或者IPL_ORIGIN_BL左下角(一般數學座標)
dataOrder表示資料是每個IPL_DATA_ORDER_PIXEL或者IPL_DATA_ORDER_PLANE,前者表示rgbrgbrg這樣的資料排列,後者表示rrrrr...ggggg...bbbbb....這樣三面的排列,所以儲存影像資料的imageData就受到這個參數的影響,必須知道資料正確的排列才能正確處理資料
ROI則是感興趣的範圍,用以將圖形特定範圍切出,用來處理這部分,有mask的觀念,可以使用
void cvSetImageROI(IplImage *img, CvRect rect)
void cvResetImageROI(IplImge *img)
可見一個IplImage只能設定一個ROI,但是要保留多個ROI,書中有範例技巧
參考資料:
http://yester-place.blogspot.tw/2008/07/cvmat.html
http://yester-place.blogspot.tw/2008/07/iplimage1.html

2012年7月16日 星期一

Learning OpenCV -- chapter 2

載入image到記憶體
IplImage *img=cvLoadImage(filepath);
載入avi檔案,並且使用cvQueryFrame取得下一個frame
CvCapture *capture=cvCreateFileCapture(path);
IplImage *frame;
for(...){ frame=cvQueryFrame(caputre);}
調整capture,cvSetCapturePropertycvGetCaputreProperty
cvSetCaptureProperty(caputre,CV_CAP_PROP_POS_FRAME,pos);
建立影像,第一個參數為大小,第二個為每個channel的bits數,第三個為通道數目
IplImage *out=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,3);
使用演算法
cvSmooth(img,out,CV_GAUSSIAN,3,3);
建立一半大小的影像
IplImage *out=cvCreateImage(cvSize(img->width/2,img->height/2),img->depth,img->nChannel);
cvPyrDown(img,out);
從上面可以知道,所有重要的資料都被封裝成struct,然後以指標的形式在傳遞

總結以上,OpenCV是一個C lib的library,提供一個快速開發的介面,有效的節省programmer的時間,我想主要的目的是驗證一些演算法吧,真的還蠻方便的

2012年7月15日 星期日

OpenCV 2.4與Visual C++ 2010 Express

主要參考這裡
http://www.cnblogs.com/freedomshe/archive/2012/04/25/2470540.html

大致上就是設定include、設定lib,還要設定linker lib,最後記得把環境變數加進來,讓程式找的到dll,之前裝VC 2008 Express因為忘記設定(其實是找不到orz,好個VS,設定改來改去),結果就一堆symbol找不到的問題

2012年7月14日 星期六

udev的流程

udev主要功能

  • 允許動態建立刪除裝置
  • 自行配置裝置編號
  • 根據規則建立名稱,而非固定名稱
  • 建立kobject物件


udev其實利用了kobject在/sys下面建立的動態的系統資訊,udev主要成分,namedev、libsys、udev

udev配合hotplug機制,每當有裝置加入的時候,建立sysfs(kobject)下的相對應節點,跟著呼叫namedev在/dev下建立相對應的裝置
如果理解bus、device與driver三者的關係,可以理解到udev功能是可以達成的,但是接著升起的疑問是,流程是如何呢?

bus會利用match這個操作方式尋找合適的device以及driver,但是因為需要kobject來在sysfs下建立相對應的結構,是否意味著要改寫driver?從參考資料的解說,應該是不必。udev會監控linux載入的module,自動生成kobject

所以一開始就註冊了bus,udev會在module載入的時候紀錄準備生成kobject,當裝置插入主機的時候,跟著建立device,使用bus尋找到相關driver,最後udev會生成kobject,也就是在/sys下建立相對應的目錄

bus(開機時候)=>driver/udev(載入module時候)=>device(裝置插入主機接上某個bus)=>/dev/xxx與/sys/bus/xxx建立(udev透過bus.match建立)

以上就是我的理解,不知道有沒有錯誤

參考資料:
http://daydreamer.idv.tw/rewrite.php/read-49.html

2012年7月4日 星期三

character driver的岔路上

不能免俗,絕大多數入門的linux driver,除了第一個hello world module之外,跟的就是character driver,因為他最貼近一般人使用fopen, fread, fwrite, fclose的直覺
推薦服用宋寶華的範例,但是在2ed chapter 6使用的測試指令為
echo "hello world"> /dev/globalmem
cat /dev/globalmem
以上可以正常運作,可是跟著我就開始試著其他組合
echo "hello world"> /dev/globalmem
echo "again"> /dev/globalmem
cat /dev/globalmem
開始似乎不大正常的運作,即使使用
echo "hello world"> /dev/globalmem
echo "again">> /dev/globalmem
cat /dev/globalmem
也於事無補,我開始懷疑ppos這個表示目前檔案位置的參數有沒以正確運作,在經過一番追蹤,似乎發現不如預期。我恍然大悟,原來要從process、file、inode角度去切入,就明白這一切了。


過程中我也寫了個簡單的程式,使用fopen, fread, fwrite, fclose去操作,因為一點點小小的失誤反而讓我看到一些覺得奇怪的地方,首先fopen, fread, fwrite, fclose的應該是對應到了driver內的operations,但是我在fopen的時候,最後讀寫模式寫錯了"w+",只寫了"w",結果編譯過程還是通過了,但是fread沒有作用,照樣有輸出,真是好神奇阿@@a有興趣的人可以用strace試驗看看

cross compile strace

指令如下
CC=arm-linux-gcc LD=arm-linux-ld RANLIB=arm-linux-ranlib ./configure --host=arm-linux --target=arm-linux --prefix=./install_dir
完成之後直接放到Target上就可以了
相關資料
https://www.ibm.com/developerworks/cn/linux/l-tsl/

ioremap的理由

參考資料內說得非常好,有許多理由
1. 一般io access, 有所謂的IO bus或使用memory mapped IO, 以I386而言,   它有in/out系列的instruction來做IO bus access, 而ARM則沒有. 
2. 對於IO bus access, 一般driver只要有access right就可, 而由於一般而   言C/C++ 無法generate in/out系列的instruction, 所以要以inline   assembly來達成這個目的, 而一般的inb(), inw(), outb(), outw()...等   等的functions或macros會可以做到這件事. 
3. 對於memory mapped IO, 由於是直接access memory, 必須跟OS的memory   system結合, 所以就會有一些access right, non-cacheable, virtual   address等的問題. 
4. 對PCI而言, 還有所謂的PCI bus address, 而一般PCI bus driver在啟動   後會enumerate上面的card, 了解它們的memory regions的size, 並assign   PCI bus address給每一memory region, 至於如何由system bus address   對到PCI bus address要看SOC的設計. 在access時, 是virtual address->   physical address->PCI bus address. 
5. OS並不會知道那一塊被拿來當memory mapped IO, 所以, 往往是只有開放   RAM的部份的space, 其實一般startup code其MMU的部份還會打開SOC的   register space或一些local bus banks的space, 以相同的virtual address   來使用對應的physical address, 但是, startup code一般我們希望不要   放太多變動性太大的東西, 這樣也會把太多枝枝葉葉的codes放入主要程式   中, 另外, 與PCI相關的考量放入startup code, 也會變得很繁瑣. 所以   ioremap很重要的是就是讓driver可以為它要使用的空間找到一塊virtual   address的space. 
6. 有一些扁平式的設計, 直接一開始全部對應好, 將4G virtual address對應   到等值的physical address, 所以你不用ioremap也可使用正常, 不過, 使   用ioremap也沒甚麼不好吧, 反而是養成良好的習慣. 而通常, 有virtual   memory的MMU, 一般都是只開有限的區域, 即使它們只是直接對應等值的   address, 因為這樣可以加強系統的debug功能, report非法的memory access. 
7. 一般ARM有分TLB或protection unit的, protection unit只做保護和cache/   write buffer的控制, TLB則可以實現virtual memory.

因為一般在kernel模式下操作的是virtual address,透過MMU的page table來處理virtual address與physical address,但是實體io port或者memory,要是直接存取會產生兩個問題,一個是受限於特殊硬體架構,另外一個是不應直接處理physical address。於是乎ioremap就是透過修改page table,讓physical address對應到virtual address(等於在virtual address規劃出一區塊,使OS知道使用該區塊等同在使用某區塊的physical address),這樣一來就某種程度隔開特殊的硬體架構,也避免直接使用physical address。

跟著由於硬體的特性,雖然ioreamp讓程式設計師使用實體記憶體如同使用一般memory一樣,但是因為有時硬體可能只是個io port,訊號隨著時間一直變動(有點類似volatile變數一樣),所以在讀取跟寫入的時候最好透過特殊函數如writel或者readl之類,不要直接用assign operator(等號)

http://www.programmer-club.com/showSameTitleN/embedded/1820.html
http://blog.csdn.net/do2jiang/article/details/5450839

2012年7月1日 星期日

inode、file以及container_of

回顧LDD3的第一個character device範例,其實感覺裡面一次給出了太多東西,會令人暈頭轉向,其實在宋寶華的書中給出一個比較簡單的範例


  • LDD3一次配置多個device,對多個device視為同一類,使用同一個module
  • 宋寶華一開始只有一個device,只給定一個module


兩者之間的差異決定的對程式展現的方式,其中以container_of這個macro最為經典,建議先看完宋寶華的範例,再瀏覽LDD3的範例,可以知道這個macro的關鍵性作用


LDD3展示了,一個module可以同時為同一類的裝置提供驅動程式,讓device(裝置)與operations(操作),可以結合,同時可以分開,一個module提供多個裝置,相同的操作,其中隱含了物件或者說ADT(abstract data type)的概念,這個在driver開方上很重要,udev更是把這種概念發揚光大

inode跟file兩個重要的結構提供了不同的作用,inode大多存在於kernel space,file則是user/kernel space都有(使用者一般操作file),同時也是支撐VFS (virtual file system)的兩個重要結構


  • file結構有兩個重要成員,一個是file_operations,也就是操作file的functions,另外一個是private_data指標,通常module會將對應的device資料的記憶體位置交給private_data
  • inode則擁有i_cdev或者i_bdev,分別表示character device或者block device這兩個指標,雖然一般開發者會重新包裝自己的結構,將struct cdev包裝起來,以支援定義的驅動程式,如LDD3範例


struct scull_dev {
struct scull_qset *data;  /* Pointer to first quantum set */
int quantum;              /* the current quantum size */
int qset;                 /* the current array size */
unsigned long size;       /* amount of data stored here */
unsigned int access_key;  /* used by sculluid and scullpriv */
struct semaphore sem;     /* mutual exclusion semaphore     */
struct cdev cdev;  /* Char device structure */
};

struct scull_dev *scull_devices; /* allocated in scull_init_module */
最後一個scull_devices表示一堆同類的devices

有了以上的瞭解之後來看open、read函數
static int open(struct inode *inode,struct file *filp){
...
}


static ssize_t read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){
...
}

在LDD3的範例在init函數配置好了所有的scull dev(因為不只一個device),但是在open函數的時候要如何找到對應的scull_devices中的哪一個?因為我們想把他assign給file.private_data方便以後給read函數使用
一般而言,由於inode包含了cdev這個變數,我們知道cdev來自於scull_dev裡面,所以會想藉此找出scull_dev,裡面包含了如何計算cdev應該在scull_dev內的位移,配合位移,藉由調整記憶體的address,我們就可以找到scull_dev了,而此時container_of就可以幫我們忙

struct scull_dev *dev; /* device information */


dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
constainer_of這個macro會自動完成計算位移以及轉型的動作,將目前打開的device所指向的scull_dev變數找出來。也就是只有scull_device.cdev結構成員,卻可以找到scull_dev的結構變數。做了那麼多工作,就是為了把scull_device配置給filp->private_data,後面read函數就可以應用她了

如果今天只有一個device,那麼其實也不用那麼麻煩,就如宋寶華的範例,直接將struct scull_dev onlyone設定為global變數,然後直接在open函數的時候assign給filp->private_data即可


由上面可以了解到,LDD3在一個範例內展示了

  • VFS這樣的抽象操作需要file以及inode配合
  • scull這個module可以同時支援多個一樣的裝置
  • 如何註冊character device,以及要注意的事項
  • 如何實作file operations
  • 隱含在Linux Device Driver中分層以及分工的架構
  • ...

不細看還真的很難體驗這個module竟然有如此多的東西,也感嘆這章節竟然寫得如是簡單/簡潔,真的不知道LDD3的作者是故意的還是假設讀者的Linux背景很夠

如果有興趣了解container_of到底如何運作,可以參考參考資料連結

參考資料:
http://space.itpub.net/14805538/viewspace-445624
http://tw.myblog.yahoo.com/hughes-blog/article?mid=88&prev=-1&next=87