1.进程: 由程序段、相关数据段和PCB(进程控制块,PCB是进程存在的唯一标识)组成了进程映像(进程实体)。进程可以定义为程序的一次执行过程。进程是计算机资源分配的基本单位。
2.进程间通讯方式:
①共享内存: 低级的共享是基于数据结构的共享。高级的共享是基于存储区的共享。
※ 额外简单写一下C++共享内存实现原理,具体要自行百度。
每个新创建的共享内存对象都用一个shmid_kernel的数据结构来表达。系统中所有的shmid_kernel数据结构都保存在shm_segs向量表中。该向量表的每一个元素都是一个指向shmid_kernel的指针。这个表的大小是128.故,共享内存的最大个数是128.
数据结构shmid_kernel结构如下
struct shmid_kernel
{
struct shmid_ds u;//描述共享内存信息的结构,包括基本信息和有多少进程在使用它等。
unsigned long shm_npages;//表示共享内存的页面数
unsigned long* shm_pages;//记录共享内存各个页面地址的数组
struct vm_area_struct * attaches;//用来描述被共享的物理内存对象所映射的各进程的虚拟内存区域
}
共享内存的步骤
a. 共享内存的创建和获得。 创建一个键值为key的共享内存对象。或者获得已经存在的键值为key的某共享内存对象的应用标识符。创建时要检查合法性。申请内存用于建立shmid_kernel结构。根据共享内存页面数,申请一块空间用于建立页表,将页表清零。(注意,真正的共享内存区域是在第一个进程试图访问它时才创建)。在shm_segs向量表中添加这一项。返回引用标识符。
b. 关联。 将共享内存区域映射到进程的虚拟地址空间。 从虚拟内存地址空间开辟一个空间进行映射(至于这个空间是开辟在哪,搜资料没有找到特别明确的答案,不过大体上应该在堆中),填写一个vm_area_struct结构。
c. 分离。 当进程不需要这个共享内存时,就会分离,这种分离只影响当前进程。当前进程的vm_area_struct数据结构从shmid_ds(以前查的资料怎么没提到在 attaches数组里怎么搞?感觉还得查查)删除,并被释放。当前进程的页表也被更新,共享内存对应的虚拟内存页数被标为无效。
※ java中好像只有共享文件映射?这个还需要详细查查。
②消息传递: 进程间的数据交换必须是以格式化的消息传递。方式有直接通信方式和间接通信方式。 具体实现自行百度
③管道通信:管道是指用于连接一个读进程和一个写进程以实现他们之间通信的一个共享文件。管道会被限制大小,在linux下是4KB。写进程把管道文件写满,读进程就开始读,当管道文件里还有数据时,写进程就不会写。管道通信是半双工的方式。
※ java中有一个PipeWriter和PipeReader类可以实现管道通信。使用方法,自行百度。
④信号量:用于对共享资源进行加锁。semaphore,主要操作分为P 和V,使用谁就P(),释放谁就V();
⑤socket:可用于不同主机的进程的通信
3. 进程切换的过程
① 保存处理机上下文。包括程序计数器和其他寄存器。
② 更新PCB信息
③ 把进程的PCB移入相应的队列
④ 选择另一个进程执行,并更新其PCB
⑤ 更新内存管理的数据结构
4. 进程调度的方式
进程的调度,分为作业调度,中级调度,进程调度
作业调度:主要任务是按一定的原则,从外存上处于后备状态的作业中挑选一个(或多个)作业,给它们分配资源和内存,并建立相应的进程。
中级调度:把那些暂时不能运行的进程调至外村。当它们具备运行条件且内存有空闲时。由中级调度来决定将就绪进程再调入内存。
进程调度:按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它。
进程调度的方式分为剥夺式调度和非剥夺式调度。Linux采用的是剥夺式调度。
典型的调度方法:
①先来先服务
②短作业优先
③优先级优先
④高响应比优先调度 : 响应比的计算为 (等待时间+服务时间)/服务时间
⑤时间片轮转调度
⑥多级反馈队列调度
※linux下采用的是剥夺式的具有优先级的时间片轮转调度。 高优先级的进程会获得更多的时间片。低优先级的进程时间片获得少。
5. 线程
线程是基本的cpu执行单元。由线程ID,程序计数器,寄存器集合和堆栈组成。线程自己只有一小部分资源,它可以与同属一个进程的其他线程共享进程所有用的全部资源。
线程和进程的比较
①调度:同一个进程中,线程的切换不会引起进程的切换。
②拥有资源:线程持有自己的状态和计数器,以及一部分寄存器,线程可以访问其隶属进程的系统资源。
③并发: 不仅进程间可以并发执行,多个线程之间也可以并发执行。
④系统开销:对于创建和撤销进程时,系统都要为之分配和回收资源,如内存空间、I/O设备等。进程切换还要涉及cpu环境的保存和设置。线程切换时,只需要保存少量寄存器内容。
⑤通信: 进程的地址空间相互独立。通信就使用上述的几种通信方式。而线程间共享进程资源,可以直接读写进程的数据段(如全局变量)来进行通信。
6.死锁
所谓死锁是指多个进程因竞争资源而造成的一种僵局,若无外力作用,这些进程都无法向前推进。
① 死锁发生的条件
a.互斥条件:进程要求对所分配的资源进行排他性控制。
b. 不剥夺条件: 进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走
c. 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求。这个资源已经被其它进程占有,而此进程又握着自己原先占有的资源不放。
d. 循环和等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得资源的同时又被链中的下一个进程所请求。
② 死锁处理的策略
1). 预防死锁: 破坏死锁的四个条件
2). 避免死锁: 银行家算法(具体自行百度):预分配,判断是否存在一个资源分配的安全序列。
3). 死锁的检测和解除: 用资源分配图来进行检测 (资源分配图解释转自 http://blog.youkuaiyun.com/coding1994/article/details/52474731),具体使用方法请自行百度
检测到死锁的解除方法有:
a. 资源剥夺法
b. 撤销进程法
c. 进程回退法
7.程序的装入和链接
链接分为 :
①静态链接:在程序运行前就将各目标模块和它们所需要的库函数链接成一个完整的可执行文件
②装入时动态链接:将用户源程序编译后所得到的的一组目标木块,在装入内存时,用边装入边链接的链接方式。
③运行时动态链接:对某些目标模块的链接,是在程序执行中需要该目标模块时,才对它进行链接。
装入分为:
①绝对装入。编译时,如果知道程序将驻留在内存中的某个位置,编译程序将产生绝对地址的目标代码。绝对装入程序按照装入模块中的地址,将程序和数据装入内存。绝对装入方式只适合单道程序环境。
②可重定位装入:根据内存的当前情况,将装入模块装入到内存的适当位置。装入时对目标程序中指令和数据的修改过程称为重定位,地址变换通常是在装入时一次完成的,所以又称为静态重定位。
③动态运行时装入,也称为动态重定位,把地址转换推迟到程序真正要执行时才进行。
8. linux采用的内存管理是请求分页式。 对每个进程都有一个驻留集(也称作工作集),是指某段时间间隔内。进程要访问的页面集合(可以理解为分配给进程的内存页面数)。这个驻留集的大小不是确定的,要考虑到页面置换的抖动的情形(是指,页面频繁的换出,发生抖动时即就代表给进程分配的页面太少),这个时候需要一些策略改变驻留集大小。策略有: (根据策略描述,感觉应该是采用可变分配局部置换的方法,但是我并没有查询资料,只是猜测)
①固定分配局部置换。它为每个进程分配一定数目的物理块,在整个运行期间都不改变。若进程在运行时发生缺页,只能从该进程的内存页面中选出一块换出
②可变分配全局置换。为系统的每个进程分配一定数目的物理块,操作系统也保持一个空闲物理块队列。当某进程发生缺页时,系统从空闲物理块队列取出一个物理块来分配给该进程,并将欲调入的页装入其中。
③可变分配局部置换 。它为每个进程分配一定数目的物理块,当某进程发生缺页时,只允许从该进程在内存的页面中选出一块换出。当进程发生抖动时,系统再为该进程分配若干物理块,直至该进程缺页率趋于适当程度。反之,若进程在运行中缺页率特别低,则适当减少分配给该进程的物理块。