Linux 进程间通信

目录

1.基本介绍

1.1 进程间通信引入

1.2 进程间通信目的

1.3 进程间通信方式

2. 管道

2.1 匿名管道:pipe

2.2 命名管道

3. 共享内存

3.1 使用流程

4. 消息队列

4.1 使用流程

5. 信号量


1.基本介绍

  • 1.1 进程间通信引入

因为进程的独立性,使得它们之间通信较为麻烦,因此需要OS提供一些方法 使得进程之间能够共同访问一个相同的媒介。

  • 1.2 进程间通信目的

    • 数据传输
    • 数据共享
    • 进程间的访问控制
    • 事件通知
  • 1.3 进程间通信方式

    • 通信目的不同,使用场景不同
      • -> OS提供多种进程间通信方式
        • 管道(命名管道/匿名管道) -> 传输数据
        • 共享内存 -> 共享数据
        • 消息队列 -> 传输数据
        • 信号量 -> 进程间的访问控制

2. 管道

内核中的一块缓冲区,主要用于实现进程间数据资源传输

  • 半双工通信
    • 双向选择的单向通信
  • 管道生命周期随进程 -> 进程结束,管道消失。
  • 原理
    • OS为了进程,在内核创建了一块缓冲区,只要进程能够访问到缓冲区,它们就能通过管道进行通信。
  • 管道提供流式服务
    • 面向字节流数据传输 -> 传输灵活,但是会造成数据粘连。
  • 2.1 匿名管道:pipe

    • 只能用于具有亲缘关系的进程间通信
      • 缓冲区没有具体标识符,只能通过子进程复制父进程的方式获取到管道的操作句柄
        • 两个文件描述符 fd[0] -> 读   fd[1] -> 写
    • 原理
      • 内核的缓冲区,需要父进程创建子进程,通过复制的方式,让子进程能够访问到这个缓冲区。
    • 匿名管道的创建接口:int pipe(int pipefd[2]);
      • pipefd:输出型参数
        • pipefd[0]:向管道读数据
        • pipefd[1]:向管道写数据
      • 返回值
        • 成功 -> 0
        • 失败 -> -1
    • 读写特性
      • 虽然管道提供了双向选择,但是若没有用到某一端,就把这一端关闭。
        • 若管道中没有数据,read会堵塞,直到读到数据返回
        • 过管道中数据满了,write会阻塞,直到数据被读取,管道中有空闲位置,写入数据后返回。
        • 若管道所有的读端都被关闭,则write会触发异常,发送SIGPIPE信号,导致进程退出(通知用户没人读了)。
        • 若管道所有的写端都被关闭,则read读完管道数据后,返回0(通知用户没人写了)。
    • 管道自带同步与互斥特性
      • 当读写大小不超过PIPE_BUF时,读写操作受保护,保证操作原子性(操作不可被打断)。
      • 同步:保证对临界资源访问的时序可控性
        • 控制购买商品的人不能一直购买,完成一次购买后,排队等待下一次购买
      • 互斥:保证对临界资源同一时间的唯一访问性
        • 一名顾客在买东西的时候,其他的顾客只能等待
/*
 * 匿名管道的基本使用
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main(){
    //创建管道
    int pipefd[2];
    int ret=pipe(pipefd);
    if(ret <0){
		perror("pipe error");
		return -1;
    }
    int pid=fork();
    if(pid<0){
		perror("fork error");
		return -1;
    }else if(pid==0){
	//子进程 write
		sleep{3};
		char *ptr="bonsoir~~~";
		write(pipefd[1],ptr,strlen(ptr));
    }else{
	//父进程 read
		char buf[1024]={0};
		read(pipefd[0],buf,1023);
		printf("read buf:[%s]\n",buf);
    }
    return 0; 
}
  • 匿名管道实现命令连接,将一个命令的输出结果作为下一个命令的输入
/*
 * 匿名管道实现命令连接
 * ps -ef|grep ssh
 * ps -ef:
 *    输出:所有的进程信息写入标准输出
 * grep ssh:
 *    从标准输入读取数据,进行过滤处理
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(){
    int pipefd[2];
    int ret=pipe(pipefd);
    //管道创建失败
    if(ret<0){
	perror("pipe error");
	return -1;
    }
    //创建子进程1执行前部分命令
    int pid1=fork();
    if(pid1==0){
	//child1 -> ps -ef
	//关闭读端
	close(pipefd[0]);
	//将输出放入管道
	dup2(pipefd[1],1);
	execlp("ps","ps","-ef",NULL);
    }
    
    //创建子进程2执行后部分命令
    int pid2=fork();
    if(pid2==0){
	//child2 -> grep ssh
	//关闭写端
	close(pipefd[1]);
	//从管道中读取数据
	dup2(pipefd[0],0);
	execlp("grep","grep","ssh",NULL);
    }
    //parent
    //关闭父进程管道
    close(pipefd[0]);
    close(pipefd[1]);
    waitpid(pid1,NULL,0);
    waitpid(pid2,NULL,0);
    return 0;
}
  • 2.2 命名管道

    • 可见于文件系统,因为创建命名管道会随之在文件系统中创建一个命名管道文件(管道的名称)。
      • 所有的进程都能够通过打开管道文件,进而获取管道的操作句柄
      • 因此命名管道可以用于同一主机上任意进程间通信
    • 原理
      • 依然还是内核的缓冲区,只是通过文件向所有进程都提供了能够访问管道的机会而已
    • 命名管道的创建接口:int mkfifo(const char *pathname, mode_t mode);
      • pathname:管道文件名
      • mode:创建权限
      • 返回值:
        • 成功 -> 0
        • 失败 -> -1
    • 打开特性
      • 以只读方式打开时,若管道文件没有其他进程以写的方式打开,则阻塞,直到文件被以写的方式打开
      • 以只写方式打开时,若管道文件没有其他进程以读的方式打开,则阻塞,直到文件被以读的方式打开
      • 以读写方式打开时,则不会阻塞。
    • 读写特性与匿名管道相同
/*命名管道的基本使用 读*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main(){
    char *file="./test.fifo";
    umask(0);
    int ret=mkfifo(file,0664);
    if(ret<0){
	if(errno!=EEXIST){
	    perror("mkfifo error");
	    return -1;  
	}
    }
    
    int fd=open(file,O_RDONLY);
    if(fd<0){
	perror("open error");
	return -1;
    }
    printf("open success!!\n");
    while(1){
	char buf[1024]={0};
	//读取数据
	int ret =read(fd,buf,1023);
	//读到数据
	if(ret>0){
	    printf("read buf:[%s]\n",buf);
	//所有写端关闭
	}else if(ret==0){
	    printf("write closed");
	}else{
	    perror("read error");
	}
    }
    close(fd);
    return 0;
}
/*命名管道的基本使用 写*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main(){
    char *file="./test.fifo";
    umask(0);
    int ret=mkfifo(file,0664);
    if(ret<0){
	if(errno!=EEXIST){
	    perror("mkfifo error");
	    return -1;  
	}
    }
    int fd=open(file,O_WRONLY);
    if(fd<0){
	perror("open error");
	return -1;
    }
    printf("open success!!\n");
    while(1){
	//写入数据
	char buf[1024]={0};
	scanf("%s",buf);
	write(fd,buf,strlen(buf));
    }
    close(fd);
    return 0;
}

3. 共享内存

在物理内存中开辟出一块空间,映射到虚拟地址空间。多个进程通过将同一块内存映射到自己的虚拟地址空间,达到数据共享的一个目的。

  • 是最快的进程间通信方式
    • 其他进程间通信方式 -> 将数据从用户态拷贝到内核态,使用时从内核态拷贝到用户态;需要跟内核缓冲区进行数据交互
    • 共享内存 -> 直接将一块内存映射到虚拟地址空间,用户可以直接通过地址对内存进行操作,并反馈到其他进程
    • 相较于其他进程间通信方式,少了两步数据拷贝的过程。
  • 3.1 使用流程

    • 1. 创建 / 打开共享内存     shmget
      • key_t ftok(const char *pathname, int proj_id);
        • pathname:文件名
        • proj_id:数字
        • 通过文件的inode节点号和proj_id共同得出一个key值
      • int shmget(key_t key, size_t size, int shmflg);
        • key:共享内存标识符
        • size:共享内存大小
        • shmflg:打开方式 / 创建权限
          • IPC_CREAT    共享内存不存在则创建,存在则打开
          • IPC_EXCL      与IPC_CREAT同用,若存在 -> 报错;不存在 -> 创建
          • mode_flags     权限
        • 返回值
          • 成功 -> 操作句柄shmid
          • 失败 -> -1
    • 2. 将共享内存映射到虚拟地址空间     shmat
      • void *shmat(int shmid, const void *shmaddr, int shmflg);
        • shmid:创建恭喜囊诶村返回的操作句柄
        • shmaddr:用于指定映射在虚拟地址空间的首地址,通常置NULL
        • shmflg:0 -> 可读可写
        • 返回值
          • 成功 -> 映射首地址,通过这个地址对共享内存进行操作
          • 失败 -> (void*)-1
    • 3. 对共享内存进行基本的内存操作     memcpy...
    • 4. 解除映射关系     shmdt
      • int shmdt(const void *shmaddr);
        • shmaddr:映射返回的首地址
    • 5. 删除共享内存     shmctl
      • int shmctl(int shmid, int cmd, struct shmid_ds *buf);
        • shmid:操作句柄
        • cmd
          • IPC_RMID:删除共享内存
        • buf:设置 / 获取共享内存信息,不使用则置NULL
    • 共享内存的查看
      • ipcs命令   ipcs -m
    • 共享内存的删除
      • ipcrm命令   ipcrm -m shmid
  • 共享内存并不是立即删除的,只是拒绝后续映射连接。当共享内存映射连接数为0时,则删除共享内存。

4. 消息队列

  • 传输的是有类型的数据块,用户可以根据自己的需要选择性的获取某些类型的数据。
  • 4.1 使用流程

    • 1. 创建消息队列    msgget
      •  int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
        • msgp:数据节点
    • 2. 添加数据节点    msgsnd
      • int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    • 3. 获取数据节点    msgrcv
      • ​​​​​​​ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
    • 4. 删除    msgctl
      • int msgctl(int msqid, int cmd, struct msqid_ds *buf);

5. 信号量

  • 内核中的一个计数器(具有等待、唤醒功能的等待队列),用于资源计数
    • 若计数<=0 ,没有资源 -> 需要等待
    • 若计数>0 ,有资源 -> 可以获取资源,计数-1
    • 若放入资源 -> 计数+1,并且唤醒等待的进程
  • 实现进程间的同步与互斥
    • 互斥:资源为0或1时,才具有互斥效果;但是可以实现互斥。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值