●🧑个人主页:你帅你先说.
●📃欢迎点赞👍关注💡收藏💖
●📖既选择了远方,便只顾风雨兼程。
●🤟欢迎大家有问题随时私信我!
●🧐版权:本文由[你帅你先说.]原创,CSDN首发,侵权必究。
📌📌📌为您导航📌📌📌
1.冯诺依曼体系结构
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
截至目前,我们所认识的计算机,都是由一个个的硬件组件组成
输入单元:包括键盘, 鼠标,扫描仪, 写板、磁盘等
中央处理器(CPU):含有运算器和控制器等
存储器:也就是内存,不是磁盘。
输出单元:显示器,打印机等
关于冯诺依曼,必须强调几点:
- 这里的存储器指的是内存
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
- 一句话,所有设备都只能直接和内存打交道。
从冯诺依曼体系来解释两个人在QQ上发消息的过程:
2.操作系统
概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
- 内核(进程管理,内存管理,文件管理,驱动管理)
- 其他程序(例如函数库,shell程序等等)
设计OS的目的
- 与硬件交互,管理所有的软硬件资源
- 为用户程序(应用程序)提供一个良好的执行环境
定位
- 在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
操作系统是什么?
一款专门针对软硬件资源进行管理工作的软件
为什么要有操作系统?
对下管理软硬件,对上给用户提供稳定的、高效的、安全的运行环境。
我们一直在强调操作系统的功能是管理,但管理究竟是什么?六个字概括:先描述,再组织
。
总结
计算机管理硬件
1.描述起来,用struct结构体
2.组织起来,用链表或其他高效的数据结构
系统调用和库函数概念
- 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
- 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
3.进程
3.1 基本概念
- 课本概念:程序的一个执行实例,正在执行的程序等。(简单来说就是加载到内存的程序,就叫进程)
- 内核观点:担当分配系统资源(CPU时间,内存)的实体。
OS上面,PCB,进程控制块,就是一个结构体类型。在Linux系统中,PCB是struct task_struct
描述进程-PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
为什么要有PCB?有了刚刚上面对管理的概括,你就能理解为什么要有PCB了,因为管理是先描述,再组织。
PCB就是来描述进程的。
task_struct-PCB的一种
- 在Linux中描述进程的结构体叫做task_struct。
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
进程和程序有什么区别?
有了进程控制块,所有的进程管理任务与进程对应的程序毫无关系,与进程对应的内核创建的该进程的PCB强相关。
查看进程
我们所有启动程序的过程,本质都是在系统上面创建进程。
显示进程编号
终止进程
kill -9 进程编号
getpid()是查看当前进程的编号,getppid()可以查看父进程的编号,当你查看时你会发现这个进程的父进程就是bash。在命令行上运行的进程,父进程基本都是bash。
task_ struct内容分类
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。比如return 0。返回的这个0可以理解成就是退出代码
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据。
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
3.2 如何理解上下文数据?
在Windows中,你可以在任务管理器中查看一个个进程
很多人都会有一个误区,一个CPU上可以运行多个进程,但实际上一个CPU一次只能运行一个进程,那为什么我们可以同时看到那么多进程在运行,你有没有想过,假如规定一个进程单次运行的时间片为10ms,10ms对于我们来说是非常短的,每过1s,其实就过去了100个10ms,所以我们能看到多个进程在同时运行,本质上是通过CPU的快速切换来完成的。但CPU快速切换会有一个问题,CPU只有一个,只能保存一份临时数据,而进程的代码可能不是短时间内可以运行完的,比如规定每个进程只能运行10ms,而我这个进程可能需要50ms才能运行完,但突然要切换到下一个进程,然后这个在CPU上存储的数据就会被新的进程给覆盖,这样下一次切换到此进程时就找不到原来的数据了,所以就有了一个上下文数据保护机制,在切换到下一个进程前,进程要把CPU寄存器中的数据带走,等到下一次再切换到此进程时,就可以继续运行未完成的进程。
3.3 fork()创建子进程
fork()的作用是通过系统调用创建进程。
运行后,你会惊奇的发现居然有两个结果,原因是因为有两个进程同时执行了这段代码,一个是系统本身的进程,一个是我们刚刚创建的进程。
如何理解fork()创建子进程?
1.在操作系统角度,创建进程的方式是没有区别。
2.fork()本质是创建进程,系统里多了一个进程,与进程相关的内核数据结构和进程的代码和数据在系统里多了一份。
3.新创建出来的进程会"继承"父进程的代码和数据,内核数据结构也会以父进程为模板,初始化子进程的task_struct
。虽然父进程和子进程的数据是共享的,但如果其中一个进程要写入数据就与另一个进程无关,操作系统主要是通过"写时拷贝"来完成进程数据的独立性,
你可以把父进程和子进程想象成两个指针,都指向同一块空间,但子进程突然想修改数据,这个时候操作系统就会把这块空间拷贝一份,让子进程指向它,子进程再对这块空间的数据进行修改。如果一直没有人修改父进程或子进程,那么它们就只会共享这一份数据,不会再拷贝一份出来。
4.子进程从fork()之后开始往下执行。
如何理解有两个返回值?
pid_t fork()
{
//创建子进程的逻辑
return xxx;
}
前面说过,子进程创建完之后,父子共享一份代码和数据。当创建子进程成功时,给父进程返回子进程的pid,给子进程返回0。失败时,返回一个负数。因为返回值是数据,所以当返回后发送了数据的写入操作,即写时拷贝,此时父子进程不再共享同一份资源。接下来我们具体来看看怎样使用多进程。
3.4 进程状态
ps aux / ps axj 命令(| grep 文本)
进程状态
进程状态的意义:方便OS快速判断进程,完成特定的功能,比如调度,本质是一种分类。
- R运行状态(running):
并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做
可中断睡眠
(interruptible sleep)。浅度睡眠
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
深度睡眠,此状态无法被操作系统给结束掉。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
这个进程的资源即进程相关的内核数据结构和代码数据都会被回收。
⭐️⭐️⭐️⭐️⭐️
针对于S状态,如果你写一段死循环输出信息的代码,再去查看进程状态,你会发现程序处于S状态,这是为什么?
因为输出结果是需要显示在屏幕上的,而IO等待外设是需要花费时间的,这个等待时间在系统看来是非常慢的,所以我们查看时,大部分都是处于S状态。
所谓的进程,在运行的时候,有可能因为运行需要,可能会在不同的队列里(run_queue,wait_queue等等)。
这张图现在来看大家可能会很懵,别急,我们慢慢解释。
首先,我们把从运行状态的task_struct放到等待队列中,就叫做挂起等待(阻塞)。
反过来,从等待队列,放到运行队列,被CPU调度的过程就叫做唤醒进程。
这张图上的过程分别对应什么状态?
挂起(等待)状态对应于S、D状态,运行态和就绪态对应R状态,就绪态就是运行队列里正在运行的进程,而就绪态则是准备运行的进程。
3.5 僵尸进程&&孤儿进程
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程,僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态。维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护,那一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费,因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间。
为什么要有僵尸状态?
因为进程处于僵尸状态时,资源还没有被回收,所以进程的退出信息暂时被保留在了task_struct中,直到父进程读取了子进程的状态后子进程才退出。换句话说,一个进程的正常退出流程是先变成Z状态再变成X状态。
代码模拟僵尸进程
在父进程休眠期间,我们用kill杀死子进程,但此时父进程还没有休眠完成,没法读取子进程的退出状态信息。
孤儿进程
父进程先退出,子进程后退出就称之为“孤儿进程”,孤儿进程被1号进程领养,由1号进程回收。
注意:1号进程指的是init进程,并不是指bash。
模拟孤儿进程状态
在子进程运行期间,让父进程先退出,就会变成孤儿进程,从图中我们发现,最终这个进程的父进程变成了1号进程(即操作系统)。
4.进程优先级
- cpu资源分配的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
4.1查看系统进程
ps -l
在Linux上我们经常会看到S+,这个+表示在前台运行,没有+表示在后台运行。
我们很容易注意到其中的几个重要信息,有下:
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI:代表这个进程的nice值
在这里说一下这个nice值,其表示进程可被执行的优先级的修正数值
,PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。所以,调整进程优先级,在Linux下,就是调整进程nice值,nice其取值范围是-20至19
,一共40个级别。需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
4.2用top命令更改已存在进程的nice
在root用户下
top
进入top后按“r”–>输入进程PID–>输入nice值
注意:虽然我们前面说PRI(new)=PRI(old)+nice
,但实际上,这个PRI(old)每次调整都默认为80。
不知道大家有没有想过,为什么nice值的范围是一个相对小的范围?
优先级再怎么设置,也只能是一种相对的优先级,不能出现绝对的优先级,否则会出现很严重的进程"饥饿问题"。系统调度器会较为均衡的分配资源给每个进程。
进程的几个性质
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高。
效完成任务,更合理竞争相关资源,便具有了优先级。
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
5.环境变量
Linux已经讲了这么多了,不知道大家有没有一个疑问,为什么我们自己写的程序要加./
而系统命令则可以直接写呢?ls、pwd、ll等命令都没有./,而执行C语言程序则要加上./
。其实这跟环境变量有关。
这里边以冒号为分隔符分出了好几个命令,当我们执行系统命令时,系统就会一个个从这个路径进行寻找,也就是说,当我们执行系统命令时,系统有一条自己的专属路径,我们执行自己写的程序要加./
是因为./
要在当前路径寻找,然后执行,这跟系统命令的原理是一样的,只是系统已经规定了在哪个路径找,我们直接敲命令就行。
5.1基本概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性,即环境变量可以被子进程继承。
5.2常见环境变量
PATH : 指定命令的搜索路径
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL : 当前Shell,它的值通常是/bin/bash
除了环境变量,一般系统中也会有本地变量,但本地变量只在本次登录中有效。
5.3和环境变量相关的命令
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
显示某个环境变量
echo $环境变量名
添加新路径
export PATH=$PATH:路径
显示所有环境变量
env | grep 变量名
显示本地定义的shell变量和环境变量
set | grep 变量名
取消环境变量
unset 环境变量
5.4命令行参数
此时会把你的命令都显示出来。
那这个命令行参数有什么用呢?
我们在学习Linux时,指令有很多选项,比如ls -l
,用来完成同一个命令不同子功能选项底层使用的就是命令行参数。
5.5获取环境变量
main函数还有第三个参数,env,可以通过这个参数获取环境变量。
除了这种方式,还有一种不用给main函数传参的方式也可以获取环境变量。
最后一种获取方式是通过getenv获取
getenv("环境变量名")
5.6环境变量的组织方式
环境变量通常具有全局属性,可以被子进程继承下去。
#include <stdio.h>
#include <stdlib.h>
int main()
{
char * env = getenv("MYENV");
if(env)
{
printf("%s\n", env);
}
return 0;
}
直接查看,发现没有结果,说明该环境变量根本不存在,添加环境变量到系统,export MYENV=“hello world”,再次运行程序,发现有结果了,说明:环境变量是可以被子进程继承下去的。
喜欢这篇文章的可以给个一键三连
点赞👍关注💡收藏💖