文档库 最新最全的文档下载
当前位置:文档库 › 实验三 进程同步实验

实验三 进程同步实验

实验三 进程同步实验
实验三 进程同步实验

实验三进程同步机制

一、实验内容:

学习Windows有关进程/线程同步的背景知识和API,学习windows平台下常用的同步方式,并分析2个实验程序(利用信号量实现两个进程间的同步和利用互斥量实现读者写者问题),观察程序的运行情况并分析执行结果。

二、实验目的:

在本实验中,通过对互斥量(Mutex)和信号量(Semaphore)对象的了解,来加深对Windows 进程、线程同步的理解。

(1) 了解互斥量和信号量对象。

(2) 通过分析实验程序,理解管理信号量对象的API。

(3) 理解在进程中如何使用信号量对象。

(4) 通过分析实验程序,理解在线程中如何使用互斥量对象。

(5) 理解父进程创建子进程的程序设计方法,理解在主线程中创建子线程的方法。

三、实验要求:

(1) 理解Windows有关进程/线程同步的背景知识和API。

(2) 按要求运行2个程序,观察程序执行的结果,并给出要求的结果分析。

(3) 参照3-2程序,写出一个实现单个生产者—消费者问题的算法,可以使用单个缓冲区,也可以使用缓冲池,生产者随机产生任意形式的数据并放入缓冲区中,消费者则以随机的时间间隔从缓冲区中取数据,随机时间请使用随机数产生。

四、并发与同步的背景知识

Windows开发人员可以使用同步对象来协调线程和进程的工作,以使其共享信息并执行任务。此类对象包括互斥量Mutex、信号量Semaphore、事件Event等。

多进程、多线程编程中关键的一步是保护所有的共享资源,工具主要有互斥量Mutex 和信号量Semaphore等;另一个是协调线程使其完成应用程序的任务,为此,可利用内核中的信号量对象或事件对象。

互斥量是一个可命名且安全的内核对象,主要目的是引导对共享资源的访问。拥有单一访问资源的线程创建互斥体,所有希望访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去。

利用CreateMutex() API可创建互斥量,创建时可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体。

为了获得互斥体,首先,想要访问调用的线程可使用OpenMutex() API来获得指向对象的句柄;然后,线程将这个句柄提供给一个等待函数。当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权。当线程获得拥有权时,线程控制了对共享资源的访问——必须设法尽快地放弃互斥体。放弃共享资源时需要在该对象上调用ReleaseMutex() API。然后系统负责将互斥量拥有权传递给下一个等待着的线程(由到达时间决定顺序) 。

信号量Semaphore与互斥量Mutex的用法不同,互斥量Mutex保证任意时刻只能有一个进程或线程获得互斥体,信号量允许多个线程同时使用共享资源,这与操作系统中的Wait/Signal操作【也称PV操作】相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数或者0。每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完

共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。

此外,windows还提供了另外一个容易误导大家理解的同步对象,取名为Critical Section,中文取名为临界区,请大家注意与课本讲的临界资源、临界区的概念相区分。课本上临界区指“对临界资源进行访问的代码”;而这种称为“Critical Section”互斥机制,并不是这个意思,而是访问临界区之前的一种加锁机制,与互斥量Mutex的作用类似,只是“Critical Section”互斥机制只能在同一进程内部各个线程间使用,而Mutex互斥机制是可以跨进程使用的。

五、实验步骤

1. 信号量Semaphore对象

清单3-1程序展示如何在进程间使用信号量对象。

父进程启动时,利用CreateSemaphore () API创建一个命名的、可共享的信号量对象,并利用CreateProcess()创建子进程,然后调用WaitForSingleObject()函数去获取信号量,但是由于信号量的初始值为0,所以父进程阻塞在此,无法成功占有信号量;子进程创建后,调用OpenSemaphore()打开父进程创建的信号量,然后调用ReleaseSemaphore()函数释放了1个信号量,此后,处于阻塞状态的父进程获取了子进程释放的信号量,从而解除了阻塞,继续运行至程序结束。

清单3-1 创建和打开信号量对象在进程间传送信号

// Semaphore信号量项目

# include

# include

# include

// 定义一个信号量的名字

static LPCTSTR g_szSemaphoreName =

"shmtu.os2012.semaphore";

// 本方法只是创建了一个进程的副本,以子进程模式(由命令行指定) 工作

BOOL CreateChild()

{

// 提取当前可执行文件的文件名

TCHAR szFilename[MAX_PA TH] ;

GetModuleFileName(NULL, szFilename, MAX_PA TH) ;

// 格式化用于子进程的命令行,指明它是一个EXE文件和子进程

TCHAR szCmdLine[MAX_PA TH] ;

sprintf(szCmdLine, "\"%s\" child" , szFilename) ;

// 子进程的启动信息结构

STARTUPINFO si;

ZeroMemory(reinterpret_cast(&si), sizeof(si)) ;

si.cb = sizeof(si); // 必须是本结构的大小

// 返回的子进程的进程信息结构

PROCESS_INFORMA TION pi;

// 使用同一可执行文件和告诉它是一个子进程的命令行创建进程

BOOL bCreateOK = CreateProcess(

szFilename, // 生成的可执行文件名

szCmdLine, // 指示其行为与子进程一样的标志

NULL, // 子进程句柄的安全性

NULL, // 子线程句柄的安全性

FALSE, // 不继承句柄

CREA TE_NEW_CONSOLE, // 特殊的创建标志

NULL, // 新环境

NULL, // 当前目录

&si, // 启动信息结构

&pi ) ; // 返回的进程信息结构

// 释放对子进程的引用

if (bCreateOK)

{

CloseHandle(pi.hProcess);

CloseHandle(pi.hThread);

}

return(bCreateOK) ;

}

// 下面的方法创建一个信号量和一个子进程,然后等待子进程在返回前释放一个信号

void WaitForChild()

{

HANDLE hSemaphore = CreateSemaphore( //创建一个信号量,初始值0,最大值为1 NULL, // 缺省的安全性,子进程将具有访问权限

0, //初始值0

1, //最大值为1

g_szSemaphoreName //信号量名称

);

if (hSemaphore != NULL)

{

cout << " 信号量对象已经建立啦。" << endl;

// 创建子进程

if ( CreateChild())

{

cout << " 父进程建立了一个子进程" << endl;

// 等待,直到子进程发出信号

cout << "因为当前信号量的值为0,所以父进程暂时阻塞在此啦." << endl;

WaitForSingleObject(hSemaphore, INFINITE);

cout << "因为子进程释放了1个信号量,所以,父进程可以解除阻塞啦。" << endl;

}

// 清除句柄

CloseHandle(hSemaphore);

hSemaphore=INV ALID_HANDLE_V ALUE;

}

}

// 以下方法在子进程模式下被调用,其功能只是释放1个信号量

void SignalParent()

{

// 尝试打开句柄

cout << "child process begining......" << endl;

HANDLE hSemaphore = OpenSemaphore(

SEMAPHORE_ALL_ACCESS, // 所要求的最小访问权限

FALSE, // 不是可继承的句柄

g_szSemaphoreName); // 信号量名称

if(hSemaphore != NULL)

{

cout<<"如果你同意释放一个信号量,请按任意键"<

getchar();

cout << "此刻,子进程释放了1个信号量." << endl;

ReleaseSemaphore(hSemaphore,1,NULL);

}

// 清除句柄

CloseHandle(hSemaphore) ;

hSemaphore = INV ALID_HANDLE_V ALUE;

}

int main(int argc, char* argv[] )

{

// 检查父进程或是子进程是否启动

if (argc>1 && strcmp(argv[1] , "child" )== 0)

{

// 向父进程发出信号

SignalParent() ;

cout << "子进程马上就要运行结束了。请按任意键继续。" << endl ;

}

else

{

// 创建一个信号量并等待子进程

WaitForChild();

cout << "父进程马上就要运行结束了。请按任意键继续。" << endl ;

}

getchar();

return 0;

}

步骤1:编译并执行3-1.exe程序。

程序运行结果是(分行书写) :

①__________________________________________________________________

②__________________________________________________________________

③__________________________________________________________________

④__________________________________________________________________

⑤__________________________________________________________________

⑥__________________________________________________________________

阅读和分析程序3-1,请回答:

(1) 程序中,创建一个信号量使用了哪一个系统函数?创建时设置的信号量初始值是多

少,最大值是多少?

a. __________________________________________________________________

b. __________________________________________________________________

(2) 创建一个进程(子进程) 使用了哪一个系统函数?

____________________________________________________________________ (3) 从步骤1的输出结果,对照分析3-1程序,能够看出程序运行的流程吗?请简单描

述:

________________________________________________________________________

________________________________________________________________________

________________________________________________________________________

________________________________________________________________________

________________________________________________________________________

________________________________________________________________________

步骤2:编译程序生成执行文件3-1.exe,在命令行状态下执行程序,分别使用格式:

(1) 3-1 child

(2) 3-1 或3-1 ***

运行程序,记录执行的结果,并分行说明产生不同结果的原因。

2. 互斥量Mutex对象

注意:多线程编程需要在Visual C++中设定多线程C运行时库。打开你的工程项目后,在Visual C++的“Project”菜单下面,选择“Settings”菜单,如下两图所示,可以分别设定Debug版本和Release版本所使用的多线程C运行时库,以Debug开头的都是为Debug 版本准备的,都选Multithread版本。

如果使用标准C 库而调用VC运行时库函数,则在程序的link阶段会提示如下错误:

清单3-2的程序中是读者写者问题的一个实现,满足读者优先原则,使用同步机制的互斥量实现,对每个读者和写者分别用一个线程来表示。

测试数据文件的数据格式说明:

测试数据文件包括n行测试数据,每行描述创建的是用于产生读者还是写者的数据。每行测试数据包括4个字段,各字段间用空格分隔。

●第一字段为线程序号。

●第二字段表示相应线程角色,W表示写者,R表示读者。

●第三字段为线程延迟。

●第四字段为线程读写操作持续时间。

清单3-2 利用互斥量Mutex实现读者写者问题

#include "windows.h"

#include "process.h"

#include

#include

#include

#include

#include

#include

#define READER 'R' // 读者

#define WRITER 'W' // 写者

#define INTE_PER_SEC 1000 // 每秒时钟中断数目

#define MAX_THREAD_NUM 64 // 最大线程数目

#define MAX_FILE_NUM 32 // 最大数据文件数目

#define MAX_STR_LEN 32 // 字符串长度

volatile int readcount = 0; // 读者数目

HANDLE RP_Write;

struct ThreadInfo // 定义线程数据结构

{

int serial; // 线程序号

char entity; // 线程类别(判断是读者线程还是写者线程)

double delay; // 线程延迟

double persist; // 线程读写操作持续时间

};

////////////////////////////////////////////////////////////////////////

/// 读者优先——读者线程

/// p:读者线程信息

unsigned int __stdcall RP_ReaderThread(void *p)

{

// 互斥变量

HANDLE h_Mutex;

h_Mutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE,"mutex_for_readcount");

DWORD wait_for_mutex; //等待互斥变量所有权

DWORD m_delay; //延迟时间

DWORD m_persist; //读文件持续时间

int m_serial; //线程序号

//从参数中获得信息

m_serial = ((ThreadInfo *)(p))->serial;

m_delay = (DWORD)(((ThreadInfo *)(p))->delay*INTE_PER_SEC);

m_persist = (DWORD)(((ThreadInfo*)(p))->persist*INTE_PER_SEC);

Sleep (m_delay) ; //延迟等待

printf("Reader thread %d sents the reading require......\n",m_serial);

//等待互斥信号,保证对readcount的访问、修改互斥

wait_for_mutex = WaitForSingleObject(h_Mutex,-1);

// 读者数目增加

readcount++;

if(readcount ==1)

{ //这是第一个读者,第二个读者到来时,readcount为2,if的条件不满足,不会进入if语句内部执行

//这是第一个读者,如果此刻没有写者正在写,则RP_Write信号量状态为可用(未占用),那它就必须先占用(锁定)RP_Write信号量,这就实现了读-写互斥

//如果此刻有写者正在写,则RP_Write信号量被写者占用(锁定),读者想对RP_Write加锁就会阻塞在此,等待RP_Write信号量释放后,才能继续运行

WaitForSingleObject(RP_Write, INFINITE);

}

ReleaseMutex(h_Mutex) ; //释放互斥信号

// 读文件

printf("Reader thread %d begins to read file.\n",m_serial);

Sleep(m_persist) ;

// 退出线程

printf("Reader thread %d finished reading file.\n",m_serial);

//等待互斥信号,保证对readcount的访问、修改互斥

wait_for_mutex = WaitForSingleObject(h_Mutex,-1) ;

//读者数目减少

readcount-- ;

if(readcount == 0)

{

//如果所有读者读完,则释放RP_Write信号量;此刻若有写者正在等待(处于阻塞状态),将(自动)唤醒写者

ReleaseMutex(RP_Write);

}

ReleaseMutex(h_Mutex) ; //释放互斥信号

return 0;

}

////////////////////////////////////////////////////////////////////////

// 读者优先——写者线程

// p:写者线程信息

unsigned int __stdcall RP_WriterThread(void* p)

{

DWORD m_delay; //延迟时间

DWORD m_persist; //写文件持续时间

int m_serial; //线程序号

//从参数中获得信息

m_serial = ((ThreadInfo *)(p))->serial;

m_delay = (DWORD)(((ThreadInfo *)(p))->delay*INTE_PER_SEC) ;

m_persist = (DWORD)(((ThreadInfo *)(p))->persist*INTE_PER_SEC) ;

Sleep (m_delay); //延迟等待

printf("Writer thread %d sents the writing require.\n",m_serial);

// 写者在进行写数据之前,必须确定没有其它写者正在写,也没有其它读者在读,

// 通过RP_Write互斥量实现,若有其它写者正在写或其它读者在读,则该写者调用“WaitForSingleObject(RP_Write, INFINITE);”后将阻塞在此

// 等待RP_Write信号量释放后,才能够继续向下执行。

WaitForSingleObject(RP_Write, INFINITE);

// 写文件

printf("Writer thread %d begins to write to the file.\n",m_serial);

Sleep(m_persist) ;

// 退出线程

printf("Writer thread %d finished writing to the file.\n",m_serial);

//写者写完后,需要释放RP_Write信号量资源

ReleaseMutex(RP_Write);

return 0;

}

///////////////////////////////////////////////////////////

// 读者优先处理函数

// file:文件名

void ReaderPriority(char *file)

{

int i;

DWORD n_thread = 0; // 线程数目

UINT thread_ID; // 线程ID

DWORD wait_for_all; // 等待所有线程结束

// 互斥对象

HANDLE h_Mutex;

h_Mutex = CreateMutex(NULL,FALSE,"mutex_for_readcount");

// 线程对象的数组

HANDLE h_Thread[MAX_THREAD_NUM] ;

ThreadInfo thread_info[MAX_THREAD_NUM] ;

readcount = 0 ; // 初始化readcountt

RP_Write = CreateMutex(NULL, FALSE, NULL); // 初始化用于读-写互斥、写-写互斥的信号量ifstream inFile; // 打开文件

inFile.open(file) ;

printf("Reader Priority:\n\n") ;

while(inFile)

{

//读人每一个读者、写者的信息

inFile>>thread_info[n_thread].serial ;

inFile>>thread_info[n_thread].entity;

inFile>>thread_info[n_thread].delay;

inFile>>thread_info[n_thread++].persist;

inFile.get();

}

for(i = 0; i < (int)(n_thread); i++)

{

if(thread_info[i].entity == READER || thread_info[i].entity == 'r')

{

// 创建读者线程

h_Thread[i] = (HANDLE)_beginthreadex(NULL, 0, RP_ReaderThread, &thread_info[i], 0, &thread_ID);

}

else {

// 创建写者线程

h_Thread[i] = (HANDLE)_beginthreadex(NULL, 0, RP_WriterThread, &thread_info[i], 0, &thread_ID);

}

}

//等待所有线程结束

wait_for_all = WaitForMultipleObjects(n_thread,h_Thread,TRUE,-1) ;

printf("All reader and writer have finished operating.\n");

//关闭线程句柄,关闭信号量句柄

for(i = 0; i < (int)(n_thread); i++) { CloseHandle(h_Thread[i]); }

CloseHandle(h_Mutex);

CloseHandle(RP_Write);

}

///////////////////////////////////////////////////////////////////

//主函数

int main(int argc, char* argv[])

{

char ch;

while ( true )

{

printf("***********************************\n");

printf(" 1: Reader Priority\n") ;

printf(" 2: Exit to Windows\n") ;

printf("***********************************\n");

printf( "Enter your choice (1 or 2): ");

//如果输入信息不正确,继续输入

do{

ch = (char)_getch() ;

} while(ch != '1' && ch != '2');

system("cls") ; //清除控制台显示的信息

//选择2,返回

if(ch == '2')

return 0;

//选择l,读者优先

else

ReaderPriority("thread.dat");

//结束

printf("\nPress Any Key To Continue:");

_getch() ;

system("cls") ;

}

return 0;

}

步骤3:编译并建立3-2.exe可执行文件。在工具栏单击“Execute Program”按钮,执行3-2.exe程序。

(1) 对运行的结果逐行给出分析描述。

____________________________________________________________________ ________________________________________________________________________ ________________________________________________________________________ ________________________________________________________________________ ________________________________________________________________________

(2) 根据运行输出结果,对照分析3-2程序,画出程序运行的流程图

(3) 自己定义一组实验数据并保存到文件后再执行程序(文件名字为thread.dat, 必须放在相同目录下,使用记事本按数据格式要求编辑),分析执行结果,并观察结果是否与设想的一致。

下面是一个测试数据文件的例子,仅供参考,实验是自己定义相关数据:

1 R 3 5

2 W 4 5

3 R 5 2

4 R 6 5

5 W 5.1 3

3. 选做部分:编程实现生产者—消费者问题

参照3-2程序,写出实现单个生产者—消费者问题的算法,可以使用单个缓冲区,也可以使用缓冲池,生产者随机产生任意形式的数据并放入缓冲区中,消费者则以随机的时间间隔从缓冲区中取数据,随机时间请使用随机数产生。

首先创建一个生产者线程和一个消费者线程,然后生产者与消费者都以各自的速度生产和消费,而缓冲区则是两者的临界资源。

实验报告内容包括:(1)设计思路的说明;(2)需要几个同步信号量;(3)解决方法的整体流程;(4)含有详细注释的源代码;(5)测试结果及分析。

几个API函数说明

1.CreateThread

函数功能:该函数创建一个在调用进程的地址空间中执行的线程。

函数原型:HANDLE CreateThread (LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStacksize, LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter, DWORD dwCreatiOnFlags, LPDWORD lpThreadId); 参数说明:

●lpThreadAttributes:指向一个SECURITY_ATTRIBUTES结构,该结构决定了返回的句柄

是否可被子进程继承。若lpThreadAttributes为NULL,则句柄不能被继承。

在Windows NT中该结构的lpSecurityDescriptor成员定义了新进程的安全性描述符。

若lpThreadAttributes为NULL,则线程获得一个默认的安全性描述符。

●dwStackSize:定义原始堆栈提交时的大小(按字节计)。系统将该值舍人为最近的页。

若该值为0,或小于默认时提交的大小,默认情况是使用与调用线程同样的大小。更多的信息,请看ThreadStackSize。

●lpStartAddress:指向一个LPTHREAD_START_ROUTINE类型的应用定义的函数,该线程

执行此函数。该指针还表示远程进程中线程的起始地址。该函数必须存在于远程进程中。

●lpParameter:定义一个传递给该进程的32位值。

●dwCreationFlags:定义控制进程创建的附加标志。若定义了CREATE_SUSPENDED标志,

线程创建时处于挂起状态,并且直到ResumeThread函数调用时才能运行。若该值为0,则该线程在创建后立即执行。

●lpThreadId:指向一个32位值,它接收该线程的标识符。

返回值:若函数调用成功,返回值为新线程的句柄;若函数调用失败,返回值为NULL。

2.ExitThread

函数功能:该函数结束一个线程。

函数原型:VOID ExitThread(DWORD dwEextCode)

参数:dwExitCode定义调用线程的退出代码。使用GetExitCodeThread函数来检测一个线程的退出代码。

返回值:无。

说明:调用ExitThread函数,是结束一个线程的较好的方法。调用该函数后(或者直接地调用,或者从一个线程过程返回),当前线程的堆栈取消分配,线程终止。若调用该函数

时,该线程为进程的最后一个线程,则该线程的进程也被终止。

线程对象的状态变为发信号状态,以释放所有正在等待该线程终止的其他线程。

线程的终止状态从STILL_ACTIVATE变为dwExitCode参数的值。

线程结束时不必从操作系统中移去该线程对象。当线程的最后一个句柄关闭时,该线程对象被删除。

3._beginthreadex

函数功能:如果使用C/C++语言编写多线程应用程序,一定不能使用操作系统提供的CreateThread API,而应该使用C/C++运行时库中的_beginthreadex

函数原型:

uintptr_t _beginthreadex(

void *security, //Pointer to a SECURITY_ATTRIBUTES structure

unsigned stack_size,

unsigned ( __stdcall *start_address )( void * ),

void *arglist,

unsigned initflag,//Initial state of new thread (0 for running or CREATE_SUSPENDED for suspended);

unsigned *thrdaddr

);

参数:

●security:安全属性的指针,可为NULL

●stack_size: 线程栈的大小,0代表系统默认大小

●start_address:线程函数的运行地址,该线程执行此函数(注:函数名也是该函数

的运行地址)

●arglist:参数值指针

●initflag:线程初始状态的值,0代表就绪状态,CREATE_SUSPENDED代表挂起状

●thrdaddr:存放线程ID的变量地址

返回值:

若函数调用成功,返回值为新线程的句柄;若函数调用失败,返回值为NULL。

说明:

_beginthreadex函数与Win32 API 中的CreateThread函数类似,但有如下差异:_beginthreadex 函数初始化某些C 运行时库变量,在线程中若需要使用C 运行时库,则需要使用_beginthreadex 函数创建线程。

4.Sleep

函数功能:该函数对于指定的时间间隔挂起当前的执行线程。

函数原型:VOID Sleep(DWORD dwMilliseconds)

参数:dwMilliseconds:定义挂起执行线程的时间,以毫秒(ms)为单位。取值为0时,该线程将余下的时间片交给处于就绪状态的同一优先级的其他线程。若没有处于就绪状态的同一优先级的其他线程,则函数立即返回,该线程继续执行。若取值为INFINITE 则造成无限延迟。

返回值:该函数没有返回值。

说明:一个线程可以在调用该函数时将睡眠时间设为0ms,以将剩余的时间片交出。5.CreateMutex

函数功能:该函数创建有名或者无名的互斥对象。

函数原型:HANDLE CreateMutex (LPSECURITY_ATTRIBUTES lpMutexAttributes,

BOOL bInitialOwner, LPCTSTR lpName);

参数:

●lpMutexAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构决定子进程是否

能继承返回句柄。如果lpMutexAttributes为NULL,那么句柄不能被继承。

在Windows NT中该结构的lpSecurityDescriptor成员指定新互斥对象的安全描述符。如果lpMutexAttributes为NULL,那么互斥对象获得默认的安全描述符。

●bInitialOwner:指定互斥对象的初始所属身份。如果该值为TRUE,并且调用者创建互

斥对象,那么调用线程获得互斥对象。否则,调用线程不能获得互斥对象。判断调用者是否创建互斥对象请参阅返回值部分。

●lpName:指向以NULL结尾的字符串,该字符串指定了互斥对象名。该名字的长度小于

MAX_ PATH且可以包含除反斜线(\)路径分隔符以外的任何字符。名字是区分大小写的。

如果lpName与已存在的有名互斥对象名相匹配,那么该函数要求用MUTEX_ALL_ACCESS 权限访问已存在的对象。在这种情况下,由于参数bInitialOwner已被创建进程所设置,该参数被忽略。如果参数lpMutexAttributes不为NULL,它决定句柄是否解除继承,但是其安全描述符成员被忽略。

如果lpName为NULL,那么创建的互斥对象无名。

如果lpName与已存在的事件、信号量、可等待定时器、作业或者文件映射对象的名字相匹配,那么函数调用失败,并且GetLastError函数返回ERROR_INVALID_HANDLE,其原因是这些对象共享相同的名字空间。

返回值:

如果函数调用成功,返回值是互斥对象句柄;如果函数调用之前,有名互斥对象已存在,那么函数给已存在的对象返回一个句柄,并且函数GetLastError返回ERROR_ALREADY_EXISTS,否则,调用者创建互斥对象。

如果函数调用失败,则返回值为NULL。若想获得更多错误信息,请调用GetLastError函数。

说明:

由函数CreateMutex返回的句柄有MUTEX_ALL_ACCESS权限可以去访问新的互斥对象,并且可用在请求互斥对象句柄的任何函数中。

当一个互斥对象不被任何线程拥有时,处于信号态。创建该对象的线程可以使用bInitialOwner标志来请求立即获得对该互斥对象的所有权。否则,线程必须使用等待函数【例如WaitForSingleObject函数】来请求所有权。当互斥对象处于信号态,等待的线程获得对该对象的所有权时,此互斥对象的状态被设置为非信号态,等待函数返回。任意时刻,仅有一个线程能拥有该互斥对象,线程可以使用ReleaseMutex函数来释放对这个互斥对象的所有权。

若线程已经拥有了一个互斥对象,那么它可以重复调用等待函数而不会发生阻塞,一般情况下,用户不会重复等待同一个互斥对象,这种机制防止了线程因等待它已经拥有的互斥对象而发生死锁。然而,线程必须为每一次等待调用一次ReleaseMutex函数来释放该互斥对象。

两个或多个进程可以调用CreateMutex来创建同名的互斥对象,第一个进程实际创建互斥对象,以后的进程打开已存在的互斥对象的句柄。这使得多个进程可以得到同一个互斥对象的句柄,从而减轻了用户的负担,使用户不必判断创建进程是否为第一个启动的进程。使用这种技术时,应该把bInitialOwner标志设为FALSE;否则很难确定开始时哪一个进程拥有该互斥对象。

由于多进程能够拥有相同互斥对象的句柄,通过使用这个对象,可使多进程同步。以下为共享对象机制:

?如果CreateMutex中的lpMutexAttributes参数允许继承,由CreateProcess函数创建的子进程可以继承父进程的互斥对象句柄。

?一个进程可以在调用DuplicateHandle函数时指定互斥对象句柄来创建一个可以被其他进程使用的双重句柄。一个进程在调用OpenMutex或CreateMutex函数时能指定互斥对象名。

?使用CloseHandle函数关闭句柄,进程结束时系统自动关闭句柄。当最后一个句柄被关闭时,互斥对象被销毁。

6.ReleaseMutex

函数功能:该函数放弃指定互斥对象的所有权。

函数原型:BOOL ReleaseMutex(HANDLE hMutex)

参数:hMutex:互斥对象句柄。为CreateMutex或OpenMutex函数的返回值。

返回值:如果函数调用成功,那么返回值是非零值;如果函数调用失败,那么返回值是零值。

若想获得更多错误信息,请调用GetLastError函数。

说明:如果调用线程不拥有互斥对象,ReleaseMutex函数失败。

一个线程通过调用等待函数拥有互斥对象。创建该互斥对象的线程也拥有互斥对象,而不需要调用等待函数。当互斥对象的所有者线程不再需要互斥对象时,它可以调用ReleaseMutex函数。

当一个线程拥有一个互斥对象后,它可以用该互斥对象多次调用等待函数而不会阻塞。这防止一个线程等待一个它已拥有的互斥对象时出现死锁。不过,为了释放所有权,该线程必须为每一个等待操作调用一次ReleaseMutex函数。

7.WaitForSingleObject

函数功能:当下列情况之一发生时该函数返回:(1)指定对象处于信号态;(2)超时

函数原型:DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds)

参数:

●hHandle:等待对象句柄。

●dwMilliseconds:指定以毫秒为单位的超时间隔。如果超时,即使对象的状态是非信号

态的并且没有完成,函数也返回。如果dwMilliseconds是0,函数测试对象的状态并立刻返回;如果dwMilliseconds是INFINITE,函数从不超时。

返回值:如果函数调用成功,返回值表明引起函数返回的事件。可能值如下:

●WAIT_ABANDONED:指定对象是互斥对象,在线程被终止前,线程没有释放互斥对象。

互斥对象的所属关系被授予调用线程,并且该互斥对象被置为非信号态。

●WAIT_OBJECT_0:指定对象的状态被置为信号态。

●WAIT_TIMEOUT:超时,并且对象的状态为非信号态。

如果函数调用失败,返回值是WAIT_FAILED。

说明:

WaitForSingleObjects函数决定等待条件是否被满足。如果等待条件并没有被满足,调用线程进入一个高效的等待状态,当等待满足条件时占用非常少的处理器时间。

在运行前,一个等待函数修改同步对象类型的状态。修改仅发生在引起函数返回的对象身上。例如,信号的计数减1。

WaitForSingleObject函数能等待的对象包括:Change notification(改变通告);Console input(控制台输入);Event(事件);Job(作业);Mutex(互斥对象);Process(进程);Semaphore(信号量);Thread(线程);Waitabletimer(可等待定时器)。

当使用等待函数或代码直接或间接创建窗口时,一定要小心。如果一个线程创建了任何窗口,它必须处理进程消息。消息广播被发送到系统的所有窗口。一个线程用没有超时的等待函数也许会引起系统死锁。间接创建窗口的两个例子是DDE和COM CoInitialize。因此,如果用户有一个创建窗口的线程,用MsgWaitForMultipleObjects或MsgWaitForMultipleObjectsEx函数,而不要用SignalObjeectAndWait函数。

8.WaitForMultipleObjects

函数功能: WaitForMultipleObjects函数当下列条件之一满足时返回:(1)任意一个或全部指定对象处于信号态;(2)超时间隔已过。

函数原型:DWORD WaitForMultipleObiects(DWORD nCount,CONST HANDLE *lpHandles, BOOL fWaitAll,DWORD dwMilliSeconds);

参数:

●nCount:指定由lpHandles所指向的数组中的句柄对象数目最大对象句柄数目

MAXIMUM_WAIT_OBJECTS。

●lpHandles:指向对象句柄数组的指针。该数组可以包含不同类型对象的句柄。在

WindowsNT中,该句柄必须有SYNCHRONIZE访问权限。若想获得更多的信息,请查看StandardAccess Rights。

●fWaitAll:指定等待类型。如果为TRUE,当lpHandles指向的数组里的全部对象为

号态时,函数返回。如果为FALSE,当由lpHandles指向的数组里的任一对象为信号态时,函数返回。对于后者,返回值指出引起函数返回的对象。

●dwMilliseconds:指定以毫秒为单位的超时间隔。如果超时,即使bWaitAll参数指

定的条件没有满足,函数也返回。如果dwMilliseconds是0,函数测试指定对象的状态并立刻返回。如果dwMilliseconds是INFINITE,函数从不超时。

返回值:如果函数调用成功,返回值表明引起函数返回的事件。可能值如下:

●WAIT_OBJECT_0到WAIT_OBJECT_0 + nCount-1:如果bWaitAll为TRUE,那么返回

值表明所有指定对象的状态为信号态。如果bWaitA11为FALSE,那么返回值减去WAIT_OBJECT_0表明引起函数返回的对象的pHandles数组索引。如果多于一个对象变为信号态,则返回的是数组索引最小的信号态对象索引。

●WAIT_ABANDONED_0到WAIT_ABANDONED_0 + nCount-1:如果bWaitAll为TRUE,那么

返回值表明所有指定对象的状态为信号态,并且至少一个对象是己放弃的互斥对象。

如果bWaitAll为FALSE,那么返回值减去WAIT_ABANDONED_0表明引起函数返回的放弃互斥对象的pHandles数组索引。

●WAIT_TIMEOUT:超时并且由参数bWaitAll指定的条件没有满足。

如果函数调用失败,返回值是WAIT_FAILED。

9.信号量包含的几个操作原语:

https://www.wendangku.net/doc/8b14460677.html,/en-us/library/ms685129(v=VS.85).aspx

创建一个信号量:CreateSemaphore()

打开一个信号量:OpenSemaphore()

释放信号量:ReleaseSemaphore()

等待信号量:WaitForSingleObject()

函数原型:

HANDLE CreateSemaphore (

LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //Semaphore的安全属性,可为NULL LONG lInitialCount, //信号量的初始值

LONG lMaximumCount, //允许的信号量最大值

LPCTSTR lpName //信号量的名字

);

返回值:

If the function succeeds, the return value is a handle to the semaphore object. If the named semaphore object existed before the function call, the function returns a handle to the existing object and GetLastError returns ERROR_ALREADY_EXISTS.

If the function fails, the return value is NULL. To get extended error information, call GetLastError.

函数原型:

HANDLE OpenSemaphore (

DWORD dwDesiredAccess, //希望获得的Semaphore的访问属性,

//例如SYNCHRONIZE或SEMAPHORE_ALL_ACCESS

BOOL bInheritHandle,//TRUE表示子进程可以继承该Semaphore的句柄; FALSE表示

子进程不可继承该Semaphore的句柄

LPCTSTR lpName //想打开的Semaphore的名字,需保证该名字所对应Semaphore

已经创建

);

函数原型:

BOOL ReleaseSemaphore (

HANDLE hSemaphore, //想释放(signal)信号的信号量句柄

LONG lReleaseCount, //想释放(signal)的信号数量

LPLONG lpPreviousCount //在释放信号前的信号值快照,可能已经被其他线程或进程修改);

实验二(1)进程同步

实验二(2)进程同步 一、实验目的 1、生产者-消费者问题是很经典很具有代表性的进程同步问题,计算机中的很多同步问题都可抽象为生产者-消费者问题,通过本实验的练习,希望能加深学生对进程同步问题的认识与理解。 2、熟悉VC的使用,培养和提高学生的分析问题、解决问题的能力。 二、实验内容及其要求 1.实验内容 以生产者/消费者模型为依据,创建一个控制台进程,在该进程中创建n个线程模拟生产者和消费者,实现进程(线程)的同步与互斥。 2.实验要求 学习并理解生产者/消费者模型及其同步/互斥规则;设计程序,实现生产者/消费者进程(线程)的同步与互斥; 三、实验算法分析 1、实验程序的结构图(流程图); 2、数据结构及信号量定义的说明; (1) CreateThread ●功能——创建一个在调用进程的地址空间中执行的线程 ●格式 HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParamiter, DWORD dwCreationFlags, Lpdword lpThread ); ●参数说明 lpThreadAttributes——指向一个LPSECURITY_ATTRIBUTES(新线程的安全性描述符)。dwStackSize——定义原始堆栈大小。 lpStartAddress——指向使用LPTHRAED_START_ROUTINE类型定义的函数。 lpParamiter——定义一个给进程传递参数的指针。 dwCreationFlags——定义控制线程创建的附加标志。 lpThread——保存线程标志符(32位) (2) CreateMutex ●功能——创建一个命名或匿名的互斥量对象 ●格式 HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName); bInitialOwner——指示当前线程是否马上拥有该互斥量(即马 ●参数说明 lpMutexAttributes——必须取值NULL。上加锁)。 lpName——互斥量名称。 (3) CreateSemaphore ●功能——创建一个命名或匿名的信号量对象 ●格式 HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName ); ●参数说明 lpSemaphoreAttributes——必须取值NULL。

实验三-进程管理

实验三进程管理 一、实验目的 1.熟悉和理解进程和进程树的概念,掌握有关进程的管理机制 2.通过进程的创建、撤销和运行加深对进程并发执行的理解 3.明确进程与程序、并行与串行执行的区别 4.掌握用C 程序实现进程控制的方法 二、实验学时 2学时 三、实验背景知识 所涉及的系统调用 1、exec( )系列(exec替换进程映像) 系统调用exec( )系列,也可用于新程序的运行。fork( )只是将父进程的用户级上下文拷贝到新进程中,而exec( )系列可以将一个可执行的二进制文件覆盖在新进程的用户级上下文的存储空间上,以更改新进程的用户级上下文。exec( )系列中的系统调用都完成相同的功能,它们把一个新程序装入内存,来改变调用进程的执行代码,从而形成新进程。如果exec( )调用成功,调用进程将被覆盖,然后从新程序的入口开始执行,这样就产生了一个新进程,新进程的进程标识符id 与调用进程相同。 exec( )没有建立一个与调用进程并发的子进程,而是用新进程取代了原来进程。所以exec( )调用成功后,没有任何数据返回,这与fork( )不同。exec( )系列系统调用在UNIX系统库unistd.h中,共有execl、execlp、execle、execv、execvp五个,其基本功能相同,只是以不同的方式来给出参数。 #include int execl(const cha r *pathname, const char *arg, …); int execlp(const char *, const char *arg, …); int execle(const char *pathname, const char *arg, …, const char *envp[ ]); int execv(const char *pathname, char *const argv[ ]); int execvp(const char *, char *const argv[ ]); 参数: path参数表示你要启动程序的名称包括路径名。 arg参数表示启动程序所带的参数,一般第一个参数为要执行命令名,不是带路径且arg必须以NULL结束。 返回值:成功返回0,失败返回-1 注:上述exec系列函数底层都是通过execve系统调用实现. 1)带l 的exec函数:execl,execlp,execle,表示后边的参数以可变参数的形式给出且都以一个空指针结束。 #include

windows进程管理实验报告

实验报告 课程名称:操作系统 实验项目:windows进程管理 姓名: 专业:计算机科学与技术 班级: 学号:

计算机科学与技术学院 计算机系 2019 年 4 月 23 日

实验项目名称: windows进程管理 一、实验目的 1. 学习windows系统提供的线程创建、线程撤销、线程同步等系统调用; 2. 利用C++实现线程创建、线程撤销、线程同步程序; 3. 完成思考、设计与练习。 二、实验用设备仪器及材料 1. Windows 7或10, VS2010及以上版本。 三、实验内容 1 线程创建与撤销 写一个windows控制台程序(需要MFC),创建子线程,显示Hello, This is a Thread. 然后撤销该线程。 相关系统调用: 线程创建: CreateThread() 线程撤销: ExitThread() 线程终止: ExitThread(0) 线程挂起: Sleep() 关闭句柄: CloseHandle() 参考代码: ; } 运行结果如图所示。 完成以下设计题目: 1. 向线程对应的函数传递参数,如字符串“hello world!”,在线程中显示。 2. 如何创建3个线程A, B, C,并建立先后序执行关系A→B→C。

实验内容2 线程同步 完成父线程和子线程的同步。父线程创建子线程后进入阻塞状态,子线程运行完毕后再唤醒。 相关系统调用: 等待对象 WaitForSingleObject(), WaitForMultipleObjects(); 信号量对象 CreateSemaphore(), OpenSemaphore(), ReleaseSemaphore(); HANDLE WINAPI CreateSemaphore( _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes _In_ LONG lInitialCount, _In_ LONG lMaximumCount, _In_opt_ LPCTSTR lpName ); 第一个参数:安全属性,如果为NULL则是默认安全属性 第二个参数:信号量的初始值,要>=0且<=第三个参数 第三个参数:信号量的最大值 第四个参数:信号量的名称 返回值:指向信号量的句柄,如果创建的信号量和已有的信号量重名,那么返回已经存在的信号量句柄参考代码: n"); rc=ReleaseSemaphore(hHandle1,1,NULL); err=GetLastError(); printf("Release Semaphore err=%d\n",err); if(rc==0) printf("Semaphore Release Fail.\n"); else printf("Semaphore Release Success. rc=%d\n",rc); } 编译运行,结果如图所示。

os实验二 进程同步

实验二:进程同步 一.实验目的 (1)掌握基本的同步算法,理解生产者消费者模型。 (2)学习使用Windows XP中基本的同步对象,掌握相关API的使用方法。 (3)了解Windows XP中多线程的并发执行机制,实现进程的同步与互斥。 二.实验属性 该实验为设计性实验。 三.实验仪器设备及器材 普通PC386以上微机 四.实验要求 本实验要求2学时完成。 本实验要求完成如下任务: (1)以生产者/消费者模型为依据,在Windows XP环境下创建一个控制台进程,在该进程中创建n个线程模拟生产者和消费者,实现进程(线程)的同步与互斥。 学习并理解生产者/消费者模型及其同步/互斥规则; 学习了解Windows同步对象及其特性; 熟悉实验环境,掌握相关API的使用方法; 设计程序,实现生产者/消费者进程(线程)的同步与互斥。 (2)扩展任务2选1: 1>利用信号量机制,写出不会发生死锁的解决哲学家进程(线程)。 最多允许4个同时进餐;奇:先左后右偶:先右后左。 2>利用信号量机制,写出不会发生死锁的读者写者进程(线程)。五:实验内容: 利用至多同时允许4位哲学家同时去拿左边筷子的方法解决进餐死锁的问题。 实验详细设计:流程图:

程序首先创建一个线程参数结构体 struct ThreadInfo { int serial; double delay; }; 设置最多同时去拿筷子的人数#define MAX_BUFFER_NUM 4 设置一个信号量数组用来表示五位哲学家的左右边的筷子HANDLE chopstick [5]; 设置同时去拿筷子的人数的信号量HANDLE People; 设置一个互斥信号量HANDLE h_mutex; 在main()函数中,首先创建信号量: for (int i=0;i<5;i++) { chopstick[i]=CreateSemaphore(NULL,n_Buffer_or_Critical,n_Buffer_or_Criti cal,"chopstick"+i); } People=CreateSemaphore(NULL,MAX_BUFFER_NUM,MAX_BUFFER _NUM,"People");

操作系统-进程管理实验报告

实验一进程管理 1.实验目的: (1)加深对进程概念的理解,明确进程和程序的区别; (2)进一步认识并发执行的实质; (3)分析进程争用资源的现象,学习解决进程互斥的方法; (4)了解Linux系统中进程通信的基本原理。 2.实验预备内容 (1)阅读Linux的sched.h源码文件,加深对进程管理概念的理解; (2)阅读Linux的fork()源码文件,分析进程的创建过程。 3.实验内容 (1)进程的创建: 编写一段程序,使用系统调用fork() 创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”,子进程分别显示字符“b”和“c”。试观察记录屏幕上的显示结果,并分析原因。 源代码如下: #include #include #include #include #include int main(int argc,char* argv[]) { pid_t pid1,pid2; pid1 = fork(); if(pid1<0){ fprintf(stderr,"childprocess1 failed"); exit(-1); } else if(pid1 == 0){ printf("b\n"); } 1/11

else{ pid2 = fork(); if(pid2<0){ fprintf(stderr,"childprocess1 failed"); exit(-1); } else if(pid2 == 0){ printf("c\n"); } else{ printf("a\n"); sleep(2); exit(0); } } return 0; } 结果如下: 分析原因: pid=fork(); 操作系统创建一个新的进程(子进程),并且在进程表中相应为它建立一个新的表项。新进程和原有进程的可执行程序是同一个程序;上下文和数据,绝大部分就是原进程(父进程)的拷贝,但它们是两个相互独立的进程!因此,这三个进程哪个先执行,哪个后执行,完全取决于操作系统的调度,没有固定的顺序。 (2)进程的控制 修改已经编写的程序,将每个进程输出一个字符改为每个进程输出一句话,再观察程序执行时屏幕上出现的现象,并分析原因。 将父进程的输出改为father process completed 2/11

进程管理实验报告

实验2过程管理实验报告学生号姓名班级电气工程系过程、过程控制块等基本原理过程的含义:过程是程序运行过程中对数据集的处理,以及由独立单元对系统资源的分配和调度。在不同的数据集上运行程序,甚至在同一数据集上运行多个程序,是一个不同的过程。(2)程序状态:一般来说,一个程序必须有三种基本状态:就绪、执行和阻塞。然而,在许多系统中,过程的状态变化可以更好地描述,并且增加了两种状态:新状态和终端状态。1)就绪状态,当一个进程被分配了除处理器(CPU)以外的所有必要资源时,只要获得了处理器,进程就可以立即执行。此时,进程状态称为就绪状态。在系统中,多个进程可以同时处于就绪状态。通常,这些就绪进程被安排在一个或多个队列中,这些队列称为就绪队列。2)一旦处于就绪状态的进程得到处理器,它就可以运行了。进程的状态称为执行状态。在单处理器系统中,只有一个进程在执行。在多处理器系统中,可能有多个进程在执行中。3)阻塞状态由于某些事件(如请求输入和输出、额外空间等),执行进程被挂起。这称为阻塞状态,也称为等待状态。通常,处于阻塞状态的进程被调度为-?这个队列称为阻塞队列。4)新状态当一个新进程刚刚建立并且还没有放入就绪队列中时,它被称为新状态。5)终止状态是

什么时候-?进程已正常或异常终止,操作系统已将其从系统队列中删除,但尚未取消。这就是所谓的终结状态。(3)过程控制块是过程实体的重要组成部分,是操作系统中最重要的记录数据。控制块PCB记录操作系统描述过程和控制过程操作所需的所有信息。通过PCB,一个不能独立运行的程序可以成为一个可以独立运行的基本单元,并且可以同时执行一个进程。换句话说,在进程的整个生命周期中,操作系统通过进程PCB管理和控制并发进程。过程控制块是系统用于过程控制的数据结构。系统根据进程的PCB来检测进程是否存在。因此,进程控制块是进程存在的唯一标志。当系统创建一个进程时,它需要为它创建一个PCB;当进程结束时,系统回收其PCB,进程结束。过程控制块的内容过程控制块主要包括以下四个方面的信息。过程标识信息过程标识用于对过程进行标识,通常有外部标识和内部标识。外部标识符由流程的创建者命名。通常是一串字母和数字。当用户访问进程时使用。外部标识符很容易记住。内部标识符是为了方便系统而设置的。操作系统为每个进程分配一个唯一的整数作为内部标识符。通常是进程的序列号。描述性信息(process scheduling message)描述性信息是与流程调度相关的一些有关流程状态的信息,包括以下几个方面。流程状态:表

山东大学操作系统实验报告4进程同步实验

山东大学操作系统实验报告4进程同步实验

计算机科学与技术学院实验报告 实验题目:实验四、进程同步实验学号: 日期:20120409 班级:计基地12 姓名: 实验目的: 加深对并发协作进程同步与互斥概念的理解,观察和体验并发进程同步与互斥 操作的效果,分析与研究经典进程同步与互斥问题的实际解决方案。了解 Linux 系统中 IPC 进程同步工具的用法,练习并发协作进程的同步与互斥操作的编程与调试技术。 实验内容: 抽烟者问题。假设一个系统中有三个抽烟者进程,每个抽烟者不断地卷烟并抽烟。抽烟者卷起并抽掉一颗烟需要有三种材料:烟草、纸和胶水。一个抽烟者有烟草,一个有纸,另一个有胶水。系统中还有两个供应者进程,它们无限地供应所有三种材料,但每次仅轮流提供三种材料中的两种。得到缺失的两种材料的抽烟者在卷起并抽掉一颗烟后会发信号通知供应者,让它继续提供另外的两种材料。这一过程重复进行。请用以上介绍的 IPC 同步机制编程,实现该问题要求的功能。 硬件环境: 处理器:Intel? Core?i3-2350M CPU @ 2.30GHz ×4 图形:Intel? Sandybridge Mobile x86/MMX/SSE2 内存:4G 操作系统:32位 磁盘:20.1 GB 软件环境: ubuntu13.04 实验步骤: (1)新建定义了producer和consumer共用的IPC函数原型和变量的ipc.h文件。

(2)新建ipc.c文件,编写producer和consumer 共用的IPC的具体相应函数。 (3)新建Producer文件,首先定义producer 的一些行为,利用系统调用,建立共享内存区域,设定其长度并获取共享内存的首地址。然后设定生产者互斥与同步的信号灯,并为他们设置相应的初值。当有生产者进程在运行而其他生产者请求时,相应的信号灯就会阻止他,当共享内存区域已满时,信号等也会提示生产者不能再往共享内存中放入内容。 (4)新建Consumer文件,定义consumer的一些行为,利用系统调用来创建共享内存区域,并设定他的长度并获取共享内存的首地址。然后设定消费者互斥与同步的信号灯,并为他们设置相应的初值。当有消费进程在运行而其他消费者请求时,相应的信号灯就会阻止它,当共享内存区域已空时,信号等也会提示生产者不能再从共享内存中取出相应的内容。 运行的消费者应该与相应的生产者对应起来,只有这样运行结果才会正确。

实验一 进程管理

实验一进程管理 【实验目的】 1)加深对进程概念及进程管理各部分内容的理解。 2)熟悉进程管理中主要数据结构的设计和进程调度算法、进程控制机构、同步机构、通讯机构的实施。 【实验要求】 调试并运行一个允许n 个进程并发运行的进程管理模拟系统。了解该系统的进程控制、同步及通讯机构,每个进程如何用一个PCB 表示、其内容的设置;各进程间的同步关系;系统在运行过程中显示各进程的状态和有关参数变化情况的意义。 【实验环境】 具备Windows或MS-DOS操作系统、带有Turbo C 集成环境的PC机。 【实验重点及难点】 重点:理解进程的概念,进程管理中主要数据结构的设计和进程调度算法、进程控制机构、同步机构、通讯机构的实施。 难点:实验程序的问题描述、实现算法、数据结构。 【实验内容】 一.阅读实验程序 程序代码见【实验例程】。 二.编译实验例程 用Turbo C 编译实验例程。 三.运行程序并对照实验源程序阅读理解实验输出结果的意义。 【实验例程】 #include #define TRUE 1 #define FALSE 0 #define MAXPRI 100 #define NIL -1 struct { int id; char status; int nextwr; int priority; } pcb [3]; struct { int value; int firstwr; } sem[2]; char savearea[3][4],addr; int i,s1,s2,seed, exe=NIL;

init() { int j; for (j=0;j<3;j++) { pcb[j].id=j; pcb[j].status='r'; pcb[j].nextwr=NIL; printf("\n process%d priority?",j+1); scanf("%d",&i); pcb[j].priority=i; } sem[0].value=1; sem[0].firstwr=NIL; sem[1].value=1; sem[1].firstwr=NIL; for(i=1;i<3;i++) for(j=0;j<4;j++) savearea[i] [j]='0'; } float random() { int m; if (seed<0) m=-seed; else m=seed; seed=(25173*seed+13849)%65536; return(m/32767.0); } timeint(ad) char ad; { float x; x=random(); if((x<0.33)&&(exe==0))return(FALSE); if((x<0.66)&&(exe==1))return(FALSE); if((x<1.0)&&(exe==2))return(FALSE); savearea[exe][0]=i; savearea[exe][1]=ad; pcb[exe].status='t'; printf("times silce interrupt'\n process%d enter into ready.\n",exe+1); exe=NIL; return(TRUE); } scheduler()

进程管理实验报告

进程的控制 1 .实验目的 通过进程的创建、撤消和运行加深对进程概念和进程并发执行的理解,明确进程与程序之间的区别。 【答:进程概念和程序概念最大的不同之处在于: (1)进程是动态的,而程序是静态的。 (2)进程有一定的生命期,而程序是指令的集合,本身无“运动”的含义。没有建立进程的程序不能作为1个独立单位得到操作系统的认可。 (3)1个程序可以对应多个进程,但1个进程只能对应1个程序。进程和程序的关系犹如演出和剧本的关系。 (4)进程和程序的组成不同。从静态角度看,进程由程序、数据和进程控制块(PCB)三部分组成。而程序是一组有序的指令集合。】2 .实验内容 (1) 了解系统调用fork()、execvp()和wait()的功能和实现过程。 (2) 编写一段程序,使用系统调用fork()来创建两个子进程,并由父进程重复显示字符串“parent:”和自己的标识数,而子进程则重复显示字符串“child:”和自己的标识数。 (3) 编写一段程序,使用系统调用fork()来创建一个子进程。子进程通过系统调用execvp()更换自己的执行代码,新的代码显示“new

program.”。而父进程则调用wait()等待子进程结束,并在子进程结束后显示子进程的标识符,然后正常结束。 3 .实验步骤 (1)gedit创建进程1.c (2)使用gcc 1.c -o 1编译并./1运行程序1.c #include #include #include #include void mian(){ int id; if(fork()==0) {printf(“child id is %d\n”,getpid()); } else if(fork()==0) {printf(“child2 id %d\n”,getpid()); } else {id=wait(); printf(“parent id is %d\n”,getpid()); }

实验二进程同步

实验二进程同步演示 一、实验目的 ?深入掌握进程同步机制——信号量的应用; ?掌握Windows编程中信号量机制的使用方法; ?可进行简单的信号量应用编程。 二、实验工具 Windows系统+ VC++ 6.0 三、实验内容 1、复习教材上信号量机制的定义与应用,复习经典进程同步问题——生产者消费者问题及其同步方案; 2、验证后附的参考代码pc.cpp(生产者消费者问题),掌握Windows系统中信号量的定义与使用方法; 注意: (1)代码中生产者和消费者所做的工作用过程Producer和Consumer描述,并通过创建线程的方法创建3个生产者线程和1个消费者线程,具体创建方法:CreateThread(NULL,0,Producer,NULL,0,&producerID[i]);其中第3个参数就是指定该线程所做的工作为过程Producer; (2)问题中设置了三个信号量g_hMutex(用于互斥访问临界区buffer)、 g_hFullSemaphore、g_hEmptySemaphore(用于控制同步的资源信号量),先声明,再定义,最后使用。互斥信号量和资源信号量的定义方法不同: g_hMutex = CreateMutex(NULL,FALSE,NULL); 互斥信号量最开始没有指定针对那个资源g_hFullSemaphore = CreateSemaphore(NULL,SIZE_OF_BUFFER-1,SIZE_OF_BUFFER-1,NULL); 其中第2和3个参数为信号量的初始值和最大值 信号量的使用方法:WaitForSingleObject为信号量的P操作,每对一个信号量执行该操作,则信号量值减1,并判断减1后值是否仍大于等于0,如是则该操作成功,否则进程阻塞;ReleaseSemaphore为信号量的V操作,每执行一次将该信号量的值加1,并起到唤醒作用。如: WaitForSingleObject(g_hFullSemaphore,INFINITE); … ReleaseSemaphore(g_hEmptySemaphore,1,NULL);

操作系统实验报告--实验一--进程管理

实验一进程管理 一、目的 进程调度是处理机管理的核心内容。本实验要求编写和调试一个简单的进程调度程序。通过本实验加深理解有关进程控制块、进程队列的概念,并体会和了解进程调度算法的具体实施办法。 二、实验内容及要求 1、设计进程控制块PCB的结构(PCB结构通常包括以下信息:进程名(进程ID)、进程优先数、轮转时间片、进程所占用的CPU时间、进程的状态、当前队列指针等。可根据实验的不同,PCB结构的内容可以作适当的增删)。为了便于处理,程序中的某进程运行时间以时间片为单位计算。各进程的轮转时间数以及进程需运行的时间片数的初始值均由用户给定。 2、系统资源(r1…r w),共有w类,每类数目为r1…r w。随机产生n进程P i(id,s(j,k),t),0<=i<=n,0<=j<=m,0<=k<=dt为总运行时间,在运行过程中,会随机申请新的资源。 3、每个进程可有三个状态(即就绪状态W、运行状态R、等待或阻塞状态B),并假设初始状态为就绪状态。建立进程就绪队列。 4、编制进程调度算法:时间片轮转调度算法 本程序用该算法对n个进程进行调度,进程每执行一次,CPU时间片数加1,进程还需要的时间片数减1。在调度算法中,采用固定时间片(即:每执行一次进程,该进程的执行时间片数为已执行了1个单位),这时,CPU时间片数加1,进程还需要的时间片数减1,并排列到就绪队列的尾上。 三、实验环境 操作系统环境:Windows系统。 编程语言:C#。 四、实验思路和设计 1、程序流程图

2、主要程序代码 //PCB结构体 struct pcb { public int id; //进程ID public int ra; //所需资源A的数量 public int rb; //所需资源B的数量 public int rc; //所需资源C的数量 public int ntime; //所需的时间片个数 public int rtime; //已经运行的时间片个数 public char state; //进程状态,W(等待)、R(运行)、B(阻塞) //public int next; } ArrayList hready = new ArrayList(); ArrayList hblock = new ArrayList(); Random random = new Random(); //ArrayList p = new ArrayList(); int m, n, r, a,a1, b,b1, c,c1, h = 0, i = 1, time1Inteval;//m为要模拟的进程个数,n为初始化进程个数 //r为可随机产生的进程数(r=m-n) //a,b,c分别为A,B,C三类资源的总量 //i为进城计数,i=1…n //h为运行的时间片次数,time1Inteval为时间片大小(毫秒) //对进程进行初始化,建立就绪数组、阻塞数组。 public void input()//对进程进行初始化,建立就绪队列、阻塞队列 { m = int.Parse(textBox4.Text); n = int.Parse(textBox5.Text); a = int.Parse(textBox6.Text); b = int.Parse(textBox7.Text); c = int.Parse(textBox8.Text); a1 = a; b1 = b; c1 = c; r = m - n; time1Inteval = int.Parse(textBox9.Text); timer1.Interval = time1Inteval; for (i = 1; i <= n; i++) { pcb jincheng = new pcb(); jincheng.id = i; jincheng.ra = (random.Next(a) + 1); jincheng.rb = (random.Next(b) + 1); jincheng.rc = (random.Next(c) + 1); jincheng.ntime = (random.Next(1, 5)); jincheng.rtime = 0;

进程的同步实验报告

操作系统 实验报告 哈尔滨工程大学 计算机科学与技术学院

一、实验概述 1. 实验名称 进程的同步 2. 实验目的 ⑴使用EOS的信号量,编程解决生产者 消费者问题,理解进程同步的意义。 ⑵调试跟踪EOS信号量的工作过程,理解进程同步的原理。 ⑶修改EOS的信号量算法,使之支持等待超时唤醒功能(有限等待),加深理解进程同步的原理。 3. 实验类型 验证+设计 4. 实验内容 ⑴准备实验 ⑵使用EOS的信号量解决生产者-消费者问题 ⑶调试EOS信号量的工作过程 ①创建信号量 ②等待释放信号量 ③等待信号量(不阻塞) ④释放信号量(不唤醒) ⑤等待信号量(阻塞) ⑥释放信号量(唤醒) ⑷修改EOS的信号量算法 二、实验环境 WindowsXP + EOS集成实验环境 三、实验过程 1. 设计思路和流程图

图4-1.整体试验流程图

图4-2.Main 函数流程图、生产者消费、消费者流程图 2. 算法实现 3. 需要解决的问题及解答 (1). 思考在ps/semaphore.c 文件内的PsWaitForSemaphore 和PsReleaseSemaphore 函数中,为什么要使用原子操作?

答:在执行等待信号量和释放信号量的时候,是不允许cpu响应外部中断的,如果此时cpu响应了外部中断,会产生不可预料的结果,无法正常完成原子操作。 (2). 绘制ps/semaphore.c文件内PsWaitForSemaphore和PsReleaseSemaphore函数的流程图。 (3).P143生产者在生产了13号产品后本来要继续生产14号产品,可此时生产者为什么必须等待消费者消费了4号产品后,才能生产14号产品呢?生产者和消费者是怎样使用同步对象来实现该同步过程的呢? 答:这是因为临界资源的限制。临界资源就像产品仓库,只有“产品仓库”空闲生产者才能生产东西,有权向里面放东西。所以它必须等到消费者,取走产品,“产品空间”(临界资源)空闲时,才继续生产14号产品。 (4). 根据本实验3.3.2节中设置断点和调试的方法,自己设计一个类似的调试方案来验证消费者线程在消费24号产品时会被阻塞,直到生产者线程生产了24号产品后,消费者线程才被唤醒并继续执行的过程。 答:可以按照下面的步骤进行调试 (1) 删除所有的断点。 (2) 按F5启动调试。OS Lab会首先弹出一个调试异常对话框。 (3) 在调试异常对话框中选择“是”,调试会中断。 (4) 在Consumer函数中等待Full信号量的代码行(第173行)WaitForSingleObject(FullSemaphoreHandle, INFINITE); 添加一个断点。 (5) 在“断点”窗口(按Alt+F9打开)中此断点的名称上点击右键。 (6) 在弹出的快捷菜单中选择“条件”。 (7) 在“断点条件”对话框(按F1获得帮助)的表达式编辑框中,输入表达式“i == 24”。 (8) 点击“断点条件”对话框中的“确定”按钮。 (9) 按F5继续调试。只有当消费者线程尝试消费24号产品时才会在该条件断点处中断。 4. 主要数据结构、实现代码及其说明 修改PsWaitForSemaphore函数 if (Semaphore->Count>0){ Semaphore->Count--; flag=STATUS_SUCCESS; }//如果信号量大于零,说明尚有资源,可以为线程分配 else flag=PspWait(&Semaphore->WaitListHead, Milliseconds); KeEnableInterrupts(IntState); // 原子操作完成,恢复中断。 return flag; }//否则,说明资源数量不够,不能再为线程分配资源,因此要使线程等待 修改PsReleaseSemaphore函数 if (Semaphore->Count + ReleaseCount > Semaphore->MaximumCount) {

进程同步实验报告

实验三进程的同步 一、实验目的 1、了解进程同步和互斥的概念及实现方法; 2、更深一步的了解fork()的系统调用方式。 二、实验内容 1、预习操作系统进程同步的概念及实现方法。 2、编写一段源程序,用系统调用fork()创建两个子进程,当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”;子进程分别显示字符“b”和字符“c”。程序的输出是什么?分析原因。 3、阅读模拟火车站售票系统和实现进程的管道通信源代码,查阅有关进程创建、进程互斥、进程同步的系统功能调用或API,简要解释例程中用到的系统功能或API的用法,并编辑、编译、运行程序,记录程序的运行结果,尝试给出合理的解释。 4、(选做)修改问题2的代码,使得父子按顺序显示字符“a”;“b”、“c”编辑、编译、运行。记录程序运行结果。 三、设计思想 1、程序框架 (1)创建两个子进程:(2)售票系统:

(3)管道通信: 先创建子进程,然后对内容加锁,将输出语句存入缓存,并让子进程自己进入睡眠,等待别的进程将其唤醒,最后解锁;第二个子进程也执行这样的过程。父进程等待子进程后读内容并输出。 (4)修改程序(1):在子进程的输出语句前加上sleep()语句,即等待父进程执行完以后再输出。 2、用到的文件系统调用函数 (1)创建两个子进程:fork() (2)售票系统:DWORD WINAPI Fun1Proc(LPVOID lpPartameter); CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); CloseHandle(hThread1); (HANDLE)CreateMutex(NULL,FALSE,NULL); Sleep(4000)(sleep调用进程进入睡眠状态(封锁), 直到被唤醒); WaitForSingleObject(hMutex,INFINITE); ReleaseMutex(hMutex); (3)管道通信:pipe(fd),fd: int fd[2],其中: fd[0] 、fd[1]文件描述符(读、写); lockf( fd,function,byte)(fd: 文件描述符;function: 1: 锁定 0:解锁;byte: 锁定的字节数,0: 从当前位置到文件尾); write(fd,buf,byte)、read(fd,buf,byte) (fd: 文件描述符;buf : 信息传送的源(目标)地址;byte: 传送的字节数); sleep(5); exit(0); read(fd[0],s,50) (4)修改程序(1):fork(); sleep(); 四、调试过程 1、测试数据设计 (1)创建两个子进程:

操作系统实验二(进程管理)

操作系统进程管理实验 实验题目: (1)进程的创建编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”;子进程分别显示字符“b”和字符“c”。试观察记录屏幕上的显示结果,并分析原因。 (2)进程的控制修改已编写的程序,将每个进程输出一个字符改为每个进程输出一句话,在观察程序执行时屏幕上出现的现象,并分析原因。 (3)编制一段程序,使其实现进程的软中断通信。要求:使用系统调用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按Del键);当捕捉到中断信号后,父进程调用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:Child process 1 is killed by parent! Child process 2 is killed by parent! 父进程等待两个子进程终止后,输出如下的信息后终止:Parent process is killed! 在上面的程序中增加语句signal(SIGINT, SIG_IGN)和signal(SIGQUIT, SIG_IGN),观察执行结果,并分析原因。 (4)进程的管道通信编制一段程序,实现进程的管道通信。使用系统调用pipe( )建立一条管道线;两个进程P1和P2分别向管道各写一句话:Child 1 is sending a message! Child 2 is sending a message! 而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。要求父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。 实验源程序及报告: (1)、进程的创建 #include int main(int argc, char *argv[]) { int pid1,pid2; /*fork first child process*/ if ( ( pid1=fork() ) < 0 ) { printf( "ProcessCreate Failed!"); exit(-1); }

实验二进程同步实验

实验二进程同步 一、实验目的: 掌握基本的同步算法,理解经典进程同步问题的本质;学习使用Linux的进程同步机制,掌握相关API的使用方法;能利用信号量机制,采用多种同步算法实现不会发生死锁的哲学家进餐程序。 二、实验平台: 虚拟机:VMWare9以上 操作系统:以上 编辑器:Gedit | Vim 编译器:Gcc 三、实验内容: (1)以哲学家进餐模型为依据,在Linux控制台环境下创建5个进程,用semget函数创建一个信号量集(5个信号量,初值为1),模拟哲学家的思考和进餐行为:每一位哲学家饥饿时,先拿起左手筷子,再拿起右手筷子;筷子是临界资源,为每一支筷子定义1个互斥信号量;想拿到筷子需要先对信号量做P操作,使用完释放筷子对信号量做V操作。 伪代码描述: semaphore chopstick[5]={1,1,1,1,1}; ?第i位哲学家的活动可描述为: do{ printf("%d is thinking\n",i); printf("%d is hungry\n",i); wait(chopstick[i]); 当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐;b.至多只允许有4位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐;c.规定奇数号哲学家先拿起他左手的筷子,然后再拿起他右手的筷子,而偶数号哲学家则先拿起他右手的筷子,然后再拿起他左手的筷子。方法a在示例程序中给出,请用方法b和c写出不会发生死锁的哲学家进餐程序。 (3)设计程序,实现生产者/消费者进程(线程)的同步与互斥。在该程序中创建4个进程(或线程)模拟生产者和消费者,实现进程(线程)的同步与互斥。

Linux 进程管理实验

Linux 进程管理实验 一、实验内容: 1. 利用bochs观测linux0.11下的PCB进程控制结构。 2. 利用bochs观测linux0.11下的fork.c源代码文件,简单分析其中的重要函数。 3. 在fork.c适当位置添加代码,以验证fork函数的工作原理。 二、Linux进程管理机制分析 Linux有两类进程:一类是普通用户进程,一类是系统进程,它既可以在用户空间运行,又可以通过系统调用进入内核空间,并在内核空间运行;另一类叫做内核进程,这种进程只能在内核空间运行。在以i386为平台的Linux系统中,进程由进程控制块,系统堆栈,用户堆栈,程序代码及数据段组成。Linux系统中的每一个用户进程有两个堆栈:一个叫做用户堆栈,它是进程运行在用户空间时使用的堆栈;另一个叫做系统堆栈,它是用户进程运行在系统空间时使用的堆栈。 1.Linux进程的状态: Linux进程用进程控制块的state域记录了进程的当前状态,一个Linux 进程在它的生存期中,可以有下面6种状态。 1.就绪状态(TASK_RUNNING):在此状态下,进程已挂入就绪队列,进入准备运行状态。 2.运行状态(TASK_RUNNING):当进程正在运行时,它的state域中的值不改变。但是Linux会用一个专门指针(current)指向当前运行的

任务。 3.可中断等待状态(TASK_INTERRUPTIBLE):进程由于未获得它所申请的资源而处在等待状态。不管是资源有效或者中断唤醒信号都能使等待的进程脱离等待而进入就绪状态。即”浅睡眠状态”。 4.不可中断等待状态(TASK_UNINTERRUPTIBLE):这个等待状态与上面等待状态的区别在于只有当它申请的资源有效时才能被唤醒,而其它信号不能。即“深睡眠状态”。 5.停止状态(TASK_STOPPED):当进程收到一个SIGSTOP信号后就由运行状态进入停止状态,当收到一个SINCONT信号时,又会恢复运行状态。挂起状态。 6.终止状态(TASK_ZOMBIE):进程因某种原因终止运行,但进程控制块尚未注销。即“僵死状态”。 状态图如下所示: 2.Linux进程控制块:

相关文档