1 进程
冯诺依曼体系结构
冯诺依曼:计算机之父,在物理、化学、数学、也具有很大贡献,开始是研究原子弹的。
冯诺依曼体系结构图:
关于冯诺依曼,必须强调几点:
- 这里的存储器指的是内存,且所有数据在内存中存储的时候,采用2进制的方式进行存储;
- 不考虑缓存情况,这里的cpu能且只能对内存进行读写,不能访问外设(输入或者输出设备);
- 外设要输入或者输出数据,也只能写入内存或者从内存中读取,且中央处理器的数据来源于存储器。
- 一句话,所有设备都只能直接和内存打交道。
2 操作系统(Operator System)
概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:内核(进程管理,内存管理,文件管理,驱动管理);其他程序(例如函数库,shell程序等等)。
操作系统本身就是一个软件,管理计算机的软硬件资源。原生的操作系统 = 操作系统内核 + 一组应用。
设计OS的目的
- 与硬件交互,管理所有的软硬件资源
- 为用户程序(应用程序)提供一个良好的执行环境
- 操作系统的简单流程图,由下到上依次执行
定位
在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件。如何理解管理?管理 = 描述 + 组织。
总结
计算机管理硬件:描述 + 组织。描述用struct结构体,组织用链表或其他高效的数据结构。
3 进程
基本概念
操作系统是通过结构体来描述进程,通过双向链表来组织进程。如下图所示:
进程和程序的区别
程序:程序就是一个静态的文本文件,例如产生了一个静态的out文本文件。
进程:进程运行起来就是一个实例。从内核中分析在操作系统内核当中,为进程创建了一个task_struct结构体;或称之为进程控制块PCB(process control block),可以理解为进程属性的信息。 task_struct是Linux内核的一种数据结构,它会被装在到RAM内存中并且包含进程的信息。可以理解为,创建一个进程 ,就是创建了一个test_struct结构体, 也就是进程控制块 。
了解task_struct结构体当中的内容
struct task_struct
{
进程标识符(PID):在当前的操作系统中唯一标识当前进程—进程标识符相当于“身份证”
内存指针:指向程序的地址空间,如下图所示:
进程当中的相关指令
- ps aux:查看当前操作系统当中(进程)的信息
- ps aux | grep “XXX”:在ps aux的结果当中过滤字符串“XXX”
- find: 在操作系统当中查找一个文件 ;语法:find [path] -name [filename]
- 例如查找内核源码,内核源码一般在根目录下的usr目录下,可以用find命令查找。
- 举例查找sched.h命令:find /usr -name sched.h ,其中查看一条结果如下:
- /src/kernels/3.10.0-1127.18.2.el7.x86_64/include/linux/sched.h
- 使用vim /src/kernels/3.10.0-1127.18.2.el7.x86_64/include/linux/sched.h打开此文件,里面就有task_struct的定义,在vim命令栏下,输入/task_struct即可搜索带有task_struct的文本信息。
进程状态 :进程中分为3种状态
1. 运行状态:可执行程序正在CPU上面进行运算;
2. 就绪状态:程序已经准备好,在就绪队列当中等待获取CPU的资源;
3. 阻塞状态:可执行程序有时候需要等待输入设备提供数据,等待IO就绪;
并发执行:多个进程使用一个CPU,每一个进程都独占CPU一小会,然后让出CPU资源,供其他进程执行。
并行执行:多个进程,在同一时刻每一个进程都占有一个CPU进程运算。
进程中状态的表示规则
- R:表示进程中的运行状态
实例:生成test可执行程序,过滤字符串"./test",并进行搜索,搜索结果如下图所示:
- S:可中断睡眠状态
- D:磁盘睡眠状态,不可被打断。实例如下:
- T:暂停状态。又分为前台进程(“+”代表前台进程)+后台进程(没有“+”代表后台进程)。
前台进程中例如打开了qq界面,后台进程中例如后台运行着微信。
实例:./ test运行一个死循环程序,此时状态和上一幅图一样,都是前台进程。输入命令Ctrl+z后暂停进程,ps aux | grep ./test重新查看进程如下图所示:
此时前台进程转化到后台进程状态。若想在转化到前台进程,输入fg,此时又会转化到前台进程中,继续进行死循环。
- t:跟踪状态 – gdb调试程序员的时候可以发现程序是t状态。
实例:先打开进程,查看进程ip。如下所示:
让进程进入gdb调试模式---->gdb -p 8419(此时进入了调试模式)
查看进程模式状态,输入ps aux | grep ./test查看结果如下:
此时进程在跟踪状态中进行调试。结束后又进入原先状态。 - X:死亡状态
- Z:僵尸状态 僵尸状态详细介绍可以看这篇博客:僵尸状态和孤儿状态分析
程序计数器:保存进程即将要执行的下一条指令。
上下文数据:保存上一次执行的时候,寄存器当中的值。
- 进程在执行的时候,进程会进行切换,切换是系统调度的;
- 在被切换出去的时候,该进程当中的程序计数器会保存程序要执行的下一条指令,上下文信息会保存寄存器当中的值;
- 再次切换回来的时候,通过程序计数器和上下文信息来恢复之前的场景,继续运算。
IO信息&记账信息
当前进程启动的时间(15:39);当前程序占据CPU的时间(0:00)
打开文件的信息:ll /proc/ [PID]/fd
举例:ll /proc/12341/ fd 打开结果如下:其中0、1、2、代表标准输入,标准输出、标准错误。
进程优先级(PR): - - 了解
PR值越小优先级越低,反之优先级越高。
PR(new) = PR(old) + NI;NI值时修正PR值得一个参数 。输入r可进行修改, 输入top可查看系统资源消耗情况:
通过系统调用创建进程–fork初识
讲解前提:
PCB(task_struct)中保存有程序计数器和上下文信息,用来执行和保存命令,并且一块PCB指向一块程序的地址空间。
我们在创建子进程也就是新的进程中,需要调用fork函数。其命令如下:
#include <unistd.h>
peid_t fork(void);(peid_t是int类型)
其中fork函数是有返回值的,失败返回值小于;成功返回两次。父进程返回值大于0,子进程返回0。
- 父进程调用fork函数,创建子进程,子进程的PCB(task_struct)是拷贝父进程的。实验代码如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("-----begin-----\n");
fork();
printf("linux84 666\n");
return 0;
}
输出结果:
执行逻辑:
在父进程中,先开始打印begin,然后父进程调用fork函数,子进程拷贝父进程PCB(task_struct),其中拷贝过去task_struct中的程序计数器保存的是下一次进程即将要执行的命令,在本例也就是打印printf函数中的“linux84 666”,因此父进程和子进程会打印两个“linux84 666”。
通过ps -ef可以查看进程的pid和进程的父进程的pid,具体代码和例子看本篇博客:子进程和父进程例题分析
在进程中还有和进程无关的知识点,但比较重要,想要阅读的读者可以参考这篇博客:环境变量
进程的虚拟地址空间
前提知识:父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:
- 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量;
- 但地址值是一样的,说明,该地址绝对不是物理地址!
- 在Linux地址下,这种地址叫做虚拟地址;
- 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理OS必须负责将虚拟地址转化成物理地址。
进程地址空间
所以之前说‘程序的地址空间’是不准确的,准确的应该说成进程地址空间,那该如何理解呢?看图:
说名:当fork完成之后,父进程之前的数据会被子进程通过页表结构映射到同一块物理内存上。
写时拷贝:
- 当fork的时候,如果父子进程不修改数据,则页表的映射关系不会改变。
- 当其中有一方修改数据的时候吗,为了防止导致另一方读取到的数据是错误的,所以需要在物理内存中重新开辟一段空间,保存修改后的值,并且将修改后的进程的页表结构当中的映射关系重新指向新的物理内存。由上图可知20(g_val)是新开辟的一块物理内存空间,10(g_val)是以前父进程和子进程共同指向的一块物理内存空间。
分页式
前提内容:
-
进程虚拟地址空间分成了一页一页的小块,物理内存分成了一块一块的小块,一页的大小 = 一块的大小 = 4096k
-
页表维护了页和快的关系。
概念:
计算虚拟地址通过页表映射到哪一个物理内存地址上。
注意: 上图中的虚拟地址空间指的是一块虚拟地址空间,虚拟地址空间由页号和页内偏移构成,页号通过页表上的页号找到页表中的快号,快号的起始地址加上页内偏移找到物理内存上的地址。
计算规则:
- 页号 = 虚拟地址/快的大小;
- 页内偏移 = 虚拟地址 % 快的大小;
- 快的起始地址 = 快号 * 快大小;
- 物理地址 = 快的起始位置 + 页内偏移。
分段式
虚拟地址的构成 = 短号 + 段内偏移
段页式
- 通过段号找到页的起始位置;
- 通过页的起始位置,找到对应的页表结构;
- 通过页号,找到对应的快号,通过快号,计算出快的起始位置;
- 快的起始地址加上页内偏移计算出物理地址。
}