Linux进程概念(万字详解)

目录

一.基本概念

二.描述进程-PCB

1.task_struct-PCB的一种

2.task_struct内容分类

三.查看进程

1.查看系统目录下面的进程 

 2.查看具体的进程文件

 3.ps命令与grep命令搭配使用

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

1.通过系统调用获取进程的PID和PPID                     

2.fork创建子进程 

3.通过if判断实现进程分流

 五.Linux进程状态 

1.具体操作系统,Linux操作系统的源代码当中对于进程状态有如下定义

2.运行状态-R

3.浅度睡眠 - S

4.深度睡眠状态-D

5.暂停状态-T

6.死亡状态-X

7.僵尸状态-Z 

8.孤儿进程 

六.进程优先级 

1.基本概念

2.查看系统进程

3.PRI VS NI

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

5.通过renice命令更改进程的nice值

七.其他概念 

八.环境变量

1.基本概念

2.常见环境变量 

3.查看环境变量的方法 

4.测试PATH

补充:

5.和环境变量相关的命令 

6.环境变量组织方式

7.通过代码获取环境变量

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

九.程序地址空间

1.通过代码感受 

2. 关于相同地址却有不同的值现象

十.进程地址空间

1.基本概念

 2.关于地址空间不同区域的划分

3.将task_struct ,mm_struct , 页表 ,物理内存联系起来

十一.Linux2.6内核进程调度队列 

1.一个CPU拥有一个runqueue

2.优先级

3.活动队列

4.过期队列

5.active指针和expired指针


一.基本概念

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

代码经过编译链接后便会生成一个可执行程序,这个可执行程序本质上是一个文件,是放在磁盘上的;当我们双击这个可执行程序将其运行起来时,本质上是OS将这个程序加载到内存当中了,因为CPU只能和内存进行数据交互,而一旦将这个程序加载到内存后,它就不叫程序了应该叫做进程。

                

                

                        

二.描述进程-PCB

系统允许多个进程同时运行,OS需要对进程进行管理,如何管理进程?
先描述,再组织; 之后对该进程的管理实际上就是对其描述信息的管理 

文件=内容+属性
进程=对应的文件+进程属性
进程=可执行程序+内核的数据结构(管理)
进程:可执行程序与管理进程需要的数据结构的集合

描述进程的数据结构,就是一批结构体对象

                                 

1.task_struct-PCB的一种

  • PCB实际上是对进程控制块的统称,在Linux中描述进程的结构体叫做task_struct。
  • task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含进程的信息。

                                         

2.task_struct内容分类

task_struct结构中当中主要包含以下信息:

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

 (1)标识符 : pid , ppid

                               

(2) 状态

 ①当前进程正在运行,为什么显示的是休眠状态(s)? (本程序每隔一秒打印一句话)
查看进程状态只是查询这一瞬间的状态,因为代码里面有sleep,查询的时候可能99%的可能是休眠,0.01%运行,所以大部分查询的时候是休眠s。

                                 

②去掉sleep,还是s状态?
这个代码看起来是死循环一直在疯狂打印,但是程序里面使用了printf,是往显示器上面打印,外设是很慢的,而我们让CPU执行printf函数是很快的,意味着OS调度这个进程的时候大部分在休眠,等待外设把信息打印出来,主要矛盾不是CPU把printf数据往缓冲区里面写,而是外设把缓冲区的内容打印到显示器,刷新的过程太慢了,所以大部分查询的状态时s

                                                         

(3) 优先级

①什么是优先级: 竞争某种资源,谁先,谁后拿到这种资源

②为什么要有优先级:资源有限!CPU资源是有限的,进程是有多个的,进程运行需要优先级管理.

③优先级 vs 权限 

权限是能/不能 ,如果能,谁先谁后的问题(优先级)。

④如何理解进程排队: 

OS将每一个进程都进行描述,形成了一个个的进程控制块(PCB),并将这些PCB以双链表的形式组织起来

                                         

(4)程序计数器 

程序计数器PC,EIP : 进程需要在切换的时候,要保存该数据(保存是为了恢复,OS下一次调度本进程CPU要知道执行到哪一条指令了)

                                 

(5)上下文数据

每个运行的进程,都有自己的“时间片” ,进程调度是基于时间片的轮转。


①. CPU内部只有一套寄存器,计算需要将内存数据移动到CPU内的寄存中(一份拷贝),形成当前进程的上下文数据 

②.进程被切换,可能在任何时间点(时间片到了 / 当前进程被抢占)
③.如果进程直接走了,下一次,该如何回来呢? (当兵的例子:应征入伍,保留学籍,都是为了恢复!)
恢复上下文数据

④上下文被保存到哪里??
保存到进程控制块里面(task_struct)(不太准确!) , 但是好理解!

⑤寄存器是所以进程共享的,但是寄存器上的数据是属于进程本身的,所以进程被切换下去之后需要保存上下文数据
                                                

(6)前台进程和后台进程 

①死循环的时候其他命令没有作用
Linux中只有一个前台进程,后台进程可以有多个且后台进程不影响命令行的输入 

                                         

②进程放到后台

                                                

                                

三.查看进程

1.查看系统目录下面的进程 

 在根目录下有一个名为proc的系统文件夹 , 文件夹当中包含大量进程信息,其中有些子目录的目录名为数字 , 这些数字其实是某一进程的PID,对应文件夹当中记录着对应进程的各种信息

                                 

 2.查看具体的进程文件

                         

 3.ps命令与grep命令搭配使用

[gsx@VM-0-2-centos ~]$  ps aux | head -1 && ps aux | grep myproc | grep -v grep

                

                

                

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

1.通过系统调用获取进程的PID和PPID

补充: 

 

                        

2.fork创建子进程 

 (1)fork之后有两个进程

                                        

(2)fork函数创建的子进程的代码和数据从何而来

 ①fork创建子进程,fork之前的代码,被父进程执行。fork之后的代码,默认情况被父子都可以执行        

② 如何理解进程创建 ?    创建进程,是系统多了一个进程,,多一个进程系统就要多一组管理进程的数据结构+该进程对应的代码和数据

③fork()创建子进程,OS做了什么系统内存多了一个进程,先描述在组织,OS重新创建一个pcb

④子进程执行的代码和数据来自哪里?父进程

父进程创建子进程的时候,代码是共享的,数据是各自私有一份(写时拷贝),代码是逻辑,一般是不可被修改的。数据,既可读又可写,通过数据私有,表现进程独立性!

⑤fork之后谁先运行不一定,取决于OS的调度算法。

                                                

3.通过if判断实现进程分流

通过返回值的不可以使父子进程各自做不同的事情


fork函数的返回值:
① 如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
② 如果子进程创建失败,则在父进程中返回 -1。

                        

                        

                         

 五.Linux进程状态 

1.具体操作系统,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 *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*/
};

                         

2.运行状态-R

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

② 如果只有1个CPU,可不可以同时存在多个R状态的进程 ? 绝对可以

  • 进程是R状态不代表正在运行!代表可被调度!
  • 只有是R状态的进程才有可能被调度进程 - 面试
  • 所有处于R状态的队列都会被链接起来,形成一个运行队列,CPU只需要在这个队列中选择就行(FIFO)

                                

3.浅度睡眠 - S

①S: 通常用来等待某种事件发生(这里的睡眠有时候也叫做可中断睡眠 )。

②浅度睡眠体现在进程可以随时被唤醒,也可以被杀掉

                        

4.深度睡眠状态-D

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

②例如:某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。

               

                                

5.暂停状态-T

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

①我们对一个进程发送SIGSTOP信号,该进程就进入到了暂停状态。

# 程序收到SIGSTOP命令停了下来

                                                                                 

②发送SIGCONT信号,该进程就继续运行了 

                

6.死亡状态-X

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

7.僵尸状态-Z 

 ①当一个进程将要退出的时候,在系统层面,该进程曾经申请的资源并不是立即被释放,而是要暂时存储一段时间,以供操作系统或是其父进程进行读取。

②如果退出信息一直未被读取,则相关数据是不会被释放掉的,一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态(zombie),该进程为僵尸进程。

③僵尸状态的存在是必要的,因为进程被创建的目的就是完成某项任务,那么当任务完成的时候,调用方是应该知道任务的完成情况的,所以必须存在僵尸状态,使得调用方得知任务的完成情况,以便进行相应的后续操作。

④为什么要有僵尸状态? 保持进程基本退出信息,方便父进程读取,获得退出原因

⑤如何判断进程退出后的任务完成情况?   退出码   

//echo  $? 获取最近一次进程退出的退出码

                                                         

⑥模拟僵尸进程 

(1)该代码: fork函数创建的子进程在打印5次信息后会退出,而父进程会一直打印信息。也就是说,子进程退出了,父进程还在运行,但父进程没有读取子进程的退出信息,那么此时子进程就进入了僵尸状态。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>

int main()
{
   printf("I am running ....\n");

    pid_t id = fork();
    if(id == 0){ //child
        int count = 5;
        while(count){
            printf("I am child , pid: %d, ppid: %d , count:%d\n", getpid(), getppid(),count);
            sleep(1);
            count--;
        }

        printf("child quit ....\n"); //5s 
        exit(1);
    }
    else if(id > 0){ //father
        while(1){
            printf("I am father,pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
        }

        exit(0);
    }
    else{
        //do nothing
    }

    return 0;
}

 (2)结果

                                                

⑦shell监视脚本

[gsx@VM-0-2-centos 220608]$ while :; do ps aux | head -1 && ps aux | grep myproc | grep -v grep ; echo "----------" ; sleep 1 ;done

                 

                                

 ⑧僵尸进程的危害

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

                                

8.孤儿进程 

①但若是父进程先退出,那么将来子进程进入僵尸状态时就没有父进程对其进行处理,此时该子进程就称之为孤儿进程。

②若是一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程会被1号init进程领养,由init进程回收

③Linux中所有的进程都有爹,除了1号
 

④孤儿进程模拟

(1)fork函数创建的子进程会一直打印信息,而父进程在打印5次信息后会退出,此时该子进程就变成了孤儿进程。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>

int main()
{
   printf("I am running ....\n");

    pid_t id = fork();
    if(id == 0){
        //child
        while(1){
            printf("I am child,pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
        }

        exit(0);
    }
    else if(id > 0){
        //father
        int count = 5;
        while(count){
            printf("I am father , pid: %d, ppid: %d , count:%d\n", getpid(), getppid(),count);
            sleep(1);
            count--;
        }

        printf("father quit ....\n"); //5s 
        exit(1);
    }
    else{
        //do nothing
    }

return 0;
}

 (2)结果:

                

                          

                                      

六.进程优先级 

1.基本概念

(1)什么是优先级?

优先级实际上就是获取某种资源(CPU ,IO)的先后顺序,而进程优先级实际上就是进程获取CPU资源分配的先后顺序。

(2)为什么要有优先级?

当被管理的对象多的时候,资源有限,进程多就要涉及仲裁,先让谁用,后让谁用,就产生了优先级概念。

                                

2.查看系统进程

在Linux或者Unix操作系统中,用ps -l命令查看

[gsx@VM-0-2-centos 220610]$ ps -l

 其中的几个重要信息,有下:

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

                                                

3.PRI VS NI

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

注意: 在Linux操作系统当中,PRI(old)默认为80, NI默认为0,即PRI(new) = 80 + NI,与上一次的PRI没有关系
                                

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

命令 : top
进入top后按“r”–>输入进程PID–>输入nice值 (没什么特殊情况,建议不要改)

(1)top命令就相当于Windows操作系统中的任务管理器,它能够动态实时的显示系统当中进程的资源占用情况 ,时刻在变化的。

                                                

(2)更改nice值 

 注意: 若是想将NI值调为负值,也就是将进程的优先级调高,需要使用sudo命令提升权限。

5.通过renice命令更改进程的nice值

使用: renice  值  进程ID(如果是负数要sudo)

                       

                                 

七.其他概念 

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

                        

                                

八.环境变量

1.基本概念

  • 什么叫做环境变量?    环境变量是与OS相关的一组变量,只要是变量就一定有变量名和变量内容,只不过这组变量是由OS帮我们生成,供我们在特定场景下去使用
  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数  如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

                                                

2.常见环境变量 

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

                                        

3.查看环境变量的方法 

 echo $NAME   //NAME:你的环境变量名称

                                

关于shell  : 通过查看环境变量SHELL来知道自己当前所用的命令行解释器的种类

命令行解释器实际上是系统当中的一条命令,当这个命令运行起来变成进程后就可以为我们进行命令行解释

               

                                                

4.测试PATH

补充:

系统当中我们执行某些命令的时候,本质是环境变量在给我们做相关的影响

  • 1.我们写的程序要运行为什么要带./ ?   告诉OS要执行的程序就在当前目录下面
  • 2.ls等命令为什么不需要带?   ls在执行时,OS能够找到ls命令在哪
  • 3.OS如何找到ls命令?    环境娈量

                                                 

(1)每个路径由:隔开,Linux在执行ls命令时/在执行任何命令时都会默认从左向右依次在各个路径下对ls程序的查找

                                                 

(2)为什么执行任何命令都不用带路径?

根本原因是我们有一个环境变量叩PATH,是系统启动的时候我们的系统自动生成的一个环境变量,它里面包含了多条路径;所以我们在执行命令的时候要先找到命令,它会默认在PATH变量的多条路径当中依次查找,找到之后就执行对应的特定可执行程序,把它加载到内存,就跑起来了。


我也想执行程序的时候不在使用./,怎么做?

  • 方法1: 把可执行程序拷贝到 /usr/bin目录下面

这种做法不推荐,系统当中存在各种各样的软件,你把你的程序拷贝到系统默认的可执行程序路径下会污染别人写好的工具集 ; 污染命令池

                                

  •  方法2:  将可执行程序所在的目录导入到环境变量PATH当中,这样一来,没有指定路径时系统就会来到该目录下进行查找了


 关于清空PATH

1.path就是个变量,你把它改了没有问题,虽然你把它改了当前无法使用,但是你重新登录之后就又可以使用了。
⒉.重新登录后OS为你重新生成,这个PATH的路径本质是在系统的配置文件中写的,不用担心

                                                

5.和环境变量相关的命令 

(1) echo: 显示某个环境变量值

                        

(2) export: 设置一个新的环境变量

                                        

(3) env: 显示所有环境变量

                                

(4)set: 显示本地定义的shell变量和环境变量      unset: 清除环境变量

                                        

(5)终端之间通信 

                                

6.环境变量组织方式

每个程序都会收到一张环境变量表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境变量字符串,最后一个字符指针为空 ,作为结束标志。

                                        

7.通过代码获取环境变量

mainret = main( argc, argv, envp);  //main函数也是被调用的,调用的时候会传递三个参数

(1)命令行的前两个参数

①获取命令行参数

解释 : main函数的第二个参数是一个字符指针数组,数组当中的第一个字符指针存储的是可执行程序的路径,其余字符指针存储的是所给的若干选项,最后一个字符指针为空为结束标志,而main函数的第一个参数代表的就是字符指针数组当中的有效元素个数。 

                                 

②测试用例

#include<stdio.h>
#include<unistd.h>
#include<string.h>

int main(int argc ,char* argv[],char* envp[])
{
  if(argc == 2)
  {
    if(strcmp(argv[1],"-a") == 0){
      printf("hello world \n");
    }
    else if(strcmp(argv[1],"-b") == 0){
      printf("hello XiaoMi \n");
    }
    else {
      printf("defalut !\n");
    }
  }
  
  printf("argc : %d\n",argc);
}

 

                                

(2)命令行的第三个参数 

①方法1 : 通过第三个参数获取环境变量

#include<stdio.h>

int main(int argc ,char* argv[],char* envp[])
{
   int i =0;                                                                                   
   while(envp[i] != NULL){
      printf("envp[%d]: %s\n", i ,envp[i]);
      ++i;
   }

   return 0;
}

                        

②方法2 

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

int main(int argc ,char* argv[],char* envp[])
{
   extern char** environ;
   int i =0;                                                                                   
   while(environ[i] != NULL){
      printf("envp[%d]: %s\n", i ,environ[i]);
      ++i;
   }

   return 0;
}

                                

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

getenv函数可以根据所给环境变量名,在环境变量表当中进行搜索,并返回一个指向相应值的字符串指针

#include <stdio.h>
#include <stdlib.h>
int main()
{
 printf("%s\n", getenv("PATH"));
 return 0;
}

                        

                 

九.程序地址空间

1.通过代码感受 

 结果:

                        

2. 关于相同地址却有不同的值现象

(1)代码1: 父子进程代码共享,读取到同一个值,地址相同没有问题。

                                 

(2)代码2: 子进程中更改g_val 的值 ,父子进程中g_val值不同,但是地址相同

 

 理解:

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

②如果说我们是在同一个物理地址处获取的值,那必定是相同的,而现在在同一个地址处获取到的值却不同,这只能说明我们打印出来的地址绝对不是物理地址!!!

③实际上,我们在语言层面上打印出来的地址都不是物理地址,而是虚拟地址。物理地址用户一概是看不到的,是由操作系统统一进行管理的。

④OS负责将虚拟地址转化为物理地址
 

                

                                

十.进程地址空间

1.基本概念

①什么是地址空间? 本质是用来描述进程所占有资源的相关的一张表,这个地址空间在内核当中就是一个数据结构,这个结构将我们的内存空间划分成了若干层区域

②进程地址空间本质是内存中的一种内核数据结构, struct  mm_struct 。

③每一次申请一个新进程的时候,OS还会为当前新进程创建一个属于该进程的地址空间mm_struct

                                                         

 2.关于地址空间不同区域的划分

 如果想要扩大区域,只需要修改结构体中的特定字段就行 ;32位机器地址从0x00000000->0xffffffff.

                                        

3.将task_struct ,mm_struct , 页表 ,物理内存联系起来

重新理解地址相同,值不同: 写时拷贝

(1)为什么地址相同?
子进程刚创建出来时,子进程的映射关系是来源于父进程,没有进行写入操作时,父子访问的虚拟地址是一样的,打印出来的数字也是—样的 ; 子进程写入后,进行父子进程数据独立,os在底层重新给子进程开辟空间,重新构建映射关系。此时父进程不受影响,只影响子进程单独开辟物理空间。虚拟地址是不改变的,改变的只是虚拟地址->物理地址的映射关系。 

 (2)地址空间,是对物理内存的一种虚拟化表示,虚拟空间,最终一定要以某种方式转化到物理内存。

(3)进程运行时具有独立性的!在数据层面上先进行独立!

(4)什么叫做创建进程?  创建task_struct ,创建mm_struct ,创建页表

深入理解进程 : 一堆数据结构再加代码数据的集合称为进程
 

(5)申请空间的本质是:向内存索要空间,得到物理地址,然后在特定区域申请没有被使用的虚拟地址,建立映射关系,返回虚拟地址。

(6)为什么要有地址空间? 

1.从此以后,不会再有任何系统级别的越界问题存在(物理内存)

①任何地址,都要经过转换才能访问物理地址

②页表不会给你映射除此进程之外的地址空间(虚拟地址空间+ 页表:保护内存)

2.每个进程都认为看到的是相同的空间范围(构成,顺序)

3.每个进程都认为自己在独占内存,更好的完成进程独立性以及合理使用空间(将进程调度和内存管理进行解耦/分离)

4.存在很多情况,一个进程的数据存放的内存位置是不连续的(一个进程在运行时可能数据增加,造成原来的空间不够用),造成访问特别不方便,增加了异常越界的概率。

                                

(7)关于越界,非法访问内存

 ①—个进程如果越界了,非法访问别人的空间存不存在?NO,一个进程只会映射自己的数据,如果越界访问到别人空间,在页表中没有这个映射关系,就被OS终止了。

②—个进程越界自己的空间,对于字符常量区的字符串进行修改,虽然页表有这层映射,但是页表的权限管理认为这个空间只有可读权限,但是你写入操作,直接就崩溃了。

③所以有时候C/C++程序越界了,有时崩溃有时并没有。这和越界的性质有关系:与越界后的地址权限有关。
                                

 (8)可执行程序经过编译后分段

                                 

(9)形象化举例 

                 

                         

                        

十一.Linux2.6内核进程调度队列 

 

1.一个CPU拥有一个runqueue

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

                 

2.优先级

queue下标说明:

普通优先级:100~139。
实时优先级:0~99。
我们进程的都是普通的优先级,前面说到nice值的取值范围是-20~19,共40个级别,依次对应queue当中普通优先级的下标100~139。

注意: 实时优先级对应实时进程,实时进程是指先将一个进程执行完毕再执行下一个进程,现在基本不存在这种机器了,所以对于queue当中下标为0~99的元素我们不关心。

                                 

3.活动队列

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

调度过程如下:

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

bitmap[5]:queue数组当中一共有140个元素,即140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5 ×  32个比特位表示队列是否为空,这样一来便可以大大提高查找效率。

总结: 在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不会随着进程增多而导致时间成本增加,我们称之为进程调度的O(1)算法。

                         

4.过期队列

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

                         

5.active指针和expired指针

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

 

  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值