当前位置: 首页 > article >正文

【Linux进程篇3】说白了,Linux创建进程(fork父子进程)也就那样!!!

---------------------------------------------------------------------------------------------------------------------------------

每日鸡汤:没人可以好运一生,只有努力才是一生的护身符,不放弃、不辜负。

---------------------------------------------------------------------------------------------------------------------------------

目录

前言

一:前期知识铺垫

1.1:查看/杀死进程指令

1.1.1:查看进程指令

1.1.2:杀死进程指令

1.2:获取进程PID(编号标识符)

1.2.1:while循环监视脚本

1.2.2:获取进程PID

1.3:获取进程的父进程编号标识符(PPID)

1.4:总结

二:父进程fork创建子进程

三:创建进程需要打败的四个怪兽(问题)

3.1:怪兽一:为什么fork要给子进程返回0,给父进程返回子进程的PID?

3.2:怪兽二:fork函数究竟在干什么?干了什么?

3.3:怪兽三:一个函数是如何返回2次的?如何理解?

3.4:怪兽四:一个变量怎么会有两个不同的内容?如何理解?


前言

根据前篇文章,我们已经了解了操作系统通过先描述再组织来对进程进行管理。描述进程操作系统自动生成task_struct结构体,组织进程将多个task_struct结构体对象”链起来“形成数据结构,这样对进程的管理就变成了对链表的增删改查

一:前期知识铺垫

1.1:查看/杀死进程指令

1.1.1:查看进程指令

ps ajx:查看所有的进程;

ps ajx | grep pro:只查看名为pro的进程

ps ajx | head -1 ; ps ajx | grep pro:查看pro进程的时候将最开始的那一列名称显示出来。

多行指令并行起来【;  &&】:

所以将 ps ajx | head -1 指令和 ps ajx | grep pro指令并行起来:

法一:ps ajx | head -1 && ps ajx | grep pro

法二:ps ajx | head -1 ; ps ajx | grep pro

1.1.2:杀死进程指令

但是发现有多余的进程——grep --color=auto pro;这是因为grep”关键字过滤“,要过滤名为pro的进程,前提就是将grep指令运行起来,一旦被运行起来,grep就也被加载到了内存当中了,形成进程。那么如何将它给取消(杀)掉呢?

杀死进程的两种方式:

法一:grep -v grep【ps ajx | head -1 && ps ajx | grep pro | grep -v prep

法二:kill -9 该进程的PID编号【常用】

1.2:获取进程PID(编号标识符)

1.2.1:while循环监视脚本

while :; do echo "Hello Linux"; sleep 1; done

while循环打印Hello Linux,每间隔1秒打印一次。

每间隔1秒获取一个进程 ,用来监视进程情况。

while :; do ps ajx | head -1 ; ps ajx | grep pro | grep -v grep; echo "----------------------"; sleep 1; done

开始时没有进程,之后运行可执行程序pro进程时,立刻就出现了./pro进程了

1.2.2:获取进程PID

PID也是task_struct内部的属性之一。通过getpid函数获取该进程的PID编号

看例子:查看getpid函数用法:man getpid

pro.c文件内部

 看运行情况:

1.3:获取进程的父进程编号标识符(PPID)

PPID:该进程父进程的PID。

getppid函数:获取当前进程的父进程PID。

查看getppid函数的用法:man getppid

验证情况:

pro.c文件内部:

 运行情况:

 那么该进程的父进程到底是什么?

查看父进程:ps ajx | head -1 ; ps ajx | grep 4901 | grep -v grep

 该进程的父进程是bash进程。bash进程的PID=4901

1.4:总结

  1. 进程的PCB数据结构体中含有一系列属性,例如PID和PPID就是PCB数据结构体中的属性。
  2. 通过系统调用接口使用getpid()方法获取某一个进程的PID,使用getppid()方法来获取该进程的PPID(父进程PID)
  3. 发现,无论我们怎样操作进程,父进程PPID总是不变的(除了重新登陆xshell)
  4. 查看父进程:ps ajx | head -1 ; ps ajx | grep 21823(该进程的PPID)=> bash进程,PID = 21823,即该进程是通过bash进程创建出来的【使用fork函数】
  5. 每次登录xshell的时候,系统就会创建一个新的bash进程,所以每一次的bash进程的PID可能会不同=> 在终端上输入的所有进程都是bash的子进程。

二:父进程fork创建子进程

fork函数手动创建进程:

fork函数创建进程成功,会返回两个值。

pro.c文件

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

int main()
{
    printf("begin:我是一个进程,pid:%d, ppid:%d\n",getpid(),getppid());
    pid_t id = fork();
    sleep(1);
    if(id == 0)
    {
        //子进程
        printf("子进程id = %d\n",id);
        while(1)
        {
            printf("我是一个子进程,pid: %d, ppid: %d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else if (id > 0)
    {
        //父进程
        printf("父进程id = %d\n",id);
        while(1)
        {
            printf("我是一个父进程,pid: %d, ppid: %d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else 
    {
        //错误,error
    }
    return 0;
}

编译运行情况: 

 执行流,是从上往下依次进行的。

  1. 并且fork函数给父进程返回子进程的PID,给子进程返回0。为什么?
  2. fork函数到底干了什么使得一个id进入了两个循环。
  3. fork函数怎么能够返回两次的。为什么?
  4. 发现一个id变量竟然可以有两个值。为什么?

三:创建进程需要打败的四个怪兽(问题)

3.1:怪兽一:为什么fork要给子进程返回0,给父进程返回子进程的PID?

答:返回不同的返回值,是为了区分让不同的执行流执行不同的代码块。即,可以通过fork返回值明确访问哪一个子进程。

一般而言,fork之后的代码块”父子共享“。

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

int main()
{

    printf("begin:我是一个进程,pid:%d, ppid:%d\n",getpid(),getppid());
    pid_t id = fork();
    printf("检测父进程和子进程代码共享\n");    
    sleep(1);
    if(id == 0)
    {
        //子进程
        printf("子进程id = %d\n",id);
        while(1)
        {
            printf("我是一个子进程,pid: %d, ppid: %d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else if (id > 0)
    {
        //父进程
        printf("父进程id = %d\n",id);
        while(1)
        {
            printf("我是一个父进程,pid: %d, ppid: %d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else 
    {
        //错误,error
    }
    return 0;
}

若fork之后的代码父子共享,那么”检测父进程和子进程代码共享“这句话会执行两次!!!

3.2:怪兽二:fork函数究竟在干什么?干了什么?

知道:

进程 = 内核数据结构(PCB/task_struct) + 代码和数据

所以, fork创建子进程的本质:系统中多了一个进程(PCB / task_struct)。并且,通过fork创建的子进程以父进程为模板,在对应的属性(pid,ppid,...)进行一些修改来创建的一个新的进程。

 fork之后,父子进程之后代码是共享的。且,在代码加载到内存中之后,代码是不可修改的,即内存中的代码是不可被修改的。

那么fork函数之后,父子代码是共享的,那么数据呢?=>问题四解决。

我们为什么要创建子进程呢?最主要的目的就是需要想办法让父与子进程执行不同的代码块(让fork具有不同的返回值)。

3.3:怪兽三:一个函数是如何返回2次的?如何理解?

首先说明,fork也是一个函数。并且因为父子进程代码是共享的。

 因为父子进程都是独立的task_struct,都是可以被CPU调度运行的。

return ret;  :也是代码,也是属于父与子共享的【父进程返回一个ret,子进程返回一个ret】

所以一个函数返回了2次的。

3.4:怪兽四:一个变量怎么会有两个不同的内容?如何理解?

我们已经知道通过fork函数是可以返回2次的。那么一个id变量怎么会有不同的内容?

一个学习知识点:进程是具有独立性的!!!

任何平台,进程在运行的时候,是具有独立性的【例如,关闭酷狗音乐进程,并不影响xshell进程...】,即一个进程退出崩溃不影响另一个进程。

因为父进程和子进程属于两个进程,两个进程各自具有独立性!

那么如何看待进程中【代码和数据】中的数据?答:因为在内存中的数据可能被修改,所以为了保证父子的独立性,就不能让父进程和子进程共享同一份数据!!!

父进程的数据是原有的数据,那么子进程的数据呢

理论情况下:操作系统会单独的拷贝父进程的数据,在粘贴到另外内存中开辟的空间,作为子进程的数据!

但是,实际上数据层面会发生写时拷贝【用多少,给多少空间】(达到节省空间的目的)

步骤如下:

首先,父进程和子进程同时指向相同的代码和数据。之后父进程和子进程进行后续代码的共享。但是当子进程想对数据(共享的某一数据)进行修改时,操作系统为了保证进程的独立性,会进行拦截(将子进程要修改数据的要求进行拦截),此时操作系统将要修改的数据变量那一部分(id变量)重新在内存中开辟合适的新空间,将该变量放入新建空间中,该新建空间独属于子进程。这样就只开辟一个4字节的空间即可。子进程修改多少数据,就创建多大的空间。

写时拷贝 

fork函数的 return 过程的时候,是写入吗? 

  • pid_t id = fork();
  • 在父进程 return 时,id就是父进程的数据
  • 在子进程 return 时,操作系统写时拷贝,在新空间内定义一个新的id变量【独属于子进程的id】

如果父子进程被创建好,fork()往后,谁先运行呢?

关于谁先运行,由调度器【将进程调度到CPU中】决定,是不确定的,在内存中,有许多的进程,各个进程都要进入到CPU中执行,各个进程就是竞争关系,为了避免一个进程被调用多次,而另一个进程一次也没有被调用过,这就需要有调度器(保证进程之间公平)。


http://www.kler.cn/a/390244.html

相关文章:

  • 使用@react-three/fiber,@mkkellogg/gaussian-splats-3d加载.splat,.ply,.ksplat文件
  • RHCE web解析、dns配置、firewalld配置实验
  • Redis - String 字符串
  • jQuery笔记
  • rust模式和匹配
  • 分布式----Ceph部署(上)
  • MySQL基础篇总结
  • vue/react前端项目自定义js脚本实现自定义部署等操作
  • 高级java每日一道面试题-2024年11月01日-Redis篇-Redis支持的数据类型有哪些?
  • Android 编译系统
  • Selenium+Pytest自动化测试框架 ------ 禅道实战
  • 青训5_1112_01 小S的倒排索引(内置方法 set(a) set(b) 及sorted 排序)
  • pytorch detach方法介绍
  • 最新发布“秒哒”,李彦宏:一个只靠想法就能赚钱的时代来了
  • 使用HTML、CSS和JavaScript创建动态雪人和雪花效果
  • 华为OD机试 - 垃圾信息拦截(Python/JS/C/C++ 2024 C卷 100分)
  • Maven 项目模板
  • 探索Python图像处理的奥秘:Pillow库的全面指南
  • 请简述Vue与React的区别
  • 【Linux】进程信号全攻略(一)
  • 云上盛宴-腾讯云双11活动玩法攻略
  • 【Linux探索学习】第十一弹——初识操作系统:冯诺依曼体系结构与操作系统的概念与定位
  • 开源数据库 - mysql - mysql-server-8.4(gtid主主同步+ keepalived热切换)部署方案
  • Lua进阶用法之Lua和C的接口设计
  • uniapp实现H5和微信小程序获取当前位置(腾讯地图)
  • 确定图像的熵和各向异性 Halcon entropy_gray 解析