Linux多进程通信基础

Posted by 卢小胖 on 2023-08-23
Estimated Reading Time 7 Minutes
Words 1.6k In Total
Viewed Times

1、Linux系统中进程的概念,以及创建进程

在Linux中主要提供了fork、vfork、clone三个进程创建方法。

//头文件
#include <sys/types.h>
#include <unistd.h>

int main(){
printf("process id is :%d \n",getpid());

pid_t pid = fork();

sleep(1);

if (pid == 0){
printf("我是子进程,id:%d\n",getpid());
}
else if (pid > 0){
printf("我是父进程,id:%d\n",getpid());
}
}

执行结果:

process id is :13078 
我是子进程,id:13079
我是父进程,id:13078

为什么执行了两次了,这就是fork机制导致的

fork函数创建了一个新的进程,新进程(子进程)与原有的进程(父进程)一模一样。子进程和父进程使用相同的代码段;子进程拷贝了父进程的堆栈段和数据段。子进程一旦开始运行,它复制了父进程的一切数据,然后各自运行,相互之间没有影响。

线程与进程区别,以及通信方式

  • 线程之间是共享进程资源的,实现线程通信的方式很多种,共享对象,消息队列,注意的是线程通信需要注意线程安全问题,在这里不详细介绍
  • 进程通信基本是依靠系统提供的方式,在不同系统上有独特的进程通信实现方式,在Linux上大致分为这几种:
    • 管道(无名管道Pipe,有名管道FIFO)
    • 信号量
    • Socket
    • 共享内存
    • mmap

参考:

2、实现进程通信

2.1 无名管道Pipe

管道概念
1、管道是操作提供的一块操作系统内存;
2、管道是Unix中最古老的进程间通信方式;
3、我们把一个进程连接到另一进程的数据流称为管道。

管道函数:int pipe(int pipefd[2])

实现代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

void test_pipe()
{
//fd是管道的读写文件描述符,0-代表读,1-代表写
int fd[2];
//创建管道
pipe(fd);

//创建一个子进程
pid_t pid = fork();

if (pid == 0)
{
//当前是子进程
write(fd[1],"hello",5);
}
else if (pid > 0)
{
//当前是父进程
char buf[12];
int ret = read(fd[0],buf,sizeof(buf));
if (ret > 0)
{
//从管道读取成功
//write(STDOUT_FILENO, buf, ret);
}
printf("pipe read:%s\n",buf);
}
}

输出:
pipe read:hello��$�

参考:

2.2 命名管道(FIFO)

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。

FIFO是unix基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。备进程可以打开这个文件进行 read/write,实际上是在读写内核通道,这样就实现了进程间通信。

创建管道
使用命令:mkfifo myfifo
使用函数:int mkfifo(const char *pathname, mode_t mode); 成功:0;失败:-1

原理:内核会针对fifo文件开辟一个缓冲区,操作FIFO文件,可以操作缓冲区,实现进程通信。一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件IO函数都可以用于FIFO。如:close、read、write、unlink等

创建打开FIFO并将文件中的字符串写入FIFO:

#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
if(argc != 2){
fprintf(stderr,"usage:%s srcfile\n",argv[0]);
exit(EXIT_FAILURE);
}
int infd;
infd = open(argv[1],O_RDONLY);
if(infd == -1){
perror("open error");
exit(EXIT_FAILURE);
}
if(mkfifo("tmpfifo",0644) == -1){
perror("mkfifo error");
exit(EXIT_FAILURE);
}
int fd ;
fd = open("tmpfifo",O_WRONLY);
if(fd == -1){
perror("open error");
exit(EXIT_FAILURE);
}
char buf[1024*4];
int n = 0;
while((n = read(infd,buf,1024*4))){
write(fd,buf,n);
}
close(infd);
close(fd);
printf("write success\n");
return 0;
}

打开FIFO读取FIFO中的字符串,放入新文件中:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
if(argc != 3){
fprintf(stderr,"usage:%s desfile\n",argv[0]);
exit(EXIT_FAILURE);
}
int outfd;
outfd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC);
if(outfd == -1){
perror("open error");
exit(EXIT_FAILURE);
}
int fd ;
fd = open(argv[2],O_RDONLY);
if(fd == -1){
perror("open error");
exit(EXIT_FAILURE);
}
char buf[1024*4];
int n = 0;
while((n = read(fd,buf,1024*4))){
write(outfd,buf,n);
}
close(fd);
close(outfd);
unlink(argv[2]);
printf("read success\n");
return 0;
}

2.3 信号量(SEMAPHORE)

Linux中信号和信号量是存在区别的:

  • Linux内核的信号量用来操作系统进程间同步访问共享资源。
  • signal,又简称为信号(软中断信号 )用来通知进程发生了异步事件。

具体信号量实现,参考:

2.4 共享内存(SHARE MEMORY)

2.5 内存映射(MAPPED MEMORY)

很多黑科技开源库都是基于mmap实现的,理解mmap是提升代码能力的必不可少的。

mmap函数原型:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • addr : 映射地址,可以传NULL
  • length:映射区的长度
  • prot:
    • PROT_EXEC pages may be executed.
    • PROT_READ pages may be read.
    • PROT_WRITE pages may be written.
    • PROT_NONE pages may not be accessed.
  • flags:
    • MAP_SHARED 映射区是共享的,对内存的修改会影响到源文件
    • MAP_PRIVATE 映射区是私有的
  • fd:文件描述符,open打开一个文件
  • offset:偏移量
  • 返回值
    • 成功:返回可用内存首地址
    • 失败:返回MAP_FAILED

释放mmap内存函数原型:

int munmap(void *addr, size_t length);
  • addr:传mmap的返回值
  • length:mmap创建长度
  • 返回值:
    • 成功:0
    • 失败:-1
void test_mmap()
{
int fd = open("/tmp/mem.txt",O_RDWR|O_CREAT);
printf("fd:%d\n",fd);
//创建映射区
char* mem = (char*)mmap(nullptr,8,PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
if (mem == MAP_FAILED)
{
printf("error!");
return;
}
//拷贝数据
strcpy(mem,"hello");
//释放mmap
munmap(mem,8);
close(fd);
}

2.6 消息队列(MESSAGE QUEUE)