目录
冯诺依曼体系结构
操作系统
如何理解“管理”
系统调用和库函数的概念
进程
描述进程-PCB
查看进程
通过系统调用获取进程标识符
通过系统调用创建进程-fork初识
进程状态
操作系统--进程状态
僵尸进程和孤儿进程(点击)
进程优先级
概念
查看系统进程
其他概念
环境变量
常见环境变量
环境变量相关的命令
环境变量的组织方式
通过代码如何获取环境变量
获取环境变量
程序地址空间
进程地址空间
问题总结
关于冯诺依曼,必须强调几点:
- 这里的存储器指的是内存。
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入输出设备)
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
- 一句话,所有设备都只能直接和内存打交道。
为什么要有内存?(内存在我们看来就是体系结构的一个大的缓存,适配外设和CPU速度不均的问题)
- 技术角度:CPU的运算速度>寄存器的速度>L1~L3Cache>内存>>外设(磁盘)>> 光盘磁带
- 数据角度:外设不和CPU直接交互而是和内存交互,CPU也是如此。
- 成本角度:寄存器>>内存>>磁盘(外设)
操作系统
操作系统包括内核和其他程序,它是与硬件交互,管理所有的软硬件资源,为用户程序提供一个良好的执行环境。在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件。
操作系统如何提供服务?
操作系统不相信任何人的,不会直接暴漏自己的任何数据结构代码逻辑,其他数据相关的细节。操作系统是通过系统调用的方式,对外提供接口服务的。Linux操作系统是用C语言写的这里的接口,本质就是C函数。
如何理解“管理”
管理的本质:不是对被管理对象进行直接管理,而是只要拿到被管理对象的所有的相关数据,我们对数据的管理,就可以体现对人的管理。
系统调用和库函数的概念
- 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
- 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
进程
操作系统里可能同时存在大量的进程,那么操作系统是怎么进行管理的?先把进程描述起来,再把进程组织起来。
进程 = 可执行程序 + 该进程对应的内核数据结构
描述进程-PCB
- 进程信息被放在一个叫做进程控制块的数据结构中。
- Linux操作系统下的PCB是:task_struct
task_struct是Linux中描述进程的结构体,是Linux内核的一种数据结构,会被装在到RAM里并包含着进程的信息。所有运行在系统里的进程都以task_struct链表的形式存在内核里。
task_struct内容分类:
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
查看进程
进程的信息可以通过 /proc系统文件查看
可通过ps用户级工具来获取进程信息。
通过系统调用获取进程标识符
通过系统调用创建进程-fork初识
为什么会打印if条件中的两个printf语句?
fork()之后,父进程和子进程会共享代码,一般都会执行后续的代码,fork()之后,父进程和子进程返回值不同,可以通过不同的返回值判断让父子执行不同的代码块。fork()之后,给父进程返回子进程的pid。
操作系统--进程状态
- 运行状态:进程只要在运行队列中就叫做运行状态,代表我已经准备好了,随时可以调度
- 终止状态:该进程还在,只不过永远不运行了,随时等待被释放
-
进程阻塞:1、一个进程,使用资源的时候,可不仅仅是在申请CPU资源
2、进程可能申请更多的其他资源:磁盘、网卡、显卡、显示器资源、声卡/音响
如果我们申请CPU资源,暂时无法得到满足。需要排队的----运行队列
如果我们申请其他慢设备的资源-----也是需要排队。
进程等待某种资源(非CPU),资源没有就绪的时候,进程需要在该资源的等待 队列中进行排队,此时进程的代码并没有运行,进程所处的状态就叫做阻塞。
-
进程挂起:短期内不会被调度(你等的资源,短期内不会就绪)进程,它的代码和数据依旧 在内存中,就会浪费空间,OS就会吧该进程的代码和数据置换到磁盘上。
Linux操作系统--进程状态
- R运行状态(running):进程只要在运行队列中就叫做运行状态。并不一定说进程一定在运行中。
- S睡眠状态(sleeping):意味着进程在等待时间完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped):可以通过发送SIGSTOP信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
- Z僵尸状态(zombie):当一个Linux中的进程退出的时候,一般不会直接进入X状态,而是进入Z状态
僵尸进程和孤儿进程(点击)
进程优先级
概念
- cpu资源分配的先后顺序,就是指进程的优先权。
- 优先权高的进程有优先执行的权利。
- 可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
查看系统进程
使用ps -l命令来查看进程
如上图我们可以获取到一些信息:
- UID:代表执行者的身份
- PID:代表这个进程的代号
- PPID:代表这个进程是由那个进程发展衍生而来的,亦即父进程的代号
- PRI:代表这个进程可被执行的优先级,其值越小越被执行
- NI:代表这个进程的nice值
nice值:表示进程可被执行的优先级的修正数值,加入nice值后,PRI会变为:PRI(new)=PRI(old)+nice,调整进程优先级就是调整nice值。注意:进程的nice值不是进程的优先级。
其他概念
- 竞争性:系统进程数目众多,而CPU资源只有少量,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
- 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。
- 并行:多个进程在多个CPU下分别同时进行运行,称为并行。
- 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
多个进程都在系统中运行 != 多个进程都在同时运行
环境变量
环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数
常见环境变量
- PATH:指定命令的搜素路径
- HOME:指定用户的主工作目录
- SHELL:当前shell
环境变量相关的命令
- echo:显示某个环境变量值
- export:设置一个新的环境变量
- env:显示所有环境变量
- unset:清除环境变量
- set :显示本地定义的shell变量和环境变量
环境变量的组织方式
每个程序都会收到一张环境变量,环境变量是一个字符指针数组,每个指针指向一个以‘\0'结尾的环境字符串。
通过代码如何获取环境变量
①、命令行第三个参数
int main(int argc, char *argv[], char *env[])
②、通过第三方变量environ获取
int main(int argc, char *argv[])
printf("%s\n", environ[i]);
获取环境变量
printf("%s\n", getenv("PATH"));
环境变量通常是具有全部属性的,是会被子进程继承下去的。本地变量本质就是在bash内部定义的变量,不会被子进程继承下去。
程序地址空间
从第一段程序,我们发现,输出出来的变量值和地址一模一样,这是因为子进程按照父进程模板,父子并没有对变量进行任何修改。
从第二段程序,我们发现,父子读取同一个变量,地址是一样的,但是后续有人修改了变量的值,所以父子读取到的内容是不一样的。可以得到以下结论:
- 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
- 地址是一样的,说明,该地址绝对不是物理地址
- 在Linux地址下,这种地址叫做虚拟地址
- 我们在用C/C++语言所看到的地址,全部都是虚拟地址,物理地址,用户一概看不到,由OS统一管理
OS必须负责将虚拟地址转化成物理地址
进程地址空间
每一个进程在启动的时候都会让操作系统给他创建一个地址空间,该地址空间就是进程地址空间。所以前边说的‘程序的地址空间’是不够准确的,准确来说应该是‘进程地址空间’
所谓的地址空间,其实就是OS通过软件的方式,给进程提供一个软件视角,认为自己会独占系统的所有资源。
上边的图就可以清晰的说明,同一个变量,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址。
问题总结
- fork有两个返回值,pid_t id 同一个变量,怎么会有不同的值? pid_t id是属于父进程栈空间中定义的变量,fork内部,return会被执行两次,return的本质就是通过寄存器将返回值写入到接收返回值的变量中。当id=fork()的时候,谁先返回谁就要发生写时拷贝,所以同一个变量,会有不同的内容值,本质是因为大家的虚拟地址是一样的,但是大家对应的物理地址是不一样的。
- 为什么要有虚拟地址空间? 保护内存:直接让进程访问物理内存是不安全的,给访问内存添加了一层软硬件层,可以对转化过程进行审核,非法的访问就可以直接拦截。进程管理:通过地址空间进行功能模块的解耦。让进程或程序可以以一种统一的视角看待内存,方便以统一的方式来编译和加载所有的可执行程序简化进程本身的设计与实现。