logo资料库

操作系统课设之-linux.docx

第1页 / 共28页
第2页 / 共28页
第3页 / 共28页
第4页 / 共28页
第5页 / 共28页
第6页 / 共28页
第7页 / 共28页
第8页 / 共28页
资料共28页,剩余部分请下载后查看
2.1什么是Linux
2.3 Intel 8254 PIT芯片简介
2.4 时间系统一些基本概念
2.5 动态定时器
2.6 时钟中断
我们组此次课设的选取的题目是:阅读 Linux 内核代码,任务 4: 时钟中断与进程调度。 一.导言 其中含 3 个部分: (1)kernel/sched.c (2)arch/i386/kernel/time.c (3)kernel/timer.c 在课设期间,我们参考了有关 linux 内核代码的书籍,以及在网上查阅了很多相关的资料进 行阅读分析初步以有助于我们对程序的理解。我们用的阅读软件是:Source Insight3.5。 二.相关知识简介 2.1什么是Linux Linux是一类Unix计算机操作系统的统称。Linux操作系统的内核的名字也是“Linux”。 Linux操作系统也是自由软件和开放源代码发展中最著名的例子。严格来讲,Linux这个词本 身只表示Linux内核,但在实际上人们已经习惯了用Linux来形容整个基于Linux内核,并且 使用GNU 工程各种工具和数据库的操作系统。 2.2 什么是进程调度 进程:计算机中进程分为两种,一为普通进程,它的优先级用进程控制块中的counter表 示,二为实时进程,实时进程的优先级用rt_priority表示,它在进程控制块中有定义,当就绪 队列中同时存在两种进程的时候,实时进程总是先于普通进程运行,它的实现机制是实时进 程的权值以1000做为基础值,也就是说实时进程的权值是1000加上它的进程优先级,而普通 进程的优先级就是它的进程优先级. 2.3 Intel 8254 PIT芯片简介 Intel 8254 PIT有3个计时通道,每个通道都有其不同的用途: (1) 通道0用来负责更新系统时钟。每当一个时钟滴答过去时,它就会通过IRQ0向系统产 生一次时钟中断。 (2) 通道1通常用于控制DMAC对RAM的刷新。 (3) 通道2被连接到PC机的扬声器,以产生方波信号。 1
每个通道都有一个向下减小的计数器,8254 PIT的输入时钟信号的频率是1193181HZ, 也即一秒钟输入1193181个clock-cycle。每输入一个clock-cycle其时间通道的计数器就向 下减1,一直减到0值。因此对于通道0而言,当他的计数器减到0时,PIT就向系统产生一次 时钟中断,表示一个时钟滴答已经过去了。当各通道的计数器减到0时,我们就说该通道处 于“Terminal count”状态。 2.4 时间系统一些基本概念 1、实时时钟(RTC时钟) 也叫CMOS时钟,是PC主板上的一块芯片(或叫做时钟电路),靠电池供电,即使系统断电, 也可以维持日期和时间。独立于操作系统,也被称为硬件电路,为整个计算机提供一个计时 标准,是最原始最底层的时钟数据。Linux只用RTC来获取时间和日期,也允许进程对RTC编 程。内核通过访问I/O端口存取RTC,同时,系统管理员可以配置时钟。 2、操作系统时钟(软时钟、系统时钟) 产生于PC主板上的定时/计数芯片,由操作系统控制这个芯片的工作,基本单位是该芯 片的计数周期。开机时操作系统去的RTC中的时间数据来初始化OS时钟,通过计数芯片的向 下计数形成OS时钟。 3、时钟运作机制 RTC是OS时钟的基准,操作系统通过读取RTC来初始化OS时钟,此后二者保持同步运行, 每隔一个固定时间会刷新或校正RTC中的信息,共同维持着系统时间。 4、时间基准:1970-01-01 00:00:00 5、时间系统 用全局变量jiffies表示系统自启动以来的时钟滴答数目。 2.5 动态定时器 动态定时器是指内核的定时器队列是可以动态变化的,内核函数init_timer()用来初始 化一个定时器。 假定一个定时器要经过interval个时钟滴答后才到期(interval=expires-jiffies), 则Linux采用了下列思想来实现其动态内核定时器机制:对于那些0≤interval≤255的定时 器,Linux严格按照定时器向量的基本语义来组织这些定时器,也即Linux内核最关心那些在 接下来的255个时钟节拍内就要到期的定时器,因此将它们按照各自不同的expires值组织成 256个定时器向量。而对于那些256≤interval≤0xffffffff的定时器,由于他们离到期还有 一段时间,因此内核并不关心他们,而是将它们以一种扩展的定时器向量语义 2.6 时钟中断 可编程定时器/计数器产生输出脉冲,送入CPU,引发一个中断请求信号,我们称之为 时钟中断。每产生时钟中断,时钟中断处理程序要完成的工作,处理与时间相关的所有信息 (系统时间、进程的时间片、延时、使用CPU的时间、各种定时器),检查是否需要调度以 及处理bottom-half,在创建进程之前,必须保证已初始化好时钟中断处理程序。 三.代码分析之逐步深入 3.1 代码之中的一些变量及函数的语义分析 3.1.1 变量 2
kernel/sched.c 1 counter是进程的动态优先级,它定义了一个在就绪队列的进程当它得到CPU后可运行的 时间,用静态优先级初始化,当然计算机是以时钟中断做为时间的计数器,每发送一个时钟中 断,动态优先级上的时间片就减少一个时钟中断的时间,时间片减到0的时候就退出该进程而 调度另一个进程获得CPU。 2 nice是进程的静态优先级,当一个在就绪队列中的进程获得CPU之后,它被赋予此进程可 占有CPU的时间.这个时间被称为时间片. 3 mm_struct *active_active 内核线程用来指向调用它的普通进程的内存地址空间.当普 通进程在运行时如果发生系统调用,程序就会从用户态转为内核态,内核态中执行的是内核 线程,内核线程没有内存空间地址结构mm_struct,当他需要内存空间地址的时候就会调用用 户态对应进程的用以空间地址结构mm_struct.内核线程就是通过active_mm指针来指向用户 态进程的mm_struct结构 pid是进程标志符,操作系统每创建一个新的进程就要为这个新进程分配 5 pid_t pid 一个进程控制快(PCB),那么系统内核是怎样区分这些进程的呢?就是通过进程标志符pid,系 统在为新的进程分配进程控制块的候,它不是自己去创建,而是直接从上一个进程中复制它 的进程控制块,其中里面的大部分东西保留下来,只做少量的改动,然后它的进程标志符加1 赋值给新的进程控制块. 6 SMP(Symmetrical Multi-Processing)平时所说的双CPU系统,实际上是对称多处理机系 统中最常见的一种,通常称为2路对称多处理。 arch/i386/kernel/time.c kernel/timer.c (1):8253/8254 PIT的本质就是对由晶体振荡器产生的时钟周期进行计数, (2)时钟滴答(clock tick):当PIT通道0的计数器减到0值时,它就在IRQ0上产生一次时钟 中断,也即一次时钟滴答。PIT通道0的计数器的初始值决定了要过多少时钟周期才产生一次 时钟中断,因此也就决定了一次时钟滴答的时间间隔长度。 (3) 时钟滴答的频率(HZ):1秒时间内PIT所产生的时钟滴答次数,i386平台HZ的值是100。 根据HZ的值,可以知道一次时钟滴答的具体时间间隔应该是(1000ms/HZ)=10ms。 (4) 时钟滴答的时间间隔:用全局变量 tick 来表示时钟滴答的时间间隔长度,定义在 kernel/timer.c 文件中,long tick = (1000000 + HZ/2) / HZ;单位微妙(μs),在不同 平台上宏 HZ 的值会有所不同,方程式 tick=1000000÷HZ 的结果可能会是个小数,因此将 其进行四舍五入成一个整数,所以 Linux 将 tick 定义成(1000000+HZ/2)/HZ,其中被 除数表达式中的 HZ/2 的作用就是用来将 tick 值向上圆整成一个整型数。另外,Linux 还 3
用 宏 TICK_SIZE 来 作 为 tick 变 量 的 引 用 别 名 ( alias ) , 其 定 义 如 下 ( arch / i386/kernel/time.c): #define TICK_SIZE tick (5) 宏 LATCH:Linux 用宏 LATCH 来定义要写到 PIT 通道 0 的计数器中的值,它表示 PIT 将 每隔多少个时钟周期产生一次时钟中断。显然 LATCH 应该由下列公式计算: LATCH=(1秒之内的时钟周期个数)÷(1秒之内的时钟中断次数)=(CLOCK_TICK_RATE) ÷ ( HZ ) 。 Linux 将 LATCH 定 义 为 ( include/linux/timex.h ) : #define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ) (6) 全局变量jiffies:这是一个32位的无符号整数,用来表示自内核上一次启动以来的时 钟滴答次数。每发生一次时钟滴答,内核的时钟中断处理函数timer_interrupt()都要将 该全局变量jiffies加1 (7) 全局变量xtime:它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准 1970-01-01 00:00:00的相对秒数值。结构timeval是Linux内核表示时间的一种格式 (Linux内核对时间的表示有多种格式,每种格式都有不同的时间精度),其时间精度是微 秒。该结构是内核表示时间时最常用的一种格式,如下所示: /从1970年1月1号开始计算的秒数 struct timeval { time_t tv_sec; suseconds_t tv_usec; //微秒:百万分之一秒,当前秒内的微秒数 }; 其中,成员tv_sec表示当前时间距UNIX时间基准的秒数值,而成员tv_usec则表示一秒 之内的微秒值,且1000000>tv_usec>=0。 (8) 全局变量sys_tz:它是一个timezone结构类型的全局变量,表示系统当前的时区信息。 结构类型timezone定义在include/linux/time.h头文件中,如下所示: struct timezone { int tz_minuteswest; int tz_dsttime; }; 基于上述结构,Linux在kernel/time.c文件中定义了全局变量sys_tz表示系统当前所处 //格林尼治时间往西方的时差 /*时间修正方式*/ 的时区信息,如下所示: struct timezone sys_tz; (9)timer.h中内核定时器的定义 struct timer_list{ struct list_headlist; unsigned long expires; unsigned long data; void (*function)(unsignedlong);/*定时器到期时执行的函数*/ }; /*动态定时器链表*/ /*定时器到期的时间*/ /*function的参数*/ (10)双向链表元素list:用来将多个定时器连接成一条双向循环队列。 (11) expires:指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计 4
数(也即时钟节拍数)。当一个定时器的expires值小于或等于jiffies变量时,我们就说这 个定时器已经超时或到期了。在初始化一个定时器后,通常把它的 expires 域设置成当前 expires 变量的当前值加上某个时间间隔值(以时钟滴答次数计)。 (12) 函数指针 function:指向一个可执行函数。当定时器到期时,内核就执行 function 所指定的函数。 而 d a 域则被内核用作 function 函数的调用参数。 (13) 全局变量wall_jiffies:Linux内核定义了一个类似于jiffies的全局变量 wall_jiffies,来保存内核上一次更新xtime时的jiffies值。时钟中断的底半部分每一次更 新xtime的时侯都会将wall_jiffies更新为当时的jiffies值。全局变量wall_jiffies定义在 kernel/timer.c文件中: unsigned long wall_jiffies; (14) unsigned long fast_gettimeoffset_quotient; 全局变量,无符号长整型,与可编程定时器 PIT 比,用 TSC 寄存器可获得更精确的时间 度量。但使用 TSC 前,它必须精确地确定 1 个 TSC 计数值到底代表多长的时间间隔,即到底 要 过 多 长 时 间 间 隔 TSC 寄 存 器 才 会 加 1 。 Linux 内 核 用 全 局 变 量 fast_gettimeoffset_quotient 来 表 示 这 个 值 . 这 个 变 量 的 值 是 通 过 下 述 公 式 来 计 算:fast_gettimeoffset_quotient = (2^32) / (每微秒内的时钟周期个数) (15) unsigned long cpu_khz: 全局变量,无符号长整型,在校准 TSC 时,检测到的 CPU 频率。 (16) static int delay_at_last_interrupt: 静 态 全 局 变 量 , 整 型 , 从 产 生 时 钟 中 断 的 那 个 时 刻 到 内 核 时 钟 中 断 服 务 函 数 timer_interrupt 真正在 CPU 上执行的那个时刻之间的时间延迟间隔。 (17) static unsigned long last_tsc_low: 静态全局变量,无符号长整型,表示中断服务 timer_interrupt 真正在 CPU 上执行时刻 的 TSC 寄存器值的低 32 位(LSB),即表示上一次时钟中断服务函数 timer_interrupt()执 行时刻的 CPU TSC 寄存器的值。 (18) static long last_rtc_update: 静态全局变量,长整型,表示内核最近一次成功地对 RTC 进行更新的时间(单位是秒数), 每一次成功地调用 set_rtc_mmss()函数后,内核都会马上将 last_rtc_update 更新为当前 时间。 (19) static struct irqaction irq0: 静态全局变量,时钟中断请求的中断服务描述符,结构 irq0 中的 next 指针被设置为 NULL,IRQ0 所对应的中断服务队列中只有 irq0 这唯一的一个元素,且 IRQ0 不允许中断共 享。 (20) 进程用 task_struct 表示,所有进程被组织到以 init_task 为表头的双向链表中 struct task_struct { volatile long state; unsigned long flags; /* 进程的状态*/ /* 进程标志 */ 5
int sigpending; mm_segment_t addr_limit; /* 线性地址空间:0-0xBFFFFFFF 为用户线性空间地址; 0-0xFFFFFFFF 为内核线性空间地址 */ struct exec_domain *exec_domain; volatile long need_resched; /*标志下一次有调度机会的时候是否调用此进程. 如果此进程没有没有被用户关闭或者其代码全被执行完了,在下一次调度机会应该还被调用. 如果被用户关闭则直接退出该进程.*/ unsigned long ptrace; int lock_depth; long counter; /* 进程的动态优先级,定义了一个在就绪队列的进程当它得到 CPU 后可运行的时间,用静态优先级初始化,当然计算机是以时钟中断做为时间的计数器,每 发送一个时钟中断,动态优先级上的时间片就减少一个时钟中断的时间,时间片减到 0 的时 候就退出该进程而调度另一个进程获得 CPU.*/ long nice; /* 进程的静态优先级,当一个在就绪队列中的进程获得 CPU 之 后,它被赋予此进程可占有 CPU 的时间.这个时间被称为时间片.*/ unsigned long policy; struct mm_struct *mm; /* 进程采用的调度策略,在代码后面有说明 */ /* 进程属性中指向内存管理的数据结构 mm_struct 的指 针,mm_struct 数据结构是描述内存存储信息的数据结构,进程控制块 task_struct 中用 mm 指针指向 mm_struct 数据结构.也就是在进程的属性中通过 mm 指针来管理起对应的内存区*/ int processor; }; (21)runqueue_head: 以 runqueue_head 为表头的链表记录了所有处于就绪态的进程(当前 正在运行的进程也在其中,但 idle_task 除外),调度器总是从中选取最适合调度的进程投 入运行。 (22)IDLE 进程 系统最初的引导进程(init_task)在引导结束后即成为 cpu 0 上的 idle 进程。在每个 cpu 上都有一个 idle 进程,正如上文所言,这些进程登记在 init_tasks[]数 组中,并可用 idle_task()宏访问。idle 进程不进入就绪队列,系统稳定后,仅当就绪队列 为空的时候 idle 进程才会被调度到。 init_tasks 调度器并不直接使用 init_task 为表头的进程链表,而仅使用其中的 "idle_task"。该进程在引导完系统后即处于 cpu_idle()循环中。SMP 系统中,每个 CPU 都 分别对应了一个 idle_task,它们的 task_struct 指针被组织到 init_tasks[NR_CPUS]数组 中,调度器通过 idle_task(cpu)宏来访问这些"idle"进程。 6
3.1.2 完成功能的主要函数 函数说明: (1) Calibrate_tsc 功能: 根据上述公式 fast_gettimeoffset_quotient = (2^32) / (每微秒内的时钟周期 个数)来计算 fast_gettimeoffset_quotient 的值,只被初始化函数 time_init()所调用 参数:无 返回值:TSC 的每一次计数真正代表多长的时间间隔(单位为 us),即一个时钟周期的真正 时间间隔长度 (2) Do_fast_gettimeoffset 功能:通过 delay_at_last_interrupt、last_tsc_low 和时刻 x 处的 TSC 寄存器值计算时刻 x 距上一次时钟中断产生时刻的时间间隔偏移 参数:无 返回值:offset_usec 的值 (3) Do_gettimeofday 功能:完成实际的当前时间检索工作,执行步骤如下: ( 1 ) 调 用 函 数 do_gettimeoffset() 计 算 从 上 一 次 时 钟 中 断 发 生 到 执 行 do_gettimeofday()函数的当前时刻之间的时间间隔 offset_usec。 (2)通过 wall_jiffies 和 jiffies 计算 lost_usec 的值。 (3)然后,令 sec=xtime.tv_sec,usec=xtime.tv_usec+lost_usec+offset_usec。显 然,sec 表示系统当前时间在秒数量级上的值,而 usec 表示系统当前时间在微秒量级上的 值。 (4)用一个 while{}循环来判断 usec 是否已经溢出而超过 106us=1 秒。如果溢出,则 将 usec 减去 106us 并相应地将 sec 增加 1,直到 usec 不溢出为止。 (5)最后,用 sec 和 usec 分别更新参数指针所指向的 timeval 结构变量。至此,整个 查询过程结束。 参数: struct timeval *tv 返回值:无 7
(6)Do_settimeofday 功能:设定实际的当前时间 1>调用 do_gettimeoffset()函数计算上一次时钟中断发生时刻到当前时刻之间的时间 间隔值。 2>通过 wall_jiffies 与 jiffies 计算二者之间的时间间隔 lost_usec。 3>从 tv->tv_usec 中减去 fixed_usec,即:tv->tv_usec-=(lost_usec+offset_usec)。 4> 用 一 个 while{} 循 环 根 据 tv->tv_usec 是 否 小 于 0 来 调 整 tv 结 构 变 量 。 如 果 tv->tv_usec 小于 0,则将 tv->tv_usec 加上 106us,并相应地将 tv->tv_sec 减 1。直到 tv->tv_usec 不小于 0 为止。 5>用修正后的时间 tv 来更新内核全局时间变量 xtime。 6>最后,重置其它时间状态变量。 参数:struct timeval *tv 返回值:无 (7)Do_slow_gettimeoffset 功能:与 do_fast_gettimeoffset 相对应 参数:无 返回值:延时长度 (8)Do_timer_interrupt 功能:调用 sched.c 的 do_timer()获取机器自启动以来时钟嘀嗒次数 参数:int irq, void *dev_id, struct pt_regs *regs 返回值:无 (9)Get_cmos_time 功能:内核在启动时从 RTC 中读取启动时的时间与日期,仅仅在内核启动时被调用一次 参数:无 返回值:调用 mktime()函数将当前时间与日期转换为相对于 1970-01-01 00:00:00 的 秒数值,并将其作为函数返回值返回。 (10)Set_rtc_mmss 功能:用来更新 RTC 中的时间。 参数:unsigned long nowtime,是以秒数表示的当前时间 返回值:更新后的 RTC 时间(秒数) (11)Timer_interrupt 功 能 : IRQ0 有 中 断 请 求 时 , 如 果 满 足 响 应 条 件 , 系 统 执 行 此 函 数 以 调 用 8
分享到:
收藏