說明:只供學習交流,轉載請註明出處
一,共享內存的基本概念
共享內存是IPC(進程間通信)中最簡單的方式之一。共享內存充許兩個或多個進程訪問同一塊內存,就如同malloc()函數向不同進程返回了指向同一個物理內存的指針。當一個進程改變了這塊地址中的內容的時候,其它進程都會察覺到這個更改。因爲所有進程共享同一塊內存。共享內存在各種進程間通信中具有最高的效率。訪問共享內存區域和訪問進程獨有的內存區域一樣快,並不需要通過系統調用或者其他切入內核的過程來完成。同時它也避免了對數據的各種不必要的複製。
由於多個進程同時需要對共享內存空間進行操作,必須對共享內存空間的訪問採用某種同步方式。常見的方式是使用信號量來確保在某個時刻只有一個進程訪問共享內存。
二,創建共享內存
要使用共享內存進行進程間通信,首先需要創建共享內存資源。shmget函數用於創建新的共享內存段,或是對一個已經存在的共享內存段進行存取。shmget函數的具體信息如表所示:
shmget函數
頭文件 |
#include <sys/ipc.h> #include <sys/shm.h> |
||
函數原型 |
int shmget(key_t key, size_t size, int shmflg); |
||
返回值 |
成功 |
失敗 |
是否設置errno |
共享內存段標識符 |
-1 |
是 |
函數功能:分配共享內存
參數說明:
key:key爲共享內存的鍵值,當key的取值爲IPC_PRIVATE,或key不取IPC_PRIVATE,同時內核中沒有與key相對應的共享內存段存在,shmflg設置了IPC_CREAT,則函數shmget()將創建一塊新的共享內存段。如果shmflg參數同時設置了IPC_CREAT和IPC_EXCL,且內核中存在與key相關的共享內存段,shmget函數將調用失敗。errno將設置爲EEXIST。
size:size用於指定創建的共享內存段的大小。
shmflg:表示的操作類型,也可用於設置共享內存的訪問權限,兩者通過“|”鏈接表示。取值如下:
IPC_CREAT:如果共享內存不存在,則創建一個共享內存,否則打開操作。
IPC_EXCL:只有在共享內存不存在的時候,新的共享內存才建立,否則就產生錯誤。
SHM_HUGETLB:給共享內存分配HUGETLB內存頁。
mode_flags(最後9位):指定創建的共享內存的訪問權限。
錯誤信息:
EACCES:用戶無訪問共享內存段的權限。
EEXIST:參數shmflg設置了IPC_CREAT |IPC_EXCL,而key相關的共享內存段存在。
EINVAL:參數size無效或與key相關的共享內存段存在,但參數指定的size大小超過共享內存段大小。
ENFILE:打開的文件達到系統限制。
ENOENT:與key相關的共享內存段不存在,而shmflg參數未使用IPC_CREAT。
ENOMEM:段空間不足。
ENOSPC:超出系統對共享內存標識符的限制。
EPERM:設置了SHM_HUGETLB標誌,但調用進程沒有相應的權限。
實例:
程序演示了shmget函數的使用方法。在調用shmget函數時使用了不同的參數。示範了在不同調用參數的情況下,shmget函數的不同返回結果和錯誤信息。
程序中shmget參數調用示例
參數key |
參數shmflg |
shmget調用結果 |
errno信息 |
IPC_PRIVATE |
無要求 |
成功 |
無 |
系統中不存在相同的key內存段 |
IPC_CREAT|權限值 |
成功 |
無 |
系統中存在相同的key內存段 |
IPC_CREAT| IPC_EXCL|權限值 |
失敗 |
EEXIST |
系統中存在相同的key內存段 |
IPC_CREAT | 權限值 |
成功 |
無 |
具體代碼如下:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void)
{
key_t key;
int shmid;
int proj_id;
key = IPC_PRIVATE;
shmid = shmget(key, 1024, 0600);
if ( shmid == -1)
{
perror("cannot create a shared memory segment");
}
else
{
printf("1.key=IPC_PRIVATE, shared memory segment shmid=%d\n",shmid);
}
proj_id = 1;
key=ftok("./program", proj_id);
if (key == -1)
{
perror("cannot generate IPC key");
}
shmid = shmget(key, 1024, IPC_CREAT|0660);
if (shmid == -1)
{
perror("cannot create a shared memory segment");
}
else
{
printf("2,key=%d generated by ftok,shared memory shmid=%d\n", key,shmid);
}
shmid=shmget(key,1024,IPC_CREAT|IPC_EXCL|0660);
if ( shmid == -1 )
{
perror("cannot create a shared memory segment");
}
shmid=shmget(key, 1024, IPC_CREAT|0660);
if ( shmid == -1 )
{
perror("cannot create a shared memory segment");
}
else
{
printf("Access the existing shared memory segment\n");
}
return (0);
}
運行結果:
[root@localhost test]# ./shmget
1.key=IPC_PRIVATE, shared memory segment shmid=1245199
2,key=16913142 generated by ftok,shared memory shmid=1114125
cannot create a shared memory segment: File exists
Access the existing shared memory segment
[root@localhost test]#
三,共享內存的基本數據結構
系統爲每個共享內存段維護着對應的結構體shmid_ds的實例,該結構體的具體定義如下:
struct shmid_ds {
structipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /*pid of creator */
__kernel_ipc_pid_t shm_lpid; /*pid of last operator */
unsignedshort shm_nattch; /* no. of current attaches */
unsignedshort shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
結構體ipc_perm用於保存共享內存段的訪問權限及共享內存段的創建者等信息。結構體ipc_perm具體定義如下:
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsignedshort seq;
};
shm_segsz:段大小。
shm_atime:最後一次調用shmat函數的時間。
shm_dtime:最後一次調用shmdt函數的時間。
shm_ctime:最後一次調用shmctl函數的時間。
shm_cpid:創建共享內存段的進程的進程號。
shm_lpid:最後一次調用shmat或shmclt函數的進程號。
shm_nattch:當前連接的進程數。
當創建了新的共享內存段,共享內存段將被初始化爲0,與其相關的數據結構shmid_ds將按以下情況進行初始化。
shm_perm.cuid和shm_perm.uid將被設置爲調用進程的有效用戶ID。
shm_perm.cgid和shm_perm.gid將被設置爲調用進程的有效組ID。
shm_perm.mode的後9位將被設置爲shmflg的後9位值。
shm_segsz被設置爲size的值。
shm_lpid、shm_nattch、shm_atime和shm_dtime被設置爲0.
shm_ctime被設置爲當前的系統時間。
四,shmctl函數
shmctl函數提供了對共享內存的多種控制操作,該函數的具體信息如下表所示:
頭文件 |
#include <sys/ipc.h> #include <sys/shm.h> |
||
函數原型 |
int shmctl(int shmid, int cmd, struct shmid_ds *buf); |
||
返回值 |
成功 |
失敗 |
是否設置errno |
返回結果依賴於cmd參數 |
-1 |
是 |
說明:shmctl函數根據參數cmd給出的控制信息,對共享內存標識符爲shmid的共享內存段進行控制。參數buf爲指向shmid_ds結構體的指針。
cmd參數可取下面的值:
IPC_STAT:將內核中共享內存與指定共享內存標識符shmid相關的數據複製到指向shmid_ds結構體的buf指針中。
IPC_SET:使用buf指向的shmid_ds結構體中的參數,設置指定共享內存標識符shmid中的相關字段。
IPC_RMID:刪除指定共享內存標識符shmid的共享內存段。進行必須確保共享內存段被成功地刪除,否則共享內存所佔用的空間將不能釋放。
IPC_INFO(Linux特有參數):獲得系統共享內存限制和相關的參數,將其保存在buf參數指向的內存空間中。buf爲指向shminfo結構體的指針,該結構體在<sys/shm.h>中定義。該定義在定義了_GNU_SOURCE宏時是可見的。shminfo結構體的定義如下:
struct shminfo{
intshmmax; //最大共享內存段
intshmmin;//最小共享內存段,爲1
intshmmni;//最大共享內存段數
intshmseg; //進程可以訪問的最大共享內存段數,未使用
intshmall; //系統最大共享內存頁
};
SHM_INFO(Linux特有的參數):獲得共享內存段消耗的系統資源信息,將其保存在buf參數指向的內存空間中。buf爲指向shm_info結構體的指針,與shminfo類似。
該結構體也是在<sys/shm.h>中定義,並在定義了_GNU_SOURCE宏時是可見。
shm_info結構體的定義如下:
struct shm_info {
intused_ids; //當前系統中存在的共享內存段
unsignedlong shm_tot; //總的共享內存頁數
unsignedlong shm_rss; //駐留的共享內存頁數
unsignedlong shm_swp; //交換的共享內存頁數
unsignedlong swap_attempts; //在Linux 2.4以後的內核中就不再使用了
unsignedlong swap_successes; //在Linux 2.4以後的內核中就不再使用了
};
SHM_STAT(Linux特有的參數):與使用IPC_STAT獲得的結果相同。只是shmid不是共享內存標識符,而是內核中維護的內部共享內存段數組的索引值。
SHM_LOCK(Linux特有的參數):阻止共享內存段的交換。
SHM_UNLOCK(Linux特有的參數):對共享內存解鎖,充許其換出。
錯誤信息:
EACCES:無對共享內存的訪問權限。
EFAULT:cmd中設置了IPC_SET或IPC_STAT參數,但buf指向非法地址空間。
EIDRM:共享內存標識符指向已刪除的標識符。
EINVAL:shmid爲非法共享內存標識符或非法的cmd參數。
ENOMEM:在Linux 2.6.9以後的內核版本中支持,cmd參數中設置了SHM_LOCK,所要創建的共享內存段大小超出了調用進程限制。
EOVERFLOW:cmd參數設置了IPC_STAT,獲得的GID或UID值過大,無法保存在buf指向的數據結構中。
EPERM:cmd參數設置了IPC_SET或IPC_RMID,調用進程的有效用戶ID與共享內存的創建者或所有者不一致,進程沒有相應的權限。
實例:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define LINUX_ENV
#ifndef LINUX_ENV
#define _GNU_SOURCE
#endif
int main(void)
{
key_t key;
int proj_id;
int shmid;
struct shmid_ds buffer;
proj_id = 2;
key = ftok("./program", proj_id);
if ( key == -1 )
{
perror("Cannot generate the IPC key");
return (1);
}
shmid = shmget(key, 1024, IPC_CREAT|0660);
if ( shmid == -1 )
{
perror("Cannot create a shared memory segment");
return (1);
}
printf("shmid = %d\n", shmid);
shmctl(shmid, IPC_STAT, &buffer);
printf("======shared memory info======\n");
printf("effective user id : %d\n", buffer.shm_perm.uid);
printf("effective group id : %d\n", buffer.shm_perm.gid);
printf("message queue's creator user id : %d\n", buffer.shm_perm.cuid);
printf("message queue's creator group id : %d\n", buffer.shm_perm.cgid);
printf("access mode : %x\n", buffer.shm_perm.mode);
printf("PID of creator : %d\n", buffer.shm_cpid);
printf("No.of current attaches : %d\n", buffer.shm_nattch);
#ifdef LINUX_ENV
shmctl(shmid, SHM_INFO, &buffer);
struct shm_info *mem_info;
mem_info = (struct shm_info*)(&buffer);
printf("# of currently existing segments : %d\n", mem_info->used_ids);
printf("Total number of shared memory pages : %d\n", mem_info->shm_tot);
printf("# of resident shared memory pages : %d\n", mem_info->shm_rss);
printf("# of swapped shared memory pages: %d\n", mem_info->shm_swp);
#endif
return (0);
}
運行結果:
root@localhost test]# ./shmctl
shmid = 1277968
======shared memory info======
effective user id : 0
effective group id : 0
message queue's creator user id : 0
message queue's creator group id : 0
access mode : 1b0
PID of creator : 29900
No.of current attaches : 0
# of currently existing segments : 17
Total number of shared memory pages : 967
# of resident shared memory pages : 840
# of swapped shared memory pages: 0
[root@localhost test]#