微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

进程概念跑路人笔记

前言

本此将讲述一些对进程了解的一些铺垫性知识,和一些与进程有关的概念。


冯诺依曼体系结构

我们常见的笔记本,台式计算机,服务器等大部分都是遵循冯诺依曼体系结构实现的机器。

冯诺依曼结构如下图:

图片来自网络)

image-20220818180034461

我们的冯诺依曼由输入设备、输出设备、存储器、运算器、控制器组成。下面分别介绍一下这些都是什么

输入设备:键盘,鼠标,写字板,磁盘,网卡

输出设备:显示器,音响,网卡,显卡,磁盘

存储器:就是内存(通俗的讲)

运算器+控制器[cpu]:算数计算+逻辑计算

而且冯诺依曼体系必须遵循的是:

我们的输入设备,输出设备,只能从我们的内存中拿取数据在不考虑缓存的情况下我们的cpu也只能从我们的内存中读取和输出数据。

总之:所有设备都只和内存打交道。


案例:

我们在使用微信等软件聊天的时候,就是我们先在键盘输入数据到内存,内存将数据给cpu计算计算后cpu吧内存又重新给内存,内存将数据给到网卡上发出。我们接收是也是通过网卡进行接收到内存,内存给cpu计算然后显示到我们的屏幕上。


那我们为啥不直接将内存步骤省去直接输入输出对接我们的cpu呢?

因为慢。

我们的输入输出设备的速度是远远跟不上我们的cpu的。

所以我们就先将我们的数据先存储在我们的内存中,且部分数据会预装载到我们内存中(内存现在一般为8G左右),然后cpu从内存里拿东西,但是cpu计算的快的很,我们的输出设备跟不上咋办,那就让cpu把数据放到内存,我们的输出设备就可以慢慢的从内存里拿了。

那我们为啥不升级输入输出设备呢?

因为贵。hhhh

所以冯诺依曼的体系可以说一比较经济且好使的了。这也是为什么他是我们计算机通用的体系。

操作系统

我们电脑一般都是预装的Windows系统。当然也有Linux系统,还有最近我国整的openKylin系统(注:好像是开源的,大家可以了解一下)

那么我们的操作系统的作用是什么呢?为啥电脑里都要有个操作系统呢?

答:管理电脑的。

没错我们的操作系统的作用就是可以简单的理解成一个管理电脑的软件。

概念

任何一个计算机系统都包含一个基本的程序集合,被称为操作系统(OS)。笼统的理解下操作系统一个包括内核 其他程序

  • 内核(进程管理,内存管理,文件管理,驱动管理)
  • 其他程序(函数库,shell程序等等)

设计OS的目的

  • 与硬件交互,管理所有的软硬件资源
  • 用户程序(应用程序)提供一个良好的执行环境。

那么什么是管理呢?

根据我们的理解,管理应该不是对一个人进行的工作,而是对一类人。

及 管理不是对被管理对象进行直接管理,而是只要拿到被管理对象的所有的相关数据,我们对数据的管理,就可以体现对人的管理。


那我们的操作系统又是如何对如此巨大的数据进行管理的呢?

其实我们知道的是大部分的数据都是重复且正常的我们只需要对少量的异常数据进行管理即可。

所以我们就应该使用某种技术将我们需要管理的对象描述出来。

因为操作系统是C语言写的,所以我们需要用struct当我们需要的对象进行描述好,然后使用某种数据结构来将我们的对象一个个的链接起来。(如双链表…….)

及我们应该做到先描述再组织

系统调用和库函数概念

  • 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用

  • 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就和有利于更上层或者开发者进行二次开发。


我们使用的电脑的结构应该是如下图的,而我们在使用库函数时是调用了lib部分的内容,而lib就需要去系统调用接口的内容实现自己的lib。

及如果出现了一个新的操作系统时,我们的C,Java,C++…语言是需要自己实现lib来适配操作系统的。这也是为什么我们操作系统出现新操作系统出现比较困难的原因。

image-20220820163034296

我们的操作系统对硬件或者旭东的管理都是为了服务我们的用户,如当我们使用C语言的printf函数时,我们并没有直接让硬件(显示器)打印数据,而是通过各种层然后经过操作系统的管理下才得到了打印数据的行为。而printf后发生的一切都是操作系统的工作,我们就无需过分消耗精力关心了。

进程

我们先在linux中跑一个进程。

进程代码如下:

#include<stdio.h>                                                              
#include<string.h>  
#include<unistd.h>     
int main()  
{  
     while(1)  
    {  
        printf("I am a proccess\n");  
        sleep(1);  
    }  
    return 0;  
}

一个很简单的死循环程序.我们将其运行起来他就是一个进程了。当然别人的运行程序也是进程。

当然我们像在Linux中的ls cd ….等都是一个程序其实不过因为运行很快所以我们看不到他的进程内容

所以我们写了一个死循环程序来让大家看一看我们的进程都有什么内容

查看进程的指令如下:

ps axj |grep "test"

ps 是看进程的工具axj是我们的选择,|是管道grep是字符串查找。“test”是字符串其实也是我们进程的名字(应该是./test)

内容如下

image-20220820173516195

一个就是我们的test进程,第二个是我们的grep进程。

在讲这些表格内容都是什么之前我们先看一下根目录内容一个东西。

image-20220820174005818

其中proc是我们的内存文件系统,他记录了当前系统实时的进程信息。

那么proc里都有什么东西呢?

让我们看看

image-20220820174506276

一堆以数字为名称文件夹,和一些文件

那么这些数字又是什么意思呢。

先让我们看一下ps工具的表格形式

image-20220820174422317

其中PID是我们对进程做标记的标识符,所以我们可以通过PID来在proc文件里查找我们./test进程文件夹里的内容.

image-20220820175245221

image-20220823113358108

这也是为什么我们使用fopen创建一个文件的时候,我们的程序会知道当前路径在哪里,因为已经在proc文件中保存了.

而我们的PID是在哪里保存的呢.

答:在**进程控制块—PCB(linux叫做task_struct)**中。


我们的PID讲的差不多了,让我们再讲讲PPID是什么。

PPID听名字和PID很像,那么他是什么呢?

答:父进程的PID。

我们创建的进程都有一个共同的父进程bash。

包括ls cd 等指令他们的父进程都是bash。

如何证明呢?

我们将我们的test进程多次终止重启,来看看他的PPID是否会发生改变。

image-20220823113448698

可以看到我们的PID虽然在终止重启后发生了改变但是我们的PPID始终都是不变的。

所以我们可以知道我们的子进程都是可以得到我们父进程的PPID的。

fork函数

所以让我们讲一个可以建立子进程的函数

fork();这个函数是linux的系统调用函数,在windows中不一定可以使用。

文件如下:

#include <sys/types.h>

fork()可以创建一个子进程并将父进程中fork下的代码与子进程共享。(包括fork代码

切我们的的fork();函数

直接说他的性质吧。

fork();函数创建的子进程是会在父进程之后被执行的,原因很简单,我们的父进程必须先执行完fork函数的核心功能之后才会创建好我们的子进程。从而我们的子进程才能被执行。

fork函数返回值

fork函数会给我们的子进程部分的函数返回值为0对父进程部分的函数返回值是他子进程的pid

原因其实很简单,因为我们的子进程其实是可以很容易的找到我们的父进程的(ppid)—使用getppid();函数就可以得到父进程的pid。

而我们的父进程却只能通过fork的返回值来确定子进程。

而且我们总要通过一个方式来确定谁是父进程谁是子进程,而fork的返回值就是一个很好的确定方式。

PCB—进程控制块

我们的进程PCB其实就是一个结构体,一个用于保存进程属性的结构体。

我们PCB保存内容有许多,同时也保存了代码和数据,这也就是为什么我们的fork()函数可以一个函数两次返回而且相同变量名的变量可以接收的原因。

image-20220823144432745

我们值得注意的是通过fork();函数得到的子进程是我们fork();函数完成自己核心功能之后得到的。

切我们的子进程和父进程的代码共享,数据不共享。

以后我们提到进程就应该想到PCB

进程状态

每个进程都是有着状态区别的。

每个进程一共有四种状态

运行态 终止态 阻塞态 挂起态

运行态: 运行态不是正在运行的状态。 而是时刻准备运行的状态。

image-20220823152351635

这种状态我们就叫做运行态,每个PCB在此时都时刻准备运行时每个PCB都是运行态。

终止态:不是已经被释放掉的进程,而是已经不会再被调用了的进程状态

为什么我们的进程已经不会被调用了但是却没有被释放而是被保存成为一个终止态的状态呢?其实原因很简单因为我们的操作系统OS可能很忙,没时间去处理释放你,所以就给予了一个终止态的状态。

阻塞态:进程等待非cpu资源时的状态。及资源没有就绪的时候,进程需要在该资源的等待队列中进行排队,此时进程并没有运行而是在内存中等待,这时所处的状态就叫阻塞。

举个例子:我们在看直播看视频聊天时……(等需要用到网卡设备)又加着下载着东西,这时虽然你的cpu可以轻易完成任务,但是你的网卡却不大行,这时就会容易出现我们的下载任务进度暂停,原因很简单,我们需要网卡的进程太多了,而网卡已经无法完成如此多工作了,这时我们的操作系统(OS)就会把占着cpu却不运算的程序(下载或其他)给搬运到其他外设的等待队列上。这时的状态就是阻塞。

挂起态:当阻塞状态的进程也过多导致我们内存空间不足的时候,就会出现操作空间将一些短期不会被执行的进程的PCB移到磁盘的情况。这种情况就叫做挂起态。

不举例子了233

Linux具体状态例子

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = 
{
	"R (running)", /* 0 */[重点]
	"S (sleeping)", /* 1 */[重点]
	"D (disk sleep)", /* 2 */
	"T (stopped)", /* 4 */[重点]
	"t (tracing stop)", /* 8 */
	"X (dead)", /* 16 */
	"Z (zombie)", /* 32 */[重点]
};

我们的Linux一共有如上7个状态。

部分状态和我们之前讲的进程状态很想我们就简单的看一下:

  • R运行状态(running):并不意味着进程一定在完成中,他表示要么是在运行中要么在运行队列里。

  • S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠可以叫做可中断睡眠)

    • 可中断睡眠的意思就是因为我们的操作系统是有着杀死进程的权力的,当我们的计算机比较忙碌的时候就会将部分可中断睡眠的进程杀死以给我们的内存争取时间。
  • D磁盘休眠状态(disk sleep): 有时候也叫不可中断睡眠状态(uninteruptible sleep),在这个状态的进程通常会等待IO的结束

  • T停止状态:可通过发送SLGSTOP信号给进程来停止这个进程。这个被停止的进程可通过发送SIGCONT信号让进程继续运行。

    • 如何发送呢?我们在Linux中可以借助kill命令来完成。(如下图)
    • image-20220824164746431

    • kill使用格式 kill + num +pid
  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态

Z(Zombie)僵尸状态

故名思意,僵尸状态就是一个进程已经进入了死亡状态,但是我们的操作系统并不能随时将该进程的资源释放掉。

那么什么时候会出现僵尸进程呢?

首先我们要知道我们创建进程是为了什么?

当然是为了完成任务,并且告诉父进程自己的任务完成的怎么样了。

那么当我们的子进程已经完成了自己的所有任务,但是父进程还在等待中没有时间接收子进程的结束任务报告。这时我们的子进程就会变成僵尸进程。(值得一提的是父进程回收子进程结束进度的报告我们叫做等待,后续会讲解)。

僵尸进程的PCB会一直不被回收知道我们的父进程等待子进程。

如下代码就可以出现子进程的僵尸状态

#include<cstdlib>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int cnt = 3;
        while(cnt)
        {
            printf("我是子进程我还剩%d\n",cnt--);
            sleep(1);
        }
        printf("子进程僵尸化\n");
        exit(0);
    }
    else{
        while(1)
        {
            sleep(1);
        }
    }
    return 0;
}

image-20220824173100601

因为我们的父进程一直在sleep没有时间回收子进程所有导致我们的子进程僵尸化。

可以看到pid为14372的进程就是僵尸进程。

这样的进程有什么危害呢?

  • 进程的退出状态必须被一直维护下去,因为他要告诉父进程,任务出错了还是完成了,但是如果父进程一直不读取的话,子进程就会一直处于僵尸状态。
  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,及Z状态一直不退出,PCB一直都要维护。
  • 一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
  • 也就是说会造成内存泄漏

孤儿进程

孤儿进程,就是父进程死亡但是子进程还没有完成运行。这时子进程就会变成孤儿进程。

这时的孤儿进程会被进程pid为1接收(及操作系统)被操作系统接收并继续运行直到死亡。

原文地址:https://www.jb51.cc/wenti/3281964.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐