Linux 进程通信(System V)共享内存区

来源:互联网 发布:淘宝客服没经验能干吗 编辑:程序博客网 时间:2024/05/17 10:53
一:简介

前面已经学过:pipe,FIFO,msg queue, 今天要学的是“共享内存区”

1.共享内存区是进程通信中最快的方式,而且传递的信息量是很大的!
2.是通过内存区间映射到进程空间来实现的!因此这种进程间的通信不再涉及到内核!
(即进程不是通过执行任何的进入内核的系统调用来传递数据的。这样内核就必须建立允许各个进程之间的共享内存区的映射关系,然后一直管理该内存区!同时也要保证所谓的同步,有序,而且没有死锁!)
3.简单的实现过程:
<1>.server获得访问共享内存区的权限
<2>.server从输入文件中读取数据到共享内存区( 需要一次copy内容 )
<3>.server输入数据OK后,通知用户进程
<4>.最后用户进程从共享内存区中取出data( 需要一次copy内容 )

但是与pipe,FIFO以及msg queue的区别是:
此三者需要进程的操作是:server和client的发送和接受data都是需要进行一次copy,所以一共有4次数据拷贝,所以效率不如“共享内存区”

4.数据结构:
#include

struct shmid_ds
{
struct ipc_perm shm_perm; //!> 权限设置结构体
size_t shm_segsz; //!> 内存块大小
pid_t shm_lpid; //!> 最后一次操作的进程ID
pid_t shm_cpid; //!> 创建进程的ID
shmatt_t shm_nattch; //!> 当前的附接数
shmatt_t shm_cnattch; //!> 内核的附接数
time_t shm_atime; //!> 最后一次关联时间
time_t shm_dtime; //!> 分离时间
time_t shm_ctime; //!> 最后一次改变时间
}; 


5.共享内存区的创建和操作:

#include
#include
#include 

int shmget( key_t key, size_t size, int oflag );
参数:
key:ftok返回值或者IPC_PRIVATE
size:共享内存区大小( 字节为单位,if访问一个已经存在的,那么就是0 )
oflag:权限的组合( 同前面讲的 )


创建OK后,那么就可以使用shmat函数来链接到它的地址空间!
void * shmat( int shmid, const void * addr, int flag ); 
//!> --------> 链接共享区到哪个地址上与addr参数以及flag中是否指定SHM_RND有关!
//!> if addr == 0,那么连接到内核选择的第一个可用地址上
//!> if addr != 0 && 没有SHM_RND,那么连接到addr上
//!> if addr != 0 && 指定SHM_RND,那么连接到addr-( addr mod SHMLBA )上,SHM_RND是取整意思
//!> SHMLBA是指最低边界地址倍数。所以此算式表示靠近addr的一个边界地址上 

使用OK后就可以断开链接:shmdt函数
int shmdt( const void * addr ); //!> 注意参数是shmat的返回值!!!


最后要删除内存:shmctl函数
int shmctl( int shmid, int cmd, struct shmid_ds * buff );
参数:
shmid:就是创建或者打开是shmget函数的返回值
cmd:有多种取值
buff:主要用在IPC_STAT取回结构体的值和IPC_SET设置结构体值中使用!
cmd:
IPC_STAT:取得shmid_ds的结构体,放在buff中
IPC_SET:按照buff设置结构体权限值-> sem_perm.uid, sem_perm.gid, sem_perm.mode;注意其允许执行的权限进程( 与前面一样 )
IPC_RMID:删除共享内存区,注意其删除是与文件inode差不多,只有当计数值为0 才删除,否则仅仅是删除一个标志顺便计数器--就OK!
SHM_LOCK:锁住共享内存区,只有super用户权限才OK!
SHM_UNLOCK: 解锁,权限用户为super

二.
//-------------------------------------------------------------------------------------------------
//简单的函数应用



////// 创建实例

#include
#include
#include
#include
#include

int main( int argc, char ** argv )
{
int semid;
int flag;
size_t len;

if( argc != 3 ) //!> 表示我们要输入2个字符串参数(因为第一个是默认的程序运行的全路径名 )
{
printf( "usage:shmget \n" );
exit( EXIT_FAILURE );
}

len = atoi( argv[2] ); //!> 第二个参数作为长度而已
flag = IPC_CREAT; //!> 创建标志( 具有唯一性 )-------> 注意IPC_EXCL:决定了唯一性! | IPC_EXCL

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
//!> Create the sempore
if( ( semid = semget( ftok( argv[1], 'a' ), len, flag ) ) == -1 )
{
printf("\nCreate semaprore error...\n");
exit( EXIT_FAILURE );
}

printf( "\nThe semid = %d\n", semid );

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
//!> Display attribute
int dis;
struct shmid_ds buff[10];
if( ( dis = shmctl( semid, IPC_STAT, buff ) ) == -1 )
{
printf( "\nDisplay the attribute error...\n" );
exit( EXIT_FAILURE );
}

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
//!> Delete the semapore
int del;
if( ( del = shmctl( semid, IPC_RMID, NULL ) ) == -1 )
{
printf("\n Delete error... \n");
exit( EXIT_FAILURE );
}

return 0;
}

三:
//-------------------------------------------------------------------------------------------------
// 生产者与消费者

1.core简介:
我们可以知道在server(生产者)中我们给的最大的src就是5个,所以if我们仅仅是只执行serever,那么执行5次后必须要等待,因为P不到src了,但是if有client(消费者)存在,那么就可以,因为消费者消费OK后
归还src,那么server又可以执行下去了。

2.
CODE:

////// producer

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define MAXSHM //!> 定义缓冲区变量个数

union semun //!> 此处我们主要是为了SETVALUE使用
{
int val; //!> 设置信号的值 
struct semid_ds* buf; //!> buffer:IPC_STAT, IPC_SET
unsigned short* array; //!> GETALL, SETALL的数组
};

int main()
{
key_t ipckey; //!> ipc 的 key
key_t semkey; //!> 信号量的key

int shmid; //!> ipc(此处是共享内存区模式导致)的ID
int semid; //!> 共享内存区ID

char* addr_c; //!> 共享区的地址

//!>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 对于共享区的处理(key and id and addr. )

ipckey = ftok( "/tmp/Linux/ipc", 368 ); //!> get ipc key
if( ipckey == -1 )
{
printf( "\nCreate IPC key error...\n" );
exit( EXIT_FAILURE );
}

shmid = shmget( ipckey, 1024, IPC_CREAT | 0666 ); //!> get ipc id
if( shmid == -1 )
{
printf( "\nCreate IPC id error...\n" );
exit( EXIT_FAILURE );
}

addr_c = ( char * )shmat( shmid, NULL, 0 ); //!> 链接到第一个可用的地址上
if( *( ( int * )addr_c ) == -1 ) //!> Set addr...
{
printf( "\nSet addr. error...\n" );
exit( EXIT_FAILURE );
}

//!>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 对于信号量的处理(key and id)

struct sembuf P, V; //!> P 操作变量
union semun arg1, arg2, arg3; //!> 设置semid此信号集合中的三种信号量
//!> 具体的下面有解释

semkey = ftok( "/tmp/Linux/sem", 368 ); //!> get sempore key
if( semkey == -1 )
{
printf( "\nCeate sem. key error...\n" );
exit( EXIT_FAILURE );
}

//!> 请注意此处创建的一个信号量集合!
//!> 里面可以有不同的信号处理不同事件!!!!
semid = semget( semkey, 3, IPC_CREAT | 0666 ); //!> get sem. id
//!> 信号集合中信号为3种
if( semid < 0 )
{
printf( "\nCreate sem. id error...\n" );
exit( EXIT_FAILURE );


//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 初始化信号灯中信号量



arg1.val = 0; //!> 缓冲区无数据,( 即信号满 )
if( semctl( semid, 0, SETVAL, arg1 ) == -1 ) //!> 设置VALUE = arg1 = 0
//!> sem 编号为0
printf( "\nSelValue (信号满) error...\n" );
exit( EXIT_FAILURE );
}

arg2.val = MAXSHM; //!> 缓冲区 5 个空闲元素
if( semctl( semid, 1, SETVAL, arg2 ) == -1 ) //!> 设置VALUE == arg2 = 5
//!> sem 编号为1
printf( "\nSetValue (信号空) error...\n" );
exit( EXIT_FAILURE );
}

arg3.val = 1; //!> 这个相当于是互斥使用缓冲区
if( semctl( semid, 2, SETVAL, arg3 ) == -1 )
{
printf( "\nCreate 互斥缓冲区 error...\n" );
exit( EXIT_FAILURE );
}

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 初始化 P 操作

P.sem_num = 0; 
P.sem_op = -1; //!> 注意:P的操作是 -- 操作( 就是-1处理 ) 
P.sem_flg = SEM_UNDO;

V.sem_num = 0;
V.sem_op = 1; //!> 注意:V的操作是 ++ 操作( 就是+1处理 )
V.sem_flg = SEM_UNDO;

//!> 下面进行的就是简单的PV操作 
int i = 0; 
while( i < 10 ) //!> 进行10次操作
{
P.sem_num = 1; //!> 注意P操作的是index=1的信号量
//!> 也就是存在元素的集合(--操作)
semop( semid, &P, 1 ); //!> 进行P操作一次

P.sem_num = 2; //!> 此处是互斥操作信号量
semop( semid, &P, 1 ); //!> 只让一个进程操作,其他的等待

addr_c[i] = i + 'a'; //!> 仅仅是为了输出显示而已
printf("\n 产生空间 addr_c[%d] = %c \n", i, addr_c[i]);

V.sem_num = 2; //!> 对互斥操作进行V
semop( semid, &V, 1 );

V.sem_num = 0; //!> P一个src后,就要加入开始没有元素的信号量中
semop( semid, &V, 1 );

i++;
sleep( 1 );
}

sleep( 60 );

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 退出共享区失败

if( shmdt( addr_c ) == -1 ) 
{
printf( "\n退出共享区失败\n" );
exit( EXIT_FAILURE );
}

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 删除共享区

if( shmctl( shmid, IPC_RMID, NULL ) == -1 ) 
{
printf( "\n删除共享区失败\n" );
exit( EXIT_FAILURE );
}

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 删除信号集

if( semctl( semid, 0, IPC_RMID, 0 ) == -1 )
{
printf( "\n撤销信号集失败\n" );
exit( EXIT_FAILURE );
}

exit( EXIT_SUCCESS );
}



///// consumer

#include
#include
#include
#include
#include
#include
#include
#include
#include

#define MAXSHM 5

union semun //!> 此处我们主要是为了SETVALUE使用
{
int val; //!> 设置信号的值 
struct semid_ds* buf; //!> buffer:IPC_STAT, IPC_SET
unsigned short* array; //!> GETALL, SETALL的数组
};

int main()
{
key_t ipckey;
key_t semkey;

int shmid;
int semid;

char* addr_c;

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 得到IPC/SEM的key和id

ipckey = ftok( "/tmp/Linux/ipc", 368 ); //!> get ipc key
if( ipckey == -1 )
{
printf("\nCreate IPC key error...\n");
exit( EXIT_FAILURE );
}

shmid = shmget( ipckey, 1024, IPC_EXCL | 0666 );
if( shmid == -1 )
{
printf( "\nCreate IPC ID error...\n" );
exit( EXIT_FAILURE );
}

addr_c = ( char * )shmat( shmid, NULL, 0 );
if( *( ( int * ) addr_c ) == -1 )
{
printf( "\nCreate addr error...\n" );
exit( EXIT_FAILURE );


//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 定义信号量的数据结构

struct sembuf P, V;

semkey = ftok( "/tmp/Linux/sem", 368 );
if( semkey == -1 )
{
printf("\nCreate sem. key error... \n");
exit( EXIT_FAILURE );
}

semid = semget( semkey, 0, IPC_EXCL | 0666 ); 
//!> 在“生产者”中已经创建了,所以此处只要引用就好,所以第二参数0
if( semid < 0 )
{
printf( "\nCreate sem. ID error...\n" );
exit( EXIT_FAILURE );
}

//!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//!> 初始化P V 操作

P.sem_num = 0;
P.sem_op = -1; //!> 减去1操作
P.sem_flg = SEM_UNDO;

V.sem_num = 0;
V.sem_op = 1; //!> 加上1操作
V.sem_flg = SEM_UNDO;

int i = 0;
while( i < 10 )
{
//!> 先需要等待
P.sem_num = 0; //!> 注意开始我们知道这里面没有空间,
//!> 所以要等待server端的V操作,整体
//!> 看来我们知道,server P10次V10次,
//!> 此处就连续的读写就可以了
semop( semid, &P, 1 );

P.sem_num = 2; //!> 此处是互斥操作
semop( semid, &P, 1 );

printf("\n消费空间 addr_c[%d] = %c\n", i, addr_c[i]); 

V.sem_num = 2; //!> 释放互斥区
semop( semid, &V, 1 );

V.sem_num = 1; //!> 那么 原来装有src信号量有了新的空间
semop( semid, &V, 1 ); //!> 也就是 ++ 处理

i++;
sleep( 2 );
}

//!>>>>>>>>>>>>>>>>>>>>>>>>
//!> 下面释放链接

if( shmdt( addr_c ) == -1 )
{
printf( "\n退出共享区失败\n" );
exit( EXIT_FAILURE );
}

exit( EXIT_FAILURE );
}
0 0