操作系统进程实验
1. 实验内容说明
1.1 父子进程的协调合作
该程序运行后创建了一个子进程,父进程向子进程发送信号,并等待子进程完成。子进程创建一个新路径/text,而后睡眠,等待被kill信号唤醒,唤醒后先执行信号处理程序,在创建的新路径下新建一个文件并写入内容,然后子进程改换图像,运行ls程序打印出新路径下所有内容。
1.2 两进程的消息队列通信
服务端进程先运行,根据规定的关键字创建一块消息队列,等待客户端进程向消息队列写入信息,从消息队列读取客户端的消息,并向消息队列写入给客户端的回复信息。
客户端进程是一次性的,运行后,向消息队列写入信息,等待服务端进程读取,之后读取服务端进程的回复信息。
1.3 两进程的共享内存通信
该程序运行后,新建一块共享内存,并将之关联到进程的虚地址空间。父进程打开输入文件,每次从里面读入一行内容,写到共享内存区,共三次。子进程打开输出文件,每次从共享内存区读取内容后,将内容写入输出文件。父进程写入前子进程不能读,子进程没读取上次内容前,父进程不能写,父子进程的读写过程不能中断。使用三个信号量实现同步与互斥。
2. 实验内容程序框图
2.1 父子进程的协调合作
2.2 两进程的消息队列通信
2.3 两进程的共享内存通信
3. 实验数据结构及代码说明
3.1 父子进程的协调合作
父进程创建一个子进程后,父进程向子进程发送信号,并等待子进程完成。子进程创建一个新路径/text,而后睡眠,等待被kill信号唤醒,唤醒后先执行信号处理程序,在创建的新路径下新建一个文件并写入内容,然后子进程改换图像,运行ls程序打印出新路径下所有内容。
# include<sys/types.h>
# include<signal.h>
# include<unistd.h>
# include<stdio.h>
# include<wait.h>
# include<stdlib.h>
# include<sys/stat.h>
int main ( )
{
int status=1;
pid_t pid;
void func(); // 声明信号处理程序
signal(SIGUSR1, func); //设置信号16的处理程序为func()
while((pid=fork())==-1);
if(pid){//父进程
printf("parent\n");
kill(pid,SIGUSR1); //向子进程发送信号
wait(&status); //等待子进程完成
printf("Child %d,status=%d \n", pid, status); //打印子进程号和status
}
else{//子进程
printf("Before sleep\n");
mkdir("./text", 0777);//在当前路径建立一个新目录text
sleep(10);//进入睡眠,等待被kill信号唤醒
printf("After sleep\n");
//改换子进程图像,调用ls程序打印text目录下所有内容
execl("/bin/ls", "ls", "-l", "./text", (char*)0);
printf("excel error\n");//改换失败后显示提示
exit(2);
}
}
void func(){//信号处理函数
FILE *f;//文件指针
f = fopen("./text/message.txt", "w");//新建文件message
fprintf(f, "%s", "I Love China!\n");//向文件写入内容
fclose(f);//关闭文件
printf("write finished!\n");//显示文件建立完成提示
}
3.2 两进程的消息队列通信
3.2.1 头文件
定义了消息队列关键字以及发送消息的数据结构
# include<errno.h>
# include<sys/types.h>
# include<sys/ipc.h>
# include<sys/msg.h>
# include<unistd.h>
# include<stdio.h>
# include<string.h>
# define MSGKEY 1949//消息队列关键字
struct msgtype{//发送消息的数据结构
long mtype;//消息类型
int text;//消息正文
};
3.2.2 Server主程序
根据规定的关键字创建一块消息队列,等待客户端进程向消息队列写入信息,客户端写入后,从消息队列读取客户端的消息,并向消息队列写入给客户端的回复信息。
# include"msgcom.h"
int main(){
struct msgtype buf;
int qid, pid=getpid();
//创建消息队列
if((qid=msgget(MSGKEY, IPC_CREAT|0666))==-1)
return -1;
while(1){
msgrcv(qid, &buf, 512, 1, MSG_NOERROR);//等待接收
printf("Server%d receive : %d\n", pid, buf.text);
buf.mtype = 2;
buf.text = 1314;
printf("Server%d send : %d\n", pid, buf.text);
//构造回复消息并发送
msgsnd(qid, &buf, sizeof(int), 0);
}
}
3.2.3 Client主程序
客户端进程是一次性的,运行后,向消息队列写入信息,等待服务端进程读取并回复,服务端回复写入后,读取服务端进程的回复信息。
# include"msgcom.h"
void main(){
struct msgtype buf;
int qid, pid=getpid();
//获取消息队列描述字
qid = msgget(MSGKEY, IPC_CREAT|0666);
buf.mtype = 1;
buf.text = 520;
printf("Client%d send: %d\n", pid, buf.text);
//构造消息并发送
msgsnd(qid, &buf, sizeof(buf.text), 0);
//等待回复消息并接收
msgrcv(qid, &buf, 512, 2 ,MSG_NOERROR);
printf("Client%d recevie : %d\n", pid, buf.text);
}
3.3 两进程的共享内存通信
3.3.1 头文件
主要定义了创建信号量函数和对信号量的操作semWait和semSignal函数。
# include<sys/types.h>
# include<sys/ipc.h>
# include<sys/sem.h>
# include<stdio.h>
# include<sys/shm.h>
# include<unistd.h>
# include<stdlib.h>
# define SHMKEY 2022
# define SIZE 512
# define SEMKEY1 1997
# define SEMKEY2 1999
# define SEMKEY3 2001
static void semcall(int sid, int op){//对信号量的操作函数
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = op;
sb.sem_flg = 0;
if(semop(sid, &sb, 1)==-1){
perror("semop");
exit(-1);
}
}
int creatsem(key_t key){//创建信号量函数
int sid;
union semun{
int val;
struct semid_ds *buf;
ushort *array;
}arg;
if((sid=semget(key, 1 ,0666|IPC_CREAT))==-1){
perror("semget");
exit(-1);
}
arg.val=1;//将信号量初始值设为1
if(semctl(sid,0,SETVAL,arg)==-1){
perror("semctl");
exit(-1);
}
return(sid);
}
void semWait(int sid){//对信号量-1
semcall(sid, -1);
}
void semSignal(int sid){//对信号量+1
semcall(sid ,1);
}
3.3.2 主程序
父进程每次从输入文件读出内容向共享内存区写入,子进程从共享内存去读出内容,写入到输出文件中,共执行三个循环。此过程中父子进程一动一停,同步和互斥由三个信号量实现。
sid1是当前缓冲区能读次数,初始值为0
sid2是当前缓冲区能写次数,初始值为1
mutex是互斥信号量,初始值为1
# include"sem.h"
int main(){
char *segaddr; //共享内存指针
//sid1是当前缓冲区容量,mutex是互斥信号量
int segid, sid1, sid2, mutex;
//申请一块共享内存
if((segid=shmget(SHMKEY, SIZE, IPC_CREAT|0666))==-1){
perror("shmget");
exit(-1);
}
segaddr=shmat(segid, 0 ,0);//将共享内存关联到虚地址空间
sid1=creatsem(SEMKEY1);//sid1是当前缓冲区能读次数
sid2=creatsem(SEMKEY2);//sid2是当前缓冲区能写次数,初始值为1
mutex=creatsem(SEMKEY3);//mutex是互斥信号量,初始值为1
semWait(sid1);//将sid1的初始值置为0
int i=0,j=0;//循环变量
if(!fork()){//子进程sid1=0,sid2=mutex=1
FILE *fp2 = fopen("./text/recever.txt","w");//打开输出文件
while(j++<3){
printf("son%d\n", j);
semWait(sid1);//sid1-1
printf("son before mutex\n");
semWait(mutex);//mutex-1
printf("son before puts %s\n", segaddr);
fputs(segaddr, fp2);//将共享区内容写入输出文件
semSignal(mutex);//mutex+1
semSignal(sid2);//sid2+1
}
fclose(fp2);
}
else{//父进程
FILE *fp1 = fopen("./text/send.txt","r");//打开输入文件
while(i++<3){//输入文件一共有三行内容,每次读入一行写到共享区,共三次
printf("fa%d\n",i);
semWait(sid2);//sid2-1
printf("fa before mutex\n");
semWait(mutex);//mutex-1
fgets(segaddr, 100, fp1);//从输入文件读入一行内容存到共享区
printf("fa after gets %s\n", segaddr);
semSignal(mutex);//mutex+1
semSignal(sid1);//sid1+1
}
fclose(fp1);
}
}
4.实验结果
4.1 父子进程的协调合作
从执行结果的输出可以看出,父进程先获得了处理机,然后开始等待子进程的完成,子进程获得了处理机新建了./text目录,打印了before sleep。然后进入睡眠,然后没过多久(比10秒短很多)子进程就被kill信号唤醒,等到它获得处理机,它先执行信号处理程序,新建了message.txt文件,并打印了write finished,之后子进程改换图像,执行ls程序打印了text目录下的所有内容,也就是刚刚新建的message.txt。
打开message.txt文件发现内容与预期相符,说明信号处理函数工作正常。
4.2 两进程的消息队列通信
先运行服务端进程,服务端无反应,之后运行一个客户端进程,客户端收到了消息520打印后,回复了消息1314。客户端也接收到了消息1314并打印。之后客户端进程结束,服务端继续存活等待,之后再运行几个客户端进程,发现消息队列的收发情况合理运行,符合预期。
4.3 两进程的共享内存通信
子进程先获得了处理机,由于sid1为负而阻塞,父进程获得处理机,从输入问价读入了 I Love China!并将其写入共享内存区,之后第二次想写入的时候,sid2为负,被阻塞。子进程获得了处理机,从共享区读取了内容 I Love China!并将其写入了输出文件,之后第二次想读取的时候,sid1为负被阻塞。父进程获得处理机,继续写入…
可以看到,由于三个信号量的存在,使得父子进程间的读取和写入变得非常有序,不会发生冲突。
最终,打开输入输出文件想比对,结果符合预期。