Linux进程的切换

一、进程退出

基本知识储备

1、代码不会执行了——首先可以立刻释放的就是进程对应的程序信息数据退出

2、进程退出,要有退出信息(进程的退出码)保存在自己的task_struct内部

3、管理结构task_struct必须被OS维护起来,方便用户未来进行获取进程退出的信息

task_struct->是进程的PCB

进程=内核数据结构+代码和数据

结构体->成员属性->退出信息(int code,其他信息)

二、僵尸进程

1、僵尸进程是进程的一种状态

在进程死亡后由其父进程进行回收其退出信息,而当进程死亡后其退出信息没有被回收时,就会出现僵尸进程

2、在代码层面:

我们创建子进程,其父进程设计一个循环,使其什么都不做,通过kill命令杀死子进程,此时子进程就会变成僵尸进程

在代码变成僵尸进程后会一直维持自己的task_struct,方便未来父进程读取其退出状态,当父进程读取子进程的退出信息后,子进程会自动退出。

如果没有人回收僵尸进程,就会一直消耗内存,进而造成内存泄漏!

三、孤儿进程

在上述情况中,子进程被杀死,而父进程还在。而如果将父进程杀死,子进程还在就会出现孤儿进程

子进程会被系统自动领养,退出数据也由系统自动回收

四、进程优先级

1、是什么?

获得某种资源的先后顺序,比如:排队的本质就是在确定优先级

2、为什么?

cpu的资源比较少,需要其竞争cpu资源

3、优先级VS权限

优先级是先后问题

权限是能不能的问题

两者从本质上不一样

4、怎么办?

tast_struct->优先级属性->特定的几个int类型的变量表示优先级

优先级越小代表优先级越高

优先级分pri(默认优先级)和nice(调整/修正的数据)

最终优先级=默认优先级+nice

a、文件会记录下拥有者,所属组,和对应的权限

b、linux下一切皆文件

c、所有操作,都是进程操作,进程会自己记录谁启动的自己

权限的控制

为什么有nice?
   因为要保证程序在可控范围内

分时OS->进程调度->>>保证程序调用的时间尽量平均

5、修改优先级nice:

(不是高频,不建议修改,在此只是表示可以修改)

a、指令

b、代码

nice的可修改范围是【-20,19】->40个数字

而最终优先级【60,99】

pri(最终优先级)=pri(default 80)+nice(每次修改都会重置)

五、其他概念

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

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

并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行(任何时刻)

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

六、进程的切换

1、知识储备

a、时间片:时间片到了,进程就要被切换

b、Linux是基于时间片进行调度轮换的

c、一个进程在时间片到了的时候,并不一定跑完了,可以在任何地方被重新调度

2、切换进程过程和理解

cpu中有不同寄存器,他们有不同作用,其中有ir寄存器和pc寄存器

寄存器!=寄存器中的数据

ir寄存器中储存处理器当前要需要处理的代码指令

pc寄存器中储存下一段要处理的代码指令的地址和其代码长度

->在内存中我们的代码不是一行一行进行存储的,而是连续的,pc中保存代码的长度可以帮助找到下一段代码。

当cpu处理完ir寄存器中的代码时,pc中的地址就可以寻找下一段代码,我们写的代码就是这样一句一句的执行下去。

你的进程在运行的时候,会有很多的临时数据,都在CPU的寄存器中保存。

cpu内部的寄存器的数据,是进程执行时的瞬时状态信息数据

基本过程:

1、取指令

2、更新pc

3、分析执行的指令

总结:

CPU调度进程的时候是基于时间片轮换调度的,而在CPU中有加载下一行代码的寄存器pc,有用来加载当前代码的寄存器ir,他们通过不断轮换调度来不断的加载执行代码;

六、进程数据的恢复

进行切换的核心含义就是进程上文的保存和恢复

如果没有保存,进程切换走后,下一次切换回来,上一次数据全部消失,代码就不能继续完成。

所以在进程切换时数据将保存和储存呢?

进程的上下文寄存器数据(进程的上下文数据)被保存到了当前进程的PCB(任务状态段的结构)中。

切走:将相关寄存器的内容保存到内存中。

切回:将历史保存的寄存器数据恢复到寄存器。

当进程被切回的时候接着上次没做完的事情,继续运行

————————>>>这个是所有进程都要做这个工作。

每次切换时,每次保存上下文的时候CPU都是全新的

总结:

进程在进入CPU调度的时候,因为有时间片的原因,可能不会直接跑全部代码,当代码没有全部跑完的时候,这个进程代码数据都被保存到进程的PCB中,当重新切回时再向CPU重新加载数据,CPU是不会保存数据的;

七、Linux真实的调度算法

上面说到 进程的优先级 和 CPU调度时为了进程都可以被调用而出现的分时操作,两者都是进程被调用的规则,但CPU在调用时如何同时体现两种规则??

答:FIFO调度

1、基本知识

在系统中存在这样的一个结构体,叫struct runqueue

上面结构中有三个变量最为重要

其中nr_active意思是活跃进程;

queue数组含有140个元素,前100个数据是实时动态变化的数据,不必理会。后40个数据刚好对应着40个优先级。

相同优先级的数据就会被挂在对应优先级的数组位置,形成一个hash桶,

2、bitmap原理和Linux内核O(1)调度算法

bitmap是queue数组的相位图,其相位图记录了queue中每个位置上是否含有元素

原理:bitmap含有5个int型元素,一个int型是32个比特位,32*5=160,正好覆盖queue140元素,当其对应位置上是1时便代表其对应queue数组中对应位置是否含有元素,这样便构成了相位图。

for(int i=0;i<5;i++)
{
    if(bit_map[i]==0)
        continue;
    else
        //32个比特位中确定在哪一个队列

}

上面代码一次就可以检测32个位置

时间复杂度是O(1);

在C语言阶段,我们就学会了 按位与 和 按位或 异或等操作,通过简单的有限次数的while循环就可以判定最低的比特位在int是哪一个

while(x)
{
    x&=(x-1);
}

上面程序用来判定最低的比特位在int的哪个位置,时间复杂度也为O(1);

因此整体Linux内核的调度算法就是O(1);

因此叫Linux内核O(1)调度算法

上面的结构

active队列永远都是一个存量进程竞争的情况,即:active队列只会越来越少,不会增多

时间片到了,进程就会退出

调度器要非常均衡的进行进程调度

因此优先级高的一定一直优先吗???????

        优先级低的一定一直不会被调度吗????

1、CUP调度只会从active队列中选择进程调度

2、调度有三种情况

a、运行退出

b、不退出但时间片到了

c、有新的进程产生了

swap(&active,&expired);

swap:

1、之前:按照优先级调度

2、之后:给其他优先级的进程的调度机会

struct riunqueue

{

...

struct queue* active;//活跃的

struct queue* expired;//过期的

struct queue* array[2];        

}

总结:

简单来说:在相同中存在一种名为struct run queue的结构体

在这个有两个一样的变量组

其中nr_active的意思是不是活跃进程,bitmap是位图,用来记录queue数组中的含有的元素的位置

而queue数组就是用来管理40个优先级。

系统只会调用活跃的那个结构体中的进程

当有新的进程加入时,不管他的优先级多少,都不会直接加入活跃,也就是被系统调用的那个结构体,而是加入不活跃的那个结构体中。

而一个进程被调用结束后也不会放回活跃的结构体中,而是放到不活跃的那个结构体中。

所以活跃的那个结构体中的进程越来越少,当活跃的那个结构体中进程为零时,系统会通过改变指针的指向,将活跃的变为不活跃的,将不活跃的变为活跃的。

此时就完成一轮系统调用;

八、Linux中的链式结构:

Linux中都是双链表结构

1、所有的进程都要用链表链接

2、进程可以在调度队列中,也可以在堵塞队列

其中链表中只有只有指针域,没有数据域

他们的链接方式是上面这样的结构。

那么问题来了,如果我们可以从头开始访问结构体,我们可以找到他的数据域和指针域

如果是上面的这样的结构,我们如何找到他的数据开始的地方呢?????
答:在内存中,我们是由低地址到高地址来进行存储。假设存在这样的一个结构体

//低地址
struct A
{    
    int a;
    int b;
    int c;
    int d;
}
//高地址

如果我们知道了c的地址,我们怎么找到第一个元素的起始位置。

(struct A*)0->c=偏移量

上面的公式是将地址为0的地方强转成struct A*类型的元素,此时0地址处就是a元素的所在位

我们找到此时c的地址位,这个时候就是a元素到c元素的偏移量

此时再用原先c的地址减去偏移量,就得到了原链表中链表首元素的地址;

这样做意义是什么??

答:这样做一个进程既可以在全局链表中,也可以在任意一个其他数据结构中,只要加节点字段即可;

九、命令行参数

main函数的命令行参数

int main(int argc,char *argv[])
{
    printf("argc: %d\n",argc);
    for(int i=0;i<argc;i++)
    {
    printf("argv[%d]:%s\n",i,argv[i]);
    
    }
    return 0;
}

命令行参数列表:argc:参数个数,argv[]:参数的清单

为什么要有他:

同一个程序,可以根据命令行参数,根据选项的不同,表现处不同的功能

比如:指令中选项的实现!

命令行参数就是一串字符串,最先被shell拿到,按照空格被打散,形成一张表和元素个数

命令行启动的程序,父进程是谁-------->shell ,对于数据(尤其是只读),子进程也能看见。

十、环境变量

具有全局属性的变量叫做全局变量。

PATH:指定命令的搜素路径

为什么系统知道,命令在/usr/bin路径下,我能更改,让他认识我们的路径吗??

PATH环境变量,告诉shell,你应该去哪个路径下查指令。

如果我不想带路径,让我的程序执行起来,

1、cp/usr/bin---done

2、把自己的路径,添加到PATH中

环境变量不是存在于内存中,在系统的配置文件中

当你在启动shell时,系统会记录你的uid

我们登录后->启动一个shell进程->读取用户和系统相关的环境变量的配置文件->形成自己的环境变量表->子进程

———————————————————————————————————————————

感谢你的观看,如果有什么问题,可以给我留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值