命名管道于System V

文章目录

管道的回顾

进程是具有独立性的—>进程通信的成本比较高—>必须先解决一个问题—>要让不同的进程看到同一份资源(内存文件,内存,队列)【一定是需要OS提供的】—-> pipe的本质:是通过子进程继承父进程资源的特性,达到一个目的让不同的进程看到同一份资源

我们通常标识一个文件:路径+文件名(具有唯一性)

命名管道

为了解决匿名管道只能在父子通信,我们就引入了命名管道
命名管道可以在命令行里面弄,也可以在程序里面弄

命令行

1
mkfifo filename

创建管道文件
在这里插入图片描述

在这里插入图片描述
测试
:我们虽然在是同一个文件,但是在不同的进程当中
一个进程中的数据通过管道文件传递到另一个进程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码中的使用

在这里插入图片描述
失败返回-1

在进程中创建一个命名管道文件,他的权限要和掩码进行按位&,我们标准情况下的掩码都是002

  • umask(0)

可以将掩码设置成0

我们发现一方发的信息直接就被另一方给收到了

在这里插入图片描述

因为命名管道是基于字节流的,所以实际上信息传递的时候,是需要通信双方定制”协议“的,到网络的时候就可以了解 了,今天我们就单纯的进行字符串通信即可

进程间通信的目的:一个进程去控制另一个进程
命名管道的数据不会刷新到磁盘,为了效率

在这里插入图片描述
命名管道为什么一定要有名字

为了保证不同进程看到同一个文件(通过文件名来操作)所以命名管道
而匿名管道是通过父子进程的方式,看到同一份资源,所以就不需要名字,来标识同一个资源

在这里插入图片描述

client.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include"head.h"

int main()
{
//因为fifo已经创建好了,所以我们不用再创建,直接用就行了
int fd=open(MY_FIFO,O_WRONLY);
//把文件打开
if(fd<0)
{
perror("open");
return 2;
}
//业务逻辑,我们再这里进行写入
while(1)
{
printf("请输入: ");//因为没有+\n,为了避免缓冲区的问题
fflush(stdout);//刷新一下缓冲区

char buffer[64]={0};
ssize_t s=read(0,buffer,sizeof(buffer));//我们要处理掉最后的回车键
//我们从命令行中获取输入,输入到buffer里面
if(s>0)
{
buffer[s]=0;//把最后的\0给吃掉
buffer[s-1]=0;
printf("%s\n",buffer);//读一条就发一下文件里面的内容
write(fd,buffer,strlen(buffer));//传给server
}
}
close(fd);

return 0;
}

server.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include"head.h"

int main()
{
umask(0);//把掩码设置为1
if(mkfifo(MY_FIFO,0666)<0)
{
perror("mkfifo");
return 2;
}
//对于管道文件,我们只需要进行文件操作即可
int fd=open(MY_FIFO,O_RDONLY,0666);
//我们让他执行读操作
if(fd<0)
{
perror("open");
return 1;
}
//执行业务逻辑
while(1)
{
char buffer[64]={0};
//我们每个5秒才从缓冲区里面读进来
sleep(5);
ssize_t s=read(fd,buffer,sizeof(buffer));//我们对fd文件进行读取,client通过管道对fd这个文件进行写入,我们在通过这个进行读取
if(s>0)
{
//读取结束了,buffer里面就是我们需要的字符串,我们用client去控制server让他去进行操作
if(strcmp(buffer,"show")==0)
{
if(fork()==0)
{
//子进程
execlp("ls","ls","-a",NULL);//程序替换
exit(1);//如果执行失败我们就可以把退出码设置为1
}
waitpid(-1,NULL,0);//父进程执行等待
}
if(strcmp(buffer,"sl")==0)
{
if(fork()==0)
{
//子进程
execlp("sl","sl",NULL);//程序替换
exit(1);//如果执行失败我们就可以把退出码设置为1
}
waitpid(-1,NULL,0);//父进程执行等待
}
buffer[s]=0;
printf("client# %s\n",buffer);
}
else if(s==0)//我们发现client的结束的时候,文件读写结束了,就会被不断的刷屏了
{
printf("client quit....\n");
}
else
{
perror("read");
break;
}
}
close(fd);
return 0;
}

System V标准进行通信方式

OS层面上,专门为进程间通信设计的一个方案,要不要给用户用,以什么方式给用户用?
肯定要给用户用,
OS不相信任何用户,给用户提供功能的时候,采用系统调用!
System V进程间通信,一定会存在专门原来通信的接口(system call)

所以就需要有个人组织机构来定制标准,就要有人来定制标准,在同一主机上的通信方案: system V

system V

  1. 共享内存
  2. 消息队列
  3. 信号量

共享内存

在这里插入图片描述
在这里插入图片描述

准备工作:

  1. OS 内存可不可能存在多个进程,同时使用不同的共享内存来通信??
    可能—> 共享内存在系统中你可能有多份!—》操作系统也要管理这些共享内存,—》如何管理这些共享内存呢?—》先描述再组织(一定要有内核数据结构)
  2. 怎么保证两个或两个以上的进程会看到同一个共享内存,(共享内存一定要有一定的标识唯一的ID),方便让不同的进程就能识别同一个共享内存的资源!!
    这个ID在哪里呢,这个id就在描述的结构体里面

shmget

在这里插入图片描述
失败返回-1

创建共享内存段

  • size:创建这共享内存的大小(我们一般建议是4KB的整数倍),共享内存在内核当中申请的基本单位是页,内存页(4KB)

如果我申请来了4097个字节>4KB,所以内核会向上取整,变成8KB,我们就给你4097,但是操作系统是按照4096*2的方式给你的

  • shmflg:标记选项

在这里插入图片描述

  • IPC_CREAT:创建一个新的共享内存段,如果单独使用IPC_CREAT,或者flag为0,创建一个共享内存,如果创建的共享内存已经存在,则直接返回当前已经存在的共享内存(不存在则创建,存在就获取一个),(基本不会空手而归)

  • IPC_EXCL:一般不会单独使用的,IPC_CREAT|IPC_EXCL这两个单独使用的才有意义
    如果不存在共享内存则创建,如果已经存在了共享内存,则返回出错!(意义在于,如果调用成功,得到的一定是一个最新的,没有被人使用的共享内存!)

  • key:为了保证看到的是唯一的标识符,目的是为了让不同的进程来识别的,本质是可以用这标识符让不同的进程看到同一份资源:先让不同的进程看到同一个ID,
    用ftok来操作


在这里插入图片描述
./server进程执行结束,该进程曾经创建的共享内存没有被释放,
第二次运行的时候发现他说文件已经存在(创建共享内存失败)

system V 的IPC 资源,生命周期是随内核的(只能通过程序员使用命令或者系统调用来释放,或者OS 重启)
于文件不同:文件只要和他相关的进程退出的话,那么这个文件所对应的资源也都全部被释放掉

ipc

ipcs

命令行中可以查看共享内存,消息队列,信号量
在这里插入图片描述

1
ipcs -m//只查看共享内存

在这里插入图片描述

ipcrm

把共享空间给删除掉
ipcrm -m shmid
在这里插入图片描述

key和shmid

  • key:只是用来在系统层面进行标识唯一性的,不能原来管理共享内存
  • shmid:是OS 给用户返回的id,用来在用户层对共享内存进行管理

shmid相当于fd,而key相当于文件的地址

命令行是属于用户层:所以肯定是使用shmid

ftok

先调用ftok,获得key值,传给shmget
在这里插入图片描述
pathname :我们自定义的路径名
proj_id:我们自定义的项目id
失败了就返回-1

如我们的路径名“./temp”
项目id:0行66
这数字是什么不重要,只要保证唯一性就可以了

怎么保证不同的进程看到的是同一个共享内存?只要我们形成key的算法+原始数据是一样 的,。形成同一个id

这里的key就是会设置进内核的关于shm的内核中的数据结构中

shmctl

在这里插入图片描述
成功返回0,
shmid:就是我们shmget里面创建获得的id
cmd:IPC_RMID,把这段给销毁掉
在这里插入图片描述
buf:
struct shmid_ds,共享内存的结构体
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
shmid也是数组下标
控制共享内存(我们只了解删除)

shmat shmdt

在这里插入图片描述

return:成功就返回那个共享空间段的地址(虚拟地址:我们程序员看到的地址都是虚拟地址,不可以看到物理地址),失败的话就返回-1
如malloc,返回的空间也都是虚拟的,相当于平白无故多了一个空间

在这里插入图片描述

attach:把我们调用的继承和共享内存关联起来
关联和去关联

  • shmid:来表示一个共享内存,
  • shmaddr:我们要挂接的时候,我们想要把它挂接到我的地址空间的什么地方去,但是我们一般不关心不清楚,所以我们就直接设置为NULL,就可以了
  • shmflag:我们也直接设置为0就可以了

shmdt:去关联,并不是释放共享内存,而是取消当前进程和共享内存的关系,(和页表构建映射关系的页表项删掉)返回值不重要
参数是我们关联后的返回值

struct shmid_ds

在这里插入图片描述
shmid_ds:权限,内存段的大小,上一次挂接的时间,上一次取消挂接的时间,创建者的pid,一共有多少个挂接
ipc_perm里面还有很多的uid,,同时还有key,我们可以根据key值找到对应的消息队列
消息队列和这个也是类似的,

  1. 我们会发现消息队列,信号量,共享内存,的接口类似
  2. 数据结构的第一个结构类型是完全一样的(struct ipc_perm)

结论:所有的ipc资源都是通过数组组织起来的,

所有的System V 标准的IPC 资源,XXid_ds结构体的第一个成员都是ipc_perm(这个是一样的,)

在这里插入图片描述
这些数组可以给我们指向不同的结构体,使用数组的下标
指向一个一个IPC资源的结构体指针

struct ipc_perm* ipc_id_arry[64]
ipc_id_arry[0]=(ipc_perm)&shmid_ds;我们通过强转,
当我们需要使用第一个资源的时候不需要强转,当我们使用其他资源的时候就需要强转
(shmid_ds*)ipc_id_arry[0]->shmid_ds的其他属性
相当于c++的切片功能

信号量

临界资源

所有可以被多个执行流同时访问的资源就是临界资源,
例如:
多进程启动后同时向显示器打印,这个显示器就是所谓的临界资源
而我们在学习进程间通信的时候,管道,共享内存,消息队列等,不同的进程能够同时访问
共享内存是最典型的共享资源

所以凡是进程间通信,必定需要引入可以被多个进程看见的资源(通信需要),这一份资源就变成了临界资源,同时,也引入一个新的问题:临界资源的问题

但是我们在进程中定义一个全局变量,之后fork子进程,但是这个全局变量不是临界资源,因为子进程如果对这个数据进程修改的话,会发生写实拷贝

临界区

进程的代码可以很多的,其中用来访问临界资源的代码叫做临界区
在这里插入图片描述
类似显示器:
显示器叫做临界资源,在显示器中printf叫做临界区

原子性

一件事情要么不做,要么就做完,没有中间态,就叫做原子性
非原子性:有中间过程

比如学习,要么啥也不学,要么就考第一名,

1
2
3
4
5
//在多进程中,定义了一个全局变量
int cout=100;//
//多进程=父+子
cout--; cout--;//父子都-

父和子对于cout处理不同
这里的cout不是原子的,cout是为了保护临界资源的
每个人想要进入电影院,必须要先看到cout,
cout本身就是也是临界资源的
所以信号本身就是临界资源的,

互斥

在任意时刻只能允许一个执行流进入临界资源,执行他的临界区

1
2
3
4
5
6
7
8
sem=1//临界资源只有一份
if(sem>0)
sem--;//进入--
else if(sem<=0)
{
//等待,进入临界区执行
}
sem++;//出去++

只有0和1就叫做2元信号量

什么是信号量

管道,匿名or命名,共享内存,消息队列,都是以传输数据为目的的!
信号量不是以传输数据为目的的!通过共享内存“资源的方式”,来达到多个进程的同步和互斥的目的!
信号量的本质:

是一个计数器,类似 int count :衡量临界资源的中资源的数目的

  • 在电影院的某一个放映厅也是一个临界资源!可以被不同的人访问,我们在进去的时候别人也可以进去,
  • 并不是我们坐在这个位置上,这个位置才属于我们,在外面买到票的时候,这座位就已经属于我们了,
  • 而买票的本质:对临界资源的预定机制

一个放映厅最怕什么?一共有100个座位,卖了110张票,所以最多只能买100张票,———用信号量来约束这行为

信号量本身一个临界资源
所以信号量的p操作和delete操作就是必须保证是原子的

1
2
3
4
5
6
int count=100;
cout--;//一个人买走了一张票
//愉快的看电影
cout++;//访问电影院的人出去了,


信号量的伪代码

1
2
3
4
5
6
7
if(cout>0)
cout--;
else
{
//等待
}
cout++;

命名管道于System V
http://example.com/2022/04/29/命名管道于System V/
作者
Zevin
发布于
2022年4月29日
许可协议