目录
管理的核心理念:先描述(对象有各种信息),再组织(数据结构)
类比后,操作系统也是通过给用户提供接口的方式!操作系统也是不相信任何人,
windows的系统接口和Linux的系统接口一样吗? ? 肯定不一样。
1.查看进程的第一种最常用的方式:ps axj | grep ' mytest'
4.为什么 fork()给父进程返回子进程的pid,给子进程返回0
调用一个函数,当这个函数准备return的时候,这个函数的核心功能完成了吗? ?
3.Linux下的优先级的相关概念和操作(怎么办) ps -la查看优先级
在抢占式多任务处理中,进程被抢占时,哪些运行环境需要被保存下来?
一.冯诺依曼体系结构
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
输入输出设备这些外设不和CPU直接打交道,而是把数据放到内存中。CPU也是直接从内存中读数据。
输入:键盘,话筒,摄像头,磁盘,网卡...
输出:显示器,音响,磁盘,网卡,显卡...(输入输出都是外设)
(运算器+控制器) [CPU]:算数计算+逻辑计算
存储器:就是内存
注意:
1.磁盘既可以从硬盘读取数据也可以向硬盘写入数据
2.冯诺依曼体系结构必要组成部分:
CPU 运算器与控制器,RAM 内存(存储器),ROM 磁盘(输入输出设备)
Cache 缓存是一种技术,不属于冯诺依曼体系结构必要组成部分。
3.冯诺依曼体系结构计算机的基本原理是存储程序和程序控制
1.为什么要有内存?
a.技术角度
cpu的运算速度 > 寄存器的速度 > L1~L3Cache > 内存 >> 外设(磁盘) >> 光盘磁带
数据角度:外设不和CPU直接交互,而是和内存交互,CPU也是如此
内存在我们看来,就是体系结构的一个大的缓存,适配外设和CPU速度不均的问题的!
b.成本角度
寄存器>>内存>>磁盘(外设)
计算机蔓延全世界 ! 那计算机一定有2个特点:有效 ,便宜
2.存储器/内存 的意义
使用较低的钱的成本,能够获得较高的性能。平衡输入输出速度慢和cpu运行速度快
几乎所有的硬件,只能被动的完成某种功能,不能主动的完成某种功能,一般都是要配合软件完成的(OS+CPU)
我们自己写的软件,编译好之后,要运行,必须先加载到内存,为什么? ?
体系结构规定!局部性原理
二.操作系统
1.管理的理解一:管理的本质-对数据做管理。
操作系统是一款软件,搞管理的软件
管理的本质:对数据做管理。
校长连我的面都不见,如何管理我呢?.
管理你要和你打交道,要和你见面吗?
他是怎么做到的?
管理的本质:不是对被管理对象进行直接管理,而是只要拿到被管理对象的所有的相关数据,我们对数据的管理,就可以体现对人的管理! ! ! ! !
2.管理的理解二:如何管好大量的数据!
如何管好大量的数据!
人认识世界的方式:
通过属性认识世界的!
一切皆对象
一切事物都可以通抽取对象的属性,来达到描述对象的目的
class OBJ {
//成员属性
}
管理的核心理念:先描述(对象有各种信息),再组织(数据结构)
对进程的管理变成了对链表的增删查改
3.进程(和PCB)
什么是进程?进程是动态的过程,是一个运行起来的程序,进程=可执行程序(例如:内存中的test.exe)+该进程对应的内核数据结构(PCB)进程=内核的进程数据结构(task_struct, mm_ struct,page table) +进程的代码和数据
task_struct 是一个具体的PCB,PCB进程控制块在linux中叫 task_struct
程序是文件吗?——是的!文件在磁盘! !
操作系统里面可能同时存在大量的进程。操作系统要将所有的进程管理起来,
对进程的管理,本质就是对进程数据的管理。先描述(对象有各种信息),再组织(数据结构)
进程与程序,作业
- 进程与程序不一定是一一对应的,一个程序可以同时运行多次,也就有了多个进程
- 进程与作业不一定是一一对应的,因为一个作业任务的完成可由多个进程组成,且必须至少由一个进程组成
进程控制块-PCB
二.操作系统OS
1.OS介绍
操作系统:内存管理,进程管理,文件管理,驱动管理
什么是操作系统(OS),类比银行管理系统,操作系统就是银行的行长。
银行给所有人提供服务的方式是通过窗口提供的:
我们见到的所有的银行,都是一个封闭体,暴露出来一些窗口。因为银行是不相信任何人的!防止一些不法分子闯入,要防止少数人,给多数人提供服务。
类比后,操作系统也是通过给用户提供接口的方式!操作系统也是不相信任何人,
要防止少数人恶意修改代码,要给多数人提供服务。内核是使用C语言写的!接口:给我们提供底层是C语言的函数调用。系统可以调用各种函数。
操作系统不相信任何人的!不会直接暴露自己的任何数据结构,代码逻辑,其他数据相关的细节!!
操作系统是通过系统调用的方式,对外提供接口服务的!
Linux操作系统是用C语言写的,这里所谓的“接口”,本质就是C函数!
我们学习系统编程本质就是在学习这里的系统接口
windows的系统接口和Linux的系统接口一样吗? ? 肯定不一样。
操作系统管理结构官方图:
2.OS为什么要给我提供服务呢?
计算机和OS 设计出来是为了给人提供服务的
printf or cout 向显示器打印,显示器是硬件->所谓的打印,本质就是将数据写到硬件上
我们自己的C程序,没有资格向硬件写入
三.进程的查看
我们自己写的代码,编译成为可执行程序,启动之后就是一个进程!
那么别人写的呢??启动之后是不是进程呢??肯定算!!
1.查看进程的第一种最常用的方式:ps axj | grep ' mytest'
ps axj 选中所有进程,ps axj | grep 'mytest' :从所有进程中筛选出mytest的进程
ps axj | grep 'mytest' | grep -v grep :从所有进程中筛选出mytest的进程,同时把进程grep隐藏(我们正在使用grep,grep也是进程)
ps axj | head -l :从所有进程中筛选出第一行,第一行是PPID,PID....COMMAND,这些名称。
ps axj | grep 25979:从所有进程中筛选出进程25979
当前路径?:当前进程所在地路径,进程自己会维护。
每一个进程在系统中,都会存在一个唯一的标识符(pid)!就如同同学们在学校每个学生都有一个学号的东西!
pid (process id )
ps ajx | head -1 && ps axj | grep' mytest '| grep -v g
ctrl+c和kill -9 进程pid 杀进程
-9是9号命令,以后再谈
2.pid意思是当前进程,ppid意思是父进程
getpid():得到当前进程的标识,getppid():得到当前进程的父进程标识(想得到当前进程的子进程标识只能用fork)
几乎我们在命令行上所执行的所有的指令(你的cmd), 都是bash进程(bash的pid是29673)的子进程!
bash进程和init进程:
bash就是我们每打开一个终端后运行的第一个程序,他会捕捉你的键盘输入,看你输入了什么指令,则会解析执行对应的指令程序,在终端中运行的执行程序所对应进程默认父进程就是这个bash进程(因为这些指令程序对应的进程都是这个bash进程创建的)
init进程(也叫1号进程,就是操作系统)是系统内非常 重要的一个管理进程,系统中只有一个这个进程(bash进程是打开了多少终端就会运行多少个),进程PID为1
3.代码创建子进程 fork()
fork函数是用来创建子进程的,它有两个返回值:父进程返回子进程的pid,给子进程返回0
fork如何做到会有不同的返回值?
例:printf打印2次
printf为什么会打印两次?:fork之后,父进程和子进程会共享代码,一般都会执行后续的代码
例2:
fork之后,父进程和子进程会共享代码,一般都会执行后续的代码
fork之后,父进程和子进程返回值不同,可以通过不同的返回值,判断,让父子执行不同的代码块,即 if 和 else 都执行
4.为什么 fork()给父进程返回子进程的pid,给子进程返回0
父亲:儿子 = 1:n (n>=1)
一个父亲有可以多个儿子,所以父进程必须有标识子进程的方案,fork之后,给父进程返回子进程的pid!
子进程最重要的是要知道自己被创建成功了,因为子进程找父进程成本非常低!所以不需要标识符,返回0就行了,getppid()可以得到子进程的父进程
5.为什么fork会返回两次!
fork之后,OS做了什么?是不是系统多了一个进程
task_ struct + 进程代码和数据
task_ struct + 子进程的代码和数据
子进程的task_ stuct对象内部的数据从哪里来的:基本是从父进程继承下来的
子进程执行代码,计算数据的,子进程的代码从哪里来?:和父进程执行同样的代码,fork之后,父子进程代码共享!而数据要各自独立。
(父子进程代码共享:不同的返回值,让不同的进程执行不同的代码)
调用一个函数,当这个函数准备return的时候,这个函数的核心功能完成了吗? ?
已经完成!
1.子进程已经被创建了
2.将子进程放入运行队列!(就是进程被运行)
如何理解2.进程被运行:
运行队列runqueue是链式队列
fork会返回两次解释:
因为return之前子进程放入运行队列,就有父进程和子进程两个进程运行,因为return pid也是代码,父子共享代码,return pid会被父子各自执行一次,则有两个返回值
三.进程状态(运行态,终止态,阻塞态,挂起态)
1.操作系统原理上的进程状态
运行态:进程只要在运行队列中叫做运行态,代表我已经准备好了,随时可以调度! !(并不是正在CPU上运行的进程)
终止态:该进程还在,只不过永远不被调度和运行了,随时等待被释放!(并不是已被释放的进程)
进程都终止了,为什么不立马释放对应的资源,而要维护一个终止态? ?
答:释放要花时间!当前你的操作系统有可能很忙,并不能立即处理。
阻塞态:进程等待某种资源(非CPU),资源没有就绪的时候,进程需要在该资源的等待队列中进行排队,此时进程的代码并没有运行,进程所处的状态就叫做阻塞!(访问外设才会阻塞,比如while(1) printf(“11”);访问显示器就是访问外设 会阻塞,单一个while(1)就不会阻塞)
1.一个进程,使用资源的时候,可不仅仅是在申请CPU资源!
2.进程可能申请更多的其他资源:磁盘,网卡,显卡,显示器资源,声卡/音响
如果我们申请CPU资源,暂时无法得到满足,需要排队的--运行队列
那么如果我们申请其他慢设备的资源呢?也是需要排队的! (task_ struct在 进程排队)
当进程访问某些资源(磁盘网卡),该资源如果暂时没有准备好,或者正在给其他进程提供服务,此时: 1.操作系统会将当前进程从runqueue中移除 2. 操作系统将当前进程放入对应设备的描述结构体中的等待队列!
比如:CPU在运行队列中执行一个进程A,执行进程A的代码中需要访问磁盘资源disk,但是磁盘暂时没有准备好,或者正在给其他进程提供服务,则进程A不会占着CPU的资源等磁盘,而是操作系统把进程A放进磁盘的等待队列去等待,当我们的进程此时在等待磁盘的时候,该进程的代码,不会被执行,我们看到的情况就是:我的进程卡住了,这叫做 进程阻塞
等到磁盘就绪后再重新放回CPU的运行队列去运行代码
挂起态
如何内存不足了怎么办? ?
操作系统就要帮我们进行辗转腾挪
短期内不会被调度(你等的资源,短期内不会就绪)的进程,它的代码和数据依旧在内存中!就是白白的浪费空间!OS就会把该进程的代码和数据置换到磁盘上(swap分区)!
2.Linux内核中的进程状态
(1)R,S,D
R运行态:如果不访问外部资源(例如:printf访问显示屏这些外设)那么基本上就是在申请CPU资源,那就是R状态,例如下面:
S阻塞态:申请外部资源,比如有一个printf,还是死循环,虽然屏幕上疯狂刷屏打印的内容,但实际上cpu打印是极快的,大部分时间都是在等 显示器 这个资源,所以状态是S阻塞状态,例如下图: S状态也叫浅度睡眠/可中断睡眠,因为这个状态是可以被唤醒的(又操作系统把S态转为R态即为唤醒),也可以随时被我们用kill -9 (pid) 杀掉
D(disk sleep) :一般而言,linux中,如果我们等待的是磁盘资源(disk),我们进程阻塞所处的状态就是D
D解释:进程让磁盘把自己的数据写入磁盘,进程要等待磁盘写完返回一个值,代表写入成功或失败,此状态下进程在等待磁盘写入数据,操作系统无权杀死进程,否则当磁盘写完时找不到进程,会引起数据丢失,这个进程的状态就叫做D状态。(想要杀死D进程只能关机)
(2)Z (zombie) 和 X(dead)
X死亡状态就是之前说的终止态,可以随时把它的资源回收掉。
僵尸进程(zombie)简介:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
僵尸进程是指先于父进程退出的子进程程序已经不再运行,但是因为需要保存退出原因,因此僵尸进程不会完全释放资源退出,
它不会自动退出释放所有资源,也不会被kill命令再次杀死
僵尸进程会产生资源泄露,需要避免僵尸进程的产生采用进程等待(wait/waitpid)方式完成
Z (zombie)僵尸状态:
当一个Linux中的进程退出的时候,一般不会直接进入x状态(x是死亡状态,资源可以立马回收),而是进入Z状态。过程:进程退出后进入僵尸状态,必须被父进程/操作系统回收,父进程/操作系统会读取该进程的退出信息后,该进程才能进入X状态 释放。
为什么?进程为什么被创建出来呢? ?答:因为一定是要有任务让这个进程执行,当该进程退出的时候,我们怎么知道,这个进程把任务给我们完成的如何了呢? ?答:一般需要将进程的 执行结果 告知给父进程。进程Z,就是为了 维护退出信息(放在task_ struct中,进程的代码和数据可以释放了,但是task_ struct不可释放),可以让父进程或者OS读取的!如何读取的?:通过进程等待来进行读取的!如何等待?后面讲
—如何模拟僵尸进程?
如果创建子进程,子进程退出了,父进程不退出,也不等待子进程,子进程退出之后所处的状态就是Z
例子:下面子进程退出后就是僵尸状态了,只是父进程没有管,所以子进程一直处于僵尸状态
—长时间僵尸,有什么问题? ?
如果没有人回收子进程的僵尸,该状态会一直维护! 该进程的相关资源 (task_ struct) 不会被释放!导致内存泄漏!一般必须要求父进程进行回收(后面说!)
—僵尸进程必须使用waitpid/wait接口进行等待
僵尸进程会造成资源泄露,必须使用wait/waitpid接口进行等待处理
(3)孤儿进程
孤儿进程简介:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。(孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作,孤儿进程不会造成资源浪费)
————————————————————————————————————————
S+:带+是前台进程。S:不带+是后台进程。
后台进程 ctrl+c 终止不掉,只能kill -9 pid 杀掉
————————————————————————————————————————
孤儿进程是运行在后台的,孤儿进程退出不会成为僵尸进程,因此也不会资源泄露
父进程为什么没有Z呢?而是直接没了? ?
父进程没有Z是因为父进程的父进程是bash,他被bash回收了(我们上面写的子进程没被回收是因为父进程没有写waitpid进行回收)。
孤儿进程会被1号进程 (init进程) 领养:
如果父进程提前退出,子进程还在运行,子进程会被1号进程 (init进程) 领养! ! 孤儿进程不会造成资源浪费(1号进程就是操作系统!)
孤儿进程示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
while (1)
{
sleep(1);
printf("hello bit!\n");
}
pid_t id = fork();
if (id == 0)
{
//child
int cnt = 5;
while (1)
{
printf("我是子进程,我还剩下 %d S\n", cnt--);
sleep(1);
}
printf("我是子进程,我已经僵尸了, 等待被检测\n");
exit(0);
}
else {
//father
int cnt = 3;
while (cnt)
{
printf("我是父进程,我:%d\n", cnt--);
sleep(1);
}
exit(0);
}
}
(4)T和t
T(stopped) 常规暂停,跟阻塞没关系
kill -19 pid 是暂停命令(19号sigstop);kill -18 pid 是唤醒命令(19号sigcont是continue意思);
可以通过信号控制进程的起停:
t (tracing stop ) :(tracing-追踪)也是暂停 进程被调试的时候,遇到断点所处的状态
3.守护进程/精灵进程
这两种是同一种进程的不同翻译,是特殊的孤儿进程,不但运行在后台,最主要的是脱离了与终端和登录会话的所有联系,也就是默默的运行在后台不想受到任何影响,并且退出后不会成为僵尸进程。
一般以服务器的方式工作,对外提供服务的服务器,都是以守护进程(精灵进程)的方式在服务器中工作的,一旦启动之后,除非用户主动关闭,否则,一直会在运行
守护进程特点:①父进程是系统。②
(1)介绍PGID,SID等各个名称
PPID:父进程的id。PID:进程本身的id。PGID:当前进程所属的进程组。(同组进程的第一个进程一般是进程组的组长)SID:当前进程的会话id。
COMMAND:命令。TIME:运行的时间。UID:用户名映射后的数字,用户UID。
TPGID:当前进程组和终端相关的信息,-1 说明进程和终端无关系,非-1说明有关。
TTY:哪一个终端
(2)会话
用户登录时会建立一个会话,会话内部会有一个前台进程组 和 0个或者多个后台进程组,前台进程在会给我们加载bash。
在会话中启动一个进程组(可以只有一个进程),来完成某种任务,所有会话内的进程fork创建子进程,一般依旧属于当前会话! !
让某个后台进程脱离当前会话,让它独立的在计算机中形成一个新会话,自成进程组,这种进程叫做守护进程/精灵进程。
4.操作系统和Linux内核中的进程状态对应关系
表格:
操作系统 Linux内核中的进程状态
运行状态 R状态
终止状态 Z 或者 X状态
进程阻塞 S 或者 D状态
进程挂起 S 或 D 或 T状态
四.进程优先级
1.什么是优先级 优先级VS权限 (是什么)
优先级是进程获取资源的先后顺序
权限:说的是 能还是不能的问题
优先级:说的是 你能,只不过是先还是后
2.为何会存在优先级? (为什么)——资源不够!
排队的本质叫做确认优先级
系统里面永远都是, 进程占大多数,而资源是少数!——>进程竞争资源是常态!——> 一定需要确认先后
3.Linux下的优先级的相关概念和操作(怎么办) ps -la查看优先级
优先级由 priority+nice 两个值构成
PRI and NI
PRI vs NI
ps -la查看优先级
优先级由 priority+nice 两个值构成
priority(优先级)默认优先级是80,数字越小,优先级越高;数字越大,优先级越低
nice:进程优先级的修正数据!
要更改进程优先级,需要更改不是pri,而是NI,nice
(3)修改优先级操作
普通用户不能随意修改进程优先级,只能超级用户修改,输入超级用户命令 sudo top 运行起来,再输入 r ,再输入进程的pid后回车,输入想改成的优先级的值。(优先级的值最小是-20,最大19。)
Linux不允许进程无节制的设置优先级,nice[-20, 19],prio [60,99]
优先级值 prio = prio_old + nice
每次设置优先级,这个 prio_old 优先级都会被恢复成为80,因为nice[-20, 19],所以prio [60,99]。
五.进程间切换(寄存器)
进程切换主要手段是通过上下文切换来完成的。
1.上下文切换:
CPU内部有大量寄存器,寄存器中会存在很多临时数据,这些 寄存器里面的临时数据 就叫当前进程的上下文;上下文存入和移出寄存器,也就是上下文保护和恢复的过程叫做上下文切换
寄存器VS寄存器里面的临时数据:
寄存器是一堆硬件设备。
2.竞争性与独立性,并行与并发
进程:内核结构+代码和数据
如何做到进程具有独立性的呢? ?——进程地址空间
并行 解释::
如果存在多个CPU的情况,在任何一个时刻,都有可能有两个进程在同时被运行(就是在
CPU上运行),这样进程在不同CPU下同时运行 叫做并行
并发 解释:(切换的过程是操作系统中的一个软件调度器做的)
我的电脑是单CPU的,但是我的电脑中有各种进程都可以在跑啊
——多个进程都在你的系统中运行 != 多个进程都在你的系统中同时运行
不要以为进程一旦占有CPU,就会一直执行到结束,才会释放CPU资源!我们遇到的大部分操作系统都是分时的!
操作系统会给每一个进程, 在一次调度周期中, 赋予一个时间片轮转的概念! 例如:进程1给20ms(20ms就是进程1的时间片),进程2给30ms
在一个时间段内,多个进程都会通过切换交叉的方式,让多个进程代码,在一段时间内都得到推进,这种现象,我们叫做并发。(比如每个进程走20ms就切换,5个进程就是100ms,1秒内能每个进程执行10次,所以是多个进程都得到推进)
3.抢占式内核
操作系统,就是简单的根据队列来进行先后调度的吗??有没有可能突然来了一个优先级更高的进程??
——现在的计算机支持 抢占式内核!
解释:正在运行的低优先级进程,但如果来个优先级更高的进程,我们的调度器会直接把进程从CPU上剥离,放上优先级更高的进程,这就叫做进程抢占。
进程的优先级和队列
假设有5种优先级: a. 操作系统内允许不同优先级进程的存在。b: 相同优先级的进程,是可能存在多个的!
队列不能头插,如果来了个优先级更高的进程,如何体现优先级呢?
假设有5种优先级,底层就要维护一个指针数组:task struct * queue[ 5 ],一共5个元素,每个元素代表一个优先级,从上往下:优先级最高的为0,优先级为1的,优先级为2的,优先级为3的,优先级为4的;每一个元素存的都是类型是 task struct * 的指针,这个指针链着一个队列,根据不同的优先级,将特定的进程放入不同的队列中,CPU再从上往下,由优先级高到低遍历调用不为空的队列:O(1)调度算法,比如下图中遍历,发现优先级为0的队列不为空,就首先从优先级为0的队列取头部的进程
了解内容:实际上操作系统会把上面的哈希表task struct * queue[ 5 ]做成位图形式,通过扫描位图去遍历;并且运行队列会实现两个这样的哈希表位图,操作系统先处理第一个叫active的位图,新增的进程放入第二个叫 old 的位图,最后处理完第一个位图后,swap(active,old),再集中式处理新增的这一批进程
struct runqueue
{
hash_ queue* active
hash queue* old
}
在抢占式多任务处理中,进程被抢占时,哪些运行环境需要被保存下来?
抢占是多个进程抢占cpu运行自己的程序,所以要保存什么?要保存的是cpu中关于当前进程的数据,比如寄存器中要处理的数据或指令及地址,而全局变量是什么?是个变量,数据在内存中,跟进程切换毫无关系,所以不在保存的范围内
所以要保存的有:所有cpu寄存器的内容( cpu上正在处理的数据),页表指针(程序切换时会将页表起始地址加载到寄存器中),程序计数器(下一步程序要执行的指令地址)
4.寄存器
CPU内的寄存器是一堆存储器,是组件,功能是:可以临时的存储数据(非常少,但是非常重要!)
寄存器:①可见寄存器(是给我们用的寄存器,例如:eax,ebx,ecx,edx这些通用寄存器;ebp,esp,eip这些栈中的等)
②不可见寄存器(给操作系统用的寄存器)
当进程在被执行的过程中,一定会存在大量的临时数据,会暂存在CPU内的寄存器中!
寄存器上的数据的重要性:
生活中的例子:上学上到一半去当兵,所以要保留学籍,当兵完回来继续上学,就恢复学籍。
我们把进程在运行中产生的各种寄存器数据,我们叫做进程的硬件上下文数据
当进程被剥离:需要保存上下文数据(保留学籍)
当进程恢复的时候:需要将曾经保存的上下文数据恢复到寄存器中(恢复学籍)
进程的上下文数据保存在哪里呢?——保存在进程的PCB中,linux下的PCB叫task struct
要准确区分:寄存器vs寄存器里面的数据!
寄存器只有一套,寄存器里面的数据可以有多份。(图书馆是一个CPU,寄存器就是图书馆的每一个座位,数据像同学和他的书包等东西,自习完了就把东西收拾走人,这个座位还可以让成百上千的同学使用)