收藏 分享(赏)

UNIX系统程序设计实验指导.doc

上传人:春华秋实 文档编号:3291520 上传时间:2018-10-10 格式:DOC 页数:9 大小:50.50KB
下载 相关 举报
UNIX系统程序设计实验指导.doc_第1页
第1页 / 共9页
UNIX系统程序设计实验指导.doc_第2页
第2页 / 共9页
UNIX系统程序设计实验指导.doc_第3页
第3页 / 共9页
UNIX系统程序设计实验指导.doc_第4页
第4页 / 共9页
亲,该文档总共9页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

1、.页眉.页脚实验一 实现带参数的简单 shell实验一利用课本第 9 页程序 1-5 的框架,实现允许输入命令带参数的简单shell。原来的实现是不能够带参数的。输入命令所能带的参数个数,只受到系统键盘输入缓冲区长度(以及 shell 输入缓冲区长度)的限制,该缓冲区的缺省长度是 4096 个字节。实现时要解决的主要问题有:1正确理解并使用系统调用 fork(),execve() 和 waitpid(),特别是 execve()函数。fork()函数创建一个新的进程。新进程就是所谓的子进程,它是执行 fork()函数的进程(父进程) 的“克隆” ,也就是说,子进程执行的程序与父进程的完全一样。

2、当 fork()函数返回值为 0 时表示处于子进程中;而返回值大于 0 时表示处于父进程中,此时的返回值是子进程的进程 id。因此,fork()的返回值可以用来划分仅仅适合父进程和子进程执行的程序段。fork()函数返回值为-1 时表示出错。如果子进程只是运行与父进程完全一样的程序,那用处是很有限的。要让子进程运行不同于父进程的程序,就必须调用 execve 函数,它是所有其他 exec 函数的基础。execve 函数把调用它的进程的程序,替换成 execve 函数的参数所指定的程序。运行 execve 函数成功后,进程将开始运行新的程序,也就是 execve 函数的参数所指定的程序。exec

3、ve 函数原型:int execve(const char *path, const char *argv,const char *envp);其中:path:要执行的程序路径名,比如“/bin/ls” , “cd”, “/usr/bin/gcc”等等。argv:参数表,比如 ls 命令中可带的命令行参数 -l,-a 等。注意,argv 的第一个元素必须是要执行的程序(命令)的路径名。envp:环境变量表,供要执行的命令使用。实参数用 NULL 或系统环境变量environ 均可。注意,因为 environ 由系统提供,属于外部变量,所以说明时必须用“extern”修饰。例子:char *ar

4、gv = “gcc”, “-g”, “-c”, “hello.c”, NULL;char *argv1 = “/bin/ls”, “-l”, “-a”, NULL;execve(“/usr/bin/gcc”, argv, environ); / 编译程序“hello.c”execve(“/bin/ls”, argv1, NULL); / 执行命令“ls l a”execve(“/usr/ls”, argv1, NULL); / 出错,因为目录/usr/下没有 ls 程序。/ 注意,在 argv1 的第一个字符串 “/bin/ls”中,只有 ls 是有用的。系统调用 waitpid()用于等待子

5、进程结束、获取子进程的运行状态,详细说明在第八章。本实验仅仅用它使父进程等待子进程结束,因此维持程序 1-5 的用法即可。2根据简单 shell 的输入,构造 execve 函数的参数。根据程序 1-5,数组 buf 保存用户的输入,包括命令和参数。由于 shell 命令的命令名和各参数之间是用空格分开,因此可以用空格作为分界符。通过一个循环可以把 buf 数组中的命令和各个参数依次分离开来,并赋给数组 argv 的各元.页眉.页脚素适当的指针值。argv 数组的最后一个指针必须是 NULL。接着就可以调用execve(argv0,argv, environ)来执行用户输入的命令。提示:arg

6、v 数组中各指针所指向的字符串,可以直接利用 buf 的存储空间,不需要另外分配内存。3正确编译程序。由于书中例子用到了本书第一版中作者自己定义的 error.c 文件,因此编译时要记得把 error.c 文件和源代码文件一起编译, error.c 文件可以到 cs8 的/home/luwei/common 目录下复制。可以用如下命令编译:gcc error.c o实验二 同步与异步 write 的效率比较1、程序的参数和输入实验要求程序必须指定输出的文件名,而该文件是否按同步方式打开,则是可以选择的。因此程序至少带一个、至多两个输入参数。程序默认从标准输入STDIN_FILENO 读取输入文

7、件,可以利用 shell 的输入定向功能选择具体的输入文件。2、 系统调用 times()的说明#include clock_t times(struct tms *buf);struct tms clock_t tms_utime; /* 记录进程除系统调用外所使用的 CPU 时间 */clock_t tms_stime; /* 记录进程的系统调用所使用的 CPU 时间 */clock_t tms_cutime; /* 记录子进程除系统调用外所使用的 CPU 时间 */clock_t tms_cstime; /* 记录子进程的系统调用所使用的 CPU 时间 */;times 函数的返回值是进

8、程迄今为止的存活时间。所有时间都是以“滴答”为单位的,函数 sysconf(_SC_CLK_TCK)可获得所运行系统每秒的滴答数 (参考课本P33)。3、计算 write 耗费的时间为了准确计算 write 耗费的时间,很重要的就是要避免将 read 的时间计入,因为 I/O 操作的时间通常是毫秒级的,不可以忽略。一种有效的方法是,设置一个与输入文件长度相同的缓冲区,一次性地将输入文件读入缓冲区,而后就不必再读输入文件。这样就可以有效避免计入 read 的时间。有同学可能会问,难道可以在内存中创建一个几十上百兆乃至上 G 的缓冲区吗?回答是没问题!因为我们所运行的操作系统系统具有虚拟存储管理功

9、能。.页眉.页脚设置输入缓冲区时需要知道输入文件的长度。除了使用系统调用 stat 外,更简单的方法是利用 lseek 的返回值来获取文件的长度。在按每一个给定大小的输出缓冲区计算写文件时间时,应当在开始写之前调用times(),记录下开始时间,然后在整个输入缓冲区都复制到输出文件之后,再调用 times(),两次调用 times()的时间间隔,就是在这个给定大小的输出缓冲区的限制下,复制整个输入文件所耗费的写时间。至于在每一次写的时候所执行的其他语句,它们相较于 I/O 操作,所花费的时间极小,可以忽略不计。注意,在开始按一个给定大小的输出缓冲区复制输入文件时,应当先将输出文件的写位置复位到

10、输出文件的开头(这意味着不能以 O_APPEND 方式打开输出文件) 。可以使用 lseek 做到这一点,以避免多次打开、关闭输出文件。实验三 目录树的遍历实验三主要以课本 99102 页程序 4-7 为框架,在此基础上进行扩展。需要注意的问题主要有:1正确理解程序 4-7。程序 4-7 递归降序遍历目录层次结构,并按照文件类型进行计数。主要涉及到三个函数,ftw4(), dopath()和 myfunc()。ftw4() 函数以所带参数 pathname 为要遍历的起始目录,计算出该目录下各种不同类型的文件的个数和所占百分比,并显示出来。它调用了另外两个函数,一个是dopath()函数,这是

11、一个递归函数,对指定的起始目录下的每个目录项,按深度优先进行遍历;而对所访问的节点,则调用 myfunc()进行处理。main函数输出统计结果。三个函数的参数含义如下:(1) myftw(char* pathname, Myfunc* func);pathname 给出指要遍历开始的目录。func 是 Myfunc 类型的函数指针,定义访问的实际操作。(2) dopath(Myfunc* func);参考第一个函数(3) int myfunc(const char * pathname,const struct stat *statptr,int type);pathname 指向当前访问节点

12、的路径名。Statptr 指向当前访问节点的 i-节点的结构,该结构保存有许多该文件的信息。type 给出当前访问节点的类型,在实验中可以自己定义它的涵义。myfunc()的返回值通常是 0,实际上在程序 4-7 中它的值总是 0。但是在dopath()函数中, myfunc()的返回值非 0 意味着终止遍历。另外,程序 4-7 中用到课本 2-3 中的函数 path_alloc(),用于分配存放路径名的内存空间。2实验三要求根据用户输入的命令行选项的不同,来实现三种功能:.页眉.页脚(1)argc 为 2 时,命令格式为myfind 它除了实现程序 47 功能外,还要统计出,在常规文件中,文

13、件长度不大于 4096 字节的常规文件,在所有允许访问的普通文件中所占的百分比。程序也不允许打印出任何路径名。这个功能实现比较简单,只要略加修改 myfunc()和 main()这两个函数就可以了。(2)argc 为 4 且 argv2 = “-comp”时,命令格式为myfind -comp 它的功能是,输出在目录子树之下,所有与文件内容一致的文件的绝对路径名。不允许输出任何其它的路径名,包括不可访问的路径名。为提高程序效率,在比较文件是否相同时,可先比较两个文件的大小,如果大小不同,则内容肯定不同,这样就免去了读文件所浪费的时间;如果大小相同,则再通过读文件进行比较。此时应当注意输入缓冲区

14、不必开的太大,你可以从实验二得到启发。由于要求输出符合要求的文件的绝对路径名,因此当参数 pathname 不是绝对路径时,要调用 getcwd()等函数来取得文件的绝对路径名。(3) argc 大于等于 4 且 argv2 = “-name”时,命令格式为myfind -name 是一个以空格分隔的文件名序列(不带目录)。命令输出在目录子树之下,所有与 序列中文件名相同的文件的绝对路径名。不允许输出不可访问的或无关的路径名。实现方法可以通过循环,把当前遍历的文件名和这个序列中的文件名进行比较,如果和序列中的一个文件名相同,就符合条件,此时输出符合条件的文件的绝对路径名。3在实现上述三种功能时

15、,主要是通过实现三个不同的 myfunc()来完成的。第一个功能只须对 myfunc()略加修改即可。而第二个和第三个功能,则需要分别定义另外两个函数,不妨分别称做 myfunc2()和 myfunc3(),去完成指定的功能。至于 dopath()函数则基本不必修改,因为它只是按深度优先完成对目录树的遍历,这是一种“标准”操作。而遍历时对节点(即文件或目录)的操作(所谓的“访问” )则是由具体参数 func()决定的。不过,如果你觉得程序输出的绝对路径名因为含有“”而不够美观,则也可以对 dopath()函数略做修改来纠正这个缺欠。实验四 模拟“五个哲学家”问题该实验的要点是,解决并发环境下,

16、多进程之间的同步与互斥问题。进程间的同步互斥必然涉及进程间的通信(信息交换) 。但是进程的内存空间是彼此隔离的,因此它们之间的通信只能通过如下手段:IPC 机制、管道、信号或文件。就目前所学知识和实验要求而言,使用文件是可行的,有关方法见目录.页眉.页脚“common”下的文件“lock.h”和“lock.c ”。由于程序要生成多个进程,因此还要对作业控制的机制有所了解,这部分内容请参考第九章。如在课堂上所说的,如果哲学家中同时存在左撇子和右撇子,则哲学家问题有解。模拟程序的框架如下所示,其中,函数 lock()和 unlock()的定义见前面所说的文件。定义 5 个文件,分别表示 5 个叉子

17、。其文件名可以按如下方式说明:static char* forks5 = “fork0“, “fork1“, “fork2“, “fork3“, “fork4“;哲学家的行为可以用如下函数描述:void philosopher(int i)while(1) thinking(i, nsecs); / 哲学家 i 思考 nsecs 秒takeFork(i); / 哲学家 i 拿起叉子eating(i, nsecs); / 哲学家 i 进餐 nsecs 秒putFork(i); / 哲学家 i 放下叉子在主程序里,可以用下面的程序段生成 5 个哲学家进程:#define N 5for(i = 0;

18、 i N; i+) pid = fork();if( pid = 0 )philosopher(i);wait(NULL); /* 注意,如果父进程不等待子进程的结束,那么需要终止程序运行时,就只能从控制台删除在后台运行的哲学家进程 */拿起叉子可以如此定义:void takeFork(i)if( i = N - 1 ) lock( forks0 );lock( forksi );else lock( forki );lock( forki + 1 );.页眉.页脚放下叉子可以如此定义:void putFork(i)if( i = N - 1 ) unlock( forks0 );unlock

19、( forksi );else unlock( forki );unlock( forki + 1 );实验五 信号处理本实验要求利用可靠信号机制解决信号处理时可能出现的时间窗口,以及非局部转移等问题,将学习使用 sigaction,alarm, sigpending,sigsetjmp 和siglongjmp 等函数解决在处理信号时遇到的问题。一个方便的开始:在程序清单 1-8 的基础上进行修改。可以直接利用系统 shell(在 cs8 是bash):execl(“/bin/sh”, “sh”, “-c”, buf, (char *) 0);这样程序 sigtest 就具有系统 shell

20、的全部功能。如果命令带“-t”选项,则在创建执行上面函数的子进程之前,必须设置闹钟;在子进程结束之后,必须将闹钟清零。需要处理的信号:因为需要使用闹钟,所以实验需要处理两个信号:SIGALRM 和SIGQUIT。如果当前程序正在执行用户命令,则信号处理函数必须“杀死”用户命令进程:kill(pid, SIGKILL); / pid 为用户命令进程的 ID对于信号 SIGQUIT 还有一种可能:正在接收用户输入的命令串。此时需要放弃当前输入,重新开始接收输入。解决方法可能需要使用非局部转移机制。信号 SIGALRM 和 SIGQUIT 之间嵌套关系的处理如果这两个信号同时发生,或者在处理信号 S

21、IGALRM 时产生信号SIGQUIT(或者反过来) ,那么应当如何处理?无论如何,如果同时存在多个未.页眉.页脚决信号,系统总是一个信号处理完了,再处理下一个信号,但是在处理完全部未决信号之前,不会返回被中断的函数或系统调用。因此合理的处理方法是, 无论上述两个信号哪个先处理,另一个未决信号就应该忽略(清除未决信号) ;在处理其中一个信号时,屏蔽另一个信号(如果发生,就是未决信号) 。static volatile pid_t pid; / 全局变量,存放执行用户命令的子进程的 ID,/ 非 0 表正在执行用户命令设置信号 SIGALRM 处理方式的代码(供参考):struct sigact

22、ion act, oact;act.sa_handler = func; / 信号 SIGALRM 的处理函数sigemptyset(sigaddset( / 在处理信号 SIGALRM 时,屏蔽信号/ SIGQUITact.sa_flags = 0;#ifdef SA_RESTART / 如果定义了该常量,则系统默认不重启,应改为重启act.sa_flags |= SA_RESTART;#endifsigaction(SIGALRM, 信号 SIGQUIT 处理函数的末尾应该包含如下代码段(供参考):sigset_t pendmask;sigemptyset(sigpending( / 获得

23、未决信号集合pid = 0; / 表示当前无正在执行的用户命令alarm(0); / 清除闹钟if(sigmember( / 清除未决信号 SIGALRMsigaction(SIGALRM, / 恢复原来的处理方法实验六 用线程解决“N 个哲学家”问题在这个实验里,待解决的就是如何使用线程描述的问题。由于同一个进程中的所有线程共享相同的内存空间,因此可以使用全局变量表示叉子。但是,为了避免“时间窗口” ,仍然必须使用“原子”操作进行互斥。可以使用在semaphore.h 里说明的信号量完成这个需求,具体细节参考课件和联机文档。模拟程序的框架如下:定义一个全局的信号量指针,表示叉子。可以按如下方

24、式说明:static sem_t *forks;.页眉.页脚哲学家的行为可以用如下函数描述:void* philosopher(void *n)int i = (int) n;while(1) thinking(i, nsecs); / 哲学家 i 思考 nsecs 秒takeFork(i); / 哲学家 i 拿起叉子eating(i, nsecs); / 哲学家 i 进餐 nsecs 秒putFork(i); / 哲学家 i 放下叉子在主程序里,可以用下面的程序段生成 N 个哲学家线程:/* 注意,Linux 系统对线程的处理与进程类似,因此,你能创建的线程数目与进程数目的总和,不能大于系统

25、对你的限制,具体说就是 20 个 */forks = (sem_t *) malloc( N * sizeof(sem_t) ); / 分配 N 个信号量for( i = 0; i N; i+)sem_init(forks + i, 0, 1); / 初始化信号量for(i = 0; i N; i+) status = pthread_create(if( status 0 )/ 错误处理;pthread_join(tid, NULL); /* 我们对线程的返回值不感兴趣,但是若不等待子线程的结束,则主线程的结束将导致整个进程的结束 */拿起叉子可以如此描述:void takeFork(i)if( i = N - 1 ) sem_wait( sem_wait( else sem_wait( sem_wait( 放下叉子可以如此描述:.页眉.页脚void putFork(i)if( i = N - 1 ) sem_post( sem_post( else sem_post( sem_post( 编译写好的程序:假定程序名为:phr_thr.c,按如下命令编译程序 :gcc phr_thr.c error.c lpthread ophr_thr选项“-lpthread”在使用 pthread 线程包的系统时是必须的。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 学术论文 > 毕业论文

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报