Linux进程概念(下)

前言

上文介绍了进程的基本概念,进程=内核数据结构+可执行程序,查看进程的方式ps 和/proc指令。

又熟悉常见的进程状态,状态修改的本质就是将PCB标志位更改,然后放到指定的队列中。

本文继续介绍进程的概念,将介绍进程的优先级、O(1)调度算法、环境变量、进程地址空间等等。


进程的优先级

为什么要有优先级?


因为CPU资源是少数的,系统内的进程是多数的,所以进程之间会存在竞争CPU资源,就要对进程进行排队,这个排队的过程就是确定进程的优先级。

查看系统的优先级

ps -al 查看系统默认的优先级

 PRI和NICE

  • linux下利用PRI和NICE同时确认系统的优先级
  • PRI是默认优先级,每一次都会被设置为80,而NICE是调整值。
  • 如果NICE是-10, 优先级=80-10=70。如果NICE=15,优先级=80+15=95。
  • 优先级的范围是60-99,不在这个范围的优先级会被调整为边界。

为什么要限定优先级的范围?
本质就是让CPU调度的时候,能够较为均衡的把每一个进程都调度,防止进程饥饿问题。

修改系统的优先级

TOP+R指令

进程的上下文切换 

CPU只有一套寄存器,进程是独立的,也就是说每一个进程都能使用这一套寄存器。

进程的上下文数据会被保存在寄存器中,上下文数据就是产生的临时数据等等。

进程的时间片结束时候,会将寄存器中的上下文数据保存到进程PCB当中,当该进程的时间片再次到来,会将保存在PCB中的上下文数据恢复到寄存器当中。这个过程就叫做上下文切换。


Linux内核2.6下的O(1)调度算法

处于运行队列的PCB都会等待CPU调度。

运行队列会维护俩份队列和位图,活跃进程指针和过期队列进程指针。

调度进程时候,会先到通过活跃进程的bitmap找到哪一位上不为0,然后到活跃队列的对应下标去取PCB来调度。而调度完的PCB和后来的PCB都应该被放到过期队列中。

当活跃队列为空时,就交换活跃队列和过期队列的指针。

 所谓的插队就是将后来的进程放到活跃队列中,而不是放到过期队列中。

  • 提高效率的操作是bitmap:5*32=160位,就可以32位一起判断,而不是遍历整个队列。
  • 保证均衡调度的是:维护双队列,后来的放到过期队列,不影响先来正在调度的进程。

命令行参数

命令行参数是main函数中的参数,可以在执行前通过命令行可执行程序+选项的方式,被进程获取。

测试简单的命令行参数

注意:

  • 命令行参数是以字符串的形式组织起来。
  • 在最后一个参数结束后,会以nullptr结尾。

 命令行参数的意义?
支持各种指令级别选项设置。

实际上,我们在linux下输入的各种指令就是可执行进程,如果输入选项字段,就是命令行参数。


环境变量

linux各种指令的本质就是可执行程序,这一点是可以被验证的。

模拟touch创建一个文件

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 int main(int argc,char * argv[]){
  4     if(argc!=2){
  5         printf("command error!\n");
  6         exit(0);
  7     }
  8     FILE * fp;
  9     fp=fopen(argv[1],"w+");
 10 
 11     fclose(fp);
 12     return 0;
 13 }

与linux下不同的是,我们需要指明路径,而OS的程序就不用。

主要是因为OS会通过环境变量的方式内保留可执行程序的搜索路径

环境变量的概念

环境变量是指操作系统用来指定操作系统运行环境的一些参数。

比如在链接动静态库的时候,不用指明具体路径,照样可以链接成功。

环境变量通常有特定的功能,具有全局属性。

常见的环境变量

通过env查看全部的环境变量

常见的环境变量:

  • PATH:默认的搜索路径
  • PWD:当前目录
  • LOGNAME:日志名称
  • HOME:家目录,用户登录到linux下的目录。

查看环境变量

通过echo  $(环境变量)的方式查看莫一个环境变量

echo &PATH

测试环境变量 

将之前撰写的mytouch添加进行PATH搜索路径环境变量

  • 1.将mytouch添加进行指定的目录
  • 2.为PATH添加路径

验证一:

验证二:
通过指令为PATH添加mytouch的路径

 PATH=$PATH:/home/lyh/proctest

获取环境变量

通过miai函数传参

其实main函数的第三个参数是一张环境变量表

通过main函数的第三个参数就能获取到环境变量表,这个环境变量表和env指令看到的是一样的。

当环境变量作为参数传递给main函数,实际上是以字符串数组的形式,末尾是NULL结尾

通过getenv

系统调用getenv获取指定的环境变量

环境变量是从父进程继承过来的,但是bash进程的环境变量是从哪里来的?

从配置文件中读取而来的。

在home目录中存在一个/profile 文件,配置着环境变量

我们也可以自主添加环境变量,比如:变量名=值

 但是这样添加的,只能称为本地变量,如果想成为环境变量,就必须通过export导入

删除导入的环境变量:unset

注意:
即使通过export导入的环境变量,在shell重新登录时候,都会被清空,因为shell会读取配置文件的环境变量,除非你把自定义的环境变量导入到配置文件中。 

获取环境变量的第三种方式

通过environ全局参数,用法和main函数的参数一致。

 int main(){
    //这里只是声明一下
    extern char **  environ;
    for(int i=0;environ[i];i++){ 
        printf("environ[%d]:%s\n",i,environ[i]);
    } 
    return 0;
 }   

环境变量具有全局属性,会随着子进程一直继承下去。

本地变量vs环境变量

本地变量只在当前会话的bash中有效,并且不会随着进程继承下去。环境变量会在所有的子进程中继承。

linux下指令分类:常规的进程(fork子进程程序替换实现)。2.内建命令:由bash提供的函数。


进程地址空间 

实验:在父进程中创建全局变量值为100,fork创建子进程。五秒后,子进程将值修改为200。观察父子进程的值是否一致。

最后发现父进程的值为100,子进程的值为200。val的值是不一致的,但是同一个值的地址确不同。

说明我们这个地址肯定不是内存上的地址。到达是什么?是虚拟地址。本文就来探讨一下进程地址空间的相关内容。


每一个进程都有一个虚拟地址空间的存在

当进程被启动的时候,会创建PCB建立地址空间。操作系统必然会对这些地址空间管理,所以地址空间就是内核的数据结构。会被记录在进程的PCB中。

进程地址空间就是(struct mm_struct)

进程间不会共享同一个地址空间,因为要保证进程的独立性。让进程知道自己有机会获取到所有的物理空间。

页表:(空间映射)

每个进程都存在自己的地址空间,当磁盘上的数据被加载到内存上时,会先建立虚拟地址和物理地址的映射。并且将这种映射关系保存到页表上,用于从虚拟地址,找到实际内存上的地址。

什么是地址空间

回答这个问题前,先了解一下区域划分。在一根直尺上,如果最小刻度是cm。

那么假设区域1的起始地址是0cm,结束地址是10cm。区域2的起始地址是 11cm,结束是30cm。

那对于区域1中,我们也可以继续划分,我们可以按照mm划分,划分成为更小的刻度。

所以区域划分的本质就是用start和end标志你的空间。

进程地址空间的划分也是如此

struct destop_area{
    _size,
    字符常量区_start,_end,
    代码区 _start,_end,
    栈区 _start,_end
    ......
}

看一看内核中的定义

asmlinkage int
osf_set_program_attributes(unsigned long text_start, unsigned long text_len,
			   unsigned long bss_start, unsigned long bss_len)
{
	struct mm_struct *mm;

	lock_kernel();
	mm = current->mm;
	mm->end_code = bss_start + bss_len;
	mm->brk = bss_start + bss_len;
#if 0
	printk("set_program_attributes(%lx %lx %lx %lx)\n",
		text_start, text_len, bss_start, bss_len);
#endif
	unlock_kernel();
	return 0;
}

内核中就是由start和end组成

所以进程地址空间就是struct结构体,维护了start和end指针对物理地址做了虚拟的空间划分。

页表是被保存在寄存器上的,也可以通过指针找到页表。

页表的地址是虚拟还是物理地址?物理地址。


回答实验的问题

为什么一个变量,有俩个值。

当一个进程被启动时,就会创建pcb结构体,创建mm_struct地址空间。同时保存页表,对必要的数据做物理内存到虚拟地址上的映射,也就是说假设变量的地址为0x00003344那么物理地址映射的可能就是0x22334455。这时创建子进程,子进程就会以父进程的PCB为模板创建子进程的PCB,同时也会复制一份父进程的地址空间,以及页表映射。

上图有点小错误,应该指向的是已经初始化的全局区。

当子进程对全局变量修改时,由于进程间的独立性,就会发生写时拷贝。

子进程会重写开辟物理内存,将父进程的值拷贝进去,然后进行修改,重新建立页表的映射。

所以我们看到的地址就是虚拟地址,同一个虚拟地址在不同的进程下,就有了俩个值。


为什么要有进程地址空间

1.让进程可以以统一的视角看待内存。任意一个进程 通过地址空间+页表将乱序的内存变得井然有序。

2.可以对访问内存进行安全检查。比如我们不能修改代码区的字段。

实际上,页表还存在第三列,就是权限位。比如字符常量区的权限就是r,栈区的就是rw

当我们要修改字符常量区时,就会被权限位拦住,不让你访问。

缺页中断:
页表还存在一列表示内存是否已经分配。

当程序被加载到内存时候,不需要把整个代码+数据全部加载,而是加载一部分。但是会先建立页表的左边部分,然后确定第4列数据是否分配。如果访问到尚未分配的地址时候,就会暂停进程,加载资源,修改页表,再去执行进程。

这一个过程就叫做缺页中断。

3.实现进程管理和内存管理的解耦:进程只需要负责去访问地址读取资源,加载资源由OS自动完成。


写时拷贝

什么时候进行写时拷贝?
当父进程fork创建子进程时候,就会将父进程的页表权限位全部修改位只读。然后为子进程创建PCB,地址空间,页表等等。

当用户进行读写的时候,就会被页表的权限位拦住出错:1.真的出错了,访问到真的只读区,直接不访问。2.同一份资源,就会重新申请物理内存,拷贝旧内容到新地址处。这就是写时拷贝。

关于进程概念暂时到此为止了,后续也会进行补充,进程的概念是庞大的,涉及到的知识点也比较复杂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深度搜索

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值