计算机是由软件和硬件组成的:
底层的硬件→内核(kernel:输出的借口为system call,用作系统调用)→lib库(库调用;调用就是载入别人事先编写好的功能模块,比如函数、类);
程序由指令和数据组成;
CPU上的指令分为普通指令(环三)和特权指令(环零);
特权指令不允许程序随意调用,一般只有内核可以调用;因为特权指令一般是执行硬件操作的,比如读写等,显然类似这些指令是不可能随意授权给普通程序的;
任何程序不得直接使用特权指令,用到的时候可以通过像内核申请系统调用来执行特权指令;
程序执行时是依照指令的顺序由上而下根据选择循环等结构有序的执行;
一个程序运行时一旦需要运行某个特权指令时就会去调用内核,接下来程序会先停止,等待内核把需要调用的功能(特权指令)拿到CPU上来执行,等待执行完成后,将结果返回(调用返回),然后程序继续执行其他操作;
代码执行过程中可能会不断地在自己可以执行的代码和调用由内核执行的代码之间来回转换;
模式切换:(用户代码(用户空间/模式)↔内核代码(内核空间/模式))
内核的功能:进程管理、文件系统、网络功能、安全功能、内存管理、驱动程序等;
系统运行时会有多个程序一起执行,称作并行执行;是怎么实现的那?
其实当有多个程序一起执行时,系统会把程序对CPU的使用时间切割成时间片,某个程序执行一小段时间后再由下一个程序再执行一小段时间,当这个时间很短的时候给人的感觉就好像是多个程序在同时执行一样;但是还有特殊情况,比如某个程序比较重要特别着急就需要提前执行,所以引进了优先级,使某些重要的程序得以优先执行;优先执行就代表它会抢占其他程序使用CPU的时间,这是可以理解的,但是不可以随便抢占,比如你总不能在人家刚得到机会使用CPU的时候就抢占人家吧,太不人道了!!所以规定可以隔一定的时间才可以开始抢占;
CPU只有一颗,所以内核就负责将这些运行的进程按照划分的时间片根据其优先级调度到CPU上运行,当运行时间满时会马上结束这个进程的CPU使用,然后根据优先级再调度下一个进程使用CPU;
但是你有没有想到,当内核把下一个进程调度到CPU前应该保存前一个进程在CPU上运行的状态,要不然下一次再运行这个程序的时候就得从头开始,这绝不是我们想看到的,所以内核会先保存此进程的现场之后再恢复下一个进程的现场(两个进程都是已经运行一段时间的情况);
那保存现场时产生的数据保存在哪那?(且听明天分解)
开始分解:
内核为每一个运行在当前系统中的进程都会为其创建一个用来追踪此进程的元数据结构(进程名、进程号、进程所占用的内存空间、所运行的CPU时长等);
对于进程而言,内核中使用的结构体称为tast struct(任务结构体,因为每一个进程在内核中称为一个任务):用来存储进程信息(元数据结构)的固定格式;
假如说主机上只有一颗CPU,在某一时刻CPU要么在执行内核代码要么在执行用户代码,不可能同时执行这两类代码;万一某一时刻用户的代码像脱缰的野马一样跑起来,想摆脱内核的控制,该怎么办?
任何时刻当一个用户进程试图执行特权指令时,CPU会触发中断立即唤醒内核,内核就会立刻回来控制场面;所以用户进程进程是不可能摆脱内核控制的(当然是排除内核bug的情况的)
进程创建:
内核启动初始化完毕以后会创建出第一个进程(init),标志着用户空间和内核空间已经创建完毕;
进程都有其父进程创建
每一个进程都是由父进程fork()而来(就像办理准生证),然后会clone()(克隆)自己的数据给子进程;
当进程创建子进程后,子进程和父进程使用同一段地址空间,一旦子进程需要修改数据的时候,会复制一份父进程的数据然后开始做修改等操作;(COW(写时复制))
人类世界都是黑发人送白发人,但是进程的世界是白发人送黑发人;
任何父进程创建的子进程都要由父进程进行销毁;
父进程创建子进程是因为父进程运行时某一时刻需要完成某一任务它就会启动另外一个进程让它去执行然后返回结果给自己;
不论是父进程还是子进程在内核看来他们都是相同的,对于内核来说他们都是单独的需要执行的实体内核都会链表或者说是tast struct来存储进程相关的各种各样的结构信息并来追踪它们,里面会记录着进程的父进程是谁,子进程是谁;
进程优先级:
0-139:
1-99:实时优先级,数字越大优先级越高;
100-139:动态优先级(用户可调用的优先级),数字越小优先级越高;
nice值:-20—19;
通过调整nice值来调整优先级,一般用户只能减小优先级,无法增大,只有管理员可以增大;
内核为了快速的实现优先级的调度,内核把系统上所有待运行的进程划分到140个队列中,相同优先级的排成一队;如果内核想要调度某个优先级的进程只需要扫描每个队列的首部就可以了;
每个优先级队列都有两队,一个为待运行队列,一个为过期队列,所以其实一共是280个队列,一般被扫描的都是待运行队列,过期队列放置的是在同一个优先级上已经被调度过的进程;当待运行队列被调度完以后,过期队列会替换待运行队列称为新的待运行队列;
Big O标准:判断一个算法随着代码复杂度的提升,所需要的运行时间的曲线变化;
O(1):无论怎么增加复杂度,时间恒定;(最理想的算法)
O(logn),O(n),O(n^2),O(2^n)等;
内核在进行进程调度时,它不是将一个进程运行完再运行第二个,而是每个运行一个时间片的时间后再运行下一个进程;
task_struct:
https://blog.csdn.net/gatieme/article/details/51383272
进程内存:
每一个进程使用内存都不是通过划分实体内存实现的,为了完成现代操作系统上的多任务管理 一块内存上,内核会占用一段地址空间(将来会增长),剩余的地址空间就会留给进程使用;为了高效的分配内存,内存会被分为4k大小的内存页框(page frame:存储页面数据),当进程运行需要内存时内核就会把空闲的page frame分配给进程(一般是不连续的,但是有一个中间层可以把不连续的page frame虚拟成连续的地址空间);站在进程的角度看:进程认为只有自己和内核;
堆和栈在进程所占地址空间的两头,进程运行时数据增加,堆和栈会向中间增长;
一旦物理内存不够用了,就会启用swap分区,它会扫描内存空间,找出最近最少使用(LRU算法)的page frame页框,把它们转移到swap分区,当又有进程需要这一块数据的时候,内核会把它重新加载到内存中,并且重新建立线性地址空间和物理地址空间的映射关系;内核的task_struct会记录着进程的线性地址空间与物理内存空间的映射关系,所以进程每一次访问线性地址空间时,内核都会将其线性地址转换为对应的物理地址,然后到对应的物理地址空间中取得数据返回给进程使用;(进程只能访问自己的线性地址空间)
在CPU中有一个专门的硬件负责完成这一过程
MMU:Memory Management Unit 内存管理单元
当一个进程被加载至CPU中时,系统就会把映射关系放在MMU中完成实时转换;
一个进程有些数据是必须在内存中的,有些数据是可以被交换到swap分区或者实现就是存在磁盘中的
前者为常驻内存集,后者为虚拟内存集;
进程间通信:
IPC:Inter Process Communication
同一主机:
signal:通过信号通信;
shm:共享内存通信;
semerphor
不同主机:
rpc:remote procecure call 远程过程调用
调用其他主机上的数据(比如库函数),然后等其返回结果;
socket:套接字
通过网络实现通信,创建socket文件建立TCP或UDP链接创建虚链路,使其调用某端口处于监听状态,实时接收其他主机发来的请求;然后只需往socket文件中发送数据,就会传给远方主机,对方就可以从对应的socket文件中获取数据;
Linux内核:抢占式多任务
前面有介绍
进程类型:
守护进程:daemon,由内核在系统引导过程中启动的进程
用户进程:用户通过终端启动的进程(前台进程)
并不是所有在前台启动的进程都是用户进程,通过service启动的服务一般把它运行为守护模式;
进程状态:
运行态:running
就绪态:ready
睡眠态:
可中断:interruptable
进程运行时需要的数据已在内存中,可以直接运行使用;
不可中断:uninterruptable
当一个进程正运行那,突然需要打开一个大文件时,文件还在磁盘中,内核就需要时间来把数据加载到内存中,这段时间这个进程会把CPU让出来,让其他进程继续在CPU上运行,等它加载完数据以后再运行;
内核加载数据到内存中的步骤:首先从磁盘中把数据加载至内核内存,然后将数据从内核内存中复制一份到进程内存中再给进程使用;第一段是数据装入的过程,第二段才是系统调用上的I/O过程;
当一个进程运行需要数据进行I/O才能运行时,内核就会把它置为睡眠态,等到数据加载完毕才开始运行;
停止态:暂停于内存中,不会运行也不会被调度,除非手动启动它;
僵死态:zombie
进程分类:
CPU-Bound:CPU密集型,比如播放一个超清蓝光电影就需要很多的CPU时间来进行解码,但是不需要进行交互;所以应该多分配CPU。
IO-Bound:IO密集型,比如从键盘输入字符,它对CPU消耗不大,但是一直在使用IO;所以优先级应该高一点。
当一个进程使用CPU时间过多时,系统会降低其优先级,使其使用CPU的机会降低,但是在CPU比较空闲时,还是会给它足够的运行时间的,毕竟闲着就是浪费嘛;