操作系統課設詳細解答

操作系統課設詳細解答

一、題目一

實驗一 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問題,我借鑑了《操作系統》課程書籍中的讀者優先的思路,並將其實現,在這個過程中收穫非常多也非常大,對於信號量以及進程的瞭解也更加深刻。

以上只是操作系統課設部分設計內容,如果想要完整操作系統課設源代碼資源請點擊下面資源鏈接進行下載,希望能幫助到你!

操作系統課設完整資源:點擊打開下載資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章