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,又叫超级进程(代码片段

请教一个Linux下C语言的进程间的信号问题

详解linux进程间通信-信号

Linux操作系统 进程之间的通信

linux 进程间通信 dbus-glib实例详解四(下) C库 dbus-glib 使用(附代码)

linux进程间通信之System V共享内存详解及代码示例