写在前面:
我的Linux的学习之路非常坎坷。第一次学习Linux是在大一下的开学没多久,结果因为不会安装VMware就无疾而终了,可以说是没开始就失败了。第二次学习Linux是在大一下快放暑假(那个时候刚刚过完考试周),我没什么事做就又重拾Linux,不服输的我选择再战Linux,这一次学习还算顺利,虽然中间有些小插曲但是不影响整体学习进度, 我看着B站上的视频一点点学习Linux,基本上把Linux的基础指令学完了。学完之后我又遇到问题了,视频基本上到这就结束了,而我却不知道下一步该学什么,于是就没怎么碰Linux,结果没过多长时间我就把学的Linux指令忘的一干二净。现在是我第三次学习Linux,我决定重新开始学Linux,同时为了让自己学习的效果更好,我选择以写blog的形式逼迫自己每天把学习到的Linux知识整理下来。这也就是我写这个系列blog的原因。
IPC(Interprocess Communication)进程间通信
进程间通信的常用方式,特征:
pipe()
函数,创建并打开管道。
int pipe(int fd[2]);
参数:
fd[0]
:读端fd[1]
: 写端返回值:
errno
被设置管道通信原理(图):
源代码:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2];
char p[30]="This a test about pipe\n";
char buf[30];
pid_t pid;
pipe(fd);
pid=fork();
if(pid==0)
{
close(fd[1]);
read(fd[0],buf,sizeof buf);
printf("%s",buf);
}
else
{
close(fd[0]);
printf("I am parent,i will write something to mychild\n");
write(fd[1],p,strlen(p));
}
return 0;
}
效果:
read
读取数据,返回实际读到的字节数。read
返回0(类似读到文件的末尾)read
阻塞等待。我们使用管道通信实现父子进程ls | wc -l
功能
思路分析:
execlp
,但是ls
命令输出到屏幕 ,我们又想到之前学的函数dup2
重定向,我们可以把STDOUT_FILENO
重定向到管道的写端。execlp
,但是wc
接受的命令是来自屏幕,我们又想到之前的学的函数duo2
重定向,把STDIN_FILENO
重定向到管道的读端。源代码:
#include<stdio.h>
#include<unistd.h>
int main()
{
int fd[2];
pipe(fd);
pid_t pid;
pid= fork();
if(pid==0)
{
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
execlp("ls","ls",NULL);
perror("child error");
}
else
{
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
execlp("wc","wc","-l",NULL);
perror("parent error");
}
return 0;
}
效果:
我们用一个父进程创建两个子进程,用这两个子进程来实现上面的功能ls | wc -l
源代码:
#include<stdio.h>
#include<unistd.h>
int main()
{
int fd[2],i;
pipe(fd);
for(i=0;i<2;i++)
{
if(fork())
break;
}
if(2==i)
{
close(fd[1]);
close(fd[0]);
wait(NULL);
wait(NULL);
printf("I am parent,i wait two children successfully\n");
}
else if(0==i)
{
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
execlp("ls","ls",NULL);
perror("1th child error");
}
else if(1==i)
{
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
execlp("wc","wc","-l",NULL);
perror("2th child error");
}
return 0;
}
效果:
这里唯一要注意的是我们用兄弟通信时,要把父进程的读端和写端都关闭
一个pipe可以有一个写端多个读端
一个pipe可以有多个写端一个读端
管道的默认大小是4096(4k)
优点:
fifo管道:可以用于无血缘关系的进程间通信。fifo操作起来像文件
这个函数和open
差不多,只不过创建的文件类型不同罢了。
返回值:
errno
被设置fifo_w
,一个负责读(我们命令为fifo,r
)。同时使用mkfifio
创建一个命名管道(我们命名为fifo_test
)。fifo_w
,我们打开管道的写端fd=open("fifo_test",O_WRONLY)
,接下来操作和文件一样,使用write
向fifo_test
里写数据。fifo_r
,我们打开管道的读端fd=open("fifo_test",O_RDONLY)
,接下来操作和文件一样,使用read
向fifo_test
里读数据。fifo_w
部分:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
char p[100]="This is a test about fifo";
char buf[100];
int cnt=0;
int fd=open("fifo_test",O_WRONLY);
if(fd==-1)
perror("open file error");
while(1)
{
sprintf(buf,"%s---%d",p,cnt++);
write(fd,buf,strlen(buf));
sleep(1);
}
return 0;
}
fifo_r
部分:
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fd=open("fifo_test",O_RDONLY);
if(fd==-1)
perror("open file error");
while(1)
{
int res;
char buf[100];
res=read(fd,buf,sizeof buf);
if(res<0)
perror("read error");
printf("%s\n",buf);
sleep(1);
}
return 0;
}
打开的文件是内核中的一块缓冲区。多个无血缘关系的进程,可以同时访问该文件。
void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset
addr
:指定映射区的首地址。通常传NULL
,表示让系统自动分配length
:共享内存映射区的大小。(<=
文件的实际大小)prot
:共享内存映射区的读写属性。
PROT_READ
:读PROT_WRITE
:写PROT_READ|PROT_WRITE
:读/写flags
:标注共享内存的共享属性。
MAP_SHARED
修改会反映到磁盘上。MAP_PRIVATE
修改不反映到磁盘上。fd
:用于创建共享内存映射区的那个文件的 文件描述符。offset
:默认0,表示映射文件全部。偏移位置。需是 4k 的整数倍
。MAP_FAILED
,其实就是void*
类型的0
.int munmap(void *addr, size_t length);
作用:释放映射区
参数传的一般和mmap
一样即可(这样肯定不会错)。
我们手动传入一个参数(表示映射的文件名),然后建立映射区,通过映射区的首地址来读写。
源代码:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/mman.h>
#include<fcntl.h>
int main(int argc,char* argv[])
{
if(argc==1)
{
printf("argument error\n");
return -1;
}
int fd=open(argv[1],O_RDWR| O_CREAT | O_TRUNC,0644);
ftruncate(fd,100);
int len=lseek(fd,0,SEEK_END);
char* ret=mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(ret==MAP_FAILED)
perror("mmap error");
char p[100]="This is a test about mmap\n";
memcpy(ret,p,strlen(p));
printf("%s",ret);
close(fd);
munmap(ret,len);
return 0;
}
效果:
fd = open("文件名", O_RDWR);
mmap(NULL, 有效文件大小, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
mmap_w
部分:
#include<stdio.h>
#include<unistd.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fd=open("mmap_test",O_RDWR|O_TRUNC);
ftruncate(fd,100);
int len =lseek(fd,0,SEEK_END);
char* p=mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(p==MAP_FAILED)
perror("mmap error");
scanf("%s",p);
close(fd);
munmap(p,len);
return 0;
}
mmap_r
部分:
#include<stdio.h>
#include<unistd.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<string.h>
int main()
{
sleep(10);
int fd=open("mmap_test",O_RDWR);
int len =lseek(fd,0,SEEK_END);
char* p=mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(p==MAP_FAILED)
perror("mmap error");
printf("%s",p);
close(fd);
munmap(p,len);
return 0;
}
效果:
匿名映射:只能用于 血缘关系进程间通信。
p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
MAP_SHARED
时,要求:映射区的权限应该<=
文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE
则无所谓,因为mmap
中的权限是对内存的限制.mmap
使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。如,400字节大小的文件,在建立映射区时,offset
4096字节,则会报出总线错误.munmap
传入的地址一定是mmap返回的地址。坚决杜绝指针++操作,即adrr++
.想要操作,先拷贝一份。0
.mmap
创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。个人亲身经验:我们学习的一系列Linux命令,一定要自己亲手去敲。不要只是看别人敲代码,不要只是停留在眼睛看,脑袋以为自己懂了,等你实际上手去敲会发现许许多多的这样那样的问题。正可谓“键盘敲烂,月薪过万”
如果你觉得我写的题解还不错的,请各位王子公主移步到我的其他题解看看
- 数据结构与算法部分(还在更新中):
- C++ STL总结 - 基于算法竞赛(强力推荐)
- 动态规划——01背包问题
- 动态规划——完全背包问题
- 动态规划——多重背包问题
- 动态规划——分组背包问题
- 动态规划——最长上升子序列(LIS)
- 二叉树的中序遍历(三种方法)
- 最长回文子串
- 最短路算法——Dijkstra(C++实现)
- 最短路算法———Bellman_Ford算法(C++实现)
- 最短路算法———SPFA算法(C++实现)
- 最小生成树算法———prim算法(C++实现)
- 最小生成树算法———Kruskal算法(C++实现)
- 染色法判断二分图(C++实现)
- Linux部分(还在更新中):
“种一颗树最好的是十年前,其次就是现在”
所以,
“让我们一起努力吧,去奔赴更高更远的山海”
如果有错误?,欢迎指正哟😋
🎉如果觉得收获满满,可以动动小手,点点赞👍,支持一下哟🎉