本文是操作系统课程设计关于进程线程管理通信与同步互斥的综合实验(长文警告) 。
一、实验目的
以OpenEuler等Linux操作系统为实验对象,加深对Linux进程、线程概念的理解,掌握利用Linux系统调用创建、管理进程的方法,掌握利用POSIX线程(Pthread)库创建管理线程的方法,认识进程、线程并发执行的实质;
深入理解Linux内核提供的消息队列、共享内存、管道、软中断四种进程间通信机制,掌握利用系统调用实现进程间通信;
了解Pthread线程库提供的线程间通信机制,掌握使用Pthread API实现线程间通信的方法;
深入理解Linux系统提供的多种进程同步互斥机制,掌握使用信号量实现进程间的同步互斥的方法;
了解Pthread提供的线程同步互斥机制,掌握使用互斥变量和条件变量实现多线程间的同步互斥的方法
二、实验环境 硬件:
软件:
虚拟机:VMware Workstation Pro 15.5.6
Linux系统:华为openEuler-20.03-LTS-x86_64
Pthread线程库,POSIX接口
gcc编译
C语言
三、实验内容 在Linux环境下,采用C/C++/Java(或其它语言)编程,完成以下实验内容。
1、第一组 进程的创建与管理
阅读Linux内核源码,分析Linux进程的组成,观察进PCB/task_struc等进程管理数据结构;
利用Linux内核提供的fork()
、exec()
、wait()
等系统调用,创建管理多个进程,观察父子进程的结构和并发行为,掌握睡眠、撤销等进程控制方法;
掌握ps
、top
、pstree –h
、vmstat
、strace
、ltrace
、sleep x
、kill
、jobs
等命令的功能和使用方式;
2、第二组 线程的创建与管理
了解POSIX 线程标准库(Pthread线程库)定义的线程结构和提供的线程管理API;
利用Pthread线程库API,创建管理多个线程,观察线程的结构和并发执行行为;
3、第三/四组 进程/线程通信
了解Linux提供的消息队列、共享内存、管道、软中断Signal等四种通信机制,编程实现进程间通信;
线程通信
了解Linux所支持的线程机制,在一个进程内创建多个主从线程,采用参数传递机制,实现线程间通信;
4、第五组 进程/线程同步互斥 要求:参照2019-2020学年操作系统期末考试信号量题目,设计实现三个进程/线程A、B、C,分别模拟题目所描述的生产产品A、B、C的三个worker,观察并记录进程/线程的创建和同步互斥行为。重点分析信号量设计方案是否合理、符合预期,避免设计方案导致死锁或不符合题目要求。
期末考试试题如下:
An assembly line is to produce a product C with four part As, and three part Bs. The worker of machining(加工) A and worker of machining B will produce two part As and one part B independently each time. Then the two part As or one part B will be moved to the station(工作台), which can hold at most 12 of part As and part Bs altogether. Two part As must be put onto the station simultaneously. The workers must exclusively put a part on the station or get it from the station. In addition, the worker to make C must get all part of As and Bs for one product once.
Using semaphores to coordinate the three workers who are machining part A, part B and the product C to manufacture the product without deadlock.
It is required that
(1) definition and initial value of each semaphore, and
(2) the algorithm to coordinate the production process for the three workers should be given.
进程同步互斥
创建3个Linux进程,分别模拟生产产品A、B、C的三个worker的行为,利用Linux内核信号量,实现三者间正确的同步互斥;
线程同步互斥
利用Pthread API,创建3个Linux线程,分别模拟生产产品A、B、C的三个worker的行为,采用Pthread提供的信号量/管程机制,现三者间正确的同步互斥;
四、实验步骤 0、实验环境配置 0-1. Linux 操作系统安装
注意安装操作系统所需的软硬件环境和硬件配置要求。
0-2. Pthread 线程库安装 观察确认所安装的Linux发行版本带有Pthread线程库,注意:
某些版本Ubuntu Linux默认不带Pthread线程库,即使在编译的度时候 加上-lpthread
也不行,man不到相关Pthread函知数。此时,需要在/usr/lib/…
下导入动态库libpthread.a
,具体方法可以查阅网上相关资料。
后续编程时导入头文件:#include <pthread.h>
因为我电脑早已经安装了VMware虚拟机,就不重新安装了,直接下载安装OpenEuler。
1、下载OpenEuler
进入OpenEuler下载页面 ,下载openEuler-20.03-LTS-x86_64-dvd.iso镜像。
等待下载完成。
2、创建虚拟机
打开VMware Workstation Pro,选择“创建新的虚拟机”。
选择“自定义”。
设置虚拟机的硬件兼容性限制,按照默认选择。
选择下载好的openEuler-20.03-LTS-x86_64-dvd.iso镜像。
选择Linux 操作系统。
配置虚拟机的名称,位置,处理器,内存,网络连接、控制器、磁盘等。
创建完成。
3、安装OpenEuler
启动虚拟机。
选择第一项安装OpenEuler。
等待check完毕。
选择安装过程中所使用的语言。
选择分区和软件模块,点击“开始安装”后,开始安装系统,在安装期间可以设置Root密码以及创建用户。
安装完成。
重启后登录OpenEuler系统。
创建的wxy用户以及root用户均可以成功登录,安装OpenEuler完成。
1、第一组 进程的创建与管理 实验1-1. 进程观察 查阅相关资料,阅读Linux内核源码,分析Linux进程的组成,了解进程状态,观察进PCB/task_struc等进程管理数据结构;
实验1-2. 进程创建与管理 参照**“【实验指导】1. 进程创建及管理示例”** 中的程序,结合所查阅的参考资料,利用Linux内核提供的fork()
、exec()
、wait()
、exit()
、kill()
等系统调用,创建管理多个进程,观察父子进程的结构和并发行为,掌握睡眠、撤销等进程控制方法;
要求:本组实验至少用到fork()
、exec()
、wait()
、exit()
、kill()
、getpid()
五个系统调用。
1-2-1 fork()
点击查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> int main (int argc, char **argv) { __pid_t pid; if ((pid = fork()) > 0 ) { printf ("I am the parent process.\n" ); } else if (pid == 0 ) { printf ("I am the child process.\n" ); } else { printf ("fork error\n" ); exit (0 ); } }
fork的返回值如果是>0,那么就是父进程,这个>0的返回值就是创建的子进程的PID;
fork的返回值如果是=0,那么就是子进程;
fork的返回值如果是<0,那么就是fork失败;
1-2-2 exec()
点击查看代码
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 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> int main (int argc, char **argv) { char *envp[] = {"PATH=/tmp" , "USER = 1ei" , "STATUS = testing" , NULL }; char *argv_execv[] = {"echo" , "excuted by execv" , NULL }; char *argv_execvp[] = {"echo" , "excuted by execvp" , NULL }; char *argv_execve[] = {"env" , NULL }; if (fork() == 0 ) { if (execl("/bin/echo" , "echo" , "executed by execl" , NULL ) < 0 ) perror("Err on execl" ); } if (fork() == 0 ) { if (execlp("echo" , "echo" , "executed by execlp" , NULL ) < 0 ) perror("Err on execlp" ); } if (fork() == 0 ) { if (execle("/usr/bin/env" , "env" , NULL , envp) < 0 ) perror("Err on execle" ); } if (fork() == 0 ) { if (execv("/bin/echo" , argv_execv) < 0 ) perror("Err on execv" ); } if (fork() == 0 ) { if (execvp("echo" , argv_execvp) < 0 ) perror("Err on execvp" ); } if (fork() == 0 ) { if (execve("/usr/bin/env" , argv_execve, envp) < 0 ) perror("Err on execve" ); } }
exec用一个指定的程序文件,重新初始化一个进程。
可指定新的命令行参数和环境参数(初始化堆栈底部)。
exec不创建新进程,只是将当前进程重新初始化了指令段和用户数据段,堆栈段以及CPU的PC指针。
6种格式exec系统调用,exec前缀,后跟以下字母:
1-2-3 wait()
点击查看代码
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 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> int main (int argc, char **argv) { __pid_t pid; if ((pid = fork()) > 0 ) { int status, wait_pid; printf ("I am the parent process.\n" ); wait_pid = wait(&status); printf ("pid=%d,wait_pid=%d\n" , pid, wait_pid); printf ("status=%#X,WTERMSIG=%d,WEXITSTATUS=%d\n" , status, WTERMSIG(status), WEXITSTATUS(status)); } else if (pid == 0 ) { int i; for (i = 0 ; i < 3 ; i++) { printf ("I am the child process.\n" ); sleep(1 ); } exit (5 ); } else { printf ("fork error\n" ); exit (0 ); } }
函数返回值为已终止的子进程PID。
status中含有子进程终止的原因。
TERMSIG(status)为被杀信号。
EXITSTATUS(status)为退出码。
子进程正常退出,且退出码为5,因此wait的status退出码刚好是5。
1-2-4 kill()
点击查看代码
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 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> int main (int argc, char **argv) { __pid_t pid; if ((pid = fork()) > 0 ) { int status; printf ("I am the parent process.\n" ); sleep(3 ); kill(pid, 5 ); wait(&status); printf ("%#X,%d,%d\n" , status, WTERMSIG(status), WEXITSTATUS(status)); } else if (pid == 0 ) { while (1 ) { printf ("I am the child process.\n" ); sleep(1 ); } exit (0 ); } else { printf ("fork error\n" ); exit (0 ); } }
可以发现子进程被父进程杀掉了,且终止码为5,刚好wait的终止码也是5。
1-2-5 getpid()
点击查看代码
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 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> int main (int argc, char **argv) { __pid_t pid; if ((pid = fork()) > 0 ) { printf ("I am the parent process.\tpid=%d,child_pid=%d\n" , getpid(), pid); } else if (pid == 0 ) { printf ("I am the child process.\t\tpid=%d,ppid=%d\n" , getpid(), getppid()); exit (0 ); } else { printf ("fork error\n" ); exit (0 ); } }
可以发现,父进程打印的子进程pid和子进程的pid相等,子进程打印的父进程的pid也和父进程的pid相等。
实验1-3. 进程管理命令 了解ps
、top
、pstree –h
、vmstat
、strace
、ltrace
、sleep x
、kill
、jobs
等命令的功能,使用这些命令观察进程结构和行为;
1-3-1 ps
查看正在运行的进程。
1-3-2 top
top查看进程相关信息。
1-3-3 pstree –h
pstree查看进程树。
1-3-4 vmstat
vmstat可以查看系统相关信息。
1-3-5 strace
先运行这个程序(ltrace、kill、jobs也是一样):
点击查看代码
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 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> int main (int argc, char **argv) { __pid_t pid; if ((pid = fork()) > 0 ) { while (1 ) { printf ("I am the parent process.\tpid=%d,child_pid=%d\n" , getpid(), pid); sleep(3 ); } } else if (pid == 0 ) { while (1 ) { printf ("I am the child process.\t\tpid=%d,ppid=%d\n" , getpid(), getppid()); sleep(3 ); } exit (0 ); } else { printf ("fork error\n" ); exit (0 ); } }
屏幕输出:
strace就相当于是调试信息:
1-3-6 ltrace
ltrace也是同理。
1-3-7 sleep x
睡眠了3s后继续输出。
1-3-8 kill
运行kill后只有父进程输出,子进程被杀掉了。
1-3-9 jobs
jobs查看已挂载的程序,不用携带任何参数,这里查看了正在后台运行的1-2-5程序。
2、第二组 线程的创建与管理 实验2-1. Pthread线程库背景知识 了解POSIX 线程标准库(Pthread线程库)相关知识,分析Pthread线程结构,掌握所提供的线程管理API,如pthread_create()
, pthread_join()
, pthread_self()
, pthread_detach()
, pthread_exit()
;
实验2-2. 线程创建与管理 参照**“【实验指导】 2. 线程创建及管理程序示例”** ,查阅参考资料,利用Pthread API,创建和管理线程,观察线程的结构和并发执行行为;
要求:本组实验至少用到pthread_create()
, pthread_exit()
,pthread_join()
, pthread_self()
等四个API。
2-2-1 pthread_create()
函数原型:
1 2 #include <pthread.h> int pthread_create (pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *), void *arg)
函数说明:创建线程。
参数和返回值:
参数
说明
thread
该参数是指向线程标识符的指针,当线程创建成功时,用来返回创建的线程ID
attr
该参数用于指定线程的属性,NULL表示使用默认属性
start_routine
该参数为一个函数指针,指向线程创建后要调用的函数,是一个以指向void的指针作为参数和返回值的函数指针,这个被线程调用的函数也被称为线程函数
arg
指向传递给线程函数的参数,NULL代表不传参数
返回值
成功:0 出错:返回错误码
点击查看代码
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void *mythread1 (void *arg) { int i; for (i = 0 ; i < 5 ; i++) { printf ("I am thread 1\n" ); sleep(2 ); } } void *mythread2 (void *arg) { int i; for (i = 0 ; i < 5 ; i++) { printf ("I am thread 2\n" ); sleep(2 ); } } int main (int argc, char const *argv[]) { pthread_t id1, id2; int res; res = pthread_create(&id1, NULL , mythread1, NULL ); if (res) { printf ("Create pthread error!\n" ); return -1 ; } res = pthread_create(&id2, NULL , mythread2, NULL ); if (res) { printf ("Create pthread error!\n" ); return -2 ; } pthread_join(id1, NULL ); pthread_join(id2, NULL ); return 0 ; }
可以发现,创建了两个线程:thread1和thread2。两个线程分别打印自己的信息,等带2s后继续打印。
2-2-2 pthread_exit()
函数原型:
1 2 #include <pthread.h> void pthread_exit (void *retval)
函数说明:线程退出。
参数和返回值:
参数
说明
Rretval
线程结束时的返回值,可由其它函数如pthread_join0来获取
返回值
void
点击查看代码
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void *create (void *arg) { printf ("New thread is create...\n" ); pthread_exit((void *)6 ); } int main (int argc, char const *argv[]) { pthread_t tid; int res; void *temp; res = pthread_create(&tid, NULL , create, NULL ); printf ("I am the main thread!\n" ); if (res) { printf ("thread id not create...\n" ); return -1 ; } res = pthread_join(tid, &temp); if (res) { printf ("thread is not exit...\n" ); return -2 ; } printf ("Thread is exit code %ld \n" , (long )temp); return 0 ; }
可以发现,子线程退出,主线程接收到了子线程的退出码6。
2-2-3 pthread_join()
函数原型:
1 2 #include <pthread.h> int pthread_join (pthread_t thread,void **thread_return)
函数说明:线程等待。
参数和返回值:
参数
说明
thread
等待退出的线程ID
thread_return
用于定义的指针,用来存储被等待线程结束时的返回值(不为NULL时)
返回值
成功:0 出错:返回错误码
点击查看代码
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 <pthread.h> #include <stdio.h> #include <unistd.h> void *thread (void *str) { int i; for (i = 0 ; i < 4 ; ++i) { sleep(2 ); printf ("This is the thread:%d\n" , i); } return NULL ; } int main (int argc, char const *argv[]) { pthread_t pth; long i; int ret = pthread_create(&pth, NULL , thread, (void *)(i)); pthread_join(pth, NULL ); printf ("123\n" ); for (i = 0 ; i < 3 ; ++i) { sleep(1 ); printf ("This is the main:%ld\n" , i); } return 0 ; }
可以发现主线程在调用pthread_join后等待子线程输出完毕结束后再进行的输出,相当于是主线程被阻塞。
2-2-4 pthread_self()
函数原型:
1 2 #include <pthread.h> Pthread_t pthread_self (void )
函数说明:获取调用线程的标识ID。
参数和返回值:
参数
说明
void
无
返回值
返回调用该函数的线程的标识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 <pthread.h> #include <stdio.h> #include <unistd.h> void *create (void *arg) { printf ("New thread...\n" ); printf ("This thread's id is %u \n" , (unsigned int )pthread_self()); printf ("This thread process pid is %d\n" , getpid()); return NULL ; } int main (int argc, char const *argv[]) { pthread_t tid; int res; printf ("Main thread is starting...\n" ); res = pthread_create(&tid, NULL , create, NULL ); if (res) { printf ("thread id not create...\n" ); return -1 ; } printf ("The main process pid is %d\n" , getpid()); sleep(1 ); return 0 ; }
可以发现,子线程成功打印了自己的线程id,并且子线程的进程pid和主线程的进场pid相等,这也说明这两个线程属于同一个进程。
2-2-5 pthread_cleanup_push()
,pthread_cleanup_pop()
1、函数原型:
1 2 #include <pthread.h> void pthread_cleanup_push (void (*rtn)(void *), void *arg)
函数说明:将清除函数压入清除栈
参数和返回值:
参数
说明
rtn
清除函数
arg
清除函数的参数
返回值
void
2、函数原型:
1 2 #include <pthread.h> void pthread_cleanup_pop (int execute)
函数说明:将清除函数弹出清除栈
参数和返回值:
参数
说明
execute
执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数。 非0:执行;0:不执行
返回值
void
点击查看代码
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 #include <pthread.h> #include <stdio.h> #include <unistd.h> void *clean (void *arg) { printf ("Cleanup %s\n" , (char *)arg); return (void *)0 ; } void *thr_fn1 (void *arg) { printf ("Thread1 start\n" ); pthread_cleanup_push((void *)clean, "Thread1 first handler" ); pthread_cleanup_push((void *)clean, "Thread1 second handler" ); printf ("Thread1 push complete\n" ); if (arg) { return ((void *)1 ); } pthread_cleanup_pop(1 ); pthread_cleanup_pop(1 ); return (void *)2 ; } void *thr_fn2 (void *arg) { printf ("Thread2 start\n" ); pthread_cleanup_push((void *)clean, "Thread2 first handler" ); pthread_cleanup_push((void *)clean, "Thread2 second handler" ); printf ("Thread2 push complete\n" ); if (arg) { return ((void *)3 ); } pthread_cleanup_pop(0 ); pthread_cleanup_pop(1 ); return (void *)4 ; } int main (int argc, char const *argv[]) { int res; pthread_t tid1, tid2; void *tret; res = pthread_create(&tid1, NULL , thr_fn1, (void *)1 ); if (res != 0 ) { printf ("Create theate error...\n" ); return -1 ; } res = pthread_create(&tid2, NULL , thr_fn2, (void *)0 ); if (res != 0 ) { printf ("Create theate error...\n" ); return -1 ; } res = pthread_join(tid1, &tret); if (res != 0 ) { printf ("Thread_join error...\n" ); return -1 ; } printf ("Thread1 exit code %ld\n" , (long )tret); res = pthread_join(tid2, &tret); if (res != 0 ) { printf ("Thread_join error...\n" ); return -1 ; } printf ("Thread2 exit code %ld\n" , (long )tret); return 0 ; }
对于Thread1,传入的参数是1,因此函数thr_fn1只执行了两个pthread_cleanup_push,在pthread_cleanup_pop之前就return 1了,因此Thread1没有Cleanup输出,且返回码是1。
对于Thread2,传入的参数是0,因此函数thr_fn2在执行了两个pthread_cleanup_push后又执行了两个pthread_cleanup_pop,但是第一个pthread_cleanup_pop传的参数是0,第二个pthread_cleanup_pop传的参数是1,而且这个是栈的结构,第一个出栈的“Thread2 second handler”,参数为0,不清理;第二个出栈的“Thread2 first handler”,参数为1,清理。因此Thread2只有一个Cleanup输出(Thread2 first handler),且返回码是4。
2-2-6 综合 点击查看代码
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 #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #define THREAD_NUMBER 3 #define REPEAT_NUMBER 5 #define DELAY_TIME_LEVELS 10.0 void *thrd_func (void *arg) { long thrd_num = (long )arg; int delay_time = 0 ; int count = 0 ; printf ("Thread %ld is starting\n" , thrd_num); for (count = 0 ; count < REPEAT_NUMBER; count++) { delay_time = (int )(rand() * DELAY_TIME_LEVELS / (RAND_MAX)) + 1 ; sleep(delay_time); printf ("\tThread %ld:job %d delay=%d\n" , thrd_num, count, delay_time); } printf ("Thread %ld finished\n" , thrd_num); pthread_exit(NULL ); } int main (int argc, char const *argv[]) { pthread_t thread[THREAD_NUMBER]; long no, res; void *thrd_ret; srand(time(NULL )); for (no = 0 ; no < THREAD_NUMBER; no++) { res = pthread_create(&thread[no], NULL , thrd_func, (void *)no); if (res != 0 ) { printf ("Create thread %d failed\n" , no); exit (res); } } printf ("Creating threads success\nWaiting for thread to finish...\n" ); for (no = 0 ; no < THREAD_NUMBER; no++) { res = pthread_join(thread[no], &thrd_ret); if (!res) { printf ("Thread %d joined\n" , no); } else { printf ("Thread %d joined failed\n" , no); } } return 0 ; }
这个综合案例就是创建了3个线程,让3个线程共用同一个执行函数。每个线程都有5次循环(可以看成5个小任务,也就是5个job),每次循环之间会随机等待1~10s的时间,意义在于模拟每个任务的到达时间是随机的,并没有任何特定的规律。最后就是三个线程相继完成了各自的任务,都成功join,最后主线程结束。
3、第三组 进程间通信 了解Linux提供的消息队列(消息传递)、共享内存、管道/命名管道、信号(signal)/软中断四种进程间通信机制的实现原理和方法;
消息队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。具有一定权限的进程通过向消息队列中写入组织成消息的数据、从队列中读取数据,实现相互间通信。消息队列克服了信号signal承载信息量少,管道pipe只能承载无格式字节流以及缓冲区大小受限等缺点;
共享内存:多个进程通过访问同一块内存空间,实现快速的进程间通信是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥;
管道(Pipe)及命名管道(named pipe):用于具有亲缘关系进程间的通信,命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还支持无亲缘关系进程间的通信;
信号(Signal:也称为软中断,是一种基于事件的通信机制,用于通知接受进程有某种事件发生。除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction;
参照**“【实验指导】 6.3 进程间通信示例”** ,查阅参考资料,选择上述四种通信方式中的一种 ,编程实现进程间通信,观察进程间通信过程。
我选择的是消息队列(消息传递)方式。
消息结构体:
1 2 3 4 5 struct my_message { long int message_type; };
相关函数:
函数原型
说明
int msgget(key_t key, int msgflg);
创建和访问一个消息队列
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
将消息添加到消息队列,即消息发送
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
从一个消息队列获取消息,即消息接收
int msgctl(int msgid, int command, struct msgid_ds *buf);
控制消息队列
代码:
信息发送方:msgsend.c
点击查看代码
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 #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/msg.h> #include <errno.h> #define MAX_TEXT 512 struct msg_st { long int msg_type; char text[MAX_TEXT]; }; int main () { int running = 1 ; struct msg_st data ; char buffer[BUFSIZ]; int msgid = -1 ; msgid = msgget((key_t )1234 , 0666 | IPC_CREAT); if (msgid == -1 ) { fprintf (stderr , "msgget failed with error: %d\n" , errno); exit (EXIT_FAILURE); } while (running) { printf ("Enter some text: " ); fgets(buffer, BUFSIZ, stdin ); data.msg_type = 1 ; strcpy (data.text, buffer); if (msgsnd(msgid, (void *)&data, MAX_TEXT, 0 ) == -1 ) { fprintf (stderr , "msgsnd failed\n" ); exit (EXIT_FAILURE); } if (strncmp (buffer, "end" , 3 ) == 0 ) running = 0 ; sleep(1 ); } exit (EXIT_SUCCESS); }
消息接受方:msgreceive.c
点击查看代码
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 #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <sys/msg.h> struct msg_st { long int msg_type; char text[BUFSIZ]; }; int main () { int running = 1 ; int msgid = -1 ; struct msg_st data ; long int msgtype = 0 ; msgid = msgget((key_t )1234 , 0666 | IPC_CREAT); if (msgid == -1 ) { fprintf (stderr , "msgget failed with error: %d\n" , errno); exit (EXIT_FAILURE); } while (running) { if (msgrcv(msgid, (void *)&data, BUFSIZ, msgtype, 0 ) == -1 ) { fprintf (stderr , "msgrcv failed with errno: %d\n" , errno); exit (EXIT_FAILURE); } printf ("You wrote: %s\n" , data.text); if (strncmp (data.text, "end" , 3 ) == 0 ) running = 0 ; } if (msgctl(msgid, IPC_RMID, 0 ) == -1 ) { fprintf (stderr , "msgctl(IPC_RMID) failed\n" ); exit (EXIT_FAILURE); } exit (EXIT_SUCCESS); }
在第一个终端中编译运行msgsend.c:
新开一个终端编译运行msgreceive.c:
在第一个终端的发送进程中输入,能在第二个终端的接收进程中显示:
在发送进程中输入end,接收进程接收到了end信号,就会删除消息队列,退出进程;发送进程也会在退出。
在同一个终端,发送进程后台运行也是同样的道理。但是如果开两个接收进程,一个发送进程,那么就会出现接收进程争抢使用消息队列的情况,并且每个消息只会被使用一次,不会出现被重复消费的情况。因此最后的end消息因为只会被一个接收方拿到,且将消息队列删除,发送方和接收方1正常退出,但是导致另一个接收方2找不到消息队列,出错退出。
msgsend:
msgreceive1:
msgreceive2:
4、第四组 线程间通信 了解Linux中的线程概念、线程通信机制、线程间同步互斥模式,以及多线程编程方式;
参照*“【实验指导】 6.2.7 Linux c/c++线程间参数传递”**中的 【示例2-3-3】~【示例2-3-5】***,编程实现线程间参数传递。
示例2-3-3 向新建的线程传递字符串 代码:
点击查看代码
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 #include <stdio.h> #include <pthread.h> #include <unistd.h> void *create (void *arg) { char *str; str = (char *)arg; printf ("The parameter passed from main is %s\n" , str); return (void *)0 ; } int main () { int error; pthread_t id1; char *str1 = "Hello!" ; char *attr = str1; error = pthread_create(&id1, NULL , create, (void *)attr); if (error != 0 ) { printf ("This pthread is not created!\n" ); return -1 ; } sleep(1 ); printf ("pthread is created..\n" ); return 0 ; }
运行结果:
可以发现Hello字符串已经传到了子线程,并在子线程打印输出。
示例2-3-4 向新建的线程传递字符串 代码:
点击查看代码
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 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> struct member { int a; char *s; }; void *create (void *arg) { struct member *temp ; temp = (struct member *)arg; printf ("member->a = %d\n" , temp->a); printf ("member->s = %s\n" , temp->s); return (void *)0 ; } int main (int argc, char const *argv[]) { int error; pthread_t id1; struct member *p ; p = (struct member *)malloc (sizeof (struct member)); p->a = 1 ; p->s = "Robben!" ; error = pthread_create(&id1, NULL , create, (void *)p); if (error) { printf ("pthread is not created!\n" ); return -1 ; } sleep(1 ); printf ("pthread is created!\n" ); free (p); p = NULL ; return 0 ; }
运行结果:
可以发现,结构体member传递到了子线程中,member中的int型a和char*型s,在子线程中被输出。
示例2-3-5 验证新建立的线程可以共享进程中的数据 代码:
点击查看代码
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 #include <stdio.h> #include <unistd.h> #include <pthread.h> static int a = 5 ;void *create (void *arg) { printf ("New pthread...\n" ); printf ("a = %d\n" , a); a = -1 ; return (void *)0 ; } int main (int argc, const char *argv[]) { int error; pthread_t id1; a = 10 ; error = pthread_create(&id1, NULL , create, NULL ); if (error != 0 ) { printf ("new thread is not created!\n" ); return -1 ; } sleep(1 ); printf ("a = %d\n" , a); printf ("New thread is created...\n" ); return 0 ; }
运行结果:
可以发现,定义了一个静态变量a=5
,在主线程创建子线程之前a被赋值为10,然后主线程创建子线程,子线程读取a,读取到的是重新赋值后的a(10),因此子线程中输出a = 10。然后子线程中将a修改为-1,因为主线程等待(sleep)了1s,因此主线程拿到的a是子线程修改后的a(-1),因此主线程中输出a = -1。这也证明了新建立的线程可以共享进程中的数据,同一进程中的主线程和子线程可以共享进程中的静态数据。
5、第五组 进程/线程间同步与互斥(二选一)
针对2019-2020学年操作系统期末考试信号量题目,定义合理的锁和信号量,设计生产产品A、B、C的三个worker三个进程/线程A、B、C的业务流程和同步互斥机制。
要求:给出具体的设计方案,并单独提交设计方案,类似期末考试答题形式;
参照**“【实验指导】 6.5 进程间同步互斥示例”** ,根据上一步的设计方案,利用semget、semctl、semop等信号量原语,以进程方式编程实现该设计方案;
观察分析程序运行结果,重点分析信号量设计方案是否合理、程序运行是否符合预期,应避免设计方案导致死锁或不符合题目要求。
参照**“【实验指导】 6.6 线程间同步互斥示例”** ,根据第一步的设计方案,利用Pthread提供的pthread_mutex_init()
、pthread_mutex_lock()
、pthread_mutex_unlock()
等线程同步互斥API,以线程方式编程实现该设计方案;
观察分析程序运行结果。重点分析信号量设计方案是否合理、程序运行是否符合预期,应避免设计方案导致死锁或不符合题目要求。
要求:
基于进程和基于线程的同步互斥实现方案二选一,完成其中一个即可;
不论采用进程方式、还是线程方式,模拟A、B、C三个worker的三个进程/线程A、B、C应当多次循环反复执行,便于观察同步互斥效应;
根据设计方案,给出程序代码,程序运行结果,结果分析。
我选择的是基于进程的同步互斥实现方案。
1、设计方案 方案1(采用二元互斥信号量+整形变量,效率比方案2高): 整型变量 1 2 3 int count_A=0 ; int count_B=0 ; int empty=12 ;
信号量 经分析题目,发现共需要4个信号量,分别为:
1 2 3 4 semaphore MUTEX_STATION = 0 semaphore SUSPEND_A = 1 semaphore SUSPEND_B = 2 semaphore SUSPEND_C = 3
WorkerA 点击查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 WorkerA() { while (1 ) { P(MUTEX_STATION, -1 ); if (count_A <= 7 && empty >= 2 ) { count_A += 2 ; empty -= 2 ; V(MUTEX_STATION, 1 ); V(SUSPEND_C, 1 ); } else { V(sem_id, MUTEX_STATION, 1 ); P(sem_id, SUSPEND_A, -1 ); } } }
WorkerB 点击查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 WorkerB() { while (1 ) { P(MUTEX_STATION, -1 ); if (count_B <= 7 && empty >= 1 ) { count_B += 1 ; empty -= 1 ; V(MUTEX_STATION, 1 ); V(SUSPEND_C, 1 ); } else { V(MUTEX_STATION, 1 ); P(SUSPEND_B, -1 ); } } }
WorkerC 点击查看代码
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 WorkerC() { while (1 ) { P(MUTEX_STATION, -1 ); if (count_A >= 4 && count_B >= 3 ) { count_A -= 4 ; count_B -= 3 ; empty += 7 ; V(MUTEX_STATION, 1 ); V(SUSPEND_A, 1 ); V(SUSPEND_B, 1 ); } else { V(MUTEX_STATION, 1 ); P(SUSPEND_C, -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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <time.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/shm.h> #define NUM_SEMAPHORE 4 #define MUTEX_STATION 0 #define SUSPEND_A 1 #define SUSPEND_B 2 #define SUSPEND_C 3 #define SEM_KEY 0x11223344 #define SHM_KEY1 0x11223355 #define SHM_KEY2 0x11223366 #define NONE "\e[0m" #define RED "\e[0;31m" #define YELLOW "\e[1;33m" #define PINK "\e[1;35m" #define GREEN "\e[1;32m" #define CYAN "\e[0;36m" #define BUF_SIZE 12 char *buf;int *info; int i, sv, sem_id, shm_id, info_shm_id, C_num = 0 ;union semun { int val; struct semid_ds *buf ; unsigned short *array ; struct seminfo *__buf ; }; void P (int sem_id, int sem_num, int op) { if (op < 0 ) { struct sembuf sem_buf ; sem_buf.sem_num = sem_num; sem_buf.sem_op = op; sem_buf.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_buf, 1 ) == -1 ) { perror(RED "Semaphore P" NONE); exit (2 ); } } else { perror(RED "Semaphore P" NONE); exit (3 ); } return ; } void V (int sem_id, int sem_num, int op) { if (op > 0 ) { struct sembuf sem_buf ; sem_buf.sem_num = sem_num; sem_buf.sem_op = op; sem_buf.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_buf, 1 ) == -1 ) { perror(RED "Semaphore V" NONE); exit (2 ); } } else { perror(RED "Semaphore V" NONE); exit (3 ); } return ; } void show_shm () { printf (CYAN "[" ); for (i = 0 ; i < BUF_SIZE - 1 ; i++) { printf ("%c," , buf[i]); } printf ("%c]\n\n" NONE, buf[BUF_SIZE - 1 ]); } void create_ipc () { union semun arg [NUM_SEMAPHORE ]; arg[MUTEX_STATION].val = 1 ; arg[SUSPEND_A].val = 0 ; arg[SUSPEND_B].val = 0 ; arg[SUSPEND_C].val = 0 ; if ((sem_id = semget(SEM_KEY, NUM_SEMAPHORE, IPC_CREAT | IPC_EXCL | 0666 )) == -1 ) { perror(RED "Create Semaphores" NONE); exit (1 ); } printf ("Create Semaphores: OK\n" ); printf ("Sem_id = %d\n" , sem_id); for (i = 0 ; i < NUM_SEMAPHORE; i++) { semctl(sem_id, i, SETVAL, arg[i]); } for (i = 0 ; i < NUM_SEMAPHORE; i++) { printf ("The Sem[%d] = %d\n" , i, semctl(sem_id, i, GETVAL, NULL )); } printf ("\n" ); info_shm_id = shmget(SHM_KEY2, 3 * sizeof (int ), IPC_CREAT | IPC_EXCL | 0666 ); if (info_shm_id == -1 ) { perror(RED "Create Info Share Memory" NONE); exit (1 ); } printf ("Create Info Share Memory: OK\n" ); info = (int *)shmat(info_shm_id, 0 , 0 ); if (info == (int *)-1 ) { perror(RED "Attach Info Share Memory" NONE); exit (1 ); } info[0 ] = 0 ; info[1 ] = 0 ; info[2 ] = 12 ; printf ("Attach Info Share Memory: OK\n" ); printf ("count_A=%d, count_B=%d, empty=%d\n\n" , info[0 ], info[1 ], info[2 ]); shm_id = shmget(SHM_KEY1, BUF_SIZE * sizeof (char ), IPC_CREAT | IPC_EXCL | 0666 ); if (shm_id == -1 ) { perror(RED "Create Buffer Share Memory" NONE); exit (1 ); } printf ("Create Buffer Share Memory: OK\n" ); buf = (char *)shmat(shm_id, 0 , 0 ); if (buf == (char *)-1 ) { perror(RED "Attach Buffer Share Memory" NONE); exit (1 ); } for (i = 0 ; i < BUF_SIZE; i++) { buf[i] = ' ' ; } printf ("Initialize QUEUE: OK\n" ); show_shm(); } void remove_ipc () { if (semctl(sem_id, 0 , IPC_RMID, 0 ) == -1 ) perror(RED "Remove Semaphores" NONE); else printf ("Remove Semaphores: OK\n" ); if (shmctl(shm_id, IPC_RMID, 0 ) == -1 ) perror(RED "Remove Buffer Share Memory" NONE); else printf ("Remove Buffer Share Memory: OK\n" ); if (shmctl(info_shm_id, IPC_RMID, 0 ) == -1 ) perror(RED "Remove Info Share Memory" NONE); else printf ("Remove Info Share Memory: OK\n" ); } void sig_handler (int sig) { printf ("\n" ); remove_ipc(); printf ("EXIT: OK\n" ); } int main (int argc, char const *argv[]) { srand(time(NULL )); create_ipc(); if (fork() == 0 ) { while (1 ) { sleep(1 + random() % 10 ); P(sem_id, MUTEX_STATION, -1 ); if (info[0 ] <= 7 && info[2 ] >= 2 ) { for (i = 0 ; i < 2 ; i++) { char *c; c = strchr (buf, ' ' ); *c = 'A' ; } printf (YELLOW "Worker A puts 2 As to the station\n" NONE); show_shm(); info[0 ] += 2 ; info[2 ] -= 2 ; printf ("count_A=%d, count_B=%d, empty=%d\n\n" , info[0 ], info[1 ], info[2 ]); V(sem_id, MUTEX_STATION, 1 ); V(sem_id, SUSPEND_C, 1 ); } else { V(sem_id, MUTEX_STATION, 1 ); printf ("Worker A suspended\n\n" ); P(sem_id, SUSPEND_A, -1 ); } } } else if (fork() == 0 ) { while (1 ) { sleep(1 + random() % 10 ); P(sem_id, MUTEX_STATION, -1 ); if (info[1 ] <= 7 && info[2 ] >= 1 ) { char *c; c = strchr (buf, ' ' ); *c = 'B' ; printf (PINK "Worker B puts 1 B to the station\n" NONE); show_shm(); info[1 ] += 1 ; info[2 ] -= 1 ; printf ("count_A=%d, count_B=%d, empty=%d\n\n" , info[0 ], info[1 ], info[2 ]); V(sem_id, MUTEX_STATION, 1 ); V(sem_id, SUSPEND_C, 1 ); } else { V(sem_id, MUTEX_STATION, 1 ); printf ("Worker B suspended\n\n" ); P(sem_id, SUSPEND_B, -1 ); } } } else if (fork() == 0 ) { while (1 ) { P(sem_id, MUTEX_STATION, -1 ); if (info[0 ] >= 4 && info[1 ] >= 3 ) { for (i = 0 ; i < 4 ; i++) { char *c; c = strchr (buf, 'A' ); *c = ' ' ; } for (i = 0 ; i < 3 ; i++) { char *c; c = strchr (buf, 'B' ); *c = ' ' ; } printf (GREEN "Worker C gets 4 As and 3 Bs from the station\n" NONE); show_shm(); info[0 ] -= 4 ; info[1 ] -= 3 ; info[2 ] += 7 ; printf ("count_A=%d, count_B=%d, empty=%d\n\n" , info[0 ], info[1 ], info[2 ]); V(sem_id, MUTEX_STATION, 1 ); V(sem_id, SUSPEND_A, 1 ); V(sem_id, SUSPEND_B, 1 ); printf (GREEN "Worker C is producing C-%d......\n\n" NONE, ++C_num); sleep(1 + random() % 10 ); printf (GREEN "C-%d is produced\n\n" NONE, C_num); } else { V(sem_id, MUTEX_STATION, 1 ); printf ("Worker C suspended\n\n" ); P(sem_id, SUSPEND_C, -1 ); } } } else { signal(SIGINT, sig_handler); wait(&sv); wait(&sv); wait(&sv); printf ("THANKS\n" ); return 0 ; } }
方案2(采用多元信号量,效率不如方案1高): 信号量 经分析题目,发现共需要6个信号量,分别为:
1 2 3 4 5 6 semaphore MUTEX = 1 ; semaphore A_FULL = 0 ; semaphore A_EMPTY = 9 ; semaphore B_FULL = 0 ; semaphore B_EMPTY = 8 ; semaphore ALL_EMPTY = 12 ;
分析:
WorkerA最多生产9个A,如果生产A的个数大于9,则B的个数就会小于3,导致C永远从Station中拿不到4个A和3个B,产生死锁。但是由于WorkerA一次生产2个A,则WorkerA最多生产8(2×4)个A,因此使用信号量A_EMPTY来控制。A_EMPTY初始值可以设置为8,但是A_EMPTY初始值也可以设置为9,都是可以的。
同理,WorkerB最多生产8个B,如果生产B的个数大于8,则A的个数就会小于4,导致C永远从Station中拿不到4个A和3个B,产生死锁。因此使用信号量B_EMPTY来控制。
WorkerA和WorkerB一起最多生产12个A和B,因此使用信号量B_EMPTY来控制。
需要一个互斥量MUTEX,用于WorkerA、WorkerB和WorkerC对Station的互斥访问。
WorkerA 点击查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 WorkerA() { while (1 ) { P(A_EMPTY,-2 ); P(ALL_EMPTY,-2 ); P(MUTEX,-1 ); V(MUTEX,1 ); V(A_FULL,2 ); } }
WorkerB 点击查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 WorkerB() { while (1 ) { P(B_EMPTY,-1 ); P(ALL_EMPTY,-1 ); P(MUTEX,-1 ); V(MUTEX,1 ); V(B_FULL,1 ); } }
WorkerC 点击查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 WorkerC() { while (1 ) { P(A_FULL,-4 ); P(B_FULL,-3 ); P(MUTEX,-1 ); V(MUTEX,1 ); V(ALL_EMPTY,7 ); V(B_EMPTY,3 ); V(A_EMPTY,4 ); } }
编程实现 点击查看代码
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <time.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/shm.h> #define NUM_SEMAPHORE 6 #define MUTEX 0 #define A_FULL 1 #define A_EMPTY 2 #define B_FULL 3 #define B_EMPTY 4 #define ALL_EMPTY 5 #define SEM_KEY 0x11223344 #define SHM_KEY 0x11223355 #define NONE "\e[0m" #define RED "\e[0;31m" #define YELLOW "\e[1;33m" #define PINK "\e[1;35m" #define GREEN "\e[1;32m" #define CYAN "\e[0;36m" #define BUF_SIZE 12 char *buf;int i, sv, sem_id, shm_id, C_num = 0 ;union semun { int val; struct semid_ds *buf ; unsigned short *array ; struct seminfo *__buf ; }; void P (int sem_id, int sem_num, int op) { struct sembuf sem_buf ; sem_buf.sem_num = sem_num; sem_buf.sem_op = op; sem_buf.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_buf, 1 ) == -1 ) { perror(RED "Semaphore P" NONE); exit (2 ); } return ; } void V (int sem_id, int sem_num, int op) { struct sembuf sem_buf ; sem_buf.sem_num = sem_num; sem_buf.sem_op = op; sem_buf.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_buf, 1 ) == -1 ) { perror(RED "Semaphore V" NONE); exit (2 ); } return ; } void show_shm () { printf (CYAN "[" ); for (i = 0 ; i < BUF_SIZE - 1 ; i++) { printf ("%c," , buf[i]); } printf ("%c]\n\n" NONE, buf[BUF_SIZE - 1 ]); } void create_ipc () { union semun arg [NUM_SEMAPHORE ]; arg[MUTEX].val = 1 ; arg[A_FULL].val = 0 ; arg[A_EMPTY].val = 9 ; arg[B_FULL].val = 0 ; arg[B_EMPTY].val = 8 ; arg[ALL_EMPTY].val = 12 ; if ((sem_id = semget(SEM_KEY, NUM_SEMAPHORE, IPC_CREAT | IPC_EXCL | 0666 )) == -1 ) { perror(RED "Create Semaphores" NONE); exit (1 ); } printf ("Create Semaphores: OK\n" ); printf ("Sem_id = %d\n" , sem_id); for (i = 0 ; i < NUM_SEMAPHORE; i++) { semctl(sem_id, i, SETVAL, arg[i]); } for (i = 0 ; i < NUM_SEMAPHORE; i++) { printf ("The Sem[%d] = %d\n" , i, semctl(sem_id, i, GETVAL, NULL )); } shm_id = shmget(SHM_KEY, BUF_SIZE * sizeof (char ), IPC_CREAT | IPC_EXCL | 0666 ); if (shm_id == -1 ) { perror(RED "Create Share Memory" NONE); exit (1 ); } printf ("Create Share Memory: OK\n" ); buf = (char *)shmat(shm_id, 0 , 0 ); if (buf == (char *)-1 ) { perror(RED "Attach Share Memory" NONE); exit (1 ); } for (i = 0 ; i < BUF_SIZE; i++) { buf[i] = ' ' ; } printf ("Initialize QUEUE: OK\n" ); show_shm(); } void remove_ipc () { if (semctl(sem_id, 0 , IPC_RMID, 0 ) == -1 ) perror(RED "Remove Semaphores" NONE); else printf ("Remove Semaphores: OK\n" ); if (shmctl(shm_id, IPC_RMID, 0 ) == -1 ) perror(RED "Remove Share Memory" NONE); else printf ("Remove Share Memory: OK\n" ); } void sig_handler (int sig) { printf ("\n" ); remove_ipc(); printf ("EXIT: OK\n" ); } int main (int argc, char const *argv[]) { srand(time(NULL )); create_ipc(); if (fork() == 0 ) { while (1 ) { sleep(1 + random() % 10 ); P(sem_id, A_EMPTY, -2 ); P(sem_id, ALL_EMPTY, -2 ); P(sem_id, MUTEX, -1 ); for (i = 0 ; i < 2 ; i++) { char *c; c = strchr (buf, ' ' ); *c = 'A' ; } printf (YELLOW "Worker A puts 2 As to the station\n" NONE); show_shm(); V(sem_id, MUTEX, 1 ); V(sem_id, A_FULL, 2 ); } } else if (fork() == 0 ) { while (1 ) { sleep(1 + random() % 10 ); P(sem_id, B_EMPTY, -1 ); P(sem_id, ALL_EMPTY, -1 ); P(sem_id, MUTEX, -1 ); char *c; c = strchr (buf, ' ' ); *c = 'B' ; printf (PINK "Worker B puts 1 B to the station\n" NONE); show_shm(); V(sem_id, MUTEX, 1 ); V(sem_id, B_FULL, 1 ); } } else if (fork() == 0 ) { while (1 ) { P(sem_id, A_FULL, -4 ); P(sem_id, B_FULL, -3 ); P(sem_id, MUTEX, -1 ); for (i = 0 ; i < 4 ; i++) { char *c; c = strchr (buf, 'A' ); *c = ' ' ; } for (i = 0 ; i < 3 ; i++) { char *c; c = strchr (buf, 'B' ); *c = ' ' ; } printf (GREEN "Worker C gets 4 As and 3 Bs from the station\n" NONE); show_shm(); V(sem_id, MUTEX, 1 ); V(sem_id, ALL_EMPTY, 7 ); V(sem_id, B_EMPTY, 3 ); V(sem_id, A_EMPTY, 4 ); printf (GREEN "Worker C is producing C-%d......\n\n" NONE, ++C_num); sleep(1 + random() % 10 ); printf (GREEN "C-%d is produced\n\n" NONE, C_num); } } else { signal(SIGINT, sig_handler); wait(&sv); wait(&sv); wait(&sv); printf ("THANKS\n" ); return 0 ; } }
2、运行结果及分析 两种方案的运行情况只选择了其中一种方案的运行结果,如果工作时间一致的话,两种方案的运行情况基本一致,但是因为使用了随机工作时间,每次运行的结果可能不一样,因此只选择了其中的一次运行结果。
1、编译
2、运行 方案一:
方案二:
3、分析 从运行结果来看,完全没有死锁现象,程序完全正常运行,输出结果也正常,创建和初始化信号量和共享内存,释放和删除信号量和共享内存都正常结束。
方案一的输出中有count_A、count_B、和empty的值,以及Worker A、Worker B和Worker C在进入阻塞状态时的输出,整个过程比较清晰。
方案二的基本过程在输出中,共享内存的状态也很清晰:
2A-B-2A-B-2A-B-C-2A-B-2A-B-2A-B-C-2A-B-2A-B-B-C-2A-B-2A-B-B-C-2A……
方案一和方案二多次运行都没有产生死锁,并且因为设置了随机等待时间模拟生产,每次的结果都有所不同,但都符合预期。但是经过分析,方案二可能会导致Worker A和Worker B都等待Worker C,但是此时工作台却还有一个空位的情况,但是并没有产生死锁,只是效率相较于方案一会低一些。
五、实验总结 这次操作系统课程设计的5组实验,虽然难度可能不是那么高,但是结合了操作系统理论课所学的知识,很有实践意义。经过这5组实验,我首先认识了华为最新的OpenEuler开源Linux操作系统,学习了Linux操作系统的安装、基本操作和使用。这次实验还加深了我对操作系统进程、线程概念和消息队列、共享内存、管道、软中断四种进程间通信机制的理解,掌握了利用Linux系统调用创建、管理进程的方法、利用POSIX线程(Pthread)库创建管理线程的方法和实现进程间通信和线程间通信的方法,深刻地认识了进程、线程并发执行的实质,并掌握了编程使用互斥变量和条件变量实现多线程间的同步互斥。
在实验的过程中,也遇到了各种各样的小问题,比如Linux的命令报错啊,编程gcc编译不通过等等,但经过上网查找资料和与同学讨论交流,都完美解决了问题,总体上看实验过程比较顺利。经过这次的课程设计,明显加深了对操作系统理论课知识的理解,并拓展了很多新的知识和内容,让我受益匪浅。
六、项目源码 本项目的源码已经存放于我的Github,欢迎下载查看: