操作系统课设详细解答

操作系统课设详细解答

一、题目一

实验一 Windows 进程管理

二、实验目的

(1)学会使用 VC 编写基本的 Win32 Consol Application(控制台应用程序)。
(2)通过创建进程、观察正在运行的进程和终止进程的程序设计和调试操作,进一步熟悉操作系统的进程概念,理解 Windows 进程的“一生”。
(3)通过阅读和分析实验程序,学习创建进程、观察进程、终止进程以及父子进程同步的基本程序设计方法。

三、总体设计

1.背景知识

Windows 所创建的每个进程都从调用 CreateProcess() API 函数开始,该函数的任务是在对象管理器子系统内初始化进程对象。每一进程都以调用 ExitProcess() 或 TerminateProcess() API 函数终止。通常应用程序的框架负责调用 ExitProcess() 函数。对于 C++ 运行库来说,这一调用发生在应用程序的 main() 函数返回之后。

2.模块介绍

创建进程子进程startClone( )模块,主函数模块,互斥信号量的创建与释放。

3.设计步骤

(1)编写基本的 Win32 Consol Application
步骤 1:登录进入 Windows 系统,启动 VC++ 6.0。
步骤 2:在“FILE”菜单中单击“NEW”子菜单,在“projects”选项卡中选择“Win32 ConsolApplication”,然后在“Project name”处输入工程名,在“Location” 处输入工程目录。创建一个新的控制台应用程序工程。
步骤 3:在“FILE”菜单中单击“NEW”子菜单,在“Files”选项卡中选择“C++ Source File”,然后在“File” 处输入 C/C++源程序的文件名。
步骤 4:将清单 1-1 所示的程序清单复制到新创建的 C/C++源程序中。编译成可执行文件。
步骤 5:在“开始”菜单中单击“程序”-“附件”-“命令提示符”命令,进入 Windows“命令提示符”窗口,然后进入工程目录中的 debug 子目录,执行编译好的可执行程序,列出运行结果(如果运行不成功,则可能的原因是什么?
答:路径不对或者没有编译文件等。

图1-1 一个简单的 Windows 控制台应用程序输出结果

在这里插入图片描述
(2)创建进程
本实验显示了创建子进程的基本框架。该程序只是再一次地启动自身,显示它的系统进程 ID和它在进程列表中的位置。
步骤 1:创建一个“Win32 Consol Application”工程,然后拷贝清单 1-2 中的程序,编译成可执行文件。
步骤 2:在“命令提示符”窗口运行步骤 1 中生成的可执行文件,列出运行结果。按下ctrl+alt+del,调用 windows 的任务管理器,记录进程相关的行为属性。
步骤 3:在“命令提示符”窗口加入参数重新运行生成的可执行文件,列出运行结果。按下ctrl+alt+del,调用 windows 的任务管理器,记录进程相关的行为属性。
步骤 4:修改清单 1-2 中的程序,将 nClone 的定义和初始化方法按程序注释中的修改方法进行
修改,编译成可执行文件(执行前请先保存已经完成的工作)。再按步骤 2 中的方式运行,看看结果会有什么不一样。列出行结果。从中你可以得出什么结论?说明 nClone 的作用。 变量的定义和初始化方法(位置)对程序的执行结果有影响吗?为什么?
答:控制程序执行过程,当nClone>5时跳出循环,创建子进程结束;有,在第二次更改中,由于nClone每次都初始化为0,会陷入死循环,不断创建子进程。

图1-2 创建子进程(1)

在这里插入图片描述

图1-3 创建子进程(2)

在这里插入图片描述
(3)父子进程的简单通信及终止进程
步骤 1:创建一个“Win32 Consol Application”工程,然后拷贝清单 1-3 中的程序,编译成
可执行文件。
步骤 2:在 VC 的工具栏单击“Execute Program”(执行程序) 按钮,或者按 Ctrl + F5 键,或者
在“命令提示符”窗口运行步骤 1 中生成的可执行文件,列出运行结果。
步骤 3:按源程序中注释中的提示,修改源程序 1-3,编译执行(执行前请先保存已经完成的
工作),列出运行结果。在程序中加入跟踪语句,或调试运行程序,同时参考 MSDN 中的帮助文件
CreateProcess()的使用方法,理解父子进程如何传递参数。给出程序执行过程的大概描述。
步骤 4:按源程序中注释中的提示,修改源程序 1-3,编译执行,列出运行结果。
步骤 5:参考 MSDN 中的帮助文件 CreateMutex() 、 OpenMutex() 、 ReleaseMutex() 和WaitForSingleObject()的使用方法,理解父子进程如何利用互斥体进行同步的。给出父子进程同步过程的一个大概描述。
答:CreateMutex() 创建互斥体hMutexSuicide信号、 OpenMutex()打开互斥体、 ReleaseMutex()释放互斥体、WaitForSingleObject()检测Hhandle信号状态,通过这些只允许有一个状态被创建或者使用也就是信号量唯一,实现进程同步。

图1-4父子进程的简单通信及终止进程的示例程序

在这里插入图片描述

四、详细设计

  1. 数据结构
    数组、函数调用,父子进程参数的传递、父子进程利用互斥信号进行同步、互斥体的创建、获得、检测与释放、API接口等。
  2. 程序流程图

图1-5 一个简单的 Windows 控制台应用程序流程图

在这里插入图片描述

图1-6 创建子进程流程图

在这里插入图片描述

图1-7父子进程的简单通信及终止进程的示例程序流程图

在这里插入图片描述
3. 关键代码
1-1 一个简单的 Windows 控制台应用程序

#include <windows.h> 
#include <iostream> 
#include <stdio.h>
// hello 项目
# include <iostream> 
void main() 
{ 
 std::cout << “Hello, Win32 Consol Application” << std :: endl ; 
}

1-2 创建子进程

// 创建传递过来的进程的克隆过程并赋于其 ID 值
void StartClone(int nCloneID) 
{ 
 // 提取用于当前可执行文件的文件名
 TCHAR szFilename[MAX_PATH] ; 
 GetModuleFileName(NULL, szFilename, MAX_PATH) ; 
 
 // 格式化用于子进程的命令行并通知其 EXE 文件名和克隆 ID 
 TCHAR szCmdLine[MAX_PATH]; 
 sprintf(szCmdLine,"\"%s\" %d",szFilename,nCloneID); 
 // 用于子进程的 STARTUPINFO 结构
 STARTUPINFO si; 
 ZeroMemory(&si , sizeof(si) ) ; 
 si.cb = sizeof(si) ; // 必须是本结构的大小
 // 返回的用于子进程的进程信息
 PROCESS_INFORMATION pi; 
 // 利用同样的可执行文件和命令行创建进程,并赋于其子进程的性质
 BOOL bCreateOK=::CreateProcess( 
 szFilename, // 产生这个 EXE 的应用程序的名称
 szCmdLine, // 告诉其行为像一个子进程的标志
 NULL, // 缺省的进程安全性
 NULL, // 缺省的线程安全性
 FALSE, // 不继承句柄
 CREATE_NEW_CONSOLE, // 使用新的控制台
 NULL, // 新的环境
 NULL, // 当前目录
 &si, // 启动信息
 &pi) ; // 返回的进程信息
 // 对子进程释放引用
 if (bCreateOK) 
 { 
 CloseHandle(pi.hProcess) ; 
 CloseHandle(pi.hThread) ; 
 } 
} 

1-3 父子进程的简单通信及终止进程的示例程序

// procterm 项目
static LPCTSTR g_szMutexName = "w2kdg.ProcTerm.mutex.Suicide" ; 
// 创建当前进程的克隆进程的简单方法
void StartClone() 
{ 
 // 提取当前可执行文件的文件名
 TCHAR szFilename[MAX_PATH] ; 
 GetModuleFileName(NULL, szFilename, MAX_PATH) ; 
 // 格式化用于子进程的命令行,字符串“child”将作为形参传递给子进程的 main 函数
 TCHAR szCmdLine[MAX_PATH] ; 
//实验 1-3 步骤 3:将下句中的字符串 child 改为别的字符串,重新编译执行,执行前请先保存已经
完成的工作
 sprintf(szCmdLine, "\"%s\"child" , szFilename) ; 
 // 子进程的启动信息结构
 STARTUPINFO si; 
 ZeroMemory(&si,sizeof(si)) ; 
 si.cb = sizeof(si) ; // 应当是此结构的大小
 // 返回的用于子进程的进程信息
 PROCESS_INFORMATION pi; 
 // 用同样的可执行文件名和命令行创建进程,并指明它是一个子进程
 BOOL bCreateOK=CreateProcess( 
 szFilename, // 产生的应用程序的名称 (本 EXE 文件) 
 szCmdLine, // 告诉我们这是一个子进程的标志
 NULL, // 用于进程的缺省的安全性
 NULL, // 用于线程的缺省安全性
 FALSE, // 不继承句柄
 CREATE_NEW_CONSOLE, //创建新窗口
 NULL, // 新环境
 NULL, // 当前目录
 &si, // 启动信息结构
 &pi ) ; // 返回的进程信息
 // 释放指向子进程的引用
 if (bCreateOK) 
 { 
 CloseHandle(pi.hProcess) ; 
 CloseHandle(pi.hThread) ; 
 } 
} 
void Parent() 
{ 
 // 创建“自杀”互斥程序体
 HANDLE hMutexSuicide=CreateMutex(  
 NULL, // 缺省的安全性
 TRUE, // 最初拥有的
 g_szMutexName) ; // 互斥体名称
 if (hMutexSuicide != NULL) 
 { 
 // 创建子进程
 std :: cout << "Creating the child process." << std :: endl; 
 StartClone() ; 
 // 指令子进程“杀”掉自身
 std :: cout << "Telling the child process to quit. "<< std :: endl; 
 //等待父进程的键盘响应
 getchar() ; 
 //释放互斥体的所有权,这个信号会发送给子进程的 WaitForSingleObject 过程
 ReleaseMutex(hMutexSuicide) ; 
 // 消除句柄
 CloseHandle(hMutexSuicide) ; 
 } 
} 

五、实验结果与分析

实验1-1结果分析:修改void成int,然后加上return 0,从main()函数开始,运行输出Hello, Win32 Consol Application。
实验1-2结果分析:从main()函数开始,首先判断argc的值(argc初始值默认为1)因为argc不满足大于1,所以不能将argv[1]赋值给nClone;然后nClone < c_nCloneMax ,则调用StartClone (++nClone)函数,创建子进程;创建子进程后,argc的值变为2,然后将自增的nClone赋值argv[1],然后将继续执行main()函数,直到(nClone >c_nCloneMax),跳出,结束创建新进程。
实验1-3结果分析:从main()函数开始,首先判断argc的值(argc初始值默认为1),决定进行父进程还是子进程,因为argc不满足大于1,所以调用parent()函数,在执行parent()函数过程中调用StartClone() ;然后通过sprintf(szCmdLine, “”%s"child" , szFilename)将argv[1]赋值child,后面满足条件后调用child()函数;由于设置了互斥信号,则只允许一个进程进行,所以只有当父进程释放互斥信号hMutexSuicide时,子进程检测获得才结束进程。

六、小结与心得体会

通过这个实验加深了我对操作系统的进程概念的了解,理解 Windows 进程的“一生”所有进程都是以调用CreateProcess()API函数开始的ExitProcess函数结束的。利用 CreateMutex() API 可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体,放弃共享资源时需要在该对象上调用 ReleaseMute() API函数等。

一、题目二

实验二 Linux 进程管理

二、实验目的

通过进程的创建、撤销和运行加深对进程概念和进程并发执行的理解,明确进程和程序之间的区别。

三、总体设计

1.背景知识

在 Linux 中创建子进程要使用 fork()函数,执行新的命令要使用 exec()系列函数,等待子进 程结束使用 wait()函数,结束终止进程使用 exit()函数。fork()原型如下:pid_t fork(void);fork 建立一个子进程,父进程继续运行,子进程在同样的位置执行同样的程序。对于父进程,fork()返回子进程的 pid, 对于子进程,fork()返回 0。出错时返回-1。

2.模块介绍

2-1:一个父进程,两个子进程
2-2:一个父进程,一个子进程
2-3:一个父进程,多个子进程

3.设计步骤

(1)进程的创建
任务要求:编写一段程序,使用系统调用 fork()创建两个子进程。当此程序运行时,在系统 中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”; 两子进程分别显示字符“b”和字符“c”。
步骤 1:使用 vi 或 gedit 新建一个 fork_demo.c 程序,然后拷贝清单 2-1 中的程序,使用 cc 或者gcc 编译成可执行文件 fork_demo。例如,可以使用 gcc –o fork_demo fork_demo.c 完成编译。
步骤 2:在命令行输入./fork_demo 运行该程序。

图2-1 进程的创建输出结果

在这里插入图片描述
(2)子进程执行新任务
任务要求:编写一段程序,使用系统调用 fork()创建一个子进程。子进程通过系统调用 exec 更换自己原有的执行代码,转去执行 Linux 命令/bin/ls (显示当前目录的列表),然后调用 exit()函 数结束。父进程则调用 waitpid()等待子进程结束,并在子进程结束后显示子进程的标识符,然后正 常结束。程序执行过程如图 2-1 所示。
步骤 1:使用 vi 或 gedit 新建一个 exec_demo.c 程序,然后拷贝清单 2-2 中的程序(该程序的执 行如图 2-1 所示),使用 cc 或者 gcc 编译成可执行文件 exec_demo。例如,可以使用 gcc –o exec_demo exec_demo.c 完成编译。
步骤 2:在命令行输入./exec_demo 运行该程序。
步骤 3:观察该程序在屏幕上的显示结果,并分析。

图2-2 子进程执行新任务输出结果

在这里插入图片描述
(3)实现一个简单的 shell(命令行解释器) (此任务有一些难度,可选做)。
任务要求:要设计的 shell 类似于 sh,bash,csh 等,必须支持以下内部命令:
cd <目录>更改当前的工作目录到另一个<目录>。如果<目录>未指定,输出当前工作目录。如
果<目录>不存在,应当有适当的错误信息提示。这个命令应该也能改变 PWD 的环境变量。
environ 列出所有环境变量字符串的设置(类似于 Unix 系统下的 env 命令)。
echo <内容 > 显示 echo 后的内容且换行
help 简短概要的输出你的 shell 的使用方法和基本功能。
jobs 输出 shell 当前的一系列子进程,必须提供子进程的命名和 PID 号。
quit,exit,bye 退出 shell。

图2-3 实现一个简单的 shell输出结果

在这里插入图片描述

四、详细设计

  1. 数据结构
    一个进程创建多个子进程时,则子进程之间具有兄弟关系,数据结构为链表结构,也运用了一些C++库函数。

  2. 程序流程图

图2-4 进程的创建流程图

在这里插入图片描述

图2-5 子进程执行新任务流程图

在这里插入图片描述

图2-6 实现一个简单的 shell(命令行解释器)流程图

在这里插入图片描述
3. 关键代码
2-1 创建进程

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main ()
{
int x;
while((x=fork())==-1);
if (x==0){
x=fork();
if(x>0)
printf("b");
else
printf("c");
}
else
printf("a");
}

2-2 子进程执行新任务

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
/* fork a child process */
pid = fork();
if (pid < 0)
{ /* error occurred */
fprintf(stderr, "Fork Failed");
return 1;
}
else if (pid == 0)
{ /* 子进程 */
execlp("/bin/ls","ls",NULL);
}
else { /* 父进程 */
/* 父进程将一直等待,直到子进程运行完毕*/
wait(NULL);
printf("Child Complete");
}
return 0;
} } 
return 0; 
}

2-3 实现一个简单的 shell(命令行解释器) (选做)

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    char cmd[666];
    char cata[100];
    while(1)
    {
        int len,i,flag,cnt;
        printf("Enter commands:");
// print String
        scanf("%s",cmd);
// Calculation String
        len = strlen(cmd);

// for cd
        if(cmd[0]=='c')
        {
            flag=0;
            cnt=0;
// Start after command
            for(i=3; i<len-1; i++)
            {
// String is not null
                if(cmd[i]!=' ') flag=1;
                if(flag)
                {
                    cata[cnt++] = cmd[i];
                }
            }
// String is null
            if(cnt==0)
            {
                printf("path error!\n");
                cata[0]='.';
                cata[1]='\0';
            }
        }

//for echo
        if(cmd[0]=='e'&&cmd[1]=='c')
        {

            flag = 0;
            for(i=5; i<len-1; i++)
            {
                if(cmd[i]!=' ') flag=1;
                if(flag)
                {
                    putchar(cmd[i]);
                }
            }
            if(flag) putchar('\n');
        }

// for help
        if(cmd[0]=='h')
        {
            printf("/**********Method***********/\n");
            printf("print cd<catalog> :find directory\n");
            printf("print environ :List set\n");
            printf("print echo<content> : print content\n");
            printf("print help :List Method\n");
            printf("print jobs :provide PID\n");
            printf("print quit,exit,bye :break \n");
            printf("/******Method***************/\n");
        }

// for quit,exit,bye
        if(cmd[0]=='q'||cmd[1]=='x'||cmd[0]=='b')
        {
            printf("break\n");
            return 0;
        }
        else
        {

            cnt=0;



// child process
            pid_t pid = fork();
            if(pid<0)
            {
// error occurred
                fprintf(stderr,"Fork Failed" );
                return 1;
            }
            else if(pid==0)
            {
//cd
                if(cmd[0]=='c')
                {
                    execlp("/bin/ls",cata,NULL);
                }
//jobs
                else if(cmd[0]=='j')
                {
                    execlp("pstree","-p",NULL);
                }
//environ
                else if(cmd[1]=='n')
                {
                    execlp("env","",NULL);
                }
            }
            else
            {
//wait child process exit
                wait();
            }
        }
        printf("\n");
    }
    return 0;
}

五、实验结果与分析

实验2-1结果分析:修改后代码清单2-1后,从main()函数开始,运行父进程,通过while((x=fork())== -1)判断创建进程是否成功,如果x>0,则继续创建子进程,若成功,则此时有两个子进程和一个父进程,先创建的子进程会输出c,接下来是父进程执行完毕,输出a,后面是后创建的子进程执行完毕输出b;所以最终的输出结果是abc。
实验2-2结果分析:从main()函数开始,父进程创建子进程,首先判断子进程是否创建成功,如果pid<0则创建进程失败,当pid=0时,运行子进程,输出系统当前目录。父进程将会一直等待子进程信号,只有当子进程释放信号,父进程输出“Child Complete”。
实验2-3结果分析:从main()函数开始,根据下面这些关键字的标志位进行设置判断,然后再在判断之后对下面的功能进行实现:cd <目录>更改当前的工作目录到另一个<目录>。如果<目录>未指定,输出当前工作目录。如果<目录>不存在,应当有适当的错误信息提示。这个命令应该也能改变 PWD 的环境变量。environ 列出所有环境变量字符串的设置(类似于Unix 系统下的 env 命令)。echo <内容 > 显示 echo 后的内容且换行help 简短概要的输出你的 shell 的使用方法和基本功能。jobs 输出 shell 当前的一系列子进程,必须提供子进程的命名和 PID 号。quit,exit,bye 退出 shell,也就是依次终止运行的父子进程。

六、小结与心得体会

通过这个实验加深了我对Linux操作系统的进程概念的了解,也学会了在Linux基本运行,也使我明白了在Linux系统中子进程的创建,以及父子进程的运行过程,加深了对进程运行的理解。在Linux中利用fork建立一个子进程,父进程继续运行,子进程在同样的位置执行同样的程序。对于父进程,fork()返回子进程的 pid, 对于子进程,fork()返回 0,出错时返回-1,while((x=fork())==-1)这句话是用来判断子进程是否能创建成功,而且当x=0时运行子进程,当x>0时父进程执行,而x<0时,则进程创建不成功,通过代码确定父子进程的先后执行顺序。同时也完成实现一个简单的 shell(命令行解释器),这个是选做但我也挑战自己做这道题目,从中也收获非常多,采用了关键字这种思路去慢慢分块实现不同命令的功能,对于逻辑处理也提升很多。

一、题目三

实验三 互斥与同步

二、实验目的

(1) 回顾操作系统进程、线程的有关概念,加深对 Windows 线程的理解。
(2) 了解互斥体对象,利用互斥与同步操作编写生产者-消费者问题的并发程序,加深对 P (即semWait)、V(即 semSignal)原语以及利用 P、V 原语进行进程间同步与互斥操作的理解。

三、总体设计

1.基本原理与算法

1.1、利用的是互斥与同步中的信号量
1.2、使用信号量解决有限缓冲区生产者和消费者问题

2.模块介绍

主要有两大模块:生产者和消费者;生产者又包括Produce(),Append(); 消费者包括Take(),Consume(); 线程的创建。

3.设计步骤

(1) 生产者消费者问题
步骤 1:创建一个“Win32 Consol Application”工程,然后拷贝清单 3-1 中的程序,编译成可执行文件。
步骤 2:在“命令提示符”窗口运行步骤 1 中生成的可执行文件,列出运行结果。
步骤 3:仔细阅读源程序,找出创建线程的 WINDOWS API 函数,回答下列问题:线程的第一个执行函数是什么(从哪里开始执行)?它位于创建线程的 API 函数的第几个参数中?
答:Produce()函数,位于第三个参数。
步骤 4:修改清单 3-1 中的程序,调整生产者线程和消费者线程的个数,使得消费者数目大与生产者,看看结果有何不同。察看运行结果,从中你可以得出什么结论?
答:当生产者个数多于消费者个数时生产速度快,生产者经常等待消费者对产品进行消费;反之,消费者经常等待生产者生产。
步骤 5:修改清单 3-1 中的程序,按程序注释中的说明修改信号量 EmptySemaphore 的初始化方法,看看结果有何不同。
答:结果为空,因为参数设置成可用资源为0,所以进程无法使用。
步骤 6:根据步骤 4 的结果,并查看 MSDN,回答下列问题:
1)CreateMutex 中有几个参数,各代表什么含义。
2)CreateSemaphore 中有几个参数,各代表什么含义,信号量的初值在第几个参数中。
3)程序中 P、V 原语所对应的实际 Windows API 函数是什么,写出这几条语句。
4)CreateMutex 能用 CreateSemaphore 替代吗?尝试修改程序 3-1,将信号量 Mutex 完全用CreateSemaphore 及相关函数实现。写出要修改的语句。
答:
(1)3个;LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针BOOLbInitialOwner, // 初始化互斥对象的所有者;LPCTSTRlpName // 指向互斥对象名的指针;第二个参数是FALSE,表示刚刚创建的这个Mutex不属于任何线程。

(2)4个;//第一个参数:安全属性,如果为NULL则是默认安全属性 //第二个参数:信号量的初始值,要>=0且<=第三个参数 //第三个参数:信号量的最大值 //第四个参数:信号量的名称。

(3)WaitForSingleObject(FullSemaphore,INFINITE); P(full);
WaitForSingleObject(Mutex,INFINITE); //P(mutex);
ReleaseMutex(Mutex); //V(mutex);
ReleaseSemaphore(EmptySemaphore,1,NULL); //V(empty);

(4)可以,Mutex=CreateSemaphore(NULL,false,false,NULL);生产者,消费者内: ReleaseMutex(Mutex);改为 ReleaseSemaphore(Mutex,1,NULL)。

图3-1 生产者消费者问题输出结果

在这里插入图片描述
(2) 读者写者问题
根据实验(1)中所熟悉的 P、V 原语对应的实际 Windows API 函数,并参考教材中读者、写者问题的算法原理,尝试利用 Windows API 函数实现第一类读者写者问题(读者优先)。

图3-2 读者写者问题输出结果

在这里插入图片描述
四、详细设计

  1. 数据结构
    应用了循环队列、数组,信号量。
  2. 程序流程图

图3-3 生产者消费者问题流程图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

图3-4 读者写者问题流程图

在这里插入图片描述
3. 关键代码
3-1 创建进程

int main()
{
//创建各个互斥信号
//注意,互斥信号量和同步信号量的定义方法不同,互斥信号量调用的是 CreateMutex 函数,同步信号量调用的是 CreateSemaphore 函数,函数的返回值都是句柄。
    Mutex = CreateMutex(NULL,FALSE,NULL);
   EmptySemaphore = CreateSemaphore(NULL,SIZE_OF_BUFFER,SIZE_OF_BUFFER,NULL);
//将上句做如下修改,看看结果会怎样
   // EmptySemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER-1,NULL);
    FullSemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER,NULL);
//调整下面的数值,可以发现,当生产者个数多于消费者个数时,
//生产速度快,生产者经常等待消费者;反之,消费者经常等待
    const unsigned short PRODUCERS_COUNT = 10; //生产者的个数
    const unsigned short CONSUMERS_COUNT = 1; //消费者的个数
//总的线程数
    const unsigned short THREADS_COUNT = PRODUCERS_COUNT+CONSUMERS_COUNT;
    HANDLE hThreads[THREADS_COUNT]; //各线程的 handle
    DWORD producerID[PRODUCERS_COUNT]; //生产者线程的标识符
    DWORD consumerID[CONSUMERS_COUNT]; //消费者线程的标识符
//创建生产者线程
    for (int i=0; i<PRODUCERS_COUNT; ++i)
    {
        hThreads[i]=CreateThread(NULL,0,Producer,NULL,0,&producerID[i]);
        if (hThreads[i]==NULL) return -1;
    }
//创建消费者线程
    for (int i=0; i<CONSUMERS_COUNT; ++i)
    {
        hThreads[PRODUCERS_COUNT+i]=CreateThread(NULL,0,Consumer,NULL,0,&consumerID[i]);
        if (hThreads[i]==NULL) return -1;
    }
    while(p_ccontinue)
    {
        if(getchar())  //按回车后终止程序运行
        {
            p_ccontinue = false;
        }
    }
    return 0;
}
//消费者
DWORD WINAPI Consumer(LPVOID lpPara)
{
    while(p_ccontinue)
    {
        WaitForSingleObject(FullSemaphore,INFINITE); //P(full);
        WaitForSingleObject(Mutex,INFINITE); //P(mutex);
        Take();
        Consume();
        Sleep(1500);
        ReleaseMutex(Mutex); //V(mutex);
        ReleaseSemaphore(EmptySemaphore,1,NULL); //V(empty);
    }
    return 0;
}

3-2 子进程执行新任务

int main()
{
    Mutex = CreateMutex(NULL,FALSE,NULL);
     X = CreateMutex(NULL,FALSE,NULL);
    const unsigned short READERS_COUNT = 2;//创建两个读进程
    const unsigned short WRITERS_COUNT = 1;//创建一个写进程
    const unsigned short THREADS_COUNT = READERS_COUNT+WRITERS_COUNT;
    HANDLE hThreads[THREADS_COUNT];
    //创建写线程
    for (int i=0; i<WRITERS_COUNT; ++i)
    {
        hThreads[i]=CreateThread(NULL,0,writer,NULL,0,NULL);
        if (hThreads[i]==NULL) return -1;
    }
    //创建读线程
    for (int i=0; i<READERS_COUNT; ++i)
    {
        hThreads[WRITERS_COUNT+i]=CreateThread(NULL,0,reader,NULL,0,NULL);//生产者线程函数Producer 线程ID&producerID[i]
        if (hThreads[i]==NULL) return -1;
    }
    //程序人为终止操作设计
    while(p_ccontinue)
    {
        if(getchar())  //按回车后终止程序运行
        {
            p_ccontinue = false;
        }
    }
    return 0;
}
//写者
DWORD WINAPI writer(LPVOID lpPara)
{
    while(p_ccontinue)
    {
        WaitForSingleObject(Mutex,INFINITE);
        Write();
        Sleep(1500);
        ReleaseMutex(Mutex); //V(mutex);
    }
    return 0;
}

五、实验结果与分析

实验3-1结果分析:修改后代码清单3-1后,从main()函数开始,首先创建了生产者-消费者问题中应用到的互斥信号和同步信号以及其他基础定义,创建消费者生产者线程;最初生产者满足条件生产产品,所以先执行生产者,然后当资源有产品时,会执行消费者,生产者和消费者在代码运行过程中出现是随机的,当生产者多于消费者时,生产速度快,生产者经常等待消费者;反之,消费者经常等待;若缓冲区为空,则必定是生产者运行,缓冲区为满,则消费者运行,生产者等待,而对于结果的表示,则是调用了Append()和Consume()中的循环输出。
实验3-2结果分析:这个是读写者中读者优先的问题,从main()函数开始,首先创建了生产者-消费者问题中应用到的两个互斥信号以及其他基础定义,创建消读者写者线程;最初写者先创建先运行,然后会执行读者线程,由于设置了两个互斥信号量可以将其中一个作为读者优先设置信号量,当第一个读者拿到这个互斥信号量时,写者就得等待读者释放这个信号量,而其他读者就不用就直接拿到不用判断可以运行输出。对于结果的表示,也是调用了read ()和Write()函数进行输出。

六、小结与心得体会

通过这个实验,我更好的了解互斥体对象,利用互斥与同步操作编写生产者-消费者问题的并发程序,加深对 P (即 semWait)、V(即 semSignal)原语以及利用 P、V 原语进行进程间同步与互斥操作的理解,生产者消费者问题是一个典型的例题,主要涉及同步与互斥,这也保证了在程序运行过程中只能有一个线程进行。然后对于3-2问题,我借鉴了《操作系统》课程书籍中的读者优先的思路,并将其实现,在这个过程中收获非常多也非常大,对于信号量以及进程的了解也更加深刻。

以上只是操作系统课设部分设计内容,如果想要完整操作系统课设源代码资源请点击下面资源链接进行下载,希望能帮助到你!

操作系统课设完整资源:点击打开下载资源

猜你喜欢

转载自blog.csdn.net/rothschild666/article/details/103638277