调度程序所用数据结构—Linux

在之前的文章中对于Linux进程调度部分做了较浅显的分析,在这一篇中将基于Linux内核源码中进程调度的部分来探讨调度程序中所使用的数据结构。

数据结构runqueue

数据结构runqueue是Linux2.6调度程序最重要的数据结构。系统中的每个CPU都有它自己的运行队列,所有的runqueue存放在runqueue每CPU变量中。
每CPU变量 是一种内核使用的同步技术,在CPU之间复制数据结构。最简单也是最重要的同步技术就是将内核变量声明为每CPU变量(per-cpu variable)。每CPU变量主要是数据结构的数组,系统的每个CPU对应数组的一个元素。(最好的同步技术是把设计不需要同步的内核放在首位)

宏this_rq()产生本地CPU运行队列的地址,而宏cpu_rq(n)产生索引为n的CPU的运行队列的地址。

struct runqueue {
	spinlock_t lock;	//保护进程链表的自旋锁
	unsigned long nr_running, 	//运行队列链表中可运行进程的数量
				  nr_switches, 		//CPU执行进程切换的次数
				  expired_timestamp,	//过期队列中最老的进程被插入队列的时间
			      nr_uninterruptible;		//先前在运行队列链表中而现在睡眠在TASK_UNINTERRUPTIBLE状态的进程的数量(对所有运行队列来说,只有这些字段的总数才是有意义的)
	task_t *curr, 	//当前正在运行进程的进程描述符指针(对本地CPU,它与current相同)
		   *idle;	//当前CPU(this CPU)上swapper进程的进程描述符指针
	struct mm_struct *prev_mm;	//在进程切换期间用来存放被替换进程的内存描述符的地址
	prio_array_t *active, 	//指向活动进程链表的指针
				 *expired, 	//指向过期进程链表的指针
				  arrays[2];	//活动进程和过期进程的两个集合
	int prev_cpu_load[NR_CPUS];
#ifdef CONFIG_NUMA
	atomic_t *node_nr_running;
	int prev_node_load[MAX_NUMNODES];
#endif
	task_t *migration_thread;	//迁移内核线程的进程描述符指针
	struct list_head migration_queue;	//从运行队列中被删除的进程的链表

	atomic_t nr_iowait;		//先前在运行队列的链表中,而现在正在等待磁盘I/O操作结束的进程的数量
};

自旋锁: 是为实现保护共享资源而提出一种锁机制。
自旋锁与互斥锁的区别:
相同点:自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。
不同点:但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

runqueue数据结构中最重要的字段是与可运行进程的链表相关的字段。系统中每个可运行的进程属于且只属于一个运行队列。只要可运行进程保持在同一个运行队列中,它就只可能在拥有该运行队列的CPU上执行。但是,可运行进程却会从一个运行队列迁移到另一个运行队列。

运行队列的arrays字段是一个包含两个 prio_array_t 结构的数组。每个数据结构都表示一个可运行进程的集合,并包含140个双向链表头(每个链表对应一个可能的进程优先级)、一个优先级位图和一个集合中所包含的进程数量的计数器。

struct prio_array {
	int nr_active;	//链表中进程描述符的数量
	unsigned long bitmap[BITMAP_SIZE];	//优先权位图:当且仅当某个优先权的进程链表不为空时设置相应的位标志
	struct list_head queue[MAX_PRIO];	//140个优先权队列的头结点
};

runqueue结构的active字段指向arrays中的两个prio_array_t数据结构之一:对应于包含活动进程的可运行进程的集合。
相反,expired字段指向数组中的另一个prio_array_t数据结构:对应于包含过期进程的可运行进程的集合。

arrays中两个数据结构的作用会发生周期性的变化:活动进程突然变成过期进程,而过期进程变为活动进程,程序调度简单地交换运行队列的active和expired字段的内容以完成这种变化。

进程描述符

每个进程描述符都包括几个与调度相关的字段。

当新进程被创建的时候,由copy_process()调用的函数sched_fork()用下述方法设置current进程(父进程)和p进程(子进程)的time_slice字段:

   ......
   p->time_slice = (current->time_slice + 1) >> 1;
	/*
	 * The remainder of the first timeslice might be recovered by
	 * the parent if the child exits early enough.
	 */
	p->first_time_slice = 1;
	current->time_slice >>= 1;
   ......

换言之,父进程剩余的节拍数被划分成两等份:一份给父进程,另一份给子进程。
这样做是因为一个进程不能通过创建多个后代来霸占资源(除非它有给自己实时策略的特权)。

若父进程的时间片只剩下一个时钟节拍,则划分操作强行把current->time_slice置为0,从而耗尽父进程的时间片。这种情况下,copy_process把current->time_slice重新置为1,然后调用scheduler_tick()递减该字段。
在这里插入图片描述
函数copy_process()也初始化子进程描述符中与进程调度相关的几个字段:

	p->first_time_slice = 1;
	//current->time_slice >>= 1;
	p->timestamp = sched_clock();

因为子程序未用完它的时间片(如果一个进程在它的第一个时间片内终止或执行新的程序,则把子进程的剩余时间奖励给父进程),所以first_time_slice标志被置为1。用函数sched_clock()所产生的时间戳的值初始化timestamp字段。

关于Linux内核的内容会告一段落,以上只是将去年写好但是仍在草稿箱内的文章发布出来罢了。接下来会进行一些与流媒体服务器相关的学习,当然还有日常的温故知新(指算法)。希望现在的带有目的性的“被动学习”可以为以后仅仅凭借着兴趣喜好的“主动学习”鉴定基础顺而化作推力。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值