Linux进程通信
每一个进程的数据都是存储在物理内存中,进程通过各自的进程虚拟地址空间进行访问,访问的时候通过各自的页表映射关系访问到物理内存。至于物理内存如何存储,页表如何映射,进程是不清楚的,这也就造成了进程的独立性,所以本质上进程通信是进程和进程之间交换数据的手段.
常见的进程通信方式有:管道、共享内存、消息队列、信号量、信号、网络
目的是实现:数据传输、资源共享、通知时间、进程控制
管道在内核当中是一块缓冲区,供应进程进行读写,交换数据
int pipe(int pipefd[2]);//创建匿名管道本质上就是在内核上创建一个缓冲区
代码验证:
#include
#include
int main(){
int fd[2];
int ret=pipe(fd);
if(ret<0){
perror("pipe");
return 0;
}
//正常通信
printf("fd[0]:%d\n",fd[0]);
printf("fd[1]:%d\n",fd[1]);
while(1){
sleep(1);
}
return 0;
}
可见管道的读端与写端对应的是一个文件描述符(对应在struct task_struct中的file数组)
由图可见不同的进程要用同一个匿名管道进行通信,要求两个进程拥有管道读写两端的文件描述符,因此采用父子进程进行通信(子进程拷贝父进程的PCB中的文件描述符);
#include
#include
#include
int main(){
int fd[2];
int ret=pipe(fd);
if(ret<0){
perror("pipe");
return 0;
}
//正常通信
printf("fd[0]:%d\n",fd[0]);
printf("fd[1]:%d\n",fd[1]);
pid_t pid=fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
//child
printf("child,fd[0]:%d\n",fd[0]);
printf("child,fd[1]:%d\n",fd[1]);
char buf1[1024]={0};
read(fd[0],buf1,sizeof(buf1)-1);
printf("buf1:%s\n",buf1);
} else{
//father
printf("father,fd[0]:%d\n",fd[0]);
printf("father,fd[1]:%d\n",fd[1]);
char buf[]="i am pipe_test";
write(fd[1],buf,strlen(buf));
}
while(1){
sleep(1);
}
return 0;
}
没有标识符的情况下,只有具有亲缘性关系的进程可以实现通信,并且为了保证子进程的匿名管道中有父进程匿名管道读写两端的文件描述符,必须父进程先创建匿名管道,父进程再创建子进程
管道的生命周期跟随进程,进程退出之后,管道也就随之销毁了,其大小为64K.
从读端进行读的时候,是将数据从管道中读走了(并不是拷贝)
读端可以自定义选择读多少内容
当pipe_size小于4096字节时,读/写操作在同一时刻只有一个进程在操作,保证进程在操作的时候不会被其他进程打扰阻塞属性(
读写两端的文件描述符的初始属性为阻塞属性
当write一直调用写,而读端不去读,则写满之后就会阻塞
当read一直进行读,当管道内部被读完之后,read就会阻塞
int fcntl(int fd,int cmd,…/arg/)
参数:fd:要操作的文件描述符
cmd:告知fcntl函数做什么操作
F CETFL:获取文件描述符的属性信息
F SETFL:设置文件描述符的属性信息
返回值:
0,设置成功,-1,设置失败
设置非阻塞属性的时候,宏的名字为0_NONBLOCK
第一步:先获取文件描述符原来的属性:
int flag=fcntl(fd[0],F_GETFL);
第二部:设置新属性的时候不要忘记带上原来的属性:新的属性=老的属性|增加的属性
fcntl(fd[0],F_SETFL,flag | 0_NONBLOCK);
代码:
#include
#include
#include
#include
int main(){
int fd[2];
int ret=pipe(fd);
if(ret<0){
perror("pipe");
return 0;
}
//正常通信
int read_ret=fcntl(fd[0],F_GETFL);
printf("read_ret:%d\n",read_ret);
int write_ret=fcntl(fd[1],F_GETFL);
printf("write_ret:%d\n",write_ret);
fcntl(fd[1],F_SETFL,write_ret | O_NONBLOCK);
write_ret=fcntl(fd[1],F_GETFL);
printf("write_ret:%d\n",write_ret);
return 0;
}
读设置成为非阻塞属性
写不关闭,一直读,读端调用read函数之后,返回值为-1,errno设置为EAGAIN
写关闭,一直读,读端read函数返0,表示什么都没有读到
写设置成为非阻塞
读不关闭,一直写,当把管道写满之后,则在调用write.就会返回-1
读关团·一直写,写端调用write进行写的时候,就会发生崩溃。(本质原因是因为写端的进程,收到SIGPIPE信号,导致写端进程崩溃)
代码验证让父子进程做不一样的事,这里让子进程写close(fd[0]),父进程读close(fd[1]);
#include
#include
#include
int main(){
int fd[2];
int ret = pipe(fd);
if(ret < 0){
perror("pipe");
return 0;
}
pid_t pid = fork();
if(pid < 0){
perror("fork");
return 0;
}else if(pid == 0){
//child
/*
* 1.关闭子进程的读端
* 2.设置子进程的写端为非阻塞
* 3.子进程进行写
* */
//下面调用sleep(3), 就是想让父进程先将fd[0]关闭掉, 子进程在向下运行
sleep(3);
close(fd[0]);
int flag = fcntl(fd[1], F_GETFL);
fcntl(fd[1], F_SETFL, flag | O_NONBLOCK);
ssize_t write_size;
while(1){
write_size = write(fd[1], "s", 1);
//为了验证上面调用write是否正常, 如果正常调用则下面的输出一定会打印
printf("i am child, i run hare\n");
if(write_size < 0){
break;
}
}
printf("write_size: %ld\n", write_size);
perror("write");
}else{
//father
/*
* 1.关闭父进程写端
* 2.关闭父进程读端/不关闭父进程的读端
* */
close(fd[1]);
//关闭读端
close(fd[0]);
//不关闭读端
while(1){
sleep(1);
}
}
return 0;
}
1.mkfifo命令,fifo命名管道文件,相当于内核缓冲区的标识符,不同的进程用过这个文件可以找到内核的缓冲区
2.函数创建:
int mkfifio(const char*pathname,mode_t mode);
参数:
pathname:要创建的命名管道的路径以及文件名
mode_t:命名管道文件的权限,八进制数组(0664)
返回值:0,成功,-1,失败
#include
#include
int main(){
mkfifo("./mkfifo_func_fifo",0664);
return 0;
}
支持不同间进程进行通信
往管道中写:
#include
#include
#include
#include
int main(){
//1.打开命名管道文件
int fd = open("../fifo", O_WRONLY);
if(fd < 0){
perror("open");
return 0;
}
//2.调用write往管道当中写内容
while(1){
const char* content = "i am write process..., hhhh";
write(fd, content, strlen(content));
sleep(1);
}
close(fd);
return 0;
}
往管道中读;
#include
#include
#include
int main(){
//1.open
int fd = open("../fifo", O_RDONLY);
if(fd < 0){
perror("open");
return 0;
}
//2.read
while(1){
char buf[1024] = { 0 };
read(fd, buf, sizeof(buf) - 1);
printf("buf is %s\n", buf);
}
close(fd);
return 0;
}