Linux:进程概念详解
一、冯诺依曼计算机体系
(一)体系概念
冯诺依曼将计算机结构分为上面的五个部分:
- 储存器:一般是指内存空间,平时我们用到的磁盘是输入/输出设备。
- 控制器:控制和协调计算机各个模块的工作,与储存器合并称之为 CPU,控制计算机处理数据。
- 运算器:执行算术和逻辑运算。
- 输入设备:用于将数据和指令输入到计算机中,如键盘、鼠标等。
- 输出设备:用于将计算机处理后的结果输出,如显示器、打印机等。
为何在体系中加入储存器,如果输入设备的资源直接交给运算器不是更好吗?:
为了能够持久地保存数据,需要一个可靠的、容量较大的存储器来存储数据和指令。存储器可以提供高速、可靠的数据存储,使得计算机能够随时使用之前存储的数据。
当代计算机是性价比的产物:
为了使得内存实现高速缓存,同时价格不能太贵,计算机内存一般分为下面几个区域,平时 CPU 在运算时直接提取在寄存器中的数据,使得运算速度得以提高。
(二)计算机之间的数据传输
计算机之间的数据传输可以看作是两个冯诺依曼计算机之间进行文件的拷贝。
二、操作系统
(一)操作系统设计的意义
- 资源管理: 操作系统负责管理计算机硬件资源,操作系统可以提高计算机的利用率和性能。(对下:管理软硬件资源)
- 提供接口和服务: 操作系统为用户和应用程序提供了一种交互和访问系统资源的方式。它通过图形界面或命令行界面提供给用户一个友好的用户界面,使得用户能够方便地使用计算机系统。(对上:实现与用户良好的交流界面)
(二)操作系统的管理功能
操作系统在管理时采取先描述,再组织的形式。即在管理内存,文件,进程等,先用一个struct
来描述相关的信息,接着在使用相关的数据结构将他们逻辑的链接起来。
(三)系统调用的实质
系统调用使用相关的系统调用函数,其中函数的形参是用户传入给操作系统的数据,返回参数是操作系统将相关形参交给硬件系统运算后得到的数据,操作系统将其返回给用户。
三、进程详解
(一)进程概念
进程是由两个部分组成,即PCB + 程序代码和数据。
PCB指的是一个管理进程的结构体,在
linux
中,它是一个tast_struct
结构体,其中主要的成员如下:
-
标示符: 描述本进程的唯一标示符,用来区别其他进程,查看当前进程的标识符使用
getid()
,使用ps
,top
,可以查看进程的信息。 -
状态: 任务状态,退出代码,退出信号等。
-
优先级: 相对于其他进程的优先级。
-
程序计数器: 程序中即将被执行的下一条指令的地址。
-
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
-
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
-
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
-
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
(二)进程相关操作
1、获取进程标识符 pid
2、显示进程信息
-
PS查询:
因为在使用grep
查询时,指令本身也是一个进程,因此这里会出现两个进程。
-
使用
top
查询:
-
通过目录查询:
进程产生后生成相关的目录,可以使用ls /proc
查询。
此时我们可以更进一步查看进程里面有些什么文件。
exe: 当前进程的可执行程序位置。
cwd:current work dir当前程序所在的路径,我们之前在学C语言的文件操作时,所提到的默认路径就是这个
如果想更改默认路径可以使用chdir
函数。
3、结束进程
- 使用
kill -9 进程名
杀死进程。
- control + c 杀死进程。
4、创建进程
我们可以使用函数fork
,来手动创建一个进程,创建后第二句话由父进程和子进程分别打印一次。
子进程没有自己的代码和数据,共用一份代码,写时拷贝。
fork函数要点:
1、为什么fork
会给父子不同的返回值?
父 : 子 = 1 : n
,在后续的使用中,子进程需要父进程去回收,因此这个是为了标识不同的进程。
2、为什么一个函数返回两次?
在进行fork
函数时,已经创建了两个进程,但是还没有执行到返回值的位置,后续两个进程都会有返回值。
3、为什么一个变量又等于零,又大于零?
子进程与父进程的代码虽然相同,但是数据不同。进程具有独立性,也称之为写时拷贝。
(三)进程状态
1、操作系统统称的进程状态
通常来说,操作系统中统称的进程状态有三种——运行、挂起、阻塞。
- 运行状态: 当进程处于调度队列中,那么它就处在运行状态。
- 阻塞状态: 进程在被CPU执行时,如果进程所需的硬件没有准备就绪,时间片又用完了,此时进程就会进入设备队列的等待队列。
- 挂起状态: 内存资源严重不足时,在调度队列和设备等待队列中有些进程占用着空间不办事,此时操作系统只将
PCB
保存下来,将代码和数据转移到磁盘中的一个swap
分区之中。
2、Linux中特定的进程状态
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
-
R:进程在前台执行时状态为R+,在后台执行时为R
使用下面的语句可以使得进程后台运行。
-
S:浅休眠状态,可以使用
control + c
停止,对应阻塞。 -
D:深休眠状态,此时进程不能被操作系统停止,此时只能等待进程自己醒来,或者重启操作系统/断电。
-
T:暂停状态,
control + c
引起的。 -
t :暂停状态,
gdb
调试引起的暂停状态。 -
x:死亡状态,进程回收。
-
Z:僵尸状态,进程结束后到回收前,处于僵尸状态主要是为了获取进程结束后的信息。
僵尸进程及危害:
当一个子进程结束后,我们不对其进行回收,就会产生僵尸进程。僵尸进程会造成内存泄漏的问题。
孤儿进程:
如果父进程比子进程先结束,此时子进程称为孤儿进程。子进程由于没有父进程,后面容易成为僵死进程,因此我们需要对孤儿进程进行领养,由1
号进程进行领养,故此时子进程的父进程是一号进程。
(四)进程优先级
1、进程优先级概念
CPU
资源分配的先后顺序,就是指进程的优先权(priority)。优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux
很有用,可以改善系统性能。
进程的优先级可以变化但是幅度不能太大。
查看进程的优先级:
PRI
:进程的实际优先级NI
:进程优先级的偏移量,进程的优先级PRI
=NI
+ 80(优先级默认值)。UID
:进程启动者的id
。
我们之前在创建文件时,系统实际保存的是创建者的
id
,通过进程打开文件,比较进程启动者的id
和拥有者、所属组、其它 作比较,来识别当前的身份。(进程代表用户)
2、更改进程的优先级
我们可以通过更改nice
的值间接的更改进程优先级。
- 通过
top
命令,输入r
,接着输入进程的标识符即pid
,然后就能输入更改后的nice
值了。
查看优先级信息,发现此时已经将nice
更改为10,PRI
=NI
+ 80 = 90.
renice
更改优先级
系统不允许我们频繁的更改nice
值,需要使用指令提权。
进程的优先级也是有范围的,范围是
60 ~ 99
。这是因为优先级设立不合理,会导致优先级低的进程长期得不到执行,进而导致进程饥饿
3、补充概念-竞争、独立、并行、并发
- 竞争性:进程通常有很多个,而
CPU
资源通常只有一个。为了高效使用CPU
资源,进程之间就形成了竞争关系。 - 独立:各个进程之间独立使用进程资源,期间互不干扰。
- 并行:多个进程在多个
CPU
下运行。 - 并发:多个进程在一个
CPU
下运行,通过进程切换,一段时间内多个进程都得以进行。
(五)进程切换和进程调度
CUP和寄存器浅谈:
寄存器是CPU
内部的临时地址空间,寄存器类似定义的变量,寄存器里面可以存储数据。
1、进程切换
进程在利用CPU
资源时,进程有一个时间片,如果时间片的时间被执行完,那么调度器就会进行进程切换。具体做法是将进程运行的临时数据从寄存器提取到进程,称之为保留进程上下文数据,之后当再次轮到执行该进程时,我们从进程中恢复进程上下文数据到寄存器。
-
上下文数据保存到了哪里?
保存到进程里面的task_struct
里面,里面有一个TSS
任务状态段的结构体。
-
调度器的作用
进程切换,调度进程。
2、进程调度
一个CPU
有一个运行队列,它来自于queue[140]
。
- 实时操作系统:实时操作系统是
CPU
在执行进程时,按照优先级顺序,从优先级高的(数字小)的开始执行,必须执行完一个进程才能进行下一个。例如刹车。 - 分时操作系统:实时操作系统是
CPU
在执行进程时,按照优先级顺序,从优先级高的(数字小)的开始执行,时间片执行完毕就能进行下一个。例如我们的windows
。
(六)环境变量
1、环境变量概念
- 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但
是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
(1)命令行参数
之前我们在写main
函数时,一直不知道右侧的参数作用是什么,前两个的名字是命令行参数,实际就是字符指针数组,前者传入的是字符指针数组的大小argc
。
作用:
让一个程序通过选项实现不同的子功能,下面的代码实际上模仿了系统中指令的实现。
(2)初步理解环境变量
到了这里我们就能理解为什么我们平时使用指令时为啥可以带选项了,可是为什么我们在执行系统指令时不用加./
呢?
因为我们在执行自己写的指令时,需要指明当前路径系统才能找到可执行程序的地址。而系统中的指令有
环境变量
。来为我们找到程序所在的地址。
既然指令在/usr/bin/
目录下系统可以自己找到,那么我们把自己的可执行程序也移动到该目录下,就不用再加./
了,我们进一步理解环境变量的的作用,帮助我们到指定的路径下去找指的的文件
2、认识环境变量
在linux
中,我们可以使用env
来查看系统中的环境变量。环境变量的种类有很多,定义格式是名字 + 内容
。
-
PATH
查看单一的环境变量也能使用指令echo + $名字
,环境变量PATH
就是控制管理平时系统查找指定程序的路径,通常是从前向后依次查询,其中:
作为分隔符。
-
USER
当前登录用户。 -
HOME
当我们使用指令cd ~
时,~
就是当前家目录的路径。 -
SHELL
就是bash
进程的路径。
-
HISTSIZE
历史命令条数限制。
-
PWD/OLDPWD
当前路径和上一次访问路径。
3、更改 bash 中的环境变量
那么如果我们不想使用./
方式运行程序,我们可以又多了一种方式,将当前路径添加到PATH
环境比变量中去。
但是这种方式是赋值,那么系统中的指令不是就跑不起来了吗,所以我们要使用类似+=
的方式来修改环境变量。
4、环境变量的来源–系统文件
(1)模拟可执行程序寻找环境变量
当我们登录linux
的时候,操作系统为当前窗口分配一个bash
进程,这个进程会读取系统中的环境变量形成一张环境变量表。
我们在键盘输入指令如ls -a -b
时,bash
会读取形参一张命令行参数表,接着拿着这张参数表去和环境变量表中的PATH
一个一个对比,直到找到相关的命令执行。
(2)环境变量表的来源
环境变量表是从系统中相关的配置文件中来的,在用户的家目录下,分别是bash_profile
和.bashrc
通过这两个文件,我们就知道系统是先调用bash_profile
,里面间接调用.bashrc
,接着再去调用/etc/bashrc
进行初始化。我们如果想要更改bash
默认初始化的环境变量,可以将第10行代码改为第12行代码。
5、添加 & 获取环境变量到系统文件
(1)添加 / 取消环境变量
-
1、export + 环境变量定义
想要取消使用unset + 环境变量名称
即可。
-
2、系统调用 getenv / putenv
根据环境变量的名字返回环境变量的内容。
根据这个继承的原理,我们可以设计一个个性化的只有某某能够执行的程序。
(2)获取环境变量
- 1、通过进程继承环境变量
当我们执行当前代码时,main
的第三个参数会继承父进程bash
的环境变量。
- 2、通过全局变量
environ
获取
(3)本地环境变量
bash
会记录两套变量,一套是环境变量,另一套是本地变量,本地变量不会被子进程继承,只在bash
内部使用。
为什么
bash
要有本地变量呢?使得
bash
本身变成一门语言,我们可以直接在bash
上面写一下简单的程序。
(七)程序地址空间
1、虚拟地址空间
(1)概念引入
每一个进程在运行时都会分配一个虚拟地址空间,地址从下到上依次减小。在 32 位机器中,虚拟地址空间总共有 2^32个地址,共 4G。
实际上,linux
内核的地址空间是一个mm_struct
结构体。下面的结构体中记录了每个空间的起始地址。
事实上,mm_struct
里面的开始和结束是每个区域整体的划分,在每一个区域中又分别划分成很多小区域,
(2)虚拟内存空间和内存空间关系
这个内存空间不是真实的内存,这一点我们可以通过下面的代码看出来。
我们发现子进程和父进程的地址是一样的,但是相同地址的值却不相同,进一步证明了当前地址是虚拟地址。
2 、磁盘加载程序过程
(1)页表概念
我们在创建变量后,会在内存中存在一个真实的物理空间,同时虚拟地址空间会记录下这个地址空间的地址,我们在访问变量时只需要拿着这个虚拟地址空间就能找到它的实际物理空间。储存这个映射的表格就叫做页表。
子程序会继承环境变量,同时也会继承这张页表,是通过浅拷贝,如果直接读取访问数据,那么内存不会有变化。但是子进程如果需要写入数据,在写入时操作系统会重新为变量分配内存,页表中对应的地址就会发生变化。
(2)加载过程
磁盘中的程序在被执行时,首先要在物理内存空间中开辟代码和数据所需的地址空间,再去虚拟地址空间的相应位置开辟空间,接着我们再去填充页表,虚拟内存和内存间就建立了一一映射的关系。
3、虚拟地址空间的意义
- 将地址从无序变得有序:
我们的程序在加载到物理内存后,它的内存空间可能是断断续续的。然而虚拟内存空间却是可以将它变成逻辑上连续的内存。 - 页表可以保护内核:
页表中其实还有一个控制rwx
的权限位,通过判断用户有没有这份权限,页表判断要不要转换到物理地址。
情景模拟:
1、我们之前的野指针问题就是页表访问真实的物理空间的时候转换失败,因此就发生空指针访问失败的问题。
2、const
修饰的字符串常量不可被修改也是页表权限控制的。
- 让进程管理和内存管理进行解耦
原文地址:https://blog.csdn.net/2301_81454749/article/details/144976686
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!