百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

Linux-多进程开发,看完就明白了(linux多进程聊天室)

sinye56 2024-11-06 12:03 4 浏览 0 评论

  /*
  #include <sys/types.h>
  #include <unistd.h>
  pid_t fork(void);
  作用:创建子进程
      返回值:
          fork()返回值会返回两次,一次是在父进程中,一次是在子进程中
          在父进程中返回创建的子进程的ID,
          在子进程中,返回0
          如何区分父进程和子进程,通过fork的返回值
          在父进程中返回-1,表示创建子进程失败,并且设置errno


      父子进程之间的关系:
      区别:
          1.fork()函数的返回值不同
              父进程中:>0,返回子进程的PID
              子进程中:=0
          2.pcb中的一些数据
              当前进程的id pid
              当前进程的父进程的id ppid
              信号集
          共同点:
              某些状态下,子进程刚被创建出来,还没有执行任何的写数据操作
                  -用户区的数据
                  -文件描述符表
              父子进程对变量是否是共享的
                  -刚开始是一样的,如果修改类数据,就不共享了
                  读时共享(子进程刚创建),写时拷贝
                  父子进程之间不能用变量进行通信,它们之间互不影响

  */
  #include <sys/types.h>
  #include <unistd.h>
  #include <stdio.h>
  int main()
  {

      int num =10;
      //创建子进程
      pid_t pid = fork();//pid_t本质上是int类型的
      //判断是父进程还是子进程
      if(pid>0){
          printf("pid : %d\n", pid);
          //如果大于0,返回的是创建的子进程的id,当前是父进程
          printf("i am parent process,pid : %d, ppid:%d\n",getpid(),getppid());
          printf("parent num: %d\n",num);
          num+=10;
          printf("parent num+=10: %d\n",num);
      }else if(pid ==  0)
      {
          //当前是子进程
          printf("i am child process, pid :%d,ppid : %d\n",getpid(),getppid());
          printf("child num: %d\n",num);
          num+=100;
          printf("child num+=100: %d\n",num);
      }
      for(int i =0;i<3;i++)//默认父子进程都会去执行
      {
          printf("i : %d\n", i);
          sleep(1);

      }
      return 0;
  }

exec函数族

可执行文件的用户区去替换进程中的用户区,一般情况下会创建一个子进程,在子进程创建exec函数族中的函数

查看execl

execlp

/*#include <unistd.h>

       extern char **environ;

       int execlp(const char *file, const char *arg, ...);
        -会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就
            参数:-file:需要指定的执行的可执行文件的文件名
                a.out    ps
                  -arg:是执行可执行文件所需要的参数列表
                    第一个参数一般没有什么作用,为了方便一般写的执行的参数的名称,从第二个人参数开始往后,就是程序执行所需要的参数列表
                    参数最后需要以NULL结束(哨兵)
             返回值:-仅仅在出错的时候返回,返回-1,并且设置errno
             如果调用成功,就没有返回值
                    
*/
#include <unistd.h>
#include <stdio.h>

int main()
{
    //创建一个子进程在子进程中执行exec函数族中的函数

    pid_t pid =fork();
    if(pid >0){
        //父进程
        printf("i am parent process,pid : %d\n",getpid());
        sleep(1);
    }else if(pid == 0)
    {
        //子进程
        execlp("ps","ps","aux",NULL);
        printf("i am child process , pid:%d\n",getpid());
    }
    for(int i =0;i<3;i++){
        printf("i=%d , pid = %d\n",i,getpid());
    }

    return 0;
}

可执行程序的程序名,第二个参数

进程控制

fork函数 读时共享,写时复制 主要目的是降低内存的使用

子进程退出,父进程能得到子进程退出的状态 父进程有义务回收子进程的资源

#include <stdlib.h>
#include<unistd.h>
#include <stdio.h>
int main()
{
    printf("hello\n");
    printf("world");

    exit(0);

    return 0;  //和exit是一样的,都表示进程退出

}

如下,1领养了孤儿进程 ,由1进程回收资源

将exit()改成_exit()后 world在缓冲区里

孤儿进程

父进程运行结束,子进程还在运行,这样的子进程称为孤儿进程

? 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表出面处理它的一切善后工作。

孤儿进程并不会有什么危害

如下,1领养了孤儿进程 ,由1进程回收资源

父进程结束后,显示了一个终端(回到前台)

为什么都显示在终端?父子进程的内核去的一些部分是共享的 文件描述符表前三个(0,1,2)标准输入,输出,错误

关于C/C++Linux后台服务器开发高级架构师知识点学习视频 点击 正在跳转 获取,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。

僵尸进程

? 每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉,需要父进程去释放。
? 进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸
(Zombie)进程。

僵尸进程不能被kii -9杀死

僵尸进程 内核区数据没办法释放

如何解决呢?

ctrl+c 发送一个9号信号把进程杀死 杀死父进程

此时僵尸进程就不存在了 不过此方法只是在演示阶段使用,一般是wait 或waitpid

wait

#include <sys/types.h>
#include <sys/wait.h>
#include<stdio.h>
#include<unistd.h>
#include <stdlib.h>
int main()
{
    //有一个父进程,创建5个子进程(兄弟)
    pid_t pid;
    for(int i=0;i<5;i++)    //这里不只产生5个
    {
        pid =fork();
        if(pid ==0)
        {
            break;       //此时子进程就不会产生孙子进程
        }
    }
    if(pid >0)
    {//父进程
        while(1)
            {
            printf("i am parent,pid = %d\n",getpid());
            //int ret = wait(NULL);
            int st;
            int ret = wait(&st);
            if(ret ==-1)
            {
                break;
            }

            if(WIFEXITED(st))
            {
                //是不是正常退出
                printf("退出的状态码:%d\n",WEXITSTATUS(st));
            }
            if(WIFSIGNALED(st))
            {
                //是不是异常终止
                printf("被哪个信号干掉了:%d\n",WTERMSIG(st));
            }
            printf("child die,pid =%d\n",ret);  //如果成功了,返回值就是子进程的id,失败返回-1
            sleep(1);
            }
    }
    else if (pid ==0){
        //子进程
        while(1)
        {
            printf("child,pid = %d\n",getpid());
            sleep(1);

        }
        exit(1);




    }
    return 0;

}

通过信号杀死

kill -9

默认是阻塞状态

非阻塞状态

int main()
{
    //有一个父进程,创建5个子进程(兄弟)
    pid_t pid;
    for(int i=0;i<5;i++)    //这里不只产生5个
    {
        pid =fork();
        if(pid ==0)
        {
            break;       //此时子进程就不会产生孙子进程
        }
    }
    if(pid >0)
    {//父进程
        while(1)
            {
            printf("i am parent,pid = %d\n",getpid());
            sleep(1);
            //int ret = wait(NULL);
            int st;
            int ret = waitpid(-1,&st,WNOHANG);//非阻塞
            if(ret ==-1)
            {
                break;
            }
            else if(ret == 0)
            {
                //还有子进程存在
                continue;
            }else if(ret>0)//回收到某一个具体的子进程
            {

                if(WIFEXITED(st))
                {
                    //是不是正常退出
                    printf("退出的状态码:%d\n",WEXITSTATUS(st));
                }
                if(WIFSIGNALED(st))
                {
                    //是不是异常终止
                    printf("被哪个信号干掉了:%d\n",WTERMSIG(st));
                }
                printf("child die,pid =%d\n",ret);  //如果成功了,返回值就是子进程的id,失败返回-1
            }
            sleep(1);
            }
    }
    else if (pid ==0){
        //子进程
        while(1)
        {
            printf("child,pid = %d\n",getpid());
            sleep(1);
        }
        exit(1);
    }
    return 0;
}

父进程可继续往下执行

进程间通信

进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源。

进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信(IPC)

03匿名管道

? 管道也叫无名(匿名)管道,它是是 UNIX 系统 IPC(进程间通信)的最古老形式,所有的 UNIX 系统都支持这种通信机制。

统计一个目录中文件的数目命令:ls | wc –l,为了执行该命令,shell 创建了两个进程来分别执行 ls 和 wc。

管道两端对应两个文件描述符支持读操作和写操作

一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少

通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺序是完全一样的。

在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的。 全双工:

半双工:就像对讲机 需要CSMA/CD

? 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据,在管道中无法使用 lseek() 来随机的访问数据。

匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。

管道数据结构

循环队列

示例:

/*
 #include <unistd.h>

       int pipe(int pipefd[2]);

    功能:创建一个匿名管道,用来进程间通信
    参数:int pipefd[2]这个数组是一个传出参数
        Pipefd[0]对应的是管道的读端
        pipefd[1]对应的是管道的写端
    返回值: 成功返回0,失败返回-1
    注意:匿名管道只能用于具有关系的进程间的通信(父子,兄弟)
*/
//子进程发送数据给父进程,父进程读取到数据输出
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
int main()
{
    //在fork()之前创建管道
    int pipefd[2];

    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        exit(0);
    }
    //创建子进程
    pid_t pid = fork();
    if(pid>0)
    {
        //父进程
        //从管道的读取段读取数据
        char buf[1024]={0};
        int len = read(pipefd[0],buf,sizeof(buf));
        printf("parent recv : %s,pid : %d\n",buf,getpid());
    }
    else if(pid == 0)
    {
        sleep(10);
        //子进程
        char* str = "hello,i am child";
        write(pipefd[1],str,strlen(str));
    }


    return 0;
}

只有当管道里面有数据,才能去读,不然处于

父子进程相互读写

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
int main()
{
    //在fork()之前创建管道
    int pipefd[2];

    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        exit(0);
    }
    //创建子进程
    pid_t pid = fork();
    if(pid>0)
    {
        printf("i am parent process pid : %d\n",getpid());
        //父进程
        //从管道的读取段读取数据
        char buf[1024]={0};
        while(1)
        {
        int len = read(pipefd[0],buf,sizeof(buf));
        printf("parent recv : %s,pid : %d\n",buf,getpid());


        char* str = "hello,i am parent";
        write(pipefd[1],str,strlen(str));
        sleep(1);
        }

    }
    else if(pid == 0)
    {
        sleep(10);
        //子进程
    printf("i am child process,pid : %d\n",getpid());
     char buf[1024]={0};
        while(1)
        {
        //向管道中写入数据
        char* str = "hello,i am child";
        write(pipefd[1],str,strlen(str));
        sleep(1);

        int len = read(pipefd[0],buf,sizeof(buf));
        printf("child recv : %s,pid : %d\n",buf,getpid());
        }

    }


    return 0;
}

ulimit -a 管道大小:8块 512字节/块 4k

存在t的原因,是没有清除数组

为什么会出现这种情况?

自己写入的数据被自己读了

怎么解决这个问题? 父子进程要么读,要么写,一般情况下不会出现一个子进程又读又写

管道非阻塞

char buf[1024]={0};
        int flags = fcntl(pipefd[0],F_GETFL);//获取原来的flag标记
        flags |= O_NONBLOCK;    //修改flag的值
        fcntl(pipefd[0],F_SETFL,flags);  //设置新的flag

有名管道

FIFO 提供了一个路径名与之关联 其打开方式与打开一个普通文件是一样的 只要可以访问该路径,就能够彼此通过FIFO通信

有名管道的使用

mkfifo 名字 严格遵循先进先出

方法1: 通过命令创建

方法2:

通过函数创建

int main()
{

    int ret = mkfifo("fifo1",0664);
    if(ret == -1)
    {
        perror("mkfifo");
        exit(0);
    }
    return 0;
}

相关推荐

Linux两种光驱自动挂载的方法

环境:CentOS6.4西昆云服务器方式一修改fstab文件/etc/fstab是系统保存文件系统信息?静态文件,每一行描述一个文件系统;系统每次启动会读取此文件信息以确定需要挂载哪些文件系统。参...

linux系统运维,挂载和分区概念太难?在虚机下操作一次全掌握

虚拟机的好处就是可以模拟和学习生产环境的一切操作,假如我们还不熟悉磁盘操作,那先在虚机环境下多操作几次。这次来练习下硬盘扩容操作。虚拟机环境:centos8vm11linux设备命名规则在linux中...

Linux 挂载 NFS 外部存储 (mount 和 /etc/fstab)

mount:手工挂载,下次重启需再重新挂载,操作命令:mount-tnfs-ooptionsserver:/remote/export/local/directory上面命令中,本地目录...

在Linux中如何设置自动挂载特定文件系统(示例)

Linux...

Linux环境中的绑定挂载(bind mount)

简介:Linux中的mount命令是一个特殊的指令,主要用于挂载文件目录。而绑定挂载(bindmount)命令更为特别。mount的bind选项将第一个目录克隆到第二个。一个目录中的改变将会在...

Linux挂载CIFS共享 临时挂载 1. 首先

如何解决服务器存储空间不足的问题?大家好,欢迎回来。在上一期视频中,我为大家介绍了如何利用Linux挂载来扩容服务器存储空间。这一期视频,我将以Linux为例,教大家如何进行扩容。群辉使用的是Linu...

Linux 硬盘挂载(服务器重启自动挂载)

1、先查看目前机器上有几块硬盘,及已挂载磁盘:fdisk-l能够查看到当前主机上已连接上的磁盘,以及已经分割的磁盘分区。(下面以/dev/vdb磁盘进行分区、挂载为例,挂载点设置为/data)df...

linux 挂载磁盘

在Linux中挂载硬盘的步骤如下:...

笨小猪教您Linux磁盘挂载

本教程针对Linux系统比较熟悉或者想学习Linux基础的用户朋友,本教程操作起来比较傻瓜式,跟着步骤就会操作,本文使用的工具是XShell同时多多注意空格(文中会有提示)。【问答】什么是磁盘挂载?答...

Linux 磁盘挂载和docker安装命令

本篇给大家介绍Linux磁盘挂载和docker安装的相关内容,Linux服务器的操作是一个手熟的过程,一些不常用的命令隔断时间就忘记了,熟话说好记性不如烂笔头,还需在平时的工作中多练习记录。...

Linux设置开机自动挂载分区

有时候,我们在安装完Linux系统之后,可能在使用过程中添加硬盘或者分区进行使用,这时候就需要手动把磁盘分区挂载到某个路径,但是开机之后就会消失,需要重新挂载,非常麻烦,那么我们应该如何设置开机自动挂...

在linux挂载一个新硬盘的完整步骤

以下是在Linux中挂载新原始磁盘的完整步骤,包括分区、创建文件系统以及使用UUID在/etc/fstab中启动时挂载磁盘:将新的原始磁盘连接到Linux系统并打开电源。运行以下命令,...

Linux系统如何挂载exFAT分区

简介:Linux系统中不能像Windows系统那样自动识别加载新设备,需要手动识别,手动加载。Linux中一切皆文件。文件通过一个很大的文件树来组织,文件树的根目录是:/,从根目开始录逐级展开。这些文...

Linux系统挂载硬盘

fdisk-l查看可挂载的磁盘都有哪些df-h查看已经挂载的磁盘...

WSL2发布,如何在Win10中挂载Linux文件系统

WSL2是最新版本的架构,它为Windows子系统提供支持,使其能够在Windows上运行ELF64Linux二进制文件。通过最近的更新,它允许使用Linux文件系统访问存储在硬盘中的文件。如果你...

取消回复欢迎 发表评论: