您的当前位置:首页操作系统实验(实验2-5)

操作系统实验(实验2-5)

来源:小侦探旅游网
实验二

进入VI编辑器 格式:vi 文件名 例 :vi sy.c

Vi编辑器三种工作方式:

1.编辑方式:进入VI 处于编辑方式

2.文本输入方式:在编辑方式下输入a ,进入追加方式,输入i,进入插入方式 3. 命令方式:在输入方式下,按Esc 键,由文本输入转向编辑方式,输入冒号:进入命令方式 4.退出vi : wq写文件退出

:w wenjianming 写文件 : q! 不写退出 :wq! 写退出 编译c文件

Gcc -o wenjianming.out wenjianming.c 运行文件:

./wenjianming.out

1. 实验内容和目的

用vi编辑器编辑下列文件,使用gcc编译器和gdb调试器,对下列程序编译运行,分析运行结果。要求至少完成3个程序。 2.程序示例

(1) /* 父子进程之间的同步之例 */ #include main( ) {

int pid1;

if(pid1=fork()) /*create child1 */

{ if (fork()) /*create the child2*/

{printf (“parent’s context.\\n”);

printf(“parent is waiting the child1 terminate.\\n); wait(0);

printf(“parent is waiting the child2 terminate.\\n”); wait(0);

printf(“parent terminate.\\n”); exit(0); } else

/* child2*/

printf(“child2’s context.\\n”);

sleep(5);

printf(“ child2 terminate.\\n”); exit(0); }

else { if(pid1==0)/* child1 */ { printf(“child1’s context.\\n”); sleep(10);

printf(“child1 terminate.\\n”); exit(0); } } }

分析: 上述程序是父进程首先创建一个子进程,若成功,再创建另一个子进程,之后三个进程并发执行。究竟谁先执行,是随机的,可根据执行结果判断。试分析该程序的所有运行结果。

注释: fork( ) 调用正确完成时,给父进程返回地是被创建子进程的标识,给子进程返回的是0;创建失败时,返回给父进程的时-1; exit(0) 进程终止自己

wait(0) 父进程同步等待子进程结束,即无子进程结束,父进程等待。 (2)管道通信机制

通过使用管道实现两个和多个进程之间的通信。所谓管道,就是将一个进程的标准输出与另一个进程的标准输入联系在一起,进行通信的一种方法。同组进程之间可用无名管道进行通信,不同组进程可通过有名管道通信。

使用无名管道进行父子进程之间的通信 #include #include #include int pipe( int filedes[2]);

char parent[]=”a message to pipe’ communication.\\n”; main()

{ int pid,chan1[2]; char buf[100]; pipe(chan1); pid=fork(); if(pid<0)

{ printf(“to create child error\\n”); exit(1); }

if(pid>0)

{ close(chan1[0]); /*父进程关闭读通道*/

printf(“parent process sends a message to child.\\n”); write(chan1[1],parent,sizeof(parent)); close(chan1[1]);

printf(“parent process waits the child to terminate.\\n”);

wait(0);

printf(“parent process terminates.\\n”); } else{

close(chan1[1]);/*子进程关闭写通道*/ read(chan1[0],buf,100);

printf(“the message read by child process form parent is %s.\\n”,buf); close (chan1[0]);

printf(“child process terminates\\n”); } }

观察运行结果。

注释:pipe( int filedes[2]):创建一个无名管道,filedes[0]为读通道,filedes[1]为写通道。 (3)Linux中的多线程编程threads.c #include #include #include #include #define MAX 10

pthread_t thread[2]; pthread_mutex_t mut; int number=0, i;

void *thread1() {

printf (\"thread1 : I'm thread 1\\n\");

for (i = 0; i < MAX; i++) {

printf(\"thread1 : number = %d\\n\ pthread_mutex_lock(&mut); number++;

pthread_mutex_unlock(&mut); sleep(2); }

printf(\"thread1 :主函数在等我完成任务吗?\\n\"); pthread_exit(NULL); }

void *thread2() {

printf(\"thread2 : I'm thread 2\\n\");

for (i = 0; i < MAX; i++)

{

printf(\"thread2 : number = %d\\n\ pthread_mutex_lock(&mut); number++;

pthread_mutex_unlock(&mut); sleep(3); }

printf(\"thread2 :主函数在等我完成任务吗?\\n\"); pthread_exit(NULL); }

void thread_create(void) {

int temp;

memset(&thread, 0, sizeof(thread)); //comment1 /*创建线程*/

if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) //comment2

printf(\"线程1创建失败!\\n\"); else

printf(\"线程1被创建\\n\");

if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) //comment3

printf(\"线程2创建失败\"); else

printf(\"线程2被创建\\n\"); }

void thread_wait(void) {

/*等待线程结束*/

if(thread[0] !=0) { //comment4 pthread_join(thread[0],NULL); printf(\"线程1已经结束\\n\"); }

if(thread[1] !=0) { //comment5 pthread_join(thread[1],NULL); printf(\"线程2已经结束\\n\"); } }

int main()

{

/*用默认属性初始化互斥锁*/ pthread_mutex_init(&mut,NULL);

printf(\"我是主函数哦,我正在创建线程,呵呵\\n\"); thread_create();

printf(\"我是主函数哦,我正在等待线程完成任务阿,呵呵\\n\"); thread_wait();

return 0; }

3. 注意:Gcc –lpthread –o thread.out thread.c

线程相关操作 1) pthread_t

pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义: typedef unsigned long int pthread_t; 它是一个线程的标识符。 2)pthread_create

函数pthread_create用来创建一个线程,它的原型为:

extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr, void *(*__start_routine) (void *), void *__arg));

第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。 3)pthread_join pthread_exit

函数pthread_join用来等待一个线程的结束。函数原型为:

extern int pthread_join __P ((pthread_t __th, void **__thread_return));

第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:

extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给 thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。 在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数pthread_create,

pthread_join和pthread_exit。下面,我们来了解线程的一些常用属性以及如何设置这些属性。 互斥锁相关

互斥锁用来保证一段时间内只有一个线程在执行一段代码。 1) pthread_mutex_init

函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数 pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数 pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值, PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、 PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上锁、解锁机制,一般情况下,选用最后一个默认属性。

2) pthread_mutex_lock pthread_mutex_unlock pthread_delay_np pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。 注意:

1)需要说明的是,上面的两处sleep不光是为了演示的需要,也是为了让线程睡眠一段时间,让线程释放互斥锁,等待另一个线程使用此锁。

2)请千万要注意里头的注释comment1-5,如果没有comment1和comment4,comment5,将导致在pthread_join的时候出现段错误,另外,上面的comment2和comment3是根源所在,所以千万要记得写全代码。因为上面的线程可能没有创建成功,导致下面不可能等到那个线程结束,而在用pthread_join的时候出现段错误(访问了未知的内存区)。另外,在使用memset的时候,需要包含string.h头文件。 实验报告

(1) 实验题目。

(2) 说明对程序的理解,程序后加上注释语句。 (3) 分析程序的执行结果并加以解释。

实验三

1.实验内容与目的

熟悉有关文件的系统调用,学习文件系统的系统调用命令,提高对文件系统实现功能的理解和掌握。使用creat open read write 等系统调用用C语言编程实现复制文件。 2.注释

(1) Int creat(const char *pathname, mode_t mode);

返回值:如果正确创建,返回文件的描述符;否则返回-1;

Pathname是要创建文件的路径名。创建文件时,文件只能以只写方式打开

Mode 用来规定该文件的拥有者,小组用户和其他用户的访问权限,要求用按位逻辑加对下

列符号常量进行所需的组合(同open函数)。 (2) int open(const char *pathname, int flags);

int open(const char *pathname, int flags, mode_t mode); int close(int fd);

open函数有两个形式.其中pathname是我们要打开的文件名(包含路径名称,缺省是认为在当前路径下面).flags可以去下面的一个值或者是几个值的组合. O_RDONLY:以只读的方式打开文件. O_WRONLY:以只写的方式打开文件. O_RDWR:以读写的方式打开文件. O_APPEND:以追加的方式打开文件. O_CREAT:创建一个文件.

O_EXEC:如果使用了O_CREAT而且文件已经存在,就会发生一个错误. O_NOBLOCK:以非阻塞的方式打开一个文件. O_TRUNC:如果文件已经存在,则删除文件的内容.

前面三个标志只能使用任意的一个.如果使用了O_CREATE标志,那么我们要使用open的第二种形式.还要指定mode标志,用来表示文件的访问权限.mode可以是以下情况的组合. ----------------------------------------------------------------- S_IRUSR 用户可以读 S_IWUSR 用户可以写

S_IXUSR 用户可以执行 S_IRWXU 用户可以读写执行 ----------------------------------------------------------------- S_IRGRP 组可以读 S_IWGRP 组可以写

S_IXGRP 组可以执行 S_IRWXG 组可以读写执行 ----------------------------------------------------------------- S_IROTH 其他人可以读 S_IWOTH 其他人可以写

S_IXOTH 其他人可以执行 S_IRWXO 其他人可以读写执行 ----------------------------------------------------------------- S_ISUID 设置用户执行ID S_ISGID 设置组的执行ID -----------------------------------------------------------------

我们也可以用数字来代表各个位的标志.Linux总共用5个数字来表示文件的各种权限. 00000.第一位表示设置用户ID.第二位表示设置组ID,第三位表示用户自己的权限位,第四位表示组的权限,最后一位表示其他人的权限.

每个数字可以取1(执行权限),2(写权限),4(读权限),0(什么也没有)或者是这几个值的和. 比如我们要创建一个用户读写执行,组没有权限,其他人读执行的文件.设置用户ID位那么我们可以使用的模式是--1(设置用户ID)0(组没有设置)7(1+2+4)0(没有权限,使用缺省)5(1+4)即10705:

open(\"temp\T,10705);

如果我们打开文件成功,open会返回一个文件描述符.我们以后对文件的所有操作就可以对这个文件描述符进行操作了.

当我们操作完成以后,我们要关闭文件了,只要调用close就可以了,其中fd是我们要关闭的文件描述符.

(3)ssize_t read(int fd, void *buffer, size_t count); ssize_t write(int fd, const void *buffer, size_t count);

fd是我们要进行读写操作的文件描述符,buffer是我们要写入文件内容或读出文件内容的内存地址.count是我们要读写的字节数.

对于普通的文件read从指定的文件(fd)中读取count字节到buffer缓冲区中(记住我们必须提供一个足够大的缓冲区),同时返回count.

如果read读到了文件的结尾或者被一个信号所中断,返回值会小于count.如果是由信号中断引起返回,而且没有返回数据,read会返回-1,且设置errno为EINTR.当程序读到了文件结尾的时候,read会返回0.

write从buffer中写count字节到文件fd中,成功时返回实际所写的字节数. 可能用到的头文件

#include #include #include #include #include

4. 实验报告

(1) 实验题目。

(2) 采用的数据结构及符号说明。

(3) 打印一份源程序清单,并附加流程图与注释。 (4) 分析文件系统中常用的系统调用。

实验四 模拟内存管理程序(4学时) 1、 实验目的

了解简单的固定大小内存分配方法,掌握分区存储管理技术,了解在分区管理机制下所需的数据结构。

2、 实验内容

1)、 将1024K内存按如下块大小分成十个内存块。

内存块号 内存块大小 起始地址 内存块状态 1 512 10 NO 2 256 522 NO 3 256 778 NO 4 128 1034 NO 5 128 1162 NO 6 128 1290 NO 7 32 1418 NO 8 32 1450 NO 9 16 1482 NO 10 16 1498 NO

其中,在内存状态中用NO代表该内存块未被分配;用进程名代表该内存块已被分配。

2)、 编制模拟内存管理程序,根据调入内存的进程大小分别采用最先适应法和最佳适应法分配内存块。最佳适应法,如有一个12K的程序被运行时,它应该被分配到内存块号9,若有200K的程序运行时,应将其调入内存块号2。最先适应法,则是按内存块号的顺序,依次放入各进程名。有关编程请仔细阅读后

面的参考流程。

3、 实验要求

1)、 要求通过键盘输入若干进程名称和程序所占内存空间的大小,把这些进程分配到内存表中,显示内存分配情况。

2)、 编制程序可以循环输入,输入某进程结束或某进程添加进来、进程占用存储空间的大小,并显示内存分配情况。 3)、 当没有区间存放程序时,应有提示。 4)、 所编写的程序,应有退出功能。

5)、 每添加、结束一个进程,应有输出显示,输出显示的格式如下:

内存块号 内存块大小 起始地址 进程名(内存状态) 1 512 10 A1 2 256 522 NO 3 256 778 A2 4 128 1034 A3 5 128 1162 NO 6 128 1290 NO 7 32 1418 NO 8 32 1450 NO 9 16 1482 NO 10 16 1498 NO 4、实验报告格式要求

1)、实验题目

2)、程序所采用的数据结构、符号说明,及采用哪一种内存分配方法 3)、写出源程序清单,要求附加流程图与注释 4)、写出内存分配的变化情况

实验五 进程管理设计

一. 目的和要求

进程调度是处理机管理的核心内容。本实验要求用C语言编写和调试一个简单的进程调度程序。通过本实验可以加深理解有关进程控制块、进程队列的概念,并体会和了解优先数和时间片轮转调度算法的具体实施办法。

二. 实验内容

1. 设计进程控制块PCB表结构,分别适用于优先数调度算法和循环轮转算法。

PCB结构通常包括以下信息:进程名、进程优先数(或轮转时间片)、进程所占用的CPU时间、进程的状态、当前队列指针等。根据调度算法的不同,PCB结构的内容可以做适当的删除。

2. 建立进程就绪队列,对两种不同算法编制入链子程序。

3. 编制两种进程调度算法:1)优先数调度;2)循环轮转调度。

三. 实验环境

1. IBM 286以上微型计算机及其兼容机。 2. DOS系统要求3.3及更高的版本。 3. TURBO C 2.0。

四. 实验要求

本实验要求用C 语言编写,选用优先数算法和简单时间片轮转法对五个进程进行调度,每个进程可以有三种状态:运行状态(RUN)、就绪状态(READY)和完成状态(FINISH)。并假定初始状态为就绪状态。

五. 模拟算法提示

1.数据结构

设计进程控制块结构如下: PCB: NAME PRIO/ROUND CPUTIME COUNT NEEDTIME STATE NEXT 其中: NAME——进程标识符 PRIO——进程优先数 ROUND——进程轮转时间片 CPUTIME——进程占用CPU时间 COUNT——计数器 NEEDTIME——进程完成还要的CPU时间 STATE——进程的状态 NEXT——链指针

进程控制块链结构中主要指针如下: RUN——当前运行进程指针 READY——就绪队列头指针 TAIL——就绪队列尾指针 FINISH——完成队列头指针 为了方便处理,程序中进程的运行时间以时间片为单位计算。各进程的优先数或轮转时间片数以及进程需运行的时间片数的初值均由用户给定。 2.程序说明

1)在优先数算法中,进程每执行一次,优先数减3,CPU时间片数加1,进程还需要的时间片数减1。在轮转法中,采用固定时间片,时间片数为2,进程每次执行一次,CPU时间片加2,进程还需要的时间片数减2,并排列就绪队列的尾上。

2) 程序结构说明如下:

整个程序由FIRSTIN,INSERT1,INSERT2,PRINT,CREAT,PRISCH和ROUNDSCH

函数组成。其中: INSERT1的功能是把还未完成且优先数小于别的进程的进程PCB按进程优先数的顺序插入到就绪队列中。 INSERT2是轮转法使用的函数,将执行了一个单位时间片数(为2)且还未完成的进程的PCB插到就绪队列的队尾。 FIRSTIN 的功能是将就绪队列中的第一个进程投入运行。 PRINT打印每执行一个时间片后的所有进程的状态,这里,就绪(等待)用“W”代表。 CREATE 的功能是创建新的进程,即创立进程的PCB,并将此PCB链入到就绪队列中去。 PRISCH按优先数算法调度进程。 ROUNDSCH按时间片轮转法调度进程。 主程序中定义了PCB 的结构和其它变量:number——进程数,algo为10个字符长的字符串,存放要求输入的算法的名,PRIORITY 为优先数算法,ROUNDROBIN为循环轮转法,在程序运行时输入其中的一个。 3.主要算法

算法 PRISCH

{ while (当前运行进程不为空) {

进程占用CPU 时间加1 进程到完成还要的CPU时间减1 进程优先数减3

if(进程到完成还要的CPU时间为0) { 将该进程插入到完成队列中 修改该进程状态 当前运行进程指针为空 if(就绪队列不为空) FIRSTIN函数 }

else if (就绪队列不为空且当前进程优先数小于就绪队列中第一

个进程的优先数)

{ 修改当前进程状态为W INSERT1函数 FIRSTIN函数 } PRINT函数 } }

算法 ROUNDSCH

{ while (当前运行进程不为空)

{

进程占用CPU 时间加1 进程到完成还要的CPU时间减1 进程计数器加1

if(进程到完成还要的CPU时间为0) { 将该进程插入到完成队列中 修改该进程状态 当前运行进程指针为空 if(就绪队列不为空) FIRSTIN过程 }

else if (进程计数器等于进程轮转时间片) { 修改当前进程计数器为0 if(就绪队列不为空) { 修改当前进程状态为W INSERT2过程 FIRSTIN过程 } } PRINT过程 } }

六. 实验报告

1. 实验题目。

2. 采用的数据结构及符号说明。

3. 打印一份源程序清单,并附加流程图与注释。 4. 打印出两种算法的执行结果。

5. 比较两种算法的优缺点并分析实验过程中遇到的问题,谈谈实验后的体会。

因篇幅问题不能全部显示,请点此查看更多更全内容