2015年2月28日 星期六

[轉] Linux中Workqueue機制分析

什麼是 workqueue ?
Linux 中的 Workqueue 機制就是為了簡化核心執行緒的建立。 通過呼叫 workqueue 的介面就能建立核心執行緒。 並且可以根據當前系統 CPU 的個數建立執行緒的數量,使得執行緒處理的事務能夠並行化。

workqueue 是核心中實現簡單而有效的機制,他顯然簡化了核心 daemon 的建立,方便了用戶的編程,



Workqueue 機制的實現
Workqueue 機制中定義了兩個重要的數據結構,分析如下:

1、  cpu_workqueue_struct 結構。 該結構將CPU 和核心執行緒進行了綁定。 在建立 workqueue 的過程中, Linux 根據當前系統 CPU 的個數建立 cpu_workqueue_struct 。 在該結構主要維護了一個任務隊列,以及核心執行緒需要睡眠的等待隊列,另外還維護了一個任務上下文,即 task_struct 。

2、  work_struct 結構是對任務的抽象。 在該結構中需要維護具體的任務方法,需要處理的數據,以及任務處理的時間。 該結構定義如下:

struct work_struct {
 unsigned long pending;
 struct list_head entry;/* 將任務掛載到 queue 的掛載點 */
 void (*func)(void *);  /* 任務方法 */
 void *data;            /* 任務處理的數據 */
 void *wq_data;         /* work 的屬主 */
 strut timer_list timer;/* 任務延時處理定時器 */
};

      

       當用戶呼叫 workqueue 的初始化介面 create_workqueue 或者 create_singlethread_workqueue 對 workqueue 隊列進行初始化時,核心就開始為用戶分配一個 workqueue 對象,並且將其鏈到一個全局的 workqueue 隊列中。 然後 Linux 根據當前 CPU 的情況,為 workqueue 對象分配與 CPU 個數相同的 cpu_workqueue_struct 對象,每個 cpu_workqueue_struct 對像都會存在一條任務隊列。 緊接著, Linux 為每個 cpu_workqueue_struct 對象分配一個核心 thread ,即核心 daemon 去處理每個隊列中的任務。 至此,用戶呼叫初始化介面將 workqueue 初始化完畢,返回 workqueue 的指標。



       在初始化 workqueue 過程中,核心需要初始化核心執行緒,註冊的核心執行緒工作比較簡單,就是不斷的掃描對應 cpu_workqueue_struct 中的任務隊列,從中獲取一個有效任務,然後執行該任務。 所以如果任務隊列為空,那麼核心 daemon 就在 cpu_workqueue_struct 中的等待隊列上睡眠,直到有人喚醒 daemon 去處理任務隊列。



       Workqueue 初始化完畢之後,將任務運行的上下文環境構建起來了,但是具體還沒有可執行的任務,所以,需要定義具體的 work_struct 對象。 然後將 work_struct 加入到任務隊列中, Linux 會喚醒 daemon 去處理任務。



       上述描述的 workqueue 核心實現原理可以描述如下:

点击看大图
    在 Workqueue 機制中,提供了一個系統默認的 workqueue 隊列—— keventd_wq ,這個隊列是 Linux 系統在初始化的時候就建立的。 用戶可以直接初始化一個 work_struct 對象,然後在該隊列中進行調度,使用更加方便。



Workqueue 編程介面

1
 create_workqueue
 用於建立一個 workqueue 隊列,為系統中的每個 CPU 都建立一個核心執行緒。

 輸入參數:
@name : workqueue 的名稱

2
 create_singlethread_workqueue 用於建立 workqueue ,只建立一個核心執行緒。

輸入參數:
@name : workqueue 名稱

3
 destroy_workqueue
 釋放 workqueue 隊列。

輸入參數:
@ workqueue_struct :需要釋放的 workqueue 隊列指標

4
 schedule_work
 調度執行一個具體的任務,執行的任務將會被掛入 Linux 系統提供的 workqueue —— keventd_wq

輸入參數:
@ work_struct :具體任務對象指標

5
 schedule_delayed_work
 延遲一定時間去執行一個具體的任務,功能與 schedule_work 類似,多了一個延遲時間,

輸入參數:
@work_struct :具體任務對象指標
@delay :延遲時間

6
 queue_work
 調度執行一個指定 workqueue 中的任務。

輸入參數:
@ workqueue_struct :指定的 workqueue 指標
@work_struct :具體任務對象指標

7
 queue_delayed_work
 延遲調度執行一個指定 workqueue 中的任務,功能與 queue_work 類似,輸入參數多了一個 delay 。

基本順序

1.創建工作隊列
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);
2.創建工作
DECLARE_WORK(name, void (*function)(void *), void *data);
//一個函數搞定定義和初始化
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);
//要先定義work_struct,再調用這兩個函數
//INIT_WORK做更全的初始化,若需要改變工作隊列,則用PREPARE_WORK


3.提交工作給一個工作隊列
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay);
//返回0,添加成功,非0表明已存在此工作
int cancel_delayed_work(struct work_struct *work);
//返回非0,取消成功,返回0,取消不成功,工作仍在運行
void flush_workqueue(struct workqueue_struct *queue);
//確保cancel_delayed_work調用返回0後,停止運行工作

4.刪除隊列
void destroy_workqueue(struct workqueue_struct *queue);
//用完後刪掉工作隊列

2015年2月23日 星期一

android native library logging by logcat

http://www.kunli.info/2011/08/21/android-native-code-study-note/

http://blog.csdn.net/powq2009/article/details/39667105
http://blog.xuite.net/free6d1823/blog/162208532-How+to+use+ALOG

http://stackoverflow.com/questions/19887158/how-take-log-output-from-a-native-library-function-in-android
http://blog.csdn.net/learnerz/article/details/6698669


How to build kernel, uboot, android separately

1.单独编译boot.img

配置文件(board_name要与目标平台相匹配):
./device/amlogic/board_name/Kernel.mk
配置情况一:配置defconfig(要与目标平台相匹配)
KERNEL_DEFCONFIG := meson6_g24_jb_defconfig
配置情况二:配置源码位置
KERNET_ROOTDIR :=?common
配置情况三:配置ko(按需增减以及修改ko)
define cp-kernel-modules
……
endef
编译命令:
. build/envsetup.sh
lunch g24ref-user
make bootimage

2.单独编译recovery.img

编译命令:
. build/envsetup.sh
lunch g24ref-user
make recoveryimage

3.手工编译kernel

目的:
如果小范围修改kernel代码,需要重新编译boot.img。直接用make bootimage是可以的。不过在这样会搜索android的所有Makefile,速度会比较慢。所以可以选择手工编译,方法和以前相类似,但是要指定kernel输出文件的相对于源码的位置(out/target/product/xxx/obj/KERNEL_OBJ)。
3.1编译boot.img
在Android根目录下的命令:
device/amlogic/g24ref/quilk_build_kernel.sh bootimage
<注>
生成的boot.img在out/target/product/xxx/obj/KERNEL_OBJ/arch/arm/boot中。
从卡启动kernel命令:
   mmcinfo;fatload mmc 0:1 0x82000000 boot.img;bootm

3.2编译recovery.img
在Android根目录下的命令:
device/amlogic/g24ref/quilk_build_kernel.sh recoveryimage
<注>
生成的recovery.img在out/target/product/xxx/obj/KERNEL_OBJ/arch/arm/boot中。
从卡启动kernel命令:
   mmcinfo;fatload mmc 0:1 0x82000000 recovery.img;bootm

3.3查遍所有配置
命令:
make help
3.4菜单配置kernel
命令:
device/amlogic/g24ref/quilk_build_kernel.sh menuconfig
<注> 在菜单中按需配置kernel。
3.5保存config
命令:
device/amlogic/g24ref/quilk_build_kernel.sh saveconfig

2015年2月15日 星期日

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

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

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