1. 冯诺依曼体系结构
输入设备:键盘,话筒,网卡,磁盘(外存)
外设:
输出设备:显示器,磁盘,网卡,打印机
CPU=运算器+控制器
存储器:内存
软件运行必须先加载,程序运行之前在磁盘
为什么必须先加载这是由体系结构规定的
Input 数据是从一个设备“拷贝”到另一个设备
体系结构的效率由设备的拷贝效率决定
CPU只能对内存进行读写,不能访问外设(输入/输出设备)
我们的程序必须从外设加载到内设
可执行程序运行的时候必须先加载到内存 CPU才能访问到
CPU执行我们的代码,访问我们的数据
CPU在数据层面,只和内存打交道,访问我们的数据
外设要输入或者或者输出数据只能写入内存或者从内存中读取
所有的设备都只能直接和内存打交道
越靠近内存,CPU效率就越高,成本也就越高
这个体系的存在让当代计算机变成性价比的产物
数据流得本质是在实现冯诺依曼原则
2.操作系统 -- 是一款进行软硬件管理的软件
2.1 概念
任何计算机系统都包含一个基本的程序集合,称为 OS
操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(函数库,shell程序...)
printf的本质:是我们把我们的数据写入了硬件,显示器!
2.2设计 OS 的目的
对上,为用户程序(应用程序)提供一个良好的执行环境
目的
对下,与硬件交互,管理所有的软硬件资源
手段
向下进行软硬件的管理的软件
硬件部分遵循的就是冯诺依曼体系
1.软硬件体系的层状结构
2.访问操作系统,必须使用系统调用 -- 其实就是函数,只不过是系统提供的
3.我们的程序,只要我们判断出了它访问了硬件,那么它必须贯穿整个软硬件体系结构
4.库可能在底层封装了系统调用
2.3管理的理解
管理的本质:
操作系统是先把进程描述起来,再把进程组织起来,这样来管理进行进程管理的
任何管理系统都是先描述再组织的
1.要管理,管理者和被管理者可以不需要见面
2.管理者和被管理者,根据数据进行管理
3.不需要见面,由中间层获取数据
类解决的是先描述的问题
任何面向对象的语言都必须提供 类+容器
描述起来:用struct结构体
组织起来:用链表或者其他较高的数据结构
2.4系统调用和库函数概念
理解系统调用:
操作系统要向上提供对应的服务(访问硬件的能力)操作系统
但又不相信任何用户,人
解决的方法是:系统调用
操作系统基本上都是使用C语言的,给我们提供的系统调用就是C语言风格的C函数
函数调用,基本上就会有输出参数和返回值
访问了硬件必须访问了系统调用
操作系统要给我们的开发者提供各种库
在开发角度,操作系统会对外表现为一个整体,但是会暴露自己的部分接口,供上层开发者使用,这部分由操作系统提供的接口叫做系统调用
系统调用在使用上,功能比较基础,对用户的要求相对也比较高,开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很利于上层用户或者开发者进行二次开发
库函数和系统调用是属于上下层的关系
3.进程
./可执行程序
-->本质就是创建进程
3.1 进程的概念以及task_struct
一个程序没运行起来,这个程序在磁盘
我们编译好的可执行程序就是一个二进制文件,在运行之前都只是我们磁盘上的文件
当我们运行的时候,这个可执行程序(代码和数据)就会被加载到内存当中
操作系统也是一款软件,也加载到内存里的
加载到内存里,我们内存里的操作系统不知道也不清楚这个进程是哪个
当有很多可执行程序加载到内存的时候,操作系统必然要对这些可执行程序做管理
-->
怎么管理?先描述(必须先有描述进程的task_struct)再组织,管理成特定的数据结构,然后操作系统就会把进程管理转化为对数据结构的增删查改
-->
在操作系统内要给每一个代码和数据构建一个struct结构体,把加载进来的可执行程序的所有属性和数据全部都维护起来
就有了对应的一个一个的结点
-->
我们就形成了一个加载到内存当中的程序列表,这个程序列表我们就叫做进程列表
-->
进程不仅仅是把可执行程序(代码和数据)从磁盘加载到内存
进程=内核数据结构对象+自己的代码和数据
在操作系统学科里,我们把描述进程的该struct结构体里称之为PCB
linux这个具体的一个操作系统里PCB叫做
stact task_struct
{
}
进程的所有属性都在task_struct里,都可以直接或间接通过task_struct找到
-->进程=PCB(task_struct)+自己的代码和数据
管理进程会变成对进程列表的增删查改
-->有一个可执行程序也要加载
当他加载到内存的时候,自己的代码和数据加载到内存里来了,操作系统还要给他创建对应的PCB数据结构对象,PCB能找到自己的代码和数据,PCB还要被链入到整个的进程列表当中
所以CPU未来要调度的时候,是先找一个PCB,找到它之后,再根据PCB找到它匹配的代码和数据再进行调度
-->删除
有一个进程运行结束了,代码运行完了,在操作系统内把代码和数据释放掉,再在链表中将该节点释放掉
task_struct 里有什么?
-->
标识符:描述进程的唯一标示符,用来区别其他进程
状态:任务状态,退出代码,退出信号...
优先级:相对于其他进程的优先级
程序设计器:程序中即将被执行的下一条指令的地址
内存指针:包括程序代码和进程相关数据的指针,还有其他进程共享的内存块的指针
是一个进程就有自己的id信息
3.2 查看进程
3.2.1 ps axj
3.2.2还有另一种查看进程的方法
还可以通过Linux中的目录结构 proc
3.3通过系统调用获取进程标识符
进程id (PID)
父进程id(PPID)
我们运行的所有指令其实都是进程
杀进程:ctrl+c
kill
每次启动同一个程序,它的pid是不一样的
man getpid
打开另一个xshell
父进程id
Linux中所有的进程都是被他的父进程创建的,没有母进程
父进程id是不会改变的
我们每次登陆云服务器时,os会给每一个用户分配一个bush
前面带 - 表示的是远程登陆
相当于把命令行输给bush
我们运行的所有指令其实都是进程,他们的父进程都是bush(父进程的父进程是bash)
父进程就是bush
补充:
cwd
exe
打开文件的时候可以带上路径
新建文件,带路径会在指定路径下新建
不带路径就会在当前路径下新建
任何更改一个进程所处的当前路径?
chdir
这样就改了
就是一个绝对路径
再看我们的hello.txt文件
3.4 通过系统调用创建进程-fork初识
---> 创建子进程
代码刚开始运行他是一个执行流的,一旦我们执行完我们的fork他就会有执行流,这两个执行流都会执行后续的代码
id一定是不一样的
fork之后的原理
-->
新加载出来一个PCB
fork之后要用if进行分流
1.fork会有两个返回值
2.一个函数会返回两次
return 的本质其实是对变量 pid_t id 进行修改,这个变量看上去是一个变量,其实对应的是两个内容
3.一个变量,既 ==0 又 > 0,导致 if else 同时成立?原因
-->
写实拷贝
我们这里进行修改来看一下
子进程修改了并不会影响父进程
写实拷贝图示
如何保证父子进程的独立性?
父子创建我们用fork,使用fork之后再做 if else 分流,父子就可以执行不同的代码块
fork之后代码共享,父子进程以各自的执行周期各自去执行,并不是代表后续代码在fork内部它的return语句就各自执行一次了
你跑你的我跑我的,各自去执行