linux进程间通信--共享内存

Posted sucfrperperseverance

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux进程间通信--共享内存相关的知识,希望对你有一定的参考价值。

共享内存 :内核空间预留出来的一块内存,用于进程间通信

共享内存是一种最高效的进程间通信方式,因为进程可以直接读写内存,不需要任何数据的复制,为了在进程间交换信息,内核专门留出了一块内存区,这段内存可以由要访问的进程将其映射到自己的私有地址空间,因此,进程就可以直接读写这一内存而不需要进行数据的复制,从而大大提高了效率,当然,由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

(1)int shmget(key_t key, size_t size, int shmflg);

功能:获取共享内存段的ID

参数:
  @key IPC_PRIVATE 或 ftok()
  @size 申请的共享内存段大小 [4k的倍数]
  @shmflg IPC_CREAT | 0666 或 IPC_CREAT | IPC_EXCL

返回值:
成功返回ID,失败返回-1

(2)void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存到用户空间
参数:
@shmid 共享内存段ID
@shmaddr NULL:系统自动完成映射
@shmflg:   SHM_RDONLY:只读   0:读写
返回值:
成功返回映射后的地址,失败返回(void *)-1

(3)int shmdt(const void *shmaddr);
功能:撤销映射
参数:
@shmaddr 共享内存映射的地址

注意:当一个进程结束的时候,它映射共享内存,会自动撤销映射


(4)int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:根据命令控制共享内存
参数:
@shmid 共享内存段的ID
@cmd IPC_STAT[获取属性],IPC_SET[设置属性],IPC_RMID[删除IPC对象]
@buf 保存属性
返回值:
成功返回0,失败返回 -1

----------------------------------------------------------------------------
注意:当我们调用shmctl删除共享内存的时候,并不会立即删除。只有当共享内存映射
次数为0,才会删除共享内存对象
-----------------------------------------------------------------------------

实例如下:

shm_r.c

 

#define SHM_SIZE 4096

typedef struct{
  int read_flag;
  int write_flag;
  char mtxt[1];//标示数据存放地址
}data_t;

int shm_init(const char *pathname,int id)
{
  int shmid;
  key_t key;

  key = ftok(pathname,id);
  if(key < 0){
    fprintf(stderr,"Fail to ftok : %s!\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  shmid = shmget(key,SHM_SIZE,IPC_CREAT | 0666);
  if(shmid < 0){
    fprintf(stderr,"Fail to shmget : %s\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  return shmid;
}

void read_shm(void *addr)
{
  data_t *pdata;
  pdata = (data_t *)addr;

  pdata->read_flag = 0;
  pdata->write_flag = 1;

while(1){
    if(pdata->read_flag){
    printf("Read : %s!\\n",pdata->mtxt);
    pdata->write_flag = 1;
    pdata->read_flag = 0;
  }
 }

}

//./a.out pathname
int main(int argc, const char *argv[])
{
  int shmid;
  void *addr;

  if(argc < 2){
    fprintf(stderr,"Usage : %s <pathname> \\n",argv[0]);
    exit(EXIT_FAILURE);
  }

  shmid = shm_init(argv[1],\'k\');
  addr = (void *)shmat(shmid,NULL,0);
  if(addr == (void *)-1){
    fprintf(stderr,"Fail to shmat : %s\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  read_shm(addr);

  return 0;
}

 

shm_w.c

#define SHM_SIZE 4096

typedef struct{
  int read_flag;
  int write_flag;
  char mtxt[1];//标示数据存放地址
}data_t;

int shm_init(const char *pathname,int id)
{
  int shmid;
  key_t key;

  key = ftok(pathname,id);
  if(key < 0){
    fprintf(stderr,"Fail to ftok : %s!\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  shmid = shmget(key,SHM_SIZE,IPC_CREAT | 0666);
  if(shmid < 0){
    fprintf(stderr,"Fail to shmget : %s\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  return shmid;
}

void write_shm(void *addr)
{
  data_t *pdata;
  pdata = (data_t *)addr;

  pdata->read_flag = 0;
  pdata->write_flag = 1;

  while(1){
    if(pdata->write_flag){
    fgets(pdata->mtxt,SHM_SIZE - 8,stdin);//读写标志共占8个字节
    pdata->mtxt[strlen(pdata->mtxt) - 1] = \'\\0\';

    pdata->write_flag = 0;
    pdata->read_flag = 1;
}
}

}

//./a.out pathname
int main(int argc, const char *argv[])
{
  int shmid;
  void *addr;

  if(argc < 2){
    fprintf(stderr,"Usage : %s <pathname> \\n",argv[0]);
    exit(EXIT_FAILURE);
  }

  shmid = shm_init(argv[1],\'k\');
  addr = (void *)shmat(shmid,NULL,0);
  if(addr == (void *)-1){
    fprintf(stderr,"Fail to shmat : %s\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  write_shm(addr);

  return 0;
}

运行并执行shm_w.c

运行并执行shm_r.c

思路还是比较简单

上面的代码可以改进吗?

这段代码涉及到信号量的知识,可以查看:linux进程间通信--信号量

先引进:

sem.c

union semun {
  int val; /* Value for SETVAL */
  struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
  unsigned short *array; /* Array for GETALL, SETALL */
  struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */
};

void init_sem_value(int sem_id,int sem_num,int value)
{
  union semun sem_val;

  sem_val.val = value;

  if(semctl(sem_id,sem_num,SETVAL,sem_val) < 0)
  {
    perror("Fail to semctl");
    exit(EXIT_FAILURE);
  }

  return ;
}

int init_sems(const char *filename,int nsem)
{
  key_t key;
  int sem_id;

  key = ftok(filename,\'b\');
  if(key < 0){
    perror("Fail to ftok");
    exit(EXIT_FAILURE);
  }

  sem_id = semget(key,nsem,IPC_CREAT|IPC_EXCL| 0666);
  if(sem_id < 0)
  {
  //已经存在
  if(errno == EEXIST)
  {
    //重新获得ID
    sem_id = semget(key,nsem,IPC_CREAT | 0666);

  }else{
    //其他错误
    perror("Fail to semget");
    exit(EXIT_FAILURE);
  }  

//不存在,创建好后,初始化
 }else{

  init_sem_value(sem_id,0,0);//0号信号灯初始化为0
  init_sem_value(sem_id,1,1);//1号信号灯初始化为1
 }

return sem_id;
}

void P(int sem_id,int sem_num)
{
  struct sembuf sem;

  sem.sem_num = sem_num;
  sem.sem_op = -1;
  sem.sem_flg = 0;

  if(semop(sem_id,&sem,1) < 0)
  {
    perror("Fail to semop for P\\n");
    exit(EXIT_FAILURE);
  }

  return ;
}

void V(int sem_id,int sem_num)
{
  struct sembuf sem;

  sem.sem_num = sem_num;
  sem.sem_op = 1;
  sem.sem_flg = 0;

  if(semop(sem_id,&sem,1) < 0)
  {
    perror("Fail to semop for V\\n");
    exit(EXIT_FAILURE);
  }

  return ;
}

 

shm_r.c修改如下:

#define SHM_SIZE 4096

int shm_init(const char *pathname,int id)
{
  int shmid;
  key_t key;

  key = ftok(pathname,id);
  if(key < 0){
    fprintf(stderr,"Fail to ftok : %s!\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  shmid = shmget(key,SHM_SIZE,IPC_CREAT | 0666);
  if(shmid < 0){
    fprintf(stderr,"Fail to shmget : %s\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  return shmid;
}

void read_shm(void *addr,int semid)
{
  char *pdata;
  pdata = (char *)addr;


while(1){
  //P操作读资源
  P(semid,0);//申请读资源

  printf("Read : %s!\\n",pdata);
  //V操作写资源
  V(semid,1);//释放写资源
 }

}

//./a.out pathname
int main(int argc, const char *argv[])
{
  int shmid;
  int semid;
  void *addr;

  if(argc < 2){
    fprintf(stderr,"Usage : %s <pathname> \\n",argv[0]);
    exit(EXIT_FAILURE);
  }

  shmid = shm_init(argv[1],\'k\');
  addr = (void *)shmat(shmid,NULL,0);
  if(addr == (void *)-1){
    fprintf(stderr,"Fail to shmat : %s\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  //信号灯集
  semid = init_sems(argv[1],2);

  read_shm(addr,semid);

  return 0;
}

 

shm_w.c修改如下:

#define SHM_SIZE 4096

int shm_init(const char *pathname,int id)
{
  int shmid;
  key_t key;

  key = ftok(pathname,id);
  if(key < 0){
    fprintf(stderr,"Fail to ftok : %s!\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  shmid = shmget(key,SHM_SIZE,IPC_CREAT | 0666);
  if(shmid < 0){
    fprintf(stderr,"Fail to shmget : %s\\n",strerror(errno));
    exit(EXIT_FAILURE);
  }

  return shmid;
}

void write_shm(void *addr,int semid)
{
  char *data = (char *)addr;

  while(1){
    P(semid,1);
    fgets(data,SHM_SIZE,stdin);
    data[strlen(data) - 1] = \'\\0\';
    V(semid,0);
  }
}

//./a.out pathname
int main(int argc, const char *argv[])
{
  int shmid;
  int semid;
  void *addr;

  if(argc < 2){
    fprintf(stderr,"Usage : %s <pathname> \\n",argv[0]);
    exit(EXIT_FAILURE);
  }

  shmid = shm_init(argv[1],\'k\');
  addr = (void *)shmat(shmid,NULL,0);
  if(addr == (void *)-1){
    fprintf(stderr,"Fail to shmat : %s\\n",strerror(errno));
    exit(EXIT_FAILURE);
}

  semid = init_sems(argv[1],2);

  write_shm(addr,semid);

  return 0;
}

 运行结果如上

 

以上是关于linux进程间通信--共享内存的主要内容,如果未能解决你的问题,请参考以下文章

Linux-进程间通信

Linux-进程间通信

Linux-进程间通信

Linux之进程间通信

Linux进程间通信—使用共享内存

Linux进程IPC浅析[进程间通信SystemV共享内存]