Linux——进程概念

目录

  • 一、系统调用和库函数概念
  • 二、基本概念
  • 三、描述进程-PCB
    • 3.1 task_struct-PCB的一种
    • 3.2 task_ struct内容分类
  • 四、组织进程
  • 五、查看进程
  • 六、通过系统调用获取进程标示符
  • 七、通过系统调用创建进程- fork初始
    • 7.1 fork函数创建子进程
    • 7.2 fork 之后通常要用 if 进行分流
  • 八、进程状态
    • 8.1 课本给出的图(引入)
    • 8.1.1运行状态
    • 8.1.2 阻塞状态
    • 8.2 Linux进程状态
      • 8.2.1 运行状态-R
      • 8.2.2 浅度睡眠状态-S
      • 8.2.3 深度睡眠状态-D
      • 8.2.4 暂停状态-T
      • 8.2.5 死亡状态-X
      • 8.2.6 僵尸状态-Z
    • 8.3 僵尸进程
      • 8.3.1僵尸进程的危害
    • 8.4 孤儿进程
  • 九、进程优先级
    • 9.1 基本概念
    • 9.2 查看系统进程
    • 9.3 PRI与NI
    • 9.4 查看进程优先级信息
    • 9.5 通过top命令更改进程的nice值
    • 9.6 renice命令更改进程的nice值
    • 9.7 四个重要概念
  • 十、环境变量
    • 10.1 基本概念
    • 10.2 常见环境变量
    • 10.3 查看环境变量方法
      • 10.3.1 测试PATH
      • 10.3.2 测试HOME
      • 10.3.3 测试SHELL
    • 10.4 和环境变量相关的命令
    • 10.5 环境变量的组织方式
    • 10.6 通过代码如何获取环境变量
    • 10.7 通过系统调用获取环境变量
    • 10.8 环境变量通常是具有全局属性的
  • 十一、进程地址空间
    • 11.1 地址空间是什么?如何理解地址空间的区域划分?
    • 11.2 为什么要有进程地址空间?
  • 十二、inux2.6内核进程调度队列
    • 12.1 一个CPU拥有一个runqueue
    • 12.2 优先级
    • 12.3 活动队列
    • 12.4 过期队列
    • 12.5 active指针和expired指针


一、系统调用和库函数概念

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

二、基本概念

  • 课本概念:程序的一个执行实例,正在执行的程序等
  • 内核观点:担当分配系统资源(CPU时间,内存)的实体

对进程初步的概念
一个已经加载到内存的程序(就是冯诺依曼体系),叫做进程
正在运行的程序,叫做进程

理解
按照冯诺依曼体系结构
在这里插入图片描述

你向朋友认识介绍你的室友,你会讲:他的姓名、年龄、身高、爱好等等。

  • 描述的都是属性,其实生活中描述任何一件事,一个人都是从他的属性描述,当属性够多,这一堆属性的集合,就是目标对象。

在这里插入图片描述

  • 根据进程的PCB类型,为该进程创建对应的PCB对象

1.比如你高考完后你所报考的大学去你的高中把你的大学的档案转到你所报考的大学,而你还在放暑假中,你的数据信息已经先过去了。2.开学时你进了学校,学校有你的个人基本信息,也有你个人的存在,那么你就是这所学校的一名学生。

PCB就是你的个人基本信息,而你就是这个磁盘中的文件

在这里插入图片描述

如你学校(操作系统)的一名保安他是不是你学校的学生呢?当然不是,他个人(文件)虽然在这所学校,但学生管理系统中并没有保安(文件)的个人基本信息,那么校长的这份管理表并不会对保安做管理。

  • 所以光把一个可执行程序的二进制代码加载到内存就如同在学校的保安一样并不是这个学校的学生

描述进程的PCB结构体和该进程对应的代码和数据合起来才是进程PCB是由操作系统内部维护的,代码和数据是由我们写的,当加载进程时本质上不仅仅是代码和数据加载到内存了而且操作系统还根据操作系统内描述进程的控制块为当前的进程创建对应的PCB,把该进程的相关属性填充好并初始化形成一个PCB,这个结构体对象、结构体变量,是由操作系统自己形成的。

所以我们这里对进程的理解:
进程 = 内核PCB数据结构对象 + 我们自己的代码和数据
在这里插入图片描述
所谓的对进程做管理本质是对PCB做管理,对我们写的代码和数据(二进制文件)压根就不管
那么进程所对应我们的代码和数据在内存的什么位置?怎么找到代码和数据?
PCB属性中包含我们对应的相关指针信息,通过指针找到进程所对应的代码和数据。

当然操作系统中不止一个进程,那如果是多个进程呢?
在这里插入图片描述到现在我们对进程的理解:

  • 进程 = 内核PCB数据结构对象 + 我们自己的代码和数据
  • 操作系统管理的是进程在内核中创建的PCB对象,并不是我们写的代码数据,将PCB对象用链表全部连接起来,转换成了对链表的增删查改

三、描述进程-PCB

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

3.1 task_struct-PCB的一种

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

3.2 task_ struct内容分类

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

四、组织进程

可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。
Linux内核中,最基本的组织进程task_struct的方式是采用双链表组织的。
PCB不仅仅属于某个双链表

五、查看进程

进程的信息可以通过 /proc 系统文件夹查看
系统当中动态运行的所有进程相关信息
在这里插入图片描述
每一个进程在系统运行期间终止在启动PID是变化的
在这里插入图片描述如果不想显示grep
在这里插入图片描述如果想终止一个进程
在这里插入图片描述

kill -9 pid:杀掉一个进程(也是终止一个进程)
为什么是-9呢?
使用kill命令可以列出当前系统所支持的信号集

在这里插入图片描述

查看PID唯一标识符对应的目录属性
在这里插入图片描述主要看cwd和exe
在这里插入图片描述

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

查看指定pid的进程文件:

如果想只查看这个目录我们可以:

[ling@iZ2zefqbzvwrp9dqb7s3h5Z lesson]$ ls /proc/

通过使用系统调用函数,getpid和getppid即可分别获取进程的PID和PPID。
在这里插入图片描述
当运行该代码生成的可执行程序后,循环打印该进程的pid和ppid。
在这里插入图片描述通过ps命令循环打印查看该进程的信息,即可发现通过ps命令得到的进程的pid和ppid与使用系统调用函数getpid和getppid所获取的值相同。
在这里插入图片描述

七、通过系统调用创建进程- fork初始

7.1 fork函数创建子进程

  • fork是一个系统调用级别的函数,其功能就是创建一个子进程。
  • fork有两个返回值

如下:

在这里插入图片描述
实际上,使用fork函数创建子进程,在fork函数被调用之前,代码被父进程执行,而fork函数之后的代码,则默认情况下父子进程都可以执行。

  • 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)

在这里插入图片描述

1.为什么fork给子进程返回0,给父进程返回子进程的pid?
因为返回不同的返回值,是为了区分让不同的执行流,执行不同的代码块。父进程是唯一的,子进程可以有很多个,父进程要拿到子进程的pid用来标记子进程的唯一性,而子进程调用getpid直接就能获取进程的pid

2.怎么理解一个函数返回两次?
在这里插入图片描述

fork是个函数且有返回值,由于代码共享执行return语句时父子进程各执行一次,这样fork函数就有两次返回值了。

2.fork代码层面

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

当调用fork后,创建子进程,后续的代码是共享的,父和子进程各自return形成两次返回,两次返回变量在接收时发生写时拷贝让父子进程在接收变量时发生不同的值,所以后续可以使用对接收的值进行if判断对父子进程进行分流,让父子进程执行不同的代码块

八、进程状态

8.1 课本给出的图(引入)

在这里插入图片描述就绪状态:进程已经准备好执行,但尚未被CPU分配时间片。处于就绪状态的进程在等待被调度器选中以便获得CPU资源。

运行状态:进程正在CPU上执行其指令。一个进程只有在运行状态下才能进行实际的工作。

阻塞状态:进程因为等待某些事件的发生(如I/O操作完成、获取锁等)而无法继续执行。处于阻塞状态的进程不会占用CPU资源。

8.1.1运行状态

所有处于运行状态,即可被调度的进程,都被放到运行队列当中,当操作系统需要切换进程运行时,就直接在运行队列中选取进程运行
在这里插入图片描述为了避免CPU被一个进程一直占用,例(死循环)。
每一个进程都有一个叫:时间片的概念:每个进程最多在CPU待XXX秒。
所以我们就看到了在一个时间段内,所有进程代码都会被执行——并发执行。
又有大量的把进程从CPU放上去,拿下来的动作——进程切换。

8.1.2 阻塞状态

在这里插入图片描述

8.2 Linux进程状态

一个进程从创建而产生至撤销而消亡的整个生命期间,有时占有处理器执行,有时虽可运行但分不到处理器,有时虽有空闲处理器但因等待某个时间的发生而无法执行,这一切都说明进程和程序不相同,进程是活动的且有状态变化的,于是就有了进程状态这一概念。

Linux操作系统kernel源代码当中对于进程状态有如下定义:

```
/*
* 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 *task_state_array[] = {
"R (running)",       /*  0*/
"S (sleeping)",      /*  1*/
"D (disk sleep)",    /*  2*/
"T (stopped)",       /*  4*/
"T (tracing stop)",  /*  8*/
"Z (zombie)",        /* 16*/
"X (dead)"           /* 32*/
};
```

进程的当前状态是保存到自己的进程控制块(PCB)当中的,在Linux操作系统当中也就是保存在task_struct当中的。
在Linux操作系统当中我们可以通过 ps aux 或 ps axj 命令查看进程的状态。

[ling@iZ2zefqbzvwrp9dqb7s3h5Z ~]$ ps aux

在这里插入图片描述

[ling@iZ2zefqbzvwrp9dqb7s3h5Z ~]$ ps axj    

在这里插入图片描述

8.2.1 运行状态-R

一个进程处于运行状态(running),并不意味着进程一定处于运行当中,运行状态表明一个进程要么在运行中,要么在运行队列里。也就是说,可以同时存在多个R状态的进程。

在这里插入图片描述可见虽然程序一直在运行还是S状态这是因为对于执行该一个进程来说CPU太快了外设太慢,处于CPU大多时间在等待外设(这里也就是我们的显示器)

如下设为死循环该进程一直占用着CPU,就为R状态了
在这里插入图片描述

R+:在前台运行
R:在后台运行

8.2.2 浅度睡眠状态-S

一个进程处于浅度睡眠状态(sleeping),意味着该进程正在等待某件事情的完成,处于浅度睡眠状态的进程随时可以被唤醒,也可以被杀掉(这里的睡眠有时候也可叫做可中断睡眠(interruptible sleep))。

在这里插入图片描述或者scanf函数,等待键盘输入数据,一直都处于等待状态也就是浅度睡眠。

8.2.3 深度睡眠状态-D

一个进程处于深度睡眠状态(disk sleep),表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的结束。

  • 不响应任何请求

例:某个进程对磁盘进行写入操作,那么在磁盘进行写入的期间,该进程处于深度睡眠,虽然该进程不是R状态操作系统也无法将该进程杀掉,因为改进等待磁盘的回复(写入是否成功)以做出对进程相应回复,进程在反馈给用户是否写入成功。

8.2.4 暂停状态-T

在Linux当中,我们可以通过发送SIGSTOP信号使进程进入暂停状态(stopped),发送SIGCONT信号可以让处于暂停状态的进程继续运行。

例如,我们对一个进程发送SIGSTOP信号,该进程就进入到了暂停状态。
在这里插入图片描述
这时候可以 kill -18 pid让它继续运行

8.2.5 死亡状态-X

死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态(dead)。

8.2.6 僵尸状态-Z

当一个进程将要退出的时候,在系统层面,该进程曾经申请的资源并不是立即被释放,而是要暂时存储一段时间,以供操作系统或是其父进程进行读取,如果退出信息一直未被读取,则相关数据是不会被释放掉的,一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态(zombie)。

简单的说就是子进程完成了自己的任务或是因异常而死掉,该子进程并不能直接结束释放资源,操作系统会保留该进程的状态等待父进程读取,等待期间就是僵尸状态。

例如,我们写代码时都在主函数最后返回0。
在这里插入图片描述
实际上这个0就是返回给操作系统的,告诉操作系统代码顺利执行结束。在Linux操作系统当中,我们可以通过使用echo $?命令获取最近一次进程退出时的退出码。
在这里插入图片描述

  • 进程退出的信息(例如退出码),是暂时被保存在其进程控制块当中的,在Linux操作系统中也就是保存在该进程的task_struct当中。

8.3 僵尸进程

相反父进程先退出
子进程的pid为1,1就是操作系统

一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态。而处于僵尸状态的进程,我们就称之为僵尸进程。
如下:子进程完成自己的任务进入Z状态
在这里插入图片描述

在这里插入图片描述
子进程一般退出时,如果父进程没有主动回收,内存资源一直被占用,造成内存泄漏
子进程信息会一直让自己处于Z状态
进程的相关信息资源尤其stack_struct结构体不能被释放

8.3.1僵尸进程的危害

  1. 僵尸进程的退出状态必须一直维持下去,因为它要告诉其父进程相应的退出信息。可是父进程一直不读取,那么子进程也就一直处于僵尸状态。
  2. 僵尸进程的退出信息被保存在task_struct(PCB)中,僵尸状态一直不退出,那么PCB就一直需要进行维护。
  3. 若是一个父进程创建了很多子进程,但都不进行回收,那么就会造成资源浪费,因为数据结构对象本身就要占用内存。
  4. 僵尸进程申请的资源无法进行回收,那么僵尸进程越多,实际可用的资源就越少,也就是说,僵尸进程会导致内存泄漏。

8.4 孤儿进程

在Linux当中的进程关系大多数是父子关系,若子进程先退出而父进程没有对子进程的退出信息进行读取,那么我们称该进程为僵尸进程。但若是父进程先退出,那么将来子进程进入僵尸状态时就没有父进程对其进行处理,此时该子进程就称之为孤儿进程。
若是一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程会被1号init进程领养,此后当孤儿进程进入僵尸状态时就由int进程进行处理回收。

如:父进程先退出

在这里插入图片描述

在这里插入图片描述

九、进程优先级

9.1 基本概念

优先级实际上就是获取某种资源的先后顺序,而进程优先级实际上就是进程获取CPU资源分配的先后顺序,就是指进程的优先权(priority),优先权高的进程有优先执行的权力。

优先级存在的原因?
优先级存在的主要原因就是资源是有限的,而存在进程优先级的主要原因就是CPU资源是有限的,一个CPU一次只能跑一个进程,而进程是可以有多个的,所以需要存在进程优先级,来确定进程获取CPU资源的先后顺序。

9.2 查看系统进程

在Linux或者Unix操作系统中,用ps -l命令会类似输出以下几个内容:

```powershell
[ling@iZ2zefqbzvwrp9dqb7s3h5Z test12]$ ps -l
```

在这里插入图片描述列出的信息当中有几个重要的信息,如下:

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

查看用户id
显示账号名称和编号
在这里插入图片描述

9.3 PRI与NI

  • PRI代表进程的优先级(priority),通俗点说就是进程被CPU执行的先后顺序,该值越小进程的优先级别越高。
  • NI代表的是nice值,其表示进程可被执行的优先级的修正数值。
  • PRI值越小越快被执行,当加入nice值后,将会使得PRI变为:PRI(new) = PRI(old) + NI。
  • 若NI值为负值,那么该进程的PRI将变小,即其优先级会变高。
  • 调整进程优先级,在Linux下,就是调整进程的nice值。
  • NI的取值范围是-20至19,一共40个级别。

注意: 在Linux操作系统当中,PRI(old)默认为80,即PRI = 80 + NI。

9.4 查看进程优先级信息

当我们创建一个进程后,可以使用ps -al命令查看该进程优先级的信息。

[ling@iZ2zefqbzvwrp9dqb7s3h5Z test12]$ ps -al

在这里插入图片描述

注意: 在Linux操作系统中,初始进程一般优先级PRI默认为80,NI默认为0。

9.5 通过top命令更改进程的nice值

top命令就相当于Windows操作系统中的任务管理器,它能够动态实时的显示系统当中进程的资源占用情况。
在这里插入图片描述
使用top命令后按“r”键,弹出输入待调整nice值的进程的PID。
在这里插入图片描述
输入进程PID并回车后,弹出提示输入调整后的nice值。
在这里插入图片描述
输入nice值按q退出,这里输入10,ps命令查看进程的优先级信息,即可发现进程的NI变成了10,PRI变成了90(80+NI)。
在这里插入图片描述注意: 若是想将NI值调为负值,也就是将进程的优先级调高,需要使用sudo命令提升权限。

9.6 renice命令更改进程的nice值

使用renice命令,后面跟上更改后的nice值和进程的PID即可。
在这里插入图片描述输入完后,用ps命令查看进程的优先级信息,也可以发现进程的NI变成了15,PRI变成了95(80+NI)。
在这里插入图片描述使用renice命令将NI值调为负值,也需要使用sudo命令提升权限。

9.7 四个重要概念

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便有了优先级。

  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。

  • 并行: 多个进程在多个CPU下分别同时进行运行,这称之为并行。

  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

前三个都比较容易理解

为了防止一个进程独占CPU,每个进程在CPU上运行是有时间限制的,每个进程在CPU最多只能运行XXX的时间,依次按队列排下去,称为时间片。
又防止一个进程的优先级较高该进程运行完在队列中总是排在靠前,所以Linux维护两个队列,一个队列是第一次待运行的进程,另一个队列是已经运行过一次,依次按队列拍下去。这称为并发也是进程的切换基于时间片轮转的调度算法

十、环境变量

10.1 基本概念

  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
  • 例如,我们编写的C/C++代码,在各个目标文件进行链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,并且在系统当中通常具有全局特性。

10.2 常见环境变量

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

10.3 查看环境变量方法

echo命令来查看环境变量,方式如下:

echo $NAME //NAME为待查看的环境变量名称

例:查看环境变量PATH

在这里插入图片描述

10.3.1 测试PATH

为什么执行ls命令的时候不用带./就可以执行,而我们自己生成的可执行程序必须要在前面带上./才可以执行?
在这里插入图片描述容易理解的是,要执行一个可执行程序必须要先找到它在哪里,既然不带./就可以执行ls命令,说明系统能够通过ls名称找到ls的位置,而系统是无法找到我们自己的可执行程序的,所以我们必须带上./,以此告诉系统该可执行程序位于当前目录下。

而系统就是通过环境变量PATH来找到ls命令的,查看环境变量PATH
在这里插入图片描述
如上环境变量PATH当中有多条路径,这些路径由冒号隔开,当你使用ls命令时,系统就会查看环境变量PATH,然后默认从左到右依次在各个路径当中进行查找。

而ls命令实际就位于PATH当中的某一个路径下,所以就算ls命令不带路径执行,系统也是能够找到的。

在这里插入图片描述如果让我们的指令也不加./直接使用

方法一:将可执行程序拷贝到环境变量PATH的某一路径下。
既然在未指定路径的情况下系统会根据环境变量PATH当中的路径进行查找,那我们就可以将我们的可执行程序拷贝到PATH的某一路径下,此后我们的可执行程序不带路径系统也可以找到了。

```powershell
[ling@iZ2zefqbzvwrp9dqb7s3h5Z test13]$ sudo cp proc /usr/bin
```

在这里插入图片描述

方式二:将可执行程序所在的目录导入到环境变量PATH当中。
export PATH=$PATH:mycmd程序所在路径。

将可执行程序所在的目录导入到环境变量PATH当中,这样就可以没有指定路径时系统就会来到该目录下进行查找了。
在这里插入图片描述拷贝到PATH路径后就可以不加./直接运行了
在这里插入图片描述

10.3.2 测试HOME

任何一个用户在运行系统登录时都有自己的主工作目录(家目录),环境变量HOME当中即保存的该用户的主工作目录。

用root和普通用户,分别执行 echo $HOME

  • 普通用户
    在这里插入图片描述
  • root
    在这里插入图片描述

10.3.3 测试SHELL

我们在Linux操作系统当中所输入的各种命令,实际上需要由命令行解释器进行解释,而在Linux当中有许多种命令行解释器(例如bash、sh),我们可以通过查看环境变量SHELL来知道自己当前所用的命令行解释器的种类。
在这里插入图片描述
而该命令行解释器实际上是系统当中的一条命令,当这个命令运行起来变成进程后就可以为我们进行命令行解释。
在这里插入图片描述

10.4 和环境变量相关的命令

  1. echo:显示某个环境变量的值。
  2. export:设置一个新的环境变量。
  3. 显示所有的环境变量。
    在这里插入图片描述
环境变量名称表示内容
PATH命令的搜索路径
HOME用户的主工作目录
用户的主工作目录当前Shell
HOSTNAME主机名
TERM终端类型
HISTSIZE记录历史命令的条数
SSH_TTY当前终端文件
USER当前用户
MAIL邮箱
LANG编码格式
LOGNAME登录用户名
  1. set:显示本地定义的shell变量和环境变量。
    在这里插入图片描述

  2. unset:清除环境变量。

10.5 环境变量的组织方式

环境变量是系统提供的一组name=value形式的变量,不同的环境变量有不同的用户,通常具有全局属性
在这里插入图片描述每个程序都会收到一张环境变量表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串,最后一个字符指针为空。

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

在有些教材会看到main函数这样的写法命令行参数
int main(int argc, char *argv[])
main函数其实有三个参数,只是我们平时基本不用它们,所以一般情况下都没有写出来。

  • main函数的前两个参数意思。

在Linux操作系统下,编写以下代码,生成可执行程序并运行。
在这里插入图片描述
运行当前可执行程序带选项
在这里插入图片描述

main函数的第二个参数是一个字符指针数组,数组当中的第一个字符指针存储的是可执行程序的位置,其余字符指针存储的是所给的若干选项,最后一个字符指针为空,而main函数的第一个参数代表的就是字符指针数组当中的有效元素个数。
在这里插入图片描述编写一个简单的代码,该代码运行起来后会根据你所给选项给出不同的提示语句。在这里插入图片描述

输入选项运行结果如下:
在这里插入图片描述

  • main函数的第三个参数

main函数的第三个参数接收的实际上就是环境变量表,我们可以通过main函数的第三个参数来获取系统的环境变量。
在这里插入图片描述除了使用main函数的第三个参数来获取环境变量以外,我们还可以通过第三方变量environ来获取。
在这里插入图片描述
其结果是一样的
在这里插入图片描述
注意: libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用extern进行声明。

10.7 通过系统调用获取环境变量

除了通过main函数的第三个参数和第三方变量environ来获取环境变量外,我们还可以通过系统调用getenv函数来获取环境变量。
getenv函数可以根据所给环境变量名,在环境变量表当中进行搜索,并返回一个指向相应值的字符串指针。
例如,使用getenv函数获取环境变量PATH的值
在这里插入图片描述
运行结果:
在这里插入图片描述

10.8 环境变量通常是具有全局属性的

当程序变成进程启动时,要调用main函数把两张核心向量表传进来,命令行参数表和环境变量表

  • 环境变量通常具有全局属性,可以被子进程继承下去

十一、进程地址空间

在学C语言的时候,大家应该都见过这样的空间布局图
在这里插入图片描述
通过Linux操作系统中以下代码对该布局图进行验证:
在这里插入图片描述

运行结果如下,与布局图所示是吻合的:在这里插入图片描述栈是向下生长,堆是向上生长意思是创建多个栈时第一个栈总是比第二个栈的地址小,依次类推,堆则反之,地址越来越大

看一下前面的fork函数

在这里插入图片描述

fork创建一个子进程,子进程g_val的值改为200,而父进程先休眠一秒等待子进程打印完,输出g_val的值并没有改变还是全局变量的100

运行如下
在这里插入图片描述

可以看到父进程打印的全局变量g_val的值仍为之前的100,更奇怪的是在父子进程中打印的全局变量g_val的地址是一样的,也就是说父子进程在同一个地址处读出的值不同。

我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
  • 但地址值是一样的,说明该地址绝对不是物理地址
  • 在Linux地址下,这种地址叫做虚拟地址
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理OS必须负责将虚拟地址转化成物理地址
    在这里插入图片描述

虚拟地址和物理地址之间的转化由操作系统完成。

小结:一个进程要在CPU运行要经过的阶段,1.先描述PCB的属性再对应将我们的代码和数据运行,只不过前面我们学的代码和数据不够全面,其实对应的是进程地址空间。2.再组织接着展开进程地址空间阶段,由于我们的语言编成的这些代码都是虚拟地址而我们要读取到的是物理地址的内容,这个是通过页表来将所谓的虚拟地址映射到物理地址进行读取内容。而如fork这种函数产生的写时拷贝(修改值时操作系统重新在物理地址内存开辟一段空间进行修改)在我们看到同一个地址(虚拟地址)发生两个不一样的值,其实就是父子进程各自有自己的页表,其中页表中的虚拟地址相同,但两虚拟地址转换的物理地址不同,而物理地址我们是没办法查看的。

11.1 地址空间是什么?如何理解地址空间的区域划分?

地址空间引入:进程访问地址空间转换页表访问物理内存前提是当前进程正在运行。当一个进程在CPU运行时,要访问对应的内存时一定要知道该进程在内存中访问的是哪一块地址,CPU根据它的地址总线来访问对应的物理内存。
32位计算机中有32位的地址和数据总线,冯诺依曼里是一个个独立的设备,所有设备要进行通信就要用线连起来,宏观上分为三类线:地址总线、数据总线、控制总线。CPU和内存连起来的线叫系统总线,内存和外设连起来的叫IO总线,按我们32位计算机的32根的地址总线,每一根地址总线只有0、1,组合起来一共就有2^32 种,内存寻址的基本单位是字节,就注定了内存的大小:2^32 *1byte=4GB,这就是32位的计算机最多能装4GB的内存空间。
所以地址空间是地址总线排列组合形成的地址范围[0,2^32]也就是能访问物理内存的最大范围。

地址空间区域划分引入:
在这里插入图片描述比如经济学类书可以为start=90;end=120,在范围内,连续的空间中,每一个最小单位都可以有地址。

地址空间区域划分:本质是描述进程可视范围的大小,存在各种区域划分对线性地址进行start和end,地址空间内核的一个数据结构对象,类似于PCB一样,地址空间也要被操作系统管理:先描述再组织。

进程地址空间就类似于一把尺子,尺子的刻度由0x00000000到0xffffffff,尺子按照刻度被划分为各个区域,例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个边界刻度,例如代码区的开始刻度与结束刻度,如下图所示:
在这里插入图片描述在结构体mm_struct当中,各个边界刻度之间的每一个刻度都代表一个虚拟地址,这些虚拟地址通过页表映射与物理内存建立联系。由于虚拟地址是由0x00000000到0xffffffff线性增长的,所以虚拟地址又叫做线性地址。

11.2 为什么要有进程地址空间?

本来进程可以直接访问物理内存现在多了进程地址空间和页表,是更麻烦吗?肯定不是的,增加进程虚拟地址空间可以让我们访问内存时,增加一个转换的过程,这个转换过程中,可以对我们寻址请求进行审查,所以一旦异常访问,直接拦截,该请求不会到达物理内存,从而保护物理内存。

每个进程被创建时,其对应的进程控制块(task_struct)和进程地址空间(mm_struct)也会随之被创建。而操作系统可以通过进程的task_struct找到其mm_struct,因为task_struct当中有一个结构体指针存储的是mm_struct的地址。
例如,父进程有自己的task_struct和mm_struct,该父进程创建的子进程也有属于其自己的task_struct和mm_struct,父子进程的进程地址空间当中的各个虚拟地址分别通过页表映射到物理内存的某个位置。
如图:
在这里插入图片描述
而当子进程刚刚被创建时,子进程和父进程的数据和代码是共享的,即父子进程的代码和数据通过页表映射到物理内存的同一块空间。只有当父进程或子进程需要修改数据时,才将父进程的数据在内存当中拷贝一份,然后再进行修改。
在这里插入图片描述这种在需要进行数据修改时再进行拷贝的技术,称为写时拷贝技术

1.为什么数据要进行写时拷贝?

进程具有独立性。多进程运行,需要独享各种资源,多进程运行期间互不干扰,不能让子进程的修改影响到父进程。

2.为什么不在创建子进程的时候就进行数据的拷贝?

子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延时分配),这样可以高效的使用内存空间。

3、代码会不会进行写时拷贝?

90%的情况下是不会的,但这并不代表代码不能进行写时拷贝,例如在进行进程替换的时候,则需要进行代码的写时拷贝。

为什么要有进程地址空间?

1、有了进程地址空间后,就不会有任何系统级别的越界问题存在了。例如进程1不会错误的访问到进程2的物理地址空间,因为你对某一地址空间进行操作之前需要先通过页表映射到物理内存,而页表只会映射属于你的物理内存。总的来说,虚拟地址和页表的配合使用,本质功能就是包含内存。
2、有了进程地址空间后,每个进程都认为看得到都是相同的空间范围,包括进程地址空间的构成和内部区域的划分顺序等都是相同的,这样一来我们在编写程序的时候就只需关注虚拟地址,而无需关注数据在物理内存当中实际的存储位置。
3、有了进程地址空间后,每个进程都认为自己在独占内存,这样能更好的完成进程的独立性以及合理使用内存空间(当实际需要使用内存空间的时候再在内存进行开辟),并能将进程调度与内存管理进行解耦或分离。

对于创建进程的现阶段理解:
一个进程的创建实际上伴随着其进程控制块(task_struct)、进程地址空间(mm_struct)以及页表的创建。
进程=内核数据结构(tack_struct && mm _struct&& 页表)+ 程序的代码和数据

十二、inux2.6内核进程调度队列

在这里插入图片描述

12.1 一个CPU拥有一个runqueue

如果有多个CPU就要考虑进程个数的父子均衡问题。

12.2 优先级

queue下标说明:

  • 普通优先级:100~139。
  • 实时优先级:0~99。

我们进程的都是普通的优先级,前面说到nice值的取值范围是-20~19,共40个级别,依次对应queue当中普通优先级的下标100 ~ 139。
注意: 实时优先级对应实时进程,实时进程是指先将一个进程执行完毕再执行下一个进程,现在基本不存在这种机器了,所以对于queue当中下标为0~99的元素我们不关心。

12.3 活动队列

时间片还没有结束的所有进程都按照优先级放在活动队列当中,其中nr_active代表总共有多少个运行状态的进程,而queue[140]数组当中的一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进程排队调度。

调度过程如下:

  1. 从0下标开始遍历queue[140]。
  2. 找到第一个非空队列,该队列必定为优先级最高的队列。
  3. 拿到选中队列的第一个进程,开始运行,调度完成。
  4. 接着拿到选中队列的第二个进程进行调度,直到选中进程队列当中的所有进程都被调度。
  5. 继续向后遍历queue[140],寻找下一个非空队列。

bitmap[5]:queue数组当中一共有140个元素,即140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5 × \times × 32个比特位表示队列是否为空,这样一来便可以大大提高查找效率。
小结: 在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不会随着进程增多而导致时间成本增加,我们称之为进程调度的O(1)算法。

12.4 过期队列

  1. 过期队列和活动队列的结构相同。
  2. 过期队列上放置的进程都是时间片耗尽的进程。
  3. 当活动队列上的进程被处理完毕之后,对过期队列的进程进行时间片重新计算。

12.5 active指针和expired指针

  • active指针永远指向活动队列。
  • expired指针永远指向过期队列。

由于活动队列上时间片未到期的进程会越来越少,而过期队列上的进程数量会越来越多(新创建的进程都会被放到过期队列上),那么总会出现活动队列上的全部进程的时间片都到期的情况,这时将active指针和expired指针的内容交换,就相当于让过期队列变成活动队列,活动队列变成过期队列,就相当于又具有了一批新的活动进程,如此循环进行即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/12626.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Springboot框架的学术期刊遴选服务-项目演示

项目介绍 本课程演示的是一款 基于Javaweb的水果超市管理系统,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3.该项目附…

2. K8S集群架构及主机准备

本次集群部署主机分布K8S集群主机配置主机静态IP设置主机名解析ipvs管理工具安装及模块加载主机系统升级主机间免密登录配置主机基础配置完后最好做个快照备份 2台负载均衡器 Haproxy高可用keepalived3台k8s master节点5台工作节点(至少2及以上)本次集群部署主机分布 K8S集群主…

Linux第105步_基于SiI9022A芯片的RGB转HDMI实验

SiI9022A是一款HDMI传输芯片,可以将“音视频接口”转换为HDMI或者DVI格式,是一个视频转换芯片。本实验基于linux的驱动程序设计。 SiI9022A支持输入视频格式有:xvYCC、BTA-T1004、ITU-R.656,内置DE发生器,支持SYNC格式…

物联网领域的MQTT协议,优势和应用场景

MQTT(Message Queuing Telemetry Transport)作为轻量级发布/订阅协议,凭借其低带宽消耗、低功耗与高扩展性,已成为物联网通信的事实标准。其核心优势包括:基于TCP/IP的异步通信机制、支持QoS(服务质量&…

Baklib推动数字化内容管理解决方案助力企业数字化转型

内容概要 在当今信息爆炸的时代,数字化内容管理成为企业提升效率和竞争力的关键。企业在面对大量数据时,如何高效地存储、分类与检索信息,直接关系到其经营的成败。数字化内容管理不仅限于简单的文档存储,更是整合了文档、图像、…

PAT甲级1052、Linked LIst Sorting

题目 A linked list consists of a series of structures, which are not necessarily adjacent in memory. We assume that each structure contains an integer key and a Next pointer to the next structure. Now given a linked list, you are supposed to sort the stru…

18 大量数据的异步查询方案

在分布式的应用中分库分表大家都已经熟知了。如果我们的程序中需要做一个模糊查询,那就涉及到跨库搜索的情况,这个时候需要看中间件能不能支持跨库求交集的功能。比如mycat就不支持跨库查询,当然现在mycat也渐渐被摒弃了(没有处理笛卡尔交集的…

UE求职Demo开发日志#21 背包-仓库-装备栏移动物品

1 创建一个枚举记录来源位置 UENUM(BlueprintType) enum class EMyItemLocation : uint8 {None0,Bag UMETA(DisplayName "Bag"),Armed UMETA(DisplayName "Armed"),WareHouse UMETA(DisplayName "WareHouse"), }; 2 创建一个BagPad和WarePa…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.22 形状操控者:转置与轴交换的奥秘

1.22 形状操控者:转置与轴交换的奥秘 目录 #mermaid-svg-Qb3eoIWrPbPGRVAf {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Qb3eoIWrPbPGRVAf .error-icon{fill:#552222;}#mermaid-svg-Qb3eoIWrPbPGRVAf…

Redis - String相关命令

目录 setgetmsetmgetsetnx、setex、psetexincr、incrby、decr、decrby、incrbyfloatappendgetrangesetrangestrlen字符串类型编码方式总结 Redis - String Redis存储的字符串,是直接按二进制方式存储,不会做任何编码转换,存的是什么&#xff…

html基本结构和常见元素

html5文档基本结构 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>文档标题</title> </head> <body>文档正文部分 </body> </html> html文档可分为文档头和文档体…

LabVIEW的智能电源远程监控系统开发

在工业自动化与测试领域&#xff0c;电源设备的精准控制与远程管理是保障系统稳定运行的核心需求。传统电源管理依赖本地手动操作&#xff0c;存在响应滞后、参数调节效率低、无法实时监控等问题。通过集成工业物联网&#xff08;IIoT&#xff09;技术&#xff0c;实现电源设备…

33.Word:国家中长期人才发展规划纲要【33】

目录 NO1.2样式​ NO3​ 图表 ​ NO4.5.6​ 开始→段落标记视图→导航窗格→检查有无遗漏 NO1.2样式 F12/另存为&#xff1a;Word.docx&#xff1a;考生文件夹样式的复制样式的修改 样式的应用&#xff08;没有相似/超级多的情况下&#xff09;——替换 [ ]通配符&#x…

图漾相机——Sample_V1示例程序

文章目录 1.SDK支持的平台类型1.1 Windows 平台1.2 Linux平台 2.SDK基本知识2.1 SDK目录结构2.2 设备组件简介2.3 设备组件属性2.4 设备的帧数据管理机制2.5 SDK中的坐标系变换 3.Sample_V1示例程序3.1 DeviceStorage3.2 DumpCalibInfo3.3 NetStatistic3.4 SimpleView_SaveLoad…

Java集合框架

欢迎并且感谢大家指出我的问题&#xff0c;由于本人水平有限&#xff0c;有些内容写的不是很全面&#xff0c;只是把比较实用的东西给写下来&#xff0c;如果有写的不对的地方&#xff0c;还希望各路大牛多多指教&#xff01;谢谢大家&#xff01;&#x1f970; 一、引言 在 Ja…

DeepSeek最新图像模型Janus-Pro论文阅读

目录 论文总结 摘要 1. 引言 2. 方法 2.1 架构 2.2 优化的训练策略 2.4 模型扩展 3. 实验 3.1 实施细节 3.2 评估设置 3.3 与最新技术的比较 3.4 定性结果 4. 结论 论文总结 Janus-Pro是DeepSeek最新开源的图像理解生成模型&#xff0c;Janus-Pro在多模态理解和文…

Python爬虫:1药城店铺爬虫(完整代码)

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

【蓝桥杯】日志统计

日志统计&#xff08;编程题&#xff09;https://dashoj.com/d/lqbproblem/p/53https://dashoj.com/d/lqbproblem/p/53https://dashoj.com/d/lqbproblem/p/53 题目 日志统计(编程题) 讲解 这个讲解感觉比较通俗易懂。 蓝桥杯2018年省赛B组08&#xff08;c/c&#xff09;日…

一文讲解Spring中应用的设计模式

我们都知道Spring 框架中用了蛮多设计模式的&#xff1a; 工厂模式呢&#xff0c;就是用来创建对象的&#xff0c;把对象的创建和使用分开&#xff0c;这样代码更灵活。代理模式呢&#xff0c;是用一个代理对象来控制对真实对象的访问&#xff0c;可以在访问前后做一些处理。单…

AI 算力瓶颈,硬件、算法、共享能否破局?

随着AI技术的蓬勃发展&#xff0c;它已经实现了从实验室走向千行百业&#xff0c;然而在它想要继续深入地探索各行各业&#xff0c;解锁更多应用场景时&#xff0c;算力却成为了它面前的一道关卡。 这道关卡由无数需要处理的数据和计算任务堆积而成&#xff0c;想要实现AI技术…