前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux笔记(16)| 进程同步机制——管道和IPC

Linux笔记(16)| 进程同步机制——管道和IPC

作者头像
飞哥
发布2020-11-25 10:01:55
2K0
发布2020-11-25 10:01:55
举报
文章被收录于专栏:电子技术研习社

今天要分享的是Linux进程的同步机制,包括管道和IPC。之前学习的信号也有控制进程同步的作用,但是信号仅仅传输很少的信息,而且系统开销大,所以这里再介绍几种其他的进程同步机制。在之前的一篇文章中有提到相关内容,但是当时没有详细展开,可以回顾一下:Linux笔记(10)| 进程概述

一、管道(无名管道)

一般说管道都是指无名管道(也称匿名管道),有名管道(也称命名管道)不会直接说管道,肯定会加上前缀。

管道是Linux下最常见的进程间的通信方式之一,它是在两个进程之间实现一个数据流通的通道。它有以下特点:

1、管道一般是半双工的,数据只能向一个方向流动。

2、通常只能在父子进程或者兄弟进程之间使用。

3、管道是一种特殊的文件,并且只存在于内存当中

……

接下来说一下管道的使用:

创建管道

代码语言:javascript
复制
int pipe(int pipefd[2]);

输入参数是一个数组,实际上这个输入参数是当做输出来用的,调用这个函数,成功的话数组里就会保存两个文件描述符,并且返回0,失败返回-1.

其中文件描述符fd[0]是读端,fd[1]是写端,这是固定不变的。刚刚我们也说了,创建管道肯定是为了父子进程或者兄弟进程之间通信的,单独在一个进程里面使用管道毫无意义。

所以父进程创建好管道之后,再调用fork函数创建子进程,子进程就会继承那个管道,于是父子间可以约定谁来读,谁来写。因为是半双工通信,所以只能一个读,一个写,如果想要两端都能读写,那就要创建两个管道。

比如父进程读,子进程写,那么父进程可以先close(fd[1]),然后read(fd[0]),子进程先close(fd[0]),然后write(fd[1]).

读写的时候只要把他当做普通的文件就行了,和普通的文件描述符的读写一样,但是有一点不一样的是普通文件读完了数据还在,而管道读完之后数据就没了。

二、有名管道

管道只能在有亲缘关系的进程之间实现通信,但是有名管道可以在任何两个进程之间实现通信。有名管道严格遵循先进先出的规则,不支持lseek函数等文件定位操作。

首先创建一个有名管道:

代码语言:javascript
复制
int mkfifo(const char*pathname,mode_t mode);

pathname参数是一个普通的路径名,也就是创建后有名管道文件的名字,mode参数是文件的操作权限,调用成功返回0,调用失败返回-1.

接下来就可以使用open或者fopen函数打开刚刚创建的有名管道文件,对其进行读写操作了。

三、System V IPC机制

IPC机制由消息队列、信号量以及共享内存三种具体实现方法组成。

首先要了解两个概念,标识符关键字。每一个IPC结构(消息队列或者信号量或者共享内存)都有一个标识符,这是一个非负整数,每创建一个IPC结构,相应的标识符就会加1,这个标识符在相同的结构中是唯一的,也就是说,如果“666”是某个消息队列的标识符,那么肯定不会有第二个消息队列的标识符也是“666”。但是需要注意的是,可能某个共享内存的标识符也是“666”。于是,还得用一个东西来区分,那就是关键字了。所以,根据关键字和标识符可以唯一确定一个IPC结构。

IPC的关键字一般可以使用IPC_PRIVATE,也可以使用ftok函数获得,他们有一些区别,后面会提到。

ftok函数的使用:

代码语言:javascript
复制
key_t ftok(const char* pathname,int proj_id);

ftok函数是用于将一个路径和项目ID转换为关键字,第一个参数必须是一个存在的、可以访问的文件路径名,第二个参数项目ID只有低八位有效。

在创建一个IPC对象的时候,他们有一些共同的特点:

我们先来看一下IPC对象创建函数:

1、创建消息队列

代码语言:javascript
复制
int msgget(key_t key,int msgflg);

2、创建信号量

代码语言:javascript
复制
int semget(key_t key,int nsems,int semflg);

3、创建共享内存

代码语言:javascript
复制
int shmget(key_t ,size_t size,int shmflg);

可以发现,他们都有一个关键字key,都有一个flag参数。在使用上,也有一些共同的特点:

当key使用IPC_PRIVATE时,操作系统保证创建一个唯一的IPC对象,此时flag参数仅决定对象的存取权限。

当key使用ftok函数得到的关键字时,flag参数不仅决定对象的存取权限,还和创建方式有关,具体就是:

设置flag参数的IPC_CREAT位,但不设置IPC_EXCL位,如果不存在指定key的IPC对象,就创建,如果存在,就返回该对象。

同时设置IPC_CREAT位和IPC_EXCL位,如果对象不存在就创建,如果已经存在,则返回错误。

这和文件操作函数open是类似的。

接下来介绍一下各个IPC对象涉及到的API函数。由于具体的细节比较多,这里就不一一罗列了,可以使用man手册来查看具体细节。

1、消息队列

创建:用来创建或者打开一个消息队列

代码语言:javascript
复制
int msgget(key_t key,int msgflg);

控制:其中cmd命令参数可以获取或者设置buf里的内容,也可以删除这个消息队列(看具体的参数设置)

代码语言:javascript
复制
int msgctl(int msqid,int cmd,struct msqid_ds *buf);

发送:向消息队列里发送消息

代码语言:javascript
复制
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

接收:从消息队列接受消息

代码语言:javascript
复制
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

2、信号量

创建:用来创建或者打开一个信号量

代码语言:javascript
复制
int semget(key_t key, int nsems, int semflg);

操作:可以申请或者释放信号量。

代码语言:javascript
复制
int semop(int semid, struct sembuf *sops, size_t nsops);

控制:可以设置或返回信号量的值,可以删除信号量

代码语言:javascript
复制
int semctl(int semid, int semnum, int cmd, ...);

3、共享内存

创建:用来创建或者打开一个共享内存。

代码语言:javascript
复制
int shmget(key_t key, size_t size, int shmflg);

连接:将共享内存附加到进程里的地址空间(有点像映射)

代码语言:javascript
复制
void *shmat(int shmid, const void *shmaddr, int shmflg);

脱离:将共享内存和进程地址空间脱离,但不删除共享内存本身

代码语言:javascript
复制
void *shmat(int shmid, const void *shmaddr, int shmflg);

设置属性:可以设置或者返回buf里的内容,也可以删除共享内存。

代码语言:javascript
复制
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);

接下来写一段代码,需求是:创建一个共享内存,子进程写入数据,父进程读出数据,同时使用信号量来对读写进行保护

代码语言:javascript
复制
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define  SHMSIZE  128

int main(int argc,char* argv[])
{
  if(argc !=2){
    printf("please input param\n");
    exit(0);
  }
  key_t semkey,shmkey;
  int semid,shmid,pid;
  char* shmp;
  struct sembuf locksembuf={0,-1,SEM_UNDO};
  struct sembuf unlocksembuf={0,1,SEM_UNDO|IPC_NOWAIT};
  struct shmid_ds shmdsbuff;
  semkey=ftok(argv[1],0);
  if(semkey==-1){
    printf("semkey ftok failed\n");
    exit(0);
  }
  shmkey=ftok(argv[1],1);
  if(semkey==-1){
    printf("shmkey ftok failed\n");
    exit(0);
  }
  semid=semget(semkey,1,IPC_CREAT | 0666);        //创建信号量
  if(semid==-1){
    perror("semget");
    exit(0);
  }
  shmid=shmget(shmkey,SHMSIZE,0666 | IPC_CREAT );      //创建共享内存
  if(shmid==-1)    
  {
    perror("shmget");
    exit(0);
  }
  printf("creat share memory success\n");
  shmp=shmat(shmid,NULL,0);                             //连接共享内存(映射地址)
  if((long)shmp==-1)
  { 
    perror("shmat failed\n");
    exit(0);
  }
  pid=fork();
  if(pid<0)
  {
    perror("fork failed\n");
    exit(0);
  }
  
  else if(pid==0)                              //子进程
  {
    printf("creat child process success\n");
    if(semctl(semid,0,SETVAL,1)<0)             //初始化信号量为1
    {
      perror("semctl");
      exit(0);
    }
    if(semop(semid,&locksembuf,1)==-1)          //申请资源
    {
      perror("semop");
      exit(0);
    }
    sleep(4);
    strcpy(shmp,"hello world\n");
    if(shmdt((void*)shmp)<0)        //使共享内存脱离进程地址空间
    {
      perror("shmdt");
      exit(0);
    }
    if(semop(semid,&unlocksembuf,1)<0)      //解锁临界资源
    {
      perror("child process unlock semop");
      exit(0);
    }
  }
  else                                      //父进程
  {
    printf("This is parent process\n");
    sleep(1);
    int ret=semop(semid,&locksembuf,1);
    if(ret<0)
    {
      perror("parent process semop lock");
      exit(0);
    }
    if(shmctl(shmid,IPC_STAT,&shmdsbuff)<0)      //父进程获取共享内存的信息
    {
      perror("shmctl");
      exit(0);
    }
    
    else{
      printf("get shm success\n");
      printf("Shared Memory Infomation:\n");
      printf("\tCreator PID:%d\n",shmdsbuff.shm_cpid);
      printf("\tSize(bytes):%ld\n",shmdsbuff.shm_segsz);
      printf("\tLast Operator PID :%d\n",shmdsbuff.shm_lpid);
      printf("Receive message:%s\n",(char*)shmp);
    }
    if(shmdt((void*)shmp)<0)
    {
      perror("parent process shmdt");
      exit(0);
    }
    ret=semop(semid,&unlocksembuf,1);
    if(ret<0)
    {
      perror("parent process semop");
      exit(0);
    }
    if(shmctl(shmid,IPC_RMID,NULL)<0)
    {
      perror("shmctl RMID");
      exit(0);
    }
    if(semctl(semid,0,IPC_RMID,NULL)<0)
    {
      perror("semctl RMID");
      exit(0);
    }
  }
  return 0;
}

接下来编译执行

代码语言:javascript
复制
gcc sem.c
./a.out a.txt      //注意,当前目录下需要有一个a.txt,其他文件也行

输出:

以上就是今天的内容。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子技术研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 CMQ 版
消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档