【Linux】进程概念

目录

  • [1. 冯诺依曼体系结构](#1. 冯诺依曼体系结构)
  • [2. 操作系统](#2. 操作系统)
    • [2.1 概念](#2.1 概念)
    • [2.2 设计OS的目的](#2.2 设计OS的目的)
    • [2.3 定位](#2.3 定位)
    • [2.4 如何进行 "管理"](#2.4 如何进行 "管理")
    • [2.5 操作系统管理硬件](#2.5 操作系统管理硬件)
    • [2.6 系统调用和库函数概念](#2.6 系统调用和库函数概念)
  • [3. 进程](#3. 进程)
    • [3.1 基本概念](#3.1 基本概念)
    • [3.2 PCB](#3.2 PCB)
    • [3.3 task_ struct内容分类](#3.3 task_ struct内容分类)
    • [3.4 操作系统对进程的管理](#3.4 操作系统对进程的管理)
    • [3.5 组织进程](#3.5 组织进程)
    • [3.6 查看进程](#3.6 查看进程)
    • [3.7 通过系统调用获取进程标示符](#3.7 通过系统调用获取进程标示符)
    • 3.8通过系统调用创建进程-fork初识
  • [4. 进程状态](#4. 进程状态)
    • [4.1 Z(zombie)-僵尸进程](#4.1 Z(zombie)-僵尸进程)
    • [4.2 僵尸进程的危害](#4.2 僵尸进程的危害)
    • [4.3 孤儿进程](#4.3 孤儿进程)
  • [5. 进程优先级](#5. 进程优先级)
    • [5.1 查看系统进程](#5.1 查看系统进程)
    • [5.2 PRI and NI](#5.2 PRI and NI)
    • [5.3 其他概念](#5.3 其他概念)
      • [5.3.1 进程切换------>并发](#5.3.1 进程切换------>并发)
  • [6. 环境变量](#6. 环境变量)
    • [6.1 基本概念](#6.1 基本概念)
    • [6.2 常见环境变量](#6.2 常见环境变量)
    • [6.3 和环境变量相关的命令](#6.3 和环境变量相关的命令)
    • [6.4 环境表](#6.4 环境表)
    • [6.5 通过代码如何获取环境变量](#6.5 通过代码如何获取环境变量)
    • [6.6 通过系统调用获取或设置环境变量](#6.6 通过系统调用获取或设置环境变量)
    • [6.7 本地变量 && 内建命令](#6.7 本地变量 && 内建命令)
  • [7. 程序地址空间](#7. 程序地址空间)
    • [7.1 内存管理和进程管理](#7.1 内存管理和进程管理)
    • [7.2 子进程的进程管理与内存管理](#7.2 子进程的进程管理与内存管理)
    • [7.3 页表](#7.3 页表)
    • [7.4 大文件的惰性加载](#7.4 大文件的惰性加载)

1. 冯诺依曼体系结构

外设:

  • 输入设备:鼠标,键盘,摄像头,话筒,磁盘,网卡
  • 输出设备:显示器,播放器硬件,磁盘,网卡
    有的设备是纯的输入,输出,也有即使输入,又是输出设备

cpu/中央处理器:

  • 运算器:对我们的数据进行计算任务(算数运算,逻辑运算)
  • 控制器:对我们的计算硬件流程进行一定的控制

输入设备、输出设备、运算器、控制器、内存都是独立的个体。各个硬件单元必须用"线"链接起来("线"即总线,包括系统总线和I0总线)

计算机存储金字塔:

2. 操作系统

2.1 概念

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

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

2.2 设计OS的目的

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

2.3 定位

在整个计算机软硬件架构中,操作系统的定位是:一款纯正的"搞管理"的软件

2.4 如何进行 "管理"

2.5 操作系统管理硬件

  • 描述起来(指描述底层硬件),用struct结构体
  • 组织起来(将描述出来的底层硬件组织起来),用链表或其他高效的数据结构

2.6 系统调用和库函数概念

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

3. 进程

3.1 基本概念

  • 课本概念:程序的一个执行实例,正在执行的程序等
  • 内核观点:担当分配系统资源(CPU时间,内存)的实体。
  • 进程:内核PCB数据结构对象+你自己的代码和数据

3.2 PCB

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

3.3 task_ struct内容分类

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

3.4 操作系统对进程的管理

  • 一个操作系统,不仅仅只能运行一个进程,可以同运行多个进程
  • 操作系统必须的将进程管理起来! 如何管理进程呢?-------->先描述,在组织
  • 先描述:任何一个进程,在加载到内存的时候,形成真正的进程时,操作系统都要先创建描述进程属性的结构体对象--- PCB、process ctrlblock、进程控制块
  • 后组织:在操作系统中,对进程进行管理,变成了对单链表进行增删改查!

3.5 组织进程

可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里

3.6 查看进程

  • 进程的信息可以通过 /proc 系统文件夹查看
    如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹
  • 大多数进程信息同样可以使用top和ps这些用户级工具来获取

结束进程:

c 复制代码
# kill -9  XXX   // XXX:进程对应的PID

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

  • 进程id(PID)
  • 父进程id(PPID)
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
 printf("pid: %d\n", getpid());
 printf("ppid: %d\n", getppid());
 return 0;
}

3.8通过系统调用创建进程-fork初识

fork:通过系统调用函数,让父进程创建子进程

fork函数返回值:

如果成功,给父进程返回子进程的PID,0返回给子进程,此时返回值有两个

如果失败,给父进程返回-1,不返回给子进程

fork 之后通常要用 if 进行分流:

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
 printf("begin: 我是一个进程,pid: %d!, ppid: %d\n", getpid(), getppid());
 
 int ret = fork();
 if(ret < 0)
 {
 perror("fork");
 return 1;
 }
 else if(ret == 0)  //child
 { 
 printf("我是子进程,pid: %d!, ppid: %d\n", getpid(), getppid());
 }
 else   //father
 { 
 printf("我是父进程 : %d!, ret: %d\n", getpid(), getppid());
 }
 sleep(1);
 return 0;
}

代码执行结果:

  1. 为什么fork要给子进程返回0,给父进程返回子进程pid?

返回不同的返回值,是为了区分让不同的执行流,执行不同的代码块! 一般而言fork代码及fork之后的代码父子共享。给父进程返回子进程pid是为了让父进程区别子进程。

  1. fork函数,究竟在干什么?干了什么?
  1. 我们为什么要创建子进程呢?

为了让父和子执行不同的事情!------需要想办法让父和子执行不同的代码块

让fork具有了不同的返回值!-------区别父子进程

  1. 如果父子进程被创建好,fork0,往后,谁先运行呢??

谁先运行,由调度器决定,不确定的

  1. 一个函数是如何做到返回两次的? 如何理解?

因为fork代码及fork之后的代码父子共享,所以 fork 函数被父子进程执行了两次,所以有两个返回值。而且ret是父进程的数据,子进程无法修改,当子进程调用函数后,再赋值给 ret 时,发生了深拷贝,新空间也有一个变量,叫做 ret,所以是同一变量名,不同空间。

4. 进程状态

c 复制代码
/*
* 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 */
};
  • R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里

  • S睡眠状态(阻塞状态): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠

    (interruptible sleep))。

  • 挂起状态:或简称"挂起态",是一个表示进程被"冻结"或"停滞"的特殊状态。在此状态下,进程不会在主存中活跃,而是被转移到辅助存储器(如硬盘)中。这意味着进程在此状态下不会获得CPU的执行时间,并从活跃队列中移除

  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。

    休眠状态就是阻塞状态,我们将这种休眠状态称之为阻塞状态,于此对应有深度休眠状态,浅度睡眠的进程的会对外部的信号做出响应(比如可以直接ctrl c被终止掉)。

    而深度睡眠(disk sleep)状态也是阻塞状态,这种状态的程序是针对磁盘设计的,这种状态的 进程,操作系统是不能杀掉的。原因如下:

    操作系统在没有空间的时候,会通过杀掉进程节省资源,但当一个进程正在向磁盘中写入关键数据 时,如果杀掉该进程,那么写入磁盘就会失败导致数据丢失,为了应对这种情况设置了深度睡眠的状态,操作系统遇到这种状态的进程,就不会去杀掉他。直到进程向磁盘中写完关键数据后,深度睡眠才结束

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

  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态

4.1 Z(zombie)-僵尸进程

  • 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
  • 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态,进程的相关资源尤其是task struct 结构体不能被释放!

4.2 僵尸进程的危害

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

4.3 孤儿进程

  • 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  • 父进程先退出,子进程就称之为"孤儿进程"
  • 孤儿进程被1号init进程(系统)领养,当然要有init进程回收喽
  • 为什么孤儿进程要被领养? 因为孤儿进程未来也会退出,也要被释放

5. 进程优先级

基础概念:

  • cpu资源分配的先后顺序,就是指进程的优先权(priority)。
  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
  • 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能
  • 如果进程长时间得不到CPU资源,该进程的代码长时间无法得到推进 --- 该进程的饥饿问题

5.1 查看系统进程

在linux或者unix系统中,用ps --l命令则会类似输出以下几个内容:

我们很容易注意到其中的几个重要信息,有下:

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值,表示进程可被执行的优先级的修正数值

5.2 PRI and NI

  • PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
  • 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
  • 所以,调整进程优先级,在Linux下,就是调整进程nice值
  • nice其取值范围是-20至19,一共40个级别

5.3 其他概念

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

5.3.1 进程切换------>并发

进程切换就是从正在运行的进程中,收回CPU的使用权利,交给下一个要运行的进程。

通过进程切换,从而实现并发。

我们先了解一下寄存器:

  • 为什么函数返回值,会被外部拿到呢?-------->通过CPU寄存器
    return a -> mov eax 10
  • 系统如何得知我们的进程当前执行到哪行代码了?
    程序计数器pc, eip(寄存器):记录当前进程正在执行指令的下一行指令的地址!
  • 寄存器作用:提高进程效率,高频数据放入寄存器中
  • CPU寄存器里面保存的是进程的临时数据-----进程的上下文!

进程切换:

  • 保存上下文----->下一个进程------>又到了这一个进程------>恢复上下文
  • 进程在从CPU上离开的时候,要将自己的上下文数据保存好,甚至带走
  • 保存的目的,是为了未来的恢复

6. 环境变量

6.1 基本概念

  • 环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
  • 全局特性的原因:我们所运行的进程,都是子进程,bash本身在启动的时候,会从操作系统的配置文件中读取环境变量信息,子进程会继承父进程交给它的环境变量!

6.2 常见环境变量

  • PATH : 指定命令的搜索路径
  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  • SHELL : 当前Shell,它的值通常是/bin/bash。

6.3 和环境变量相关的命令

  1. echo: 显示某个环境变量值
c 复制代码
echo $NAME //NAME:你的环境变量名称    不加$的话,echo把NAME当作字符串,直接打印NAME
  1. export: 设置一个新的环境变量
c 复制代码
//直接设置环境变量
export XL=123456 //设置的环境变量名称为XL,环境变量值为:123456

//将本地变量转化为环境变量
a=3           //设置本地变量
export a      
  1. unset: 清除环境变量
c 复制代码
unset NAME //NAME:你的环境变量名称
  1. env: 显示所有环境变量
  2. set: 显示本地定义的shell变量和环境变量

6.4 环境表

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以'\0'结尾的环境字符串

6.5 通过代码如何获取环境变量

  • 命令行第三个参数
c 复制代码
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
 int i = 0;
 for(; env[i]; i++){
 printf("%s\n", env[i]);
 }
 return 0;
}
  • 通过第三方变量environ获取
c 复制代码
#include <stdio.h>
int main(int argc, char *argv[])
{
 extern char **environ;
 int i = 0;
 for(; environ[i]; i++){
 printf("%s\n", environ[i]);
 }
 return 0;
}

libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。

6.6 通过系统调用获取或设置环境变量

  • getenv
c 复制代码
getenv("NAME");  //NAME:环境变量名称   返回值为环境变量的值
  • putenv,下次讲解

6.7 本地变量 && 内建命令

  • 本地变量,只会在本BASH内部有效,不会被继承
  • 创建本地进程
  • 两批命令
    • 常规命令 -- 通过创建子进程完成的
    • 内建命令 -- bash不创建子进程,而是由自己亲自执行,类似于bash调用了自己写的,或者系统提供的函数

7. 程序地址空间

7.1 内存管理和进程管理

我们可以看到的所有地址都是虚拟地址,平时写的C/C++,用的指针,指针里面的地址,全部都不是物理地址。

7.2 子进程的进程管理与内存管理

  • 写时拷贝是由操作系统自动完成的,操作系统重新开辟空间,但是在这个过程中,页表左侧的虚拟地址是0感知的,一直都是不变的。
  • 页表的 rw 表示该数据的读写属性,即确定了数据是否能修改。
  • 子进程的页表是由父进程拷贝而来,但不同的是,不管父进程 rw 那一栏是什么,子进程页表的 rw 那一栏都是 r 。当子进程要修改数据时,操作系统会辨别数据是否能修改,若能修改,那操作系统会重新开辟空间,进行写时拷贝,并修改 rw 属性

7.3 页表

什么是页表?

页表是一种数据结构,用于管理虚拟内存和物理内存之间的映射关系。在操作系统中,当一个程序需要访问内存时,它会先访问虚拟内存,然后再通过页表将虚拟地址映射到物理内存中的实际地址。

页表的作用是什么?

  1. 让进程以统一的视角看待内存。
  2. 增加进程虚拟地址空间可以让我们访问内存的时候,增加一个转换的过程,在这个转化的过程中,可以对我们的寻址请求进行审查,所以一旦异常访问,直接拦截,该请求不会到达物理内存,保护物理内存
  3. 因为有地址空间和页表的存在,将进程管理模块,和内存管理模块进行解耦合!

页表地址:

页表地址也属于进程上下文,进程运行时,被放在了寄存器了。

7.4 大文件的惰性加载

  1. 概况程序在计算机如何运行:本身程序是在硬盘上,需要把程序加载进内存,然后由CPU去执行。
  2. 操作系统对大文件可以实现分批加载,意思就是说,先将大文件放在硬盘里,cpu运行时,需要哪部分数据或代码再把哪部分数据或代码加载到内存里,cpu用完后,再把在内存中的这部分数据或代码释放在内存中释放掉。
  3. 缺页中断:当cpu需要某数据或代码时,会去进程地址空间查找虚拟地址,再通过页表查看对应代码或数据有没有加载到内存,如果有,就通过物理地址在内存中查找,如果没有,就在将数据或代码从硬盘加载到内存。当没有被加载的情况,就叫做缺页中断
  4. 那么进程在被创建的时候,是先创建内核数据结构呢? 先加载对应的可执行程序呢?---------先创建内核数据结构。
相关推荐
我是高手高手高高手7 分钟前
ThinkPHP8多应用配置及不同域名访问不同应用的配置
linux·服务器·前端·php
虾稿2 小时前
[手机Linux] 七,NextCloud优化设置
linux·运维·服务器
首发运维2 小时前
centos 释放系统预留内存并关闭Kdump服务
linux·运维·centos·linux操作系统问题
新子-存在了2 小时前
linux中 mysql备份
linux·运维·mysql
最后一个bug2 小时前
rt-linux中使用mlockall与free的差异
linux·c语言·arm开发·单片机·嵌入式硬件·算法
ZHOUPUYU2 小时前
VMware虚拟机超详细安装Linux教程(最新版)
linux·运维·服务器·windows·微软·centos·虚拟机
初级代码游戏2 小时前
关于linux的ld.so.conf.d
linux·运维·服务器
xianwu5432 小时前
反向代理模块。开发
linux·开发语言·网络·c++·git
m0_748232392 小时前
在Linux centos7环境下部署wblogic使用weblogic部署war包项目
linux·运维·服务器
李先静2 小时前
用 gdbserver 调试 arm-linux 上的 AWTK 应用程序
linux·arm开发·awtk