|Jobs| 系統廠工程師需要具備的 Linux 相關知識

  • IPC 溝通有許多方式(兩個 process 互相交換資料的方法)
  • 何時要開 proccess,何時用 multi-thread 就好
  • In C language, system()
  • Makefile 如何寫
  • 系統默認路徑
    • Include 系統默認搜尋範圍: /usr/include/usr/local/include
    • Lib 預設目錄: /lib、/usr/lib、/usr/local/lib
      • 環境變數 LD_LIBRARY_PATH 中指向搜索路徑
      • /etc/ld.so.conf 文件中增加搜索目錄
    • 執行檔預設目錄: 如果不加 ./ ,預設是執行 /bin、/usr/bin

|Code| 如何在 C++ 程式使用 C api ?

C++ 可以引用其他 C語言寫的 api,這不意外。

但要怎麼做?

C++:

請在把要使用的 C api 的 header 用 extern “C” 包起來

extern "C"
{
#include "demo_c_api_A.h"
#include "demo_c_api_B.h"
}

 

而要被使用的 C api 的 header:

demo_c_api_A.h:

#ifndef DEMO_C_API_A_H
#define DEMO_C_API_A_H

#include "aaa.h"
#include <bbb.h>

#ifdef __cplusplus
extern "C"
{
#endif

...

(C 的 header 原本該寫的東西)

...

#ifdef __cplusplus
}
#endif

#endif

 

你也可以不要直接用 extern 標全部,單獨寫:

#ifndef DEMO_C_API_A_H
#define DEMO_C_API_A_H

#include "aaa.h"
#include <bbb.h>

extern int c_api_A_init();


(C 的 header 原本該寫的東西)

#endif
#endif

 

 

延伸:extern 是什麼?

ref. Why use #ifndef CLASS_H and #define CLASS_H in .h file but not in .cpp?

|Issue| dlopen ERROR: undefined symbol

我們可以利用 dlopen 載入動態函式庫,然後使用 dlsym 取得裡面的函式來使用。

這兩個方法使用方式可以參考:dlopen&dlsym 用法

有遇到載入失敗,出現:undefined symbol

後面接著一串很像函式名稱的東西。

那時候我猜測是有兩種可能:我函式有問題 (可是 compiler 應該不會過才對),或者,

沒有正確連結?

我使用 nm 去驗證我的想法。

nm 可以列出目標的所有 symbol,看是不是那個函式有在裏面,但有問題?還是那個函式真的沒有在裡面?

在 cmd line 執行:nm -C -D bad_lib.so

就會列出全部的 symbol。其中,-D 指的是察看的對象是動態檔唷!

 

還有一個小工具:ldd 順便介紹一下。

ldd (List Dynamic Dependencies) 可以尋找所使用的函式庫!

執行:ldd (選項) (參數)

就可以列出全部被用到的函式庫啦!

 

ref.

解決 undefined symbol / reference

 

|Issue| 將 .a 包進 .so後,無法使用 .so

Segmentation fault

–whole-archive:可以強制將每個對象包含在生成的共享函式庫裡

所以我把我要包進 .so 的 .a lib 都加上了這個參數

總算可以開啟 .so

*在 static 库中,连接器将停止在第一个符号,即使它是一个弱的,并停止寻找强大的。若要强制它查看所有符号( 就像对动态链接库所做的那样),ld 支持 --whole-archive 选项。

 

ref.

https://ask.helplib.com/others/post_12552910

|Code| Makefile 參數介紹

Makefile 裡面最複雜的就是很多符號以及參數不明白意思。

這邊稍微介紹一下。

跟編譯過程有關:

  • -c:編譯但不進行鏈結,會產生一個跟原始碼相同名字的 .o 檔
  • -O:表示最佳化的程度
  • -g:要包含偵錯資訊

跟連結有關:

  • -l :編譯過程需要一個 library。e.g. -lpthread 表示需要 libpthread.so 函式庫
  • -L:需要鏈結庫外部人家已經寫好的函式的目錄
  • -I : 優先搜尋的 include 檔案路徑

跟包成的目標函式庫有關:

  • -shared:如果目標為動態函式/共享函式庫,一定要加
  • -fPIC:包成動態函式
  • -static:包成靜態函式庫

其他字元:

%.o: %.cpp
g++ -Wall -g -c $^
  • $^:所有的必要條件(%.cpp)
  • $@:工作目標(%.o)
  • %:一個萬用字元,

e.g.:%.o: %.c 這一行,就會知道如果現在的工作目標是 demo.o 的話,就會去找對應的 demo.c

  • $<:第一個必要條件(%.cpp,本例子中只有一個必要條件)

傳遞參數:

  • -Wa : 將選項 (option) 傳給組譯器
  • -wl : 將選項 (option) 傳給連結器

備註:

*編譯不連結,表示只檢查 include 裡有沒有宣告,並不會去相關 lib 找函式真的是否存在

*動態函式跟共享函式差別在於:動態函式程式執行時期並不會去檢查該函式是否存在,而是程式執行到某功能時才進行檢查。

*編譯時,利用 -L 告訴編譯器可以該路徑下尋找 libpthread.so。

若使用了-l,則必須使用的 lib 在預設尋找的目錄中,

保險起見,可以利用 -L 指定多個路徑給編譯器。

ref.

http://maxubuntu.blogspot.com/2010/02/makefile.html

[Linux] 簡單的 Makefile 使用 (% 萬用字元、$@ 特殊符號、.PHONY 假目標)

|Code| 萬用物件指標 void* 是什麼 ?

寫習慣 C語言的一開始看到這個應該覺得莫名其妙。

不過這個在 C++可是非常實用的呢~~

他的用法有幾種,我目前知道的如下:

      • 未定型的指標,可以等到需要轉型時再指定型別:
        void *p1;
        int *p2;
        p1 = p2;
      • 也可以放在函式參數裡,最後再決定要傳入什麼類型的指標
        void * memcpy( void *dest, const void *src, size_t len );
        void * memset( void * buffer, int c, size_t num);
      • 給 callback function 使用:
        用在沒有回傳值的 event function
        例如:|Code| 使用 function pointer 建立 Handler
        裡面的 int demo_funcA(int, uint8_t);
        改為 void demo_funcA(int, uint8_t);
        typedef int (*cmd_handler)(int,uint8_t);
        就要改為:typedef void (*cmd_handler)(int,uint8_t);

     

  • p.s.在 C++中,函式參數為void的意思是這個函數不需要任何參數

很方便的東西,特別是搭配 callback function 用~~

|Code| 使用 function pointer 建立 Handler

有時候因為 Event 過多,不想寫太多 Switch case 或是 if … else,我們可以想個辦法 (學習晶片廠的施工方法) 整理成 table 的形式,方便維護。

#define COMMAND_A 1

#define COMMAND_B 2

int demo_funcA(int, uint8_t); // handle function 要呼叫的

int demo_funcB(int, uint8_t);

typedef int (*cmd_handler)(int,uint8_t); //要呼叫的 function,及要傳入的參數

typedef struct cmd_handler_entry{

int command;

cmd_handler handler;

}cmd_handler_entry;

 

/*

重點在這邊可以做成 table 的形式,後續維護很方便,不過要注意傳入參數的型態

*/

static cmd_handler_entry cmd_handler_entries[] = {

{ COMMAND_A, demo_funcA},

{ COMMAND_B, demo_funcB},

{ NULL, NULL}

};

 

int main(){

//在這函式中,需要下 COMMAND_B,並執行它對應的函數

cmd_handler_entry* msg_handler;

msg_handler = cmd_handler_entries;

while(msg_handler->handler != NULL){

if(msg_handler->command == COMMAND_B){

break;

}else{

msg_handler++;

}

}

if(msg_handler->handler != NULL){

//需要下 COMMAND_B

msg_handler->command = COMMAND_B;

// handler 會自動指向它的函式:demo_funcB,並傳入參數(123, ‘0x123’)

msg_handler->handler(123, ‘0x123’);

return 1;

}else{

printf(“Handler ERROR!”);

return 0;

}

}

 

|Code| 介紹基本的 Makefile 概念

這件事是這樣的:

我的 module 要在 Linux x86_64 上 complier ,且能夠在 OpenWrt arm64 上執行。

另一方面,我的 lib 會引用到其他的 lib。

所以我會遇到幾個問題:

  1. Cross complier 是什麼?什麼時候需要它?如何設定?
  2. 如何引用別人的模組
  3. 在 C++ 程式呼叫 C 語言的函式

—————————————————–

為了解決這問題,我們要先知道幾件事:

  • 認識編譯流程
  • 認識編譯目標
  • 如何寫 Makefile 並且 build 成 Library
  • Makefile 的參數,特別是 Link 相關 => 於下篇介紹

認識編譯流程

Step 1: 我們寫好的程式:*.c
Step 2: 形成機器看的:*.o
Step 3: 連結(Link) 成我們的目標

認識編譯目標

1. 執行檔:需在程式裡面有進入點 — main
2. 動態函式庫: *.so,這種函式庫的特徵在於:需要使用時才載入
這種函式庫的特徵在於:需要使用時才透過 dlopen 載入,之後使用 dlsym 取得函式
3. 靜態函式庫: *.a,這種函式庫的特徵在於:一開始就 build 成一包 lib
所以整個 lib 檔案會很大

如何寫 Makefile 並且 build 成 “執行檔”

#寫下你最後要產生的東西:執行檔? .a? .so? .o? .s?
#這邊是定義 target 叫做什麼
TARGET=AAA
#寫下編譯出來的 .o 叫什麼
SRC_OBJS := $(addsuffix .o, $(TARGET))
#寫下你會用到的 header
SRC_HEADERS = \
../../include/AAA.h \
../../include/aaa.h

#開始 make all 的動作,也就是要產生 AAA 這個執行檔
all: $(TARGET)

#這意思是:要 build 出 $(TARGET) (在這裡是 AAA)
# 需要 $(SRC_OBJS) (在這裡是 AAA.o)
$(TARGET): $(SRC_OBJS)

# Compile c source file
#這意思是:要 build 出 %.o (在這裡是 AAA.o) 需要 AAA.c, AAA.h, 和 aaa.h
%.o: %.c $(SRC_HEADERS)

# Other Targets
clean:
	-$(RM) *.o
	-$(RM) $(TARGET)
	-@echo ' '
#偽目標
.PHONY: all clean

build 成 Library 需要有其他參數,待下篇介紹參數如何寫。

來回答原本的問題:

  • Cross complier 是什麼?什麼時候需要它?如何設定?

=> 當你在編譯平台與執行平台不同時,就會需要它

e.g. Linux x86_64 上編譯 ,且要在 OpenWrt arm64 上執行

  • 如何引用別人的模組

=>  1. 在你的程式裡面,加上要引用的 lib 對應到的 header

2. 在 makefile 的 Link 的地方,指向要引用的 lib (可以是 .a, .so 看你的需求)

  • 在 C++ 程式呼叫 C 語言的函式

=> 這問題會跟 scope 有關,會跟 extern 一起介紹

 

ref.

https://blog.gtwang.org/programming/howto-create-library-using-gcc/

|Code| uint8_t vs unsigned char

越底層的程式,就要越善用資源 (空間+時間)

晶片廠的程式碼,很少直接: char 的,因為太浪費空間啦!!!

多半會用:uint8_t 表示 char 的東東

uint8_t = unsigned char

uint8_t 只會占用 8 bit (1 byte)

char 是 -128 ~ 127
unsigned char 是 0 ~ 255
extend ascii 是 0 ~ 255

 

現在 C 語言的寫法建議:

引用 stdint.h 函式庫,並使用具有帶號(signed)與長度資訊宣告方式:

  • 帶正負號的整數使用 int8_tint16_tint32_tint64_t
  • 不帶號的整數使用 uint8_tuint16_tuint32_tuint64_t
  • 32 與 64 位元長的浮點數使用 floatdouble

 

其他整理:

  1. char 可以直接 cast it to uint8_t
  2. uint8_t a = 5; printf(“%d”, a);

 

ref.

2016 年,現代 C 語言的寫法

WordPress.com.

Up ↑