Linux进程概念(上)

目录

1. 冯诺依曼体系结构

1.1 那么什么是内存?

1.2 为什么要有内存?

1.3 程序运行时必须先把程序加载到内存里?

2. 操作系统

 2.1 概念

2.2 设计OS的目的

2.3 系统调用和库函数概念

 3. 进程

3.1 进程的概念

3.2 描述进程--PCB(进程控制块)

3.3 Linux中的PCB--task_struct

3.4 父进程和子进程 

3.4 查看进程

3.5 通过系统调用获取进程标示符

4. fork创建子进程 

 5. fork的原理

扩展


 

本节重点:
认识冯诺依曼系统
操作系统概念与定位
深入理解进程概念,了解PCB
学习进程状态,学会创建进程,掌握僵尸进程和孤儿进程,及其形成原因和危害
了解进程调度,Linux进程优先级,理解进程竞争性与独立性,理解并行与并发
理解环境变量,熟悉常见环境变量及相关指令, getenv/setenv函数
理解C内存空间分配规律,了解进程内存映像和应用程序区别, 认识地址空间。
选学Linux2.6 kernel,O(1)调度算法架构

1. 冯诺依曼体系结构

截至目前,我们所认识的计算机,都是由一个个的硬件组件组成
输入单元:包括键盘, 鼠标,扫描仪, 写板等
中央处理器(CPU):含有运算器和控制器等
输出单元:显示器,打印机等
关于冯诺依曼,必须强调几点:
这里的存储器指的是内存
不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
一句话,所有设备都只能直接和内存打交道。

1.1 那么什么是内存?

内存是计算机用来存储数据和程序的临时存储器。它通常被称为RAM(随机存取存储器),用于临时存储正在运行的程序和数据,以便CPU能够快速访问和处理这些信息。

1.2 为什么要有内存?

因为内存可以解决cpu与外设效率不匹配的问题。倘若没有内存,当我们写入数据时,cpu的效率非常快,而IO设备的效率比之cpu非常非常慢,那么在这个过程中,cpu就一直在等待,浪费了非常多的资源。

内存的诞生就源于此,他的效率比cpu慢,却比IO设备快很多,

 内存具有数据存储的能力,可以提前把数据准备好,CPU只需要从内存中拿数据进行处理。此外,根据局部性原理,当CPU需要获取某一行数据时,内存可以将该行数据之后的数据一同加载进来,而CPU处理数据和内存加载数据是可以同时进行的,这样下次CPU就可以直接从内存当中获取数据。因此,内存作为计算机数据的核心,可以大大提高数据处理的效率和速度。引入内存就把效率问题,转化为了软件问题。

1.3 程序运行时必须先把程序加载到内存里?

冯诺依曼是这么规定的,cpu不与外设打交道。程序也是文件,都存储在硬盘上,硬盘属于外设,因此需要加载到内存。

对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上,请解释,从你登录上qq开始和某位朋友聊天开始,数据的流动过程。从你打开窗口,开始给他发消息,到他的到消息之后的数据流动过程。如果是在qq上发送文件呢?

聊天的时候,键盘输入的信息储存在内存中,并将其打包成特定的网络协议格式。CPU对这些数据进行处理,确保它们可以正确地通过网络发送。这些数据包通过网络发送到朋友的计算机上。朋友的计算机上的QQ程序处理这些接收到的消息,将其解包并显示在屏幕上。

如果是发送文件,这个文件首先被从硬盘读取到内存中。再以同样的方式发送到朋友那,朋友接收后,QQ程序处理这些数据包,去掉头信息,并将它们重新组装成原始的文件。这个文件随后被存储在朋友的计算机的硬盘上,朋友可以在其计算机上访问和查看这个文件。

2. 操作系统

 2.1 概念

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

内核(进程管理,内存管理,文件管理,驱动管理)

其他程序(例如函数库,shell程序等等)

2.2 设计OS的目的

与硬件交互,管理所有的软硬件资源

为用户程序(应用程序)提供一个良好的执行环境

2.3 

定位:在整个计算机软硬件架构中,操作系统的定位是: 一款纯正的“搞管理”的软件
在这里我们引入一张图:计算机的层状结构

        底层硬件是计算机系统的最底层部分,包括处理器、内存、硬盘、显卡等物理设备。这些硬件是计算机系统的基础,提供了运行程序和存储数据的能力。
        驱动程序是介于底层硬件和操作系统之间的软件。由于不同的硬件厂商生产的硬件设备可能采用不同的技术和协议,因此操作系统无法直接识别和管理这些硬件。驱动程序的作用就是为操作系统提供与硬件设备进行通信和控制的能力。驱动程序包含有关硬件设备的信息,可以让操作系统识别和管理硬件,并与其进行交互。
        操作系统是运行在计算机硬件之上的系统软件,它是计算机系统的核心。操作系统负责管理和控制计算机的硬件和软件资源,提供用户界面,支持多任务处理和文件系统等功能。操作系统通过与驱动程序进行交互,实现对底层硬件的管理和控制。

底层硬件提供了计算机系统的物质基础,驱动程序为操作系统提供了与硬件进行交互和控制的能力,而操作系统则负责管理和控制整个计算机系统的资源和运行(先描述,在组织)。

2.3 系统调用和库函数概念

在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。

 3. 进程

在还没有学习进程之前,就问大家,操作系统是怎么管理进行进程管理的呢?很简单,先把进程描述起来,再把进程组织起来!

3.1 进程的概念
 

通俗来讲,进程等于pcb(内核数据结构)+代码和数据 

进程是计算机中正在运行的程序的实例。当一个程序被执行时,操作系统会为其创建一个进程,该进程包含了程序的代码、数据、堆栈等信息。每个进程都有自己的内存空间,可以独立运行和管理资源。

进程具有以下特点:

  1. 独立性:每个进程都是独立运行的实体,拥有自己的内存空间和资源。
  2. 并发性:计算机可以同时运行多个进程,实现多任务并发执行。
  3. 隔离性:每个进程之间相互隔离,一个进程的崩溃不会影响其他进程的运行。
  4. 通信:进程之间可以通过进程间通信(IPC)机制进行数据交换和协作。

进程是操作系统中的核心概念,通过对进程的管理和调度,操作系统可以有效地控制计算机资源的分配和利用,实现多任务并发处理,提高系统的性能和效率。

这就是进程。我们可以把每一个程序都看作是一个进程。

3.2 描述进程--PCB(进程控制块)

进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
我们来看看PCB的产生过程:

操作系统管理加载到内存的程序通常遵循以下步骤:

加载程序:当用户启动一个程序时,操作系统首先会将程序的可执行文件从磁盘加载到内存中。这个过程涉及将程序的代码段、数据段和堆栈段等部分加载到内存的不同区域。
设置程序的执行环境:操作系统会为每个加载到内存的程序设置一个独立的执行环境,包括分配内存空间、建立程序的数据结构、初始化寄存器等。
 创建进程控制块(PCB):操作系统会为每个加载到内存的程序创建一个进程控制块。(PCB)是操作系统中用于管理进程的数据结构,它包含了进程的各种信息和状态,PCB对象会根据自己所包含的信息在内存中找到自己对应的程序,记录和管理程序的运行状态、资源占用情况、优先级等信息,在内存中的PCB对象会形成数据结构方便操作系统的管理(增删查改)。
分配资源:操作系统会根据程序的需求分配各种资源,如内存空间、CPU时间片,以确保程序能够正常运行。
运行程序:一旦程序被加载到内存并设置好执行环境,操作系统会按照调度算法将程序放入就绪队列,等待CPU执行。当程序获得CPU时间片时,操作系统会将控制权交给程序,程序开始执行。

 在这个过程中,PCB就诞生了,OS对进程的一切操作都是对PCB进行管理,PCB就是一个内核数据结构。

3.3 Linux中的PCB--task_struct

在Linux中描述进程的结构体叫做task_struct。

task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息

task_struct的内容分类

标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息

3.4 父进程和子进程 

在操作系统中,父进程和子进程是进程之间常见的关系。当一个进程创建另一个进程时,创建者进程称为父进程,被创建的进程称为子进程。父进程和子进程之间有以下关系:

  1. 父进程创建子进程:父进程可以通过系统调用(如fork())创建子进程,子进程是父进程的副本,但有自己独立的内存空间。

  2. 子进程继承父进程的属性:子进程会继承父进程的一些属性,如文件描述符、信号处理器等,但也可以通过系统调用修改这些属性。

  3. 进程树:父进程可以创建多个子进程,从而形成进程树的结构。父进程可以监控和管理其子进程,也可以等待子进程结束并获取其退出状态。

  4. 进程间通信:父进程和子进程之间可以通过进程间通信(IPC)机制进行数据交换和协作,如管道、共享内存、消息队列等。

总之,父进程和子进程之间是一种层次化的关系,父进程通常会创建和管理子进程,子进程可以执行独立的任务,但在某些情况下也可以与父进程进行通信和协作。

3.4 查看进程

ps axj//查当前系统运行的所有进程

 

PID 就是当前进程的标识符,PPID是当前进程的父进程的标识符,stat是当前进程的运行状态。

父进程与子进程是一对多的关系,父进程与子进程同样是相互独立的,互相不会影响运行。

ls /proc //proc文件内就是正在运行的进程信息

 ls、pwd等命令也是进程,是bash进程的子进程。

在/proc文件里就是当前正在运行的进程,文件名就是他们的PID 

 用这个命令查询code.exe进程的状态信息,当进程结束,就查不到了。

ps axj | head -1 && ps axj | grep code.exe | grep -v grep

3.5 通过系统调用获取进程标示符
 

通过下面的系统调用我们可以得到code.exe进程的id与其父进程的id 

 通过查询我们得知,code.exe的父进程就是bash,同时ps ajx与grep的父进程也都是bash进程。

4. fork创建子进程 

man fork

man fork我们可以查看fork的介绍,这里我们暂时只需要知道fork是用来创建子进程的,他有两个返回值,父进程返回子进程pid,子进程返回0。 

下面是一段使用fork创建子进程的代码,我们运行看看,同时打开一个进程监视窗口,运行如下脚本命令: 

while :; do ps ajx | head -1 && ps ajx | grep process.exe | grep -v grep ;sleep 1; done
 1: process.c+  ⮀                                                            ⮂⮂ buffers 
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 
  5 int main()
  6 {
  7     printf("process is running,pid(): %d,ppid: %d\n",getpid(),getppid());
  8     pid_t id=fork();//fork创建子进程,子进程fork返回0,父进程返回pid
  9     sleep(2);
 10     int num=100;
 11     if(id==0)
 12     {
 13         //子进程
 14         int cnt=10;
 15         while(cnt--)
 16         {
 17             num=999; 18             printf("child running! id: %d, pid(): %d, ppid(): %d, num: %d\n",id, getpi    d(),getppid(),num);
 19             sleep(1);
 20         }
 21     }
 22 
 23     else
 24     {                                                                                 
 25         while(1)
 26         {                                                                           
 27             sleep(1);                                                                28             printf("parent running!id: %d, pid(): %d,ppid(): %d, num: %d\n",id,getpid(    ),getppid(),num); 29         }                                                                           
 30     }                                                                               
 31     return 0;                                                                       
 32 }     

结果如下:

 试验成功,但这里有一个小小的问题:

上面图中红字里的num是怎么回事 呢?

 5. fork的原理

fork凭什么能返回两个值呢?以前我们学的函数可都是只能返回一个值的。 

其实父进程调用fork的时候,在fork里创建了子进程,然后父子进程共享下面的代码,同时return,不过子进程return 0,父进程return 子进程的pid(为了知道子进程的状态信息),因此fork返回的值是两个进程返回的。

那么同一个id,为什么又大于0,又等于0呢?(之前的代码逻辑使用if else 根据id的值完成两个进程的工作)

在pcb那里,我们说每个进程都有自己的进程控制块pcb,我们讲子进程会继承父进程的属性,相当于父进程的副本,但有自己的独立空间。而每一个进程的标识符必然是独一无二的,因此父子进程返回不同的PID值,只不过fork函数经过处理,子进程会返回0,父进程返回子进程的pid(因为父亲总要知道孩子所处的状态,但不会干涉孩子的生活习惯(即不会影响子进程的运行,也看不到子进程内部的状况))

扩展

我们看模块四遗留的小问题,为什么会这样呢?

父子进程是相互独立的,有各自的空间,但对于代码(只读)和程序数据是共有的。

而类似代码(只读),数据这类内容将会被他们所共有,但如果子进程会对其做修改,会发生写时拷贝,即修改时发生拷贝,此时被修改的数据对于父子进程也是独立的,修改的只是子进程拷贝来的数据。

因此在两个进程中,num呈现了不同的值。

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值