操作系统课程设计任务指导书Word格式文档下载.docx
《操作系统课程设计任务指导书Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《操作系统课程设计任务指导书Word格式文档下载.docx(59页珍藏版)》请在冰豆网上搜索。
![操作系统课程设计任务指导书Word格式文档下载.docx](https://file1.bdocx.com/fileroot1/2023-2/2/c2887234-1458-41ac-a1ff-62839cb189f3/c2887234-1458-41ac-a1ff-62839cb189f31.gif)
最简单的情形是strace执行指定的命令直至命令退出,它可以拦截并记录系统调用信息(包括系统调用名、系统调用参数、系统调用返回值),详细的信息可以参看man手册。
下面演示一个用strace追踪拷贝文件命令cp的例子,执行下面命令:
$stracecpwj1.txtwj5.txt
执行结果的输出包含许多我们不关心的信息,所以我们只列出应该注意的部分:
open("
wj1.txt"
O_RDONLY|O_LARGEFILE)=3
wj5.txt"
O_WRONLY|O_CREAT|O_LARGEFILE,0100644)=4
read(3,"
abcde\n"
4096)=6
write(4,"
6)=6
"
4096)=0
close(4)=0
close(3)=0
我们看到每一项的格式是统一的:
系统调用名系统调用参数返回值
Strace命令有许多选项,其中–c选项特别有用,它能统计系统调用的相关数据,其中包括调用次数,多少次出错返回,系统调用的耗费时间等。
下面是示例产生的结果:
$strace-ccpwj1.txtwj5.txt
%timesecondsusecs/callcallserrorssyscall
------------------------------------------------------------
78.910.00325218118open
21.090.0008698691execve
0.000.000000017read
0.000.00000001write
0.000.000000019close
0.000.000000021access
0.000.00000003brk
0.000.00000006munmap
0.000.00000003mprotect
0.000.000000028mmap2
0.000.00000003stat64
0.000.000000016fstat64
0.000.00000001geteuid32
0.000.00000001set_thread_area
100.000.0041211191total
time命令可以累计一个程序的运行时间。
格式如下:
time[options]command[arguments...]
time在命令command执行结束时在标准错误输出中输出该命令的用时情况,其结果包含下面三项:
1)实际时间(realtime):
从command命令行开始执行到结束经历的时间;
2)用户CPU时间(userCPUtime):
命令执行所占的用户态时间;
3)系统CPU时间(systemCPUtime):
命令执行所占的核心态时间(系统调用时间)。
其中,用户CPU时间和系统CPU时间之和为命令占用CPU执行的时间总和。
一般说来,实际时间要大于CPU时间,因为comand可能和其他进程交替执行。
下面是一个具体例子,测试ls命令递归列出当前目录内容所用时间。
$timels-R
执行结果如下:
.......(ls命令结果略)
real0m2.612s
user0m0.080s
sys0m2.524s
值得提醒的是,依赖于操作系统的计时机制,time命令本身不一定很精确;
此外,command的执行时间依赖于环境,所以同一命令执行多次结果也可能不同,一个比较合理的做法是一个命令执行多次然后取平均值。
mmap介绍
mmap指memory-mapped(存储映射),其将文件内容映射到进程地址空间,相比传统方法而言,往往能够减少系统调用和内容拷贝次数,从而提高性能。
其函数原型如下:
void*mmap(void*addr,size_tlen,intprot,intflags,intfd,off_toffset);
函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。
参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成(参见flags的说明)。
参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射。
len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。
prot参数指定共享内存的访问权限。
可取如下几个值的或:
PROT_READ(可读),PROT_WRITE(可写),PROT_EXEC(可执行),PROT_NONE(不可访问)。
flags由以下几个常值指定:
MAP_SHARED,MAP_PRIVATE,MAP_FIXED,其中,MAP_SHARED,MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。
offset参数一般设为0,表示从文件头开始映射。
下面给出一个使用mmap输出文件内容的例子:
#include<
stdio.h>
stdlib.h>
sys/mman.h>
sys/types.h>
sys/stat.h>
fcntl.h>
intmain(intargc,char*argv[])
{
intsrc;
char*sm;
structstatstatbuf;
size_tfz;
if(argc!
=2)
{
fprintf(stderr,"
Usage:
%s<
sourcefile>
\n"
argv[0]);
exit(EXIT_FAILURE);
}
if((src=open(argv[1],O_RDONLY))<
0)
perror("
opensource"
);
if(fstat(src,&
statbuf)<
0)
fstatsource"
fz=statbuf.st_size;
sm=mmap(0,fz,PROT_READ,
MAP_PRIVATE|MAP_NORESERVE,src,0);
if(MAP_FAILED==sm)
perror("
mmapsource"
while(fz){
putchar(*sm++);
fz--;
return(EXIT_SUCCESS);
}
任务二实现一个简单的shell
本任务主要目的在于学会如何在Unix系统下创建进程和管理进程,了解shell工作的基本原理。
实现一个简单的shell(命令行解释器),类似于sh,bash,csh等。
你的shell必须支持以下内部命令
cd<
目录>
更改当前的工作目录到另一个<
。
如果<
未指定,输出当前工作目录。
不存在,应当有适当的错误信息提示。
这个命令应该也能改变PWD的环境变量。
environ列出所有环境变量字符串的设置(类似于Unix系统下的env命令)。
echo<
内容>
显示echo后的内容且换行
help简短概要的输出你的shell的使用方法和基本功能。
jobs输出shell当前的一系列子进程,必须提供子进程的命名和PID号。
quit,exit,bye退出shell。
所有的内部命令应当优先于在$PATH中同名的程序。
任何非内部命令必须请求shell创建一个新进程,且该子进程执行指定的程序。
这个新进程必须继承shell的环境变量和指定的命令行参数。
Shell应当具有以下特征:
BatchProcessing如果shell启动带有一个文件名作为参数,打开该文件并执行文件里所有命令。
待所有进程全部结束退出shell。
该功能类似于shell的交互模式。
Debugging提供-v选项,shell启动时打开此选项将在运行过程中输出若干调试信息。
在该种模式下,shell应该显示所有被创建了的进程的PID号,通报已结束的子进程和传递给子进程的参数等。
Prompt(命令行提示符)解释器应该打印$PS2(而不是$PS1)作为提示符。
Backgroundprocessing如果命令以符号&
终止,在后台并发执行该程序。
shell立即等待下一命令行的输入,而不等待该程序的结束。
Redirect(重定向)该功能选做
Pipe(管道)该功能选做
你可假定所有的命令和参数由空格或tab符分开。
相关知识介绍
shell的主体就是反复下面的循环过程
while
(1){
接收用户输入的命令行;
解析命令行;
if(用户命令为内部命令)
直接处理;
elseif(用户命令为外部命令)
创建子进程执行命令;
else
提示错误的命令;
创建子进程要使用fork()函数,执行新的命令要使用exec()系列函数,通常shell是等待子进程结束后再接受用户新的输入,这可以使用waitpid()函数。
下面是一个完整的示例:
unistd.h>
sys/wait.h>
intmain(void)
pid_tpid;
if((pid=fork())<
0){
printf("
forkerror\n"
);
exit(EXIT_FAILURE);
}elseif(pid==0){/*childprocess*/
if(execl("
/bin/ls"
"
ls"
-l"
(char*)0)<
execlerror\n"
waitpid(pid,0,0);
/*waitforchildprocess*/
exit(0);
Fork()原型如下:
pid_tfork(void);
fork建立一个子进程,父进程继续运行,子进程在同样的位置执行同样的程序。
对于父进程,fork()返回子进程的pid,对于子进程,fork()返回0。
出错时返回-1。
Exec系列有6个函数,原型如下:
externchar**environ;
intexecl(constchar*path,constchar*arg,...);
intexeclp(constchar*file,constchar*arg,...);
intexecle(constchar*path,constchar*arg,...,char*constenvp[]);
intexecv(constchar*path,char*constargv[]);
intexecve(constchar*filename,char*constargv[],char*constenvp[]);
intexecvp(constchar*file,char*constargv[]);
exec系列函数用新的进程映象置换当前的进程映象.这些函数的第一个参数是待执行程序的路径名(文件名).这些函数调用成功后不会返回,其进程的正文(text),数据(data),bss和堆栈(stack)段被待执行程序程序覆盖。
但是进程的PID和所有打开的文件描述符没有改变,同时悬挂信号被清除,信号重置为缺省行为.
在函数execl,execlp,和execle中,constchar*arg以及省略号代表的参数可被视为arg0,arg1,...,argn.他们合起来描述了指向NULL结尾的字符串的指针列表,即执行程序的参数列表.作为约定,第一个arg参数应该指向执行程序名自身,参数列表必须用NULL指针结束.
execv和execvp函数提供指向NULL结尾的字符串的指针数组作为新程序的参数列表.作为约定,指针数组中第一个元素应该指向执行程序名自身.指针数组必须用NULL指针结束。
execle函数同时说明了执行进程的环境(environment),他在NULL指针后面要求一个附加参数,NULL指针用于结束参数列表,或者说,argv数组.这个附加参数是指向NULL结尾的字符串的指针数组,他必须用NULL指针结束。
其他函数从当前进程的environ外部变量中获取新进程的环境.
execlp和execvp可根据path搜索合适的程序运行,其他则需要给出程序全路径。
Execve()类似execv(),但是加上了环境的处理。
Wait(),waitpid()
可用来等待子进程结束。
函数原型:
pid_twait(int*stat_loc);
pid_twaitpid(pid_tpid,int*stat_loc,intoptions);
当进程调wait,它将进入睡眠状直到有一个子进程结束。
wait函数返回子进程的进程id,stat_loc中返回子进程的退出状态。
waitpid的第一个参数pid的意义:
pid>
0:
等待进程id为pid的子进程。
pid==0:
等待与自己同组的任意子进程。
pid==-1:
等待任意一个子进程
pid<
-1:
等待进程组号为-pid的任意子进程。
因此,wait(&
stat)等价于waitpid(-1,&
stat,0)
waitpid第三个参数option可以是0,WNOHANG,WUNTRACED或这几者的组合。
任务三进程/线程同步
本任务主要目的练习UNIX/LINUX同步编程,熟悉基本的线程相关API。
编程实现下图的效果,要求分别使用进程和线程
(1)进程+SYSV信号量
(2)线程实现+Posix同步操作API
无论进程形式还是线程形式,进程/线程pi的主体反复输出下面语句:
Iamprocess(orthread)pi
执行次数取必须保证进程并发情况的发生(比如p3和p4的交迭运行),大家自行调整。
必须保证parbegin(p1(),p2(),……,p6())并发形式的充分发生,一个典型的输出系列如下:
Iamthread1
.....
Iamthread3
Iamthread2
Iamthread4
Iamthread3
Iamthread5
Iamthread4
Iamthread6
.....
SYSV信号量API介绍
SystemV信号量最早为SystemV操作系统实现,故而得名。
Linux也支持SystemV信号量,SystemV信号量的函数主要有下面几个.
sys/ipc.h>
sys/sem.h>
intsemget(key_tkey,intnsems,intsemflg);
semget调用成功时返回信号量组ID(可能已经存在或通过创建新的),否则返回值为-1。
key是一个关键字,可以是用ftok()函数创建的也可以是IPC_PRIVATE表明由系统选用一个关键字。
nsems是这个信号量组中信号量的数量。
semflg是创建的权限标志,本设计中要用到IPC_CREAT。
intsemctl(intsemid,intsemnum,intcmd,.../*unionsemunarg*/);
semctl对信号量进行一系列的控制。
semid是要操作的信号量组ID,semnum是信号量组的下标,指明待操作的具体信号量,cmd是操作的命令。
经常用的两个值是:
SETVAL(设置信号量的值)和IPC_RMID(删除信号量)。
arg是一个根据cmd的设置参数。
intsemop(intsemid,structsembuf*spos,intnspos);
structsembuf{
shortsem_num;
/*在信号量组中的下标(0,1,...,nsems-1),表明对哪一个信号量操作*/
shortsem_op;
/*进行什么操作*/
shortsem_flg;
/*操作的标志,有IPC_NOWAIT,SEM_UNDO等*/
};
semop是对信号组进行操作的函数。
semid是信号量组ID,spos是一个操作数组,nspos表明操作数组的大小。
如果sem_op大于0,那么操作将sem_op加入到信号量的值中。
如果为0,当信号量的值是0的时候,函数返回,否则阻塞直到信号量的值为0。
如果小于0,函数判断信号量的值加上这个负值。
如果结果为0唤醒等待信号量为0的进程,如果小与0函数阻塞。
如果大于0,那么从信号量里面减去这个值并返回。
如果IPC_NOWAIT被设置,上面操作不满足时立即返回。
注意,semop是原子操作,也即操作数组不可能部分完成时semop就返回,要么彻底失败,要么全部完成。
函数操作成功时返回0。
本任务中可以先使用semget()创建信号量,然后使用semctl()设置信号量初值,然后通过semop()对信号量操作。
Posix线程编程介绍
下面只是一个简单的介绍,更详细的信息请查阅相关资料。
POSIX通过pthread_create()函数创建线程,API定义如下:
intpthread_create(pthread_t*thread,pthread_attr_t*attr,
void*(*start_routine)(void*),void*arg);
thread:
当pthread_create创建成功,*thread放入新创建的线程ID。
attr:
可用来定义线程的某些属性,本任务缺省的线程属性是适用的,只需将该参数设为NULL。
第三个参数start_routine是新线程启动时调用的函数名。
当start_routine返回时,新线程将终止。
第四个参数arg用作start_routine的参数
/*filethread1.c*/
pthread.h>
void*thread_function(void*arg){
inti;
for(i=0;
i<
20;
i++){
Threadisrunning!
\n"
sleep
(1);
returnNULL;
intmain(void){
pthread_tmythread;
if(pthread_create(&
mythread,NULL,thread_function,NULL)){
errorcreatingthread."
abort();
if(pthread_join(mythread,NULL)){
errorjoiningthread."
注意main()主体是主线程,当pthread_create()创建线程后,系统就有两个线程,线程并发执行,绝对不能假定两者的相对执行次序。
为了