操作系统实验实验1课案.docx
《操作系统实验实验1课案.docx》由会员分享,可在线阅读,更多相关《操作系统实验实验1课案.docx(29页珍藏版)》请在冰豆网上搜索。
![操作系统实验实验1课案.docx](https://file1.bdocx.com/fileroot1/2023-1/30/107f47ea-012b-4c6c-bb73-473c4c71c93b/107f47ea-012b-4c6c-bb73-473c4c71c93b1.gif)
操作系统实验实验1课案
广州大学学生实验报告
开课学院及实验室:
计算机科学与工程实验室2015年11月11日
实验课程名称
操作系统
成绩
实验项目名称
进程管理与进程通信
指导老师
陈康民
(***报告只能为文字和图片,老师评语将添加到此处,学生请勿作答***)
进程管理
(一)进程的创建实验
一、实验目的
1、掌握进程的概念,明确进程的含义
2、认识并了解并发执行的实质
二、实验内容
1、编写一段程序,使用系统调用fork()创建两个子进程。
当此程序运行时,在系统中有一个父进程和两个子进程活动。
让每一个进程在屏幕上显示一个字符:
父进程显示'a',子进程分别显示字符'b'和字符'c'。
试观察记录屏幕上的显示结果,并分析原因。
2、修改上述程序,每一个进程循环显示一句话。
子进程显示'daughter…'及'son……',父进程显示'parent……',观察结果,分析原因。
三、实验步骤
1、编写一段程序,使用系统调用fork()创建两个子进程。
代码:
#include
main()
{
intp1,p2;
while((p1=fork())==-1);/*创建子进程p1*/
if(p1==0)putchar('b');
else
{
while((p2=fork())==-1);/*创建子进程p2*/
if(p2==0)putchar('c');
elseputchar('a');
}
}
运行结果:
bca,bac,abc,……都有可能。
2、修改上述程序,每一个进程循环显示一句话。
子进程显示'daughter…'及'son……',父进程显示'parent……',观察结果,分析原因。
代码:
#include
main()
{
intp1,p2,i;
while((p1=fork())==-1);/*创建子进程p1*/
if(p1==0)
for(i=0;i<10;i++)
printf("daughter%d\n",i);
else
{
while((p2=fork())==-1);/*创建子进程p2*/
if(p2==0)
for(i=0;i<10;i++)
printf("son%d\n",i);
else
for(i=0;i<10;i++)
printf("parent%d\n",i);
}
}
结果:
parent…
son…
daughter..
daughter..
或parent…
son…
parent…
daughter…等
四、分析原因
除strace外,也可用ltrace-f-i-S./executable-file-name查看以上程序执行过程。
1、从进程并发执行来看,各种情况都有可能。
上面的三个进程没有同步措施,所以父进程与子进程的输出内容会叠加在一起。
输出次序带有随机性。
2、由于函数printf()在输出字符串时不会被中断,因此,字符串内部字符顺序输出不变。
但由于进程并发执行的调度顺序和父子进程抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化。
这与打印单字符的结果相同。
五、思考题
(1)系统是怎样创建进程的?
linux系统创建进程都是用fork()系统调用创建子进程。
Init程序以/etc/inittab为脚本文件来创建系统的新进程的。
新进程还克以创建新进程。
创建新进程是通过克隆老进程或当前进程来创建的。
新进程的创建实用系统调用sys_fork()或sys_clone(),并且在内核模式下完成。
在系统调用结束时,系统从物理内存中分配一个心的task_struct()数据结构和进程堆栈,同时获得一个唯一的标示此进程的标识符。
通过FORK函数调用促使内核创建一个新的进程,该进程是调用进程的一个精确的副本。
新创建的进程为子进程,调用的FORK进程为父进程,而且创建出来的是两个副本,即,两个进程,但他们互不干扰。
(2)当首次调用新创建进程时,其入口在哪里?
首次创建进程时,入口在进程1那里。
(二)进程的控制实验
一、实验目的
1、掌握进程另外的创建方法
2、熟悉进程的睡眠、同步、撤消等进程控制方法
二、实验内容
1、用fork()创建一个进程,再调用exec()用新的程序替换该子进程的内容
2、利用wait()来控制进程执行顺序
三、参考程序
#include
#include
main()
{
intpid;
pid=fork();/*创建子进程*/
switch(pid)
{
case-1:
/*创建失败*/
printf("forkfail!
\n");
exit
(1);
case0:
/*子进程*/
execl("/bin/ls","ls","-1","-color",NULL);
printf("execfail!
\n");
exit
(1);
default:
/*父进程*/
wait(NULL);/*同步*/
printf("lscompleted!
\n");
exit(0);
}
}
4、运行结果
5、思考
(1)可执行文件加载时进行了哪些处理?
解:
可执行文件加载时首先是创建一个新进程的fork系统调用,然后用于实现进程自我终止的exit系统调用;改变进程原有代码的exec系统调用;用于将调用进程挂起并等待子进程终止的wait系统调用;获得进程标识符的getpid系统调用等处理过程。
(2)什么是进程同步?
wait()是如何实现进程同步的?
解:
进程同步是指对多个相关进程在执行次序上进行协调,以使并发执行的主进程之间有效地共享资源和相互合作,从而使程序的执行具有可在现行。
首先程序在调用fork()机那里了一个子进程后,马上调用wait(),使父进程在子进程调用之前一直处于睡眠状态,这样就使子进程先运行,子进程运行exec()装入命令后,然后调用wait(0),使子进程和父进程并发执行,实现进程同步。
(三)进程互斥实验
实验目的
1、进一步认识并发执行的实质
2、分析进程竞争资源的现象,学习解决进程互斥的方法
实验内容
1、修改实验
(一)中的程序2,用lockf()来给每一个进程加锁,以实现进程之间的互斥
2、观察并分析出现的现象
#include
#include
main( )
{
intp1,p2,i;
while((p1=fork())==-1);/*创建子进程p1*/
if(p1==0)
{
lockf(1,1,0);/*加锁,这里第一个参数为stdout(标准输出设备的描述符)*/
for(i=0;i<10;i++)
printf("daughter%d\n",i);
lockf(1,0,0);/*解锁*/
}
else
{
while((p2=fork())==-1);/*创建子进程p2*/
if(p2==0)
{
lockf(1,1,0);/*加锁*/
for(i=0;i<10;i++)
printf("son%d\n",i);
lockf(1,0,0);/*解锁*/
}
else
{
lockf(1,1,0);/*加锁*/
for(i=0;i<10;i++)
printf("parent%d\n",i);
lockf(1,0,0);/*解锁*/
}
}
}
运行结果
parent…
son…
daughter..
daughter..
或parent…
son…
parent…
daughter…
大致与未上锁的输出结果相同,也是随着执行时间不同,输出结果的顺序有所不同。
四、分析原因
上述程序执行时,不同进程之间不存在共享临界资源(其中打印机的互斥性已由操作系统保证)问题,所以加锁与不加锁效果相同。
(四)守护进程实验
写一个使用守护进程(daemon)的程序,来实现:
1,创建一个日志文件/var/log/Mydaemon.log
2,每分钟都向其中写入一个时间戳(使用time_t的格式)
注意:
要root权限才能在/var/log创建文件。
守护神程序:
#include
#include
#include
main(){
time_tt;//建立time_t格式变量
FILE*fp;//建立文件
fp=fopen("/var/log/Mydaemon.log","a");//打开文件
pid_tpid;//守护神
pid=fork();
if(pid>0){
printf("Daemononduty!
\n");
exit(0);
}
elseif(pid<0){
printf("Can'tfork!
\n");
exit(-1);
}
while
(1){
if(fp>=0){
sleep(60);//等待一分钟再往文件中写入时间戳
printf("Daemononduty!
\n");
t=time(0);
fprintf(fp,"Thecurrenttimeis%s\n",asctime(localtime(&t)));
}
}
fclose(fp);//关闭文件
}
运行结果
进程通信
(一)信号机制实验
实验目的
1、了解什么是信号
2、熟悉LINUX系统中进程之间软中断通信的基本原理
实验内容
1、编写程序:
用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按^c键);捕捉到中断信号后,父进程用系统调用kill()向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
Childprocess1iskilledbyparent!
Childprocess2iskilledbyparent!
父进程等待两个子进程终止后,输出如下的信息后终止:
Parentprocessiskilled!
2、分析利用软中断通信实现进程同步的机理
参考程序
#include
#include
#include
voidwaiting(),stop();
intwait_mark;
main()
{
intp1,p2,stdout;
while((p1=fork())==-1);/*创建子进程p1*/
if(p1>0)
{
while((p2=fork())==-1);/*创建子进程p2*/
if(p2>0)
{
wait_mark=1;
signal(SIGINT,stop);/*接收到^c信号,转stop*/
waiting();
kill(p1,16);/*向p1发软中断信号16*/
kill(p2,17);/*向p2发软中断信号17*/
wait(0);/*同步*/
wait(0);
printf("Parentprocessiskilled!
\n");
exit(0);
}
else
{
wait_mark=1;
signal(17,stop);/*接收到软中断信号17,转stop*/
waiting();
lockf(stdout,1,0);
printf("Childprocess2iskilledbyparent!
\n");
lockf(stdout,0,0);
exit(0);
}
}
else
{
wait_mark=1;
signal(16,stop);/*接收到软中断信号16,转stop*/
waiting();
lockf(stdout,1,0);
printf("Childprocess1iskilledbyparent!
\n");
lockf(stdout,0,0);
exit(0);
}
}
voidwaiting()
{
while(wait_mark!
=0);
}
voidstop()
{
wait_mark=0;
}
运行结果
屏幕上无反应,按下^C后,显示Parentprocessiskilled!
分析原因
上述程序中,signal()都放在一段程序的前面部位,而不是在其他接收信号处。
这是因为signal()的执行只是为进程指定信号值16或17的作用,以及分配相应的与stop()过程链接的指针。
因而,signal()函数必须在程序前面部分执行。
本方法通信效率低,当通信数据量较大时一般不用此法。
思考
1、该程序段前面部分用了两个wait(0),它们起什么作用?
答:
父进程等待两个子进程终止。
2、该程序段中每个进程退出时都用了语句exit(0),为什么?
答:
该程序中每个进程退出时都用了语句exit(0),这是进程的正常终止。
3、为何预期的结果并未显示出?
答:
因为只执行成功两个子进程,但是并没有调用两个子进程P1,P2。
当signal()让父进程捕捉从键盘上来的信号(按下^C或者break键时),只有捕捉到信号后,父进程用系统调用kill()向两个子进程发出信号。
当子进程捕捉到信号后才能输出信息,之后父进程输出信息。
4、程序该如何修改才能得到正确结果?
答:
#include
#include
#include
Void waiting(),stop(),alarming();
Intwait_mark; Main()
{
Int P1,P2,stdout;
if (P1=fork()); /*创建子进程P1*/
{
If (p2=fork()); /*创建子进程P2*/
{
Wait_mark=1;
Signal(SIGINT,stop); /*接收到^C信号,转stop*/
Signal(SIGALRM,alarming);/*接收SIGALRM*/ Waiting();
Kill(P1,16); /*向P1发软中断信号16*/ Kill(P2,17); /*向P2发软中断信号17*/
Wait(0); /*同步*/
Wait(0);
Printf(“Parent process is killed!
\n”); Exit(0);
}
Else {
Wait_mark=1;
Signal(17,stop); /*接收到软中断信号17,转stop*/
Signal(17,stop);
Signal(SIGINT,SIG_IGN);/*忽略^C信号*/
While(wait_mark!
=0);
Lockf(stdout,1,0);
Printf(“Child process 2 is killed by parent!
\n);
Lockf(stdout,0,0);
Exit(0);
}
}
Else {
Wait_mark=1;
Signal(16,stop);
Signal(SIGINT,SIG_IGN);
While(wait_mark!
=0) Lockf(stdout,1,0);
Printf(“Child process 1 is killed by parent!
\n”); Lockf(stdout,0,0); Exit(0); }
}
Void waiting()
{
Sleep(5);
if(wait_mark!
=0);
kill(getpid(),SIGALRM);
}
Void alarming()
{
Wait_mark=0;
}
Void stop();
{
Wait_mark=0
}
(二)进程的管道通信实验
实验目的
1、了解什么是管道
2、熟悉UNIX/LINUX支持的管道通信方式
实验内容
编写程序实现进程的管道通信。
用系统调用pipe()建立一管道,二个子进程P1和P2分别向管道各写一句话:
Child1issendingamessage!
Child2issendingamessage!
父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2)。
参考程序
#include
#include
#include
intpid1,pid2;
main()
{
intfd[2];
charoutpipe[100],inpipe[100];
pipe(fd);/*创建一个管道*/
while((pid1=fork())==-1);
if(pid1==0)
{
lockf(fd[1],1,0);
sprintf(outpipe,"child1processissendingmessage!
");
/*把串放入数组outpipe中*/
write(fd[1],outpipe,50);/*向管道写长为50字节的串*/
sleep(5);/*自我阻塞5秒*/
lockf(fd[1],0,0);
exit(0);
}
else
{
while((pid2=fork())==-1);
if(pid2==0)
{lockf(fd[1],1,0);/*互斥*/
sprintf(outpipe,"child2processissendingmessage!
");
write(fd[1],outpipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}
else
{wait(0);/*同步*/
read(fd[0],inpipe,50);/*从管道中读长为50字节的串*/
printf("%s\n",inpipe);
wait(0);
read(fd[0],inpipe,50);
printf("%s\n",inpipe);
exit(0);
}
}
}
运行结果
延迟5秒后显示
child1processissendingmessage!
再延迟5秒
child2processissendingmessage!
思考题
1、程序中的sleep(5)起什么作用?
答:
自我阻塞5秒。
这样做的目的是令读进程把管道中的已有数据读完后,暂时进入睡眠状态等待,直至写进程又将数据写入管道后,再将读进程唤醒。
2、子进程1和2为什么也能对管道进行操作?
答:
因为他们的读指针和写指针都指向了管道的索引节点。
(三)消息的发送与接收实验
实验目的
1、了解什么是消息
2、熟悉消息传送的机理
实验内容
消息的创建、发送和接收。
使用系统调用msgget(),msgsnd(),msgrev(),及msgctl()编制一长度为1k的消息发送和接收的程序。
参考程序
1、client.c
#include
#include
#include
#defineMSGKEY75
structmsgform
{longmtype;
charmtext[1000];
}msg;
intmsgqid;
voidclient()
{
inti;
msgqid=msgget(MSGKEY,0777);/*打开75#消息队列*/
for(i=10;i>=1;i--)
{
msg.mtype=i;
printf(“(client)sent\n”);
msgsnd(msgqid,&msg,1024,0);/*发送消息*/
}
exit(0);
}
main()
{
client();
}
2、server.c
#include
#include
#include
#defineMSGKEY75
structmsgform
{longmtype;
charmtext[1000];
}msg;
intmsgqid;
voidserver()
{
msgqid=msgget(MSGKEY,0777|IPC_CREAT);/*创建75#消息队列*/
do
{
msgrcv(msgqid,&msg,1030,0,0);/*接收消息*/
printf(“(server)received\n”);
}while(msg.mtype!
=1);
msgctl(msgqid,IPC_RMID,0);/*删除消息队列,归还资源*/
exit(0);
}
main()
{
server();
}
程序说明
1、为了便于操作和观察结果,编制二个程序client.c和server.c,分别用于消息的发送与接收。
2、server建立一个Key为75的消息队列,等待其它进程发来的消息。
当遇到类型为1的消息,则作为结束信号,取消该队列,并退出server。
server每接收到一个消息后显示一句“(server)received。
”
3、client使用key为75的消息队列,先后发送类型从10到1的消息,然后退出。
最后一个消息,即是server端需要的结束信号。
client每发送一条消息后显示一句“(client)sent”。
4、注意:
二个程序分别编辑、编译为client与server。
执行:
./server&
ipcs-q
./client。
运行结果
从理想的结果来说,应当是每当client发送一个消息后,server接收该消息,client再发送下一条。
也就是说“(client)sent”和“(server)received”的字样应该在屏幕上交替出现。
实际的结果大多是,先由client发送了两条消息,然后server接收一条消息。
此后client、server交替发送和接收消息。
最后server一次接收两条消息。
client和server分别发送和接收了10条消息,与预期设想一致。
开启server
开启client,发送信息
Server收到信息
思考
message的传送和控制并不保证完全同步,当一个程序不在激活状态的时候,它完全可能继续睡眠,造成了上面的现象,在多次sendmessage后才recievemessa