进程间通信(一)

坚强是说给别人听的谎言 提交于 2020-01-10 03:40:06

进程间通信

一、进程间通信简介

1.进程间是相互独立的,每个进程都有自己的进程虚拟地址空间,二进程通讯需要介子,使得两个进程都能访问的公共资源;

2.进程间通讯的目的:

  • 数据传输:一个进程需要将他的数据发送给另一个进程
  • 资源共享:多个进程间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件(如子进程退出时要通知父进程回收其资源等)
  • 进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程所有陷入和异常,并能及时的知道他的改变状态

二、进程间通讯的发展及分类:

1.管道

  • 匿名管道pipe
  • 命名管道

2.System V进程间通讯

  • System V消息共享队列
  • System V共享内存
  • System V信号量

3.POSIX进程间通讯

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

三、管道

1.什么是管道:管道是内核中的一块内存,构成一个队列,使用一对文件描述符来进行访问管理这个内存,读文件描述符相当于从这个内存中取数据,写文件描述符相当于往这块内存中写数据

2.匿名管道

匿名管道的创建

int pipe(int pipefd[2]);

功能:是创建一个无名管道;

参数:是两个输出型参数,fd表示文件描述符数组,其中fd[0]端表示读端,fd[1]端表示写端。

返回值:创建成功返回0;失败返回错误码。

在这里插入图片描述
测试代码:

例子:从键盘读取数据,写入管道,读取管道,写到屏幕
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main( void )
{
int fds[2];
char buf[100];
int len;
if ( pipe(fds) == -1 )
perror("make pipe"),exit(1);
// read from stdin
while ( fgets(buf, 100, stdin) ) {
len = strlen(buf);
// write into pipe
if ( write(fds[1], buf, len) != len ) {
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
// read from pipe
if ( (len=read(fds[0], buf, 100)) == -1 ) {
perror("read from pipe");
break;
}
// write to stdout
if ( write(1, buf, len) != len ) {
perror("write to stdout");
break;
}
}

3.用fork来共享管道
在这里插入图片描述
站在文件描述符的角度:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
站在内核角度:
在这里插入图片描述
共享管道实用的注意事项:

  1. 管道使用完后一定要及时关闭文件描述符,防止资源泄漏

  2. 创建子进程时,子进程会进程父进程的文件描述符表,此时子进程也就能访问同一个管道

  3. 如果父子进程同时读取管道的内容,只有其中一个能够成功读取到管道中的数据,一旦有一个进程成功读取到管道中的内容,那么该数据就不在管道中了,相当于把数据从管道中出队了。

  4. 管道内置了“同步互斥机制”,不会出现两个进程一人读取管道一般数据的情况。其中有如下几种情况:

    a.多个进程同时去读写管道,管道数据不会错乱(内置同步互斥)

    b.如果管道为空,再去尝试读取管道数据,此时程序就会在read处阻塞。

    c.如果管道已满,再去尝试往管道中写数据,此时程序就会在write处阻塞;

四、minishell

#include <stdio.h>
#include <sys/wait.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

char g_command[1024];
static char*  pipe_command[] = {0};

//该函数的功能是获取命令行输入的数据
int GetCommand()
{
  //因为开始获得的内存中的内容是不定的,所以使用前要先初始化
  memset(g_command,'\0',sizeof(g_command));
  //这里需要为'\0'留一个位置,不能把g_command全部用来读取内容,否者就没有结束标志,容易发生内存访问越界
  if(fgets(g_command,sizeof(g_command)-1,stdin)==NULL)
  {
    printf("fgets is error!\n");
    return -1;
  }
  //printf("g_command:%s\n",g_command);
  return 0;
}

//解析字符串
char** DealCommand(char* command)
{
  if(command==NULL&&*command=='\0')
  {
    printf("dealcommand is error\n");
    return NULL;
  }

  //用来保存命令
  static char* argv[1024]={0};
  int argc=0;
  while(*command)
  {
    //isspace函数用来去掉多余的空格
    //isspace的返回值:如果当前字符是空格返回1,否则返回0
    //注意'\0'在issapce函数中不算空格的,所以要进行判断
    while(!isspace(*command)&&*command!='\0')
    {
      argv[argc]=command;
      argc++;

      //去找下一个空格
      while(!isspace(*command)&&*command!='\0')
      {
        command++;
      }
      *command='\0';
    }
    command++;
  }
  argv[argc]=NULL;

  //for(int i=0;i<argc;i++)
  //{
  //  printf("%d:%s   ",i,argv[i]);
  //}
  //printf("\n");

  return argv; 
}

//进行重定向
int redirect(char * g_command)
{
  char* ptr = g_command;
  char* file = NULL;
  int fd ;
  //用来标记是清空重定向还是追加重定向
  int redirect_type = -1;

  while(*ptr !='\0')
  {
    //如果当前字符是 > ,把他置为'\0',并判断下一个位置是否为'\0'
    if(*ptr == '>')
    {
      *ptr++ = '\0';
      redirect_type++;
      if(*ptr == '>')
      {
        *ptr++ = '\0';
        redirect_type++;
      }
      //去掉多余的空格
      while(isspace(*ptr))
      {
        ptr++;
      }
      //file就是空格后面的第一个字符串
      file = ptr;

      //继续找空格,在这两个空格之间就是文件的名称
      while(!isspace(*ptr)&&*ptr != '\0')
      {
        ptr++;
      }

      *ptr='\0';
      //如果redirect_type==0说明是清空重定向,如果==1说明是追加重新定向
      if(redirect_type == 0)
      {
        fd = open(file,O_CREAT|O_TRUNC|O_WRONLY,0664);
      }
      else
      {
        fd = open(file,O_CREAT|O_APPEND|O_WRONLY,0664);
      }
      dup2(fd,1);
    }
    ptr++;
  }
  return 0;
}

//解析字符串,获取管道的数量
int do_command(char * g_command)
{
  int pipe_num = 0;
  char * ptr =g_command;

  pipe_command[pipe_num]=ptr;
  while(*ptr != '\0')
  {
    if(*ptr == '|')
    {
      pipe_num++;
      *ptr++ = '\0';
      pipe_command[pipe_num] = ptr;
      continue;
    }
    ptr++;
  }
  pipe_command[pipe_num + 1] = NULL;
  return pipe_num;
}

//进行程序替换
int exec()
{
  redirect(g_command);
  char** argv=DealCommand(g_command);
  pid_t pid =fork();
  if(pid<0)
  {
    printf("foek is error!\n");
    return -1;
  }
  else if(pid==0)
  {
    //child
    //如果argc中为NULL,就直接返回
    if(argv[0]==NULL)
    {
      exit(-1);
    }
    //进行替换,execvp第一个参数是可执行程序名,第二个参数是该可执行程序的参数组成的数组
    execvp(argv[0],argv);
    //execl("/usr/bin/ls","ls","-a",NULL);
  }
  else
  {
    //father
    waitpid(pid,NULL,0);
  }
  return 0;
}

//添加管道功能
int do_pipe(int pipe_num)
{
  int pid = 0;
  int i;
  int pipefd[10][2] = {{0}};
  char** argv = {NULL};

  for (i = 0;i <= pipe_num;i++)
  {
    pipe(pipefd[i]);
  }

  for(i = 0;i <= pipe_num; i++)
  {
    pid = fork();
    if(pid < 0)
    {
      perror("fork is error!\n");
      continue;
    }
    else if(pid == 0)
    {
      redirect(pipe_command[i]);
      argv = DealCommand(pipe_command[i]);
      if(i != 0)
      {
        close(pipefd[i][1]);
        dup2(pipefd[i][0],0);
      }
      if(i != pipe_num)
      {
        close(pipefd[i+1][0]);
        dup2(pipefd[i+1][1],1);
      }
      execvp(argv[0],argv);
    }
    else
    {
      close(pipefd[i][0]);
      close(pipefd[i][1]);
      waitpid(pid,NULL,0);
    }

  }
  return 0;
}

int main()
{
  //循环读数据
  while(1)
  {
    printf("[dev@localhost dev]$ ");
    int ret = GetCommand();
    if(ret == -1)
    {
      //如果读取失败,继续循环读取,不能直接break调
      continue;
    }

    //处理解析数据
    //char** argv = DealCommand(g_command);

    //进行替换
    //exec(argv);
    //exec();
    int num = do_command(g_command);
    do_pipe(num);
  }

  return 0;
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!