年代久遠的東西
好久沒做都快忘光了
以前學校學的東西都忘的差不多了
這麼多年來專案做了不少個
但也只有在每次專案剛開始的時候才會用到這些東西
時間一久就什麼都忘光了= ="
TL;DR
這篇是在記錄
在 Linux 中 POSIX shared memory 跟 dynamic shared library 合併使用的方式
軟硬體設備
其實這個不重要
重要的是你的 Linux 系統本體所放的 glibc
不要因為閹割太多而導致 ld.so 的功能有缺失或是整個不見
基本上這樣的 code 在 x86 或是 arm 都沒有問題
如果 ld.so 不存在的話基本上這篇不適合可以直接左轉上一頁!!
預期的目的
這次預期的目標是
在一個 shared library 中製作一個 shared memory
然後讓另外兩個獨立的執行檔可以共同存取它
在 Linux 中 IPC 其實有不少可以用
這次選用 POSIX 的方式也沒有什麼特別的原因
單純就是懶惰...(;‘∀’)
用到的檔案
這次總共 5 個檔案
foo.c / foo.h 用來製作 shared library 本人
test_dll.c 用來製作第一個執行檔
test_dll_2.c 用來製作第二個執行檔
makefile 用來編譯
Shared library
foo.h
這個檔案裡的東西實際上只是給人看的而已
這樣跟其他人在 co-work 的時候至少別人有東西可以參考
不過編譯跟執行實際上是不需要的
foo.c
因為這次的功能設計上來說
只會在開機的時候執行第一次之後就再也不會釋放
所以就偷懶直接把 init
放到了 constructor
裡面
然後把 deinit
放到了 destructor
裡面
實際上如果沒有特別的需求的話
下面這兩行並不需要
直接放到正常的流程裡面執行就好
static void __attribute__ ((constructor)) init_lib(void);
static void __attribute__ ((destructor)) deinit_lib(void);
上面這兩行的目的在於
當這個檔案被執行到的時候會先直接執行 constructor
的部分把 init()
做完
當這個檔案被生命週期到盡頭的時候會執行 destructor
的部分做 deinit()
不過 constructor / destructor 的部分不在這篇的範圍裡面所以跳過(;^ω^)
另外因為這次的 sample code 沒有什麼功能
所以實際上 foo.h 沒有也沒差
不過為了以後功能越加越多
所以還是先把它保留下來了
這個檔案是這篇的主角之一
Shared memory 實際上就是由他建立
然後讓其他執行檔案可以使用
主要是下面這三行
// create shared memory
int fd = shm_open("/posix_shared_memory", O_CREAT | O_RDWR | O_EXCL, 0600);
// resize
ftruncate(fd, ALLOCATE_SHARED_MEMORY_SIZE);
// mount
shared_memory_address = mmap(NULL, ALLOCATE_SHARED_MEMORY_SIZE, PORT_READ | PORT_WRITE, MAP_SHARED, fd, 0);
- 先配置 shared memory
- 把這個記憶體空間擴展成指定大小
- 掛載取得 address
圖中的 if (fd >= 0)
是因為當兩個執行檔呼叫的時候為了避免重複產生而做的基本檢查
fflush()
跟msync()
這純粹只是我們的系統會一直卡 buffer
所以硬是給他加上去的而已 = ="
然後只要是遇到非同步讀寫都會遇到搶資源的問題
所以還是需要做點保護
至少保證 write after read 或 read after write
所以在 init()
的最後弄了一個 semaphore 用來卡存取狀態
這個鎖之後會給另外兩個獨立的執行檔使用
同樣偷懶的直接用 POSIX 的 semaphore
sem_t " semaphore_mutex = NULL;
semaphore_mutex = sem_open("/posix_semaphore", O_RDWR | O_CREAT, 0600, 1);
另外有個奇妙的地方
就是shm_open()
跟sem_open()
根據 man 所提供的說明
name 應該都要是 “/” 開頭的字串
不過實際上試了一下就算不加 “/”
他還是會掛到相對應的位置
理論上應該都可以在 /dev/shm
底下找到他們
在deinit()
中放入munmap()
實際上 munmap()
應該在存取完之後就直接放掉
不過這邊為了測試所以把它留到了 deinit()
的時候才執行
test_dll.c
#include <dlfcn.h>/* for dl* function */
這個檔案用到的
dlopen()
、dlsym()
、dlclose()
都是這裡面的東西
然後 load library
// libfoo.so 是用前面的 foo.c 產生出來的
// 理論上它在哪邊都沒有問題
// 只要找的到他本人就可以
void * handle = dlopen("./libfoo.so", RTLD_LAZY);
這和一般的 shared library 不同
一般的 shared library 在程式開始的時候就會去檢查 shared library 在不在了
而使用 dlopen()
載入 function 的方式
會讓 libfoo.so 只有在執行到這行的時候才會被載入
在存取 shared memory 之前
先卡一個 semaphore
// 開一個 semaphore 用來卡讀寫狀態
sem_t * sem_mutex = sem_open("/posix_semaphore", O_RDWR | O_CREAT, 0600, 1);
// 等待進入 critical section
sem_wait(sem_mutex);
個人習慣在 mmap()
之前就先上鎖了
雖然這樣會讓 critical section 變得很長
但是為了確保 mmap()
在動作時不會在中途有資料沒有被更新
導致資料不同步的問題
所以個人習慣會把鎖加在 mmap()
之前
實際上 cached/uncached 的同步還有很多東西要做
不過那不是這篇的重點所以也是先跳過(;^ω^)
然後 shm_open()
→ftruncate()
→mmap()
這裡的步驟跟前面的 foo.c 是一樣的就先略過了
接下來就是對 mmap()
返回的位置做存取了
操作完這塊空間之後就可以用 munmap()
把這塊空間放掉了
通常卡到 cached/uncached 的時候
都會在 munmap()
之前多做 invalidate 跟 flush 的動作
這樣才有辦法保證資料有確實寫入 uncached 的空間中
釋放完空間之後
就要趕快把 semaphore 給打開
不然鎖卡太久可能會造成其他問題
// 解鎖離開 critical section
sem_post(sem_mutex);
最後 shared library 確認用完之後
就可以把它給卸載了
// 釋放 shared library
dlclose(handle);
這裡沒有用到 sem_unlink()
是因為我們的系統預設沒有需要用到這裡的地方
所以就直接忽略掉沒使用了…OTL
test_dll_2.c
這個檔案跟 test_dll.c 大部分是一樣的
這個只是用來當作第二個獨立執行檔用的而已
makefile
編譯 libfoo.so
的時候要 -fPIC
不然程式載入不同系統時位置會出問題
然後加 -shared
給 LD
編譯執行檔的時候要加 -lrt -lpthread
這樣才能用 shared library
然後加 -ldl
這樣 dl* function
才能有作用
執行
./test &
./test2
執行完之後可以在 /dev/shm
底下
找到 posix_shared_memory
跟 sem.posix_semaphore
原因是因為程式執行完
沒有執行 shm_unlink()
跟 sem_unlink()
所以最後這兩個物件都沒有被消除
不過這兩個物件在重開機之後就會不見了
所以以我們的系統來說其實用不太到
其他
我們 target 的系統竟然沒有 /dev/shm
這個路徑
所以一開始的時候一直出現 bus error
不過後來手動建立這個路徑之後就沒問題了