准备
博主:大大怪先森(记得关注,下次不要迷路哦) 编程环境:xshell(点击下载)
提示:写完文章后,目录可以自动生成
前言
本文将讲解进程间通信的相关知识!!!
提示:以下是本篇文章正文内容,下面案例可供参考
一、通信的介绍
首先我们需要知道的是两个进程在实现通信的前提是必须看到同一块相同的资源,以这个资源为媒介,实现两个进程的相互通信,就好比两个人打电话,而手机就是这个交流的媒介;那么通信又有什么目的? 如下:
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
二、管道
1.匿名管道
在没有了解管道之前,我们先谈一谈进程是如何读取磁盘当中的文件的? 如下图: 那么当我们在读取文件的时候是不是可以直接可以不让内核缓冲区的数据不触发底层驱动读或写到磁盘上面,直接让父子进程访问这一块内核缓冲区,大大提高进程之间的通信小路,于是就引出的今天我们需要学习的匿名管道。 当我们同时读写打开一个匿名管道,父子进程的内容完全一样。父子进程同时对管道内容进程不同读或写操作,于是就可以实现父子进程之间的通信。
代码实现:
#include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4
5 #include<stdlib.h>
6 int main()
7 {
8 int piped[2] = {0};
9 if(pipe(piped) != 0)
10 {
11 perror("pipe error!");
12 return 1;
13 }
14
15
16 int count = 0;
17 if(fork() == 0)
18 {
19
20 close(piped[0]);
21
22 while(1)
23 {
24 write(piped[1],"a",1);
25 count++;
26 printf("count:%d\n",count);
27
28
29 }
30
31 }
32 close(piped[1]);
33 sleep(10);
34 char arr[5] = {0};
35 read(piped[0],arr,sizeof(arr));
36 printf("%s",arr);
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 return 0;
59 }
注意(4种情况,5中特点):
4种情况:
a.读端不写或或者读的满,写端等读端
b.读端关闭,写段收到os操作系统的ISIPFPIPE信号直接终止
c.写端不写或者写的慢,读端要等写端
d.写端关闭,读端读完pipe内部数据然后再读,会读到0表明文件结尾。
5种特点:
1.管道是一种只能单向通信的通信信道
2.管道是面向字节流的!
3.仅限与父子进程之间且具有血缘关系的进程
4.管道自带同步机制,原子性写入。
5.管道的生命周期是随进程的。
2.命名管道
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。 命名管道是一种特殊类型的文件 思路:我们在打开和创建文件的时候都在通过内存对于磁盘的操作,是否当我们在打开文件的时候直接将文件的内容存放在内存当中,而不是存放在磁盘当中。于是这就是命名管道的工作原理。
函数接口:
//创建命名管道
int mkfifo(const char *filename,mode_t mode);
client代码:
1 #include"comm.h"
2 #include<string.h>
3 int main()
4 {
5
6 int fd = open(MY_FIFO,O_WRONLY);
7 if(fd < 0)
8 {
9 perror("open");
10 return 1;
11 }
12
13 while(1)
14 {
15 char buffer[64] = {0};
16 fflush(stdout);
17 ssize_t s = read(0,buffer,sizeof(buffer) - 1);
18 if(s > 0 )
19 {
20 buffer[s - 1] = 0;
21 printf("%s\n",buffer);
22 write(fd,buffer,strlen(buffer));
23 }
24 }
25 return 0;
26 }
server代码:
#include"comm.h"
2 #include<string.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 umask(0);
9
10 if(mkfifo(MY_FIFO,0666) < 0)
11 {
12 perror("mkfifo");
13 return 1;
14 }
15
16 int fd = open(MY_FIFO,O_RDONLY);
17 if(fd < 0)
18 {
19 perror("open");
20 return 2;
21 }
22
23 while(1)
24 {
25 char buffer[64] = {0};
26 ssize_t s = read(fd,buffer,sizeof(buffer) - 1);
27
28 if(s > 0)
29 {
30 buffer[s] = 0;
31 if(strcmp(buffer,"show") == 0)
32 {
33 if(fork() == 0)
34 {
35 execl("/usr/bin/ls","ls","-l",NULL);
36 exit(1);
37 }
38 waitpid(-1,NULL,0);
39 }
40 else if(strcmp(buffer,"run") == 0)
41 {
42 if(fork() == 0)
43 {
44 execl("/usr/bin/sl","sl",NULL);
45 }
46 waitpid(-1,NULL,0);
47 }
48 else{
49 printf("client#%s\n",buffer);
50 }
51 }
52 else if(s == 0)
53 {
54 printf("client quit...\n");
55 }
56 else
57 {
58 perror("read\n");
59 break;
60 }
61 }
62 return 0;
63 }
区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
三、system V共享内存
工作原理:
接口函数
shmget函数:
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat函数:
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
说明:
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
shmdt函数:
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数:
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
shimid_ds就是一个数据结构(用户层面的)内容是内核数据结构的一个子集。 存放了共享内存相关的属性!!!
实例代码
client.c
#include"comm.h"
2 int main()
3 {
4 key_t key = ftok(PATH_NAME,PROJ_ID);
5 if(key < 0)
6 {
7 perror("ftok");
8 return 1;
9 }
10 int shmid = shmget(key,SIZE,IPC_CREAT);
11 if(shmid < 0)
12 {
13 perror("shmget");
14 return 2;
15 }
16 char* mem = (char*)shmat(shmid,NULL,0);
17
18 char ch = 'A';
19 while(ch <= 'Z')
20 {
21 mem[ch - 'A'] = ch;
22 ch++;
23 mem[ch - 'A'] = 0;
24 sleep(2);
25 }
26
27 shmdt(mem);
28 return 0;
29}
server.c
include"comm.h"
2 int main()
3 {
4 key_t key = ftok(PATH_NAME,PROJ_ID);
5 if(key < 0)
6 {
7 perror("ftok");
8 return 1;
9 }
10 int shmid = shmget(key,SIZE,IPC_CREAT | IPC_EXCL|0666);
11 if(shmid < 0)
12 {
13 perror("shmget");
14 return 2;
15 }
16 printf("key: %u,shmid:%d\n",key,shmid);
17
18
19 char* mem = (char*)shmat(shmid,NULL,0);
20 printf("attaches shm success\n");
21
22
23 while(1)
24 {
25 sleep(1);
26 printf("%s\n",mem);
27 }
结果演示:
四、消息队列
- 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
- 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
- 特性方面
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核
五、消号量
了解消息量之前我们先了解几个概念: 临界资源:凡是被多个执行流同时访问的资源就是临时资源,例如:最常见的屏幕打印,共享内存,管道等; 临界区:用来访问临界资源的代码区域就是临界区 原子性:一件事情要么做完要么不做,没有中间态。 互斥:在任意时刻,一个执行流在访问临界资源的时候,不再允许其他执行流的访问。 而信号量的本质就是一个计数器,用来统计临界资源当中的资源数目。 目的就是防止对于临界资源的过量使用。 具体实现步骤我们在下一章在详细讲解!!!!
结语
希望本篇文章能给各位带来帮助,如有不足还请指正!!!
码字不易,各位大大给个收藏点赞吧!!!
宝子们,点赞,支持。 三连走一波!!!
|