Linux進程通訊之System V共享內存

前面已經介紹過了POSIX共享內存區,System V共享內存區在概念上相似POSIX共享內存區,POSIX共享內存區的使用是調用shm_open建立共享內存區後調用mmap進行內存區的映射,而System V共享內存區則是調用shmget建立共享內存區而後調用shmat進行內存區的映射。ios

對每一個System V共享內存區,內核會維護一個shmid_ds的數據結構,Linux 2.6.18 中的定義以下:數據結構

 

<bits/shm.h>

/* 鏈接共享內存區的進程數的數據類型 */
typedef unsigned long int shmatt_t;

struct shmid_ds
{
    struct ipc_perm shm_perm;           /* operation permission struct */
    size_t shm_segsz;                   /* 共享存儲段的最大字節數 */

    __time_t shm_atime;                 /* time of last shmat() */
    __time_t shm_dtime;                 /* time of last shmdt() */
    __time_t shm_ctime;                 /* time of last change by shmctl() */

    __pid_t shm_cpid;                   /* pid of creator */
    __pid_t shm_lpid;                   /* pid of last shmop */

    shmatt_t shm_nattch;                /* 鏈接共享內存區的進程數 */

//保留字段
#if __WORDSIZE == 32
    unsigned long int __unused1;
	unsigned long int __unused2;
	unsigned long int __unused3;
#endif
    unsigned long int __unused4;
    unsigned long int __unused5;
};

 

1 System V共享內存區的建立和打開

下面是shmget函數的接口以及說明:函數

 

#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
                     //成功返回共享內存標識符,失敗返回-1

 

shmget函數用於建立或打開一個共享內存區對象,shmget成功調用會返回一個共享內存區的標識符,供其它的共享內存區操做函數使用。測試

key:用於建立共享內存區的鍵值,這個在前面其餘System IPC建立的時候已經討論過了,System IPC都有一個key,做爲IPC的外部標識符,建立成功後返回的描述符做爲IPC的內部標識符使用。key的主要目的就是使不一樣進程在同一IPC匯合。key具體說能夠有三種方式生成:
ui

  • 不一樣的進程約定好的一個值;
  • 經過相同的路徑名和項目ID,調用ftok()函數,生成一個鍵;
  • 還能夠設置爲IPC_PRIVATE,這樣就會建立一個新的,惟一的IPC對象;而後將返回的描述符經過某種方式傳遞給其餘進程;

size:指定建立共享內存區的大小,單位是字節。若是實際操做爲建立一個共享內存區時,必須指定一個非0值,若是實際操做是訪問一個已存在的共享內存區,那麼size應爲0spa

shmflg:指定建立或打開消息隊列的標誌和讀寫權限(ipc_perm中的mode成員)。咱們知道System V IPC定義了本身的操做標誌和權限設置標誌,並且都是經過該參數傳遞,這和open函數存在差異,open函數第三個參數mode用於傳遞文件的權限標誌。System V IPC的操做標誌包含:IPC_CREATIPC_EXCL。讀寫權限以下圖:
指針


1 System V共享內存區的讀寫權限標誌code

System V共享內存區在建立後,該size大小的內存區會被初始化爲0這和POSIX共享內存不一樣,POSIX標準並無規定新建立的POSIX共享內存區的初始內容server

2 System V共享內存區的鏈接和斷接

經過shmctl建立或打開共享內存區對象後,並無將該共享內存區映射到調用進程的地址空間中,因此沒法訪問該共享內存區,須要經過shmat函數,將該共享內存區鏈接到調用進程的地址空間中,才能進行訪問,當該進程完成對該共享內存區的訪問後,能夠調用shmdt斷接這個共享內存區,固然進程結束後會自動斷接全部鏈接的共享內存區。下面是shmatshmdt函數的接口以及說明:對象

 

#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
                        //成功返回映射區的起始地址,失敗返回-1
int shmdt(const void *shmaddr);
                        //成功返回0,失敗返回-1

 

shmat用於將一個共享內存區鏈接到調用進程的地址空間中。

shmid:打開的System V共享內存對象的標識符;

shmaddrshmflg參數共同決定了共享內存區鏈接到調用進程的具體地址,規則以下:

  • shmaddr爲空指針:鏈接的地址由系統內核決定,這是推薦的方法,具備可移植性
  • shmaddr非空:此時還要根據shmflg參數是否指定SHM_RND標誌進行判斷:
    • 沒有指定SHM_RND:共享內存區鏈接到調用進程的shmaddr指定的地址;
    • 指定SHM_RND:共享內存區鏈接到shmaddr指定的地址向下舍入SHMLBA的位置。

shmflg:除了上面說的SHM_RND外,還有能夠指定SHM_RDONLY標誌,限定只讀訪問。通常該標誌置爲0

shmdt用於將一個共享內存區從該進程內斷接,當一個進程終止時,它鏈接的全部共享內存區會自動斷接。

3 System V共享內存區的控制操做

shmctl函數能夠對共享內存區進行多種控制操做,下面是shmctl函數的接口以及說明:

 

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
                     //成功返回0,失敗返回-1

 

shmid:共享內存區的標識符。

cmd:對共享內存區控制操做的命令,Open Group SUS定義了一下三個操做命令:

  • IPC_SET:按第三個參數buf所指定的內容,設置共享內存區的操做權限字段的:shm_perm.uidshm_perm.gidshm_perm.mode。此命令只能由如下進程執行:有效用戶ID等於shm_perm.uidshm_perm.cuid,以及有超級用戶權限的進程。
  • IPC_STAT:獲取共享內存區的shmid_ds結構,存放到傳入的第三個參數中。
  • IPC_RMID:從系統內核中刪除該共享內存區。由於每一個共享內存區有一個鏈接數據的計數,除非鏈接該共享內存區的最後一個進程斷接或終止,不然該共享內存區不會被實際刪除。這和其餘的System V IPC,例如System V消息隊列的刪除差異很大,卻是和POSIX IPCxxx_unlink刪除操做很相識。調用該命令後,該共享內存區標識符不能再繼續被鏈接。此命令也只能由如下進程執行:有效用戶ID等於shm_perm.uidshm_perm.cuid,以及有超級用戶權限的進程。

Linux中還定義了其餘的控制命令,如:IPC_INFOSHM_INFOSHM_LOCKSHM_UNLOCK等,具體能夠參考Linux手冊。

下面是建立System V共享內存區和查看其屬性的測試代碼:

 

#include <iostream>
#include <cstring>
#include <errno.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h>

using namespace std;

#define PATH_NAME "/tmp/shm"

int main()
{
    int fd;

    if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
    {
        cout<<"open file "<<PATH_NAME<<"failed.";
        cout<<strerror(errno)<<endl;
        return -1;
    }

    close(fd);

    key_t key = ftok(PATH_NAME, 0);

    int shmID;
    
    if ((shmID = shmget(key, sizeof(int), IPC_CREAT | 0666)) < 0)
    {
        cout<<"shmget failed..."<<strerror(errno)<<endl;
        return -1;
    }

    shmid_ds shmInfo;
    shmctl(shmID, IPC_STAT, &shmInfo);

    cout<<"shm key:0x"<<hex<<key<<dec<<endl;
    cout<<"shm id:"<<shmID<<endl;
    cout<<"shm_segsz:"<<shmInfo.shm_segsz<<endl;
    cout<<"shm_nattch:"<<shmInfo.shm_nattch<<endl;

    return 0;
}

 

執行結果以下:

 

shm key:0x80e7
shm id:5898284
shm_segsz:4		//共享內存區的大小
shm_nattch:0	//共享內存區的鏈接數目

 

經過ipcs命令查看該新建立的共享內存區對象:

 

[root@idcserver program]# ipcs -m -i 5898284
Shared memory Segment shmid=5898284
uid=0   gid=0   cuid=0  cgid=0
mode=0666       access_perms=0666
bytes=4 lpid=0  cpid=16422      nattch=0
att_time=Not set                   
det_time=Not set                   
change_time=Tue Aug 13 15:34:35 2013 

 

4 System V共享內存區的限制

和其中的System V IPC同樣,System V共享內存也存在系統的限制,關於系統範圍內對共享內存的限制,在Linux 2.6.18 <bits/shm.h>中定義了shminfo結構,該結構顯示了系統內核的限制,以下:

 

#include <bits/shm.h>
struct  shminfo
{
    unsigned long int shmmax;	//一個共享內存區的最大字節數
    unsigned long int shmmin;	//一個共享內存區的最小字節數
    unsigned long int shmmni;	//系統範圍內的共享內存區對象的最大個數
    unsigned long int shmseg;	//每一個進程鏈接的最大共享內存區的數目
    unsigned long int shmall;	//系統範圍內的共享內存區的最大頁數
    unsigned long int __unused1;
    unsigned long int __unused2;
    unsigned long int __unused3;
    unsigned long int __unused4;
};

 

Linux shmctl中能夠指定IPC_INFO來獲取上面結構所示的系統範圍內的限制。在Linux下,具體的限制值能夠經過sysctl來查看,以下:

 

[root@idcserver program]# sysctl -a | grep shm
...
kernel.shmmni = 4096			//系統範圍內的共享內存區對象的最大個數
kernel.shmall = 4294967296		//系統範圍內的共享內存區的最大頁數
kernel.shmmax = 68719476736		//一個共享內存區的最大字節數

 

通常狀況下不須要對System V共享內存區的系統限制進程修改,由於基本能夠知足應用需求,若是要在系統範圍內對內核限制進行修改,在Linux下面能夠經過修改/etc/sysctl.conf 內核參數配置文件,而後配合sysctl命令來對內核參數進行設置。例以下面示例:

 

[root@idcserver program]#echo "kernel.shmmni= 1000" >>/etc/sysctl.conf
[root@idcserver program]#sysctl -p
[root@idcserver program]#sysctl -a |grep shm
kernel.shmmni = 1000
kernel.shmall = 4294967296
kernel.shmmax = 68719476736

 

5 System V共享內存區的使用

下面是System V共享內存區的使用示例,進程1經過共享內存區向進程2發送一條消息,對共享內存區的同步採用System V信號量來完成。以下:

 

//process 1
#include <iostream>
#include <cstring>
#include <errno.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/sem.h>

using namespace std;

#define PATH_NAME "/tmp/shm"

union semun  
{  
    int val;                             
    struct semid_ds *buf;               
    unsigned short int *array;        
    struct seminfo *__buf;            
};  

int main()
{
    int fd;

    if ((fd = open(PATH_NAME, O_RDONLY | O_CREAT, 0666)) < 0)
    {
        cout<<"open file "<<PATH_NAME<<"failed.";
        cout<<strerror(errno)<<endl;
        return -1;
    }

    close(fd);

    key_t keyShm = ftok(PATH_NAME, 0);
    key_t keySem = ftok(PATH_NAME, 1);

    int shmID, semID;
    
    if ((shmID = shmget(keyShm, sizeof(int), IPC_CREAT | 0666)) < 0)
    {
        cout<<"shmget failed..."<<strerror(errno)<<endl;
        return -1;
    }

    int *buf = (int *)shmat(shmID, 0, 0);

    if ((semID = semget(keySem, 1, IPC_CREAT | 0666)) < 0)
    {
        cout<<"semget failed..."<<strerror(errno)<<endl;
        return -1;
    }

    semun arg; 
    arg.val = 0;  //初始化信號量資源的數目爲0

    if (semctl(semID, 0, SETVAL, arg) < 0)  
    {  
        cout<<"semctl error "<<strerror(errno)<<endl;  
        return -1;  
    }  

    
    struct sembuf buffer;  
    buffer.sem_num = 0;  
    buffer.sem_op = 1;  
    buffer.sem_flg = 0;  

    *buf = 111;
    cout<<"process 1:send "<<*buf<<endl;

	//將信號量資源加1,以表示共享內存區內已有資源
    semop(semID, &buffer, 1);

    return 0;
}

//process 2
#include <iostream>
#include <cstring>
#include <errno.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/sem.h>

using namespace std;

#define PATH_NAME "/tmp/shm"

union semun  
{  
    int val;                             
    struct semid_ds *buf;               
    unsigned short int *array;        
    struct seminfo *__buf;            
};  

int main()
{
    int fd;

    if ((fd = open(PATH_NAME, O_RDONLY)) < 0)
    {
        cout<<"open file "<<PATH_NAME<<"failed.";
        cout<<strerror(errno)<<endl;
        return -1;
    }

    close(fd);

    key_t keyShm = ftok(PATH_NAME, 0);
    key_t keySem = ftok(PATH_NAME, 1);

    int shmID, semID;
    
    if ((shmID = shmget(keyShm, sizeof(int), 0)) < 0)
    {
        cout<<"shmget failed..."<<strerror(errno)<<endl;
        return -1;
    }

    int *buf = (int *)shmat(shmID, 0, 0);

    if ((semID = semget(keySem, 1, 0)) < 0)
    {
        cout<<"semget failed..."<<strerror(errno)<<endl;
        return -1;
    }
    
    struct sembuf buffer;  
    buffer.sem_num = 0;  
    buffer.sem_op = -1;  
    buffer.sem_flg = 0;  

	//得到信號量資源
	semop(semID, &buffer, 1);

    cout<<"process 2:recv "<<*buf<<endl;

    return 0;
}

 

測試結果爲:

 

# ./send 
process 1:send 111
# ./recv 
process 2:recv 111
Aug, 13 PM 22:18 @lab 今天貌似是個悲傷的日子。。。大家都在那啪啪啪,咱們只能在這擼呀擼。。。實驗室今天還一塊兒吃飯,很久沒喝酒,喝了不到兩瓶啤的就暈了,哎,老了。。。15號就要放假了,好好看書,準備找工做了。。。
相關文章
相關標籤/搜索