第一次作业:基于Linux-0.12的进程分析

Posted 马骁悦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第一次作业:基于Linux-0.12的进程分析相关的知识,希望对你有一定的参考价值。

  这次作业主要基于Linux-0.12的源代码,分析Linux是如何组织进程,进程的状态之间是如何转换,以及进程是如何调度的。

一. 进程的概念:

  1.进程就是:程序在数据集合上的一次运行过程,是系统进行资源分配和调度的独立单位。

  2.对进程的静态描述为:是一个数据集合,以及在其上运行的程序。

  3.我本来认为进程与程序是差不多的东西,但发现他们其实并不一样,进程是一个动态的概念,不同于程序又依赖于程序,既有联系又有区别,进程具备程序不具备的特征,比如:

   1).动态特征:进程具有生命周期,创建之后才产生,调度运行得不到资源就发生阻塞撤销之后就消亡。进程本身就是一个执行过程,程序,却仅仅是个静态文本(指令合集);

   2).并发特征:多个进程实体,同存于主存中,能在一段时间内同时运行。由此可见进程的并发特征是其第二基本特征,程序不具备并发性;

   3).独立特征:进程是系统进行资源分配和调度的一个基本单位,程序段是不可做为独立单位接收资源分配和调度的;

   4).结构特征(静态特征):为了描述进程的运动变化过程系统为每一个进程配置了一个进程控制块(PCB:Process Control Block),这样静态的看或从结构上看,进程就由正文段,数据集合(结构)以及PCB三部分组成,一般将这三部分组成结构成为进程映像:

进程映像

  

   5). 异步特征:各进程按照其各自独立的,不可预知的速度向前推进。

  In a word, 进程 == 可以和其他程序并发执行的 程序的 一次执行。

二. Linux操作系统是怎么组织进程的:

  1. Linux是一个多任务的开放式操作系统,进程就是许多分离的任务,每一个进程彼此独立,并通过进程间的通信机制实现进程之间的同步与互斥。在Linux系统中,进程与任务是相同的概念。

  2. 系统中有许多进程,Linux要对其进行管理和调度,就要通过存放在系统数据段中的进程控制信息,其中最重要的就是task_struct数据结构。

      • 正文段:是具有可再入性的代码组成的程序(可再入程序也称纯代码,可同时被多个进程或程序所共享的程序),因此对用不同的数据集,可以构成不同的进程,所以正文段是可以调用但是不可以修改的,一般情况下是由系统程序组成。
      • * 进程控制块(PCB):PCB中记录了用于描述进程情况及控制进程运行所需的全部属性信息。系统通过PCB感知进程的存在,是进程进行控制存在的唯一标识。系统根据PCB来对并发执行的进程进行控制和管理。系统会在创建进程时就创建该进程的PCB,在撤销一个进程时就撤销其PCB。当操作系统要调用进程执行时。需要从该进程的PCB中查询其现行状态机优先级等调度参数,在调度到某进程后,要根据PCB中保存的处理机状态信息去设置进程回复运行的现场,并根据其PCB中的进程和数据的内存地址来找到程序和数据;进程在执行过程中,当需要与其它进程通信时,也需要访问其PCB;当进程因某种原因而暂停执行时,需要将断点的现场信息保存在其PCB中。
      • 数据集合:由数据所组成的集合。 
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

 

操作系统原理与分析(第一版);曹聪,范廉明 著;科学出版社

 

以上是关于第一次作业:基于Linux-0.12的进程分析的主要内容,如果未能解决你的问题,请参考以下文章

第一次作业:基于Linux进程模型分析

第一次作业:基于Linux系统深入源码分析进程模型

第一次作业:基于Linux0.01深入源码分析进程模型

第一次作业:基于Linux2.6内核源码进程模型分析

第一次作业:基于Linux2.6.30进程模型分析

第一次作业:基于Linux操作系统的进程模型分析