`
javasee
  • 浏览: 961384 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

深入Linux内核架构第二章学习笔记

阅读更多

深入linux内核架构是本根据Linux源代码讲述内核的书,比深入理解linux内核更加贴近代码,讲的更深入浅出一些。

本书第二章主要讲述Linux进程的调度,linux的进程都可以描述为task_struct的结构,这个结构包含进程运行的所有信息。

task_struct的有一个字段是

volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */

来跟踪线程的运行状态,这个volatile 关键字很重要,它告诉编译器定义的变量随时都有可能改变,程序每次需要存储或读取这个变量的时候,直接从变量地址中读取数据。如果没有 volatile关键字,则编译器可能优化读取和存储,可能使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

task_struct另外一个重要的字段是类型为pid_tpid,它默认的最大值是32768。和windows 类似的是linux的线程id和进程id肯定不同,从内核来看,一个用户层创建的线程就是一个普通进程,从源码来看他们的实现非常类似,都调用了do_fork 函数,这是最后的两个参数不同。IA32 linux fork内核实现,

arch/x86/kernel/process_32.c

asmlinkage int sys_fork(struct pt_regs regs)

{

return do_fork(SIGCHLD, regs.esp, &regs, 0, NULL, NULL);

}

线程的实现

arch/x86/kernel/process_32.c

asmlinkage int sys_clone(struct pt_regs regs)

{

unsigned long clone_flags;

unsigned long newsp;

int __user *parent_tidptr, *child_tidptr;

clone_flags = regs.ebx;

newsp = regs.ecx;

parent_tidptr = (int __user *)regs.edx;

child_tidptr = (int __user *)regs.edi;

if (!newsp)

newsp = regs.esp;

return do_fork(clone_flags, newsp, &regs, 0, parent_tidptr, child_tidptr);

}

(parent_tidptrchild_tidptr是指向用户空间的指针,对应linux的线程实现,目前通用的linux实现有LinuxThreadsNPTL(Native Posix Thread Library)

NPTL的支持是在glibc中实现的,与内核无关。

大家输入 getconf GNU_LIBPTHREAD_VERSION 便可以看到本机NPTL的版本。

如果是早期的glibc,使用以下命令查看

$(ldd /bin/ls | grep libc.so | awk '{print $3}') | grep 'Threads|threads'

Linux 2.4 早期版本中使用linuxpthreads,那么在多线程程序中,会产生一个用户线程来维护当前的线程,例如在main()中创建了2个线程,那么用 ps axm 查看进程表,程序应有4个线程。使用NPTL后大家就会看到一个进程在运行,通过/proc/pid/status产看当前进程的线程数。

由于每个task_struct都嵌入了一个schedule entity的实例,因此进程是可以调度的。

内核版本2.6.24之后的kernelrunqueue中以新增的cfs_rq红黑树结构存放schedule entity(SE),记录每个task执行相关的时间数据。Kernelrunqueue新增task的時候以SE中的vruntime作为比较依据,和红黑树各SEvruntime相比,vruntime比较小的SE往左靠拢,反之大vruntime大的SE向右靠拢,直到这个SE成为最左边或最右边的节点为止。SE中的vruntime的类型为u64

当新的task创建出来之后task_new_fair函数通过place_entity取得新taskvruntime

static void

place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)

{

u64 vruntime = cfs_rq->min_vruntime;

/*

* 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(cfs_rq, se);

/* sleeps up to a single latency don't count. */

if (!initial && sched_feat(FAIR_SLEEPERS)) {

unsigned long thresh = sysctl_sched_latency;

/*

* Convert the sleeper threshold into virtual time.

* SCHED_IDLE is a special sub-class. We care about

* fairness only relative to other SCHED_IDLE tasks,

* all of which have the same weight.

*/

if (sched_feat(NORMALIZED_SLEEPER) && (!entity_is_task(se) ||

task_of(se)->policy != SCHED_IDLE))

thresh = calc_delta_fair(thresh, se);

/*

* Halve their sleep time's effect, to allow

* for a gentler effect of sleepers:

*/

if (sched_feat(GENTLE_FAIR_SLEEPERS))

thresh >>= 1;

vruntime -= thresh;

}

/* ensure we never gain time by being placed backwards. */

vruntime = max_vruntime(se->vruntime, vruntime);

se->vruntime = vruntime;

}

cfs_rq->min_vruntime就是整个红黑树最左边schedule entityvruntime,在kernel调用__update_curr时更新,整个红黑树中所有的schedule entityvruntime会随着时间而增加。

取得新taskvruntime之后task_new_fair函数间接调用enqueue_task_fair函数调用__enqueue_entity函式把新taskschedule entity放入cfs_rqvruntime__enqueue_entity函式中被用来決定新task该放在cfs_rq红黑树中的那个位置。

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);

}

时钟中断通过update_process_times调用scheduler_tick,在2.6.24以前的版本中 scheduler_tick通过减少当前tasktime slice直到为0,或者到某个值(这个值可以通过TIMESLICE_GRANULARITY得到)。但是在2.6.24以后不再使用time slice

void scheduler_tick(void)

{

int cpu = smp_processor_id();

struct rq *rq = cpu_rq(cpu);

struct task_struct *curr = rq->curr;

u64 next_tick = rq->tick_timestamp + TICK_NSEC;

spin_lock(&rq->lock);

__update_rq_clock(rq);

/*

* Let rq->clock advance by at least TICK_NSEC:

*/

if (unlikely(rq->clock < next_tick))

rq->clock = next_tick;

rq->tick_timestamp = rq->clock;

update_cpu_load(rq);

if (curr != rq->idle) /* FIXME: needed? */

curr->sched_class->task_tick(rq, curr);

spin_unlock(&rq->lock);

#ifdef CONFIG_SMP

rq->idle_at_tick = idle_cpu(cpu);

trigger_load_balance(rq, cpu);

#endif

}

scheduler_tick调用task_tick函数,task_tick的实现方式取决于底层的实现。

SEloadload_weight的结构体,struct load_weight {

unsigned long weight, inv_weight;

};

weightinv_weight分別来自prio_to_weightprio_to_wmult两个数组,如果taskreal-time taskidle taskkernel直接把weightinv_weight设为固定值,调用函数为set_load_weight

static void set_load_weight(struct task_struct *p)

{

if (task_has_rt_policy(p)) {

p->se.load.weight = prio_to_weight[0] * 2;

p->se.load.inv_weight = prio_to_wmult[0] >> 1;

return;

}

/*

* SCHED_IDLE tasks get minimal weight:

*/

if (p->policy == SCHED_IDLE) {

p->se.load.weight = WEIGHT_IDLEPRIO;

p->se.load.inv_weight = WMULT_IDLEPRIO;

return;

}

p->se.load.weight = prio_to_weight[p->static_prio - MAX_RT_PRIO];

p->se.load.inv_weight = prio_to_wmult[p->static_prio - MAX_RT_PRIO];

}

task是普通task,它的load取决于static prioritystatic priority在创建task时就创建好了,在100140之间。

static const int prio_to_weight[40] = {

/* -20 */ 88761, 71755, 56483, 46273, 36291,

/* -15 */ 29154, 23254, 18705, 14949, 11916,

/* -10 */ 9548, 7620, 6100, 4904, 3906,

/* -5 */ 3121, 2501, 1991, 1586, 1277,

/* 0 */ 1024, 820, 655, 526, 423,

/* 5 */ 335, 272, 215, 172, 137,

/* 10 */ 110, 87, 70, 56, 45,

/* 15 */ 36, 29, 23, 18, 15,

};

static const u32 prio_to_wmult[40] = {

/* -20 */ 48388, 59856, 76040, 92818, 118348,

/* -15 */ 147320, 184698, 229616, 287308, 360437,

/* -10 */ 449829, 563644, 704093, 875809, 1099582,

/* -5 */ 1376151, 1717300, 2157191, 2708050, 3363326,

/* 0 */ 4194304, 5237765, 6557202, 8165337, 10153587,

/* 5 */ 12820798, 15790321, 19976592, 24970740, 31350126,

/* 10 */ 39045157, 49367440, 61356676, 76695844, 95443717,

/* 15 */ 119304647, 148102320, 186737708, 238609294, 286331153,

};

sched_slice根据runqueuetask的个数决定每个task的合理执行时间,当current task使用CPU超出合理执行时间时,kernelcurrent task置为重新调度。

static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)

{

u64 slice = __sched_period(cfs_rq->nr_running);

slice *= se->load.weight;

do_div(slice, cfs_rq->load.weight);

return slice;

}

kernel判断每个task执行时间的取决于taskstatic prioritycfs_rq->nr_running(runqueuetask数量)。当runqueuetask小于等于5时,taskpriority和执行时间由taskstatic priority决定,当runqueue中的task5時,則runqueue中的task也成为影响因素之一。

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;

}

分享到:
评论

相关推荐

    Professional Linux Kernel Architecture, 精通Linux内核架构

    - **书籍介绍**:本书《Professional Linux Kernel Architecture》是一本深入剖析Linux内核架构的专业书籍,由Wolfgang Mauerer撰写,并于2008年由Wiley Publishing Inc.出版。全书共有1368页,涵盖了Linux内核的...

    鸟哥的私房菜Linux学习笔记

    #### 第二章 Linux是什么 **1. Linux历史** - **起源**: 1991年,Linus Torvalds基于GNU工具和Minix设计灵感开发出Linux内核。 - **重要事件**: - 1969年: Ken Thompson开发Unix原型。 - 1973年: C语言用于重写...

    Professional Linux Kernel Architecture

    《Professional Linux Kernel Architecture》是一本全面深入地介绍Linux内核架构的专业书籍,由Wolfgang Mauerer编写,Wiley Publishing, Inc.出版。本书针对的是对Linux内核感兴趣的高级用户和开发者,不仅介绍了...

    linux驱动开发详解(1-23章)

    第二章至第四章可能会详细讲解设备模型,包括总线、设备、驱动的抽象表示,以及如何使用udev管理设备节点。这有助于理解设备在内核中的表示方式和动态加载驱动的机制。 第五章至第八章,作者会深入讲解字符设备驱动...

    openwrt学习资料合集.rar

    `Lua程序设计(第二版).pdf`是一本关于Lua编程语言的书籍,Lua在OpenWrt中常用于编写脚本和配置。通过学习Lua,开发者可以更高效地扩展OpenWrt的功能,实现自动化任务和自定义服务。 4. **从零开始学习OpenWrt** `...

    第11章 Linux操作系统基础-教程与笔记习题

    二、Linux内核 Linux内核是系统的核心,负责硬件资源的管理,包括进程调度、内存管理、文件系统、网络协议栈等。内核通过系统调用来提供服务,允许用户空间的应用程序与硬件交互。 三、Shell与命令行 Shell是Linux...

    ARM9嵌入式系统设计基础教程 电子课件_第11章 Linux操作系统基础-教程与笔记习题

    本章的电子课件将通过实例和练习题帮助学习者深入理解这些概念,使他们具备在ARM9平台上开发和维护Linux嵌入式系统的实际能力。通过学习,你可以从零开始构建一个完整的嵌入式Linux系统,并对其中的每一个环节有清晰...

    第1章 嵌入式系统基础知识-教程与笔记习题

    嵌入式系统是现代科技发展中的重要组成部分,广泛应用于各个领域,如智能家居、汽车电子、医疗设备、工业自动化等。...通过学习本章内容,可以对嵌入式系统有一个全面的认识,并为后续的深入学习和实践打下坚实的基础。

    自己动手写操作系统(含源代码).part2

    在第二版中,你将会看到,你已经可以通过交叉编译的方式为我们的实验性 OS编写应用程序了,也就是说,它已经具备操作系统的基本功能,虽然仍然极其简陋,但第一个圈,毕竟是已经圆起来了。第三,实践类的操作系统...

    自己动手写操作系统(含源代码).part1

    在第二版中,你将会看到,你已经可以通过交叉编译的方式为我们的实验性 OS编写应用程序了,也就是说,它已经具备操作系统的基本功能,虽然仍然极其简陋,但第一个圈,毕竟是已经圆起来了。第三,实践类的操作系统...

Global site tag (gtag.js) - Google Analytics