进程间通信

理解

进程间通信是操作系统为用户提供的在不同进程之间传播或交换信息的方式。目的在于:

  1. 数据传输->一个进程要把它的数据发送给其他进程
  2. 资源共享->多个进程间共享同一份资源
  3. 通知事件->一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  4. 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。


为什么进程之间不能直接通信?
【进程的独立性】:每个进程都有一个自己的独立的虚拟地址空间,进程操作自己的虚拟地址,因此进程间无法直接通信。
操作系统如何实现进程间通信?
在进程之间提供一个公共的媒介,让每一个进程都能访问到。通信场景有所不同,操作系统提供多种方式:管道、System V(共享内存、消息队列、信号量)…

管道

管道(是Unix中最古老的进程间通信的形式,传数据流)
将内核中的一块缓冲区作为公共媒介让多个进程访问从而实现通信。

分类

  • 匿名管道
    没有名字的管道,只能用于具有亲缘关系的进程间进行通信。

  • 命名管道
    有名字的管道,内核中的(管道)缓冲区有自己的标识符,可用于同一主机上任意进程间通信。

特性

  • 半双工通信(可以选择方向的单向传输。)
  • 生命周期随进程(进程退出,管道释放)
  • 自带同步、异步
    同步:通过对条件的判断,实现对数据访问的合理有序性
    异步:通过同一时间唯一访问实现对数据访问资源的安全性
  • 以字节流传输->安全、有序,以字节为单位
匿名管道

一个进程创建一个管道,操作系统为了让进程对管道读写数据,向外返回两个操作句柄(文件描述符),一个用于读,一个用于写。

在进程中创建了管道后创建子进程,子进程通过复制父进程中两个操作句柄对管道进行操作,所以说父子进程操作同一个管道。

int pipe(int pipefd[2])
//创建匿名管道并返回两操作句柄:
pipefd[0]//从管道读取数据
pipefd[1]//向管道写入数据

匿名管道读写特性

  • 若管道中没有数据,read(读取)阻塞,若写满数据,write(写入)堵塞
  • 若管道中所有写端被关闭,读取完管道中数据后返回0,不再阻塞
  • 若管道中所有读端被关闭,继续写入数据会触发异常,导致程序退出

测试:在test目录下创建ipc文件中进行测试:

vi makefile 
#makefile中写入以下代码
pipe:pipe.c
        gcc $^ -o $@


在运行后发现子进程会等待父进程写入后再读取数据。

命名管道(FIFO文件)

命名管道是一种特殊类型的文件,实现在同一主机不相关的进程间交换数据。
创建:mkfifo
命名管道打开特性

  • 若管道以只读的方式打开会堵塞,直到管道被以写方式打开为止
  • 若管道以只写的方式打开会堵塞,直到管道被以读方式打开为止

eg:命名空间实现文件拷贝

//读取文件写入管道
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
    perror(m); \
    exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
    mkfifo("tp", 0644);
    int infd;
    infd = open("abc", O_RDONLY);
    if (infd == -1) 
    	ERR_EXIT("open");
    int outfd;
    outfd = open("tp", O_WRONLY);
    if (outfd == -1) 
    	ERR_EXIT("open");
    char buf[1024];
    int n;
    while ((n=read(infd, buf, 1024))>0)
   {
   write(outfd, buf, n);
   }
    close(infd);
    close(outfd);
    return 0;
}
//——————————————————————————
//读取管道,写入目标文件
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
 do \
 { \
 perror(m); \
 exit(EXIT_FAILURE); \
 } while(0)
 
int main(int argc, char *argv[])
{
 int outfd;
 outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
 if (outfd == -1) ERR_EXIT("open");
 
    int infd;
    infd = open("tp", O_RDONLY);
    if (outfd == -1)
        ERR_EXIT("open");
    char buf[1024];
 int n;
 while ((n=read(infd, buf, 1024))>0)
 {
write(outfd, buf, n);
 }
 close(infd);
 close(outfd);
 unlink("tp");
 return 0; }
共享内存

开辟一块物理内存,让所有进程都能访问到,从而实现数据共享【进程间通过映射链接到同一块物理内存实现数据共享】

共享内存是最快的进程间通信方式原因:内存共享直接通过虚拟地址空间访问数据内页,进行数据共享,而其他通信方式需要将数据拷贝进内核,再从内核拷贝出来从而实现通信。共享内存较其他方式少了两次数据拷贝,因此速度最快。

代码操作
  1. 创建/打开共享内存
  2. 将共享内存映射到进程的虚拟空间
  3. 通过任意的内存操作,对共享内存进行操作(memcpy/strcpy)
  4. 解除映射关系(共享内存会记录当前的进程映射链接数)
  5. 删除共享内存(当前的进程映射链接数为0时才会删除,不为0不会立即删除,只是禁止链接直到0时才会删除)

#include <sys/ipc.h>
#include <sys/shm.h>
1.创建
int shmget(key_t key, size_t size, int shmflg);
-key:共享内存标识符(多个进程通过同一标识符访问同一共享内存)
-size:要创建的共享内存,以内存页为单位
-shmflg:
IPC_PRIVATE【不常用】:私有,只用于有亲缘关系的进程间通信
IPC_CREAT :共享内存不存在就创建,存在打开
IPC_EXCL :与IPC_CREAT同时使用,不存在创建,存在报错
mode 权限:0777
-返回值:共享内存在代码中的操作句柄

vi shm_read.c#进行共享内存操作


对makefile更改:

ipcs查看进程间的通信资源

-m:共享内存
-q:消息队列
-s:信息量


在运行程序过后创建共享内存0x12345678,nattch(映射连接数)为0

若不手动删除会永远存在内核中。

特性
  • 最快的进程间通信
  • 生命周期随内核
  • 共享内存的操作并不安全【不具备同步互斥,多个进程同时操作可能造成数据二义—>操作系统需要被保护】

进程间通信标识KEY值:
key_t ftok(const char *pathname, int proj_id);
从文件inode节点取出部分数据与int proj_id中部分数据合并生成key值。
这种计算方式很依赖文件,一旦文件删除,key值会改变,多个进程无法映射同一地址空间【共享内存将不再数据共享】

shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:创建共享内存时返回的操作句柄
shmaddr:创建内存映射在虚拟内存空间中首地址,常置NULL,让操作系统分配合适的地址
shmflg:对共享内存具有权限的话,可以设置在代码中的操作权—SHM_RDONLY:只读(前提:有读的权限)/默认0,可读可写
返回值:返回共享内存在虚拟地址空间中实际映射的首地址【用来访问共享内存】

int shmctl(int shmid, int cmd, struct shmid_ds *buf)
设置/获取/删除
-shmid:操作句柄
-cmd:堆内存想进行的操作IPC_RMID:标记共享内存将要销毁(链接数为0时)
-buf:设置/获取共享内存时,返回数据/设置新数据

消息队列

本质:在操作系统内核中创建了一个优先级队列,多个进程间通过向队列中添加数据块获取数据块实现数据通信。
int msgget(key_t key, int msgflg);
struct msgbuf{type;data}-----这个结构体由用户定义,因为操作系统无法决定数据长度。

特性

  • 生命周期随内核
  • 自带同步、互斥

缺陷能存储的数据有最大长度限制

信号量

本身不是用于实现通信的,而是用于实现进程同步互斥。保护进程间对临界资源的访问时不会出现二义性。
同步:通过一定条件判断,实现进程间对临界资源访问的时序合理性
互斥:通过同一时间的唯一访问,实现进程间对临界资源访问的安全性
临界资源:进程间都能访问到的资源
临界区:对临界资源进行操作的代码区域

信号量本质是:计数器+pcb等待队列------>使一个进程陷入休眠的接口。唤醒一休眠进程的接口。

信号量同步的实现
初始化时有多少数据资源,计数器会初始化为相应数量。
进程X去获取资源,若计数器>0,有资源,获取资源、计数器-1;计数器<=0即没有资源,此时要调用使进程进入休眠的接口让进程等待、计数器-1。
进程Y产生资源,若计数器<0,调用唤醒休眠进程的接口(唤醒休眠进程X)、计数器+1;若计数器>=0,没有资源等待,计数器+1。

信息量互斥的实现:保证同一时间只有一个资源访问进程。
信息量本身是计数器,计数器为0时候让让继续要访问进程的资源进入休眠,等待被其他进程唤醒。

若计数器计数最大为1,表示只有一个数据资源、只有一个人可以访问

进程X访问临界资源:计数从1减为0,此时其他进程继续访问会陷入休眠等待
X对临界资源访问完毕,计数器加一,唤醒等待中进程,开始抢夺资源

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值