1. 程式人生 > >共享記憶體跨程序通訊

共享記憶體跨程序通訊

通過共享記憶體通訊是最快的,不過既然是共享資源,那麼就必須要有同步機制。

建立共享記憶體有兩種方式shm和mmap的方式。

  1. mmap是在磁碟上建立一個檔案,每個程序地址空間中開闢出一塊空間進行對映。
  2. 而對於shm而言,shm每個程序最終會對映到同一塊實體記憶體。shm儲存在實體記憶體,這樣讀寫的速度要比磁碟要快,但是儲存量不是特別大。
  3. 相對於shm來說,mmap更加簡單,呼叫更加方便,所以這也是大家都喜歡用的原因。
  4. 另外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 }
  1. F_SETLK:獲取(l_type為F_RDLCK或F_WRLCK)或釋放由lock指向flock結構所描述的鎖,如果無法獲取鎖時,該函式會立即返回一個EACCESS或EAGAIN錯誤,而不會阻塞。
  2. F_SETLKW:F_SETLKW和F_SETLK的區別是,無法設定鎖的時候,呼叫執行緒會阻塞到該鎖能夠授權位置。
  3. F_GETLK:F_GETLK主要用來檢測是否有某個已存在鎖會妨礙將新鎖授予呼叫程序,如果沒有這樣的鎖,lock所指向的flock結構的l_type成員就會被置成F_UNLCK,否則已存在的鎖的資訊將會寫入lock所指向的flock結構中

https://blog.csdn.net/anonymalias/article/details/9197641