• 进程概念[上]


    一、冯诺依曼体系结构

    冯 • 诺依曼体系结构核心原理为:用户输入的数据先放到内存当中,CPU 读取数据的时候就直接从内存当中读取,CPU 处理完数据后又写回内存当中,然后内存再将数据输出到输出设备当中,最后由输出设备进行输出显示。 

    参考来自:冯诺依曼体系结构-CSDN博客

    0x01 计算机组成

    1.输入设备: 键盘,磁盘,网卡,显卡,话筒,摄像头等
    2.输出设备: 显示器,磁盘,网卡,显卡,音响等
    3.存储器(内存)
    4.运算器&&控制器(cpu

     0x02 存储器分级结构

    二、 操作系统

    0x01 什么叫操作系统?

    操作系统是一款专门针对软硬件资源进行管理工作的软件
    操作系统对下管理好软硬件资源
    对上为普通用户提供良好的运行环境,为程序员提供各种基本功能

    此时就会出现一个问题:操作系统如何提供各种基本功能呢?

    因为操作系统不相信任何用户,所以会采用系统调用接口去完成各种基本功能的调用

    0x02 操作系统的作用是什么?

    以管理好软硬件资源的方式,给用户提供稳定的,高效的,安全的运行环境

    0x03 如何进行管理?

    先描述被管理对象,再把多个管理对象之间产生联系,使用特性的数据结构组织起来 --- 先描述,再组织(可以将对目标的管理转化成对数据的管理)
    比如从校长的角度,校长想要给一个学习成绩好的学生奖励,但是有一个学校有很多的学生,那么校长如何进行管理呢?此时就可以将每一个学生的数据聚合起来,形成学生的属性,然后再通过数据结构,将多个学生的聚合数据之间产生关联,这就叫先描述,再组织

    三、进程

    0x01 初看什么叫做进程?

    初步来说进程是加载到内存的程序

    0x02 那么如何管理进程呢?

    当然也是先描述,再组织,任何进程在形成之时,操作系统要为该进程创建PCB --- 进程控制块

    0x03 为什么要有PCB?

    因为管理进程的方式是先描述再组织,要描述进程,那么就需要使用结构体来描述进程的相关属性

    0x04 什么叫做PCB?

    PCB: 一个结构体类型,在Linux系统中,PCB具体就是
    struct task_struct
    {
            //进程的所有的属性
    }

    0x05  如何看见进程

    ①运行程序后

    PID:进程号,当程序运行时就会有当前进程的进程号

    ②结束程序后

    CTRL+C后,就结束了进程

    提示:
    ps axj : 查看所有进程
    head -1: 只显示结果的第一行,即每一列的列名
    曾经我们所有的启动程序的过程,本质上都是再系统上面创建进程

    0x06  再看进程

     进程加载到内存中,有了进程控制块,那么所有进程管理任务与进程对应的程序毫无关系,与进程对应的内核创建的的该进程的PCB强相关,就相当于当你面试的时候,面试官不是看你长的有多帅,有多美,而是看所收集到的数据来进行筛选,所以你是否能进面,就与这些数据强相关

     0x07 如何获取PID?

     ①getpid()

    头文件:

    #include
    #include
    介绍:
    PID是task_struct的内容之一,是描述本进程的唯一表示符,用来区别其他进程

     ②获取PID

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. while(1)
    7. {
    8. printf("pid: %d\n",getpid());
    9. sleep(1);
    10. }
    11. return 0;
    12. }

     

    ③关闭进程的俩个方式

    (1) CTRL + C
    (2) kill + 9 进程号

    0X08 如何获取PPID?

    ①获取PPID

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. while(1)
    7. {
    8. printf("getppid: %d\n",getppid());
    9. sleep(1);
    10. }
    11. return 0;
    12. }


     0x09 状态

    ①退出码&退出信号

    0x10 上下文

    ① 上下文数据

    进程执行时处理器的寄存器中的数据

    ② 进程切换

    ①CPU中存放着寻多寄存器,寄存器中保存着当前正在运行的程序的临时数据
    ②运行队列run_queue中的每一个结点都是一个task_struct ,通过指针找到相应的代码和数据
    ③每次有结点是运行状态的时候就可以加载到CPU中进行运行,但是进程的代码可能不是很短时间就能运行完成
    ④可以假设,规定每个进程单词运行的时间片是5ms,那么第一个进程在CPU中运行5ms之后,就得从CPU中拿出,再去运行队列之后进行重新排队,重新进行运行
    ⑤所以再单CPU的情况下,用户感受到的多个进程同时在运行,本质是通过CPU的快速切换完成的

    从上述可以得出一个结论:

    进程在运行期间是有切换的,进程可能存在大量的临时数据,而这些临时数据是在CPU的寄存器中保存

    但是又会得出一个疑惑点:

    CPU里面的寄存器只有一套

     这时就要提到保护上下文和恢复上下文的操作了:

    当一个task_struct结点在CPU运行之后,那么此时它要重新去排队了,那么此时CPU寄存器中所保存的临时数据就会被保存到task_struct中,这就是保护上下文
    而当这个task_struct结点又到它运行到CPU中时,那么之前保存的数据又会被重新加载到CPU的寄存器当中继续使用,这就叫恢复上下文
    可以更通俗的讲就是:让你去做其他的事情,但是又不耽误现在要做的事情,当你要回来继续做这件事时,可以继续做这件事情

    从上述可知,通过上下文,我们能感受到进程是被切换的

     四、查看进程

    0x01 通过/proc系统文件夹查进程信息

     进程启动之后会在/proc目录下形成目录,以自身的PID号作为目录文件名,形成对应文件夹,当这个对应的进程退出时,这个文件夹就会消失

     0x02 查看当前进程所对应的属性

     0x03 查看当前正在执行的程序

    提示:

    cwd:表示当前工作目录,当执行命令或打开文件时所参考的相对路径
    exe:表示我们当前正在执行的程序

    五、创建子进程

    0x01 创建第一个子进程

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. int ret = fork();
    7. if(ret > 0)
    8. {
    9. printf("hello \n");
    10. }
    11. else
    12. {
    13. printf("world\n");
    14. }
    15. return 0;
    16. }

    从之前所学中我们可以猜想,执行的结果必然是if和else中的一个,但是结果真的是这样吗?

    从结果中可以看出,竟然是if和else都执行了,这是为什么呢?

    这是因为创建了子进程

    从上面的代码中我们可能还有些疑惑,这到底是什么意思,那么我们再来看个代码:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. int ret = fork();
    7. while(1)
    8. {
    9. printf("PID:%d,PPID:%d\n",getpid(),getppid());
    10. sleep(1);
    11. }
    12. return 0;
    13. }

     从上述代码和结果中我们又可以看出,俩个进程的PID号竟然是不一样的,因为一个是父进程,另一个是子进程

    0x02 理解fork()创建子进程

    ①创建的子进程与在操作系统角度创建进程的方式是没有任何差别的,也是系统里多了一个进程,即多了一份与进程相关的内核数据结构task_struct和数据代码,但是此处的数据代码会继承父进程的代码数据,
    ②内核数据结构task_struct也会以父进程为模板,初始化子进程的task_struct

    ③fork()之后,子进程和父进程代码是共享的,但代码只有一份,是不可以被修改的
    ④数据其实也是共享的,但这要考虑到修改的情况

    0x03 写时拷贝

    ①在 Linux 中,调用 fork()创建子进程时,只是读取的时候共享一份,当需要修改的时候,并不会将父进程的所有数据复制,而是会找一块空间,将父进程该页数据复制一份给子进程 ,以此来保证进程之间的独立性
    ②当进程调用fork(),控制转移到内核中的fork()代码后,内核所做的操作:

    (1)将分配新的内存块和内核数据结构给子进程  
    (2)将父进程部分数据结构内容拷贝到子进程  
    (3)添加子进程到系统进程列表当中  
    (4)fork()返回,开始调度器调度

    fork之前父进程独立执行,fork之后,父进程与子进程俩个执行流分别执行,子进程返回0,父进程返回子进程的PID
    参考:【Linux】写时复制(CopyOnWrite)|写时拷贝|rcu_bandaoyu的博客-CSDN博客

     0x04 fork()返回值

    我们创建子进程,就是为了和父进程干不一样的事情,那么这就需要fork()的返回值来完成
    创建子进程失败:    <0

    创建子进程成功:     给父进程返回子进程的PID, 给子进程返回0

    0x05 父子进程可以做不同的事情

    1. #include<iostream>
    2. #include<unistd.h>
    3. int main()
    4. {
    5. pid_t id = fork();
    6. if(id > 0)
    7. {
    8. while(true)
    9. {
    10. std::cout << "parent:" << getpid() << " "<< getppid() << std::endl;
    11. sleep(2);
    12. }
    13. }
    14. else if(id == 0)
    15. {
    16. while(true)
    17. {
    18. std::cout << "child:" << getpid() << " "<< getppid() << std::endl;
    19. sleep(2);
    20. }
    21. }
    22. else
    23. {
    24. //如果进程创建失败,什么也不做
    25. }
    26. return 0;
    27. }

     此时,我们会有一个问题,fork()之后,父子进程谁先运行?

    这个是不确定的,这是有调度器所决定的

    六、 进程的状态信息

    0x01 进程的状态信息在哪里呢?

    在task_struct中

    0x02 进程状态的意义

    方便操作系统快速判断进程,完成特定的功能,比如调度,调度本质上也是一种分类

     0x03 运行状态(R状态)

    运行状态并不意味着进程一定在运行中,表明进程要么是在运行中,要么是在运行队列中,即当前进程已经被放在了运行队列当中,随时等待CPU进行调度

    1. #include
    2. #include
    3. int main()
    4. {
    5. while(true); //没有等待外设,只是排队CPU资源,比较快,所以一直处于运行状态
    6. return 0;
    7. }

    提示:

    ①R+:"+"表示在前台运行,ctrl +c进行停止,
    ②没有"+"则处于后台运行: 如果想要后台运行进程,则./test &,ctrl +c是停止不了的,只有通过kill + 9 + 进程号停止
    ③也可以将后台运行放到前台: fg

    0x04  睡眠状态&磁盘休眠状态(S状态&D状态)

    当完成某种任务的时候,任务条件不具备,需要进程进行某种等待,也就是当进程等待外部设备条件时,就会放入等待队列,这时的状态就是S状态或者D状态

     S睡眠状态: 意味着进程在等待时间完成,可以被操作系统消除,也可以叫做可中断睡眠状态

    1. #include
    2. #include
    3. int main()
    4. {
    5. while(true)
    6. {
    7. std::cout << "hello world" << std::endl;
    8. }
    9. return 0;
    10. }


    为什么代码一直在运行,确实S状态呢?

    因为正在打印到显示器上,外设的速度是比较慢的,等待外设就需要花费时间的,只是看起来比较快,其实进程一直是在处于休眠状态

     D磁盘休眠状态: 这个状态的进程通常会等待IO结束,即当进程在等磁盘处理数据并返回,但是此时进程什么事也没做,此时操作系统想要消除这个进程,但是如果消除了这个进程,那么磁盘处理好数据后那返回给谁呢?,所以这个进程不能被消除,所以D状态也可以叫做不可中断睡眠状态 

     0x05 挂起状态&唤醒进程

    我们把从运行状态的task_struct(run_queue),放到等待队列,就叫挂起状态(阻塞)
    从等待队列,放到运行队列,被CPU调度就叫做唤醒进程

    提示:
    ①进程在运行的时候,有可能因为运行的需要,可能会在不同的队列里
    ②在不同的队列里,所处的状态也可能是不一样的

     0x06 停止状态(T状态)

    可以通过发送 SIGSTOP 信号给进程来停止( T )进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。

    提示:
    ①kill -l :列出所有可用信号
    ②查看正在运行的进程的PID
    ③kill -信号编码 进程号
    ④进行查看是否处于T状态

    0x07 死亡状态(X状态)

    这个状态只是一个返回状态,你不会在任务列表里看到这个状态

    回收进程资源 = 进程相关的数据结构 + 代码和数据

    0x08 僵尸状态(Z状态)

    僵尸状态是为了辨别退出死亡的原因,而这些原因也是数据,保存在task_struct中,一个进程退出并不是直接进入死亡状态,而是先进入僵尸状态,再进入死亡状态

    1. #include<iostream>
    2. #include<unistd.h>
    3. using namespace std;
    4. int main()
    5. {
    6. pid_t id = fork();
    7. if(id == 0)
    8. {
    9. while(true)
    10. {
    11. cout << " I am child,running\n" << endl;
    12. sleep(2);
    13. }
    14. }
    15. else
    16. {
    17. cout << "parent do nothing\n" << endl;
    18. sleep(50);
    19. }
    20. return 0;
    21. }

     进行监控进程:

    while :; do ps axj | head -1 && ps axj | grep test | grep -v grep; sleep 1;echo "###################" done;

     当进程正在运行时,父进程一直处于运行状态,但是此时子进程被干掉,但是没有得到父进程的回收,此时子进程所处的状态就是僵尸状态

     0x09 孤儿进程

    当父进程被干掉,但子进程还在运行,此时的子进程就叫做孤儿进程,孤儿进程的父进程会变成操作系统

    1. #include<iostream>
    2. #include<unistd.h>
    3. #include<stdlib.h>
    4. using namespace std;
    5. int main()
    6. {
    7. pid_t id = fork();
    8. if(id == 0)
    9. {
    10. while(true)
    11. {
    12. cout << " I am child,running\n" << endl;
    13. sleep(2);
    14. }
    15. }
    16. else
    17. {
    18. cout << "parent do nothing\n" << endl;
    19. sleep(10);
    20. exit(1);
    21. }
    22. return 0;
    23. }

  • 相关阅读:
    【2022中国高校计算机大赛 微信大数据挑战赛】Top 1-6 方案总结
    断点测试怎么做,一文教你用Charles 工具做好接口测试!
    Hive 的函数介绍
    搭建一个自己的AI学术语音助手(一)
    低代码能为企业带来什么好处
    【ant-design-vue】ant-design-vue在uniapp使用时,auto-import失败报错
    基于Python和mysql开发的看图猜成语微信小程序(源码+数据库+程序配置说明书+程序使用说明书)
    掌握源码,轻松搭建:一站式建站系统源码 附完整搭建步骤与教程
    网络套接字2
    大语言模型(LLM)综述(四):如何适应预训练后的大语言模型
  • 原文地址:https://blog.csdn.net/qq_53010164/article/details/133619499