zoukankan      html  css  js  c++  java
  • Linux中的进程调度(四)

    先把我们最初开始的函数写上,前面写的太长,不再看下原始代码可能回不去了。
    /*
     * Share the fairness runtime between parent and child, thus the
     * total amount of pressure for CPU stays equal - new tasks
     * get a chance to run but frequent forkers are not allowed to
     * monopolize the CPU. Note: the parent runqueue is locked,
     * the child is not running yet.
     */
    static void task_new_fair(struct rq *rq, struct task_struct *p)
    {
    	struct cfs_rq *cfs_rq = task_cfs_rq(p);
    	struct sched_entity *se = &p->se, *curr = cfs_rq->curr;
    	int this_cpu = smp_processor_id();
    
    	sched_info_queued(p);
    
    	update_curr(cfs_rq);
    	place_entity(cfs_rq, se, 1);
    
    	/* 'curr' will be NULL if the child belongs to a different group */
    	if (sysctl_sched_child_runs_first && this_cpu == task_cpu(p) &&
    			curr && curr->vruntime < se->vruntime) {
    		/*
    		 * Upon rescheduling, sched_class::put_prev_task() will place
    		 * 'current' within the tree based on its new key value.
    		 */
    		swap(curr->vruntime, se->vruntime);
    	}
    
    	enqueue_task_fair(rq, p, 0);
    	resched_task(rq->curr);
    }
    上次已经分析过update_curr的代码,现在接着往下走,去看place_entity()
    static void
    place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
    {
    	u64 vruntime;
    
    	vruntime = cfs_rq->min_vruntime;
    
    	if (sched_feat(TREE_AVG)) {
    		struct sched_entity *last = __pick_last_entity(cfs_rq);
    		if (last) {
    			vruntime += last->vruntime;
    			vruntime >>= 1;
    		}
    	} else if (sched_feat(APPROX_AVG) && cfs_rq->nr_running)
    		vruntime += sched_vslice(cfs_rq)/2;
    
    	/*
    	 * The 'current' period is already promised to the current tasks,
    	 * however the extra weight of the new task will slow them down a
    	 * little, place the new task so that it fits in the slot that
    	 * stays open at the end.
    	 */
    	if (initial && sched_feat(START_DEBIT))
    		vruntime += sched_vslice_add(cfs_rq, se);
    
    	if (!initial) {
    		/* sleeps upto a single latency don't count. */
    		if (sched_feat(NEW_FAIR_SLEEPERS) && entity_is_task(se))
    			vruntime -= sysctl_sched_latency;
    
    		/* ensure we never gain time by being placed backwards. */
    		vruntime = max_vruntime(se->vruntime, vruntime);
    	}
    
    	se->vruntime = vruntime;
    }
    其中的if else if语句块在默认情况下是都不成立的,不成立的原因很简单,初始化时就这么写的,这里就不啰嗦了。 其中
    if (initial && sched_feat(START_DEBIT))
    在默认情况下还是成立的,所以进去看一下
    static u64 sched_vslice_add(struct cfs_rq *cfs_rq, struct sched_entity *se)
    {
    	return __sched_vslice(cfs_rq->load.weight + se->load.weight,
    			cfs_rq->nr_running + 1);
    }
    注释会对我们有帮助~
    /*
     * We calculate the vruntime slice.
     *
     * vs = s/w = p/rw
     */
    static u64 __sched_vslice(unsigned long rq_weight, unsigned long nr_running)
    {
    	u64 vslice = __sched_period(nr_running);
    
    	vslice *= NICE_0_LOAD;
    	do_div(vslice, rq_weight);
    
    	return vslice;
    }
    再进入到__sched_period中去
    /*
     * The idea is to set a period in which each task runs once.
     *
     * When there are too many tasks (sysctl_sched_nr_latency) we have to stretch
     * this period because otherwise the slices get too small.
     *
     * p = (nr <= nl) ? l : l*nr/nl
     */
    static u64 __sched_period(unsigned long nr_running)
    {
    	u64 period = sysctl_sched_latency;
    	unsigned long nr_latency = sched_nr_latency;
    
    	if (unlikely(nr_running > nr_latency)) {
    		period *= nr_running;
    		do_div(period, nr_latency);
    	}
    
    	return period;
    }
    注释里写的已经很清楚了,如果进程太多的话,一个调度周期就被分的太细,这样纯粹调度花费的时间所占的比重就已经很大,所以要适当把调度周期放大些,放大是根据进程数量按比例来的。 然后根据新调整的调度周期,将新建进程的虚拟运行时间增加一点(个人感觉这也是考虑到如果不增加的话,如果一个进程不断的fork子进程,会造成CPU垄断) 由于在调用place_entity时最后一个参数为1,所以if(!initial)不成立,直接将计算出来的虚拟时间赋给p的虚拟运行时间。 这便是place_entity完成的功能。 主要就是给当前进程分配一个初始的虚拟运行时间,虽然他还没有运行,就像它的名字,place_entity,给新进程找到一个合适的地方,以便接下来将其放入红黑树。 接下来再回到task_+new_fair中去,place_entity执行完毕后进入一个if条件语句,当满足以下条件时该语句块会被执行: 1)系统定义了创建进程后子进程先执行 2)当前进程所在的CPU就是子进程所被分配到的CPU 3)curr不为空(注释中说明是如果父子进程不属于同一个调度组时curr可能为空,这个在后面会研究) 4)curr的虚拟运行时间小于新进程的虚拟运行时间 如果满足这几个条件,会将父子进程的虚拟运行时间交换,为什么要交换呢? 很明显,条件中说,子进程要先运行,而此时子进程的虚拟运行时间又比父进程的运行时间大,调度器每次都会选一个虚拟运行时间最小的进程来运行,所以当然要将他们两个交换了。 这一切准备好之后(主要是记录更新了当前进程的虚拟运行时间,给新创建的子进程增加了适当的虚拟运行时间),便可以将新创建的进程插入红黑树以待调度了。于是在task_new_fair里就进程了enqueue_task函数。
    /*
     * The enqueue_task method is called before nr_running is
     * increased. Here we update the fair scheduling stats and
     * then put the task into the rbtree:
     */
    static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int wakeup)
    {
    	struct cfs_rq *cfs_rq;
    	struct sched_entity *se = &p->se;
    
    	for_each_sched_entity(se) {
    		if (se->on_rq)
    			break;
    		cfs_rq = cfs_rq_of(se);
    		enqueue_entity(cfs_rq, se, wakeup);
    		wakeup = 1;
    	}
    }
    如果在有组调度的情况下,这个函数不仅会将当前子进程加入红黑树,具体我们后面再分析。总之,它的主要动作就是调用enqueue_entity:
    static void
    enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int wakeup)
    {
    	/*
    	 * Update run-time statistics of the 'current'.
    	 */
    	update_curr(cfs_rq);
    
    	if (wakeup) {
    		place_entity(cfs_rq, se, 0);
    		enqueue_sleeper(cfs_rq, se);
    	}
    
    	update_stats_enqueue(cfs_rq, se);
    	check_spread(cfs_rq, se);
    	if (se != cfs_rq->curr)
    		__enqueue_entity(cfs_rq, se);
    	account_entity_enqueue(cfs_rq, se);
    }
    在做了一些必要的更新信息后,主要动作是调用__enqueue_entity
    /*
     * Enqueue an entity into the rb-tree:
     */
    static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
    {
    	struct rb_node **link = &cfs_rq->tasks_timeline.rb_node;
    	struct rb_node *parent = NULL;
    	struct sched_entity *entry;
    	s64 key = entity_key(cfs_rq, se);
    	int leftmost = 1;
    
    	/*
    	 * Find the right place in the rbtree:
    	 */
    	while (*link) {
    		parent = *link;
    		entry = rb_entry(parent, struct sched_entity, run_node);
    		/*
    		 * We dont care about collisions. Nodes with
    		 * the same key stay together.
    		 */
    		if (key < entity_key(cfs_rq, entry)) {
    			link = &parent->rb_left;
    		} else {
    			link = &parent->rb_right;
    			leftmost = 0;
    		}
    	}
    
    	/*
    	 * Maintain a cache of leftmost tree entries (it is frequently
    	 * used):
    	 */
    	if (leftmost)
    		cfs_rq->rb_leftmost = &se->run_node;
    
    	rb_link_node(&se->run_node, parent, link);
    	rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
    }
    这个函数很容易读懂,先是找到要插入的位置,然后将其插入,(如果插入位置是最左边的叶子结点,那么需要更新缓冲),然后链入结点,并修改结点颜色以保证红黑树的性质,以防子树高度过于不平衡。具体算法可参见算法导论,很容易懂。不过在红黑树的结点表示上,这里用到了一个技巧: 把所有结点的地址按4字节对齐,这样,每个节点的首地址最后两个比特位肯定为零,由于红黑树的颜色状态只有两个,要么红要么黑,所以编码者就利用了最低位来表示当前节点的颜色。这种用内存的方式~~ 将进程插入红黑树后,就可以通过内核在系统调用返回用户空间时进行一次调度了。
    	enqueue_task_fair(rq, p, 0);
    	resched_task(rq->curr);
    进入resched_task中去:
    /*
     * resched_task - mark a task 'to be rescheduled now'.
     *
     * On UP this means the setting of the need_resched flag, on SMP it
     * might also involve a cross-CPU call to trigger the scheduler on
     * the target CPU.
     */
    #ifdef CONFIG_SMP
    
    #ifndef tsk_is_polling
    #define tsk_is_polling(t) test_tsk_thread_flag(t, TIF_POLLING_NRFLAG)
    #endif
    
    static void resched_task(struct task_struct *p)
    {
    	int cpu;
    
    	assert_spin_locked(&task_rq(p)->lock);
    
    	if (unlikely(test_tsk_thread_flag(p, TIF_NEED_RESCHED)))
    		return;
    
    	set_tsk_thread_flag(p, TIF_NEED_RESCHED);
    
    	cpu = task_cpu(p);
    	if (cpu == smp_processor_id())
    		return;
    
    	/* NEED_RESCHED must be visible before we test polling */
    	smp_mb();
    	if (!tsk_is_polling(p))
    		smp_send_reschedule(cpu);
    }
    先加锁.
    if (unlikely(test_tsk_thread_flag(p, TIF_NEED_RESCHED)))
    		return;
    这一句很有意思,如果当前进程的TIF_NEED_RESCHED标志已经置位,那么便可以直接返回了,test_tsk_thread_flag是怎么工作的呢?一步步往下深入:
     static inline int test_tsk_thread_flag(struct task_struct *tsk, int flag)
     {
         return test_ti_thread_flag(task_thread_info(tsk), flag);
     }
      static inline int test_ti_thread_flag(struct thread_info *ti, int flag)
      {
          return test_bit(flag,&ti->flags);
      }
     #define test_bit(nr,addr) 
     (__builtin_constant_p(nr) ? 
      constant_test_bit((nr),(addr)) : 
      variable_test_bit((nr),(addr)))
    当前,flag标志应该是一个变量,(谁说的C语言里面没有多态?),接着往里面走:
     static inline int variable_test_bit(int nr, const volatile unsigned long * addr)
     {
         int oldbit;
    
         __asm__ __volatile__(
             "btl %2,%1ntsbbl %0,%0"
             :"=r" (oldbit)
             :"m" (ADDR),"Ir" (nr));
         return oldbit;
     }
    汇编语句。 btl的功能是测试某个数的特定位是零还是1,测试结果放到CF标志位里。(具体可见intel的用户手册).然后,sbbl %0, %0,也就是说,让oldbit与oldbit的值带位相减,即oldbit-oldbit-CF,如果CF标志位是零,也就是刚才位测试的结果是零的话,当然最后返回的oldbit也是零了,如果CF标志位是1,那么返回的就不一样了。返回的值为-1,多么巧妙。 如果经测试,TIF_NEED_RESCHED还没有置位,那么就将其置位。  
          set_tsk_thread_flag(p, TIF_NEED_RESCHED);
     
     static inline void set_tsk_thread_flag(struct task_struct *tsk, int flag)
     {
         set_ti_thread_flag(task_thread_info(tsk), flag);
     }
     
      static inline void set_ti_thread_flag(struct thread_info *ti, int flag)
      {
          set_bit(flag,&ti->flags);
      }
    这里所有的涉及平台相关的汇编指令全是以Intel x86为例
      static inline void set_bit(int nr, volatile unsigned long * addr)
      {
          __asm__ __volatile__( LOCK_PREFIX
              "btsl %1,%0"
              :"+m" (ADDR)
              :"Ir" (nr));
      }
    btsl就不说了,这个指令的功能就是置位,而且是原子的。 置好位后,resched_task的任务只剩下对多处理器存在的情况下任务的处理了,先留着,把整个调度过程走一遍再回过头来分析。  
  • 相关阅读:
    lightoj-1047
    lightoj-1044
    lightoj-1045
    lightoj-1082
    LeetCode偶尔一题 —— 19. 删除链表的倒数第N个节点
    Python 3.52官方文档翻译 http://usyiyi.cn/translate/python_352/library/index.html 必看!
    Python3 time模块
    JavaScript CSS 等前端推荐
    Python之 七级字典查询
    将Sublime Text 3设置为Python全栈开发环境(转一个链接)
  • 原文地址:https://www.cnblogs.com/yangce/p/2910093.html
Copyright © 2011-2022 走看看