深入解析:Linux 进程概念

1.冯诺依曼体系结构

大家常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。

关于冯诺依曼,必须强调几点:

1.这里的存储器指的是内存
2.不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
3.外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
4.一句话,所有设备都只能直接和内存打交道。

比如当我们在日常启用聊天软件时,输入设备(键盘)在接受到素材后,要交给cou处理,然后交给输出设备网卡发送出去,到达对方设备后,输入设备网卡接收后交给cpu处理,最后表现到对方的输出设备(显示器)上面。

2.操作系统

2.1 概念

操作系统(OS)是管理计算机硬件与软件资源的系统软件,它提供了一个稳定、有效的执行环境,允许用户和程序通过高效的方式使用计算机资源。简而言之,操作系统是计算机与用户之间的桥梁,帮助用户操作硬件、运行应用程序,并提供必要的服务和接口。

操作系统的功能远超简单的资源调度,它还涉及到进程管理、内存管理、文件系统管理和设备驱动管理等多个方面。

2.2 为什么需要操作系统?

操作系统的出现是因为计算机硬件的复杂性远超单一用户的需求。假设每个用户都可以直接与硬件交互,效率将大打折扣,而且系统的安全性和稳定性也无法保障,操作系统并不相信每一个用户。

操作系统通过以下方式来简化与硬件的交互:

资源管理:操作系统管理计算机的所有硬件资源,包括处理器、内存、存储设备和输入输出设备,确保资源的合理分配和高效利用。

进程与线程管理:操作系统负责调度进程的执行,确保各个进程之间不会相互干扰,献出多任务处理能力。

内存管理:操作系统通过虚拟内存管理技术,为每个程序分配独立的内存空间,有效避免了程序之间的数据干扰。

记录管理:操作系统提供文件系统,使得用户能够方便地存储和管理数据文件。

2.3. 驱动程序与硬件抽象

操作系统和硬件之间并不是直接交互的,操作系统通常依赖于驱动程序来管理硬件设备。每种硬件设备都必须一个对应的驱动程序,这些程序为操作系统提供硬件的抽象层,操作系统通过驱动程序与硬件进行交互。

例如,计算机的硬盘更换时,用户只需要更新硬盘的驱动程序,而不需要更改操作系统本身的代码。驱动程序为硬件和操作系统之间提供了一个可扩展的接口,使得硬件的更新和维护变得更加容易。

2.4. 框架调用与库函数

操作系统通常会暴露一些系统调用接口供开发者和程序使用。架构调用是操作系统向外提供的基本服务接口,程序可以凭借这些接口来执行文件操作、内存分配、进程管理等基础功能。\n\n对于开发者来说,直接使用架构调用较为复杂,因此操作系统还会献出一些封装好的库函数。这些库函数基于系统调用,将常见的操作封装成简单的函数,供程序开发者调用。

3. 进程

基本概念

课本概念:一个被加载到内存中的程序就就叫做进程。

内核观点:担当分配系统资源(CPU时间,内存)的实体。

描述进程--PCB

进程信息被放在一个叫做进程控制块的数据结构中,能够理解为进程属性的集合。
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct,在Linux中PCB就是一个struct结构体。

task_struct-PCB的一种

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

正确定义:

于是对于进程,当一个软件被加载到内存时,在成为真正的进程前,操作系统作为软硬件的管理者,对程序的代码和数据创建一个task_struct结构体来描述他,方便进行管理,进程可以理解为由task_struct和代码+资料构成的。

ps:操作系统对于PCB的结构体采用双向链表的方式管理起来。

当我们编写代码后,代码运行起来后就相当于以及形成了一个进程。

task_ struct内容分类

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

4. 进程PID

概念:

PID(Process ID)即进程标识符,是操作系统为每个进程分配的一个唯一的数字编号。

查询

实时查询系统中正在运行的进程信息。就是1.ps指令是Linux下的进程查询指令,核心作用

展示进程的父子关系、会话等详细信息,其中各参数的含义:就是在Linux中, ps ajx 是查看进程信息的命令组合,核心
-  a :显示所有终端下的进程(包括其他用户的进程);
-  j :显示与作业控制、进程组、会话相关的信息(比如PPID、PGID、SID等);
-  x :显示无终端关联的进程(比如后台进程、守护进程)。

2.通过库函数getpid()来查询

5. proc目录

Linux平台将所有进程的信息存储在 /proc 目录下。每个正在运行的进程都有一个对应的目录,目录名称即为进程的PID。

查看某个进程的信息:

通过/proc/PID 我们就能够查看到进程的详细信息。

凭借访问这些文件,大家允许了解进程的详细信息,包括内存使用情况、CPU时间、打开的文件描述符等。

6. PPID父进程的PID

在进程中也有父子的说法,父进程,子进程,当我们使用ps指令去查看进程时,PPID就是父进程的id。

经过代码打印pid和ppid

bash命令行解释器的id。就是这里的父进程的id也就

7. 通过fork创建子进程

上面可以很明显的看出来,如果一个程序正常的运行,它的父进程就是bash,那我们写的代码也想要创建子进程,让子进程去执行一些任务该怎么做呢?

从当前进程(父进程)复制出一个新进程(子进程),父子进程几乎完全独立运行。就是fork() 是 Linux 中创建新进程的核心环境调用,作用

核心特点:

1. 调用一次,返回两次:父进程中返回子进程的 PID(正数),子进程中返回 0,出错返回 -1。
2. 复制机制:子进程复制父进程的地址空间(代码、数据、打开的文件等),但后续修改会发生写时拷贝,互不影响(现代系统用“写时复制”优化,避免冗余复制)。
3. 独立运行:父子进程拥有各自的 PID、PCB(进程控制块),操作系统调度时视为两个独立进程。

工作原理:

fork创建子进程时会创建一个子进程的PCB,在CPU眼里面两个就相当于不同的进程,在代码和数据方面,代码就相当于只读指令,我们调用的printf,for循环,数据相当于我们定义的变量,代码在运行时就不可以被修改了,但是数据是有可能被修改的,因此在数据被一方修改时,修改的那一方会发生写时拷贝(会为修改方重新开辟一块物理内存,将原物理页的内容拷贝上去),避免影响另一方。

#include
#include
#include
/*
 * 测试fork创建子进程
 * 理解fork函数的返回值
 * 通过if语句进行分流
 * 总结:fork创建子进程成功时,给父进程返回子进程PID,给子进程返回0,
 如果失败返回-1;通过两次fork可以发现当父进程执行后,才会去执行子进程,
 父子进程间存在独立性,即父进程被kill后,子进程任然可以运行,父子进程间存在写时拷贝机制,
 当子进程的值发生改变时,只会作用于子进程中
 */
int main()
{
  pid_t ret = fork(); //获取返回值
  int val = 1;  //比较值
  if(ret == 0)
  {
    //在子进程内再创建(孙)子进程
    pid_t rett = fork();
    if(rett > 0)
    {
      while(1)
      {
        val = 2;  //写时拷贝
        printf("二代进程正在执行 PID:%d PPID:%d 比较值为:%d 地址:%p\n\n", getpid(), getppid(), val, &val);
        sleep(1);
      }
    }
    else if(rett == 0)
    {
      while(1)
      {
        val = 3;  //写时拷贝
        printf("三代进程正在执行 PID:%d PPID:%d 比较值为:%d 地址:%p\n\n", getpid(), getppid(), val, &val);
        sleep(1);
      }
    }
    else
      printf("进程创建失败\n");
  }
  else if(ret > 0)
  {
      while(1)
      {
        val = 1;  //写时拷贝
        printf("一代进程正在执行 PID:%d PPID:%d 比较值为:%d 地址:%p\n\n", getpid(), getppid(), val, &val);
        sleep(1);
      }
  }
  else
    printf("进程创建失败\n");
  return 0;
}

运行结果:

可以看到打印出来的val都不太相同,这里的地址其实实际上应该是不同的,都是打印出来的是虚拟地址。

posted @ 2025-12-18 13:52  yangykaifa  阅读(0)  评论(0)    收藏  举报