2015/06/16

cmake

1. Introduction


CMake 是跨平台自動化建構系統, 但用來管理Makefile也很方便
只要安裝 cmake 套件就可以使用

sudo apt-get install cmake


2. 簡易範例


假如有底下這些檔案
  • myproj/
    • main.c
    • prime.c
    • prime.h
    • CMakeLists.txt
其中 CMakeLists.txt 的內容為

add_executable(myproj main.c prime.c)

CMake 預設會去找 CMakeLists.txt, 並解譯裡面的內容
add_executable() 裡面, 第一個參數是 executable target, 名字可以自定, 這個 executable target 會由後面的 source file 所編譯成。
CMakeLists.txt 可以放很多個 add_executable(), 代表同個 Makefile 可以生出不同的 executable target

接著我們移到 myproj 的目錄底下, 並下這個 command

$ cmake .

後面的點代表當前目錄
執行後就會生出 Makefile, 要注意的是這個 Makefile 無法在沒有 CMake 的環境下單獨使用。如果將整包檔案換環境, 就應該重下cmake command

接著可以操作 Makefile 的一些 command, 像是 make, 或 make clean

3. 簡易專案


假如有底下這些檔案
  • myproj/
    • build/
    • src/
      • CMakeLists.txt
      • main.c
      • prime.c
      • prime.h

那麼移動到 build目錄底下:
$ cmake ../src

這樣就可以將 source code 跟 target file 分開

4. optimization


cmake 提供幾組 compile config, 預設值是None

  • None: default
  • Debug: 產生 debug information
  • Release: 速度最佳化
  • RelWithDebInfo: 速度最加化, 但包含 debug flag
  • MinSizeRel: 程式碼最小化

假如現在我們有這些檔案
  • myproj/
    • build/
    • release/
    • src/
      • CMakeLists.txt
      • main.c
      • prime.c
      • prime.h
然後移動到 release 目錄底下, 這樣 release 的 make file 就會包含速度最佳化

$ cmake -DCMAKE_BUILD_TYPE=Release ../src


5. my project


底下是我的 project euler 使用的 example, 裡面的檔案有:

  • pe/
    • bin/
    • debug/
      • CMakeLists.txt
    • src/
      • main.cpp
      • prime.cpp
      • CMakeLists.txt
    • release/
      • CMakeLists.txt
    • inc/
      • prime.h
其中 pe/debug/CMakeLists.txt 的內容和 pe/release/CMakeLists.txt 的內容一樣
cmake_minimum_required(VERSION 2.6)

project(pe)
subdirs(../src)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ../bin)


然後 pe/src/CMakeLists.txt 的內容
include_directories(../inc)
add_executable(pe main.cpp prime.cpp)

要建 debug 的 makefile 就轉到 pe/debug/ 目錄底下打這個 command
$ cmake -DCMAKE_BUILD_TYPE=Debug

要建 release 的 makefile 就轉到 pe/release/ 目錄底下打這個 command
$ cmake -DCMAKE_BUILD_TYPE=Release


2015/04/30

OpenMP in linux

1. Introduction


寫 project euler 的時候, 有些問題想不到速度快的解法, 只好用平行運算來加速
平常程式都以一條 thread 在執行, 要開多條 thread 來跑, 在管理上就很麻煩
OpenMP 讓人可以平行處理, 又可以減少管理的麻煩

2. 對 for 做 parallel


底下是範例程式

#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel for
 for (int i=0; i<6; i++) {
  printf("%d ", i);
 }
 printf("\r\n");

 return 0;
}

然後compile它, 這邊使用c99來compile:

c99 -o openmp_example_01 openmp_example_01.c -fopenmp

幾個重點
  • 在 header 檔要加入 omp.h
  • 在 for 迴圈前加上 #pragma omp, 這個是用來使用omp的功能, 後面是控制敘述
  • compile 的時候加上 -fopenmp
結果可以看到
4 0 1 5 2 3

每次執行的結果都不一樣

3. 語法


它的語法大改長這樣

#pragma omp <directive> [clause[[,] clause] ...]

前個例子裡, 其實 parallel 和 for 都是 directive 的敘述, 所以它可以拆成兩個
#pragma omp parallel
{
 #pragma omp for
 for (int i=0; i<6; i++) {
  printf("%d ", i);
 }
}

可以看 wiki 上面的指令清單
OpenMP in wiki

4. thread information


如果要知道目前的 thread number, 可以用 omp_get_thread_num()
#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel for
 for (int i=0; i<6; i++) {
  printf("T:%d i:%d\r\n", omp_get_thread_num(), i );
 }

 return 0;
}

執行結果
T:2 i:4
T:1 i:2
T:1 i:3
T:0 i:0
T:0 i:1
T:3 i:5

可以看到我的電腦上, 它跑了4條thread, 其中 thread 0處理 i 是 0 & 1的情況, thread 1 處理 i 是 2 & 3 的情況....

5. 控制 thread number


可以用 num_threads(n) 來控制要開多少 thread
#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel for num_threads(2)
 for (int i=0; i<6; i++) {
  printf("T:%d i:%d\r\n", omp_get_thread_num(), i );
 }

 return 0;
}

執行結果可以看到只開2條thread了
T:1 i:3
T:1 i:4
T:1 i:5
T:0 i:0
T:0 i:1
T:0 i:2

6. 手動切割 section 做平行處理


#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel sections
 {
  #pragma omp section
  {
   printf("T:%d section 0\r\n", omp_get_thread_num());
  }
  #pragma omp section
  {
   printf("T:%d section 1\r\n", omp_get_thread_num());
  }
  #pragma omp section
  {
   printf("T:%d section 2\r\n", omp_get_thread_num());
  }
 }

 return 0;
}

執行結果
T:2 section 0
T:3 section 1
T:0 section 2

要注意的是 section 之間不可以有相依性, 不然要額外做處理

7. for 裡面處理共同變數


要注意的是 parallel for 只會將 section 裡的變數做平行處理,
section 外的變數則會當成是所有thread 共用的變數,
要讓 openmp知道 section 裡的變數是每個thread自己有一份的話, 則要加上private

#include <stdio.h>
#include <omp.h>

int main() {

 int i, j;

 #pragma omp parallel for private ( j )
 for (i=0; i<2; i++) {
  for (j=0; j<2; j++) {
   printf("(%d,%d)\r\n", i, j);
  }
 }

 return 0;
}

如果不加上private, 那麼當外面的 thread 在做處理時,
就有可能不小心把別的 thread 的變數 j 加了 1

如果希望 section 裡面每個 thread 共用一份變數,
並且希望它不會有處理先後順序造成的問題, 可以使用 atomic

#include <stdio.h>
#include <omp.h>

int main() {

 int count = 0;
 #pragma omp parallel for
 for (int i=0; i<10; i++) {
  #pragma omp atomic
  count++;
 }
 printf("count: %d\r\n", count);

 return 0;
}

如果沒有加上 atomic 的話, count 最後的結果就有可能不是10







2015/01/05

FreeRTOS Low Power Support

1. Introduction


在 FreeRTOS 裡, Power Saving 相關的工作通常會放在 idle task 裡面作, 它的作法是限制periodic tick interrupt, 這樣可以減少 period task的耗電

實際作法是, 它停止 periodic tick interrupt, 然後在 restart periodic tick interrupt實調整 RTOS tick count

在 tick interrupt 停止的時候, microcontroller 就可以進 deep power saving state, 直到有interrupt發生

2. Idle Task


idle task 放在 task.c
static portTASK_FUNCTION( prvIdleTask, pvParameters )

裡面有 infinite for loop 不停地執行一些工作, 包括:

  1. 檢查有沒有 task 已經被刪除
  2. 檢查 preemption 相關的工作
  3. 呼叫 idle hook funtion
  4. Power Saving Task

其中有兩個 hook function 會因為 power saving 的機制受到影響

  • vApplicationIdleHook : 在 idle task 裡每次呼叫
  • vApplicationTickHook : 在 idle task 裡, 並且只有在 sytem tick 增加時才呼叫
這兩個 hook function 都是直接 hard code 實作, 預設是空的, 為了避免影響其它task, 這兩個hook function 裡面不可以擺可能會 blocking 的 task

3. Power Saving


底下是 idle task 裡的 power saving code:
/* This conditional compilation should use inequality to 0, not equality
to 1.  This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
user defined low power mode implementations require
configUSE_TICKLESS_IDLE to be set to a value other than 1. */
#if ( configUSE_TICKLESS_IDLE != 0 )
{
TickType_t xExpectedIdleTime;

 /* It is not desirable to suspend then resume the scheduler on
 each iteration of the idle task.  Therefore, a preliminary
 test of the expected idle time is performed without the
 scheduler suspended.  The result here is not necessarily
 valid. */
 xExpectedIdleTime = prvGetExpectedIdleTime();

 if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
 {
  vTaskSuspendAll();
  {
   /* Now the scheduler is suspended, the expected idle
   time can be sampled again, and this time its value can
   be used. */
   configASSERT( xNextTaskUnblockTime >= xTickCount );
   xExpectedIdleTime = prvGetExpectedIdleTime();

   if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
   {
    traceLOW_POWER_IDLE_BEGIN();
    portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
    traceLOW_POWER_IDLE_END();
   }
   else
   {
    mtCOVERAGE_TEST_MARKER();
   }
  }
  ( void ) xTaskResumeAll();
 }
 else
 {
  mtCOVERAGE_TEST_MARKER();
 }
}
#endif /* configUSE_TICKLESS_IDLE */

它第一步先計算預計會idle的時間 xExpectedIdleTime, 如果這個時間大於 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 才真的 sleep, 不然的話, idle時間太短又跑去sleep會造成額外的 effort

接著 vTaskSuspendAll(), 這裡將 uxSchedulerSuspended 加 1, 於是task scheduler就被停止

然後重新計算 idle time, 接著進入sleep
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();

最後 xTaskResumeAll(), 將 uxSchedulerSuspended 減 1, 將所有應該要切入 ready state 的 task 喚醒, 最後將 system tick 加回來

4. portSUPPRESS_TICKS_AND_SLEEP


portSUPPRESS_TICKS_AND_SLEEP() 這個macro根據不同的MCU而有不同的實作, 在GCC, IAR, Keil都有default的實作

通常 SysTick 跟 Cortex-M Microcontrollers Clock 是一樣的 frequency, 這種情況下, 這兩個值是一樣的, configSYSTICK_CLOCK_HZ 以及 configCPU_CLOCK_HZ

如果 SysTick 比 Core Clock還慢的話, 就要定義 configSYSTICK_CLOCK_HZ 這個值

其中sleep的機制會呼叫到Cortex-M的 function call
/* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
set its parameter to 0 to indicate that its implementation contains
its own wait for interrupt or wait for event instruction, and so wfi
should not be executed again.  However, the original expected idle
time variable must remain unmodified, so a copy is taken. */
xModifiableIdleTime = xExpectedIdleTime;
configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
if( xModifiableIdleTime > 0 )
{
 __DSB();
 __WFI();
 __ISB();
}
configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

其中會有個 pre & post processing, 中間sleep的部份取決於不同MCU而有不同的code

DSP 表示 Data Synchronization Barrier, 它將 processor目前做到一半的工作做完
WFI 表示 Wait For Interrupt, 它讓 processor 進 suspend, 直到底下其中一個發生:

  • non-masked interrupt 發生
  • 被 PRIMASK mask 的 interrupt 被 pending
  • Debug Entery 的 request

ISB 表示 Instruction Synchronization Barrier, 它將 processor 的pipe line清空, 這樣才不會拿到之前未執行的 code 來執行




2014/12/12

Symbols of object file

1. Symbol Table


在 ELF file 裡, 其中有些 section 它的 type 是 DYNSYM 或 SYMTAB, 裡面含有symbol table,
我們可以用readelf來dump這些symbols, 這邊以簡單的 hello.c 當範例, compile & link 之後
$ readelf --syms hello
Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

Symbol table '.symtab' contains 65 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000400238     0 SECTION LOCAL  DEFAULT    1 
     2: 0000000000400254     0 SECTION LOCAL  DEFAULT    2 
     3: 0000000000400274     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000400298     0 SECTION LOCAL  DEFAULT    4
....

會看到有兩個section有symbol table, 分別是 dynsym 以及 symtab

同樣的事情也可以用nm來做
$ nm hello
0000000000601040 B __bss_start
0000000000601040 b completed.6972
0000000000601030 D __data_start
0000000000601030 W data_start
......

會發現, 用 readelf 讀出來的 symbole item 比較多, 但是 readelf 裡面有不少是空的item, FILE type 的 item 也不會在 nm 裡出現

如果我們將檔案 strip 過, 會發現 symtab 被清掉了
$ strip hello

$ nm hello
nm: hello: no symbols

$ readelf --syms hello

Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__


2. Symbol type


用 nm dump 出來的 symbol 的 type , 如果是大寫表示global, 小寫表示local

text section

  • T : symbol 位於 text section
data section

  • D : symbol 位於已初始化的 data section
  • G : symbol 位於小物件使用的已初始化 data section. (NOTE: 有些環境用近距離symbol效率較好)
read-only data

  • R : symbol 位於read-only data section
BSS (non-initialize data)

  • B : symbol 位於未初始化 data section (BSS)
  • S : symbol 位於小物件使用的未初始化 data section
Weak  object

  • V : symbol 是 weak object
Weak symbol

  • W : symbol 是未解析 weak object 的 weak symbol
Shared

  • C : common symbol, 未初始化資料
Debug purpose

  • N : debug purpose symbol
  • - : symbol 是 a.out object file 的 stabs symbol (for debug information)
Absolute value

  • A : symbol 是absolute value, linking的時候不會改變
Undefined

  • U : undefined symbol, instance應該在其它object file或是shared library
Indirect reference

  • I : Indirect reference symbol, 是a.out 的 GNU extension feature
Unknown

  • ? : 






2014/12/08

NFC NXP 203 Tag content example

1. Plain text example


底下是用一般的 nfc tag writter 寫進ndef plain text "123456789"的tag content

page 0     0469b055 c22d3580 5a480000 e1101200
page 4     0103a010 440310d1 010c5402 656e3132
page 8     33343536 373839fe 00000000 00000000
page 12    00000000 00000000 00000000 00000000
page 16    00000000 00000000 00000000 00000000
page 20    00000000 00000000 00000000 00000000
page 24    00000000 00000000 00000000 00000000
page 28    00000000 00000000 00000000 00000000
page 32    00000000 00000000 00000000 00000000
page 36    00000000 00000000 00000000 00000000
page 40    00000000 00000000

可以看到它總共有42個page, 根據 NFX 203F 的 format, user data 存在 page 4~39
而page 0~3, 40~41 用於紀錄 ID, checksum, read only, counter

1.1 TLV Blocks


Tag 內容格式為 TLV (Tag, Length, Value)
Tag 的值為底下幾種

  • NULL TLV (0x00) : 用於對齊 memory
  • Lock Control TLV (0x01) : 對 lock bit 做進一步描述
  • Memory Control TLV (0x02) : 定義剩下的memory
  • NDEF Message TLV (0x03)
  • Proprietary TLV (0xFD)
  • Terminator TLV (0xFE) : 最後一個TLV
上面的例子裡, 可以看到3個TLV
TLV 1: (Lock Control)
01 03 a01044

TLV 2: (NDEF Message)
03 10 d1010c5402656e313233343536373839

TLV 3: (Terminator)
fe


1.2 NDEF Message


在 NDEF TLV 的 VALUE 為 NDEF message
d1010c5402656e313233343536373839

第一個byte 0xd1為 NDEF Flag + TNF

  • MB = 1
  • ME = 1
  • CF = 0
  • SR = 1
  • IL = 0
  • TNF = 1

接下來的 1 個byte是 Type Length
Type Length = 0x01

因為 SR (Short Record) 為 1, 所以接下來的Payload length 長度為1個byte
Payload Length = 0x0c

因為 IL (ID Length is present) 為0, 所以沒有 ID 相關的欄位

接著是1個 byte 的 Payload Type
Payload Type = 0x54 (WELL-KNOWN: urn:nfc:wkt:T (Text))

最後是NDEF Text content:
02 65 6e 31 32 33 34 35 36 37 38 39
   E  N  1  2  3  4  5  6  7  8  9

第一個byte 0x02為Status

  • bit 7 : 0表示UTF-8 encoded, 1表示UTF16 encoded
  • bit 6 : RFU (must be set to zero)
  • bit 5..0 : IANA language code的長度
可以看到它用UTF-8, IANA language code的長度為2
所以接著2個byte為 0x65, 0x6e 為 "EN" 是它的 IANA language code
接著是我們的message "123456789"













2014/11/25

objdump

1. Use objdump dump ELF binary


如果想 dump 每個 section 的 binary, 可以用objdump

$ objdump --full-contents /bin/ls
Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.
Contents of section .note.ABI-tag:
 400254 04000000 10000000 01000000 474e5500  ............GNU.
 400264 00000000 02000000 06000000 18000000  ................
Contents of section .note.gnu.build-id:
 400274 04000000 14000000 03000000 474e5500  ............GNU.
 400284 64d095bc 6589dd4b fbf1c6d6 2ae98538  d...e..K....*..8
 400294 5965461b                             YeF.
Contents of section .gnu.hash:
 400298 03000000 72000000 02000000 07000000  ....r...........
 4002a8 a201400c 12010c3f 28440003 a8040000  ..@....?(D......
 4002b8 72000000 75000000 7e000000 281d8c1c  r...u...~...(...
 4002c8 4245d5ec bbe3927c bc50769e 86f0967c  BE.....|.Pv....|
 4002d8 96a08997 3cad390d d871581c ce2c6372  ....<.9..qX..,cr
 4002e8 e46241f5 b88df10e 39f28b1c 32c4f712  .bA.....9...2...
 4002f8 ead3ef0e b3a2f712                    ........

但示通常會印出一堆, 所以通常會看特定的section
可以用 objdump 找 section
$ objdump --section-headers /bin/ls

/bin/ls:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000400238  0000000000400238  00000238  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  0000000000400254  0000000000400254  00000254  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000400274  0000000000400274  00000274  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     00000068  0000000000400298  0000000000400298  00000298  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000c18  0000000000400300  0000000000400300  00000300  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       00000593  0000000000400f18  0000000000400f18  00000f18  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  00000102  00000000004014ac  00000000004014ac  000014ac  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000090  00000000004015b0  00000000004015b0  000015b0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rela.dyn     000000a8  0000000000401640  0000000000401640  00001640  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.plt     00000a80  00000000004016e8  00000000004016e8  000016e8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         0000001a  0000000000402168  0000000000402168  00002168  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000710  0000000000402190  0000000000402190  00002190  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         0000f65a  00000000004028a0  00000000004028a0  000028a0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
......

找到想要印的section
$ objdump --full-contents --section .interp /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.

也可以指定範圍, 比如說 .interp 從 0x400238 到 0x400253
$ objdump --full-contents --start-address=0x400238 --stop-address=0x400253 /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e32             x86-64.so.2

如果指定的範圍超過 section 邊界的話, 它會將跨過的 section 名字也印出來
$ objdump --full-contents --start-address=0x400238 --stop-address=0x400280 /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.
Contents of section .note.ABI-tag:
 400254 04000000 10000000 01000000 474e5500  ............GNU.
 400264 00000000 02000000 06000000 18000000  ................
Contents of section .note.gnu.build-id:
 400274 04000000 14000000 03000000           ............

如果只想把 ELF 檔案當 binary 來印, 或是想印某些純 binary 檔
$ objdump --full-contents -b binary /bin/ls


2. Use objdump to disassemble file


假如有個 "hello.c", 內容如下:
// hello.c
#include <stdio.h>

int main() {
        printf("Hello World\n");
        return 0;
}

然後 compile : "gcc -c -g hello.c", "gcc -o hello hello.o"

可以用 objdump 反組譯
$ objdump -d hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
   e:   b8 00 00 00 00          mov    $0x0,%eax
  13:   5d                      pop    %rbp
  14:   c3                      retq

使用 -d (--disassemble) 反組譯, 通常只會包含執行碼的 section (.text之類的)
如果想反組譯所有 section, 可以使用 -D ( --disassemble-all ), 但是會把不是程式碼的部份也當成程式碼反組譯

反組譯的輸出格式是
address <symbol>
    address:    code byte sequence        disassemble result

如果不想看到 code byte sequence
$ objdump -d --no-show-raw-insn hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   push   %rbp
   1:   mov    %rsp,%rbp
   4:   mov    $0x0,%edi
   9:   callq  e <main+0xe>
   e:   mov    $0x0,%eax
  13:   pop    %rbp
  14:   retq


如果想看到逐行加上 symbol 的位置
$ objdump -d --prefix-address hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:
0000000000000000 <main> push   %rbp
0000000000000001 <main+0x1> mov    %rsp,%rbp
0000000000000004 <main+0x4> mov    $0x0,%edi
0000000000000009 <main+0x9> callq  000000000000000e <main+0xe>
000000000000000e <main+0xe> mov    $0x0,%eax
0000000000000013 <main+0x13> pop    %rbp
0000000000000014 <main+0x14> retq

此時 code byte sequence 就會被忽略, 如果想加回code byte sequence
$ objdump -d --prefix-address --show-raw-insn hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:
0000000000000000 <main> 55                      push   %rbp
0000000000000001 <main+0x1> 48 89 e5                    mov    %rsp,%rbp
0000000000000004 <main+0x4> bf 00 00 00 00              mov    $0x0,%edi
0000000000000009 <main+0x9> e8 00 00 00 00              callq  000000000000000e <main+0xe>
000000000000000e <main+0xe> b8 00 00 00 00              mov    $0x0,%eax
0000000000000013 <main+0x13> 5d                         pop    %rbp
0000000000000014 <main+0x14> c3                         retq


如果只想反組譯特定的 section
$ objdump -d --section .init hello

hello:     file format elf64-x86-64


Disassembly of section .init:

00000000004003e0 <_init>:
  4003e0:       48 83 ec 08             sub    $0x8,%rsp
  4003e4:       48 8b 05 0d 0c 20 00    mov    0x200c0d(%rip),%rax        # 600ff8 <_DYNAMIC+0x1d0>
  4003eb:       48 85 c0                test   %rax,%rax
  4003ee:       74 05                   je     4003f5 <_init+0x15>
  4003f0:       e8 3b 00 00 00          callq  400430 <__gmon_start__@plt>
  4003f5:       48 83 c4 08             add    $0x8,%rsp
  4003f9:       c3                      retq

反組譯的部份跟之前一樣, 也可以設定 --start-address 以及 --stop-address

如果 object file 包含 debug information, 加上 -l (--line-numbers) 可以把指令與原始碼行號對應資訊印出來, 但如果 object file 沒有 debug information 就沒有任何效果
$ objdump -d -l hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
main():
/home/william/temp/hello.c:3
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
/home/william/temp/hello.c:4
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
/home/william/temp/hello.c:5
   e:   b8 00 00 00 00          mov    $0x0,%eax
/home/william/temp/hello.c:6
  13:   5d                      pop    %rbp
  14:   c3                      retq


如果加上 -S (--source), 如果找的到原始檔就會顯示對應的原始碼, 但如果 object file 沒有 debug information 就沒有作用

$ objdump -d -S hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
#include <stdio.h>

int main() {
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
        printf("Hello World\n");
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
        return 0;
   e:   b8 00 00 00 00          mov    $0x0,%eax
}
  13:   5d                      pop    %rbp
  14:   c3                      retq

-S 與 -l 可以同時使用
其中可以注意的是, 在 printf 下面的 address 4, bf 00 00 00 00, 後面的位置都是00, 是因為檔案還沒 reallocate, 在 linking 之後才會嵌入位置
$ objdump -S hello

......
000000000040052d <main>:
  40052d:       55                      push   %rbp
  40052e:       48 89 e5                mov    %rsp,%rbp
  400531:       bf d4 05 40 00          mov    $0x4005d4,%edi
  400536:       e8 d5 fe ff ff          callq  400410 <puts@plt>
  40053b:       b8 00 00 00 00          mov    $0x0,%eax
  400540:       5d                      pop    %rbp
  400541:       c3                      retq
  400542:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  400549:       00 00 00
  40054c:       0f 1f 40 00             nopl   0x0(%rax)
......

可以看到執行檔裡已經嵌入位置 bf d4 05 40 00



















FreeRTOS Interrupt Management

1. Introduction


關於處理event, 有幾個要考慮的點

  • 要怎麼偵測event? 通常會用 interrupt, 但也可以用polling的方式
  • 如果使用 interrupt, 要在 ISR (Interrupt service routine) 裡面處裡多少事情? 相對地要在ISR外面處理多少事情? 是否 ISR 裡面的事情要愈少愈好?
  • ISR 裡的 code 要如何跟外面的 code 溝通? 尤其是 asynchronous 的code
FreeRTOS 沒有規定 event handle要怎麼實作, 但提供了一些interrupt API
在這些API裡, 只有 FromISR 結尾的 function 才可以在 ISR 裡呼叫, 以及以 FROM_ISR 結尾的 macro 可以在ISR裡使用

2. Binary Semaphores used for Synchronization


整個概念如下圖, 當 interrupt 發生時, ISR 解開 semaphore 並讓 handler task 變成 ready state, 然後 ISR return, 這時 handler task 有最高的 priority 並執行, 當它執行完, 等下一個 semaphore, 進入block state, 並讓低 priority 的 task 執行



在使用 semaphore 時, 分成 take & give, 實作上, 它就像是長度為1的queue, 它一樣要設定 block time, 相關的限制也相同

2.1 Create Semaphore


void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore );

xSemaphoreHandle 是 semaphore 的型態, 在使用它之前必需先 create

2.2 Take Semaphore


portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore, portTickType xTicksToWait );

如果把 xTicksToWait 設成 portMAX_DELAY, 表示要永遠等下去

xSemaphoreTake 不能在 ISR 裡面使用

可能的回傳值 :
  • pdPASS : 成功拿到 semaphore, 如果 block time > 0, 表示在 timeout 前拿到 semaphore
  • pdFALSE : 沒能在 block time timeout 前拿到 semaphore

2.3 Give Semaphore in ISR


portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore,
                                     portBASE_TYPE *pxHigherPriorityTaskWoken
                                     );

如果有 high priority task 在等這個 semaphore, 然後呼叫 xSemaphoreGiveFromISR 造成目前的 task 被 scheduler switch out, 那麼會把 pxHigherPriorityTaskWoken 這個 pointer 裡面的值設成 pdTRUE, 表示要處裡目前要被 switch out 的 task 的 context switch

可能的回傳值 :

  • pdPASS : 成功
  • pdFALSE : 如果目前的 semaphore 已經被 give 了, 會回傳失敗

2.4 sample code


static void vPeriodicTask( void *pvParameters )
{
    for( ;; )
    {
        /* 等 500ms 準備送出 sofeware interrupt */
        vTaskDelay( 500 / portTICK_RATE_MS );

        /* 送出 interrupt, 前後夾 log */
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        __asm{ int 0x82 } /* This line generates the interrupt. */
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

static void vHandlerTask( void *pvParameters )
{
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* take semaphore */
        xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
        vPrintString( "Handler task - Processing event.\r\n" );
    }
}

static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;

 /* 先設成 false, 這樣如果有 task 被switch out時會設成ture, 才能分辨 */
    xHigherPriorityTaskWoken = pdFALSE;

    /* give semaphore */
    xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        /* 做 context switch, 其中 portSWITCH_CONTEXT() 是 Open Watcom Dos 的port,
           其它平台可能有不同的 port */
        portSWITCH_CONTEXT();
    }
}

int main( void )
{
    /* 建 semaphore */
    vSemaphoreCreateBinary( xBinarySemaphore );

    /* 設定 interrupt handler */
    _dos_setvect( 0x82, vExampleInterruptHandler );

    if( xBinarySemaphore != NULL )
    {
        /* 建立要take semaphore 的 task, 它的 priority 是 3 */
        xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );

        /* 建立定期發出 software interrupt 的 task */
        xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );

        vTaskStartScheduler();
    }
    /* it should never reach here */
    for( ;; );
}

3. Counting semaphores


使用 binary semaphore 的缺點是, 在 ISR unblock binary semaphore之後, 並且讓handle task執行時, 如果這時又再有一個 interrupt, 這時候 handle task也只會做最初的那次, counting semaphore 可以解決這種情況

可以將 counting semaphore 想成是長度為n的queue, 只是我們不在乎裡面的data

通常 counting semaphore 用在兩個用途: 記錄次數, 與管理資源

底下是它的 api

xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount,
                                           unsigned portBASE_TYPE uxInitialCount );

其中 uxMaxCount 是最大值, uxInitialCount是初始值, 成功的話回傳 semaphore handle


4. Use queue in ISR


在 ISR 裡使用 queue, 有相對應的 function:

portBASE_TYPE xQueueSendToFrontFromISR( xQueueHandle xQueue,
                                        void *pvItemToQueue
                                        portBASE_TYPE *pxHigherPriorityTaskWoken
                                        );

portBASE_TYPE xQueueSendToBackFromISR( xQueueHandle xQueue,
                                       void *pvItemToQueue
                                       portBASE_TYPE *pxHigherPriorityTaskWoken
                                       );

BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
                                 void *pvBuffer,
                                 BaseType_t *pxHigherPriorityTaskWoken
                                 );

這些其實就是 xQueueSendToFront(), xQueueSendToBack() 以及 xQueueReceive()

4.1 Sample code


static void vIntegerGenerator( void *pvParameters )
{
    portTickType xLastExecutionTime;
    unsigned portLONG ulValueToSend = 0;
    int i;

    /* Initialize the variable used by the call to vTaskDelayUntil(). */
    xLastExecutionTime = xTaskGetTickCount();
    for( ;; )
    {
        /* 每 200ms block */
        vTaskDelayUntil( &xLastExecutionTime, 200 / portTICK_RATE_MS );

        /* 寫入queue 5次, 這些值將從ISR讀出 */
        for( i = 0; i < 5; i++ )
        {
            xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
            ulValueToSend++;
        }
        /* 觸發 software interrupt 0x82 */
        vPrintString( "Generator task - About to generate an interrupt.\r\n" );
        __asm{ int 0x82 } /* This line generates the interrupt. */
        vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;
    static unsigned long ulReceivedNumber;

    /* 宣告成 static const 確保它們不是 ISR 產生的 */
    static const char *pcStrings[] = {"St0\r\n", "St1\r\n", "St2\r\n", "St3\r\n"};
    xHigherPriorityTaskWoken = pdFALSE;

    /* Loop until the queue is empty. */
    while( xQueueReceiveFromISR( xIntegerQueue, &ulReceivedNumber, &xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
    {
        /* Mask最後兩個bit, 然後把對應的String放進queue裡 */
        ulReceivedNumber &= 0x03;
        xQueueSendToBackFromISR( xStringQueue, &pcStrings[ ulReceivedNumber ], &xHigherPriorityTaskWoken );
    }

    /* 如果寫入 queue 造成 high priority task 變成 ready, 就要做context switch */
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        /* NOTE: 這個context switch 是給 Open Watcom DOS port 使用, 其它平台有不同的port */
        portSWITCH_CONTEXT();
    }
}

static void vStringPrinter( void *pvParameters )
{
    char *pcString;
    for( ;; )
    {
        /* Block on the queue to wait for data to arrive. */
        xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
        vPrintString( pcString );
    }
}

int main( void )
{
    /* 建queue */
    xIntegerQueue = xQueueCreate( 10, sizeof( unsigned long ) );
    xStringQueue = xQueueCreate( 10, sizeof( char * ) );

    /* 設定 interrupt handler. */
    _dos_setvect( 0x82, vExampleInterruptHandler );

    /* low priority period task */
    xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );

    /* High priority interrupt handler */
    xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );

    vTaskStartScheduler();
    for( ;; );
}