進程間通信---共享內存
進程間的通信方式我們都熟悉,管道(命名管道)、信號(signal)、共享內存、消息隊列、套接字(socket),關於信號量個人認為應該歸為進程間的同步機制裡。
下面我們就說說共享內存的通信方式。 它是IPC中最快的。一旦內存區映射到共享它的進程的地址空間,這些進程間數據的傳遞就不在涉及內核。但是存取數據的時候需要保持同步. 關於共享內存權威的參考資料為《unix網絡編程卷2》. 在前面我們講解了mmap的內核機制,在posix v的共享內存的方式就是這個原理。system v也類似,只不過對接口進行了封裝.
我們先從posix v的方式開始說起,它常用的函數接口:
open/shm_open + mmap 、munmap、msync、shm_unlink等.
既然是open那麼就會對應文件,當然並不是所有的文件都可以映射.這裡支持的文件類型:
1 .普通的磁盤文件
2. 設備文件(除去一些特殊的文件)
3.內存文件 (例如tmpfs)
4. 匿名映射 (主要用在具有親緣關系的進程間)
先說一下常用的匿名映射,直接open /dev/zero保證映射區域都初始化為0 的匿名映射。 還有可以直接用mmap來映射,而不用打開任何文件:
- mmap( NULL,size_of_map,PROT_READ|PROT_WRITE,MAP_SHARED | MAP_ANON, -1,0 );
這裡提示一下mmap的函數原型:
- void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
匿名映射主要區別是flags 裡多了一個 MAP_ANON標志,和fd傳遞的為-1. 關於更多mmap的資料請自行查詢.
下面說一下一般mmap方式的共享內存需要注意的:
映射的內存區域大小和文件大小,內存映射的大小都是頁面的整數倍(PAGESIZE 4096默認) 比如文件大小為5000,映射了5000 ,那麼可以訪問2*PAGESIZE的內存區域,但是大於5000的內存寫不會同步到文件裡。這個內存可以訪問的大小跟文件大小也是有關系的。映射的時候會自動檢查底層的支持即文件的大小. 但是我們知道mmap的方式的優點就是可以動態的改變文件的大小,函數接口為ftruncate和fstat. 具體用法參考unix網絡編程卷2.
下面就簡單總結下它的 特點:
1. 可以隨時改變其大小
操作函數接口:ftruncate 和fstat
2. 必須先創建打開一個文件為基礎 ,這個文件可以是磁盤文件 或者內存文件比如tmpfs下的文件;如果共享內存的配置需要寫入到磁盤 ,可以選擇打開磁盤文件。如果在嵌入式裡用一般會把flash一個文件作為映射 ,既然需要頻繁的操作文件 和磁盤讀寫 所以建議還是用shm。 畢竟flash頻繁讀寫影響壽命和容易出現壞塊。
而system v的方式不需要明確的open文件,但是函數操作接口比較多.
函數接口: shmget、shmat、shmdt、shmctl等。
shm方式有些限制:
shmax 一個共享內存區的最大字節數 (具體系統不太一樣)
shmmnb 一個共享內存區的最小字節數 1
shmmni 系統范圍最大共享內存區標識數 128
shmseg 每個進程附接的最大共享內存區數 32
這些信息可以通過proc文件系統來查看。cat /proc/sys/kernel/shmmax 等. 具體查看映射的內存區 可以通過ipcs命令來查看和操作.
我們也總結下它的特點:
1.不需要創建和打開文件
2. 如果是多進程間通信,需要統一 一個key標示 。動態生成的不一定一致。
3.讀寫的速度高於mmap的方式
4. 共享內存空間不能太大,畢竟直接占用內存空間.
不論哪種方式,都會涉及多進程間的通信、數據交互什麼的,那麼就必須保持互斥和同步,這裡最常用的方式是采用信號量的方式.
信號量的接口:sem_init、sem_wait、sem_post. 由於用了信號量編譯的時候會需要鏈接線程庫 -lpthread.
下面就具體代碼示例看看它們的具體用法:
posix v方式:
1. 初始化,之後進入循環不停的寫,周期為1s。
- #include
- #include
- #include
- #include
- #include
- #define CLUSTER_SHARED_FILE "/tmp/cluster_share"
- typedef struct{
- char name[4];
- int age;
- }people;
- struct stu {
- sem_t mutex;
- people s[10];
- };
- struct stu t;
- main(int argc, char** argv) // map a normal file as shared mem:
- {
- int fd,i;
- struct stu *p_map;
- char temp;
- fd=open(CLUSTER_SHARED_FILE,O_CREAT|O_RDWR|O_TRUNC,00777);
- lseek(fd,sizeof(t)-1,SEEK_SET);
- write(fd,"",1);
- p_map = (struct stu *) mmap( NULL,sizeof(t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
- close(fd);
- temp = 'a';
- sem_init(&p_map->mutex,1,1);
- int j=0;
- while(1)
- {
- j++;
- printf("j 1\n");
- sem_wait(&p_map->mutex);
- printf("j 2\n");
- for(i=1; i<10; i++)
- {
- // temp += 1;
-
- memcpy(p_map->s[i].name, &temp,2 );
- p_map->s[i].age = p_map->s[i].age +20+i+j;
- }
- printf("j 3\n");
- sem_post(&p_map->mutex);
- printf("j 4\n");
- sleep(1);
- }
- printf(" initialize over \n ");
- sleep(50);
- munmap( p_map, sizeof(t) );
- printf( "umap ok \n" );
- }
2. 讀取操作:
- #include
- #include
- #include
- #include
- #include
- #include
- #define CLUSTER_SHARED_FILE "/tmp/cluster_share"
- typedef struct{
- char name[4];
- int age;
- }people;
- struct stu {
- sem_t mutex;
- people s[10];
- };
- struct stu t;
- main(int argc, char** argv) // map a normal file as shared mem:
- {
- int fd,i;
- struct stu *p_map;
- char temp;
- fd=open(CLUSTER_SHARED_FILE,O_CREAT|O_RDWR,00777);
- // lseek(fd,sizeof(people)*5-1,SEEK_SET);
- // write(fd,"",1);
- p_map = (struct stu *) mmap( NULL,sizeof(t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
- close( fd );
- while(1)
- {
- sleep(1);
- printf("2.....s\n");
- sem_wait(&p_map->mutex);
- printf("2.....\n");
- for(i=0; i<10; i++)
- {
- printf("name:%s,age:%d\n",p_map->s[i].name,p_map->s[i].age);
- }
- printf("2.....0\n");
- sem_post(&p_map->mutex);
- printf("2.......1");
- }
- munmap( p_map, sizeof(t));
- printf( "umap ok \n" );
- }
至於並非的寫操作,程序稍微修改一下就可以測試. 多並發沒有問題.
system V的方式,雖然接口不太一樣,但是也很類似。
1. 初始化
- #include
- #include
- #include
- #include
- #include
- #include
- //#include "shmdata.h"
- #define TEXT_SZ 2048
- struct shared_use_st
- {
- sem_t mutex;
- int written;//作為一個標志,非0:表示可讀,0表示可寫
- char text[TEXT_SZ];//記錄寫入和讀取的文本
- int cnt;
- };
- int main()
- {
- int running = 1;
- void *shm = NULL;
- struct shared_use_st *shared = NULL;
- char buffer[4096]="hello world";
- int shmid;
- int i=0;
- //創建共享內存
- #if 0
- //刪除共享內存
- if(shmctl(shmid, IPC_RMID, 0) == -1)
- {
- fprintf(stderr, "shmctl(IPC_RMID) failed\n");
- exit(EXIT_FAILURE);
- }
- #endif
- shmid = shmget((key_t)12345, sizeof(struct shared_use_st), 0666|IPC_CREAT);
- if(shmid == -1)
- {
- fprintf(stderr, "shmget failed\n");
- exit(EXIT_FAILURE);
- }
- //將共享內存連接到當前進程的地址空間
- shm = shmat(shmid, (void*)0, 0);
- if(shm == (void*)-1)
- {
- fprintf(stderr, "shmat failed\n");
- exit(EXIT_FAILURE);
- }
- printf("Memory attached at %X\n", (int)shm);
- //設置共享內存
- shared = (struct shared_use_st*)shm;
- sem_init(&shared->mutex,1,1);
- while(running)//向共享內存中寫數據
- {
- strncpy(shared->text, buffer, TEXT_SZ);
- sem_wait(&shared->mutex);
- //shared->cnt = 0;
- shared->cnt = shared->cnt +i;
- sem_post(&shared->mutex);
- i++;
- shared->written = 1;
- sleep(1);
- }
- //把共享內存從當前進程中分離
- if(shmdt(shm) == -1)
- {
- fprintf(stderr, "shmdt failed\n");
- exit(EXIT_FAILURE);
- }
- sleep(2);
- exit(EXIT_SUCCESS);
- }
2. 讀取操作
- #include
- #include
- #include
- #include
- //#include "shmdata.h"
- #include
- #define TEXT_SZ 2048
- struct shared_use_st
- {
- sem_t mutex;
- int written;//作為一個標志,非0:表示可讀,0表示可寫
- char text[TEXT_SZ];//記錄寫入和讀取的文本
- int cnt;
- };
- int main()
- {
- int running = 1;//程序是否繼續運行的標志
- void *shm = NULL;//分配的共享內存的原始首地址
- struct shared_use_st *shared;//指向shm
- int shmid;//共享內存標識符
- //創建共享內存
- shmid = shmget((key_t)12345, sizeof(struct shared_use_st), 0666);
- if(shmid == -1)
- {
- fprintf(stderr, "shmget failed\n");
- exit(EXIT_FAILURE);
- }
- //將共享內存連接到當前進程的地址空間
- shm = shmat(shmid, 0, 0);
- if(shm == (void*)-1)
- {
- fprintf(stderr, "shmat failed\n");
- exit(EXIT_FAILURE);
- }
- printf("\nMemory attached at %X\n", (int)shm);
- //設置共享內存
- shared = (struct shared_use_st*)shm;
- // shared->written = 0;
- while(running)//讀取共享內存中的數據
- {
- sem_wait(&shared->mutex);
- printf("text:%s,cnt:%d\n",shared->text,shared->cnt);
- sem_post(&shared->mutex);
- sleep(1);
- }
- //把共享內存從當前進程中分離
- if(shmdt(shm) == -1)
- {
- fprintf(stderr, "shmdt failed\n");
- exit(EXIT_FAILURE);
- }
- #if 0
- //刪除共享內存
- if(shmctl(shmid, IPC_RMID, 0) == -1)
- {
- fprintf(stderr, "shmctl(IPC_RMID) failed\n");
- exit(EXIT_FAILURE);
- }
- #endif
- exit(EXIT_SUCCESS);
- }
整體的用法還算比較簡單. 也很高效方便.