此次做业主要基于Linux-0.12的源代码,分析Linux是如何组织进程,进程的状态之间是如何转换,以及进程是如何调度的。html
一. 进程的概念:node
1.进程就是:程序在数据集合上的一次运行过程,是系统进行资源分配和调度的独立单位。linux
2.对进程的静态描述为:是一个数据集合,以及在其上运行的程序。session
3.我原本认为进程与程序是差很少的东西,但发现他们其实并不同,进程是一个动态的概念,不一样于程序又依赖于程序,既有联系又有区别,进程具有程序不具有的特征,好比:数据结构
1).动态特征:进程具备生命周期,建立以后才产生,调度时运行,得不到资源就发生阻塞,撤销以后就消亡。进程自己就是一个执行过程,程序,却仅仅是个静态文本(指令合集);并发
2).并发特征:多个进程实体,同存于主存中,能在一段时间内同时运行。因而可知进程的并发特征是其第二基本特征,程序不具有并发性;异步
3).独立特征:进程是系统进行资源分配和调度的一个基本单位,程序段是不可作为独立单位接收资源分配和调度的;函数
4).结构特征(静态特征):为了描述进程的运动变化过程系统为每个进程配置了一个进程控制块(PCB:Process Control Block),这样静态的看或从结构上看,进程就由正文段,数据集合(结构)以及PCB三部分组成,通常将这三部分组成结构成为进程映像:ui
进程映像this
5). 异步特征:各进程按照其各自独立的,不可预知的速度向前推动。
In a word, 进程 == 能够和其余程序并发执行的 程序的 一次执行。
二. Linux操做系统是怎么组织进程的:
1. Linux是一个多任务的开放式操做系统,进程就是许多分离的任务,每个进程彼此独立,并经过进程间的通讯机制实现进程之间的同步与互斥。在Linux系统中,进程与任务是相同的概念。
2. 系统中有许多进程,Linux要对其进行管理和调度,就要经过存放在系统数据段中的进程控制信息,其中最重要的就是task_struct数据结构。
PCB一般包含的内容 进程描述信息 进程控制和管理信息 资源分配清单 处理机相关信息 进程标识符(PID) 进程当前状态 代码段指针 通用寄存器值 用户标识符(UID) 进程优先级 数据段指针 地址寄存器值
3. 在linux内核代码定义了task_struct 数据结构,包含了一个进程全部的信息:
1 struct task_struct { 2 /* these are hardcoded - don't touch */
3 long state; /* -1 unrunnable, 0 runnable, >0 stopped */
4 long counter; 5 long priority; 6 long signal; 7 struct sigaction sigaction[32]; 8 long blocked; /* bitmap of masked signals */
9 /* various fields */
10 int exit_code; 11 unsigned long start_code,end_code,end_data,brk,start_stack; 12 long pid,pgrp,session,leader; 13 int groups[NGROUPS]; 14 /*
15 * pointers to parent process, youngest child, younger sibling, 16 * older sibling, respectively. (p->father can be replaced with 17 * p->p_pptr->pid) 18 */
19 struct task_struct *p_pptr, *p_cptr, *p_ysptr, *p_osptr; 20 unsigned short uid,euid,suid; 21 unsigned short gid,egid,sgid; 22 unsigned long timeout,alarm; 23 long utime,stime,cutime,cstime,start_time; 24 struct rlimit rlim[RLIM_NLIMITS]; 25 unsigned int flags; /* per process flags, defined below */
26 unsigned short used_math; 27 /* file system info */
28 int tty; /* -1 if no tty, so it must be signed */
29 unsigned short umask; 30 struct m_inode * pwd; 31 struct m_inode * root; 32 struct m_inode * executable; 33 struct m_inode * library; 34 unsigned long close_on_exec; 35 struct file * filp[NR_OPEN]; 36 /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
37 struct desc_struct ldt[3]; 38 /* tss for this task */
39 struct tss_struct tss; 40 };
三. 进程状态如何转换(给出进程状态转换图):
1. 在多道程序系统中,多个进程都要在CPU上运行,有时还要申请使用其余资源,因为资源的宝贵性,使得并不是每一个进程都能当即获得资源,从而致使进程之间的竞争(竞争是由两个以上进程以显式或隐式的方式共享资源而引发的状态)。
2. 通常状况下进程有三种状态,就绪(资源,CPU),运行(资源,CPU),阻塞(资源,CPU)。
3. Linux在每一个进程的task_struct结构中,定义了state域来描述进程的调度状态,共有五种,定义以下:
1 #define TASK_RUNNING 0
2 #define TASK_INTERRUPTIBLE 1
3 #define TASK_UNINTERRUPTIBLE 2
4 #define TASK_ZOMBIE 3
5 #define TASK_STOPPED 4
#define TASK_RUNNING 0
1)运行态或可运行态:已经占有CPU正在运行,或者正处于运行队列中,等待着系统的进程调度程序schedule()将CPU分配给它。系统中有一个运行队列run_queue,容纳了全部处于可运行状态的进程,当前正在运行的进程也一直处于该队列中,由全局变量current指向。
*该状态是Linux与通常操做系统的区别,在其余操做系统中,只有正在使用CPU的进程才处于运行状态,其余都处于就绪状态。
#define TASK_INTERRUPTIBLE 1
2)进程可中断的睡眠态:因等待某一事件或某种资源而加入等待队列,等待资源有效时被唤醒。
#define TASK_UNINTERRUPTIBLE 2
3)进程不可中断的睡眠态:此时的进程由于硬件条件的不知足而睡眠,处于等待队列中,在任何状况下都不能被打断,除非经过特定的方式来唤醒,好比经过唤醒函数wake_up()等。
#define TASK_ZOMBIE 3
4)进程僵死状态(终结态):当进程使用系统调用exit自我消亡时,将state设为此状态。发送信号给父进程并释放占有的系统资源,但它的task_struct结构仍未释放。父进程经过系统调用wait收集其中包含的出口码及一些计时信息后,释放他的task_struct结构。
#define TASK_STOPPED 4
5)进程被暂停运行状态:进程暂时中止运行来接受某种处理,经过其余进程的信号才能唤醒。
4. 进程状态的转换:
1).Linux中用户进程是由父进程执行系统调用fork()或者clone()等建立的。这些系统调用都经过调用do_fork()函数来完成子进程的建立。do_fork()函数建立一个新进程,为其建立一个task_struct结构,继承父进程现有的资源,子进程建立后的状态为TASK_RUNNING态,父进程将它挂入到运行队列run_queue中,等待处理器的分配。
2).得到CPU而正在运行的进程若是申请不到某个资源,则调用函数sleep_on()或interruptible_sleep_on()转入睡眠,其task_struct结构从run_queue队列移入xiangying的等待队列。若是调用sleep_on(),则其状态转入不可中断的睡眠态TASK_UNINTERRUPTIBLE,若是调用interruptible_sleep_on(),则其状态转入可中断睡眠态TASK_INTERRUPTIBLE 。不管转入哪一种睡眠状态,都将调用schedule()函数把睡眠进程释放的CPU从新分配给run_queue队列中的某个可运行的进程。
3).转入TASK_INTERRUPTIBLE的睡眠进程在它申请的资源有效时将被唤醒(被某函数,信号或者中断),而转入TASK_UNINTERRUPTIBLE的睡眠进程只有在它申请的资源有效时被唤醒,不能被信号,定时器中断唤醒。这些被唤醒的进程都转入TASK_RUNNING状态, 并进入run_queue队列。
4).当进程收到暂停或中止信号时,状态转入TASK_STOPPED ,暂停运行,CPU从新分配给run_queue队列中的其余可运行进程,只有经过其余进程发送恢复信号,才能把TASK_STOPPED 进程唤醒,从新进入run_queue队列。
5).当进程运行完成,执行系统调用exit()或do_exit()时,进程转入僵死态TASK_ZOMBIE,释放所占有的系统资源,同时启动schedule()把CPU分配给run_queue队列中的其余进程。
5. 进程状态转换图:
四. 进程是如何调度的:
1. Linux主要采用了基于优先权的时间片轮转法为进程分配CPU。按照这种调度方法,系统给每一个运行进程分配一个时间片,而优先权的大小又决定了哪一个进程被调度运行(Linux的进程调度并不局限于某一种调度策略,它融合了基于优先权的轮转法调度,基于优先权的先进先出调度以及多级反馈轮转调度的策略,具备很高的综合性)。
2.
long priority;
1). 进程(实时和普通)的优先级反映了进程相对于其余进程的可选择度,也是系统每次容许进程运行的时间。
long counter;
2). 进程运行所剩余的时间片,每次时钟中断发生时,值-1,直到为0,counter = 0表示进程的时间片已经用完,要中止运行。
3. Linux进程调度时机主要有:
1). 进程状态转换的时时。如进程终止、进程睡眠;
2). 当前进程的时间片用完时(current->counter=0),要从新选择一个进程;
3). 设备驱动程序,直接调用schedule();
4). 进程从中断、异常及系统调用处理后返回到用户态时。
4. Linux-0.12 schedule()以下:
void schedule(void) { int i,next,c; struct task_struct ** p; /*有信号来时,唤醒进程*/
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) { if ((*p)->timeout && (*p)->timeout < jiffies) { (*p)->timeout = 0; if ((*p)->state == TASK_INTERRUPTIBLE) (*p)->state = TASK_RUNNING; } if ((*p)->alarm && (*p)->alarm < jiffies) { (*p)->signal |= (1<<(SIGALRM-1)); (*p)->alarm = 0; } if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) && (*p)->state==TASK_INTERRUPTIBLE) (*p)->state=TASK_RUNNING; }
while (1) { c = -1; next = 0; i = NR_TASKS; p = &task[NR_TASKS]; while (--i) { if (!*--p) continue; if ((*p)->state == TASK_RUNNING && (*p)->counter > c) c = (*p)->counter, next = i; } if (c) break; for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) (*p)->counter = ((*p)->counter >> 1) + (*p)->priority; } switch_to(next); }
五. 我对Linux操做系统进程模型的见解:
Linux支持多进程,进程控制块task_struct结构包括进程标识,进程状态,进程调度,进程指针,文件管理和虚存管理等,Linux对普通进程采用的是优先级调度策略。尽可能公平合理的进行各进程之间的调度。
六. 参考资料:
https://blog.csdn.net/hgnuxc_1993/article/details/54847732
http://www.docin.com/p-820504201.html
https://blog.csdn.net/songjinshi/article/details/23262923
操做系统原理与分析(初版);曹聪,范廉明 著;科学出版社