系统编程:系统调用接口以及进程的认识(一个程序的运行)
一、认识冯诺依曼体系结构
- 冯诺依曼体系结构:现代计算机硬件体系结构
1、输入设备:采集数据的,比如典型的键盘
2、输出设备:进行数据输出,比如典型显示器
3、存储器:进行中间数据缓冲,内存条 (不是硬盘的原因:硬盘的数据吞吐量太低,内存吞吐量是硬盘的数十倍,内存速度那么快,为什么内存只用于缓冲,不使用内存存储数据而是使用硬盘?因为硬盘与内存存储介质不同,内存是易失性介质,数据掉电就会丢失,而硬盘掉电后数据不会丢失)
4、运算器:进行数据运算,运算器+控制器=中央处理器CPU
5、控制器:进行设备控制
硬件结构决定软件行为/ 所有设备都是围绕存储器工作的
所有的设备都是围绕存储器工作的
cpu不会直接从输入设备获取数据进行处理,而是先把数据放到存储器中,cpu从存储器中获取数据处理;cpu不会直接将数据交给输出设备进行输出,而是先把数据报道存储器中,控制输出设备从存储器中获取数据输出
二、简单认识操作系统
- 操作系统:内核(负责系统的核心功能-软件管理,硬件管理,内存管理,文件管理…)+外部应用
- 操作系统的功能:管理计算机软硬件资源
- 操作系统的定位:一款“搞管理”的软件
- 目的:让计算机更加好用
- 如何管理:先描述,在组织
用户----->用户操作接口(shell命令、库函数)------>系统调用接口 ------>操作系统------->驱动程序------>底层硬件
系统调用接口:操作系统向外提供访问内核的接口—每个接口完成的功能都是固定的
库函数与系统调用接口的关系:库函数封装了系统调用接口;上下级的调用关系
三、进程概念
-
1、进程基本概念:运行中的程序
一个程序运行起来,有数据以及指令需要被cpu执行处理,根据冯诺依曼体系结构----cpu不会直接去硬盘上找到程序文件进行执行处理需要先将程序数据信息,加载到内存中,然后cpu从内存中获取数据以及指令进行执行处理----程序运行会被加载到内存中 -
cpu只有一个,如何做到多个程序同时运行?
cpu的分时机制:实现系统同时运行多个程序的技术cpu只负责执行指令,处理数据(处理哪个程序其实cpu并不关系)
因此操作系统的进程管理就体现出来,对程序的运行调度进行管理。
cpu进行程序运行处理的时候,并不会一次性将一个程序运行完毕才会运行下一个,而是每个程序都只运行一段很短的时间(给一个程序分配的一个时间片),时间片运行完毕则由操作进行调度,让另一个程序的代码数据在cpu上进行处理。
cpu分时机制实现cpu轮询处理每一个运行中的程序,而程序运行调度有操作写系统进行管理。
管理思路:操作系统将每一个程序的运行信息保存下来,进行调度管理的时候才能知道这个程序上一次运行到了那里 -
2、描述进程-PCB:站在操作系统的角度,进程就是一个运行中程序的描述,通过pcb,才能实现程序的运行调度管理——PCB(进程控 制块)【Linux下这个PCB实际上是一个结构体-struct task_struct{…}】
-
3、task_struct内容分类:
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块 的指针 ,能够让操作系统调度程序运行时,知道进行对应的指令以及数据在内存中的位置
- 程序计数器:程序中即将被执行的下一条指令的地址,切换回来知道从哪里继续运行
- 上下文数据:进程执行时处理器的寄存器中的数据(上一次正在处理的数据) ,程序运行过的指令和数据,程序运行中的指令和数据,程序即将执行的指令和数据,操作调度切换进程运行的时候能够让cpu这个进程接着切换之前继续运行,继续处理之前没有处理完的数据
- 标识符:PID进程ID ,能够让操作系统识别唯一的运行中的程序
- 进程状态:任务状态
- 优先级:让操作系统运行的更佳良好
- 记账信息:一个进程在CPU上运行的时间
- I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
- CPU的分时机制:每个程序在CPU上运行都有一个时间片 ,将cpu处理程序运行的过程划分为时间片,每个程序在cpu上只运行一段很短的时间,时间片完成后切换下一个进程
- 时间片:程序在CPU上运行的这段时间,运行完毕则调度切换
并发:cpu资源不够的情况下,采用cpu分时机制,任务轮询处理;十个人过独木桥
并行:多核cpu上,多个进程同时占据cpu进行数据处理;十个人并排过马路
进程就是PCB
cpu调度----分配CPU资源,切换运行程序
4、进程状态/优先级/环境变量/进程调度
1、进程状态:
每个进程pcb中都会描述一个运行的状态信息,通过状态信息,告诉操作系统这个进程现在应该干什么.是否能够被调度处理
- 1、查看进程命令:
1、 ps -ef/ps -aux(aux比ef更详细)
2、pid_t getpid(void):获取调用进程的ID
3、pid_t fork(void):通过复制调用进程,创建一个新进程,通过复制父进程创建一个子进程,子程序因为拷贝了父进程,pcd里边的很多数据因此与父进程内存指针以及程序计数器都相同,所以运行的代码以及运行的位置都一样(父子进程运行的代码是一样的,并且子进程也是从 创建成功的下一句指令开始运行)
- 2、返回值有三种:
-1:创建子程序失败
==0:对于子进程,返回值为0
大于0:对于父进程,返回值是子进程的pid
- 3、fork创建子进程之后,父子进程代码共享,数据独有;
返回值:fork的返回值对于父子进程是不一样的
在父进程中,fork会返回子进程的pid,是大于0的
在子进程中,fork会返回0
返回-1表示创建子进程失败
可以通过返回值对父子进程的运行进行分流,让其各自运行一段代码
fork创建子进程之后,父子进程谁先运行,不一定,大家都是pcb,操作系统调度到谁谁就先运行为什么要创建子进程:子进程干的事情与父进程一样,当然使用返回值分流后可以有所不同;有任务了,创建一个子进程,让子进程去完成任务,出问题了奔溃的就是子进程,父进程就不会奔溃了(保护父进程–分摊压力)
- 4、Linux下的进程状态:
运行状态:包含就绪以及运行,也就是说正在运行,以及拿到时间片就能运行的称之为运行状态,R
休眠:暂时不需要cpu调度运行,让出cpu资源,休眠也有唤醒条件,操作系统调度程序运行的时候,会查看状态,如果是休眠,就会看唤醒条件是否满足,如果满足,则置为运行状态,进行处理,如果不满足则切换下一个进程
可中断休眠状态:可以被打断的休眠,通常满足运行条件或者被一些中断打断休眠之后进入运行状态,S
不可中断休眠状态:只能通过满足条件自然醒,进入运行状态,不会被一些中断打断休眠状态D(磁盘休眠状态)
停止状态:停止与休眠不一样(休眠c操作系统会去查看进程唤醒条件是否满足,而停止只能手动唤醒)T
死亡状态:X
追踪状态: t
僵死状态:描述的是一个进程退出来,但是进程资源没有完全被释放,等待处理的一种状态Z
kill 杀死一个进程(kill -9 强杀)
僵尸进程:处于僵死状态的进程
1、概念:子进程先于父进程退出,若父进程没有关注子进程退出在子程序会进入僵死状态称为僵尸进程。子进程退出之后并没有完全释放资源,保存退出返回值(返回原因),操作系统检测到之后,会通知父进程,但是当前父进程没有关注到这个通知,因此操作系统无法直接释放子进程所有资源,子进程则进入僵死状态称为僵 尸进程。
2、产生原因:子进程先于父进程退出,父进程没有关注到子进程退出状态,因此子
进程成为僵尸进程,子进程退出,保存退出原因,操作系统不能主动释放,只能 让父进程决定
3、僵尸进程的危害:资源泄露
4、如何处理:退出父进程
5、如何避免僵尸进程的产生:进程等待 ,一直关注子进程,退出就能直接发现
孤儿进程: 父进程先于子进程退出,则子进程会成为孤儿进程,这个孤儿进程运行在后台, 并且被1号init进程领养。
守护进程/精灵进程:是一个特殊的孤儿进程,完全独立的运行系统后台,脱离 终端的影响
2、进程优先级:决定进程cpu资源的优先分配权的等级
- 优先级:决定一个进程在CPU上运行的优先权----数字 数字越小,优先级越高
- 程序分类:CPU密集/IO密集
- 进程分类:批处理/交互式
-renice -n 19 -p pid :设置进程优先级
sudo nice -n -20 ./zombie
3、环境变量:保存系统运行环境参数的变量
- 作用:通过环境变量配置系统运行环境参数可以使系统环境配置起来更加灵活 (修改环境变量,则配置立即生效),可以通过环境变量向子进程传递数据
- 操作命令:
env:查看所有环境变量
set:查看所有变量
echo:打印指定变量的值
export:声明定义转换环境变量
unset:删除变量。包括环境变量
- 目的:
1、让系统运行环境参数配置起来更加灵活
2、环境变量具有全局特性 - 典型的环境变量:
- PATH:程序运行的默认搜索路径,运行一个程序的时候,如果没有指定程序路径,只有程序名称,则shell会去PATH环境变量保存的路径中去找这个程序,如果没有找到,就报错,没有这个命令
- SHELL/USER/PWD/HOME
- int main(int argc,char *argv[],char *env[]):argc程序运行参数的个数,argv[]保 存程序运行参数,env保存环境变量
- extern char **environ;库中的全局变量,每个节点指向一个环境变量,但是使用时需要声明,告诉编译器有这个变量
- char * getenv(const char *name):通过环境变量名称获取环境变量内容
创建一个进程,子进程可以获取到所有的环境变量
5、程序地址空间:进程的虚拟地址空间
- 因为直接访问物理内存:进程之间内存访问,缺乏控制;内存的利用率较低(每一 个程序都要求一块连续的内存)
- 地址:内存地址-------对内存以字节为存储单元的一个编号
- 程序:就是一堆死代码,保存在程序文件中(硬盘);编译器再编译程序生成可执行程序文件时,就会对每一条指令,每一个数据,进行一个地址排号,当程序运行时,就会将指令以及数据放到指定的内存地址位置;cpu就会根据地址偏移逐步执行指令,以及找到对应的数据进行处理
- 进程地址空间:
1、操作系统为每一个运行中的程序都创建了一个虚拟地址空间–mm_ struct…}和页表,在程序中看到的所有变量的地址其实都是一个虚拟地址
2、程序地址空间,是一个虚拟地址空间
3、 通过页表映射物理内存区域:避免了程序直接操作物理内存
4、实现: 一个进程的数据在物理内存中的离散式存储,提高内存利用率,并且通过页表也可以进行内存访问控制
5、如何将虚拟地址映射转换为物理地址:虚拟地址如何映射得到物理内存地址:操作系统如何进行内存管理 - 若进程直接访问物理内存,有什么不好?
1.程序在编译时,编译器就会给指令和数据进行地址编号;但是如果某个地址内存已经被占用,则这个程序就运行不起来了—编译器的地址管理麻烦(无法动态的获知什么时侯那块内存是否被使用,也就无法进行代码以及数据的地址赋值)
⒉进程直接访问物理内存,如果有一个野指针,你在操作的时候有可能就把其它进程中的数据改变了(无法进行内存访问控制)
3.程序运行加载通常需要使用一块连续的内存空间,对内存的利用率比较低 - 通过虚拟地址空间映射到物理内存上进行数据存储,可以实现数据在物理内存上的离散式存储,提高内存的利用率;并且每个进程都有自己的虚拟地址空间,因此对于每个进程来说,都会拥有自己的一块连续的空间使用
a 、分段式内存管理:
内存地址:段号+段内偏移;
借助段表:物理段起始地址; 获取到物理段起始地址后+段内地址
优点:对程序员比较友好,将程序的地址根据使用性质不同进行分段管理,但没有解决数据连续存储内存利用率低的问题
b、分页式内存管理:
内存地址:页号+页内偏移
分页式:虚拟地址的组成方式:页号+页内偏移借助页表: 物理内存块号;获取到物理内存块号之后*块大小+页内偏移
优点:因为通常物理块比较小,并且不要求同一个进程的多个数据必须在同一个块内,因此分页式实现了数据在物理内存中的离故式存储,提高了内存利用率
c、段页式内存管理:在将程序地址空间进行分段式管理,但是在每个段内进行分页式管理
内存地址:段号+段内页号+页内偏移
借助段表找到段内页表的起始地址,根据段内页号找到页表项,根据页表项可以找到物理块号
页面是用于描述虚拟地址对应物理块的大小
页表项是使用页号在页表中的找寻的对应映射关系节点,当中存储对应的物理块起始地址
- 5、虚拟地址空间:
1、概念:操作系统为进程描述的一个连续的、完整的地址空间–mm_struct{ulong size,ulong star;ulong end},每个进程都有自己独立的虚拟 地址空间,访问的都是虚拟地址,但是可以通过页表将虚拟地址映射转换为物理 内存地址,进而访问物理内存区域;通过页表映射转换实现了数据在物理内存中的离散式存储------提高了内存利用率;通过页表中进行地址访问限制,实现了内存访问控制,并且每个进程都有独立的虚拟地址空间,访问的都是自己的虚拟 地址,因此进程之间具有独立性。
2、作用:
提高内存利用率:物理内存的离散存储
保证了进程独立性:每个进程都只能访问自己虚拟地址映射的物理内存
页表可以进行内存访问控制:页表可以对每个虚拟地址进行权限标记
3、物理地址的计算方法:
虚拟地址/页面大小=页号 通过页表得到块号
块号*块大小+页内偏移(虚拟地址%页面大小)
- 6、创建一个子程序的流程:写时拷贝技术
1、创建pcd
2、拷贝父进程pcd中的数据(拥有相同的虚拟地址空间,相同的页表。。)
3、父子进程一开始映射同一块物理内存
4、等到物理内存修改的时候才为子进程重新开辟内存,拷贝数据过来(要求i进 程的独立性)
代码共享和数据独有:因为代码段只是只读的,不会修改,因此会一直映射同一 块物理内存
进程调度算法:大O(1)调度算法(多级反馈)
7、缺页中断:
- 表示当前虚拟地址要找的数据是否在物理内存中
- 磁盘分区有两种:交换分区/文件系统分区
- 交换分区:作为交换内存使用
当内存不够的时候,这时候其实内存中并不是所有的数据都是活跃数据,这时候操作系统则会根据一定的算法,将某块内存中的数据保存在磁盘的交换分区中,腾出的这块内存加载新数据。但是每个进程的页表中记录了每一个虚拟地址对应的物理地址----如果某个虚拟页面的物理内存中的数据被交换出去,保存在交换分区,则将这个页表项置为缺页中断;等待下次这个进程要访问被交换出去的数据时,触发缺页中断,重新从交换分区将数据交换回来。 - 文件系统分区:用于文件数据存储