首页

上海大学操作系统_实验六

《操作系统》实验报告

实验三 进程管理及进程通信

一. 实验目的

利用Linux 提供的系统调用设计程序,加深对进程概念的理解。 体会系统进程调度的方法和效果。 了解进程之间的通信方式以及各种通信方式的使用。

二. 实验准备

复习操作系统课程中有关进程、进程控制的概念以及进程通信等内容(包括软中断 通信、管道、消息队列、共享内存通信及信号量概念)。 熟悉本《实验指导》第五部分有关进程控制、进程通信的系统调用。它会引导你学 会怎样掌握进程控制。 阅读例程中的程序段。

三. 实验方法

用vi 编写c 程序(假定程序文件名为prog1.c )

编译程序 $ gcc -o prog1.o prog1.c 或 $ cc -o prog1.o prog1.c 运行 $./prog1.o

四. 实验内容及步骤

v i

编写使用系统调用的

C

语言程序。

1. 编写程序。显示进程的有关标识(进程标识、组标识、用户标识等)。经过5 秒

钟 后,执行另一个程序,最后按用户指示(如:Y /N )结束操作。

编译运行结果:

2. 编写程序。实现父进程创建一个子进程。体会子进程与父进程分别获得 不同

返回值,进而执行不同的程序段的方法。

编译运行:

思考:子进程是如何产生的? 又是如何结束的?子进程被创建后它的运行环境是怎样建立的?

答:子进程由fork()函数创建,通过exit()函数自我结束,子进程被创建后核心将为其分配一个进程表项和进程标识符,检查同时运行的进程数目,并且拷贝进程表项的数据,由子进程继承父进程的所有文件。

3. 编写程序。父进程通过循环语句创建若干子进程。探讨进程的家族树以

及子进程继承父进程的资源的关系。 程序如下:

编译运行:

思考:① 画出进程的家族树。子进程的运行环境是怎样建立的?反复运行此程序 看会有什么情况?解释一下。

每一次运行返回的进程号都不相同,但是都符合家族进程树,出现这样的情况是由于系统本身就是随机分配进程号的。 ② 修改程序,使运行结果呈单分支结构,即每个父进程只产生一个子进 程。画出进程树,解释该程序。

用一个break; 语句使父进程在子进程结束后跳出循环,运行结果如下:

进程家族树如下:

4. 编写程序。使用fork( )和exec( )等系统调用创建三个子进程。子进程

分别启动 不同程序,并结束。反复执行该程序,观察运行结果,结束的先后,看是否有不同次序。 编译代码如下:

编译运行:

思考:子进程运行其它程序后,进程运行环境怎样变化的?反复运行此程序看会有什么情况?解释一下。

答:子进程运行其他程序后,这个进程就完全被新程序代替。由于并没有产生新进程所以进程标识号不改变,除此之外的旧进程的其他信息,代码段,数据段,栈段等均被新程序的信息所代替。新程序从自己的main()函数开始进行。反复运行此程序发现结束的先后次序是不可预知的,每次运行结果不一样。原因是当每个子进程运行其他程序时,他们的结束随着其他程序的结束而结束,所以结束的先后次序在改变。

5. 编译程序,验证子进程继承父进程的程序、数据等资源。如用父、子进

程修改 公共变量和私有变量的处理结果;父、子进程的程序区和数据区的位置。

编译源代码如下:

编译运行结果:

思考:子进程被创建后,对父进程的运行环境有影响吗?解释一下。 答:子进程被创建后,对父进程的运行环境无影响,因为当子进程在运行时,他有自己的代码段和数据段,这些都可以作修改,但是父进程的代码段和数据段是不会随着子进程数据段和代码段的改变而改变。

6. 参照《实验指导》第五部分中“管道操作的系统调用”。复习管道通信概

念,编写一个程序。父进程创建两个子进程,父子进程之间利用管道进行通信。要 求能显示父进程、子进程各自的信息,体现通信效果。 源代码:

编译运行:

思考:①什么是管道?进程如何利用它进行通信的?解释一下实现方法。 ②修改睡眠时机、睡眠长度,看看会有什么变化。请解释。 ③加锁、解锁起什么作用?不用它行吗?

答:1. 管道是指能够连接一个写进程和一个读进程,并允许他们以生产者-消费者方式进行通信的一个共享文件,又称pipe 文件。由写进程从管道的入端将数据写入管道,而读进程则从管道出端读出数据来进行通信。 2. 修改睡眠时机和睡眠长度都会引起进程被唤醒的时间不一,因为睡眠时机决定进程在何时睡眠,睡眠长度决定进程何时被唤醒。

3. 加锁、解锁是为了解决临界资源的共享问题。不用它将会引起无法有效管理数据,即数据会被修改导致读错了数据。

7. 编程验证:实现父子进程通过管道进行通信。进一步编程,验证子进程

结束,由父进 程执行撤消进程的操作。测试父进程先于子进程结束时,系统如何处理“孤儿进程” 的。

把上题中的父进程中的wait()都去掉,运行结果如下:

思考:对此作何感想,自己动手试一试?解释一下你的实现方法。 答: 只要在父进程后加上wait()函数,然后打印“子进程已经结束”,一旦子进程结束,父进程撤销进程。把父进程中的wait()去掉,父进程先于子进程终止时. 所有子进程的父进程改变为init 进程,称为进程由init 进程领养。

8. 编写两个程序一个是服务者程序,一个是客户程序。执行两个进程之间

通过消息机制 通信。消息标识MSGKEY 可用常量定义,以便双方都可以利用。客户将自己的进程 标识(pid )通过消息机制发送给服务者进程。服务者进程收到消息后,将自己的进程 号和父进程号发送给客户,然后返回。客户收到后显示服务者的pid 和ppid ,结束。

运行结果: 服务者:

客户程序:

思考:想一下服务者程序和客户程序的通信还有什么方法可以实现?解释一下你 的设想,有兴趣试一试吗。

答:还可以用信号量机制来实现。信号量是一个整形计数器,用来控制

多个进程对共享资源的访问。或者通过消息队列信号机制,通过向消息

队列发送信息、接收信息来实现进程间的通信。

9. 这部分内容涉及《实验指导》第五部分中“有关信号处理的系统调用”。

编程实现软中断信号通信。父进程设定软中断信号处理程序,向子进程发软中断信号。子进程收到信号后执行相应处理程序。 源代码如下:

编译运行:

思考:这就是软中断信号处理,有点儿明白了吧?讨论一下它与硬中断有什么区 别?看来还挺管用,好好利用它。 答:硬中断是由外部硬件产生的,而软中断是CPU 根据软件的某条指令或者软件对标志寄存器的某个标志位的设置而产生的。

10.

怎么样,试一下吗?用信号量机制编写一个解决生产者—

消费者问

题的程序,这可是 受益匪浅的事。本《实验指导》第五部分有关进程通信的系统调用中介绍了信号量机 制的使用。

#include #include #include #include #include #include

#define N 5 // 消费者或者生产者的数目 #define M 10 // 缓冲数目 //int M=10;

int in = 0; // 生产者放置产品的位置 int out = 0; // 消费者取产品的位置

int buff[M] = { 0 }; // 缓冲初始化为0,开始时没有产品

sem_t empty_sem; // 同步信号量,当满了时阻止生产者放产品 sem_t full_sem; // 同步信号量,当没产品时阻止消费者消费

pthread_mutex_t mutex; // 互斥信号量,一次只有一个线程访问缓冲

int product_id = 0; //生产者id int prochase_id = 0; //消费者id //信号处理函数

void Handlesignal(int signo){ printf("程序退出\n",signo); exit(0); }

/* 打印缓冲情况 */ void print() { inti;

printf("产品队列为"); for(i = 0; i

printf("%d", buff[i]); printf("\n"); }

/* 生产者方法 */ void *product() {

int id = ++product_id; while(1) {//重复进行

//用sleep 的数量可以调节生产和消费的速度,便于观察 sleep(2);

sem_wait(&empty_sem);

pthread_mutex_lock(&mutex);

in= in % M;

printf("生产者%d在产品队列中放入第%d个产品\t",id, in);

buff[in]= 1; print(); ++in;

pthread_mutex_unlock(&mutex); sem_post(&full_sem); } }

/* 消费者方法 */ void *prochase() {

intid = ++prochase_id; while(1) {//重复进行

//用sleep 的数量可以调节生产和消费的速度,便于观察 sleep(5);

sem_wait(&full_sem);

pthread_mutex_lock(&mutex);

out= out % M;

printf("消费者%d从产品队列中取出第%d个产品\t",id, out);

buff[out]= 0; print(); ++out;

pthread_mutex_unlock(&mutex); sem_post(&empty_sem); } }

int main() {

printf("生产者和消费者数目都为5, 产品缓冲为10, 生产者每2秒生产一个产品,消费者每5秒消费一个产品,Ctrl+退出程序\n"); pthread_tid1[N]; pthread_tid2[N];

inti;

intret[N]; //结束程序

if(signal(SIGINT,Handlesignal)==SIG_ERR){//按ctrl+C产生SIGINT 信号 printf("信号安装出错\n"); }

// 初始化同步信号量

intini1 = sem_init(&empty_sem, 0, M);//产品队列缓冲同步 intini2 = sem_init(&full_sem, 0, 0);//线程运行同步 if(ini1 && ini2 != 0) {

printf("信号量初始化失败!\n"); exit(1); }

//初始化互斥信号量

intini3 = pthread_mutex_init(&mutex, NULL); if(ini3 != 0) {

printf("线程同步初始化失败!\n"); exit(1); }

// 创建N 个生产者线程 for(i = 0; i

ret[i]= pthread_create(&id1[i], NULL, product, (void *) (&i)); if(ret[i] != 0) {

printf("生产者%d线程创建失败!\n", i); exit(1); } }

//创建N 个消费者线程

for(i = 0; i

ret[i]= pthread_create(&id2[i], NULL, prochase, NULL); if(ret[i] != 0) {

printf("消费者%d线程创建失败!\n", i); exit(1); } }

//等待线程销毁

for(i = 0; i

pthread_join(id1[i], NULL); pthread_join(id2[i],NULL); }

exit(0);

五. 研究并讨论

1. 讨论Linux 系统进程运行的机制和特点,系统通过什么来管理进程?

在Linux 中,每个进程在创建时都会被分配一个数据结构,称为进程控制块(Process Control Block,简称PCB )。PCB 中包含了很多重要的信息,供系统调度和进程本身执行使用。所有进程的PCB 都存放在内核空间中。PCB 中最重要的信息就是进程PID ,内核通过这个PID 来唯一标识一个进程。PID 可以循环使用,最大值是32768。init 进程的pid

为1,其他进程都是init 进程的后代。

除了进程控制块(PCB )以外,每个进程都有独立的内核堆栈(8k ),一个进程描述符结构,这些数据都作为进程的控制信息储存在内核空间中;而进程的用户空间主要存储代码和数据。

Linux 操作系统使用一些系统调用(如:fork()、wait 、exit 等)来实现对进程的管理。

2. C 语言中是如何使用Linux 提供的功能的?用程序及运行结果举例说

明。

C 语言是通过在.c 文件中添加函数调用的.h 文件,来调用进程管理的相关函数,并通过getpid()和getppid ()来查看Linux 系统为每个进程分配的进程号。

如下图:父进程通过循环语句创建若干子进程,输出打印每一个进程的进程号,我们就可以通过进程号画出家族数。

3. 什么是进程?如何产生的?举例说明。

进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

引起进程创建的事件有用户登录、作业调度、提供服务和应用请求。一旦操作系统发现了要求创建新进程的事件后,便调用进程创建原语Creat( )按下述步骤创建一个新进程。申请空白PCB 、为新进程分配资源、初始化进程控制块、将新进程插入就绪队列

例如用户登入,在分时系统中,用户在终端键入登录命令后,如果是合法用户,系统将为该终端建立一个进程,并把它插入就绪队列中。

4. 进程控制如何实现的?举例说明。

进程控制一般是由OS 的内核中的原语来实现的。 例子:唤醒原语wakeup( )

当被阻塞进程所期待的事件出现时,如I/O完成或其所期待的数

据已经到达,则由有关进程(比如用完并释放了该I/O设备的进程) 调用唤醒原语wakeup( ),将等待该事件的进程唤醒。唤醒原语执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB 中的现行状态由阻塞改为就绪,然后再将该PCB 插入到就绪队列中。 5. 进程通信方式各有什么特点?用程序及运行结果举例说明。

进程间通信可分为4种形式:

(1) 主从式:① 主进程可自由地使用从进程的资源或数据;② 从进程的动作受主进程的控制;③ 主进程和从进程的关系是固定的。

(2) 会话式:① 使用进程在使用服务进程所提供的服务之前,必须得到服务进程的许可;② 服务进程根据使用进程的要求提供服务,但对所提供服务的控制由服务进程自身完成。③ 使用进程和服务进程在通信时有固定连接关系。

(3) 消息或邮箱机制:①

只要存在空缓冲区或邮箱,发送进程就可以

发送消息。② 与会话系统不同,发送进程和接收进程之间无直接连接关系,接收进程可能在收到某个发送进程发来的消息之后,又转去接收另一个发送进程发来的消息。③ 发送进程和接收进程之间存在缓冲区或邮箱用来存放被传送消息。

(4) 共享存储区方式: ①诸进程可通过对共享存储区中数据的读或写来实现通信。②进程在通信前,先向系统申请获得共享存储区中的一个分区,并指定该分区的关键字;若系统已经给其他进程分配了这样的分区,则将该分区的描述符返回给申请者。③由申请者把获得的共享存储分区连接到本进程上。④可像读、写普通存储器一样地读、写该公用存储分区。 例子:以下是管道通信的程序说明和代码实现:父进程创建两个子进程,父子进程之间利用管道进行通信。

程序运行结果:

6. 管道通信如何实现?该通信方式可以用在何处?

向管道(共享文件) 提供输入的发送进程(即写进程) ,以字符流形式将大量的数据送入管道;而接受管道输出的接收进程(即读进程) ,则从管道中接收(读) 数据。

这种通信方式用于数据传输、资源共享和事件通知。

7. 什么是软中断?软中断信号通信如何实现?

软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。

利用signal 和kill 实现软中断通信:

kill(pid,signal): 向进程pid 发送信号signal ,若pid 进程在可中断的优先级(低优先级)上睡眠,则将其唤醒。

signal(sig,ps): 设置sig 号软中断信号的处理方式;

三种处理方式:SIG_DFL:系统默认方式,一般是终止进程; SIG_IGN:忽略(屏蔽);

func(): 用制定义函数func()处理。

Signal设置的处理方式,仅一次有效,处理后即回到默认方式。 9(SIGKILL) 号软中断不允许设置。

六. 体会

在本次进程管理和进程通信的实验中,我学会了用vim 文本编译器编写C 语言

实现进程的管理和进程之间的通信,并编译和运行工程文件。掌握了如何创建子进程,理解了子进程与父进程之间的关系,实现管道通信和软中断信号通信,对一些必要的基础函数的调用能够识别和运用。

实验六 SHELL 编程

一. 实验目的

掌握vi 的三种工作方式,熟悉vi 编辑程序的使用。 学习Shell 程序设计方法。掌握编程要领。

二. 实验准备

复习操作系统课程相关的用户接口概念。 熟悉本《实验指导》第三、四部分。

三. 实验内容

学习使用vi 编辑程序。

编写Shell 程序。

将程序文件设置为可执行文件(用chmod 命令)。 在命令行方式中运行Shell 程序。

四. 实验步骤

1. 按本《实验指导》第三部分的内容。熟悉vi 的三种工作方式。熟悉使用各种编辑功能。

思考:试一试vi 的三种工作方式各用在何时?用什么命令进入插入方式?怎样退出插入方式?文件怎样存盘?注意存盘后的提示信息。

① 三种工作方式的使用时机: 插入方式

进入插入方式,屏幕下方有一行 “------Insert------”字样表示。需要输入文本的内容时用到插入方式,可以用退格键来纠正错误。在插入方式中按一下,即退 出插入方式,进入转义命令方式。

转义命令方式

刚进入 vi 或退出插入方式,即为转义命令方式。这时键入的任何字符转义为特殊功能,如:移动、删除、替换等。大多数转义命令由一个或两个字母组成,操作时没有提示符,而且输入命令不需要按。

末行命令方式

在转义命令方式中,按冒号“:”进入末行命令方式。屏幕最末一行的行首显示冒号作为命令提示。命令行输入后按开始执行。此时用户可进行文件的全局操作,如:全局查找、替换、文件读、写等。

② 用什么命令进入插入方式: 用户输入命令 功能描述

i text 在光标前插入新文本,ENTER 可重起一行 I text 在当前行起始处插入新文本 a text 在光标后输入新的文本 A text 在当前行末尾输入新的文本 o text 在当前行下产生新的一行并输入文本 O text 在当前行上产生新的一行并输入文本 ③ 怎样退出插入方式:

在插入方式中按一下,即退出插入方式,进入转义命令方式。

④ 文件怎样存盘:

存盘过程只需键 入命令: :w

命令 w 将文件以当前名字存入磁盘,并覆盖了文件先前的副本。但 w 命令不影响缓冲区的内容。

如果要将文件以不同的名字保存,例如 newfile ,需键入命令: :w newfile

如果不存在名为 newfile 的文件,vi 编辑器将存入文件并给出文件的大小。如果这个文件已经存在,那么 vi 会通知你,但并不刷新文件的内容。如果想刷新已存在的 文件的内容,则可键入命令:

:w !newfile 同样,感叹号“!”放弃了标准的 vi 防止覆盖文件的保护功能,强制刷新。

2. 创建和执行Shell 程序

用前面介绍的Vi 或其他文本编辑器编写Shell 程序,并将文件以文本文件方式保存在相应的目录中。

用chmod 将文件的权限设置为可执行模式,如若文件名为shdemo.h, 则命令如下: $ chmod 755 shdemo.h (文件主可读、写、执行,同组人和其他人可读和执行) 在提示符后执行Shell

程序:

$ shdemo.h (直接键入程序文件名执行) 或 $ sh shdemo.h (执行Shell 程序)

或 $ .shdemo.h (没有设置权限时可用点号引导) 用vim 编译程序:

运行结果如下:

3. 用vi 编写《实验指导》“第四部分Shell 程序设计”中的例1 (假设文件为prog1.h ), 练习内部变量和位置参数的用法。 用vim 编译程序如下:

用chmod 将文件的权限设置为可执行模式,并在提示符后键入命令行: $./prog1. #没有参数 或 $sh prog1.

屏幕显示: Name not provided 在提示符后键入命令行:

$./prog1.h Theodore #有一个参数 屏幕显示:

Your name is Theodore #引用$1 参数的效果 运行结果如下:

4. 进一步修改程序prog1.h ,要求显示参数个数、程序名字,并逐个显示参数。 修改后的程序如下:

运行如下:

5. 修改例1程序(即上面的 prog1.h ),用read 命令接受键盘输入。若没有输入显示第一种 提示,否则第二种提示。

修改后的程序如下:

运行如下:

6. 用vi 编写《实验指导》“第四部分 Shell 程序设计”中的例2、例3,练习字符串比较运算符、数据比较运算符和文件运算符的用法,观察运行结果。 例2的运行结果如下:

例 3:设当前目录下有一个文件 filea ,控制权为(~ r – x r – x r – x 即 0751), 有一个子目录 cppdir ,控制权为(d r w x r w x r w x)程序 compare2.h 中有 如下内容:

运行结果:

7. 修改例2程序,使在程序运行中能随机输入字符串,然后进行字符串比较。 编译程序:

运行结果:

8. 修改例3程序,使在程序运行中能随机输入文件名,然后进行文件属性判断。

运行结果:

9. 用vi 编写《实验指导》“第四部分 Shell 程序设计”中的例4、例5、例6、

例7,掌握控制语句的用法,观察运行结果。

例4:

运行结果:

例5:

运行结果:

例6:

运行结果:

例7:

运行结果:

10. 用vi 编写《实验指导》“第四部分 Shell 程序设计”中的例8 及例9掌握条件语句的用 法,函数的用法,观察运行结果。 例8:

运行结果:

例9:

运行结果:

11. 编程,在屏幕上显示用户主目录名(HOME )、命令搜索路径(PATH ),并显示由位置参数指定的文件的类型和操作权限。

运行结果:

.

思考:到此为止你对Shell 有所认识了吧?怎么样?自己再编两个程序:

① 个批处理程序,体会一下批处理概念。

运 行结果:

② 做个菜单,显示系统环境参数。将此程序设置为人人可用。

运行结果:

五. 讨论

1. Linux 的Shell 有什么特点?

Shell 编程语言具有普通编程语言的很多特点,比如它也有循环结构和分支控制结构等,用这种编程语言编写的Shell 程序与其他应用程序具有同样的效果。 2. 怎样进行Shell 编程?如何运行?有什么条件?

shell

程序就是一个包含若干行shell 或者linux 命令的文件, 像编写高级语言的程序一样,编写一个shell 程序需要一个文本编辑器,如vi 和vim 等。在文本编辑环境下,依据shell 的语法规则,输入一些shell/linux命令行,形成一个完整的程序文件。用chmod 将文件的权限设置为可执行模式,如若文件名为shdemo.h, 则命令如下: $ chmod 755 shdemo.h (文件主可读、写、执行,同组人和其他人可读和执行) 在提示符后执行Shell

程序:

$ shdemo.h (直接键入程序文件名执行) 或 $ sh shdemo.h (执行Shell 程序)

或 $ .shdemo.h (没有设置权限时可用点号引导)

3. vi 编辑程序有几种工作方式?查找有关的详细资料,熟练掌握屏幕编辑方式、转移 命

令方式以及末行命令的操作。学习搜索、替换字符、字和行,行的复制、移动, 以及在vi 中执行Shell 命令的方式。 1. 命令行模式

我们在shell 环境(提示符为$)下输入启动Vi 命令,进入编辑器时,就是处于该模式下。在该模式下,用户可以输入各种合法的Vi 命令,用于管理自己的文档。此时从键盘上输入的任何字符都被当做编辑命令来解释,若输入的字符是合法的vi 命令,则vi 在接受用户命令之后完成相应的动作。但需注意的是,所输入的命令并不在屏幕上显示出来。若输入的字符不是vi 的合法命令,vi 会响铃报警。 2. 文本输入模式

在命令模式下输入插入命令i 、附加命令a 、打开命令o 、修改命令c 、取代命令r 或替换命令s 都可以进入文本输入模式。在该模式下,用户输入的任何字符都被vi 当做文件内容保存起来,并将其显示在屏幕上。在文本输入过程中,若想回到命令模式下,按Esc 键即可。 3. 末行模式

Vi 有一个专门的“转义”命令,可访问很多面向行的Ex 命令。在命令模式下,用户按“:”键即可进入末行模式下,此时Vi 会在显示窗口的最后一行(通常也是屏幕的最后一行)显示一个“:”作为末行模式的提示符,等待用户输入命令。多数文件管理命令都是在此模式下执行的(如把编辑缓冲区的内容写到文件中等)。末行命令执行完后,vi 自动回到命令模式。

4. 编写一个具有以下功能的Shell 程序。

(1) 把当前目录下的文件目录信息输出到文件 filedir.txt 中; 运行结果:

(2) 在当前目录下建立一个子目录,目录名为 testdir2 ; 运行结果:

(3) 把当前目录下的所有扩展名为 c 的文件以原文件名复制到子目录testdir2中; 运行结果:

(4) 把子目录中的所有文件的存取权限改为不可读。(提示:用 for 循环控制语句实 现,循环的控制列表用 ’ls’ 产生。)

(5) 在把子目录 testdir2 中所有文件的目录信息追加到文件 filedir.txt 中;

(6) 把你的用户信息追加到文件 filedir.txt 中;

(7) 分屏显示文件 filedir.txt

编程如下:

六. 体会:

在本次用shell 编程实验中,我在实验三的基础上进一步的掌握了使用vim 编辑

程序。使用vim 编写Shell 程序,将程序文件设置为可执行文件(用chmod 命令),然后在命令行方式中运行Shell 程序。其实shell 程序就是一个包含若干行shell 或者linux 命令的文件, 像编写高级语言的程序一样。我掌握了字符串比较运算符、数据比较运算符和文件运算符的用法,修改文件权限,显示文件属性等等操作。