操作系统实验二

更新时间:2023-10-13 01:29:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

实验二 并发与调度

一、实验目的

在本实验中,通过对事件和互斥体对象的了解,来加深对Windows 2000线程同步的理解。通过分析实验程序,了解管理事件对象的API。了解在进程中如何使用事件对象,在进程中如何使用互斥体对象,线程如何通过文件映射对象发送数据。

在Linux Redhat 9.0操作系统平台上,用pipe()创建一个管道文件,然后用fork()创建两个生产进程和两个消费进程,它们之间通过pipe()传递消息。

二、实验环境

硬件环境:计算机一台,局域网环境; 软件环境:Windows 2000 Professional,Linux Redhat 9.0操作系统平台,Visual C++ 6.0

企业版。

三、实验内容和步骤

第一部分:互斥体对象

本程序中显示的类CCountUpDown使用了一个互斥体来保证对两个线程间单一数值的访问。每个线程都企图获得控制权来改变该数值,然后将该数值写入输出流中。创建者实际上创建的是互斥体对象,计数方法执行等待并释放,为的是共同使用互斥体所需的资源 (因而也就是共享资源) 。 利用互斥体保护共享资源

// mutex项目

# include # include class CCountUpDown {

public:

CCountUpDown(int nAccesses) :

m_hThreadInc(INVALID_HANDLE_VALUE) , m_hThreadDec(INVALID_HANDLE_VALUE) , m_hMutexValue(INVALID_HANDLE_VALUE) , m_nValue(0) ,

m_nAccess(nAccesses) {

m_hMutexValue = :: CreateMutex( NULL, TRUE, NULL) ;

m_hThreadInc = :: CreateThread( NULL, 0, IncThreadProc,

reinterpret_cast (this) , 0, NULL) ;

m_hThreadDec = :: CreateThread( NULL, 0, DecThreadProc,

reinterpret_cast (this) , 0, NULL) ;

:: ReleaseMutex(m_hMutexValue) ; }

virtual ~CCountUpDown()

{

:: CloseHandle(m_hThreadInc) ; :: CloseHandle(m_hThreadDec) ; :: CloseHandle(m_hMutexValue) ;

}

virtual void WaitForCompletion() {

if (m_hThreadInc != INVALID_HANDLE_VALUE && m_hThreadDec != INVALID_HANDLE_VALUE) {

:: WaitForSingleObject(m_hThreadInc, INFINITE) ; :: WaitForSingleObject(m_hThreadDec, INFINITE) ; } }

protected:

virtual void DoCount(int nStep) {

while (m_nAccess > 0) {

:: WaitForSingleObject(m_hMutexValue, INFINITE) ; m_nValue += nStep;

std :: cout << “thread: ” << :: GetCurrentThreadId() << “value: ” << m_nValue

<< “access: ” << m_nAccess << std :: endl; --m_nAccess; :: Sleep(1000) ; // 使显示速度放慢 :: ReleaseMutex(m_hMutexValue) ; } }

static DWORD WINAPI IncThreadProc(LPVOID lpParam) {

CCountUpDown* pThis =

reinterpret_cast < CCountUpDown* > (lpParam) ; pThis -> DoCount(+1) ; return(0) ; }

static DWORD WINAPI DecThreadProc(LPVOID lpParam) {

CCountUpDown* pThis =

reinterpret_cast (lpParam) ; pThis -> DoCount(-1) ; return(0) ; }

protected:

HANDLE m_hThreadInc; HANDLE m_hThreadDec; HANDLE m_hMutexValue; int m_nValue; int m_nAccess ;

} ;

void main()

{ CCountUpDown ud(50) ; ud.WaitForCompletion() ; }

分析程序的运行结果,可以看到线程 (加和减线程) 的交替执行 (因为Sleep() API允许Windows切换线程) 。在每次运行之后,数值应该返回初始值 (0) ,因为在每次运行之后写入线程在等待队列中变成最后一个,内核保证它在其他线程工作时不会再运行。

1) 请描述运行结果 (如果运行不成功,则可能的原因是什么?) :

____________________________________________________________________ ________________________________________________________________________ 2) 根据运行输出结果,对照分析程序,可以看出程序运行的流程吗?请简单描述: ____________________________________________________________________ ________________________________________________________________________

第二部分 线程通过文件对象发送数据

Windows 2000提供的线程间通讯类内核对象允许同一进程或跨进程的线程之间互相发送信息,包括文件、文件映射、邮件位和命名管道等,其中最常用的是文件和文件映射。这类对象允许一个线程很容易地向同一进程或其他进程中的另一线程发送信息。

演示线程通过文件对象发送数据

# include # include

static LPCTSTR g_szFileName = “w2kdg.Fileobj.file.data.txt” ; static DWORD WINAPI ThreadProc (LPVOID lpParam) {

LONG nAdd = reinterpret_cast (lpParam) ; TCHAR szFullName [MAX_PATH] ;

:: GetTempPath(MAX_PATH, szFullName) ; // 取得路径 :: strcat(szFullName, g_szFileName) ; HANDLE hFile = :: CreateFile( szFullName, // 文件的完全名称 GENERIC_READ | GENERIC_WRITE, // 具有所有的访问权 FILE_SHARE_READ, // 允许其他线程读取 NULL, // 缺省的安全性 OPEN_ALWAYS, // 创建或打开文件 FILE_ATTRIBUTE_NORMAL, // 普通文件 NULL) ; // 无模板文件 if (hFile != INVALID_HANDLE_VALUE) {

LONG nValue(0) ; DWORD dwXfer(0) ; :: ReadFile( hFile, // 要读取的文件 reinterpret_cast (&nValue) , // 缓冲区 sizeof(nValue) , // 缓冲区容量 &dwXfer, // 读取的字节数 NULL) ; // 无重叠I/O

if (dwXfer == sizeof(nValue) ) {

std :: cout << “read: ” << nValue << std :: endl; }

nValue += nAdd;

:: SetFilePointer(hFile, 0, NULL, FILE_BEGIN) ; :: WriteFile( hFile, // 要写入的文件 reinterpret_cast (&nValue) , // 数据 sizeof(nValue), // 缓冲区容量 &dwXfer, // 写入的字节数

NULL) ; // 无重叠I/O if (dwXfer == sizeof(nValue) )

{

std :: cout << “write: ”<< nValue << std :: endl; }

:: CloseHandle(hFile) ;

hFile = INVALID_HANDLE_VALUE; }

return(0) ; }

void main() {

for (int nTotal = 100; nTotal > 0; --nTotal)

{

HANDLE hThread = :: CreateThread(

NULL, // 缺省的安全性 0, // 缺省的堆栈 ThreadProc, // 线程函数 reinterpret_cast (1) , // 增量 0, // 无特殊的创建标志

NULL) ; // 忽略线程id :: WaitForSingleObject(hThread, INFINITE) ;

:: Sleep(500) ;

:: CloseHandle(hThread) ;

hThread = INVALID_HANDLE_VALUE;

} }

运行结果 (如果运行不成功,则可能的原因是什么?) :

____________________________________________________________________ ________________________________________________________________________ 阅读和分析程序,请回答问题:

1) 程序中启动了多少个单独的读写线程?

____________________________________________________________________ 2) 使用了哪个系统API函数来创建线程例程?

____________________________________________________________________ 3) 文件的读和写操作分别使用了哪个API函数?

____________________________________________________________________ ________________________________________________________________________ 每次运行进程时,都可看到程序中的每个线程从前面的线程中读取数据并将数据增加,文件中的数值连续增加。这个示例是很简单的通讯机制。可将这一示例用作编写自己的文件读/写代码的模板。

请注意程序中写入之前文件指针的重置。重置文件指针是必要的,因为该指针在读取结束时将处于前四个字节之后,同一指针还要用于向文件写入数据。如果函数向该处写入新数值,则下次进程运行时,只能读到原来的数值。那么:

4) 在程序中,重置文件指针使用了哪一个函数?

____________________________________________________________________ 5) 从输出结果,对照分析程序,可以看出程序运行的流程吗?请简单描述:

____________________________________________________________________ ________________________________________________________________________ 第三部分 用pipe()创建一个管道文件,然后用fork()创建两个生产进程和两个消费进程,它们之间通过pipe()传递消息。

fork系统调用 pid=fork();创建一个子进程,子进程是父进程的完整复制,正常返回值为非负整数。对于父进程来说该数大于0,是子进程的编号(pid);对于子进程来说该数为零。正是利用返回值的不同可以决定二者的后续动作。

pipe系统调用 ret_val=pipe(fd); 参数定义为 int fd[2]。创建一个管道文件,返回两个文件描述符fd[0]和fd[1],它们分别用于管道文件的读和写操作。管道文件创建后,可以被fork所创建的子进程共享。

#include “sys/types.h” #include “sys/file.h” #include “unistd.h” char r_buf[4]; char w_buf[4]; int pipe_fd[2];

pid_t pid1,pid2,pid3,pid4; int producer(int id); int consumer(int id);

int main(int argc,char **argv) { if(pipe(pipe_fd)<0)

{ printf(“pipe create error.\\n”); exit(-1); } else {

printf(“pipe is created successfully!\\n”); if((pid1=fork())==0) producer(1); if((pid2=fork())==0) producer(2); if((pid3=fork())==0) consumer(1); if((pid4=fork())==0) consumer(2);} close(pipe_fd[0]); close(pipe_fd[1]); int i,pid,status;

for( i=0;i<4;i++) pid=wait(&status); exit(0);}

int producer(int id)

{ printf(“producer %d is running!\\n”,id); close(pipe_fd[0]); int i=0;

for(i=1;i<10;i++) {

sleep(3);

if(id==1) strcpy(w_buf,”aaa\\0”); else strcpy(w_buf,”bbb\\0”); if(write(pipe_fd[1],w_buf,4)==-1) printf(“write to pipe error\\n”); }

close(pipe_fd[1]); printf(“producer %d is over!\\n”,id); exit(id);}

int consumer(int id) { close(pipe_fd[1]);

printf(“consumer %d is running!\\n”,id); if(id==1) strcpy(w_buf,”ccc\\0”); else strcpy(w_buf,”ddd\\0”); while(1) { sleep(1);

strcpy(r_buf,”eee\\0”);

if(read(pipe_fd[0],r_buf,4)==0) break;

printf(“Consumer %d get %s, while the w_buf is %s\\n”, id,r_buf,w_buf); }

close(pipe_fd[0]);

printf(“consumer %d is over!\\n”,id); exit(id); }

先阅读和分析程序,写出运算结果,并加以解释:

__________________________________________________________________ __________________________________________________________________ __________________________________________________________________ __________________________________________________________________

四、实验总结

请总结一下本次实验的收获、教训和感受,结合课本内容谈一下你对进程间控制的理解。

本文来源:https://www.bwwdw.com/article/4def.html

Top