先由程序管理说起:
程序的执行分为两种:1、程序的顺序执行。2、程序的并发执行
程序的顺序执行有三个特点(1)顺序性:每一个操作必须在下一个操作之前结束
(2)封闭性:程序运行时独占全机资源
(3)可再现性:只要程序下次执行的时候还是初始的环境,所得到的结果就是一致
程序的并发执行有三个特点(1)间断性:程序在并发执行的时候,由于共享系统资源,以及为了完成同一项任务而相互合作,致使在这些并发执行的程序之间,形成了相互制约的关系(相互制约的并发程序具有“执行-暂停-执行”)
(2)失去封闭性:多个程序共享系统中的各种资源,因而这些资源状态将由多个程序共同改变,在某个程序执行的时候,必然会引起其他程序的影响。
(3)不可再现性:程序的执行结果不可再现。
为什么引入进程:由于程序通常不能参与并发执行,而为了能够让程序参与并发执行,且为了对并发程序加以描述和控制,引入了“进程”这个概念。
进程的组成:由程序段、相关数据段、PCB三部分组成(对应于LINUX下就是:正文段、用户数据段、系统数据段)。而这三部分,正式所谓的“进程实体”。所谓进程,实际指的就是进程实体,PCB在,进程在;PCB亡,进程死
进程的动态性:进程的实质就是进程实体的一次执行。(由创建而产生,由调度而执行,由撤销而消亡)。进程实体有一定的生命期,而程序只是一组有序指令的集合,存在与某种戒指之上,其本身并不具备动的含义,因而是静态的。
进程的并发性:多个进程实体存在于内核中,且在同一时间段共同运行。程序不能并发执行(因为程序无PCB)
进程的独立性:独立性指的是进程实体是一个能够独立运行、独立分配资源和独立接受调度的基本单位。
凡是未建立PCB的程序都不能作为一个独立的单位参与运行进程的异步性:进程实体按照不同的异步方式运行。
那么,何为进程?
有人说:进程就是程序的一次执行。
有人说:进程是一个程序及其数据在处理及上顺序执行所发生的活动。
有人说:进程是一个程序在数据集合上运行的过程,它是系统进行
资源分配和调度的独立单位。
进程的基本状态
一般有:就绪态,执行态,阻塞态
就绪态就是“万事具备,只差CPU”(由于系统调度,反而转到运行态)
执行态就是“程序正在执行”(由于时间片用完,转到就绪态,由于有I/O请求而转到阻塞态度)
阻塞态就是“进程由于发生某种时间而暂时无法执行”(由于所等待哦的时间发生,而转到就绪态)
挂起状态:(为什么引入管其状态?)
(1)终端用户的请求:当终端用户在自己程序执行期间,可能发现有问题而希望自己暂停下来(该进程不接受调度,以便用户研究其执行情况并对程序进行修改,称这种静止状态为挂起状态)
(2)父进程请求:父进程希望挂起自己的孩子进程,以便考察和修改孩子进程,或者协调各个进程间活动。
(3)负荷调节的需要:当实时系统中工作负荷太重,已经可能影响到对实时任务的控制。
(4)操作系统的需要:操作系统有时候希望挂起某些进程,以便检查运行时候资源使用情况。
程序进程状态的转换:(引入挂起->非挂起状态)
1、活动就绪:当今成处于未被挂起的就绪状态时候,称此为活动就绪。
2、活动阻塞:未被挂起的阻塞状态
Linux下的进程状态:运行态、等待态、暂停待、僵尸态
运行态:把就绪态和运行态统称为运行态。
等待态(1)深度睡眠:等待资源有效被唤醒,而不能被其他信号唤醒。
(2)浅度睡眠:不止可以被有效资源唤醒,也可以被信号,时钟中断唤醒。
暂停态:SIGSTOP SIGTSTP SIGTTIN SIGTTOU
僵死态:进程执行结束,但尚未消亡。(结束并释放大量资源,但是未能释放PCB)
进程控制块
PCB是进程存在的唯一标准。系统为每个进程定义一个数据结构-task_struct(进程控制块PCB)
PCB的作用是使得一个多道环境下不能独立运行的程序,成为一个个能够独立运行的基本单位,一个个能与进程并发执行的进程。(操作系统根据PCB对并发执行的进程进行控制管理)
PCB常驻与内存,系统将所有PCB组织成若干链表。存放在操作系统专门开辟的PCB区内。
PCB包含信息:
1>进程标识符:用于唯一的标识一个进程。
2>处理机状态:当处理机被中断时,所有信息都保存与PCB
3>进程调度相关信息。
4>进程控制信息。Linux操作系统中,还可以ps命令查看当前系统中的进程 ps -e 截取部分信息,部分进程
PCB的存放:当创建一个进程,首先分配一个task_struct
每当进程从用户态进入内核态后,都是用栈(内核栈)。当进程一进入内核太,CPU主动设置该进程内核栈,这个栈位于内核数据段上。为了节省空间,LINUX内核栈+thread_info放在一起占用8字节的内存区。- union thread_union
- {
- struct thread_info thread_info;
- unsigned long stack[Thread_size/sizeof(long)];
- };//内核栈占用8kB的内存区,实际上,进程PCB所占用的内存是由内核动态分配的。更确切的说,内核根本不给PCB分配内存,而仅仅是给内核栈分配8KB,并把其中一部分让给PCB使用。
thread_info结构表示和硬件关系更紧密的一些数据。
thread_info与task_struct结构有一个域指向对方。
为什么定义thread_info?
(1)进程控制块PCB的所有成员都被引用最频繁的是thread_info
(2)PCB的内容越来越多,而留给内核栈的空间越来越小,因此把部分进程控制块内容移出,只保留thread_info
当前进程:内核很容易从ESP寄存器的值获得当前在CPU正在运行的thread_info,内核屏蔽ESP的低13位有效位就可一获得thread_info基地址
PCB的组织方式:
(1)链接方式:把具有同一状态的PCB,用其链接字链接成一个队列(形成就绪队列,若干阻塞队列,空白队列)
(2)索引方式:建立就绪索引表,阻塞索引表。
Linux系统中进程的组织方式1、进程链表:对于给定类型的进程进行有效的搜索,内核建立几个进程链表。由指向进程PCB指针组成
双向循环链表。:
链表头和尾巴窦唯init_task(0号内核线程PCB)。这个进程永远不会被撤销。init_task的PCB是预先由编译器分配的,它在运行过程中
保持不变,而其他PCB是在运行过程中,由系统根据当前内存情况分配的,撤销时候归还给系统。
2、哈希表:内核必须根据进程的PID导出对应的PCB。(为了加速查找,引入了哈西表)
- #define inline struct task_struct *find_task_by_pid(int pid)
- {
- struct task_struct *p,**htable=&pidhash[pid_hashfn(pid)];
- for(p=*htable;p&&p->pid!=pid;p=p->pidhash_next);
- return p;//Linux利用链表地址处理冲突,task_struct结构中两个域:pidhash_next和pidhash_prev来实现这个链表,同一个链表PID由小到大。
- }
3、就绪队列
当内核要寻找一个新进程在CPU上运行,必须值考虑处于就绪队列上的进程,可以把运行状态的进程组成一个双向循环链表。
4、等待队列
仅仅唤醒等待队列中
一个进程才有意义,flags域用来区分睡眠时候的互斥进程和非互斥进程flags=1互斥,flags=0,非互斥。
进程调度的基本原理1、时间片轮转:每个进程一次按照时间片轮流执行(先来先服务,在一个给定的时间里均能获得一个时间片)
2、优先权调度
1>非抢占式:即系统一旦将CPU分配给运行队列中优先权最高的进程后,便一直执行完成。
2>抢占式:系统中运行的进程永远是可运行进程中优先级最高的那个
3、多级反馈队列:即优先权搞的先运行给定时间片,相同优先权的进程轮流运行
4>实时调度(实时系统:对外部时间
有求必应,尽快相应)抢占式调度。
何为时间片?进程被抢占钱所能够持续运行的时间。(默认为20ms,过长,则交互能力差。过短,则切换频率,浪费大)
5>
楼梯算法
就绪队列分为两组:活跃,时间片完
优先级的动态调整:每次时钟节拍终端,进程时间片-1,当时间片为0,调度程序判断类型,若为交互式,则重置其时间片并重新插入active数组,如果不是交互式的,则从active->expired
抛弃了动态优先级的概念,而采用一种完全公平的思想。
同样为一个优先级维护一个进程队列,并将其组织在active数组中,当选取下一个被调度进程时候,SD算法也同样从active直接读取。
当进程时间片用完,并不放入expired数组,而是更低一级优先队列中。eg:进程A优先级为“1”,到达最后一个台阶时候,再次用完时间片时候将回到优先级为“2”的任务队列,该
时间片变为原来的2倍。调度函数schedule//用它来决定是否要进行进程的切换。
Linux的调度时机:
(1)进程中止,睡眠
(2)当前时间片用完,每个队列组中元素以优先级再分类,相同优先级的一个队列。
(3)设备驱动程序运行
(4)从内核到用户
进程的创建:只要用户输入一条命令,shell进程就创建一个新进程,新进程执行shell的一个拷贝。
fork()创建子进程->再fork()再创建//可能形成完整的树,每个进程只有一个父进程,但可以有多个子进程。
引起进程创建的事情:再多道程序环境下,只有作为进程时候,才能在系统中运行。
LINUX启动,创建init特殊进程,以后诞生的都是它的后代。
init为每个终端TTY创建一个新的管理进程,这些进程在终端上等待用户登录,当登录,再为每个永启动一个shell进程,由shell进程接受用户信息。
fork()的使用写实复制来实现,也就是调用fork()时候,内核并没有父进程的全部资源复制一份,而是将这些内容设置为只读状态,当父进程/紫禁城要对其进行修改的时候,内核才在修改之前将修改部分进行拷贝。
fork()的开销fork()的开销就是复制父进程页表,以及子进程创建PCB
fork()返回(1)对于父进程,返回新创建紫禁城的PID
(2)对于子进程,返回0
(3)对于错误,返回小于0的数。
创建进程:通过克隆而简历的系统调用可用来建立新进程
系统调用结束后,内核在内存中为进程分配PCB,同时新近成要使用堆栈分配无力页。
Linux为新近成分配新进程PCB(新PCB保存在链表中,而父进程PCB被复制到新PCB中)
在克隆进程时候,LINUX允许父进程和子进程共享相同资源。当某个资源被共享时候,资源引用数目+1,从而当这些进程都终止的时候,内核才会释放这些资源。
do_fork()如下:
(1)调用alloc_task_struct()函数获取8KB的union task_union内存区,用来存放内核栈。
(2)当前指针指向父进程PCB,并把父进程PCB内容拷贝到子进程PCB,此时,父子进程PCB一样。
(3)检查新创建的这个紫禁城后,当前用户所拥有的进程数目是否超过。
(4)现在,do_fork()已经获得它从父进程能利用的一切资源,接下来建立子进程的薪资远,并让内核知道新进程建立
(5)子进程被设置为“免打扰睡眠”
(6)调用get_pid()为新进程获得有效PID
(7)更新不能从父进程获得的所有PCB的其他域
(8)根据传递给clone()的参数标记,拷贝/共享打开文件等。
(9)把新PCB插入pidhash哈希表,把子进程PCB状态设置为“浅睡眠”,并且调用wak_up,把子进程插入运行队列
(10)让父子进程平分剩余时间片,返回子进程PID,由用户态下的父进程读取//解释了为什么父进程返回子进程PID
阅读(2804) | 评论(1) | 转发(0) |