你真的了解Linux中的进程吗?(下)

169 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


进程状态

一般系统进程状态:就绪阻塞运行

Linux下主要有:

  • R运行态: 相当于就绪+运行,并不意味着进程一定在运行中,它表明进程要么在运行中、要么在运行队列中。
  • S睡眠状态(可中断睡眠态):意为着这个进程在等待事件完成。
  • D磁盘休眠状态(不可中断睡眠态)这个状态的进程会等待I/O的结束。
  • T停止状态:可以通过发送SIGSTOP信号给进程来停止进程,这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行,并非什么事都不干,而是在处理某些事件。
  • X死亡状态:这个状态只是一个返回状态,不会再任务列表中看到这个状态。
  • Z僵尸状态/僵死状态:子进程先于父进程退出,父进程对于信号没有处理,此时子进程为僵死状态。

进程终止

kill + pid 终止进程 kill -9 + pid 强制停止/杀死进程(9号信号)

  • 无法被杀死的进程:僵尸进程,强杀也无法杀死。

僵尸进程

意为处于僵死状态的进程,强杀也无法停止。

产生原因

  1. 子进程先于父进程退出,操作系统检测到子进程的退出,通过信号通知父进程。
  2. 但是父进程此时正处于运行态(相当于在专心工作),对于此信号没有关注,这时子进程为了记录现场(案发现场),操作系统不会完全释放子进程资源,因为子进程的PCB包含有退出原因。
  3. 子进程这时候因为既没有运行,也没有完全退出,因此就处于僵死状态,称为僵尸进程。

危害占用资源、资源泄露

处理方法

  • 手动关闭父进程。( 但这只是处理方法,权宜之计,不是解决方法)

  • 等待进程。(完全避免僵尸进程的产生)

孤儿进程

产生原因

  • 父进程先于子进程退出,父进程退出后,此时子进程由前台进程(S+态)成为后台进程(S态)。但其并非没有父进程了,通过查看进程发现,它是被 1 号进程sbin/init"领养"了。

涉及相关与硬件相关的脱机概念,孤儿进程有可能摇身一变成为守护进程(精灵进程),我们之后讲解~


进程优先级

优先级就是决定资源的优先分配权的等级划分

  • Q:为什么存在优先级?
  • A:让操作系统运行的更加合理!

这里延伸两个相关概念:

  1. 交互式进程:一旦有操作,要【优先处理】
  2. 批处理进程:一直~处理数据,但是对CPU要求并不是很高!

通过ps -elf指令可以查看目前进程的优先级。

优先级的设置

  1. PRI 优先级
  2. NI (nice值) 【它的取值范围:-20~19

修改公式:==PRI = PRI + NI== 因为系统默认PRI无法直接设置,但是可以通过设置NI值,进而按照公式进行调整PRI值。

修改nice值指令

renice -n <size> -p <pid> 	//后缀的先后顺序固定

例如:renice -n 10 -p 7635

  • PRI越大,优先级越低
  • PRI越小,优先级越高

并行与并发

并行CPU资源足够,多个进程 ==同时== 运行 并发CPU资源不够,多个进程 ==切换== 运行,同时推进。

独立性:进程间相互独立。 竞争性:资源少,进程多,“狼多肉少”,每个进程之间都具有竞争性。


环境变量

保存系统运行环境参数的变量

目的:保存为环境变量后,运行的更加快速、高效

常见环境变量

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

环境变量相关指令

  1. echo: 通过变量名称查看指定环境变量 (例如echo &MYENV
  2. env 查看所有环境变量 (envenv | grep MYENV
  3. set 查看环境变量以及临时变量(比env查看显示的多,因为多列举了临时变量等)
  4. export 声明/设置一个环境变量
  5. unset 删除一个环境变量

环境变量的设置PATH=$PATH:/home/...

  • 获取全部main(int argc,int char *argv[],char *env[]) //方法一:参数获取 extern char **environ //方法二:全局变量获取
  • 获取指定char *getenv(const char *env_name) //方法三:通过接口获取

请看代码:

//方法一:	通过字符指针数组env[]的参数获取
#include <stdio.h>
int main(int argc,int *argv[],char *env[]){
	int i;
	for(i = 0;env[i] != NULL;i++){
		printf("env[%d] = [%s]\n",i,env[i]);
	}
	return 0;
}
//方法二		通过全局变量environ来获取,其本质是一个二级指针,相对于本程序是一个外部变量
int main(int argc,int *argv[],char *env[]){
	extern char **environ;		//在头文件中没有包含,但在库中包含
	int i;
	for(i = 0;environ[i] != NULL;i++){
		printf("environ[%d] = [%s]\n",i,environ[i]);
	}
	return 0;
}
//方法三		getenv()接口,需要包含头文件 <stdlib.h> 也是最常用的一种!
printf("PATH:[%s]\n",getenv("PATH"));

环境变量的全局特性: 在子进程中获取继承于父进程的环境变量信息(继承特性) 需要在外部定义MYENV。临时变量不可以,而是要export声明为全局变量。


程序地址空间

对于32位操作系统,寻址空间为4G。一般内核占1G,用户占3G

地址是什么?是内存的编号,指向内存的一块区域。

虚拟内存地址空间

mm_struct内存描述符,描述出了一个虚假的内存地址空间,其本质是一个结构体,也存放在tack_struct结构体中。

struct mm_struct{
	long int size;
	code_start;
	code_end;
	data_start;
	data_end;
}
  • 页表:就记录虚拟地址与物理地址之间的映射关系,联系了二者并且对(虚拟)地址进行内存访问控制

物理内存上可能不连续,但虚拟内存连续,二者是通过页表映射位置信息。这种“映射”,是拿到虚拟地址,经过一部分算法计算(页内偏移等因素影响),得到物理地址。而不是记录地址到地址的映射。

其实页表也是一个结构体,因为有了对页表的操作特性,所以也存在“二级页表”、“三级页表”等。

虚拟地址空间的好处

  1. 内存充分利用。
  2. 内存访问控制。
  3. 保证每个进程的独立性。

写时拷贝技术

优点

  1. 节省资源
  2. 提高子进程的创建效率
  3. 这种机制,会比vfork创建进程更节省空间。

过程

  1. 子进程拷贝父进程PCB,同时拷贝了虚拟空间,同时拷贝出的页表也是相同的。 (如果创建的子进程可以修改父进程的数据,就不构成进程独立性,同时会造成栈混乱)
  2. 如果一旦子进程要对内容进行修改,就会在物理内存上从新开辟一个地址,并拷贝存放更改的数据
  3. 物理地址上存储数据的地址不同,但在虚拟地址上仍是同一个地址,所以说"数据独有"。
  4. 因为代码段是只读的,如果要修改就会报错,只读属性也是页表上记录的,所以说"代码共享"。

内核O(1)调度算法

通过两个队列互相替代的一种内核调度算法。

活动队列

所有时间片还没有耗尽的进程都按照优先级放在该队列。

  • nr_active: 总共有多少个运行状态的进程
  • queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级。

从该结构中选择一个最合适的进程,我们一般思路大致是这样:

  1. 0下标开始遍历queue[140]
  2. 找到第一个非空队列,该队列必定为优先级最高的队列。
  3. 拿到选中队列的第一个进程,开始运行,调度完成。
  4. 遍历queue[140]

虽然这样的程序时间复杂度是常数,但是太低效了。所以引入了位图的表示方法:

bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样就可以大大提高查找效率。

过期队列

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

实现方式

创建一active指针永远指向活动队列。 创建一expired指针永远指向过期队列。 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片耗尽时就会入队过期队列。 所以在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程。可以开始进行下一周期的新调度。

小结

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

这是一种典型的空间换时间的操作。


【注】objdump -S + <程序名> :查看main函数的汇编代码