Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Linux笔记(16)| 进程同步机制——管道和IPC

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

作者头像
飞哥
发布于 2020-11-25 02:01:55
发布于 2020-11-25 02:01:55
2.2K00
代码可运行
举报
运行总次数:0
代码可运行

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

一、管道(无名管道)

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

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

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

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

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

……

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

创建管道

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
key_t ftok(const char* pathname,int proj_id);

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

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

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

1、创建消息队列

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int msgget(key_t key,int msgflg);

2、创建信号量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int semget(key_t key,int nsems,int semflg);

3、创建共享内存

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
int msgget(key_t key,int msgflg);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int msgctl(int msqid,int cmd,struct msqid_ds *buf);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

2、信号量

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int semget(key_t key, int nsems, int semflg);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int semop(int semid, struct sembuf *sops, size_t nsops);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int semctl(int semid, int semnum, int cmd, ...);

3、共享内存

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int shmget(key_t key, size_t size, int shmflg);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void *shmat(int shmid, const void *shmaddr, int shmflg);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void *shmat(int shmid, const void *shmaddr, int shmflg);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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
代码运行次数:0
运行
AI代码解释
复制
gcc sem.c
./a.out a.txt      //注意,当前目录下需要有一个a.txt,其他文件也行

输出:

以上就是今天的内容。

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

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Linux】system V消息队列,信号量
消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
用户11029103
2025/03/19
2230
【Linux】system V消息队列,信号量
【Linux】system V进程间通信——共享内存、消息队列、信号量
进程具有独立性:内核数据结构包括对应的代码、数据与页表都是独立的。OS系统为了让进程间进行通信:1.申请一块空间 2.将创建好的内存映射进进程的地址空间。共享内存让不同的进程看到同一份的资源就是在物理内存上申请一块内存空间,如何将创建好的内存分别与各个进程的页表之间建立映射,然后在虚拟地址空间中将虚拟地址填充到各自页表的对应位置,建立起物理地址与虚拟地址的联系。
平凡的人1
2023/10/15
4610
【Linux】system V进程间通信——共享内存、消息队列、信号量
【Linux】进程间通信「建议收藏」
进程之间可能会存在特定的协同工作的场景,而协同就必须要进行进程间通信,协同工作可能有以下场景。
全栈程序员站长
2022/11/10
1.6K0
【Linux】进程间通信「建议收藏」
Linux进程间通信之System V
对于进程间通信,想必管道大家再熟悉不过了,对于管道这种通信方式,其实是对底层代码的一种复用,linux工程师借助类似文件缓冲区的内存空间实现了管道,其实也算偷了一个小懒,随着linux的发展,linux正式推出了System V来专门进行进程间通信,它和管道的本质都是一样的,都是让不同的进程看到同一份资源。
咬咬
2024/06/12
2420
Linux进程间通信之System V
linux进程间通信方式最常用_linux进程调度
管道可用于具有亲缘关系进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
全栈程序员站长
2022/11/10
2.3K0
进程间通讯(六).semaphore and shared(1)
UNIX/Linux 是多任务的操作系统,通过多个进程分别处理不同事务来实现,如果多个进程要进行协同工作或者争用同一个资源时,互相之间的通讯就很有必要了
franket
2021/09/15
7940
【Linux进程通信】四、System V IPC
​ 这里我们介绍的这种通信方式也就是 system V IPC 在我们后面的使用和日常见到的其实并不多,但是包括其中的共享内存、消息队列、信号量,我们如果了解共享内存其原理的话,能够更好的帮助我们了解之前我们学过的进程地址空间的概念!
利刃大大
2025/03/22
2080
【Linux进程通信】四、System V IPC
System|IPC|Rethinking IPC
IPC,进程间通信,是打破地址空间隔离的必经之路。本文按照个人理解对于IPC进行了一些分类与整理。
朝闻君
2021/11/22
8690
System|IPC|Rethinking IPC
15(进程间通信)
管道是Unix系统IPC最古老的方式。管道有下列两种局限性: (1) 历史上,它们是半双工的(即数据只能在一个方向上流动)。 (2) 它们只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程就可以应用该管道
提莫队长
2019/02/21
6300
进程间通信—管道,共享内存,消息队列,信号量
在操作系统中进程具有独立性,那么进程之间进行通信必然成本不低。那么进程间通信方式有哪些呢?
梨_萍
2023/06/01
2.2K0
进程间通信—管道,共享内存,消息队列,信号量
Linux之进程间通信——system V(共享内存、消息队列、信号量等)
本文介绍了另一种进程间通信——system V,主要介绍了共享内存,消息队列、信号量,当然消息队列了信号量并非重点,简单了解即可。
摘星
2023/10/15
4440
Linux之进程间通信——system V(共享内存、消息队列、信号量等)
【Linux】SystemV IPC
那么我们知道,进程间通信的本质就是先让不同的进程看到同一份资源。我们以前学的管道都是基于文件的,那么我们还有其它方案进行进程间通信吗?有的,那么我们下面学习的共享内存就是由操作系统帮我们在地址空间中进行通信。
YoungMLet
2024/03/01
2800
【Linux】SystemV IPC
进程间通讯(六).semaphore and shared(3)
连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
franket
2021/09/15
6890
Linux应用开发【第四章】Linux进程间通信应用开发
​ 在日常工作/学习中,读者可能会经常听到如下一些词:“作业”,“任务”,“开了几个线程”,“创建了几个进程”,“多线程”,“多进程”等等。如果系统学习过《操作系统》这门课程,相信大家对这些概念都十分了解。但对很多电子、电气工程专业(或是其他非计算机专业)的同学来说,由于这门课程不是必修课程,我们脑海中可能就不会有这些概念,听到这些概念的时候就会不知所云,不过没有关系,先让我们克服对这些概念的恐惧。比如小时候刚开始学习数学的时候,先从正整数/自然数开始学习,然后逐步接触到分数、小数、负数、有理数、无理数、实数,再到复数等等。这些操作系统中的概念也是这样,让我们从初级阶段开始学起,逐步攻克这些新概念背后的真正含义。
韦东山
2021/12/15
4K0
Linux应用开发【第四章】Linux进程间通信应用开发
Linux进程间通信【消息队列、信号量】
在 System V 通信标准中,还有一种通信方式:消息队列,以及一种实现互斥的工具:信号量;随着时代的发展,这些陈旧的标准都已经较少使用了,但作为 IPC 中的经典知识,我们可以对其做一个简单了解,扩展 IPC 的知识栈,尤其是 信号量,可以通过它,为以后多线程学习中 POSIX 信号量的学习做铺垫
北 海
2023/07/01
9290
Linux进程间通信【消息队列、信号量】
进程间通讯(六).semaphore and shared(2)
SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值
franket
2021/09/15
6100
【在Linux世界中追寻伟大的One Piece】System V共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
枫叶丹
2024/10/03
1670
【在Linux世界中追寻伟大的One Piece】System V共享内存
原来\进程间通信/是这么回事......
在系统中,随着我们的进程越来越多,难免不同进程之间要互相传输一些数据,那么这个时候该怎么办呢?
董哥聊技术
2022/11/14
6830
原来\进程间通信/是这么回事......
Linux 的进程间通信:信号量
本文介绍了Linux信号量、POSIX信号量、Linux条件变量和Linux线程同步基本概念,并通过代码示例展示了如何使用这些技术进行线程同步。
邹立巍
2017/07/25
7K0
Linux:进程间通信(二.共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
Linux:进程间通信(二.共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
是Nero哦
2024/07/13
4340
Linux:进程间通信(二.共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
推荐阅读
相关推荐
【Linux】system V消息队列,信号量
更多 >
交个朋友
加入HAI高性能应用服务器交流群
探索HAI应用新境界 共享实践心得
加入架构与运维学习入门群
系统架构设计入门 运维体系构建指南
加入架构与运维工作实战群
高并发系统设计 运维自动化实践
换一批
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验