一、冯诺依曼体系结构
理解首先想要理解进程的概念,首先要对冯诺依曼体系结构有一个基本的认识。我们现代的计算机大部分都遵守冯诺依曼体系结构,在这个结构中把计算机分为五大部分,分别是输入设备、输出设备、存储器、运算器和控制器。
常见的输入设备有键盘、鼠标、硬盘、扫描仪、麦克风等等;常见的输出设备有显示屏、打印机、音响等等;运算器和控制器就是我们通常认知中的CPU(中央处理器);特别要强调的是,这里的存储器的指的是内存!硬盘属于不存储器而是输入设备!
在不考虑缓存的情况下,CPU只能对内存进行读写操作而不能访问外部设备,而外部设备要读取数据也只能对在内存中写入或者读取数据,总之一句话就是,CPU在数据层上只与内存打交道,外部设备也只能与内存打交道。
这里就可以思考几个问题,为什么一个软件在运行之前需要加载呢?是把软件加载到哪里呢?软件被加载之前又在哪里呢?
很显然上面的冯诺依曼体系结构就能很好的解释这几个问题:因为软件是被我们的CPU去执行的,而我们的CPU是只能和内存进行数据交互的,所以软件在运行之前一定要先加载,也就是把我们的软件加载到内存当中,而内存和硬盘的容量比起来,硬盘的容量明显是更大的,所以我们不可能把所有的东西都放在内存中,这个计算机的效率确实能很大的提高,但是成本同样也提高了非常的多,所以软件在运行之前都是被放在硬盘中的,只有在被运行的时候,才会加载到内存中。这里又可以想到一个东西,也就是在冯诺依曼体系结构中,数据是从一个设备“拷贝”到另外一个设备上的,所以这个体系结构的整体效率,是由设备的“拷贝”效率决定的。
二、进程
首先往小了看,进程其实就是一个正在执行的一段代码而已;而大了看呢,进程是一个被分配了系统资源(CPU时间、内存)的一个实体。具体一点,在操作系统中会有一个叫做进程控制块(PCB)的数据中存储了进程的信息,我们在运行一个软件的时候其实是没有把我们的代码全都加载到内存当中去的,当我们运行内存的时候,系统就会用PCB来存储我们这个软件所产生的进程的信息,系统就是通过这些信息去实现代码的运行的。在Linux操作系统下的PCB就是task_struct。其实这样说完很多人还是看不懂进程到底是个什么东西,但是后面还是会再重新说明的。
其实进程就是Linux操作系统内的内核数据结构加上它代码数据的总称就是进程,而一个进程的内核数据结构就是task_struct,一个程序在被调度的时候,它的信息都会被记录进一个task_struct,而task_struct里有这个进程的所有信息,操作系统中关于进程的所有操作都是围绕着task_struct来进行的,而管理task_struct采用的是链表链接的方式。
比如说一个CPU就只存在一个调度队列,一个进程如果需要被调度的话,那它就需要把它的task_struct链接到调度队列当中,此时这个进程的状态被称为运行状态,也就是说,只要这个进程在调度队列中,就算它当前没有正在被执行,它也是运行状态。
当这个进程需要用到I/O操作时,每个外部设备上也会有一个队列,这个队列叫做等待队列,当一个进程需要调用这个外部设备时,这个进程就会链入这个外部设备的等待队列,此时这个进程的状态就被叫做阻塞,如果这个等待队列之前还有其他的进程正在等待或者使用这个外部设备,那么这个进程就会在这个等待队列中一直等待,直到前面的进程都被处理完毕。
CPU内的资源是有限的,每个进程都需要有一个task_struct,当CPU内的资源极度紧缺的时候,CPU就会去寻找哪些进程是正在等待的,当CPU找到这些正在等待的进程的时候,CPU会把他们的代码数据从内存中换出到磁盘中的一个指定的swap区域,当内存资源能够得到一些释放,使其他进程能够正常运行,被换出的那些进程的状态被称为阻塞挂起,当轮到这个进程运行时,CPU会将他们的代码数据重新换回到内存中。
我们可以发现在操作系统中有非常多种不同的队列,而每个进程都需要有一个task_struct,如果按照我们之前写链表的方式来规划task_struct的话,那么每有一种队列,一个进程就需要一个task_struct,这样就会非常的浪费内存资源,所以操作系统中的链表链接的方式和我们之前学习的有一些不同。
task_struct中有非常多个下面这样的成员,这个就是task_struct的连接方式。
struct list_head{struct list_head *next,*prev;
}
比如说task_struct中有一个list_head类型的成员变量叫做links,我们把0号地址强制类型转化成struct task_struct*的类型,然后取用这个成员变量的地址就能计算出这个成员变量和task_struct这个结构体对象的起始地址的偏移量,那么我们就能根据这个对象里的next和prev指针以及计算出的偏移量找到前后链接的task_struct。这样无论在操作系统中有多少种队列,我们一个进程都只需要一个task_struct就行了。假如操作系统中有十种不同的队列,那在我们的task_struct中只需要有十个不同的list_head类型的成员变量,把不同的list_head类型的成员变量链接到不同的队列中,我们的进程就不仅可以同时处于多种状态并且只需要一个task_struct就足够了,这样大大节省了内存空间。
struct task_struct
{int x;int y;int z;list_head links;
}&((struct task_struct*)0->links)