武汉理工大学操作系统课程设计中国好学长系列之小灰灰的爸爸Word格式.docx
- 文档编号:6833810
- 上传时间:2023-05-07
- 格式:DOCX
- 页数:28
- 大小:104.30KB
武汉理工大学操作系统课程设计中国好学长系列之小灰灰的爸爸Word格式.docx
《武汉理工大学操作系统课程设计中国好学长系列之小灰灰的爸爸Word格式.docx》由会员分享,可在线阅读,更多相关《武汉理工大学操作系统课程设计中国好学长系列之小灰灰的爸爸Word格式.docx(28页珍藏版)》请在冰点文库上搜索。
通过研究内核的时间管理算法学习内核源代码。
然后应用这些知识并且使用“信号”建立一种用户空间机制来测量一个多线程程序的执行时间。
实验条件要求:
每人一台Linux主机且有超级用户权限。
2.设计说明书内容要求:
1)设计题目与要求
2)总的设计思想及系统平台、语言、工具等
3)数据结构与模块说明(功能与流程图)
4)运行结果与运行情况
3.调试报告:
1)调试记录
2)自我评析和总结
时间安排:
序号
阶段内容
所需时间
1
消化资料、系统设计
1天
2
编程、调试
3天
3
撰写报告
合计
5天
指导教师签名:
2013年12月26日
系主任(或责任教师)签名:
年月日
内核定时器
摘要
每个进程包含一到多个线程。
进程也可能是整个程序或者是部分程序的动态执行。
线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。
也可以把它理解为代码运行的上下文。
内核时间指明线程执行操作系统代码已经经过了多少个100ns的CPU时间,linux是一个具有保护模式的操作系统。
它一直工作在i386cpu的保护模式之下。
内存被分为两个单元:
内核区域和用户区域。
一般地,在使用虚拟内存技术的多任务系统上,内核和应用有不同的地址空间,因此,在内核和应用之间以及在应用与应用之间进行数据交换需要专门的机制来实现,本文站在用户空间的角度,测试一个多线程程序的程序执行时间。
当一个进程希望获得信号量时,如果信号量已经被占有,则该进程将会被放到等待队列上sleep直到cpu将其唤醒。
相对于spinlock来说开销太大,适用于长时间占有的lock。
不可用于中断状态,因为它拥有信号量的进程可以sleep,可以被抢占。
1设计题目与要求
1.1设计题目:
1.2设计要求:
通过研究内核的时间管理算法,学习内核源代码;
2总的设计思想及系统平台、语言、工具
2.1设计思想:
2.1.1Linux内核对定时器的描述
Linux在include/linux/timer.h头文件中定义了数据结构timer_list来描述一个内核定
时器:
structtimer_list{
structlist_headlist;
unsignedlongexpires;
unsignedlongdata;
void(*function)(unsignedlong);
};
各数据成员的含义如下:
(1)双向链表元素list:
用来将多个定时器连接成一条双向循环队列。
(2)expires:
指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计
数(也即时钟节拍数)。
当一个定时器的expires值小于或等于jiffies变量时,我们就说这个
定时器已经超时或到期了。
在初始化一个定时器后,通常把它的expires域设置成当前expires
变量的当前值加上某个时间间隔值(以时钟滴答次数计)。
(3)函数指针function:
指向一个可执行函数。
当定时器到期时,内核就执行function
所指定的函数。
而data域则被内核用作function函数的调用参数。
内核函数init_timer()用来初始化一个定时器。
实际上,这个初始化函数仅仅将结构中的
list成员初始化为空。
如下所示(include/linux/timer.h):
staticinlinevoidinit_timer(structtimer_list*timer)
{
timer->
list.next=timer->
list.prev=NULL;
}
由于定时器通常被连接在一个双向循环队列中等待执行(此时我们说定时器处于pending
状态)。
因此函数time_pending()就可以用list成员是否为空来判断一个定时器是否处于
pending状态。
如下所示
(include/linux/timer.h):
staticinlineinttimer_pending(conststructtimer_list*
timer)
returntimer->
list.next!
=NULL;
时间比较操作
在定时器应用中经常需要比较两个时间值,以确定timer是否超时,所以Linux内核在
timer.h头文件中定义了4个时间关系比较操作宏。
这里我们说时刻a在时刻b之后,就意味着
时间值a≥b。
Linux强烈推荐用户使用它所定义的下列4个时间比较操作宏
#definetime_after(a,b)((long)(b)-(long)(a)<
0)
#definetime_before(a,b)time_after(b,a)
#definetime_after_eq(a,b)((long)(a)-(long)(b)>
=0)
#definetime_before_eq(a,b)time_after_eq(b,a)
2.1.2Linux内核定时器
定时器是管理内核时间的基础,用来计算流逝的时间,它以某种频率(节拍率)自行触发时钟中断,当时钟中断发生时,内核就通过一种特殊中断处理程序对其进行处理。
但是原来的实现只能是time_tmytime形式的,经过简单的localtime(mytime)和ctime(&
mytime)处理.精度是不够的,为了返回高精度的时间,这里使用了gettimeofday函数。
这个syscall用来供用户获取timeval格式的当前时间信息(精确度为微秒级),以及系统的当前时区信息(timezone)。
结构类型timeval的指针参数tv指向接受时间信息的用户空间缓冲区,参数tz是一个timezone结构类型的指针,指向接收时区信息的用户空间缓冲区。
这两个参数均为输出参数,返回值0表示成功,返回负值表示出错。
函数sys_gettimeofday()的源码如下(kernel/time.c):
asmlinkagelongsys_gettimeofday(structtimeval*tv,structtimezone*tz)
if(tv){
structtimevalktv;
do_gettimeofday(&
ktv);
if(copy_to_user(tv,&
ktv,sizeof(ktv)))
return-EFAULT;
if(tz){
if(copy_to_user(tz,&
sys_tz,sizeof(sys_tz)))
return0;
显然,函数的实现主要分成两个大的方面:
(1)如果tv指针有效,则说明用户要以timeval格式来检索系统当前时间。
为此,先调用do_gettimeofday()函数来检索系统当前时间并保存到局部变量ktv中。
然后再调用copy_to_user()宏将保存在内核空间中的当前时间信息拷贝到由参数指针tv所指向的用户空间缓冲区中。
(2)如果tz指针有效,则说明用户要检索当前时区信息,因此调用copy_to_user()宏将全局变量sys_tz中的时区信息拷贝到参数指针tz所指向的用户空间缓冲区中。
(3)最后,返回0表示成功。
函数do_gettimeofday()的源码如下(arch/i386/kernel/time.c):
/*
*Thisversionofgettimeofdayhasmicrosecondresolution
*andbetterthanmicrosecondprecisiononfastx86machineswithTSC.
*/
voiddo_gettimeofday(structtimeval*tv)
unsignedlongflags;
unsignedlongusec,sec;
read_lock_irqsave(&
xtime_lock,flags);
usec=do_gettimeoffset();
unsignedlonglost=jiffies-wall_jiffies;
if(lost)
usec+=lost*(1000000/HZ);
sec=xtime.tv_sec;
usec+=xtime.tv_usec;
read_unlock_irqrestore(&
while(usec>
=1000000){
usec-=1000000;
sec++;
tv->
tv_sec=sec;
tv_usec=usec;
该函数的完成实际的当前时间检索工作。
由于gettimeofday()系统调用要求时间精度要达到微秒级,因此do_gettimeofday()函数不能简单地返回xtime中的值即可,而必须精确地确定自从时钟驱动的BottomHalf上一次更新xtime的那个时刻到do_gettimeofday()函数的当前执行时刻之间的具体时间间隔长度,以便精确地修正xtime的值.
假定被do_gettimeofday()用来修正xtime的时间间隔为fixed_usec,而从wall_jiffies到jiffies之间的时间间隔是lost_usec,而从jiffies到do_gettimeofday()函数的执行时刻的时间间隔是offset_usec。
则下列三个等式成立:
fixed_usec=(lost_usec+offset_usec)
lost_usec=(jiffies-wall_jiffies)*TICK_SIZE=(jiffies-wall_jiffies)*(1000000/HZ)
由于全局变量last_tsc_low表示上一次时钟中断服务函数timer_interrupt()执行时刻的CPUTSC寄存器的值,因此我们可以用X86CPU的TSC寄存器来计算offset_usec的值。
也即:
offset_usec=delay_at_last_interrupt+(current_tsc_low-last_tsc_low)*fast_gettimeoffset_quotient
其中,delay_at_last_interrupt是从上一次发生时钟中断到timer_interrupt()服务函数真正执行时刻之间的时间延迟间隔。
每一次timer_interrupt()被执行时都会计算这一间隔,并利用TSC的当前值更新last_tsc_low变量(可以参见7.4节)。
假定current_tsc_low是do_gettimeofday()函数执行时刻TSC的当前值,全局变量fast_gettimeoffset_quotient则表示TSC寄存器每增加1所代表的时间间隔值,它是由time_init()函数所计算的。
根据上述原理分析,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结构变量。
至此,整个查询过程结束。
函数do_gettimeoffset()根据CPU是否配置有TSC寄存器这一条件分别有不同的实现。
其定义如下(arch/i386/kernel/time.c):
#ifndefCONFIG_X86_TSC
staticunsignedlongdo_slow_gettimeoffset(void)
……
staticunsignedlong(*do_gettimeoffset)(void)=do_slow_gettimeoffset;
#else
#definedo_gettimeoffset()do_fast_gettimeoffset()
#endif
显然,在配置有TSC寄存器的i386平台上,do_gettimeoffset()函数实际上就是do_fast_gettimeoffset()函数。
它通过TSC寄存器来计算do_fast_gettimeoffset()函数被执行的时刻到上一次时钟中断发生时的时间间隔值。
其源码如下(arch/i386/kernel/time.c):
staticinlineunsignedlongdo_fast_gettimeoffset(void)
registerunsignedlongeax,edx;
/*ReadtheTimeStampCounter*/
rdtsc(eax,edx);
/*..relativetopreviousjiffy(32bitsisenough)*/
eax-=last_tsc_low;
/*tsc_lowdelta*/
*Timeoffset=(tsc_lowdelta)*fast_gettimeoffset_quotient
*=(tsc_lowdelta)*(usecs_per_clock)
*=(tsc_lowdelta)*(usecs_per_jiffy/clocks_per_jiffy)
*
*Usingamullinsteadofadivlsavesupto31clockcycles
*inthecriticalpath.
__asm__("
mull%2"
:
"
=a"
(eax),"
=d"
(edx)
rm"
(fast_gettimeoffset_quotient),
0"
(eax));
/*ouradjustedtimeoffsetinmicroseconds*/
returndelay_at_last_interrupt+edx;
对该函数的注释如下:
(1)先调用rdtsc()函数读取当前时刻TSC寄存器的值,并将其高32位保存在edx局部变量中,低32位保存在局部变量eax中。
(2)让局部变量eax=Δtsc_low=eax-last_tsc_low;
也即计算当前时刻的TSC值与上一次时钟中断服务函数timer_interrupt()执行时的TSC值之间的差值。
(3)显然,从上一次timer_interrupt()到当前时刻的时间间隔就是(Δtsc_low*fast_gettimeoffset_quotient)。
因此用一条mul指令来计算这个乘法表达式的值。
(4)返回值delay_at_last_interrupt+(Δtsc_low*fast_gettimeoffset_quotient)就是从上一次时钟中断发生时到当前时刻之间的时间偏移间隔值。
2.1.3Linux信号signal处理机制
信号signal机制是进程之间相互传递消息的一种方法,全称为软中断信号。
系统调用signal用来设定某个信号的处理方法,其调用声明的格式如下:
void(*signal(intsignum,void(*handler)(int)))(int);
成功则返回该信号以前的处理配置,出错则返回SIG_ERR。
在使用该调用的进程中加入以下头文件:
<
signal.h>
几个常见信号:
SIGINT:
当用户按某些终端键时,引发终端产生的信号.如Ctrl+C键,这将产生中断信号(SIGINT),它将停止一个已失去控制的程序。
SIGSEGV:
由硬件异常(除数为0,无效的内存引用等等)产生的信号。
这些条件通常由硬件检测到,并将其通知内核,然后内核为该条件发生时正在运行的进程产生该信号。
SIGURG:
在网络连接上传来带外数据时产生。
SIGPIPE:
在管道的读进程已终止后,一个进程写此管道时产生,当类型为SOCK_STREAM的socket已不再连接时,进程写到该socket也产生此信号。
SIGALRM:
进程所设置的闹钟时钟超时的时候产生。
SIGABRT:
进程调用abort函数时产生此信号,进程异常终止。
SIGCHLD:
在一个进程终止或停止时,它将把该信号发送给其父进程。
按系统默认,将忽略此信号,如果父进程希望被告知其子进程的这种状态改变,则应该捕捉此信号。
通常是用wait系列函数捕捉,如果不wait的话,子进程将成为一个僵尸进程。
SIGIO:
此信号指示一个异步I/O事件。
SIGSYS:
该信号指示一个无效的系统调用。
SIGTSTP:
交互式停止信号.Ctrl+Z,按下时,终端将产生此信号,进程被挂起。
2.1.4多线程编程
多线程是计算机同时运行多个执行线程的能力(这些线程可以是同一程序的组成部分,或者也可以是完全不同的程序)。
Linux系统下的多线程遵循POSIX线程接口,称为pthread。
编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。
而Linux下pthread的实现是通过系统调用clone()来实现的。
clone()是Linux所特有的系统调用,它的使用方式类似fork。
下面展示多线程程序部分050119.c。
/*050119.c*/
……
#include<
pthread.h>
stdio.h>
voidthread(void)
{
inti;
for(i=0;
i<
3;
i++)
printf("
Thisisapthread.\n"
);
}
intpthread(void)
pthread_tid;
inti,ret;
ret=pthread_create(&
id,NULL,(void*)thread,NULL);
if(ret!
=0){
printf("
Createpthreaderror!
\n"
exit
(1);
}
Thisisthemainprocess.\n"
pthread_join(id,NULL);
return(0);
我们编译此程序:
gcc050119.c-lpthread-o050119.out
运行050119.out,我们得到如下结果:
Thisisthemainprocess.
Thisisapthread.
再次运行,我们可能得到如下结果:
前后两次结果不一样,这是两个线程争夺CPU资源的结果。
上面的示例中,我们使用到了两个函数,pthread_create和pthread_join,并声明了一个pthread_t型的变量。
pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
typedefunsignedlongintpthread_t;
它是一个线程的标识符。
函数pthread_create用来创建一个线程,它的原型为:
externintpthread_create__P((pthread_t*__thread,__constpthread_attr_t*__attr,void*(*__start_routine)(void*),void*__arg));
第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。
这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。
第二个参数我们也设为空指针,这样将生成默认属性的线程。
对线程属性的设定和修改我们将在下一节阐述。
当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。
前者表示系统限制创建新的线程,例如线程数目过多了;
后者表示第二个参数代表的线程属性值非法。
创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
函数pthread_join用来等待一个线程的结束。
函数原型为:
externintpthread_join__P((pthread_t__th,void**__thread_return));
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。
这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;
另一种方式是通过函数pthread_exit来实现。
它的函数原型为:
ext
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 武汉理工大学 操作系统 课程设计 中国 好学 系列 灰灰 爸爸