摘要:前言我們知道,由于沒有多線程模型,所以更多的使用多進程模型,因此代碼相對來說更加簡潔,減少了各種線程鎖的阻塞與同步,但是也帶來了新的問題數據同步。相比多線程之前可以直接共享進程的內存,進程之間數據的相互同步依賴于共享內存。
前言
我們知道,由于 PHP 沒有多線程模型,所以 swoole 更多的使用多進程模型,因此代碼相對來說更加簡潔,減少了各種線程鎖的阻塞與同步,但是也帶來了新的問題:數據同步。相比多線程之前可以直接共享進程的內存,進程之間數據的相互同步依賴于共享內存。本文將會講解 swoole 中共享內存的源碼。
前置知識:
mmap 函數的使用: APUE 學習筆記——高級 IO
共享內存: APUE 學習筆記——進程間通信
共享內存數據結構typedef struct _swShareMemory_mmap { size_t size; char mapfile[SW_SHM_MMAP_FILE_LEN]; int tmpfd; int key; int shmid; void *mem; } swShareMemory;
注意 mem 是一個 void 類型的指針,用于存放共享內存的首地址。這個成員變量相當于面向對象中的 this 指針,通過它就可以訪問到 swShareMemory 的各個成員。
size 代表共享內存的大小(不包括 swShareMemory 結構體大小), mapfile[] 代表共享內存使用的內存映射文件的文件名, tmpfd 為內存映射文件的描述符。key 代表使用 System V 的 shm 系列函數創建的共享內存的 key 值, shmid 為 shm 系列函數創建的共享內存的 id(類似于fd),這兩個由于不是 POSIX 標準定義的 api,用途有限。
共享內存的申請與創建swoole 在申請共享內存時常常調用的函數是 sw_shm_malloc,這個函數可以為進程匿名申請一大塊連續的共享內存:
void* sw_shm_malloc(size_t size) { swShareMemory object; void *mem; size += sizeof(swShareMemory); mem = swShareMemory_mmap_create(&object, size, NULL); if (mem == NULL) { return NULL; } else { memcpy(mem, &object, sizeof(swShareMemory)); return mem + sizeof(swShareMemory); } }
從 sw_shm_malloc 函數可以看出,雖然我們申請的是 size,但是實際申請的內存是要略大的,因為還要加上 swShareMemory 這個結構體。當函數返回時,也不會直接返回申請的內存首地址,而是復制了 object 各個成員變量的值后,在申請的首地址上加上 swShareMemory 的大小。
void *swShareMemory_mmap_create(swShareMemory *object, size_t size, char *mapfile) { void *mem; int tmpfd = -1; int flag = MAP_SHARED; bzero(object, sizeof(swShareMemory)); #ifdef MAP_ANONYMOUS flag |= MAP_ANONYMOUS; #else if (mapfile == NULL) { mapfile = "/dev/zero"; } if ((tmpfd = open(mapfile, O_RDWR)) < 0) { return NULL; } strncpy(object->mapfile, mapfile, SW_SHM_MMAP_FILE_LEN); object->tmpfd = tmpfd; #endif #if defined(SW_USE_HUGEPAGE) && defined(MAP_HUGETLB) if (size > 2 * 1024 * 1024) { flag |= MAP_HUGETLB; } #endif mem = mmap(NULL, size, PROT_READ | PROT_WRITE, flag, tmpfd, 0); #ifdef MAP_FAILED if (mem == MAP_FAILED) #else if (!mem) #endif { swWarn("mmap(%ld) failed. Error: %s[%d]", size, strerror(errno), errno); return NULL; } else { object->size = size; object->mem = mem; return mem; } }
由于 swoole 的各個進程都是由 master 進程所建立,也就是各個進程之間存在親戚關系, 因此swShareMemory_mmap_create 函數直接以 匿名映射 、(/dev/zero 設備) 的方式利用 mmap 建立共享內存,并沒有 open 具體的共享內存文件,或者調用 shm_open 打開 POSIX IPC 名字。
值得注意的是 MAP_HUGETLB,這個是 linux 內核 2.6.32 引入的一個 flags,用于使用大頁面分配共享內存。大頁是相對傳統 4K 小頁而言的,一般來說常見的體系架構都會提供2種大頁大小,比如常見的 2M 大頁和 1G 大頁。使用大頁可以減少頁表項數量,從而減少 TLB Miss 的概率,提升系統訪存性能。當然有利必有弊,使用大頁降低了內存管理的粒度和靈活性,如果程序并不是對內存的使用量特別大,使用大頁還可能造成內存的浪費。
共享內存的 calloccalloc 與 malloc 大同小異,無非多了一個 num 參數
void* sw_shm_calloc(size_t num, size_t _size) { swShareMemory object; void *mem; void *ret_mem; int size = sizeof(swShareMemory) + (num * _size); mem = swShareMemory_mmap_create(&object, size, NULL); if (mem == NULL) { return NULL; } else { memcpy(mem, &object, sizeof(swShareMemory)); ret_mem = mem + sizeof(swShareMemory); bzero(ret_mem, size - sizeof(swShareMemory)); return ret_mem; } }共享內存的 realloc
realloc 函數用于修改已申請的內存大小,邏輯非常簡單,先申請新的內存,進行復制后,再釋放舊的內存:
void* sw_shm_realloc(void *ptr, size_t new_size) { swShareMemory *object = ptr - sizeof(swShareMemory); void *new_ptr; new_ptr = sw_shm_malloc(new_size); if (new_ptr == NULL) { return NULL; } else { memcpy(new_ptr, ptr, object->size); sw_shm_free(ptr); return new_ptr; } }修改共享內存的權限
在內存映射完成后,由標記讀、寫、執行權限的 PROT_READ、PROT_WRITE 和 PROT_EXEC 等權限仍可以被 mprotect 系統調用所修改。
int sw_shm_protect(void *addr, int flags) { swShareMemory *object = (swShareMemory *) (addr - sizeof(swShareMemory)); return mprotect(object, object->size, flags); }共享內存的釋放
void sw_shm_free(void *ptr) { swShareMemory *object = ptr - sizeof(swShareMemory); swShareMemory_mmap_free(object); } int swShareMemory_mmap_free(swShareMemory *object) { return munmap(object->mem, object->size); }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/29198.html
摘要:前言內存數據結構,類似于的通道,底層基于共享內存互斥鎖實現,可實現用戶態的高性能內存隊列。是當前隊列占用的內存大小,用來指定是否使用共享內存是否使用鎖是否使用通知。 前言 內存數據結構 Channel,類似于 Go 的 chan 通道,底層基于 共享內存 + Mutex 互斥鎖實現,可實現用戶態的高性能內存隊列。Channel 可用于多進程環境下,底層在讀取寫入時會自動加鎖,應用層不需...
摘要:如果互斥鎖的持有者死亡了,或者持有這樣的互斥鎖的進程了互斥鎖所在的共享內存或者持有這樣的互斥鎖的進程執行了調用,則會解除鎖定該互斥鎖。互斥鎖的下一個持有者將獲取該互斥鎖并返回錯誤。 前言 swoole_table 一個基于共享內存和鎖實現的超高性能,并發數據結構。用于解決多進程/多線程數據共享和同步加鎖問題。 swoole_table 的數據結構 swoole_table 實際上...
摘要:前言中為了更好的進行內存管理,減少頻繁分配釋放內存空間造成的損耗和內存碎片,程序設計并實現了三種不同功能的內存池,和。比較特殊的是單鏈表內存池的內存只能增加不會減少。 前言 Swoole 中為了更好的進行內存管理,減少頻繁分配釋放內存空間造成的損耗和內存碎片,程序設計并實現了三種不同功能的內存池:FixedPool,RingBuffer 和 MemoryGlobal。 其中 Memor...
摘要:的數據結構數據結構中是鏈表元素的個數,是緩沖區創建時,鏈表元素約定的大小實際大小不一定是這個值,是實際上緩沖區占用的內存總大小。中的有三種,分別應用于緩存數據發送文件提醒連接關閉三種情景。指的是元素的內存大小。 前言 swoole 中數據的接受與發送(例如 reactor 線程接受客戶端消息、發送給客戶端的消息、接受到的來自 worker 的消息、要發送給 worker 的消息等等)都...
摘要:如果在調用之前我們設置了,但是不在第二個進程啟動前這個套接字,那么第二個進程仍然會在調用函數的時候出錯。 前言 本節主要介紹 server 模塊進行初始化的代碼,關于初始化過程中,各個屬性的意義,可以參考官方文檔: SERVER 配置選項 關于初始化過程中,用于監聽的 socket 綁定問題,可以參考: UNP 學習筆記——基本 TCP 套接字編程 UNP 學習筆記——套接字選項 構造...
閱讀 1994·2021-11-15 18:09
閱讀 889·2021-09-06 15:13
閱讀 2636·2021-08-23 09:43
閱讀 2016·2019-08-30 15:54
閱讀 2209·2019-08-30 13:56
閱讀 2476·2019-08-26 11:31
閱讀 3070·2019-08-26 10:56
閱讀 685·2019-08-26 10:28