进程概念[上]

一、冯诺依曼体系结构

冯 • 诺依曼体系结构核心原理为:用户输入的数据先放到内存当中,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<sys/types.h>
#include<unistd.h>
介绍:
PID是task_struct的内容之一,是描述本进程的唯一表示符,用来区别其他进程

 ②获取PID

#include<stdio.h>    
#include<sys/types.h>    
#include<unistd.h>    
int main()    
{    
  while(1)    
  {    
    printf("pid: %d\n",getpid());        
    sleep(1);                                                                                                                                                                                                    
  }    
  return 0;    
}

 

③关闭进程的俩个方式

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

0X08 如何获取PPID?

①获取PPID

#include<stdio.h>    
#include<sys/types.h>    
#include<unistd.h>    
int main()    
{    
  while(1)    
  {    
    printf("getppid: %d\n",getppid());                                                 
    sleep(1);    
  }    
  return 0;    
}  


 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 创建第一个子进程

#include<stdio.h>    
#include<sys/types.h>    
#include<unistd.h>    
int main()    
{    
   int ret = fork();    
    
   if(ret > 0)    
   {    
      printf("hello \n");    
   }    
   else    
   {    
      printf("world\n");                                                               
   }    
    
  return 0;    
} 

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

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

这是因为创建了子进程

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

  #include<stdio.h>    
  #include<sys/types.h>    
  #include<unistd.h>    
  int main()    
  {    
    int ret = fork();    
      
    while(1)                                                                           
    {    
      printf("PID:%d,PPID:%d\n",getpid(),getppid());    
      sleep(1);    
    }    
    return 0;    
  }  

 从上述代码和结果中我们又可以看出,俩个进程的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 父子进程可以做不同的事情

#include<iostream>                                                                     
#include<unistd.h>

int main()
{
  pid_t id = fork();
  if(id > 0)
  {
    while(true)
    {
      std::cout << "parent:" << getpid() << "  "<< getppid() << std::endl;
      sleep(2);
    }
  }
  else if(id == 0)
  {
    while(true)
    {
       std::cout << "child:" << getpid() << "  "<< getppid() << std::endl;
       sleep(2);
    }
  }
  else
  {
    //如果进程创建失败,什么也不做  
  }

  return 0;
}           

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

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

六、 进程的状态信息

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

在task_struct中

0x02 进程状态的意义

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

 0x03 运行状态(R状态)

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

#include<iostream>    
#include<unistd.h>    
    
int main()    
{    
  while(true); //没有等待外设,只是排队CPU资源,比较快,所以一直处于运行状态                                                                        
  return 0;    
}  

提示:

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

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

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

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

#include<iostream>    
#include<unistd.h>    
    
int main()    
{    
  while(true)    
  {    
    std::cout << "hello world" << std::endl;                                           
  }    
  return 0;    
}   


为什么代码一直在运行,确实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中,一个进程退出并不是直接进入死亡状态,而是先进入僵尸状态,再进入死亡状态

#include<iostream>    
#include<unistd.h>    
using namespace std;    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    while(true)    
    {    
      cout << " I am child,running\n" << endl;    
      sleep(2);    
    }    
  }    
  else    
  {    
    cout << "parent do nothing\n" << endl;    
    sleep(50);                                                                                                   
  }    
    
  return 0;    
}  

 进行监控进程:

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

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

 0x09 孤儿进程

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

#include<iostream> 
#include<unistd.h>    
#include<stdlib.h>
using namespace std;
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    while(true)
    {
      cout << " I am child,running\n" << endl;
      sleep(2);
    }
  }
  else
  {
    cout << "parent do nothing\n" << endl;
    sleep(10);
    exit(1);
  }

  return 0;                                                                                                      
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值