进程概念及操作系统的知识点
目录
1>冯诺依曼体系结构
2>操作系统
a.概念
b.设计OS的目的
c.核心功能
d.理解管理
e.系统调用和库函数
3>进程
a.概念与操作
①描述进程-PCB
②task_struct
③查看进程
④获取标示符
⑤创建进程-fork
b.进程状态
①内核源代码
②查看进程状态
③僵尸进程
④孤儿进程
c.进程优先级
①概念
②查看系统进程
③PRI && NI
④查看进程优先级
⑤补充
d.进程切换
①调度队列
②活动队列
③过期队列
④队列指针
1>冯诺依曼体系结构
我们常⻅的计算机,如笔记本,即使不常⻅的计算机,如服务器,⼤部分都遵守冯诺依曼体系
计算机的硬件组件组成可分为
① 输入单元:包括键盘、鼠标、扫描仪、写板...
② 中央处理器(CPU):含有运算器和控制器...
③ 输出单元:显⽰器、打印机...
这里有几点要明白:
① 我们通常说的外设就包括输入设备和输出设备,内存指的是存储器,而外存指的是磁盘,另外这里的CPU做了简单处理,实际上CPU是有很多组成部分的
② 为什么软件运行前,必须先加载到内存?正是因为冯诺依曼体系结构
③ 我们的数据是从一个设备 ”拷贝“ 到另一个设备上的,所以体系结构的效率取决于设备的 ”拷贝“ 效率,这也是为什么要加一个存储器而不直接让CPU与外设连接,存储器在这里可以理解成一个桥梁,做适配,目的就是提高 ”拷贝“ 效率
④ 存储设备是有分级的,级别越高设备就更小更快,当然成本也越高,级别越低则相反
冯诺依曼体系结构需要注意的细节:
① 这⾥的存储器指的是内存
② 不考虑缓存情况,这⾥的CPU能且只能对内存进⾏读写,不能访问外设(输⼊或输出设备)
③ 外设(输⼊或输出设备)要输⼊或者输出数据,也只能写⼊内存或者从内存中读取
④ ⼀句话,所有设备都只能直接和内存打交道
2>操作系统
a.概念
任何计算机系统都包含⼀个基本的程序集合,称为操作系统(OS)
可以理解操作系统包括
① 内核(进程管理,内存管理,⽂件管理,驱动管理)
② 其他程序(例如函数库,shell程序等等)
b.设计OS的目的
对下,与硬件交互,管理所有的软硬件资源(是手段,不是目的)
对上,为⽤⼾程序(应⽤程序)提供⼀个良好的执⾏环境(这才是目的)
① 软硬件体系结构层状结构
② 访问操作系统,必须使用系统调用--其实就是函数,只不过是系统提供的
③ 我们的程序,只要你判断出它访问了硬件,那么它必须贯穿整个软硬件体系结构
④ 库可能在底层封装了系统调用
c.核心功能
在整个计算机软硬件架构中,操作系统的定位是:⼀款纯正的 “搞管理” 的软件
d.理解管理
这里有几点也需要注意:
① 要管理,管理者和被管理者,可以不需要见面
② 管理者和被管理者,通过 “数据” 进行管理
③ 不需要见面,可以通过中间层获取
e.系统调用和库函数
① 在开发⻆度,操作系统对外会表现为⼀个整体,但是会暴露⾃⼰的部分接⼝,供上层开发使⽤,这部分由操作系统提供的接⼝,叫做系统调⽤
② 系统调⽤在使⽤上,功能⽐较基础,对⽤⼾的要求相对也⽐较⾼,所以,有⼼的开发者可以对部分系统调⽤进⾏适度封装,从⽽形成库,有了库,就很有利于更上层⽤⼾或者开发者进⾏⼆次开发
关于系统调用,大家可以想想银行,我们在办理业务时是需要和柜台人员沟通交流进行操作的,而不是直接操作(因为银行不相信任何人,它不知道你要干嘛,但它还是要为你提供对应的服务),同样的,我们不可以直接去使用操作系统,但是可以通过它提供的接口去间接的使用
另外如果我们在银行不清楚怎么办理业务时,会有 “专业人员” 帮助我们去如何去操作,这里的 “专业人员” 就像开发者提供的库,从而让我们更好的使用操作系统
3>进程
a.概念与操作
课本概念:程序的⼀个执⾏实例,正在执⾏的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体
①描述进程-PCB
进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
在Linux中描述进程的结构体叫做task_struct,task_struct是Linux内核的⼀种数据结构,它会被装载到RAM(内存)⾥并且包含着进程的信息
②task_struct
可以在内核源代码⾥找到它
内容分类
• 标⽰符: 描述本进程的唯⼀标⽰符,⽤来区别其他进程。
• 状态: 任务状态,退出代码,退出信号等。
• 优先级: 相对于其他进程的优先级。
• 程序计数器: 程序中即将被执⾏的下⼀条指令的地址。
• 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
• 上下⽂数据: 进程执⾏时处理器的寄存器中的数据[休学例⼦,要加图CPU,寄存器]。
• I/O状态信息: 包括显⽰的I/O请求,分配给进程的I/O设备和被进程使⽤的⽂件列表。
• 记账信息: 可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等。
• 其他信息
组织进程
可以在内核源代码⾥找到它
所有运⾏在系统⾥的进程都以task_struct链表的形式存在内核⾥
③查看进程
我们历史上执行的所有的指令,工具,自己的程序,运行起来,全部都是进程!
先来简单看一下进程,ps 和 top 都可以用来获取进程信息
结束进程
进程的信息也可以通过 /proc 系统⽂件夹查看
cwd && exe
④获取标示符
(通过系统调用获取进程标示符)
进程id(PID) 父进程id(PPID)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{printf("pid: %d\n", getpid());printf("ppid: %d\n", getppid());return 0;
}
⑤创建进程-fork
(通过系统调⽤创建进程-fork初识)
fork有两个返回值
⽗⼦进程代码共享,数据各⾃开辟空间,私有⼀份(采⽤写时拷⻉)
fork 之后通常要⽤ if 进⾏分流
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int gval = 100;int main()
{printf("父进程开始运行,pid:%d\n", getpid());pid_t id = fork();if (id < 0){perror("fork");return 1;}else if (id == 0){// 子进程printf("我是一个子进程 !,我的pid: %d,我的父进程id: %d, gval:%d\n", getpid(), getppid(), gval);sleep(5);while (1){sleep(1);printf("子进程修改变量:%d->%d", gval, gval + 10);gval += 10;printf("我是一个子进程 !! 我的pid:%d,我的父进程id:%d\n", getpid(), getppid());}}else{// 父进程while (1){sleep(1);printf("我是一个父进程 !,我的pid: %d,我的父进程id: %d, gval:%d\n", getpid(), getppid(), gval);}}printf("进程开始运行,pid:%d\n", getpid());return 0;
}
b.进程状态
进程状态就是 task_struct 内的一个整数
①内核源代码
为了弄明⽩正在运⾏的进程是什么意思,我们需要知道进程的不同状态(在Linux内核⾥,进程有时候也叫做任务)
下面是Linux中状态在kernel源代码⾥定义
/*
*The task state array is a strange "bitmap" of
*reasons to sleep. Thus "running" is zero, and
*you can test for combinations of others with
*simple bit tests.
*/
static const char *const task_state_array[] = {"R (running)", /*0 */"S (sleeping)", /*1 */"D (disk sleep)", /*2 */"T (stopped)", /*4 */"t (tracing stop)", /*8 */"X (dead)", /*16 */"Z (zombie)", /*32 */
};
• R运⾏状态(running): 并不意味着进程⼀定在运⾏中,它表明进程要么是在运⾏中要么在运⾏队列⾥
• S睡眠状态(sleeping): 意味着进程在等待事件完成(这⾥的睡眠有时候也叫做可中断睡眠(interruptible sleep))
• D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束
• T停⽌状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停⽌(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运⾏
• X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表⾥看到这个状态
在理解各种状态之前,我们先了解一下进程运行、阻塞、挂起状态的区别
现在再来看一下进程状态图就可以大致理解了
另外感兴趣的可以再理解一下内核链表
②查看进程状态
ps aux / ps axj
• a:显⽰⼀个终端所有的进程,包括其他⽤⼾的进程。
• x:显⽰没有控制终端的进程,例如后台运⾏的守护进程。
• j:显⽰进程归属的进程组ID、会话ID、⽗进程ID,以及与作业控制相关的信息
• u:以⽤⼾为中⼼的格式显⽰进程信息,提供进程的详细信息,如⽤⼾、CPU和内存使⽤情况等
下面是查看各种状态的详细介绍
R 运⾏状态(running)
S 睡眠状态(sleeping)
T 停⽌状态(stopped)&& t停⽌状态(tracing stop)
D 磁盘休眠状态(Disk sleep)
X 死亡状态(dead)
就是进程结束的意思,这个状态不会显示出来
kill指令
Z 僵尸状态(zombie)
建议先看一下下面僵尸进程的介绍,再返回来看僵尸状态
③僵尸进程
• 僵死状态(Zombies)是⼀个⽐较特殊的状态。当进程退出并且⽗进程没有读取到⼦进程退出的返回代码时就会产⽣僵死进程
• 僵死进程会以终⽌状态保持在进程表中,并且会⼀直在等待⽗进程读取退出状态代码
• 所以,只要⼦进程退出,⽗进程还在运⾏,但⽗进程没有读取⼦进程状态,⼦进程进⼊Z状态(所以信息是存储在task_struct的)
举个例子
有一个人倒在了公园里面,路过的群众发现后立即报警,警察来到后第一时间封锁现场,然后呼叫了救护车,并且叫法医过来取样,因为要判断这个人倒地的原因是什么,法医取样完毕后,医护人员再把这个人送去医院
在这个人倒地到被抬上救护车的这段过程就叫僵尸状态,为什么要让这个人处于僵尸状态呢,而不是直接抬上救护车,原因是为了获取退出信息,而抬上救护车就是 X 状态了
僵尸进程的危害
• 进程的退出状态必须被维持下去,因为他要告诉关⼼它的进程(⽗进程),你交给我的任务,我办的怎么样了。可⽗进程如果⼀直不读取,那⼦进程就⼀直处于Z状态?是的!
• 维护退出状态本⾝就是要⽤数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态⼀直不退出,PCB⼀直都要维护?是的!
• 那⼀个⽗进程创建了很多⼦进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本⾝就要占⽤内存,想想C中定义⼀个结构体变量(对象),是要在内存的某个位置进⾏开辟空间!
④孤儿进程
• ⽗进程先退出,⼦进程就称之为“孤⼉进程”
• ⽗进程如果提前退出,那么孤⼉进程会被1号 init 进程领养,所以它会被 init 进程回收
c.进程优先级
①概念
• cpu资源分配的先后顺序,就是指进程的优先权(priority)
• 优先权⾼的进程有优先执⾏权利。配置进程优先权对多任务环境的linux很有⽤,可以改善系统性能
• 还可以把进程运⾏到指定的CPU上,这样⼀来,把不重要的进程安排到某个CPU,可以⼤⼤改善系统整体性能
②查看系统进程
ps -l 命令查看系统进程的相关信息
• UID : 代表执⾏者的⾝份
• PID : 代表这个进程的代号
• PPID :代表这个进程是由哪个进程发展衍⽣⽽来的,亦即⽗进程的代号
• PRI :代表这个进程可被执⾏的优先级,其值越⼩越早被执⾏
• NI :代表这个进程的nice值
③PRI && NI
• PRI代表的是进程的优先级,可以理解成程序被CPU执⾏的先后顺序,此值越⼩进程的优先级别越⾼
• NI代表的是nice值,其表⽰进程可被执⾏的优先级的修正数值
• 在Linux下,调整进程优先级就是调整进程nice值,nice的取值范围是-20⾄19 (PRI(new)=PRI(old)+nice)
需要强调⼀点的是,进程的nice值不是进程的优先级,他们不是⼀个概念,但是进程nice值会影响到进程的优先级变化
④查看进程优先级
⽤top命令更改已存在进程的nice: 进⼊top后按“r”‒>输⼊进程PID‒>输⼊nice值
其他调整优先级的命令:nice,renice
⑤补充
竞争性 | 系统进程数⽬众多,⽽CPU资源只有少量,甚⾄1个,所以进程之间是具有竞争属性的。为了⾼效完成任务,更合理竞争相关资源,便具有了优先级 |
---|---|
独⽴性 | 多进程运⾏,需要独享各种资源,多进程运⾏期间互不⼲扰 |
并⾏ | 多个进程在多个CPU下分别,同时进⾏运⾏,这称之为并⾏ |
并发 | 多个进程在⼀个CPU下采⽤进程切换的⽅式,在⼀段时间之内,让多个进程都得以推进,称之为并发 |
d.进程切换
CPU上下⽂切换:其实际含义是任务切换, 或者CPU寄存器切换。当多任务内核决定运⾏另外的任务时, 它保存正在运⾏任务的当前状态, 也就是CPU寄存器中的全部内容。这些内容被保存在任务⾃⼰的堆栈中, ⼊栈⼯作完成后就把下⼀个将要运⾏的任务的当前状况从该任务的栈中重新装⼊CPU寄存器, 并开始下⼀个任务的运⾏, 这⼀过程就是context switch
当前进程要把自己的进程硬件上下文数据保存起来,保存到进程的task_struct里面(TSS:任务状态段),参考⼀下Linux内核0.11代码
时间⽚:当代计算机都是分时操作系统,没有进程都有它合适的时间⽚(其实就是⼀个计数器)。时间⽚到达,进程就被操作系统从CPU中剥离下来
①调度队列
Linux2.6内核进程O(1)调度队列
②活动队列
• 时间⽚还没有结束的所有进程都按照优先级放在该队列
• nr_active: 总共有多少个运⾏状态的进程
• queue[140]: ⼀个元素就是⼀个进程队列,相同优先级的进程按照FIFO规则进⾏排队调度,所以,数组下标就是优先级!
• 从该结构中,选择⼀个最合适的进程,过程是怎么的呢?
-
从0下表开始遍历queue[140]
-
找到第⼀个⾮空队列,该队列必定为优先级最⾼的队列
-
拿到选中队列的第⼀个进程,开始运⾏,调度完成
-
遍历queue[140]时间复杂度是常数,但还是太低效了
• bitmap[5]:⼀共140个优先级,⼀共140个进程队列,为了提⾼查找⾮空队列的效率,就可以⽤5*32个⽐特位表⽰队列是否为空,这样,便可以⼤⼤提⾼查找效率!
③过期队列
• 过期队列和活动队列结构⼀模⼀样
• 过期队列上放置的进程,都是时间⽚耗尽的进程
• 当活动队列上的进程都被处理完毕之后,对过期队列的进程进⾏时间⽚重新计算
④队列指针
active指针 && expire指针
• active指针永远指向活动队列
• expired指针永远指向过期队列
• 活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间⽚到期时⼀直都存在的
• 在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了⼀批新的活动进程
总结:在系统当中查找⼀个最合适调度的进程的时间复杂度是⼀个常数,不随着进程增多⽽导致时间成本增加,我们称之为进程调度O(1)算法!
本篇文章到这里就结束啦,希望这些内容对大家有所帮助!
下篇文章见,希望大家多多来支持一下!
感谢大家的三连支持!