一、進程間通信的方式
在程序的運行中,我們必定會有關于進程與進程間通信的問題。而我們的先輩早已為我們準備了關于解決這些問題的方法。這些方法主要有幾種:無名管道,有名管道,信號,消息隊列,共享內存以及信號燈(信號量)等幾種方法。其中共享內存又是其中的翹楚。這篇文章講解一下共享內存。
二、共享內存介紹
1、共享內存是一種為高效的進程間通信方式,進程可以直接讀寫內存,而不需要任何數據的拷貝。
2、為了在多個進程間交換信息,內核專門留出了一塊內存區,可以由需要訪問的進程將其映射到自己的私有地址空間。進程就可以直接讀寫這一塊內存而不需要進行數據的拷貝,從而大大提高效率。
3、由于多個進程共享一段內存,因此也需要依靠某種同步機制(信號燈)。
三、共享內存的同步
共享內存為在多個進程之間共享和傳遞數據提供了一種有效的方式。但是它并未提供同步機制,所以我們通常需要用其他的機制來同步對共享內存的訪問。我們通常是用共享內存來提供對大塊內存區域的有效訪問,同時通過傳遞小消息來同步對該內存的訪問。在第一個進程結束對共享內存的寫操作之前,并無自動的機制可以阻止第二個進程開始對它進行讀取。對共享內存訪問的同步控制必須由程序員來負責。
四、共享內存的實現
共享內存的使用包括如下步驟:
1、創建/打開共享內存
2、映射共享內存,即把指定的共享內存映射到進程的地址空間用 于訪問。
3、撤銷共享內存的映射。
4、刪除共享內存對象
五、共享內存使用的函數
#include
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmdt(const void *shm_addr);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
1. shmget函數
int shmget(key_t key, size_t size, int shmflg);
該函數用來創建共享內存:
參數:key : 和信號量一樣,程序需要提供一個參數key,它有效地為共享內存段命名。有一個特殊的鍵值IPC_PRIVATE,它用于創建一個只屬于創建進程的共享內存,通常不會用到。
size: 以字節為單位指定需要共享的內存容量。
shmflag: 包含9個比特的權限標志,它們的作用與創建文件時使用的mode標志是一樣由IPC_CREAT定義的一個特殊比特必須和權限標志按位或才能創建一個新的共享內存段。
NOTE:權限標志對共享內存非常有用,因為它允許一個進程創建的共享內存可以被共享內存的創建者所擁有的進程寫入,同時其它用戶創建的進程只能讀取共享內存。我們可以利用這個功能來提供一種有效的對數據進行只讀訪問的方法,通過將數據放共享內存并設置它的權限,就可以避免數據被其他用戶修改。
返回值:創建成功,則返回一個非負整數,即共享內存標識;如果失敗,則返回-1。
2. shmat函數
第一次創建共享內存段時,它不能被任何進程訪問。
要想啟動對該內存的訪問,
必須將其連接到一個進程的地址空間。
這個工作由shmat函數完成:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
參數:shm_id : 由shmget返回的共享內存標識。shm_add: 指定共享內存連接到當前進程中的地址位置。它通常是一個空指針,表示讓系統來選擇共享內存出現的地址。
shmflg : 是一組標志。它的兩個可能取值是:
SHM_RND, 和shm_add聯合使用,用來控制共享內存連接的地址。
SHM_RDONLY, 它使連接的內存只讀。
返回值:如果調用成功, 返回一個指向共享內存第一個字節的指針;
如果失敗,返回-1。
共享內存的讀寫權限由它的屬主(共享內存的創建者),它的訪問權限和當前進程的屬主決定。共享內存的訪問權限類似于文件的訪問權限。
3. shmdt
將共享內存從當前進程中分離。
int shmdt(const void *shm_addr);
shm_addr: shmat返回的地址指針。
成功時,返回0,
失敗時,返回-1.
NOTE:共享內存分離并未刪除它,只是使得該共享內存對當前進程不再可用。
注意:當一個進程不再需要共享內存段時,它將調用shmdt()系統調用取消這個段,但是,這并不是從內核真正地刪除這個段,而是把相關shmid_ds結構的shm_nattch域的值減1,當這個值為0時,內核才從物理上刪除這個共享段。(每有一個進程映射到這段共享內存,這個值就會加1,每次調用shmctl刪除,僅僅只是刪掉這個標記)
用完共享存儲段后,將進程和該共享存儲段脫離。這并不是從系統中刪除其標識符以及其數據結構,直到某個進程(一般是創建者server)調用shmctl(IPC_RMID)特地刪除它。
4. shmctl
共享內存的控制函數
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shmid_ds結構至少包含以下成員:
struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
參數:
shm_id : 是shmget返回的共享內存標識符。
command: 是要采取的動作,
它可以取3個值:
IPC_STAT 把shmid_ds結構中的數據設置為共享內存的當前關聯值。
IPC_SET 如果進程有足夠的權限,就把共享內存的當前關聯值設置為shmid_ds結構中給出的值。
IPC_RMID 刪除共享內存段。
buf : 是一個指針,包含共享內存模式和訪問權限的結構。
返回值:
成功時,返回0,
失敗時,返回-1。
六、代碼示例
說了這么多,又到了實戰的時候了。下面我們就用代碼來進一步了解。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFF_SIZE 1024
typedef struct {
sem_t rsem; /* sem for read */
sem_t wsem; /* sem for write */
char buf[BUFF_SIZE];
}SHM;
int do_read (SHM *pSHM);
int do_write(SHM *pSHM);
int main(int argc, char *argv[])
{
int shmid = -1;
int pid = -1;
SHM *pSHM;
int retval = 0;
/* create share memory
* IPC_PRIVATE for communication betweeen parent-child processes
* IPC_CREAT to indicate a new creation of IPC object
*/
if ((shmid = shmget(IPC_PRIVATE, sizeof(SHM), 0666)) < 0) {
perror("shmget error");
return -1;
}
printf("pid[%d] create new shmid = %d\n", getpid(), shmid);
/* attach share memory */
if ((pSHM = (SHM *)shmat(shmid, NULL, 0)) == (void *)-1) {
perror("shmat error");
retval = -1;
goto _exit_point;
}
printf("parent: shmaddr = %p\n", pSHM);
/* set 3rd param of sem_init - pshared - as nonzero,
* then the semaphore is shared between processes
*/
if (sem_init(&(pSHM->rsem), 1, 0) < 0) { /* read sem is init as busy */
perror("sem_init read error");
retval = -1;
goto _exit_point;
}
if (sem_init(&(pSHM->wsem), 1, 1) < 0) { /* write sem is init as free */
perror("sem_init write error");
retval = -1;
goto _exit_point;
}
if ((pid = fork()) < 0) {
perror("fork error");
retval = -1;
goto _exit_point;
}else if (pid == 0) { /* child */
/* After a fork the child inherits the attached
* shared memory segments.
*/
retval = do_read(pSHM);
}else{ /* parent */
usleep(5000); /* just to make output looks better */
retval = do_write(pSHM);
/* detach the share memory */
if (shmdt(pSHM) < 0) {
perror("shmdt error");
retval = -1;
goto _exit_point;
}
/* wait child process */
wait(NULL);
}
_exit_point:
if (pid != 0) { /* child will not handle this */
if (shmid > 0) {
/* IPC_RMID just mark the share memory presented by
* the shmid should be deleted, but the deletion
* happens only when no proces is attached
*/
if (shmctl(shmid, IPC_RMID, NULL) < 0) {
perror("shmctl error");
}
}
}
return retval;
}
int do_write(SHM *pSHM)
{
while (1) {
/* try to lock for write */
if (sem_wait(&(pSHM->wsem)) < 0) {
perror("sem_wait error");
break;
}
printf(">");
/* access the share memory and write data which are got
* from stdin
*/
fgets(pSHM->buf, BUFF_SIZE, stdin);
/* eat the LF and complete the string */
pSHM->buf[strlen(pSHM->buf) - 1] = '\0';
/* allow read */
if (sem_post(&(pSHM->rsem)) < 0) {
perror("sem_post error");
break;
}
if (strncmp(pSHM->buf, "quit", 4) == 0) {
break;
}
usleep(500);
}
return 0;
}
int do_read(SHM *pSHM)
{
while(1) {
/* try to lock for read */
if (sem_wait(&(pSHM->rsem)) < 0) {
perror("sem_wait error");
break;
}
/* read the share memory buffer and print the content out */
printf("read buf: %s\n", pSHM->buf);
/* test the buf inside read lock */
if (strncmp(pSHM->buf, "quit", 4) == 0) {
/* detach the share memory */
if(shmdt(pSHM) < 0) {
perror("shmdt error");
return -1;
}
break;
}
/* allow write */
if (sem_post(&(pSHM->wsem)) < 0) {
perror("sem_post error");
break;
}
}
return 0;
}
大家可以看到,在這個程序中加入了信號燈的代碼。主要是因為兩個進程在調用同一個內存空間所以需要加入信號燈以達到同步或互斥的目的。
七、共享內存的優缺點
1、優點:我們可以看到使用共享內存進行進程間的通信真的是非常方便,而且函數的接口也簡單,數據的共享還使進程間的數據不用傳送,而是直接訪問內存,也加快了程序的效率。同時,它也不像匿名管道那樣要求通信的進程有一定的父子關系。
2、缺點:共享內存沒有提供同步的機制,這使得我們在使用共享內存進行進程間通信時,往往要借助其他的手段來進行進程間的同步工作。