Linux2.6.32内核笔记(3)进程管理子系统

时间:2021-05-25 14:47:31

    一、进程与程序的区别

    程序是一段存放在存储介质上的一系列代码和数据的映像,是一个静态的实体。

    进程是执行起来的程序,是一个动态地实体,还包括它管理的资源(如打开的文件,挂起的信号,地址空间等等)。

 

    二、进程四要素

    1.有一段程序供其执行,该程序不一定是一个进程独享,也可以和其他进程共享。

    2.有进程专用的内核空间堆栈

    3.在内核中有一个名为“进程控制块”的task_struct,内核通过结构对进程进行调度控制。

    4.有独立的用户空间。有独立的用户空间的是进程,有共享的用户空间的是用户线程,没有用户空间的是内核线程。

   

    三、进程状态

    进程有三态:就绪太,执行态和阻塞态。当进程被创建以后,就处于一个就绪太,等待cpu的调度,当cpu执行它的时候就处于执行态,在执行过程中进程比如访问别的资源,获取锁,信号量不成功,或者产生了中断,进程就会处于阻塞状态,当中断结束,得到锁和信号量之后进程就又从阻塞态变为就绪态,等待调度执行。

    比较重要的进程状态有以下三种:

    TASK_RUNNING(可执行状态)

    只有在该状态的进程才可能在CPU上运行。而同一时刻可能有多个进程处于可执行状态,这些进程的task_struct结构(进程控制块)被放入对应CPU的可执行队列中(一个进程最多只能出现在一个CPU的可执行队列中)。进程调度器的任务就是从各个CPU的可执行队列中分别选择一个进程在该CPU上运行。

    TASK_INTERRUPTIBLE(可中断睡眠状态)

    处于这个状态的进程因为等待某某事件的发生(比如等待socket连接、等待信号量),而被挂起。这些进程的task_struct结构被放入对应事件的等待队列中。当这些事件发生时(由外部中断触发、或由其他进程触发),对应的等待队列中的一个或多个进程将被唤醒。事实上,我们的很多进程大多时间都在这个状态。。。

    TASK_UNINTERRUPTIBLE/TASK_KILLABLE(不可中断睡眠状态)

    与TASK_INTERRUPTIBLE状态类似,进程处于睡眠状态,但是此刻进程是不可中断的,即使给他sigkill也不会中断,不会响应异步信号。这个状态有个缺点,就是假如这个进程一直处于这种状态,那么它就会占用cpu资源,使得其它进程一直处于等待状态,但是引入这个状态时为了对一些内核操作进行保护,比如与硬件设备进行交互的时候,为了避免此短暂的交互过程被打断,需要引入这个状态。为了对这个状态进行改进,linux2.6.25之后引入了         TASK_KILLABLE该状态和TASK_UNINTERRUPTIBLE是类似的,但是它可以被致命信号SIGKILL唤醒。

    其他的进程状态包括:

    TASK_TRACED:处于被调试状态的进程。

    TASK_DEAD:进程退出时所处的状态。

    EXIT_ZOMBIE:表示进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信。

   

    四、Linux进程描述

    Linux进程描述主要是是task_struct这个结构体,在/include/linux下的sched.h文件中,在第1215行:

    1.状态描述:

    volatile long state;

    int exit_state;

    状态有哪些我们上面已经分析过了。

   

    2.进程标识符:

    pid_tpid;

    pid_ttgid;

    这个pid就是每个进程都有一个标号,系统中有最大32768个进程,给每一个进程都分配一各进程号便于管理,Linux将一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID存放在tgid成员中。

   

   3.内核堆栈:

    void*stack;

    进程通过alloc_thread_info函数分配它的内核栈,通过free_thread_info函数释放所分配的内核栈,这里不做深究。

 

   4.进程亲属关系:

    structtask_struct *real_parent; /* real parent process*/

    structtask_struct *parent; /* recipient of SIGCHLD,wait4() reports */

    /*

     * children/sibling forms the list of mynatural children

     */

    structlist_head children;  /* list of my children */

    structlist_head sibling;   /* linkage in my parent's children list */

    structtask_struct *group_leader;  /* threadgroup leader */

    看到这个命名有点想笑,还有真爸爸和爸爸的区分(进程也会喜当爹吗?)

在Linux中,所有进程之间都有着直接或间接地联系,每个进程都有其父进程,也可能有零个或多个子进程。拥有同一父进程的所有进程具有兄弟关系。

   

    5.进程调度优先级:

    intprio, static_prio, normal_prio;

    unsignedint rt_priority;

    conststruct sched_class *sched_class;

    structsched_entity se;

    structsched_rt_entity rt;

   

    优先级定义在这里:

    #defineMAX_USER_RT_PRIO     100

    #defineMAX_RT_PRIO      MAX_USER_RT_PRIO

    #defineMAX_PRIO     (MAX_RT_PRIO+ 40)

    #defineDEFAULT_PRIO     (MAX_RT_PRIO + 20)

    实时优先级范围是0到MAX_RT_PRIO-1(即99),而普通进程的静态优先级范围是从MAX_RT_PRIO到MAX_PRIO-1(即100到139)。值越大静态优先级越低。

   

    五、Linux进程调度

    1.调度策略

    在sched.h中调度策略有以下几种:

    #defineSCHED_NORMAL     0

    #defineSCHED_FIFO       1

    #defineSCHED_RR     2

    #defineSCHED_BATCH      3

    /* SCHED_ISO:reserved but not implemented yet */

    #defineSCHED_IDLE       5

    /* Canbe ORed in to make sure the process is reverted back to SCHED_NORMAL on fork */

    #defineSCHED_RESET_ON_FORK     0x40000000

    SCHED_NORMAL用于普通进程,通过CFS调度器实现。

    SCHED_BATCH用于非交互的处理器消耗型进程。

    SCHED_IDLE是在系统负载很低时使用。

    SCHED_FIFO:先入先出调度算法。

    SCHED_RR:时间片轮流调度算法。

    其中SCHED_FIFO和SCHED_RR是实时调度算法,具体算法思想比较简单,顾名思义,具体实现这里不做深究。

 

    2.调度时机

    主动式

    当进程等待资源停止运行的时候,会处于睡眠状态,这时候直接调用schedule()请求调度,让出cpu。

    例:

    current->state= TASK_INTERRUPTIBLE

    schedule();

    使用指向当前进程状态的指针,将state改为可中断睡眠状态,然后调用schedule(),这样cpu就会调度其他资源执行,当然这个过程比较复杂,因为还涉及到当前状态的保存,进程资源是否回收等等。

    抢占式调度:

    首先,抢占的含义,当我们一个进程A在执行的时候,B进程在执行一项更加重要的任务,这时候就需要把cpu的资源让给B,如果A不能像上面一样主动地让出,那么B就去抢占cpu的资源。Linux2.4只支持用户态抢占,2.6既支持用户态抢占,也支持内核态抢占,这是2.6实时性提高的一个重要原因。当然也有非抢占机制,它的优势是中断响应很快,几乎不需要信号量来保护共享数据,但是明显的缺点就是实时性较差。2.6为什么要支持内核态抢占呢?因为有些进程或者线程一旦运行到内核态,就会一直运行,不出来,那它就会一直占有cpu的资源,其他紧急的进程或者线程就会处于等待状态,这样实时性就大大降低了。所以2.6开始允许优先级更高的进程在内核态中进行抢占。打个比方,贪官携款潜逃去从中国去了美国(进程A占有cpu资源从用户态进去了内核态),他就一直待在美国,没有引渡条例啊,这时候纪检的人很着急的要找他问话,怎么办呢?等呗。。。万一人家一辈子不回来了呢?有些事就不能处理了。所以现在国家正在积极的和欧美商讨引渡条例,也就是我们的2.6支持内核态抢占啦。

    用户态抢占发生的时机

    1.从系统调用返回用户空间。

    2.从中断处理程序返回用户空间。

    3.当某个进程耗尽它的时间片的时候。

    4.当一个优先级更高的进程处于可执行状态的时候。

    以上抢占情况发生时,还要满足need_resched被设置这个条件,这是一个触发条件。

    内核态抢占发生的时机:

    1.中断处理程序完成,返回内核空间之前。(贪官从美国(内核态)跑回中国来(用户态)来看爹妈老婆,返回去之前,被抓了(抢占)。。。)

    2.当内核代码解锁和使用软中断的时候,这时候再次具有课抢占性。

    不允许内核抢占时机

    1.内核正在运行中断处理程序

    在Linux内核中进程不能抢*断(中断只能被其他中断中止、抢占,进程不能中止、抢*断),在中断例程中不允许进行进程调度。进程调度函数schedule()会对此作出判断,如果是在中断中调用,会打印出错信息。

    2.内核正在进行中断上下文处理

    硬件中断返回前会执行软中断,此时仍然处于中断上下文中。

    3.持有锁的时候不应被抢占,如自旋锁,读写锁

    内核中的这些锁是为了在SMP系统中短时间内保证不同CPU上运行的进程并发执行的正确性。当持有这些锁时,内核不应该被抢占,否则由于抢占将导致其他CPU长期不能获得锁而死等。

    4.内核正在执行调度程序schedule的时候。

    抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序。

    5.内核正在对每个CPU“私有”的数据结构操作。

    在SMP中,对于per-CPU数据结构未用spinlocks保护,因为这些数据结构隐含地被保护了(不同的CPU有不一样的per-CPU数据,其他CPU上运行的进程不会用到另一个CPU的per-CPU数据)。但是如果允许抢占,但一个进程被抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题,这时应禁抢占。当然这里对于我们的s3c2440是不存在的。

    那么linux为了不让上述情况被抢占,设置了preempt_count抢占计数,被设置在进程的thread_info结构中。preempt_count()函数用于获取preempt_count的值,preemptible()用于判断内核是否可抢占。当进程进入上述状态时,就+1,退出时就-1,根据它的值来判断是否可抢占。

   

   3.调度步骤

    第一步:清理当前运行中的进程的一些资源。

    第二步:根据调度策略选择一个运行的进程。

    第三步:设置新的进程运行环境,例如堆栈,sp等。

    第四步:进程上下文切换,退出A,切到B。

 

    笔记做到这里,如有不正确的地方还请指出,大家共同进步。