《计算机操作系统》
实验报告
班级:
姓名:
学号:
实验一 进程控制与描述
一、实验目的
通过对Windows 2000编程,进一步熟悉操作系统的基本概念,较好地理解Windows 2000的结构。通过创建进程、观察正在运行的进程和终止进程的程序设计和调试操作,进一步熟悉操作系统的进程概念,理解Windows 2000中进程的“一生”。
二、实验环境
硬件环境:计算机一台,局域网环境;
软件环境:Windows 20## Professional、Visual C++ 6.0企业版。
三、实验内容和步骤
第一部分:
程序1-1Windows 20## 的GUI 应用程序
Windows 20## Professional下的GUI应用程序,使用Visual C++编译器创建一个GUI应用程序,代码中包括了WinMain()方法,该方法GUI类型的应用程序的标准入口点。
# include
# pragma comment(lib, “user32.lib” )
int APIENTRY WinMain(HINSTANCE /* hInstance */ ,
HINSTANCE /* hPrevInstance */,
LPSTR /* lpCmdLine */,
int /* nCmdShow */ )
{
:: MessageBox(
NULL,
“hello, Windows 2000” ,
“Greetings”,
MB_OK) ;
return(0) ; }
在程序1-1的GUI应用程序中,首先需要Windows.h头文件,以便获得传送给WinMain() 和MessageBox() API函数的数据类型定义。
接着的pragma指令指示编译器/连接器找到User32.LIB库文件并将其与产生的EXE文件连接起来。这样就可以运行简单的命令行命令CL MsgBox.CPP来创建这一应用程序,如果没有pragma指令,则MessageBox() API函数就成为未定义的了。这一指令是Visual Studio C++ 编译器特有的。
接下来是WinMain() 方法。其中有四个由实际的低级入口点传递来的参数。hInstance参数用来装入与代码相连的图标或位图一类的资源,无论何时,都可用GetModuleHandle() API函数将这些资源提取出来。系统利用实例句柄来指明代码和初始的数据装在内存的何处。句柄的数值实际上是EXE文件映像的基地址,通常为0x00400000。下一个参数hPrevInstance是为向后兼容而设的,现在系统将其设为NULL。应用程序的命令行 (不包括程序的名称) 是lpCmdLine参数。另外,系统利用nCmdShow参数告诉应用程序如何显示它的主窗口 (选项包括最小化、最大化和正常) 。
最后,程序调用MessageBox() API函数并退出。如果在进入消息循环之前就结束运行的话,最后必须返回0。
先分析程序功能,再写出运行结果:
操作系统将当前运行的应用程序看作是进程对象。利用系统提供的惟一的称为句柄 (HANDLE) 的号码,就可与进程对象交互。这一号码只对当前进程有效。
在系统中运行的任何进程都可调用GetCurrentProcess() API函数,此函数可返回标识进程本身的句柄。然后就可在Windows需要该进程的有关情况时,利用这一句柄来提供。
程序1-2: 获得和使用进程的句柄
# include
# include
void main()
{
HANDLE hProcessThis = :: GetCurrentProcess() ;
DWORD dwPriority = :: GetPriorityClass(hProcessThis) ;
std :: cout << “Current process priority: ” ;
switch(dwPriority)
{
case HIGH_PRIORITY_CLASS:
std :: cout << “High” ;
break;
case NORMAL_PRIORITY_CLASS:
std :: cout << “Normal” ;
break;
case IDLE_PRIORITY_CLASS:
std :: cout << “Idle” ;
break;
case REALTIME_PRIORITY_CLASS:
std :: cout << “Realtime” ;
break;
default:
std :: cout << “” ;
break;
}
std :: cout << std :: endl;
}
程序1-2中列出的是一种获得进程句柄的方法。对于进程句柄可进行的惟一有用的操作是在API调用时,将其作为参数传送给系统,正如程序1-2中对GetPriorityClass() API函数的调用那样。在这种情况下,系统向进程对象内“窥视”,以决定其优先级,然后将此优先级返回给应用程序。
OpenProcess() 和CreateProcess() API函数也可以用于提取进程句柄。前者提取的是已经存在的进程的句柄,而后者创建一个新进程,并将其句柄提供出来。
先分析程序功能,再写出运行结果:
程序1-3显示如何找出系统中正在运行的所有进程,如何利用OpenProcess() API函数来获得每一个访问进程的进一步信息。
程序1-3 利用句柄查出进程的详细信息
// proclist项目
# include
# include
# include
DWORD GetKernelModePercentage(const FILETIME & ftKernel,
const FILETIME & ftUser)
{
ULONGLONG qwKernel =
( ( (ULONGLONG) ftKernel.dwHighDateTime) << 32) +
ftKernel.dwLowDateTime;
ULONGLONG qwUser =
( ( (ULONGLONG) ftUser.dwHighDateTime) << 32) +
ftUser.dwLowDateTime;
ULONGLONG qwTotal = qwKernel + qwUser;
DWORD dwPct =
(DWORD) ( ( (ULONGLONG) 100*qwKernel) / qwTotal) ;
return(dwPct) ;
}
void main()
{
HANDLE hSnapshot = :: CreateToolhelp32Snapshot(
TH32CS_SNAPPROCESS,
0) ;
PROCESSENTRY32 pe;
:: ZeroMemory(&pe, sizeof(pe) ) ;
pe.dwSize = sizeof(pe) ;
BOOL bMore = :: Process32First(hSnapshot, &pe) ;
while(bMore)
{
HANDLE hProcess = :: OpenProcess(
PROCESS_QUERY_INFORMATION,
FALSE,
pe.th32ProcessID) ;
if (hProcess != NULL)
{ FILETIME ftCreation, ftExit, ftKernelMode, ftUserMode;
:: GetProcessTimes(
hProcess,
&ftCreation,
&ftExit,
&ftKernelMode,
&ftUserMode) ;
DWORD dwPctKernel = :: GetKernelModePercentage(
ftKernelMode,
ftUserMode ) ;
std :: cout << “Process ID: ” << pe.th32ProcessID
<< “, EXE file: ” << pe.szExeFile
<< “, % in kernel mode: ” << dwPctKernel
<< std :: endl;
:: CloseHandle(hProcess) ;
}
bMore = :: Process32Next(hSnapshot, &pe) ; }
}
程序1-3程序首先利用Windows 2000的新特性,即工具帮助库来获得当前运行的所有进程的快照。然后应用程序进入快照中的每一个进程,得到其以PROCESSENTRY32结构表示的属性。这一结构用来向OpenProcess() API函数提供进程的ID。Windows跟踪每一进程的有关时间,示例中是通过打开的进程句柄和GetProcessTimes() API来直询得到有关时间的。接下来,一个定制的帮助函数取得了几个返回的数值,然后计算进程在内核模式下消耗的时间占总时间的百分比。程序的其余部分比较简单,只是将有关信息显示给用户,清除进程句柄,然后继续循环,直到所有进程都计算过为止。
先分析程序功能,再写出运行结果:
第二部分:进程的“一生”(共三个程序)
1、 创建进程
//创建子进程
# include
# include
# include
void StartClone(int nCloneID)
{
TCHAR szFilename[MAX_PATH] ;
:: GetModuleFileName(NULL, szFilename, MAX_PATH) ;
TCHAR szCmdLine[MAX_PATH] ;
:: sprintf(szCmdLine, “\”%s\” %d”, szFilename, nCloneID) ;
STARTUPINFO si;
:: ZeroMemory(reinterpret_cast (&si) , sizeof(si) ) ;
si.cb = sizeof(si) ;
PROCESS_INFORMATION pi;
BOOL bCreateOK = :: CreateProcess(
szFilename,
szCmdLine,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi) ;
if (bCreateOK)
{ :: CloseHandle(pi.hProcess) ;
:: CloseHandle(pi.hThread) ;
}}
int main(int argc, char* argv[] )
{
int nClone(0) ;
if (argc > 1)
{ :: sscanf(argv[1] , “%d” , &nClone) ;}
std :: cout << “Process ID: “ << :: GetCurrentProcessId()
<< “, Clone ID: “ << nClone
<< std :: endl;
const int c_nCloneMax = 25;
if (nClone < c_nCloneMax)
{
StartClone(++nClone) ;
}
:: Sleep(500) ;
return 0;
}
本程序展示的是一个简单的使用CreateProcess() API函数的例子。首先形成简单的命令行,提供当前的EXE文件的指定文件名和代表生成克隆进程的号码。大多数参数都可取缺省值,但是创建标志参数使用了:
____CREATE_NEW_CONSOLE_____________________________
标志,指示新进程分配它自己的控制台,这使得运行示例程序时,在任务栏上产生许多活动标记。然后该克隆进程的创建方法关闭传递过来的句柄并返回main() 函数。在关闭程序之前,每一进程的执行主线程暂停一下,以便让用户看到其中的至少一个窗口。
CreateProcess() 函数有____10____个核心参数?本实验程序中设置的各个参数的值是:
a.__________szFilename___________________________;
b.__________szCmdLine_________________________;
c. __________NULL__________________________;
d.___________NULL________________________;
e. ___________FALSE____;
f.__________CREATE NEW CONSOLE________;
g.__________szCmdLine_________________________;
h. __________NULL__________________________;
i.___________&si________________________;
j. ___________&pi____;
程序运行时屏幕显示的信息是:
*(此图是最后出现的一个图,在此之前连续出现几个图。)
2、 正在运行的进程.
使用进程和操作系统的版本信息
// version项目
# include
# include
void main()
{
DWORD dwIdThis = :: GetCurrentProcessId() ;
DWORD dwVerReq = :: GetProcessVersion(dwIdThis) ;
WORD wMajorReq =( (WORD) dwVerReq > 16) ;
WORD wMinorReq = ((WORD) dwVerReq & 0xffff) ;
std :: cout << “Process ID: “ << dwIdThis
<< “, requires OS: “ << wMajorReq << wMinorReq << std :: endl ;
OSVERSIONINFOEX osvix;
:: ZeroMemory(&osvix, sizeof(osvix) ) ;
osvix.dwOSVersionInfoSize = sizeof(osvix) ;
:: GetVersionEx(reinterpret_cast < LPOSVERSIONINFO > (&osvix) ) ;
std :: cout << “Running on OS: “ << osvix.dwMajorVersion << “.”
<< osvix.dwMinorVersion << std :: endl;
if (osvix.dwPlatformId == VER_PLATFORM_WIN32_NT &&
osvix.dwMajorVersion >= 5)
{
:: SetPriorityClass(
:: GetCurrentProcess() ,
HIGH_PRIORITY_CLASS) ;
std :: cout << “Task Manager should now now indicate this”
“process is high priority.” << std :: endl;
}
}
分析程序,写出运行结果:
当前PID信息:_______1492______________________________________________
当前操作系统版本:___Running on OS:5.1_________________________________
系统提示信息:Task Manager should now indiate this process is high priority.
_______________________________________________________________________
程序向读者表明了如何获得当前的PID和所需的进程版本信息。为了运行这一程序,系统处理了所有的版本不兼容问题。
接着,程序演示了如何使用GetVersionEx() API函数来提取OSVERSIONINFOEX结构。这一数据块中包括了操作系统的版本信息。其中,“OS : 5.0”表示当前运行的操作系统是:
____Windows2000_当前版本为_OS:5.0________________________________
最后一段程序利用了操作系统的版本信息,以确认运行的是Windows 2000。代码接着将当前进程的优先级提高到比正常级别高。
单击Ctrl + Alt + Del键,进入“Windows任务管理器”,在“应用程序”选项卡中右键单击本任务,在快捷菜单中选择“转到进程”命令。
在“Windows任务管理器”的“进程”选项卡中,与本任务对应的进程映像名称是 (为什么?) :
____________vcspawn.exe____________________________________________
右键单击该进程名,在快捷菜单中选择“设置优先级”命令,可以调整该进程的优先级,如设置为“高”后重新运行程序,屏幕显示有变化吗?为什么?
_____屏幕显示有变化。Process ID值由1492变为:3152________________
3、 终止进程
指令其子进程来“杀掉”自己的父进程
// procterm项目
# include
# include
# include
static LPCTSTR g_szMutexName = “w2kdg.ProcTerm.mutex.Suicide” ;
void StartClone()
{
TCHAR szFilename [MAX_PATH] ;
:: GetModuleFileName(NULL, szFilename, MAX_PATH) ;
TCHAR szCmdLine[MAX_PATH] ;
:: sprintf(szCmdLine, “\” %s\“ child” , szFilename) ;
STARTUPINFO si;
:: ZeroMemory(reinterpret_cast < void* > (&si) , sizeof(si) ) ;
si.cb = sizeof(si) ;
PROCESS_INFORMATION pi;
BOOL bCreateOK = :: CreateProcess(
szFilename,
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() ;
:: Sleep(5000) ;
std :: cout << “Telling the child process to quit. ” << std :: endl;
:: ReleaseMutex(hMutexSuicide) ;
:: CloseHandle(hMutexSuicide) ;}
}
void Child()
{// 打开“自杀”互斥体
HANDLE hMutexSuicide = :: OpenMutex(
SYNCHRONIZE,
FALSE,
g_szMutexName) ;
if (hMutexSuicide != NULL)
{
std :: cout << “Child waiting for suicide instructions. ” << std :: endl;
:: WaitForSingleObject(hMutexSuicide, INFINITE) ;
std :: cout << “Child quiting. ” << std :: endl;
:: CloseHandle(hMutexSuicide) ;
}}
int main(int argc, char* argv[] )
{
if (argc >1&& :: strcmp(argv[1] , “child” ) == 0)
{ Child() ; }
else
{Parent() ;}
return 0;
}
程序说明了一个进程从“生”到“死”的整个一生。第一次执行时,它创建一个子进程,其行为如同“父亲”。在创建子进程之前,先创建一个互斥的内核对象,其行为对于子进程来说,如同一个“自杀弹”。当创建子进程时,就打开了互斥体并在其他线程中进行别的处理工作,同时等待着父进程使用ReleaseMutex() API发出“死亡”信号。然后用Sleep() API调用来模拟父进程处理其他工作,等完成时,指令子进程终止。
当调用ExitProcess() 时要小心,进程中的所有线程都被立刻通知停止。在设计应用程序时,必须让主线程在正常的C++ 运行期关闭 (这是由编译器提供的缺省行为) 之后来调用这一函数。当它转向受信状态时,通常可创建一个每个活动线程都可等待和停止的终止事件。
在正常的终止操作中,进程的每个工作线程都要终止,由主线程调用ExitProcess()。接着,管理层对进程增加的所有对象释放引用,并将用 GetExitCodeProcess() 建立的退出代码从STILL_ACTIVE改变为在ExitProcess() 调用中返回的值。最后,主线程对象也如同进程对象一样转变为受信状态。
等到所有打开的句柄都关闭之后,管理层的对象管理器才销毁进程对象本身。还没有一种函数可取得终止后的进程对象为其参数,从而使其“复活”。当进程对象引用一个终止了的对象时,有好几个API函数仍然是有用的。进程可使用退出代码将终止方式通知给调用GetExitCodeProcess() 的其他进程。同时,GetProcessTimes() API函数可向主调者显示进程的终止时间。
先分析程序功能,再写出运行结果:
1) _第一次执行时,它创建一个子进程,其行为如同“父亲”。
表示:_ Creating the child process.
2) ______用Sleep() API调用来模拟父进程处理其他工作,等完成时,指令子进程终止。
表示:Telling the child process to quit
在熟悉源代码的基础上,利用本实验介绍的API函数来尝试改进本程序 (例如使用GetProcessTimes() API函数) 并运行。请描述你所做的工作:
____GetProcessTimes() API 可向主调者显示进程终止时间___________________
_
四、实验总结
进程具有的特征:结构特征、动态性、并发性、独立性和异步性。
对于进程的定义可以从不同的角度来说,其中较为典型的定义有:
(1)进程是程序的一次执行
(2)进程是一个程序及其数据在处理机上顺序执行时发生的活动
(3)进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
对于传统os中的进程定义为:进程是进程实体的运行过程,使系统进行资源分配和调度的一个独立单位。
进程有三种基本状态:就绪状态、执行状态、阻塞状态。
创建一个进程:
(1)、申请空白的PCB
(2)为进城分配资源
(3)初始化进程控制块
(4)将进程插入就绪队列
终止一个进程:
(1)根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态
(2)若终止进程正处于执行状态,应立即中止该进程的执行,并置调度标志为真,用于
指示该进程被终止进程的后应该重新进行调度
(3)若该进程还有子孙进程,还应该将其所有的子孙进程终止,以防止他们成为不可控的进程
(4)将终止进程所拥有的全部资源,或者归还给其父进程,或者归还给系统
(5)将终止进程PCB从所在队列中移除,等待其他程序来搜索信
通过实验更清楚的了解了进程,理解了进程的创建过程和终止过程
实验二 并发与调度
一、实验目的
在本实验中,通过对事件和互斥体对象的了解,来加深对Windows 2000线程同步的理解。通过分析实验程序,了解管理事件对象的API。了解在进程中如何使用事件对象,在进程中如何使用互斥体对象,线程如何通过文件映射对象发送数据。
在Linux Redhat 9.0操作系统平台上,用pipe()创建一个管道文件,然后用fork()创建两个生产进程和两个消费进程,它们之间通过pipe()传递消息。
二、实验环境
硬件环境:计算机一台,局域网环境;
软件环境:Windows 20## 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) 请描述运行结果 (如果运行不成功,则可能的原因是什么?)
50进程交替使用互斥资源,从50开始直到0,如上图所示
2) 根据运行输出结果,对照分析程序,可以看出程序运行的流程吗?请简单描述
1、 创建互斥体用于访问数值;
2、 解除程序释放对对象的引用简单的等待方法,在两个线程终止之前可暂停主调者;
3、 使用等待方法,在两个线程终止之前可暂停主调者;
4、 确保所有对象都已准备好;
5、 等待两者完成(顺序并不重要);
6、 循环,知道所有的访问都结束为止。
第二部分线程通过文件对象发送数据
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) 程序中启动了多少个单独的读写线程?
__________________100个__________________________________________________
3) 使用了哪个系统API函数来创建线程例程?
HANDLE hThread=::CreareThread()
3) 文件的读和写操作分别使用了哪个API函数?
ReadFile()和WriteFile()
每次运行进程时,都可看到程序中的每个线程从前面的线程中读取数据并将数据增加,文件中的数值连续增加。这个示例是很简单的通讯机制。可将这一示例用作编写自己的文件读/写代码的模板。
请注意程序中写入之前文件指针的重置。重置文件指针是必要的,因为该指针在读取结束时将处于前四个字节之后,同一指针还要用于向文件写入数据。如果函数向该处写入新数值,则下次进程运行时,只能读到原来的数值。那么:
4) 在程序中,重置文件指针使用了哪一个函数?
_____SetFilePointer()___________________________________________________
4) 从输出结果,对照分析程序,可以看出程序运行的流程吗?请简单描述:
1、 创建一个进程,以子进程模式工作
2、 创建一个事件或一个子进程,然后等待子进程在返回前向事件发出信号
3、 子进程模式被调用,其功能只是向父进程发出终止信号
4、 检查父进程或子进程是否启动
5、 向父进程创建的事件发送信号
6、 创建一个事件并等待子进程发出信号
2、演示使用映射文件的内存交换数据的线程
# include
# include
// 仲裁访问的互斥体
static HANDLE g_hMutexMapping = INVALID_HANDLE_VALUE;
// 增加共享内存中的数值的简单线程
static DWORD WINAPI ThreadProc(LPVOID IpParam)
{
// 将参数看作句柄
HANDLE hMapping = reinterpret_cast (IpParam) ;
// 等待对文件的访问
:: WaitForSingleObject(g_hMutexMapping, INFINITE) ;
// 映射视图
LPVOID pFile = :: MapViewOfFile(
hMapping, // 保存文件的对象
FILE_MAP_ALL_ACCESS, // 获得读写权限
0, // 在文件的开头处 (高32位) 开始
0, // ... (低32位)
0) ; // 映射整个文件
if (pFile != NULL)
{
// 将数据看作长整数
LONG * pnData = reinterpret_cast (pFile) ;
// 改动数据
++(* pnData) ;
// 显示新数值
std :: cout << "thread: " << :: GetCurrentThreadId()
<< "value: "<< (* pnData) << std :: endl;
// 释放文件视图
:: UnmapViewOfFile(pFile) ;
pFile = NULL;
}
// 释放对文件的访问权
:: ReleaseMutex(g_hMutexMapping) ;
return(0) ;
}
// 创建共享数据空间
HANDLE MakeSharedFile()
{
// 创建文件映射对象
HANDLE hMapping = :: CreateFileMapping(
INVALID_HANDLE_VALUE, // 使用页式文件临时文件
NULL, // 缺省的安全性
PAGE_READWRITE, // 可读写权
0, // 最大容量 (高32位)
sizeof(LONG) , // ... (低32位)
NULL) ; // 匿名的
if (hMapping != INVALID_HANDLE_VALUE)
{
// 在文件映射上创建视图
LPVOID pData = :: MapViewOfFile(
hMapping, // 保存文件的对象
FILE_MAP_ALL_ACCESS, // 获得读写权
0, // 在文件的开头处(高32位)开始
0, // ... (低32位)
0 ) ; // 映射整个文件
if (pData != NULL)
{
:: ZeroMemory(pData, sizeof(LONG) ) ;
}
// 关闭文件视图
:: UnmapViewOfFile(pData) ;
}
return (hMapping) ;
}
void main()
{
// 创建数据文件
HANDLE hMapping = :: MakeSharedFile() ;
// 创建仲裁的互斥体
g_hMutexMapping = :: CreateMutex(NULL, FALSE, NULL) ;
// 根据文件创建10个线程来读写
for (int nTotal = 10; nTotal > 0 ; --nTotal)
{
// 启动线程
HANDLE hThread = :: CreateThread(
NULL, // 缺省的安全性
0, // 缺省堆栈
ThreadProc, // 线程函数
reinterpret_cast (hMapping) , // 增量
0, // 无特殊的创建标志
NULL) ; // 忽略线程id
// 等待最后的线程释放
if (nTotal ==1)
{
std :: cout << "all threads created, waiting..."<< std :: endl;
:: WaitForSingleObject(hThread, INFINITE) ;
}
:: Sleep(500) ; // 放慢显示速度,方便观察
// 释放指向线程的句柄
:: CloseHandle(hThread) ;
hThread = INVALID_HANDLE_VALUE;
}
// 关闭对象
:: CloseHandle(hMapping) ;
hMapping = INVALID_HANDLE_VALUE;
:: CloseHandle(g_hMutexMapping) ;
g_hMutexMapping = INVALID_HANDLE_VALUE;
}
程序运行结果:
阅读和分析程序,请回答:
1) 程序中用来创建一个文件映射对象的系统API函数是哪个?
CreateFileMapping( )_______________________________________________
2) 在文件映射上创建和关闭文件视图分别使用了哪一个系统函数?
a. __MapViewOfFile( )
b. ___ UnmapViewOfFile(pData)__________________________________________
3) 运行时,程序首先通过 (CreateFileMapping( ) ) 函数创建一个小型的文件映射对象 ( hMapping ) ,接着,使用系统API函数 (g_hMutexMapping ) 再创建一个保护其应用的互斥体 (CreateMutex()) 。然后,应用程序创建100个线程,每个都允许进行同样的进程,即:通过互斥体获得访问权,这个操作是由语句:_ for (int nTotal = 100; nTotal > 0 ; -- nTotal)
{
// 启动线程
HANDLE hThread = :: CreateThread(
NULL, // 缺省的安全性
0, // 缺省堆栈
ThreadProc, // 线程函数
reinterpret_cast (hMapping) , // 增量
0, // 无特殊的创建标志
NULL) ; // 忽略线程id
实现的。再通过函数
(MapViewOfFile( ) ) 操作将视图映射到文件,将高32位看作有符号整数,将该数值增加 (即命令:MapViewOfFile(
hMapping, // 保存文件的对象
FILE_MAP_ALL_ACCESS, // 获得读写权
0, // 在文件的开头处(高32位)开始
0, // ... (低32位)
0 ) ; // 映射整个文件) ,再将新数值显示在控制台上。每个线程清除文件的视图并在退出之前释放互斥体释放互斥体的语句是___ CloseHandle(g_hMutexMapping) 。当线程完成时,应用程序关闭并退出。
5) 将程序中的语句 :: Sleep(500) ; 删除 (例如在语句前面加上“//”) 后,重新编译运行,结果有变化吗?为什么?
四、实验总结
进程控制是进程管理的基本内容,它用于创建一个进程,终止一个已完成的进程,或去终止一个因出现某事件而无法运行下去的进程,还可负责进程运行中的状态转换。
从程序的运行结果,可以看到线程 (加和减线程) 的交替执行 (因为Sleep() API允许Windows切换线程) 。在每次运行之后,数值应该返回初始值 (0) ,因为在每次运行之后写入线程在等待队列中变成最后一个,内核保证它在其他线程工作时不会再运行
Windows 2000提供的线程间通讯类内核对象允许同一进程或跨进程的线程之间互相发送信息,包括文件、文件映射、邮件位和命名管道等,其中最常用的是文件和文件映射。这类对象允许一个线程很容易地向同一进程或其他进程中的另一线程发送信息。每次运行进程时,都可看到程序中的每个线程从前面的线程中读取数据并将数据增加,文件中的数值连续增加。一定要注意程序中写入之前文件指针的重置。重置文件指针是必要的,因为该指针在读取结束时将处于前四个字节之后,同一指针还要用于向文件写入数据。如果函数向该处写入新数值,则下次进程运行时,只能读到原来的数值。
在以后的学习生活,要勤于思考,多动手,把理论应用于实践,要活学活用知识。培养自己较强的动手实践能力。
实验三 生产者-消费者算法模拟实验
一、实验目的
1、掌握基本的互斥与同步算法,进一步理解“生产者-消费者”模型。
2、通过对“生产者-消费者”问题编程实现,了解线程创建、同步信号量、互斥信号量、临界区的创建和使用,初步了解并发程序设计方法。
3、进一步理解P、V原语和信号量在线程互斥和同步机制中的运用。
二、实验环境
硬件环境:计算机一台,局域网环境;
软件环境:Windows 20## Professional、Linux Redhat 9.0操作系统平台,Visual C++ 6.0专业版或企业版。
三、实验内容和步骤
1、在本次实验开始,以“生产者-消费者”模型为依据,提供了一个多线程“生产者-消费者”实例,有部分源程序代码,要求读者分析已编制的一个“生产者-消费者”实例,并将其缺失的程序代码补充完整,然后调试这段程序,得出最终的结果,并分析结果,得出相应的结论。
尝试改变一些参数,例如:改变缓冲区数、增加(减少)线程数、改变延迟数、增加(减少)生产者进程、增加(减少)消费者进程、改变消费者进程的请求序列等内容,考察这些改变对于运行结果的影响。
2、程序流程图如下图所示:
3、参考部分源程序代码:
#include
#include
#include
#include
#include
//定义一些常量;
//本程序允许的最大临界区数;
#define MAX_BUFFER_NUM 10
//秒到毫秒的乘法因子;
#define INTE_PER_SEC 1000
//本程序允许的生产和消费线程的总数;
#define MAX_THREAD_NUM 64
//定义一个结构,记录在测试文件中指定的每一个线程的参数
struct ThreadInfo
{
int serial; //线程序列号
char entity; //是P还是C
double delay; //线程延迟
int thread_request[MAX_THREAD_NUM]; //线程请求队列
int n_request; //请求个数
};
//全局变量的定义
//临界区对象的声明,用于管理缓冲区的互斥访问;
CRITICAL_SECTION PC_Critical[MAX_BUFFER_NUM];
int Buffer_Critical[MAX_BUFFER_NUM]; //缓冲区声明,用于存放产品;
HANDLE h_Thread[MAX_THREAD_NUM]; //用于存储每个线程句柄的数组;
ThreadInfo Thread_Info[MAX_THREAD_NUM]; //线程信息数组;
HANDLE empty_semaphore; //一个信号量;
HANDLE h_mutex; //一个互斥量;
DWORD n_Thread = 0; //实际的线程的数目;
DWORD n_Buffer_or_Critical; //实际的缓冲区或者临界区的数目;
HANDLE h_Semaphore[MAX_THREAD_NUM]; //生产者允许消费者开始消费的信号量;
//生产消费及辅助函数的声明
void Produce(void *p);
void Consume(void *p);
bool IfInOtherRequest(int);
int FindProducePositon();
int FindBufferPosition(int);
int main(void)
{
//声明所需变量;
DWORD wait_for_all;
ifstream inFile;
//初始化缓冲区;
for(int i=0;i< MAX_BUFFER_NUM;i++)
Buffer_Critical[i] = -1;
//初始化每个线程的请求队列;
for(int j=0;j
for(int k=0;k
Thread_Info[j].thread_request[k] = -1;
Thread_Info[j].n_request = 0;
}
//初始化临界区;
for(i =0;i
InitializeCriticalSection(&PC_Critical[i]);
//打开输入文件,按照规定的格式提取线程等信息;
inFile.open("test1.txt");
//从文件中获得实际的缓冲区的数目;
inFile >> n_Buffer_or_Critical;
inFile.get();
printf("输入文件是:\n");
//回显获得的缓冲区的数目信息;
printf("%d \n",(int) n_Buffer_or_Critical);
//提取每个线程的信息到相应数据结构中;
while(inFile){
inFile >> Thread_Info[n_Thread].serial;
inFile >> Thread_Info[n_Thread].entity;
inFile >> Thread_Info[n_Thread].delay;
char c;
inFile.get(c);
while(c!='\n'&& !inFile.eof()){
inFile>> Thread_Info[n_Thread].thread_request[Thread_Info[n_Thread].n_request++];
inFile.get(c);
}
n_Thread++;
}
//回显获得的线程信息,便于确认正确性;
for(j=0;j<(int) n_Thread;j++){
int Temp_serial = Thread_Info[j].serial;
char Temp_entity = Thread_Info[j].entity;
double Temp_delay = Thread_Info[j].delay;
printf(" \n thread%2d %c %f ",Temp_serial,Temp_entity,Temp_delay);
int Temp_request = Thread_Info[j].n_request;
for(int k=0;k
printf(" %d ", Thread_Info[j].thread_request[k]);
cout<
}
printf("\n\n");
//创建在模拟过程中几个必要的信号量
empty_semaphore= CreateSemaphore (NULL,n_Buffer_or_Critical,n_Buffer_or_Critical,
"semaphore_for_empty");
h_mutex =CreateMutex (NULL,FALSE,"mutex_for_update");
//下面这个循环用线程的ID号来为相应生产线程的产品读写时所
//使用的同步信号量命名;
for(j=0;j<(int)n_Thread;j++){
std::string lp ="semaphore_for_produce_";
int temp =j;
while(temp){
char c = (char)(temp%10);
lp+=c;
temp/=10;
}
h_Semaphore[j+1]= CreateSemaphore (NULL,0,n_Thread,lp.c_str());
}
//创建生产者和消费者线程;
for(i =0;i< (int) n_Thread;i++){
if(Thread_Info[i].entity =='P')
h_Thread[i]= CreateThread (NULL,0,(LPTHREAD_START_ROUTINE)(Produce),
&(Thread_Info[i]),0,NULL);
else
h_Thread[i]=CreateThread (NULL,0,(LPTHREAD_START_ROUTINE)(Consume),
&(Thread_Info[i]),0,NULL);
}
//主程序等待各个线程的动作结束;
wait_for_all = WaitForMultipleObjects(n_Thread,h_Thread,TRUE,-1);
printf(" \n \n全部生产者和消费者都已完成它们的工作. \n");
printf("按任意键返回!\n");
_getch();
return 0;
}
//确认是否还有对同一产品的消费请求未执行;
bool IfInOtherRequest(int req)
{
for(int i=0;i
for(int j=0;j
if(Thread_Info[i].thread_request[j] == req)
return TRUE;
return FALSE;
}
//找出当前可以进行产品生产的空缓冲区位置;
int FindProducePosition()
{
int EmptyPosition;
for (int i =0;i
if(Buffer_Critical[i] == -1){
EmptyPosition = i;
//用下面这个特殊值表示本缓冲区正处于被写状态;
Buffer_Critical[i] = -2;
break;
}
return EmptyPosition;
}
//找出当前所需生产者生产的产品的位置;
int FindBufferPosition(int ProPos)
{
int TempPos;
for (int i =0 ;i
if(Buffer_Critical[i]==ProPos){
TempPos = i;
break;
}
return TempPos;
}
//生产者进程
void Produce(void *p)
{
//局部变量声明;
DWORD wait_for_semaphore,wait_for_mutex,m_delay;
int m_serial;
//获得本线程的信息;
m_serial = ((ThreadInfo*)(p))->serial;
m_delay = (DWORD)(((ThreadInfo*)(p))->delay *INTE_PER_SEC);
Sleep(m_delay);
//开始请求生产
printf("生产者 %2d 发送生产请求信号.\n",m_serial);
//确认有空缓冲区可供生产,同时将空位置数empty减1;用于生产者和消费者的同步;
wait_for_semaphore = WaitForSingleObject(empty_semaphore,-1);
//互斥访问下一个可用于生产的空临界区,实现写写互斥;
wait_for_mutex = WaitForSingleObject(h_mutex,-1);
int ProducePos = FindProducePosition();
ReleaseMutex (h_mutex);
//生产者在获得自己的空位置并做上标记后,以下的写操作在生产者之间可以并发执行;
//核心生产步骤中,程序将生产者的ID作为产品编号放入,方便消费者识别;
printf("生产者 %2d 开始在缓冲区 %2d 生产产品.\n",m_serial,ProducePos);
Buffer_Critical[ProducePos] = m_serial;
printf("生产者 %2d 完成生产过程 :\n ",m_serial);
printf(" 缓冲区[ %2d ]:%3d \n" ,ProducePos,Buffer_Critical[ProducePos]);
//使生产者写的缓冲区可以被多个消费者使用,实现读写同步;
ReleaseSemaphore (h_Semaphore[m_serial],n_Thread,NULL);
}
//消费者进程
void Consume(void * p)
{
//局部变量声明;
DWORD wait_for_semaphore,m_delay;
int m_serial,m_requestNum; //消费者线程的序列号和请求的数目;
int m_thread_request[MAX_THREAD_NUM];//本消费者线程的请求队列;
//提取本线程的信息到本地;
m_serial = ((ThreadInfo*)(p))->serial;
m_delay = (DWORD)(((ThreadInfo*)(p))->delay *INTE_PER_SEC);
m_requestNum = ((ThreadInfo *)(p))->n_request;
for (int i = 0;i
m_thread_request[i] = ((ThreadInfo*)(p))->thread_request[i];
Sleep(m_delay);
//循环进行所需产品的消费
for(i =0;i
//请求消费下一个产品
printf("消费者 %2d 请求消费 %2d 产品\n",m_serial,m_thread_request[i]);
//如果对应生产者没有生产,则等待;如果生产了,允许的消费者数目-1;实现了读写同步;
wait_for_semaphore=WaitForSingleObject(h_Semaphore[m_thread_request[i]],-1);
//查询所需产品放到缓冲区的号
int BufferPos=FindBufferPosition(m_thread_request[i]);
//开始进行具体缓冲区的消费处理,读和读在该缓冲区上仍然是互斥的;
//进入临界区后执行消费动作;并在完成此次请求后,通知另外的消费者本处请求已
//经满足;同时如果对应的产品使用完毕,就做相应处理;并给出相应动作的界面提
//示;该相应处理指将相应缓冲区清空,并增加代表空缓冲区的信号量;
EnterCriticalSection (&PC_Critical[BufferPos]);
printf("消费者 %2d 开始消费 %2d 产品 \n",m_serial,m_thread_request[i]);
((ThreadInfo*)(p))->thread_request[i] =-1;
if(!IfInOtherRequest(m_thread_request[i])){
Buffer_Critical[BufferPos] = -1;//标记缓冲区为空;
printf("消费者 %2d 成功消费 %2d:\n ",m_serial,m_thread_request[i]);
printf(" 缓冲区[ %2d ]:%3d \n" ,BufferPos,Buffer_Critical[BufferPos]);
ReleaseSemaphore (empty_semaphore,1,NULL);
}
else{
printf("消费者 %2d 成功消费产品 %2d\n ",m_serial,m_thread_request[i]);
}
//离开临界区
LeaveCriticalSection (&PC_Critical[BufferPos]);
}
}
1、 请补全上述程序,写出程序的运行结果。
2、 分析程序的运行结果。
四、实验总结
通过本次实验,要明白进程的概念的理解,明确进程与程序的区别,深入认识程序并发执行的实质,理解进程竞争资源的现象,互斥与同步的基本概念,进程的并发控制。
实验四 页式存储管理部分功能模拟功能
1、源代码:
#include "stdio.h"
#define n 64 //实验中假定的页表长度,页表的长度实际上是由系统按照作业的长度决定的
#define length 10
struct
{ int lnumber; //页号
int flag; //表示该页是否在主存,“1”表示在主存;“0”表示不在主存
int pnumber; //表示该页所在主存块的块号
int write; //表示该页是否被修改过,“1”表示修改过;“0”表示没有修改过
int dnumber; //表示该页存放在磁盘上的位置,即磁盘块号
}page[n]; //页表定义
int m;
int page_length;
int p[length];
int head;
void page_interrupt(int lnumber)
{ int j;
printf("发生缺页中断%d\n",lnumber);
1、j=p[head];
2、p[head]=lnumber;
3、head=(head+1)%m;
4、if(page[j].write==1)
printf("将页%d写回磁盘第%d块\n",j,page[j].dnumber);
5、page[j].flag=0;
6、page[lnumber].flag=1;
7、page[lnumber].write=0;
8、page[lnumber].pnumber=page[j].pnumber;
printf("淘汰主存块%2d中的页%2d,从磁盘第%d块中调入页%2d\n",
9、page[j].pnumber10、j 11、page[lnumber].dnumber12、lnumber);
);
}
void command(unsigned laddress,int write)
{ unsigned paddress, ad,pnumber,lnumber;
kk:
13、lnumber=laddress>>10;
14、ad=laddress&0x3FF;
if(lnumber>=page_length)
{
printf("不存在该页\n");
15、page_interrupt(lnumber);
}
if(page[lnumber].flag==1)
{
16、pnumber=page[lnumber].pnumber;
17、paddress=pnumber<<10|ad;
//物理地址
printf("逻辑地址是:%x 对应物理地址是:%x\n",laddress,paddress);
if(write==1)
page[lnumber].write=1;
}
else
{
18、page_interrupt(lnumber);
goto kk;
}
}
main()
{ int lnumber,flag,pnumber,write,dnumber;
unsigned laddress;
int i;
printf("输入页表的信息,创建页表(若页号为-1,则结束输入)\n");
printf("输入页号和辅存地址:");
scanf("%d%d",&lnumber,&dnumber);
i=0;
while(lnumber!=-1)
{19、page[lnumber].lnumber=lnumber;
20、page[lnumber].dnumber=dnumber;
21、i++;
printf("输入页号和辅存地址:");
scanf("%d%d",&lnumber,&dnumber);
}
page_length=i;
printf("输入主存块号,主存块数要小于%d,(以-1结束):",i);
scanf("%d",&pnumber);
m=0;
head=0;
while(pnumber!=-1)
{ if(m<=i)
{
24、p[m]=m;
25、page[m].pnumber=pnumber;
26、page[m].flag=1;
27、m++;
}
Scanf7d",&pnumber);
}
printf("输入指令性质(1-修改,0-不需要,其他-结束程序运行)和逻辑地址:");
scanf("%d%x",&write,&laddress);
while(write==0||write==1)
{
28、command(laddress,write);
printf("输入指令性质(1-修改,0-不需要,其他-结束程序运行)和逻辑地址:");
scanf("%d%x",&write,&laddress);
}
}
5、程序运行结果及简要分析
首先,通过键盘依次输入了4个主存块,块号为4,6,8,3,同时,将第0,1,2,3个页面依次调入块4,6,8,3中,将这4个页面的页号相应地存入数组p[0],p[1],p[2],p[3]中,然后再通过键盘依次输入逻辑地址:23,456,abc,2000,然后分离出逻辑地址高六位的页号和低10位的页内地址,由于前3个逻辑地址对应的页号在主存中,所以将这3个页号对应的主存块号和页内地址合成为物理地址;对于最后1个逻辑地址,由于对应的页号不在主存中,所以要进行缺页处理,采用FIFO算法。
实验总结:
通过本次实验让我明白了许多,做事要认真仔细,页式存储管理怎么去执行,掌握请调策略,放置策略,淘汰策略,怎样进行页式地址转换,选用什么置换算法最好。
实验五 设备管理模拟实验
一、实验目的
(1) 了解Windows操作系统中设备驱动程序的组成
(2) 理解Windows操作系统的设备管理机制
(3) 掌握Windows操作系统关于设备管理的API函数的使用方法
(4) 了解并掌握Windows中鼠标、键盘等简单设备的检测方法
二、实验内容
1、源程序:
#include
#include
#include
#include
void menu();
void quit();
void error();
void CheckKeyboard();
void PlayMusic();
void main()
{
char UserSelect;
DEVMODE DevM;
int iSavecaretBlinkTime=350;
do
{
menu();
UserSelect=getch();
putchar(UserSelect);
switch(UserSelect)
{
case '1':
SystemParametersInfo(SPI_SETMOUSEBUTTONSWAP,1,0,0); //鼠标改为右手习惯
break;
case '2':
SystemParametersInfo(SPI_SETMOUSEBUTTONSWAP,0,0,0); //鼠标改为左手习惯
break;
case '3':
CheckKeyboard();
break;
case '4':
PlayMusic();
break;
case '0':
quit();
break;
default:
error();
break;
}
} while (UserSelect!='0');
}
void menu()
{
printf("\n\n\t1----------------鼠标改为右手习惯\n");
printf("\t2----------------鼠标改为左手习惯\n");
printf("\t3----------------获得键盘类型和功能键个数\n");
printf("\t4----------------喇叭播放音乐\n");
printf("\t0----------------退出\n");
printf("\n\n\t请输入选项: ");
}
void error()
{
printf("\n\n\t输入出错");
}
void quit()
{
printf("\n\n\t退出\n\n");
}
void CheckKeyboard()
{
char sKeyType[256];
int iFunctionKeynum;
switch(GetKeyboardType(0))
{
case 1:
strcpy(sKeyType,"\nIBM PC/XT或兼容(83键)键盘\n");
break;
case 2:
strcpy(sKeyType,"\nOlivertti\'ICO\'(102键)键盘\n");
break;
case 3:
strcpy(sKeyType,"\nIBM PC/XT或兼容(84键)或类似键盘\n");
break;
case 4:
strcpy(sKeyType,"\nIBM 增强型(101或102键)键盘\n");
break;
case 5:
strcpy(sKeyType,"\nNokia 1050或类似键盘\n");
break;
case 6:
strcpy(sKeyType,"\nNokia 9140或类似键盘\n");
break;
case 7:
strcpy(sKeyType,"\nJapanese键盘\n");
break;
default:
strcpy(sKeyType,"\n目前无法确定\n");
break;
}
printf("%s",sKeyType);
iFunctionKeynum=GetKeyboardType(2);
printf("\n功能键个数 %d\n",iFunctionKeynum);
}
void PlayMusic()
{
int iFreq[24]={784,659,523,784,659,523,880,698,
587,880,698,578,1568,1318,1046,1568,1318,1046,1760,1396,1174,1706,1396,1174};
int i,j;
for(i=0;i<2;i++)
{
for(j=0;j<24;j++)
{
Beep(iFreq[j],200);
}
}
}
运行结果:
依次按下相应的数字,按下1和2时,点击鼠标左键,显示不同的内容(数字1时,点击鼠标左键,显示的是右键的功能,数字2时,鼠标左键恢复原来的功能),按下数字3时,得到键盘类型信息和功能键个数,按下4,5,6时,光标的闪烁频率发生改变。按下数字7时,发出声音。
实验总结:
了解了设备管理的机制。设备管理对计算机操作起着不可替代的作用。尽管对不同的系统设备管理工具是不同的,但用户可以通过相应的工具管理系统设备。
实验六 文件系统
一、实验目的
通过本实验,要求学生熟练掌握Linux各种文件操作命令,包括:使用控制字符执行特殊功能;使用file和strings命令确定文件类型;使用cat利more命令显示文本文件的内容;使用head和tail命令显示文本文件的部分内容;使用wc命令确定单词、行和字符数;使用diff命令比较2个文件;回顾文件和目录命名约定;使用touch命令创建新文件;使用mkdir命令创建新目录;使用rm命令删除文件;使用rm -r命令删除目录。
二、实验环境
硬件环境:计算机一台,局域网环境;
软件环境: Linux Redhat 9.0操作系统平台。
三、实验内容和步骤
1. 文件信息命令
步骤1:开机,登录进入GNOME。
在GNOME登录框中填写指导老师分配的用户名和口令并登录。
步骤2:访问命令行。
单击红帽子,在“GNOME帮助”菜单中单击“系统工具”-“终端”命令,打开终端窗口。
步骤3:使用控制字符执行特殊功能。
控制字符用来执行特殊的作业,如中止和启动屏幕输出。
大多数PC键盘有两个控制键。它们通常标记为Ctr1,可以在键盘的左右下角找到。为了输入一个控制字符序列,可按住Ctrl键不放,然后按下键盘上相应的字符。
Ctrl + C:中断当前活动。当你在csh中键入一个不可识别的命令行 (例如,$ls “) ,收到第2个提示符的时候,Ctrl + C也可以用于恢复shell提示符。
Ctrl + Z:终止当前活动。显示ls命令的手册页面 (man ls) ,然后使用Ctrl -z中止输出。
当你接下Ctrl + Z的时候,发生了什么事情?
中止当前活动,返回终端窗口
Ctrl + D:表示文件的末尾或者退出。 Ctrl + D用于退出一些Linux工具程序 (bc、write等) ,退出一个终端窗口,注销一个终端会话或者命令行登录会话。作为一个一般的规则,当您出现“死机”时,或者如果Ctrl + C不起作用,可试试Ctrl + D。例如:
1) 在shell提示符下键入bc,启动基本的计算器工具程序。
2) 把两个数字相乘 (键入:458*594,然后按回车键) 。
3) 按Ctrl + D退出计算器。
当使用计算器的时候,你的提示符是什么?
This is free software with ABSOLUTELY NO WARRANTY.
Ctrl + U :擦除整个命令行。Ctrl + U最常用在:
· 一个擦除决定不执行的命令行的快捷方式。
· 如果登录到一个远程系统,退格键不起作用。
· 它可以确保在登录的时候,从一个“空”的用户帐号和口令输入开始。
· 因为在口令输入的时候看不见它们,当知道自己键入了错误字符的时候,使用Ctrl + U擦除密码,重新开始输入。
如果输入一个命令,如ls –R/,有时候,会在按下回车键之前想擦除命令行。输入一个命令,在接下回车键执行命令之前按下Ctrl + U。结果是什么?
输入的命令被擦除
步骤4:使用file命令确定文件类型。
在Linux系统中可以找到许多类型的文件。文件类型可以通过使用file命令来确定。当一个用户试图打开或阅读一个文件的时候,这个信息很重要。确定文件类型可以帮助一个用户决定使用哪个程序或命令来打开这个文件。这个命令的输出最常见的是如下几种:文本文件、可执行文件或数据文件。
1) 文本文件:包括ASCII或英语文本、命令文本和可执行的shell脚本。这种类型的文件可以使用cat或more命令读取,可以使用vi或其他文本编辑器编辑。
单击红帽子,在“GNOME帮助”菜单中单击“辅助设施”-“Text Editor”命令,在文本编辑中键入适当内容并保存为test文件。
使用file命令来确定test文件的文件类型。它是哪种类型的文件?
ASCLL test
2) 可执行 (或二进制) 文件:包括32位的可执行文件和可扩展链接格式(ELF) 编码文件,和其他动态链接的可执行文件。这种文件类型表示这个文件是一个命令或程序。
单击红帽子,在“GNOME帮助”菜单中单击“办公”-“OpenOffice.org Writer”命令,建立一个文档如ww.sxw。
使用file命令确定你所建立的文件类型。它是哪种类型的文件?(注意文件名部分必须包括扩展名,如file ww.sxw 。)
可执行文件
3) 数据文件:数据文件是由系统中运行的应用创建的。在某些情况下,文件的类型是会说明的。例如,FrameMaker (桌面印刷软件) 文档。
使用file命令确定dir1/coffees子目录中beans文件的文件类型。它是什么文件类型的?
步骤5:使用strings命令。
strings命令可以用于打印可执行文件或者二进制文件中的可读字符。
一些有编程背景的人,可以解释strings产生的输出。这个命令在这里只是作为一个展示可执行文件中可打印字符的方法来介绍。strings命令必须用于读取可执行文件,如 /usr/bin/cat。在大多数情况下,strings命令也可以给出命令的使用语法。
使用strings命令查看 /usr/bin/cal文件的可读字符。列出strings命令中的一些输出。
/lib/ld-linux.so.2 libc.so.6 putshar strerror _puts wcstombl nbstowcs
Verrx strrchr wcscpy dcgettext
步骤6:使用cat命令显示文件的内容。
cat命令在屏幕上显示一个文本文件的内容。它常用于显示如脚本文件 (类似批处理文件) 这样的短文本文件。如果文件超过一屏的话,必须使用一个屏幕可以滚动的窗口,如GNOME环境中的终端窗口。
键入 ls /dev > dev1
使用cat命令显示主目录中dev1文件的内容。文本的显示出现了什么情况?
显示出一长串字符内容,必须按滚动条才能看到全部的内容
步骤7:使用more命令显示文件的内容。
more命令是一个用于显示文本文件首选的方法,因为它会自动的一次显示一屏文件内容。如果文件的信息比一屏更长,屏幕的底部显示如下的信息:--More-- (n%) (文件的n%已经显示) 。按下回车键,继续一次显示一行信息。空格键将继续一次显示一屏内容。
使用more命令显示主目录中dev1文件的内容。文本的显示出现了什么情况?
首先显示出第一页的内容,按一次回车键则显示下一行的内容 ,按空格键则显示下一页内容
步骤8:使用head命令显示文件的一部分。
head命令用于显示一个或多个文本文件的前n行。在默认情况下,如果没有给出 -n选项,将显示前10行。当您只想查看文件的开始的几行,而不管文件的大小的时候,head命令是很有用的。
1) 单独使用head命令,显示主目录中dev1文件的开始部分。显示了多少行?
显示了10行
2) 使用带 -n选项的head命令,显示主目录中dante文件的前20行。您输入什么命令?
head -20 dante
步骤9:使用tail命令显示文件的一部分。
使用tail命令,显示文件的最后几行。在默认情况下,如果没有指定 -n选项,将显示最后10行。当检查大型日志文件最近输入内容的时候,tail命令是很有用的。备份工具程序常把备份哪个文件和什么时候做的备份,写到日志文件中去。一个备份日志文件中最后的输入通常是备份文件的总数和备份是否成功完成的信息。-n选项显示了文件的最后n行。
单独使用tail命令,显示主目录中dante文件的末端。显示了多少行?
显示了10行
步骤10:通过使用wc命令,确定行数、单词数和字符数。
wc (单词计数) 命令可以用于显示文本文件的行数、单词数、字节数或者字符数。当确定文件特征或者当比较两个文件的时候,这个命令是很有用的。使用不带选项的wc将给出文件的行数、字节数。使用带一个选项的wc,可以确定想查看的哪一项内容。
使用wc命令确定主目录中dev1文件的行数、单词数和字符数。有多少行、多少个单词和多少个字符?
7520行 7520个词 47288个字符
步骤11:使用wc计算目录条目的数目。
使用wc和ls命令确定主目录中条目 (文件和目录) 的数目。为此,必须把ls命令的输出导入到wc命令中。
更多符号是竖线,和后斜线 (\) 在同一个键上。在命令行提示行下,输入命令ls | wc -w。有多少个文件和目录名 (单词) ?
125
步骤12:使用diff命令确定文件之间的不同之处。
diff (不同) 命令用于比较2个文本文件,找出在它们之间的不同之处。wc命令可以比较文件,因为它计算行数、单词数和字符数。有可能2个文件有相同的行数、单词数和字符数,但是字符和单词不同。diff命令可以从实际上找出文件之间的不同。
这个命令的输出把2个文本文件之间的不同一行一行的显示出来。diff命令有2个选项:-i 和 -c。-i选项忽略字母的大小写,例如A和a相等。-c选项执行细致的比较。
单击红帽子,在“GNOME帮助”菜单中单击“辅助设施”-“Text Editor”命令,创建两个文件fruit1和fruit2,并键入适当内容。
使用diff命令执行细节比较,确定fruit1文件和fruit2文件之间的区别。
在fruit1文件和在fruit2文件中,哪几行是不同的?
在fruit1中的内容是123456789apple
在fruit2中的内容是abcdefghiapple
1) cat命令:
· 对fruit1文件使用cat命令。
· 对fruit2文件使用cat命令。
· 键入命令行cat fruit1 fruit2 > filex。
· 对filex文件使用cat命令。上面的命令行做了什么工作?
将fruit1和fruit2的内容各并在一起,成为filex的内容
2) 可以使用哪2个命令来确定2个文件是否相同?
diff fruit1 fruit2和cat fruit1,cat fruit2
步骤13:关闭终端窗口,注销。
2. 基本的命令行文件管理
步骤14:回顾Linux的文件和目录命名规则。
在本实验中,我们将创建文件和目录,因此,在开始之前,先来回顾一下Linux文件和目录的命名规则和指导方针。
1) 最大长度:组成文件和目录名最大长度为255个数字字母字符。一般来说,应该尽可能的保持文件名短但是仍然有意义。
2) 非数字字母字符:一些非数字字母字符或者元字符是可用的:下划线 (_) 、连字符号 (-) 和句点 (.) 。这些元字符可以在文件或目录名中使用多次(Feb.Reports.Sales是一个有效的文件或目录名) 。尽管shell允许把星号 (*) 、问号(?) 和发音符号 (~) 、方话号 ([ ]) 、&、管道 [ | ] 、引号 (“”) 和美元符号 ($) 在文件名中使用,但这不是推荐的,因为这些字符对于shell有特殊的意义。分号 (;) 、小于号 (<) 和大于号 (>) 是不允许作为文件名的。
3) 文件名扩展:文件名可以包含一个或多个扩展名。扩展名常被一个应用追加到文件的末端。扩展名通常是1个到3个字符,追加到文件名的末端,之前有一个句点 (.) 。当命名文件的时候,您可以选择使用这个规则。
4) 目录名的扩展名:目录名一般不包含扩展名,但是也没有规则反对这一点。
5) 大小写敏感:Linux文件和目录名是大小写敏感的。Project1和projectl不是同一个文件。在一个目录中,不能够有两个文件有着同样的名字。一般规则都是使用小写字母。
检查表7-1中的文件名,指出它们是否是有效或者推荐的Linux文件或目录名,为什么是或为什么不是。
表7-1 实验记录
步骤15:使用touch命令创建文件。
每次创建一个新的字处理文档或者电子数据表,就是正在创建一个新文件,应该符合之前提到的文件命名规则。也必须拥有创建文件的目录的足够权限。
使用touch命令,可以同时创建一个或多个文件。一些应用要求在写文件之前,文件必须存在。touch命令对于快速创建需要处理的文件很有用。也可以使用touch命令更新文件被访问的时间和日期,使文件可以再次被备份。当创建文件或目录的时候,可以指定绝对和相对的路径名。
命令格式:
touch filename (s)
1) 在主目录中使用touch命令创建一个名为newfile的文件,应该使用什么命令?
touch newfile
2) 使用touch命令在这个目录中创建另一个叫做filenew的新文件,应该使用什么命令?
touch filenew
3) 输入命令显示practice目录中的文件的长列表。创建的文件列出来了吗?
列出来了
4) 谁是文件的所有者?
student
5) 和文件关连的组是什么?
student
6) 创建的日期和时间是什么?
20##-12-08 9:05:49
7) 文件的大小是多少?
5.0 kb
8) 使用file命令确定newfile的文件类型。它是哪一类的文件?
empty
9) 使用touch命令同时创建3个文件:new1、new2和new3,应该使用什么命令?
cd practice
Touch new1 new2 new3
10) 输入命令显示practice目录中文件的长列表。创建的3个新文件列出来了吗?
列出来了
步骤16:使用mkdir命令创建新目录。
mkdir (创建目录) 命令用于创建目录或文件夹。目录可以包含其他目录,称为子目录,它们可以包含文件。
目录可以使用或者绝对路径名或者相对路径名创建。可以在同一行中指定多个目录名,创建多个新目录。必须有创建目录的足够权限。
mkdir directory_name (s)
1) 从主目录中,使用相对路径名改变到practice目录中。使用什么命令?
cd/practice
2) 使用mkdir命令,在这个目录中创建一个叫做newdir的子目录。使用什么命令?
mkdir/newdir
3) 输入命令,显示practice目录中文件和目录的长列表。创建的目录列出来了吗?
列出来了
4) 目录的所有者是?
root
5) 文件的大小是多少?
4096
6) 使用file命令确定newdir文件的类型。它是哪一类的文件?
目录文件directory
7) 如果名字中没有字符dir,采取别的什么方法来识别出它是一个目录?
颜色识别,目录颜色是蓝色的
8) mkdir命令创建3个目录,目录名分别为high、medium和low,应该使用什么命令?
mkdir/high mkdir/medium mkdir/low
9) 用ls命令检查创建是否成功?
成功
步骤17:使用rm命令删除文件。
rm目录可以删除单个文件或多个文件。可以通过在rm命令之后指定文件的名字,或者使用星号 (*) 和问号 (?) 元字符,同时删除几个文件。在Linux系统中删除的文件是永远被删除了,除非使用图形界面删除文件,它们才能够被恢复。rm命令可以带 –i (交互) 选项使用,它在删除文件之前会提示用户。使用rm -i命令作为防范,避免误删文件:
rm [-i] filename (s)
1) 使用rm命令删除早先在practice目录中创建的newfile文件,应该使用什么命令?
rm –r practice/newfile
2) 输入命令显示practice目录中文件的长列表。创建的文件还在吗?
不在
3) 使用带 -i选项的rm命令,删除早先在practice目录中创建的filenew文件。交互式选项起到什么作用?
在删除之前提示用户,避免误删文件
4) 删除早先创建的三个名为new1、new2和new3的文件。使用问号 (?) 通配符使用一个命令删除所有三个文件。使用什么命令?
rm new?
5) 输入命令,显示practice目录中文件的长列表。三个文件还在吗?
不在
6) 还有其他的什么方法来删除new1、new2和new3文件?
rm new[1-3]
步骤18:使用rm -r命令删除目录。
rm -r目录用于删除目录。它将删除从目标目录开始的目录,包括所有的子目录和文件。当rm命令带 -r信息使用的时候,它可以删除单个目录 (空或不空) 或目录树的整节。rm命令可以带 -i选项使用,它在删除目录之前会提醒用户:
rm –r [i] directory_name (s)
1) 删除早先创建的newdir子目录,使用什么命令?
rm –ri practice/newdir
2) 输入命令显示practice目录中文件的长列表,创建的子目录还在吗?
不在
3) 改变到早先创建的mediurn子目录中,输入什么命令?
cd practice/mediurn
4) 删除早先创建的low子目录,使用什么命令?
rm –ri practice/low
5) 用相对路径名和快捷方式,改变回到practice子目录中,应使用什么命令?
cd..
6) 使用一个命令删除high和medium子目录,应使用什么命令?
rm –ri hingh medium
步骤19:练习所学习到的内容。
通过在practice目录中创建一个三级的目录树,练习使用touch、mkdir和rm命令。试着使用有意义的目录名。记住可以使用一个命令创建整个目录结构。在每个目录中创建多个文件。记住可以使用一个命令创建多个文件。
结束的时候,请删除实验时创建的文件和目录。
步骤20:关闭终端窗口,注销
一、实验总结
通过本实验真正使用到了Linux系统,对linux系统有个大概的了解,知道了linux系统的大体结构和使用方法。知道了各个组合键的意义。能在没有鼠标的情况下进行对文件的操作。操作起来每次键入均需要重新输入指令,虽然繁复但简洁明确不易出现冲突,较windows更安全,鉴于自身的结构模式又具有不易感染不安全代码等优势。
在Linux操作系统中使用命令来分析和管理文件目录是非常重要的。Ls可以查看指定目录下的文件和目录,mkdir可以创建子目录,rmdir可以删除子目录,pwd可以查看目前目录位置,比用ls简单,cd即使更改当前目录,相当于“电梯”,tree在我们操作的系统中不可显示,cp可以创建文件等。此外在复制操作时经常出现类似于乱码的现象,经过讨论和询问,了解到,现象的产生是基于该系统不可显示中文导致。也了解到有些时候出现乱码即为操作有误。
通过这次实验,我对Linux操作系统有了更深刻的认识,同时也会简单使用Linux和对它的基本功能有了一定程度的了解与运用。还知道了Linux操作系统中各种常见命令,正是这些命令,使Linux系统的操作变得简单易学。