Linux-进程概念
冯诺依曼体系结构
生活中大部分的计算机,服务器都遵守冯诺依曼体系。
- 目前所认识的计算机,都是有一个个的硬件组件组成
- 输入单元:包括键盘, 鼠标,扫描仪, 写板等
- 中央处理器(CPU):含有运算器和控制器等
- 输出单元:显示器,打印机等
- 冯诺依曼体系结构:
- 这里的存储器指的是内存
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
- 所有设备都只能直接和内存打交道。
注:
- 输入设备:键盘、网卡、磁盘、话筒……
- CPU:运算器和控制器
- 输出设备:显示器、网卡、磁盘、音响……
- 输出设备和输入设备统称为外设
- 存储器:CPU和所有外设的缓存
- 冯诺依曼规定了硬件层面上的数据流向
- 可执行程序运行时必须先加载到内存(冯诺依曼规定)
- 在数据层面:CPU并不和外设打交道,外设只和内存打交道
- QQ中传递文件:输入:磁盘、输出:网卡 、输入:网卡、输出:磁盘
- QQ中聊天:输入:键盘、输出:网卡 、输入:网卡、输出:显示器
操作系统(Operator System)
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。
操作系统包括:
- 内核(进程管理,内存管理,文件管理,驱动管理)
- 其他程序(例如函数库,shell程序等等)
操作系统是进行软硬件资源管理的软件
操作系统:
- 可以减少用户使用计算机的成本
- 对下管理好所有的软硬件,对上给用户提供一个稳定高效的运行环境(软件:进程管理、文件管理、驱动管理…… 硬件:磁盘、网卡、显卡、内存……)
- 在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
注:
- 硬件部分遵守冯诺依曼体系
- OS不信任任何用户,任何对系统硬件或者软件访问,都必须通过OS的手
- 计算机体系是一个层状结构,任何访问硬件或者软件的行为,都必须通过OS接口,贯穿OS进行访问
- 库函数:语言或者第三方库(第一方:系统的、第二方:自己的,其余是第三方的)给我们提供的接口
- 系统调用:OS提供的接口 总结:
- 计算机管理硬件:
- 描述起来,用struct结构体
- 组织起来,用链表或其他高效的数据结构
- 操作系统是进行软硬件资源管理的软件(其中管理的本质是先描述在组织(是对数据的管理))
- 管理分为三种:管理者、执行者、被管理者(eg管理者为OS、执行者为驱动程序、被管理者为底层硬件)
补:
- 系统调用:
- 系统调用把应用程序的请求传输给系统内核执行
- 系统调用函数的执行过程应该是由用户态变为内核态(又称系统态)
- 利用系统调用能够得到操作系统提供的多种服务
- 是操作系统提供给编程人员的接口
- 系统调用给用户屏蔽了设备访问的细节
- 系统调用保护了一些只能在内核模式执行的操作指令
- read是系统调用不是库函数
进程
描述进程-PCB
基本概念
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。
描述进程-PCB
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。称之为PCB(process control block),Linux操作系统下的PCB是: task_struct 。task_struct是PCB的一种
- 在Linux中描述进程的结构体叫做task_struct。
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
task_ struct内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
注:
- 在编程语言中:顺序语句、判断语句、循环语句
- CPU中有一种寄存器叫pc指针(也称EIP),它是用来记录正在执行指令的下一条指令的地址
- CPU核心工作流程:
- 取指令
- 分析指令
- 执行指令
- CPU中运行的代码都是进程的代码
- 当一个进程在运行中,因为某些原因,需要被暂时停止执行,让出CPU,需要进程保存(保存的目的是为了恢复)自己的所有的临时数据(最重要的是进程的上下文数据)
- 在每个CPU中都有一个运行队列,其中运行队列中的进程都是处在运行状态的(CPU是选择性的调度)
总结:
- OS可以一次跑起多个程序,并且OS要管理起来这些运行起来的程序,OS要对进程进行管理
- 进程控制块(PCB):struct task_struct 结构体
- OS对进程的管理转化成为了对进程信息的管理,先描述再组织,对进程的管理转化为对双链表的增删查改
- 进程=你的程序+内核申请的数据结构(PCB)
- 优先级的本质是在资源(CPU、网卡、显卡、磁盘……)有限的前提下,确立谁先访问资源,谁后访问的问题
补:
- 进程放在CPU上之后,不是一直在运行直到进程运行结束,每个进程都有一个运行时间单位——时间片
- 一般进程让出CPU:一种是来了一个优先级更高的进程(OS必须支持抢占);另一种是时间片到了
- 单CPU,单核:跑起来多个进程,通过进程快速切换的方式,在一段时间内,让所有的进程代码都得到推进——并发
- 多CPU,多核:任何时刻,允许多个进程同时执行——并行
- 进程在CPU上运行时,会有很多寄存器上的临时数据——上下文数据
- 系统感知进程的唯一实体是PBC(进程控制块)
查看进程
通过系统调用创建进程-fork
语法:
1. 创建子进程
理解fork:
- 从程序员角度:
- 父子共享用户代码(是只读的不可修改并且不可写入),而用户数据各自私有一份(目的是不让进程间相互干扰)(数据用的是写时拷贝)
- 在操作系统中,所有进程具有独立性的
- 进程具有独立性是操作系统所表现出来的,OS通过父子进程的用户数据各自私有一份,从而不让进程互相干扰来实现的
- 从内核角度:
- 进程=我的程序+内核数据结构(PCB task_struct)
- 创建子进程,通常以父进程为模板其中子进程默认使用的是父进程的代码和数据(写时拷贝)
2. fork有两个返回值
注:
- fork有两个返回值的原因是在创建子进程成功之后,子进程和父进程共享代码
- fork:子进程的返回值是0,父进程返回值是子进程的pid,因为子进程只有一个父进程,而父进程有多个子进程,需要对每个子进程进行标识(pid),并且记住他们
- fork中父进程的返回值是不会记录到用户数据当中的,这些只是给用户看的,便于记录子进程(pid)
总结:
- 运行 man fork 认识fork
- fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
- fork 之后通常要用 if 进行分流
进程状态
Linux下的内核源代码
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
- Z(zombie)-僵尸进程:
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
R状态
S状态
T状态
Z状态
注:
- 只要进程正常结束,就是正常死亡(X状态不太好演示)
- D状态在云服务器上不容易实现
- 进程是R状态,不一定是在CPU上运行,进程在运行队列中,就是R状态(进程已准备好,等待调度)
- S状态为浅度休眠(对外部事件可以做出反应),大部分情况下都是这种状态
- D状态为深度休眠(不可以被杀掉,即便是操作系统,只能等待D状态进程自动醒来,或者是关机重启(可能被卡死))
- S状态和D状态称为等待状态
- 进程退出,一般不是立刻让OS回收信息,释放进程的所有资源
- 进程创建的目的是为了将自己退出时的相关信息,写入进程的PCB中,供OS或者父进程来进行读取,读取成功后,该进程才算真正死亡
- 进程退出时,当OS或者父进程来未读取到子进程的信息时,这是处于Z状态
状态与状态+的区别
eg
补:
kill命令
kill 命令用于删除执行中的程序或工作
语法:
kill [-s <信息名称或编号>][程序] 或 kill [-l <信息编号>]
查看进程状态
第一种:
第二种:
ps aux / ps axj 命令
补:D状态和Z状态用kill命令是杀不掉的
僵尸进程危害
- 进程的退出状态必须被维持下去,因为OS关心进程是否完成了任务。可父进程如果一直不读取,那子进程就一直处于Z状态
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护
- 一个父进程创建了很多子进程,就是不回收,会造成内存资源的浪费。因为数据结构对象本身就要占用内存。(在C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!)这样就会存在内存泄漏。
孤儿进程
父进程先退出,子进程就称之为“孤儿进程”
注:
- 孤儿进程被1号systemd进程领养,当然要有systemd进程回收
- 在centos7.6中1号进程是systemd,而在centos6.5中的1号进程是initd
进程优先级
进程的优先级:
- cpu资源分配的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能
注:
- 优先级是在一定能得到某种资源,只是先后的问题
- 权限是决定你能还是不能得到某种资源
- 优先级是得到某种资源(CPU)的先后顺序,其本质是因为资源有限(CPU)
查看系统进程
- UID : 代表执行者的身份
- PID : 代表这个进程的代号
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行
- NI :代表这个进程的nice值
- TTY:运行该程序时的终端设备
- CMD:命令
注:
- Linux的优先级由pri和nice值共同确定(优先级的数值越小,优先级越高;优先级的数值越大,优先级越低)
- nice值就是优先级的修正数据,范围是[-20,19]
- 优先级不可能一味的高,也不可能一味的低(操作系统的调度器要适度地考虑平衡问题,避免“饥饿问题”)
PRI and NI
- PRI是进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小 进程的优先级别越高
- NI就是nice值了,其表示进程可被执行的优先级的修正数值
- PRI值越小越快被执行,PRI(new)=PRI(old)+nice
- 当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
- 调整进程优先级,在Linux下,就是调整进程nice值
- nice其取值范围是-20至19,一共40个级别。
- 进程的nice值不是进程的优先级,但是进程nice值会影响到进程的优先级变化。
- nice值是进程优先级的修正修正数据
注:
- nice的值的范围[-20,19],是一种可控范围,原则上OS内的调度器,要公平(不是平均)且较高效的调度
- PRI(new)=PRI(old)+nice中的PRI(old)默认是80,因为有一个基准值,便于调整;在设计上实现起来比较方便
- 在Linux系统中,标识一个用户,并不是通过用户名标识的(是给用户看的),而是通过用户的uid(计算机比较善于处理数据)
查看进程优先级的命令
用top命令更改已存在进程的nice:
- top
- 进入top后按“r”–>输入进程PID–>输入nice值
注:
- 用top命令更改已存在进程的nice,如果输入的值大于19,NI的值就是19;如果输入的值小于-20,NI的值就是-20。
- 无论怎么输入NI的值,最终PRI的值为[60,99]
补充概念
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
注:
- 进程与资源之间,进程永远是多数的
- 竞争性和独立性是进程运行的特殊属性(或原则);并行和并发是计算机的调度特性
环境变量与命令行参数
Linux下的环境变量与命令行参数
进程地址空间(灵魂四问)
进程地址空间的分布
注:
- 进程地址空间不是内存地址空间
- 进程地址空间,会在进程的整个生命周期内一直存在,直到进程退出
什么是进程地址空间
#include<stdio.h>#include<unistd.h>#include<stdlib.h>int g_val =0;intmain(){
pid_t id =fork();if(id <0){perror("fork");return0;}elseif(id ==0){//child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
g_val=100;printf("child[%d]: %d : %p\n",getpid(), g_val,&g_val);}else{//parentsleep(3);printf("parent[%d]: %d : %p\n",getpid(), g_val,&g_val);}sleep(1);return0;}
运行结果:
child[3046]:100:0x80497e8
parent[3045]:0:0x80497e8
父子进程中的g_val的地址竟然是一样的
注:
- 任何的编程语言里面的地址,绝对不是物理地址,而是虚拟地址(C++/C语言中的&得到的是虚拟地址不是物理地址)
- 虚拟地址是操作系统提供的,数据和代码一定在物理内存上(冯诺依曼规定),因此需要将虚拟内存转化成物理内存(由OS自动完成)
- 父子进程代码共享,而数据是各自私有一份的(写时拷贝)
- 当所有程序运行起来之后,该程序立即变成进程
总结:
- 地址空间本质是进程看待内存的方式,是抽象出来的一个概念,内核struct mm_struct,这样的每一个进程,都认为自己独占系统内存资源
- 区域划分本质:将线性地址空间划分成为一个一个的area,[start,end]
- 虚拟地址本质:在[start,end] 之间的各个地址叫做虚拟地址
为什么要存在地址空间
第一种情况:
- 如果进程直接访问物理内存,那么所看到的地址就是物理地址,如果进行指针越界访问,那么进程间的独立性就无法保证。
- 因为物理内存一暴露,其中就有可能有恶意程序直接通过物理地址进行内存数据篡改,并且可以读取里面的内容
第二种情况:
- 当一个程序变为进程时,需要将可执行程序加载到内存中;当进程退出时,也需要内存知道,因此内存管理需要知道进程是运行还是终断、退出
- 因此内存管理模块和进程模块是强耦合的
- 内存管理只需知道哪些内存区域(page)是无效的,哪些区域是有有效的,只需将内存管理和进程管理进行解耦
第三种情况:
- 磁盘在存储二进制可执行程序时,是进行一个个区域划分的(代码区、全局数据区、只读数据区)这些区域是以4KB为单位划分的(页帧),而物理内存是没有按照磁盘划分顺序划分的(按照页(一页是4KB)为单位划分的)
- 如果进程想要执行代码,OS需要在物理内存中毫无章法的乱找(除非OS开辟一块连续的物理内存(不现实)),这时,虚拟地址空间是几乎是按照磁盘区域划分的,它可以经过页表的映射来找到代码的位置并顺利执行代码
注:
- 两个不同的进程虚拟地址可以完全一样
- 页表:
- 页表是完成虚拟地址到物理地址的映射
- 将虚拟地址到物理地址的转化
- 同时也可以帮系统进行合法性检测
- 内存管理模块和进程管理模块是强耦合的
- 操作系统:进程管理、文件管理、内存管理、驱动管理
- 可执行程序,其本身就已经划分成为一个个的区域(.code、.data、.bss、.readonly……),这样划分便于程序链接,而代码在用编译器编译时是将数据和代码放在一起
- 页框:页框数=物理内存大小/每一个分配内存的大小(4KB),页框以4KB位单位
- 页帧:在磁盘一个个程序被划分成4KB大小的数据
- 页号:页表项的序号
- 执行顺序语句的过程:当前语句的起始地址+当前代码的长度(虚拟地址存放代码是存放在连续区域的)
- 虚拟地址空间不存在存放地址的功能,因此需要用户必须保存起来(避免内存泄露)
- 进程与程序的区别:
- 程序是静态的,进程是动态的,程序是存储在某种介质上的二进制代码,进程对应了程序的执行过程,系统不需要为一个不执行的程序创建进程,一旦进程被创建,就处于不断变化的动态过程中,对应了一个不断变化的上下文环境。
- 程序是永久的,进程是暂时存在的。程序的永久性是相对于进程而言的,只要不去删除它,它可以永久的存储在介质当中。
总结:
- 保护物理内存,不受到任何进程内的地址直接访问,方便进行合法性校验
- 将内存管理和进程管理进行解耦
- 让每个进程,以同样的方式来看待代码和数据
地址空间与物理内存之间的关系
虚拟地址和物理地址之间是通过页表完成的映射关系
回答上面代码(为什么地址一样?)的问题
- 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
- 但地址值是一样的,说明,该地址绝对不是物理地址
- 在Linux地址下,这种地址叫做 虚拟地址
- 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理,OS必须负责将 虚拟地址 转化成 物理地址
- 同一个变量,地址相同,其实是虚拟地址相同(原因是fork出一个子进程,其中子进程会继承大部分父进程的信息(代码指向同一块空间,数据也指向同一块空间),因此g_val的地址也被继承了,但是当子进程试图修改g_val值时,这是需要用到写时拷贝将父进程的数据拷贝一份给子进程(开辟新的空间)并修改了子进程中的g_val的值),因此内容不同其实是被映射到了 不同的物理地址!
补:
- TCB: 线程控制块
- MMU:内存管理单元,一种负责处理中央处理器(CPU)的内存访问请求,功能包括虚拟地址到物理地址的转换(即 虚拟内存管理)、内存保护、中央处理器高速缓存的控制
- CACHE: 高速缓存
- DMA: 直接内存存取
版权归原作者 The August 所有, 如有侵权,请联系我们删除。