共享記憶體跨程序通訊
阿新 • • 發佈:2018-12-03
通過共享記憶體通訊是最快的,不過既然是共享資源,那麼就必須要有同步機制。
建立共享記憶體有兩種方式shm和mmap的方式。
- mmap是在磁碟上建立一個檔案,每個程序地址空間中開闢出一塊空間進行對映。
- 而對於shm而言,shm每個程序最終會對映到同一塊實體記憶體。shm儲存在實體記憶體,這樣讀寫的速度要比磁碟要快,但是儲存量不是特別大。
- 相對於shm來說,mmap更加簡單,呼叫更加方便,所以這也是大家都喜歡用的原因。
- 另外mmap有一個好處是當機器重啟,因為mmap把檔案儲存在磁碟上,這個檔案還儲存了作業系統同步的映像,所以mmap不會丟失,但是shmget就會丟失。
shm的建立要確保原子性的話,可以通過重新命名來做。
https://segmentfault.com/a/1190000000630435
1 char* SharedMemory::CreateMapping(const std::string file_name, unsigned mapping_size, bool &is_new) { 2 char* mapping = (char*)MAP_FAILED; 3 int fd = -1; 4 fd = open(file_name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0666); // 同步O_EXCL 5 if (fd == -1) { 6 fd = open(file_name.c_str(), O_RDWR, 0666); 7 if (fd < 0) { 8 return mapping; 9 } 10 } 11 12 struct stat file_stat; 13 if(fstat(fd, &file_stat)== -1) { 14 close(fd); 15 return mapping; 16 } 17 int file_size = file_stat.st_size;18 is_new = false; 19 if (file_size == 0) { 20 file_size = mapping_size; 21 ftruncate(fd, file_size); 22 is_new = true; 23 } 24 mapping = (char*)mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 25 if (is_new) { 26 memset(mapping, 0, sizeof(char) * file_size); 27 } 28 close(fd); 29 return mapping; 30 }
這裡用O_CREAT | O_EXCL來確保只建立一次檔案,如果建立失敗就以rw的方式來開啟。
互斥量同步
跨程序的同步機制,根據APUE 15.9節提到的,可以有三種方式,帶undo的訊號量、記錄鎖、互斥量。pthread帶的跨程序互斥量需要高版本支援。
1 bool SharedMemory::Init() { 2 bool is_new = false; 3 mutex_ = (pthread_mutex_t *)CreateMapping(file_name_ + ".lock", sizeof(pthread_mutex_t), is_new); 4 if (mutex_ == MAP_FAILED) { 5 return false; 6 } 7 if (is_new) { 8 InitLock(); 9 } 10 is_init_ = true; 11 return true; 12 } 13 14 void SharedMemory::InitLock() { 15 pthread_mutexattr_t attr; 16 pthread_mutexattr_init(&attr); //~necessary, or weird EINVAL error occurs when operating on the mutex 17 pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 18 pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); 19 pthread_mutex_init(mutex_, &attr); 20 } 21 22 void SharedMemory::Lock() { 23 if (!is_init_) { 24 return; 25 } 26 while (EOWNERDEAD == pthread_mutex_lock(mutex_)) { 27 pthread_mutex_consistent(mutex_); 28 pthread_mutex_unlock(mutex_); 29 } 30 } 31 32 void SharedMemory::Unlock() { 33 if (!is_init_) { 34 return; 35 } 36 pthread_mutex_unlock(mutex_); 37 }
pthread_mutex_consistent這個函式有版本限制。
如果持有 mutex 的執行緒退出,另外一個執行緒在 pthread_mutex_lock 的時候會返回 EOWNERDEAD。這時候你需要呼叫 pthread_mutex_consistent 函式來清除這種狀態,否則後果自負。
https://segmentfault.com/a/1190000000630435
pthread_mutexattr_setpshared配合PTHREAD_PROCESS_SHARED可以建立跨程序的mutex,但是必需保證mutex所在的記憶體區域可以被每個程序訪問,也就是說必需被建立在程序間共享的記憶體區域中,比如mmap建立的共享記憶體。
https://segmentfault.com/q/1010000000628904
記錄鎖
記錄鎖的功能:當一個程序正在讀或修改檔案的某個部分是,它可以阻止其他程序修改同一檔案區。
記錄鎖是更常用的方式。因為它沒有版本限制,程序退出時會自動釋放鎖。
1 void SharedMemory::InitLock(short type) { 2 if (lock_fd_ < 0) { 3 return; 4 } 5 struct flock lock; 6 lock.l_type = type; 7 lock.l_whence = SEEK_SET; 8 lock.l_start = 0; 9 lock.l_len = 0; 10 int ret = fcntl(lock_fd_, F_SETLKW, &lock); 11 //printf("InitLock %d \n", ret); 12 } 13 14 void SharedMemory::LockWrite() { 15 if (!is_init_) { 16 return; 17 } 18 19 InitLock(F_WRLCK); 20 } 21 22 void SharedMemory::LockRead() { 23 if (!is_init_) { 24 return; 25 } 26 27 InitLock(F_RDLCK); 28 } 29 30 void SharedMemory::Unlock() { 31 if (!is_init_) { 32 return; 33 } 34 InitLock(F_UNLCK); 35 }
- F_SETLK:獲取(l_type為F_RDLCK或F_WRLCK)或釋放由lock指向flock結構所描述的鎖,如果無法獲取鎖時,該函式會立即返回一個EACCESS或EAGAIN錯誤,而不會阻塞。
- F_SETLKW:F_SETLKW和F_SETLK的區別是,無法設定鎖的時候,呼叫執行緒會阻塞到該鎖能夠授權位置。
- F_GETLK:F_GETLK主要用來檢測是否有某個已存在鎖會妨礙將新鎖授予呼叫程序,如果沒有這樣的鎖,lock所指向的flock結構的l_type成員就會被置成F_UNLCK,否則已存在的鎖的資訊將會寫入lock所指向的flock結構中