高级操作系统实验报告二.docx
- 文档编号:12069352
- 上传时间:2023-06-04
- 格式:DOCX
- 页数:17
- 大小:63.51KB
高级操作系统实验报告二.docx
《高级操作系统实验报告二.docx》由会员分享,可在线阅读,更多相关《高级操作系统实验报告二.docx(17页珍藏版)》请在冰点文库上搜索。
高级操作系统实验报告二
目录
实验报告3
一、模块的编写3
1.1代码的编写3
1.2Makefile的编写3
二、编写测试代码4
2.1代码的编写4
2.2代码的分析7
三、测试结果分析7
3.1测试结果7
3.2创建的进程树8
3.3结果分析8
四、内核进程调度算法分析9
4.1内核进程调度流程9
4.2寻找最高优先级进程10
4.3进程切换11
4.4完成进程切换后11
五、内核编译11
5.1基本流程11
5.2遇到的问题12
实验报告
一、模块的编写
1.1代码的编写
示例的代码不太可行,改为如下代码:
#include
#include
#include
{printk("<0>HelloWorld!
\n");
//显示紧急信息,示例中使用<1>不会显示在shell上,会记录在/var/log/message中return0;}voidcleanup_module(void){printk("<0>Goodbye!
\n");}
1.2Makefile的编写
将此代码保存为hellomod.c文件,此文件不可以直接用gcc编译,需要配置一个Makefile文件(Makefile第一个字母必须大写),Makefile文件如下:
obj-m:
=hellomod.o
PWD:
=$(shellpwd)default:
make-C/lib/modules/2.6.18-164.el5xen/buildSUBDIRS=$(PWD)modules
clean:
rm-rf*.o.*.cmd*.ko*.mod.c.tmp_versions
然后将shell进入当前目录下:
[root@localhosthellomod]#make
[root@localhosthellomod]#insmodhellomod.ko
HelloWorld!
[root@localhosthellomod]#rmmodhellomod.ko
Goodbye!
模块编写成功!
二、调度算法测试代码
2.1代码的编写
这个测试代码是在老师的测试代码上改动的,老师的测试代码上有基础问题,不太适合我的机器,比如耗时程序时间比较短,无法测试出时间,每次测出的时间都为0,故将其循环和参数改大。
#include
#include
#include
#include
#include
#include
#include
#include
voidexe()//耗时程序
{doubledj;
inti,j;
for(j=0;j<30000;j++)
{
for(i=0;i<30000;i++)
{
dj=99999.99;
dj*=999.99;
dj/=999.99;
}
}
}
longni(inti)/*阶乘,耗时程序之一*/
{
if(i==1)
returni;
else
returni*ni(i-1);
}
voidprint_time_id()//获得进程的时间信息
{
structtmstimes_info;
doubleticks;
if((ticks=(double)sysconf(_SC_CLK_TCK))<0)/*取得滴答数与秒数转换的比例*/
perror("Cannotdeterminclocktickspersecond");/*perror就是向标准错误输出输出一个出错信息*/
elseif(times(×_info)<0)
perror("Cannotgettimesinformation");
else
{
/*输出用户的父进程号和自己的进程号*/
fprintf(stderr,"processID:
%ldparentID:
%ld\n",(long)getpid(),(long)getppid());
/*输出进程在用户态执行的时间,顺便打印进程号便于标示,因为输出是并发的*/
fprintf(stdout,"%ldUser_time:
%8.3f
seconds\n",(long)getpid(),times_info.tms_utime/ticks);
/*输出进程在用户态执行的时间,顺便打印进程号便于标示*/
fprintf(stdout,"%ldChildren's_user_time:
%8.3f
seconds\n",(long)getpid(),times_info.tms_cutime/ticks);
fprintf(stdout,"%dSystemtime:
%8.3f
seconds\n",(long)getpid,times_info.tms_stime/ticks);
fprintf(stdout,"Children'ssystemtime:
%8.3fseconds\n",times_info.tms_cstime/ticks);
}
return;
}
intmain()
{
structtmstimes_info;//时间结构体
intuid1=600,uid2=500;
inti,j,count_times=0;//孙进程的计数值
intchildpid,status;
intgrand_uid;//孙进程的id号
if(atexit(print_time_id))//系统在每个进程结束是调用显示时间的函数
{
fprintf(stdout,"Cannotinstallshow_timesexithandler\n");
exit(0);
}
for(i=0;i<2;i++)//创建两个子进程
{
intuid=fork();
if(uid==-1)
{
perror("cannotgofork");
}
elseif(uid==0)
{
if(i==0)//第一个子进程
{
count_times=2;
if(setuid(uid1)==-1)
{
perror("setuidfailed1\n");
exit
(1);
}
}
elseif(i==1)//第二个子进程
{
count_times=3;
if(setuid(uid2)==-1)
{
perror("setuidfailed1\n");
exit
(1);
}
}
for(j=0;j { grand_uid=fork(); if(grand_uid==-1) { perror("Theforkfailed\n"); exit (1); } elseif(grand_uid==0) { exe(); ni(100000); break; } } for(j=0;;j++) { grand_uid=wait(&status);/*孙进程返回-1*/ if((grand_uid==-1)&&(i==0))/*孙进程第一次循环就结束*/ gotoexit_grandson;/*grandsonexit*/ if((grand_uid==-1)&&(errno! =EINTR))/*sonbreak*/ break; } break; } } for(;;)/*父进程等待子进程结束,在退出,子进程是不会被循环阻塞的*/ { childpid=wait(&status); if((childpid==-1)&&(errno! =EINTR)) break; /*childpid=wait(childpid,&status,0);*/ } exit_grandson: exit(0); return0; } 2.2代码的分析 以上代码是参照老师给的测试程序改写的,去掉了不必要的代码。 基本思路是先创建两个子进程,然后一个进程创建2个孙进程,另一个子进程创建3个孙进程,这样可以模拟两个用户,一个用户拥有2个进程,一个用户拥有3个进程,可以模拟测试系统对于不同用户对于进程调度的分配。 三、测试结果分析 3.1测试结果 在终端输入: ./test.o>out使得输出的内容导入文件 测试结果文件如下: 12042User_time: 10.090seconds 12042Children's_user_time: 0.000seconds 134513800Systemtime: 0.020seconds Children'ssystemtime: 0.000seconds 12039User_time: 0.000seconds 12039Children's_user_time: 20.380seconds 134513800Systemtime: 0.000seconds Children'ssystemtime: 0.030seconds 12045User_time: 10.620seconds 12045Children's_user_time: 0.000seconds 134513800Systemtime: 0.010seconds Children'ssystemtime: 0.000seconds 12043User_time: 10.820seconds 12043Children's_user_time: 0.000seconds 134513800Systemtime: 0.000seconds Children'ssystemtime: 0.000seconds 12044User_time: 10.740seconds 12044Children's_user_time: 0.000seconds 134513800Systemtime: 0.000seconds Children'ssystemtime: 0.000seconds 12041User_time: 0.000seconds 12041Children's_user_time: 32.190seconds 134513800Systemtime: 0.000seconds Children'ssystemtime: 0.010seconds 12038User_time: 0.000seconds 12038Children's_user_time: 52.570seconds 134513800Systemtime: 0.000seconds Children'ssystemtime: 0.040seconds 3.2创建的进程树 进程树结构如下: 3.3结果分析 本测试代码,创建了两个子进程,一个子进程创建了2个孙进程,一个进程创建了3个孙进程,相当于两个用户,一个拥有2个进程,一个也有3个进程。 通过上面的测试发现进程12039的所有子进程占用CPU为20.38s,12041的所有子进程占用CPU的时间为32.19,12040占用CPU的时间为10.29s,12042占用的CPU时间为10.09s,12045占用的CPU的时间为10.62s,12043占用的CPU时间为10.82s,12044占用的CPU时间为10.74s。 结果计算发现,CPU的总占用时间为52.57s,12039及其子进程占用的时间占38.77%,12041及其子进程占用的时间为61.23%,12039与12041的占用时间比例为38.77: 61.23,由于12041拥有3个子进程,12039拥有2个子进程,而且每个孙进程的算法复杂度是一样的,理论上他们的时间占有比例应该是2: 3才是比较合理的,但是二者的比例也是比较接近2: 3的。 所以系统的进程调度算法是比较公平的。 四、内核进程调度算法分析 2.6.18内核的进程调度算法分析: 内核进程调度算法是基于优先级的调度算法。 优先级的范围为0 ~ MAX_PRIO-1; 内核的数据结构: struct runqueue是2.6调度器中一个非常重要的数据结构,它主要用于存放每个CPU的就绪队列信息。 内部维持着一个十字队列rq,定义了各个优先级的二级队列,然后对优先级进行修改,如下图。 4.1内核进程调度流程 内核调度的主程序函数为asmlinkagevoid__schedschedule(void)位于sched.c的3519行。 schedule的调度流程如下: 首先两个重要数据: prev和next prev 当前进程,也就是即将被切换出CPU的进程 next 下一个进程,也就是即将被切换进CPU的进程 1、准备工作 a. 做原子操作方面的检查(in_atomic()); b. 关闭内核抢占(通过函数preempt_disable()),因为此时将要对内核一系列重要数据进行操作,所以必须将内核抢占关闭; c. 将当前进程current赋值给prev,获取当前CPU的运行队列rq,释放prev的内核锁,计算prev的运行时间。 //获取rp队列,释放以前的 prev=current; release_kernel_lock(prev); rq=this_rq(); //计算运行时间 if(likely((longlong)(now-prev->timestamp) run_time=now-prev->timestamp; if(unlikely((longlong)(now-prev->timestamp)<0)) run_time=0; }else run_time=NS_MAX_SLEEP_AVG; run_time/=(CURRENT_BONUS(prev)? : 1); d. 进行内核的数据统计,如果prev处于可中断状态,而且有信号等待处理,则将prev状态置为TASK_RUNNING,否则将prev从rq中删除。 (这一部分的代码主要是因为在进程在转入睡眠状态时,需要主动调用schedule()函数); switch_count=&prev->nivcsw; if(prev->state&&! (preempt_count()&PREEMPT_ACTIVE)){ switch_count=&prev->nvcsw; if(unlikely((prev->state&TASK_INTERRUPTIBLE)&& unlikely(signal_pending(prev)))) prev->state=TASK_RUNNING; else{ if(prev->state==TASK_UNINTERRUPTIBLE) rq->nr_uninterruptible++; deactivate_task(prev,rq); } } e. 如果rq中就绪进程个数为0,而且系统是SMP,则进行负载均衡的操作,否则将next置为idle进程,赋值rq->expired_timestamp = 0,然后直接进行进程切换。 cpu=smp_processor_id(); if(unlikely(! rq->nr_running)){ idle_balance(cpu,rq); if(! rq->nr_running){ next=rq->idle; rq->expired_timestamp=0; wake_sleeping_dependent(cpu); gotoswitch_tasks; } } 4.2寻找最高优先级进程 a.如果rq的active array中进程个数为0,则将active array和expired array进行切换。 具体的过程由以下代码完成: array = rq->active; rq->active = rq->expired; rq->expired = array; rq->expired_timestamp = 0; rq->best_expired_prio = MAX_PRIO; b.用函数sched_find_first_bit()找到优先级最高的进程队列的偏移量idx,那么queue[idx]->next即为所找的next,可以通过以下三行代码快速完成: idx = sched_find_first_bit(array->bitmap); queue = array->queue + idx; next = list_entry(queue->next, task_t, run_list); c.如果next是从TASK_INTERRUPTIBLE状态中被唤醒的(actived>0),则将进程从就绪队列中删除,将进程在就绪队列上的等待时间也加在等待时间里面重新计算进程的prio,再根据新的优先级将进程插入相应就绪队列。 if(! rt_task(next)&&interactive_sleep(next->sleep_type)){ unsignedlonglongdelta=now-next->timestamp; if(unlikely((longlong)(now-next->timestamp)<0)) delta=0; if(next->sleep_type==SLEEP_INTERACTIVE) delta=delta*(ON_RUNQUEUE_WEIGHT*128/100)/128; array=next->array; new_prio=recalc_task_prio(next,next->timestamp+delta); if(unlikely(next->prio! =new_prio)){ dequeue_task(next,array); next->prio=new_prio; enqueue_task(next,array); } } 4.3进程切换 a. 如果prev! =next,则进行进程切换; b.进行进程切换前的准备: 将当前时间赋给next->timestamp,并且将rq->curr=next;可见此时的rq->curr与current不再相同; c. 进程切换,包括内存、堆栈切换等。 4.4完成进程切换后 完成进程切换过后,还需要进行释放prev的mm,给rq解锁,重新给current获得内核锁,使能内核抢占;最后检查TIF_NEED_RESCHED位,如果已被置位,则重新开始进行调度,重复上述过程;否则调度结束。 if(unlikely(reacquire_kernel_lock(prev)<0)) gotoneed_resched_nonpreemptible; preempt_enable_no_resched(); if(unlikely(test_thread_flag(TIF_NEED_RESCHED))) gotoneed_resched; 五、内核编译 5.1基本流程 1.下载linux内核,我下载了2.6.18的内核版本(其实下载那个版本的都是无所谓的),到http: //www.kernel.org/pub/linux/kernel/上下载。 2.将下载的源码包,解压任意目录下,我将其解压到本地windows分区的e盘下。 3.为了方便,将原来正在运行的系统的内核目录下的.config文件拷贝到现在的内核目录下,这样就不用配置Makefile了。 4.#makemenuconfig 选择加载本地配置文件的那一项,选择刚才加载的.config文件,然后退出保存。 5.#makezImage 编译内核,使得生成vmlinuz文件于arch/i386/boot目录下,此项如果是第一次编译则估计需要一个小时左右。 有时候他会提示内核太大需要使用makebzImage命令,那就使用makebzImage命令就好了。 6.#makemodules 编译模块,使得一些模块编译。 7.#makemodules_install安装模块 #makeinstall将刚才的vmlinuz文件拷贝到/boot目录下便于重新启动时的引导,同时自动修改grub的配置文件。 8.重新启动,选择刚才编译的内核启动机器。 5.2遇到的问题 1.以前不知道内核编译的具体流程,直接用makemenuconfig配置,配置之后编译会有这样和那样的琐碎问题,比较麻烦,也不太好解决。 后来发现这种设置只是为了生成.config文件,既然如此,不如直接将好的.config文件拷贝进来直接使用,这样省去了不必要的麻烦。 十分方便。 2.内核编译之后一定要查看grub和boot目录下的文件及配置,是否齐全,一般boot目录下需要system文件、vmlinuz文件、initrd文件,有了这三个文件才可以正常启动,如果没有这需要手工生成,然后拷贝到这个目录下,最后修改grub的配置文件。 3.磁盘空间随着内核的编译逐渐的减小,想出一个办法,将一个比较大的文件夹拷贝到另外一个比较大的分区上,然后将原来
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 高级 操作系统 实验 报告