操作系统实验报告
姓名:周洪锋 学号:20009200766 上课班级:周二、周四56节
实验一:进程的建立
实验目的
实验内容
创建进程并显示标识等进程控制块的属性信息;
显示父子进程的通信信息和相应的应答信息(进程间通信机制任选)
实验要求
实验设计与实现
在Linux操作系统中,可以使用fork()函数来复制一个当前进程,并作为当前进程的子进程。fork()函数在头文件<unistd.h>中,没有参数,返回值为pid_t类型。在父进程中,该函数返回值是子进程的pid,而在子进程中,该函数返回值始终为0,可以通过返回值的特点来区别父子进程。进程创建失败时,返回值为负数。
同时需要注意的是,在父进程结束时,子进程会被迫退出,所以需要在父进程的代码中调用wait()函数(在头文件<sys/wait.h>中),其原型如下
1 extern __pid_t wait (int *__stat_loc) ;
该函数会等待,知道子进程运行完毕,__stat__loc
参数用于接收子进程退出的信息,这里我们不关心这个值,可以直接填NULL。
fork出的子进程会复制父进程的资源(实际上用的是一种类似“懒标记”法的方法,子进程会继承父进程的资源,但是当子进程修改资源的值时,会复制一份资源,不会对父进程修改),并继续执行剩余代码。
代码中用到的剩余两个函数
1 2 pid_t getpid () pid_t getppid ()
分别用于获取自己的自己的父进程的pid。
实验结果分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <unistd.h> #include <sys/wait.h> int main () { int n=100 ; pid_t pid=fork(); if (pid<0 ) { printf ("Fail to create a new process!\n" ); return 0 ; } else if (pid==0 ) { n++; printf ("This is the child process, pid=%hd, father process' pid=%hd\n" ,getpid(),getppid()); printf ("n=%d\n" ,n); exit (0 ); } else { wait(NULL ); printf ("This is the father process, the pid=%hd,child's pid=%hd\n" ,getpid(),pid); printf ("n=%d\n" ,n); } return 0 ; }
代码中,使用if语句来区别父子进程。
运行结果如下
可以看到,父子进程可以互相显示进程控制块中的信息,且子进程中修改值并不会影响父进程。
实验二:进程间的同步
实验目的:
实验内容
利用通信API实现进程之间的同步;
建立司机和售票员进程,并实现他们间的同步运行。
实验要求
显示司机和售票员进程的同步运行轨迹
实验设计与实现
创建信号量
首先使用ftok函数来创建一个关键字,该函数原型如下
1 key_t ftok ( char * fname, int id ) ;
ftok函数是根据pathname和id来创建一个关键字(类型为 key_t),此关键字在创建信号量,创建消息队列的时候都需要使用。其中pathname必须是一个存在的可访问的路径或文件,id必须不得为0。失败返回值为-1。
利用创建的关键字,就可以使用semget函数来创建信号量。
1 int semget (key_t __key, int __nsems, int __semflg) ;
参数中,__key是上述生成的关键字,nsems是生成的信号量数量(可以产生类似数组的效果),_semflg :信号量的创建方式或权限。有IPC_CREAT,IPC_EXCL。
该函数返回一个int类型的值,这个值就可以看成是信号量的标识符。
初始化信号量
信号量创建完成后,需要对其值进行初始化。
1 int semctl (int __semid, int __semnum, int __cmd, ...) ;
第一个参数传入信号量的标识符,第二个参数是这个信号量集中的编号(如果在semget时的__nsems大于1,那这个参数就相当于信号量数组的索引),第三个参数是需要做的操作,这里我们使用SETVAL,即设置信号量集中的一个单独信号量的值。
最后还有一个可变参数,在这个实验中我们只需要初始化一个值,所以只要传入一个semun类型:
1 2 3 4 5 6 union semun { int val; struct semid_ds *buf ; unsigned short *arrary; };
声明一个semun,改变其中的val然后传入semctl函数即可。(详见完整代码)
定义P操作和V操作
1 2 3 4 5 void P (int sem_id) { sembuf op = {0 ,-1 ,0 }; semop(sem_id,&op,1 ); }
semop用于操作信号量,可以使用资源也可以释放资源,第一个参数为信号量标识符,第二参数是一个sembuf结构体的指针,第三个参数用于指定操作的结构数量。
其中sembuf结构体定义如下
1 2 3 4 5 6 struct sembuf { unsigned short sem_num; short sem_op; short semflg; };
在P操作中,需要占用资源,所以sem_op设置为-1,sem_num为0,表示我们希望修改这个信号集中的第一个信号量。
1 2 3 4 5 void V (int sem_id) { sembuf op = {0 ,1 ,0 }; semop(sem_id,&op,1 ); }
V操作同理,将sem_op设置为1即可。
实验结果分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <iostream> #include <sys/sem.h> #include <unistd.h> union semun { int val; struct semid_ds *buf ; unsigned short *array ; struct seminfo *__buf ; };int sem_id1=0 ,sem_id2=0 ;void P (int sem_id) ;void V (int sem_id) ;int main () { int key1= ftok("." ,36 ),key2=ftok("." ,98 ); if (key1==-1 ||key2==-1 ) { printf ("keyFail!\n" ); exit (0 ); } sem_id1=semget(key1,1 ,IPC_CREAT|0666 ); sem_id2=semget(key2,1 ,IPC_CREAT|0666 ); semun u1,u2; u1.val=0 ;u2.val=0 ; if (sem_id1==-1 ||sem_id2==-1 ) { printf ("semFail!\n" ); exit (0 ); } int flag1=semctl(sem_id1,0 ,SETVAL,u1); int flag2=semctl(sem_id2,0 ,SETVAL,u2); if (flag1==-1 ||flag2==-1 ) { printf ("fail to set!\n" ); exit (0 ); } pid_t pid=fork(); if (pid<0 ) { printf ("Fail to create a new process!\n" ); exit (0 ); } else if (pid==0 ) { while (true ) { P(sem_id2); printf ("The seller start to sell to ticker~\n" ); printf ("press '#stop' to stop selling:" ); std ::string s; std ::cin >>s; printf ("The seller stops selling!\n" ); V(sem_id1); } } else { while (true ) { printf ("press '#stop' to stop the car:" ); std ::string s; std ::cin >>s; printf ("The driver stops the car\n" ); V(sem_id2); P(sem_id1); printf ("The driver ignited the car~\n" ); } } return 0 ; }void P (int sem_id) { sembuf op = {0 ,-1 ,0 }; semop(sem_id,&op,1 ); }void V (int sem_id) { sembuf op = {0 ,1 ,0 }; semop(sem_id,&op,1 ); }
开始时,默认汽车在行驶,当控制台输入字符串时,汽车停止,售票员开始售票;再次输入字符串,售票员停止售票,汽车启动。
运行结果
实验三:线程进程共享数据
实验目的
了解线程与进程之间的数据共享关系。创建一个线程,在线程中更改进程中的数据。
实验内容
在进程中定义全局共享数据,在线程中直接引用该数据进行更改并输出该数据。
实验要求
显示和输出共享数据。
实验设计与实现
创建进程
1 2 3 4 int pthread_create (pthread_t *__restrict __newthread, const pthread_attr_t *__restrict __attr, void *(*__start_routine) (void *), void *__restrict __arg)
这个函数用于创建一个线程,需要指定调用的函数和函数的参数。
参数:
第一个参数:新建的线程ID,也就是指向的内存单元(传入的是指针),
第二个参数:线程属性,默认为NULL
第三个参数:可以理解为希望被执行的函数指针
第四个参数:函数的参数
1 int pthread_join (pthread_t __th, void **__thread_return) ;
当进程结束时,子进程也会被迫结束,所以需要调用这个函数,阻塞进程,直到指定的进程结束。
参数:
第一个参数:线程的ID
第二个参数:指向字符串的指针,用于接受子进程的运行结果
实验结果分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <stdio.h> #include <pthread.h> #include <errno.h> void *func (void * x) { int *p=(int *)x; *p=100 ; printf ("in thread,n=%d\n" ,*p); }int main () { pthread_t tid; int n=50 ; printf ("in main process, n=%d\n" ,n); int res= pthread_create(&tid,NULL ,func,(void *)&n); if (res!=0 ) { printf ("Fail to create thread! \n" ); return 0 ; } pthread_join(tid,NULL ); printf ("after the thread, n=%d\n" ,n); return 0 ; }
运行结果
可以看到,子线程修改了主进程中的数据,并将结果输出。
实验四:创建进程
实验目的
学会通过基本的windows或者Linux进程控制函数,由父进程创建子进程,并实现父子进程协同工作。
实验内容
创建两个进程,让子进程读取一个文件,父进程等待子进程读取完文件后继续执行,实现进程协同工作。
进程协同工作就是协调好两个进程,使之安排好先后次序,并以此执行,可以用等待函数来实现这一点。当需要等待子进程运行结束时,可在父进程中调用等待函数。
实验要求
让父子进程按要求的顺序读取文件。
实验设计与实现
在父进程中调用fork()
函数产生一个子进程,在子进程中读取文件,再在父进程中读取文件。为了保证父进程在子进程之后读取文件,所以使用wait()
函数在父进程中等待子进程运行完毕后再运行接下来的代码。
在两个进程中,使用fopen等文件指针相关操作来读取文件。
实验结果分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <unistd.h> #include <sys/sem.h> #include <sys/wait.h> int main () { pid_t pid; pid=fork(); FILE *fp=fopen("/home/zhouhf/CLionProjects/os_exp4/data.txt" ,"r" ); if (fp==NULL ) { printf ("Fail to open file!\n" ); exit (0 ); } if (pid==-1 ) { printf ("Fail to create process!\n" ); exit (0 ); } else if (pid==0 ) { char str[101 ]; fgets(str,100 ,fp); printf ("This is from child process:\n" ); puts (str); } else { int status; wait(&status); char str[101 ]; fgets(str,100 ,fp); printf ("This is from father process:\n" ); puts (str); } return 0 ; }
其中,文件data.txt的内容为如下
Hello, have a nice day!
运行结果:
实验五:匿名管道通信
实验目的
学习使用匿名管道在两个进程间建立通信。
实验内容
分别建立名为Parent的单文档应用程序和Child的单文档应用程序作为父子进程,由父进程创建一个匿名管道,实现父子进程向匿名管道写入和读取数据。
实验要求
父进程要能够从子进程创建的匿名管道中读取数据,实现进程间通信。
实验设计与实现
创建管道
函数:
1 int pipe (int __pipedes[2 ]) ;
pipe函数需要一个长度为2的数组指针作为参数,创建管道完成后,会修改数组指针的值作为标识符(_pipedes[0]是读取的管道,_pipedes[1]写入的管道),当创建失败时,该函数会返回-1值。
子进程
子进程只需要写入数据,所以可以在子进程中使用close(int)
函数将读取的管道关闭。
接下来,使用write
函数
1 ssize_t write (int __fd, const void *__buf, size_t __n)
三个参数分别是:
该函数会返回成功写入的字节数(由于各种可能导致写入失败的因素,该值可能会比预期的小)
父进程
同理,在父进程中,只需要读取数据,所以使用close
函数关闭写入管道,然后使用read
函数从管道读取内容。
1 ssize_t read (int __fd, void *__buf, size_t __nbytes)
该函数与write函数类似,但需要注意的是第一个参数要传递读取管道的标识符,buf指针是期望读入的缓冲区。
实验结果分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <unistd.h> #include <sys/wait.h> #include <cstring> #include "cstdlib" #include "time.h" int main () { int _pipe[2 ]; char msg[]="Have a nice day!\n" ; char buf[101 ]; int res=pipe(_pipe); if (res==-1 ) { printf ("Fail to build pipe!\n" ); return 0 ; } pid_t pid=fork(); if (pid==-1 ) { printf ("Fail to fork process!\n" ); return 0 ; } else if (pid==0 ) { close(_pipe[0 ]); write(_pipe[1 ],msg,sizeof (msg)); exit (0 ); } else { int status; wait(&status); close(_pipe[1 ]); read(_pipe[0 ],buf,sizeof (buf)); printf ("msg from child process :%s" ,buf); } return 0 ; }
运行结果: