Linux--进程通信
Posted qnbk
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux--进程通信相关的知识,希望对你有一定的参考价值。
进程通信
进程通信介绍
进程通信的目的
- 数据传输:一个进程需要将它的数据发送给另一给进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个或另一组进程发送消息,通知它(们)发生了某种事件(eg:进程终止时需要通知父进程)
- 进程控制:有些进程希望完全控制另一个进程的执行(eg:Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
如何做到进程通信
- 进程具有独立性(数据层面)
- 进程间通信,要借助第三方(OS)资源
- 进程A-》数据“拷贝”给OS-》OS数据“拷贝”给进程B(OS一定要提供一段内存区域,能够被双方进程看到)
进程通信本质是让不同的进程,看到同一份资源(内存文件内核缓冲等)
进程间通信发展
- 管道
- System V进程通信
- POSIX进程间通信
进程间通信发展分类
管道
- 匿名管道pipe
- 命名管道
System V IPC
- System V 消息队列
- System V共享内存
- Systwm V信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
管道
- 管道是Unix中最古老的进程间通信的形式
- 一个进程连接到另一个进程的数据流称为一个管道
管道虽然用的是文件的方案,但是OS一定不会把数据刷新到磁盘(有IO参与,降低效率)
匿名管道
匿名管道本质是没有文件名。
管道只能单向通信
#include <unistd.h>
//功能:创建无名管道
int pipe(int fd[2]);
//fd:文件描述符组,fd[0]代表读端,fd[1]代表写端
//返回值:成功返回0,失败返回错误代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
//child->write father->read
close(2);
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\\n");
return 1;
}
printf("fd[0]:%d\\n",fd[0]);
printf("fd[1]:%d\\n",fd[1]);
//相当于一个文件被打开两次
return 0;
}
用fork来共享管道原理
建立管道
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
//child->write father->read
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\\n");
return 1;
}
pid_t id = fork();
if(id == 0)
{
//child
close(fd[0]);
}
close(fd[1]);
}
close(fd[1]);
//father
waitpid(id,NULL,0);
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
//child->write father->read
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\\n");
return 1;
}
pid_t id = fork();
if(id == 0){
close(fd[0]);
const char* msg = "hello father ,I am child!";
int count = 5;
while(count){
write(fd[1],msg,strlen(msg));
count--;
sleep(1);
}
}
close(fd[1]);
exit(0);
}
close(fd[1]);
char buff[64];
while(1)
{
ssize_t s = read(fd[0],buff,sizeof(buff));
if(s > 0)
{
buff[s] = '\\0';
printf("child send to father#%s\\n",buff);
}
else if(s == 0)
{
printf("read file end!\\n");
break;
}
else
{
printf("read error!\\n");
break;
}
}
//father
waitpid(id,NULL,0);
return 0;
}
父子通信不可以创建全局缓冲区来完成通信,因为进程运行具有独立性!
内核角度–管道本质
管道特点
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道
- 管道提供流式服务
- 一般而言,进程退出,管道释放,所以管道的生命期随进程
- 一般而言,内核会对管道操作进程同步与互斥
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
//child->write father->read
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\\n");
return 1;
}
pid_t id = fork();
if(id == 0){
close(fd[0]);
const char* msg = "hello father ,I am child!";
int count = 5;
while(count){
if(count == 2){
sleep(1000);
}
else{
write(fd[1],msg,strlen(msg));
count--;
sleep(1);
}
}
close(fd[1]);
exit(0);
}
close(fd[1]);
char buff[64];
while(1)
{
ssize_t s = read(fd[0],buff,sizeof(buff));
if(s > 0)
{
buff[s] = '\\0';
printf("child send to father#%s\\n",buff);
}
else if(s == 0)
{
printf("read file end!\\n");
break;
}
else
{
printf("read error!\\n");
break;
}
close(fd[0]);
break;
}
sleep(10);
//father
int status = 0;
waitpid(id,&status,0);
printf("child quit:sig:%d\\n",status & 0x7F);
return 0;
}
查看管道大小
查看系统资源:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
//child->write father->read
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\\n");
return 1;
}
pid_t id = fork();
if(id == 0)
{
close(fd[0]);
const char* msg = "hello father ,I am child!";
char c='a';
int count = 0;
while(1)
{
write(fd[1],&c,1);
count++;
printf("%d\\n",count);
}
close(fd[1]);
exit(0);
}
close(fd[1]);
char buff[64];
while(1)
{
sleep(1000);
ssize_t s = read(fd[0],buff,sizeof(buff));
if(s > 0)
{
buff[s] = '\\0';
printf("child send to father#%s\\n",buff);
}
else if(s == 0)
{
printf("read file end!\\n");
break;
}
else
{
printf("read error!\\n");
break;
}
}
//father
waitpid(id,NULL,0);
return 0;
}
管道读写规则
没有数据可读
- O_NORNBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止
- O_NORNBLOCK enable: read返回-1,errno值为EAGAIN
当管道满的时候
- O_NORNBLOCK disable:write调用阻塞,知道有进程走读数据
- O_NORNBLOCK enable: 调用返回-1,errno值为EAGAIN
1、如果所有管道写端对应的文件描述符被关闭,则read返回0
2、如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
3、当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性
4、当要写入的数据量大于PIPE_BUF时,Linux不再保证写入的原子性
命名管道
- 管道应用的一个限制就是只能在具有相同祖先(有亲缘关系)的进程间通信
- 在不相关的进程之间交换数据,可以用FIFO文件,它被称为命名管道
- 命名管道是一种特殊类型的文件
想让无关系的进程通信-》通过名字打开同一个文件-》看到同一份资源
创建一个命名管道
在命令行中
mkfifo filename
在程序中
int mkfifo(const char* filename,mode_t mode);
创建简单命名管道
//Makefile
.PHONY:all
all:client server
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm client server myfifo
1\\
//server.c
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#define FILE_NAME "myfifo"
int main()
{
if(mkfifo(FILE_NAME,0644) < 0)
{
perror("mkfifo");
return 1;
}
return 0;
}
//client.c
#include <stdio.h>
int main()
{
return 0;
}
2\\
//comm.h
#pragma once
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define FILE_NAME "myfifo"
//client.c
#include "comm.h"
int main()
{
int fd = open(FILE_NAME,O_WRONLY);
if(fd < 0)
{
printf("open error!\\n");
return 1;
}
char msg[128];
while(1)
{
msg[0] = 0;
printf("please enter# ");
fflush(stdout);
ssize_t s = read(0,msg,sizeof(msg));
if(s > 0)
{
msg[s - 1] = 0;
write(fd,msg,strlen(msg));
}
}
close (fd);
return 0;
}
//server.c
#include "comm.h"
int main()
{
if(mkfifo(FILE_NAME,0644) < 0)
{
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open error!\\n");
return 2;
}
char msg[128];
while(1)
{
msg[0] = 0;
}
return 0;
}
此时myfifo为0-》并未被刷新到磁盘中,还在内存里-》在内存之中通信(和匿名管道底层原理一样采用文件通信)
3\\
//server.c
#include "comm.h"
int main()
{
if(mkfifo(FILE_NAME,0644) < 0)
{
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open error!\\n");
return 2;
}
char msg[128];
while(1)
{
msg[0] = 0;
ssize_t s = readLINUX PID 1和SYSTEMD PID 0 是内核的一部分,主要用于内进换页,内核初始化的最后一步就是启动 init 进程。这个进程是系统的第一个进程,PID 为 1,又叫超级进程(代码片段