zoukankan      html  css  js  c++  java
  • 源码阅读——进程管理


    *阅读版本为linux-2.6.12.1


    1.     进程管理的核心功能及相应原理

    通过调研相关文献资料(来源于读书报告网络博客相关书籍),我将进程管理的核心功能大致分为四类:控制,同步,通信,调度。

    进程控制:进程控制包括对单个进程本身的一些操作,比如控制进程的创建和删除以

    及状态的更迭等。下面详细描述一下几个重要操作

     

    1)进程的创建

    一个进程可以创建一个子进程,子进程会继承父进程所拥有的资源,如继承父进程打开的文件、分配到的缓冲区等,当子进程被撤销时,应该讲其从父进程哪里获得的资源归还给父进程,此外,撤销父进程时,也必须同时撤销其所有的子进程。

    进程创建的步骤包括:① 申请空白PCB,获得唯一的数字标识符,从PCB集合中索取一个空白的PCB。② 为新进程分配资源。③ 初始化进程控制块,包括:将系统分配的标识符和父进程标识符填入新的PCB中;使程序计数器指向程序的入口地址,使栈指针指向栈顶;将进程的状态设置为就绪状态或静止就绪状态。④ 将新进程插入到就绪队列,如果进程就绪队列能够接纳新进程,便将新进程插入就绪队列。

     

    2) 进程的终止

    引起进程终止的事件有:①正常结束异常结束,在进程运行期间,由于出现某些错误和故障而迫使进程终止。如越界错误,非法指令,等待超时,算术运算错,I/O故障等等。③ 外界干预,进程应外界的请求而终止运行。

    终止进程的步骤如下:① 根据被终止的进程的标识符,从PCB集合汇总检索除该进程的PCB,从中读出该进程的状态。② 若被终止的进程正处于执行状态,应立即终止该进程的执行,并置调度标志位真,用于指示该进程被终止后应重新进行调度。③若该进程还有子孙进程,还应将其子孙进程予以终止,以防他们成为不可控的进程。④ 将被终止的进程所拥有的全部资源,或者归还给其父进程,或者归还给操作系统。⑤ 将被终止的进程PCB从所在队列或链表中移出,等待其他程序来搜集信息。

     

    3) 进程的阻塞与唤醒

    引起进程阻塞与唤醒的事件有:① 请求系统服务,由于某种原因,操作系统并不立即满足该进程的要求,该进程只能转变为阻塞状态来等待。②当进程启动某种操作后,该进程必须在该操作完成之后才能继续执行,则必须先使该进程阻塞。③新数据尚未到达,进程所需数据尚未到达,该进程只有(等待)阻塞。④ 无新工作可做,系统往往设置一些具有某些特定功能的系统进程,每当这种进程完成任务后,便把自己阻塞起来以等待新任务到来。

    进程阻塞步骤包括:进程的阻塞是进程自身的一种主动行为,之后进程会停止执行,并将进程的状态由执行改为阻塞,并将PCB插入阻塞队列,如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞队列中,最后,转调度程序进行重新调度,将处理机分配给另一就绪进程并进行切换。即保留被阻塞进程的处理机状态到PCB中,再按新进程的PCB中的处理机状态设置CPU环境。

    进程唤醒步骤如下:当被阻塞进程所期待的时间出现时,如I/O完成获其所期待的数据已经到达,则由有关进程(如用完并释放I/O设备的进程)调用唤醒原语wakeup,将等待该事件的进程唤醒,首先将被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。值得注意的是,block原语与wakeup原因应该在不同进程中执行。

     

    进程同步进程同步主要是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效共享资源和相互合作,而从使程序的执行具有可再现性。

    总体来说,进程同步通过临界区来实现,临界区问题必须确保:互斥,前进,有限等待。临界区前后分别由进入区和退出区,其它为剩余区。

    临界区问题有两类处理方法:抢占内核和非抢占内核。非抢占内核数据从根本上不会导致竞争条件,而抢占内核情况下则复杂得多。但是考虑到后者在响应时间和实时编程的优势,这种复杂值得花费力气解决。这里讨论一些solution proposals。(a)使临界区不可被打断,与非抢占内核类似,进程在临界区内时不允许上下文切换,我们可以通过一个系统调用来实现这个需求。(b)严格轮换,但是忙等会浪费CPU资源。而且轮换如此严格,使连续多次执行某个进程的临界区成为不可能。(c)对严格轮换进行改进得到Petersons solution,使用了两个共享数据项int turnboolean flag。但是同样会导致忙等,而且可能会使进程的优先权错位(d)在硬件上实现互斥锁,同样忙等。

    实际最终我们选择的方案是——信号量。信号量是一种数据类型,只能通过两个标准原子操作访问wait()signal()。信号量通常分为计数信号量和二进制信号量,后者有时称为互斥锁。可以使用二进制信号量处理多进程的临界区问题,而计数信号量可以用来控制访问具有若干实例的某种资源,此时信号量表示可用资源的数量。

    当一个进程位于临界区时,其他试图进入临界区的进程必须在进入代码中连续地循环,这种称为自旋锁,会导致忙等。为了克服这一点可以修改wait()signal()地定义——当一个进程执行wait()需要等待的时候,改为阻塞自己而不是忙等。阻塞操作将此进程放入到与信号量相关的等待队列中,状态改为等待,然后会选择另一个进程来执行。

    考虑进程同步时,很重要的一点是避免死锁。死锁的特征包括:互斥、占有并等待、非抢占、循环等待。当死锁发生时,进程永远不能完成,所以必须解决。有三种方法:(1)使用协议确保死锁不发生(2)允许死锁然后检测恢复(3)认为死锁不存在。

    此外,进程同步中有三个经典问题,用来检验新的同步方案——生产者消费者问题、读者-写者问题、哲学家进餐问题,可以用来分析中的各种情况包括死锁问题,这里不作具体分析。

    进程通信指进程之间的信息交换,进程的互斥和同步,由于只能交换很少量的信息而被归结为低级通信,目前的高级通信机制可归结为三大类

    ①    共享存储器系统

    相互通信的进程共享某些数据结构或共享存储区,进程之间能够通过这些空间进行通信,基于此,又可以分为如下两种类型:基于共享数据结构的通信方式,在这种通信中,要求诸进程共用某些数据结构,借此实现进程间的信息交换。基于共享存储区的通信方式,为了传输大量数据,在存储器中划出一块共享存储区,诸进程可通过对共享存储区中的数据的读或写来实现通信。

    ②    消息传递系统

    进程间的数据交换是以格式化的消息为单位,程序员直接利用操作系统提供的一组通信命令(原语),不仅能实现大量数据的传递,而且还隐藏了通信的实现细节,使通信过程对用户是透明的,从而大幅减少通信程序编制的复杂性。

     

    ③    管道通信

    连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件,向管道(共享文件)提供输入的发送进程,以字符流形式将大量的数据送入管道;而接受管道输出的接受进程,则从管道中接受数据,由于发送和接受进程是利用管道进行通信的,因此叫做管道通信。管道通信需要具有三方面的协调能力:互斥(当一个进程正在对pipe执行读/写时,其他进程必须等待),同步(当写进程把一定数量的数据写入pipe,便去睡眠等待,到读进程取走数据后,再把它唤醒,当读进程读一个空pipe时,也应该睡眠等待,直到有数据写入管道,才将其唤醒),确定对方是否存在,只有确定了对方已存在时,才能进行通信。

     

    进程调度:在多道程序系统中,进程的数量往往多于处理机的个数,进程争用处理机的情况就在所难免。处理机调度是对处理机进行分配,就是从就绪队列中,按照一定的算法(公平、髙效)选择一个进程并将处理机分配给它运行,以实现进程并发地执行。进程调度的算法多样,常见的有:

    a) 先到先服务算法:即先请求cpu 的进程先分配到进程,实现简单,但平均等待时间通常较长。考虑FCFS 调度在动态情况下,会产生护航效果,会导致cpu 和设备使用率变得很低。

    b) 最短作业优先调度:cpu 空闲时,它会赋给具有最短cpu 区间的进程,SJF 算法可证为最佳,其平均等待时间最小。但是困难在于如何知道下一个cpu 区间的长度。一种方法是近似SJF 调度,可以用以前cpu 长度的指数平均来预测下一个区间长度。  该算法可改善平均周转时间和平均带权周转时间,缩短进程的等待时间,提高系统的吞吐量。 但是对长进程非常不利,可能长时间得不到执行,且难以准确估计进程的执行时间,从而影响调度性能。

    c) 优先级调度:每个进程都会有一个优先级,具有最高优先级的进程会被分配到cpu。这种算法的主要问题是无穷阻塞或者饥饿,可以使用老化的方法来处理。

    d) 轮转法调度:专门为分时系统设计,与FCFS 类似,但是增加了抢占。这里定义了一个时间片, 就绪队列作为循环队列,每个进程分配不超过一个时间片的cpu。  时间片轮转调度算法的特点是简单易行、平均响应时间短。 不利于处理紧急作业。在时间片轮转算法中,时间片的大小对系统性能的影响很大,因此时间片的大小应选择恰当 

    e) 多级队列调度:将就绪队列分为多个独立队列,根据进程属性每个队列有自己的调度算法。而且队列之间必须有调度,通常采用固定优先级抢占调度。例如,前台队列可以比后台队列具有绝对的优先级,这样也符合交互的要求。

         f) 多级队列反馈调度:与多级队列相比,差异在于允许进程在队列之间移动。

    2.     数据结构分析

            我们学过,进程控制块(PCB)的是进程管理的关键。一个进程是由一个进程控制块来描述的。那么首先需要做的就是找到这部分代码。在linux/sched.h中可以找到task_struct结构体,这是一个用于进程信息保存的结构体,包含可大量的内置类型和自定义结构体指针类型,用于linux内核进程的控制能力。 下面是截取了一小部分代码:

    代码较长,列举部分源码主要分析一下一下结构体内部结构变量的作用(省略号省去部分代码):

    structtask_struct {
    
    ......
    
     /* 进程状态 */
    
        volatilelongstate;
    
     /* 指向内核栈 */
    
        void*stack;
    
     /* 用于加入进程链表 */
    
    structlist_head tasks;
    
        ......
    
     /* 指向该进程的内存区描述符 */
    
    structmm_struct*mm,*active_mm;
    
    ......
    
     /* 进程ID,每个进程(线程)的PID都不同 */
    
        pid_t pid;
    
     /* 线程组ID,同一个线程组拥有相同的pid,与领头线程(该组中第一个轻量级进程)pid一致,保存在tgid中,线程组领头线程的pid和tgid相同 */
    
        pid_t tgid;
    
      /* 用于连接到PID、TGID、PGRP、SESSION哈希表 */
    
    structpid_link pids[PIDTYPE_MAX];
    
    ......
    
      /* 指向创建其的父进程,如果其父进程不存在,则指向init进程 */
    
        structtask_struct __rcu *real_parent;
    
      /* 指向当前的父进程,通常与real_parent一致 */
    
        structtask_struct __rcu *parent;
    
      /* 子进程链表 */
    
        structlist_head children;
    
      /* 兄弟进程链表 */
    
        structlist_head sibling;
    
      /* 线程组领头线程指针 */
    
        structtask_struct*group_leader;
    
      /* 在进程切换时保存硬件上下文(硬件上下文一共保存在2个地方: thread_struct(保存大部分CPU寄存器值,包括内核态堆栈栈顶地址和IO许可权限位),内核栈(保存eax,ebx,ecx,edx等通用寄存器值)) */
    
        structthread_struct thread;
    
      /* 当前目录 */
    
        structfs_struct*fs;
    
     /* 指向文件描述符,该进程所有打开的文件会在这里面的一个指针数组里 */
    
    structfiles_struct*files;
    
    ......
    
    /*信号描述符,用于跟踪共享挂起信号队列,被属于同一线程组的所有进程共享,也就是同一线程组的线程此指针指向同一个信号描述符 */
    
    structsignal_struct*signal;
    
    /*信号处理函数描述符 */
    
    structsighand_struct*sighand;
    
    ......
    
    }

    其中关于进程的状态分为两种,struct_task中成员state(关于运行的状态)和exit_state(关于退出的状态),参见下图:


    TASK_RUNNING : 这个状态是正在占有cpu或者处于就绪状态的进程才能拥有。

    TASK_INTERRUPIBLE :进程因为等待一些条件而被挂起进(阻塞)而所处的状态。一旦等待的条件成立,进程就会从该状态(阻塞)迅速转化成为就绪状态,也就是state域的值变为TASK_RUNNING。

    TASK_UNINTERRUPIBLE :其实他和TASK_INTERRUPIBLE 大致相同,除了传递一个信号和中断所引起的效果不同。

    TASK_STOP :进程的执行被停止,当进程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信号之后就会进入该状态。

    TASK_TRACED :进程执行被调试程序所停止,当一个进程被另外的进程所监视,每一个信号都会让进城进入该状态。

    EXIT_ZOMBIE :进程已经终止,但是它的父进程还没有调用wait4或者waitpid函数来获得有关进程终止的有关信息,在调用这两个函数之前,内核不会丢弃包含死去进程的进程描述符的数据结构的,防止父进程某一个时候需要着一些信息。

    EXIT_DEAD :进程被系统释放,因为父进程已经调用了以上所提到的函数。现在内核也就可以安全的删除该进程的一切相关无用的信息了。

     

    3.     进程创建

    下面是在linux-2.6.12.1archx86_64kernelprocess.c中的系统调用代码:

    按要求找到fork()和vfork()如下

    asmlinkage long sys_fork(structpt_regs*regs)
    
    {
    
        return do_fork(SIGCHLD, regs->rsp, regs, 0, NULL, NULL);
    
    }
    
     
    
    asmlinkage long sys_vfork(structpt_regs*regs)
    
    {
    
        return do_fork(CLONE_VFORK | CLONE_VM |SIGCHLD, regs->rsp, regs, 0,
    
                NULL, NULL);
    
    }

           从源码可以看到do_fork()均被上述两个系统调用所调用,但是笔者也发现do_fork()也出现在了clone()中,其实clone()也有创建进程的功能。则三个系统调用的执行过程如下图所示:

     

    这里着重分析一下do_fork();

    long do_fork(unsignedlong clone_flags,
    
    //该标志位的4个字节分为两部分.最低的一个字节为子进程结束时发送给父进程的信号代码,通    常为SIGCHLD(用于wait系统调用).剩余的三个字节则是各种clone标志的组合.通过clone标志可以有选择的对父进程的资源进行复制. 
    
             unsignedlong stack_start,//未被使用,通常被赋值为0
    
             structpt_regs*regs,//
    
             unsignedlong stack_size,
    
             int__user *parent_tidptr,
    
    //父进程在用户态下pid的地址,该参数在CLONE_PARENT_SETTID标志被设定时有意义
    
             int__user *child_tidptr)
    
    // 子进程在用户态下pid的地址,该参数在CLONE_CHILD_SETTID标志被设定时有意义
    
     
    
    {
    
        structtask_struct*p;
    
        int trace =0;
    
        long pid =alloc_pidmap();
    
     
    
        if (pid <0)
    
            return-EAGAIN;
    
        if (unlikely(current->ptrace)) {
    
            trace = fork_traceflag (clone_flags);
    
            if (trace)
    
                clone_flags|= CLONE_PTRACE;
    
        }
    
     
    
        p = copy_process(clone_flags, stack_start, regs, stack_size,parent_tidptr, child_tidptr, pid);//得到一个进程描述符, 内核栈, thread_info与父进程一样的子进程描述符
    
        /*
    
         * Do this prior waking up the newthread - the thread pointer
    
         * might get invalid after thatpoint, if the thread exits quickly.
    
         */
    
        //若copy_process执行不成功,则先释放已分配的pid,根据ptr_err得到错误码,保存在pid中。如果成功,则执行如下代码:首先定义了一个完成量vfork,如果clone_flags包含CLONE_VFORK标志,那么将进程描述符中的vfork_done字段指向这个完成量,之后再对vfork完成量进行初始化。
    
     
    
        if (!IS_ERR(p)){
    
            structcompletion vfork;//定义了一个完成变量, 以便clone_flags标志设置了CLONE_VFORK时, 父进程阻塞, 直到子进程调用exec或exit时
    
     
    
            if (clone_flags & CLONE_VFORK) {
    
                p->vfork_done =&vfork;
    
                init_completion(&vfork); //初始化完成变量
    
            }
    
            
    
             //如果子进程被跟踪(在父进程被跟踪的前提下,一般来说父进程是很少被跟踪的)或者设置了CLONE_STOPPED标志,就为子进程增加挂起信号。
    
            if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
    
                /*
    
                * We'll start up with an immediate SIGSTOP.
    
                */
    
                sigaddset(&p->pending.signal, SIGSTOP);
    
                set_tsk_thread_flag(p,TIF_SIGPENDING);
    
            }
    
            
    
    // 如果子进程未设置CLONE_STOPPED标志,那么通过wake_up_new_task函数使得父子进程之一优先运行;否则,将子进程的状态设置为TASK_STOPPED。
    
            if (!(clone_flags & CLONE_STOPPED))
    
                wake_up_new_task(p,clone_flags); //将子进程加入红黑树,并判断子进程是否可以抢占父进程, 此时子进程已经处于运行状态
    
            else
    
                p->state = TASK_STOPPED;
    
    
             //如果父进程被跟踪,则将子进程的pid赋值给父进程,存于进程描述符的pstrace_message字段。使得当前进程停止运行,并向父进程的父进程发送SIGCHLD信号。
    
            if (unlikely (trace)) {
    
                current->ptrace_message = pid;
    
                ptrace_notify((trace <<8) |SIGTRAP);
    
            }
    
     
    
     
    
            //如果CLONE_VFORK标志被设置,则通过wait操作将父进程阻塞,直至子进程调用exec函数或者退出。(这儿显示的就是vfork的作用)
    
            if (clone_flags & CLONE_VFORK) {
    
                wait_for_completion(&vfork);
    
                if (unlikely (current->ptrace &PT_TRACE_VFORK_DONE))
    
                    ptrace_notify((PTRACE_EVENT_VFORK_DONE <<8)|SIGTRAP);
    
            }
    
        } else {
    
            free_pidmap(pid);
    
            pid = PTR_ERR(p);
    
        }
    
        return pid; //返回子进程的进程描述符
    
    }
    
     

    vfork()fork()的区别:

    1vfork产生的子进程和父进程完全共享地址空间,包括代码段+数据段+堆栈段。子进程对共享资源进行的修改,也会影响到父进程。

    2vfork函数产生的子进程一定比父进程先运行。即父进程调用了vfork函数后会等待子进程运行后再运行。

     

    4.     进程调度

    首先在sched.c中发现有个注释如下:

    /*
    
     * schedule() is the main scheduler function.
    
     */

      Ok,很显然,我们要重点研究这个函数schedule()

      函数代码如下:

    asmlinkage void __sched schedule(void)
    
    {
    
        structtask_struct*prev,*next;
    
        structprio_array*array;
    
        structlist_head*queue;
    
        unsignedlonglong now;
    
        unsignedlongrun_time;
    
        int cpu, idx, new_prio;
    
        long*switch_count;
    
        structrq*rq;
    
     
    
        if (unlikely(in_atomic() &&!current->exit_state)) {
    
            printk(KERN_ERR "BUG: scheduling while atomic: "
    
                "%s/0x%08x/%d/n",
    
                current->comm, preempt_count(), current->pid);
    
            dump_stack();
    
        }
    
        profile_hit(SCHED_PROFILING,__builtin_return_address(0));
    
     
    
    need_resched:    //禁用内核抢占并初始化一些局部变量
    
        preempt_disable();
    
        prev = current;
    
        release_kernel_lock(prev);
    
    need_resched_nonpreemptible:
    
        rq = this_rq();
    
     
    
        if (unlikely(prev == rq->idle)&& prev->state != TASK_RUNNING) {
    
            printk(KERN_ERR "bad: scheduling from the idle thread!/n");
    
            dump_stack();
    
        }
    
     
    
        schedstat_inc(rq, sched_cnt);
    
        spin_lock_irq(&rq->lock); //关掉本地中断,并获得所要保护的运行队列的自旋锁
    
        now = sched_clock();//调用sched_clock( )函数以读取TSC,并将它的值转换成纳秒,所获得的时间戳存放在局部变量now中
    
        if (likely((longlong)(now- prev->timestamp) < NS_MAX_SLEEP_AVG)) {
    
            run_time = now -prev->timestamp;
    
            if (unlikely((longlong)(now- prev->timestamp) <0))
    
                run_time =0;
    
        } else
    
            run_time = NS_MAX_SLEEP_AVG;
    
     
    
        run_time /= (CURRENT_BONUS(prev) ?:1);
    
     
    
        // prev可能是一个正在被终止的进程。为了确认这个事实,schedule( )检查PF_DEAD标志:
    
        if (unlikely(prev->flags & PF_DEAD))
    
            prev->state = EXIT_DEAD;
    
     
    
    switch_count=&prev->nivcsw;
    
    //检查prev的状态,如果不是可运行状态,而且它没有在内核态被抢占,就应该从运行队列删除prev进程。不过,如果它是非阻塞挂起信号,而且状态为TASK_INTERRUPTIBLE,函数就把该进程的状态设置为TASK_RUNNING,并将它插入运行队列:
    
        if (prev->state &&!(preempt_count()& PREEMPT_ACTIVE)) {
    
            switch_count =&prev->nvcsw;
    
            if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&
    
                   unlikely(signal_pending(prev))))
    
                prev->state = TASK_RUNNING;
    
            else {
    
                if (prev->state == TASK_UNINTERRUPTIBLE)
    
                    rq->nr_uninterruptible++;
    
                deactivate_task(prev, rq);
    
            }
    
        }
    
     
    
        update_cpu_clock(prev, rq, now);
    
     
    
        cpu = smp_processor_id();
    
        if (unlikely(!rq->nr_running)){//如果运行队列中没有可运行的进程存在,函数就调用idle_balance( ),从另外一个运行队列迁移一些可运行进程到本地运行队列中
    
            idle_balance(cpu, rq);
    
            if (!rq->nr_running) {
    
                next = rq->idle;
    
                rq->expired_timestamp =0;
    
                wake_sleeping_dependent(cpu);// 如果idle_balance( ) 没有成功地把进程迁移到本地运行队列中,schedule( )就调用wake_sleeping_dependent( )重新调度空闲CPU(即每个运行swapper进程的CPU)中的可运行进程。
    
                goto switch_tasks;
    
            }
    
        }
    
     
    
        //假设schedule( )函数已经肯定运行队列中有一些可运行的进程,现在它必须检查这些可运行进程中是否至少有一个进程是活动的,如果没有,函数就交换运行队列数据结构的active和expired字段的内容,因此,所有的过期进程变为活动进程,而空集合准备接纳将要过期的进程。
    
        array = rq->active;
    
        if (unlikely(!array->nr_active)){
    
            /*
    
             * Switch the active and expiredarrays.
    
             */
    
            schedstat_inc(rq, sched_switch);
    
            rq->active = rq->expired;
    
            rq->expired = array;
    
            array = rq->active;
    
            rq->expired_timestamp =0;
    
            rq->best_expired_prio = MAX_PRIO;
    
        }
    
     
    
        idx = sched_find_first_bit(array->bitmap);
    
        queue = array->queue + idx;
    
        next = list_entry(queue->next, structtask_struct, run_list);
    
     
    
        if (!rt_task(next)&&interactive_sleep(next->sleep_type)) {
    
            unsignedlonglong delta = now - next->timestamp;
    
            if (unlikely((longlong)(now- next->timestamp) <0))
    
                delta =0;
    
     
    
            if (next->sleep_type == SLEEP_INTERACTIVE)
    
                delta = delta * (ON_RUNQUEUE_WEIGHT *128/100) /128;
    
     
    
            array = next->array;
    
            new_prio = recalc_task_prio(next, next->timestamp + delta);
    
     
    
            if (unlikely(next->prio !=new_prio)) {//如果优先级不相等
    
                dequeue_task(next, array);
    
                next->prio =new_prio;
    
                enqueue_task(next, array);
    
            }
    
        }
    
        next->sleep_type = SLEEP_NORMAL;
    
        if (dependent_sleeper(cpu, rq, next))
    
            next = rq->idle;
    
    switch_tasks:
    
        if (next == rq->idle)
    
            schedstat_inc(rq, sched_goidle);
    
        prefetch(next);//prefetch 宏提示CPU控制单元把next进程描述符的第一部分字段的内容装入硬件高速缓存
    
        prefetch_stack(next);
    
        clear_tsk_need_resched(prev);
    
        rcu_qsctr_inc(task_cpu(prev));
    
     
    
        //减少prev的平均睡眠时间,并把它补充给进程所使用的CPU时间片
    
        prev->sleep_avg -= run_time;
    
        if ((long)prev->sleep_avg <=0)
    
            prev->sleep_avg =0;
    
        prev->timestamp = prev->last_ran= now;
    
     
    
        sched_info_switch(prev, next);
    
        if (likely(prev != next)) {rev 和next很可能是同一个进程:在当前运行队列中没有优先权较高或相等的其他活动进程时,会发生这种情况。在这种情况下,函数不做进程切换
    
            next->timestamp = now;
    
            rq->nr_switches++;
    
            rq->curr =next;
    
            ++*switch_count;
    
     
    
            prepare_task_switch(rq, prev, next);
    
            prev = context_switch(rq, prev, next);
    
            barrier();
    
     
    
            finish_task_switch(this_rq(), prev);
    
        } else
    
            spin_unlock_irq(&rq->lock);
    
     
    
        prev = current;
    
        if (unlikely(reacquire_kernel_lock(prev) <0))
    
            goto need_resched_nonpreemptible;
    
        preempt_enable_no_resched();
    
        if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))
    
            goto need_resched;
    
    }
    
     

    unsignedlongpolicy;

    关于调度策略,是在sched.h定义的三种

    /*
    
     * Scheduling policies
    
     */
    
    #defineSCHED_NORMAL        0
    
    #defineSCHED_FIFO      1
    
    #defineSCHED_RR        2

    SCHED_NORMAL普通进程使用的调度策略,现在此调度策略使用的是CFS调度器。

    SCHED_FIFO实时进程使用的调度策略,此调度策略的进程一旦使用CPU则一直运行,直到有比其更高优先级的实时进程进入队列,或者其自动放弃CPU,适用于时间性要求比较高,但每次运行时间比较短的进程。

    SCHED_RR实时进程使用的时间片轮转法策略,实时进程的时间片用完后,调度器将其放到队列末尾,这样每个实时进程都可以执行一段时间。适用于每次运行时间比较长的实时进程。

    具体方法可以在sched.c中看到——sched_setscheduler()函数将pid所指定进程的调度策略和调度参数分别设置为param指向的sched_param结构中指定的policy和参数。

    sched_param结构中的sched_priority成员的值可以为任何整数,该整数位于policy所指定调度策略的优先级范围内(含边界值)

    5.     其他收获

    首先这次选用vscode作为工具进行阅读源码,相当有效,主要在于可以很容易地Goto Declaretion,尤其源码繁杂调用较多的情况下,解决了函数或者变量找不到定义地方的问题,用起来十分方便。

    但是仍会有多个定义不知道用哪个的情况。这个是LINUX支持的CPU较多造成的。我们可以用GDB静态分析,也可以用objdumpnm等工具来精确定位一些函数和变量,也可以根据宏来一步一步分析,比如是mips的那么我们就进mips看,这样一步一步来。

     

     

     


  • 相关阅读:
    ActiveMQ 5.15.12(2020年3月9日)
    Vert.x WebClient WebClientOptions
    第三方App接入微信登录
    Android Sutdio自带的代码检查工具analyze的使用
    WIN7系统有些文本乱码怎么办
    Visual Studio 打开程序提示仅我的代码怎么办
    WIN10平板 总是提示你需要管理员权限怎么办
    WIN10平板 如何修改网络IP地址为固定
    WIN10平板 如何设置不允许切换竖屏
    WIN10平板 如何关闭自动更新
  • 原文地址:https://www.cnblogs.com/JK-Z/p/12262056.html
Copyright © 2011-2022 走看看