1、操作系统实验 创建进程,学会通过基本的Windows进程控制函数,由父进程创建子进程。,实验目的,我们设计一个应用程序时,有时候需要另外一段代码做一些其它工作。 方法如下: 一、函数调用或者子程序 弊端:我们可以用函数调用或者子程序,但是我们调用函数之后,只能等函数返回; 二、在进程内创建一个新线程 弊端:但是如果在一个复杂应用系统中,我们有可能不慎改写了进程地址空间中的某些数据(例如某些引用数据),导致其它工作不能正确地进行。,三、创建新进程(子进程) 所以当我们需要某些工作同时进行,并且希望保护它们的运行地址空间时,一个很好地办法是创建一个新进程来执行需要同时进行的工作。,假设现在有这样的
2、一个工作,需要计算1100的和,还需要做一个工作是读写文件。我们可以让父进程计算,创建一个子进程实现读写文件。主要工作: 1、首先由父进程创建子进程 2、让子进程创建一个文件并写入数据,子进程写文件过程中,父进程继续执行计算工作 3、等子进程执行完以后,父进程读取文件内容输出,实现进程协同工作。,实验内容,父进程框架 void main() /为创建进程做准备工作 /创建子进程 If(创建失败) 返回 Else(创建成功) /执行计算1100的和 /等子进程执行完,读取子进程的文件内容,并输出。 ,程序框架,子进程框架 void main() /创建文件 If(失败) 返回 Else(成功)
3、/向文件写入数据 /读取文件内容输出 ,BOOL CreateProcess( LPCTSTR lpApplicationName , /指定可执行程序名 LPTSTR lpCommandLine , /命令行字符串,可以为NULL LPSECURITY_ATTRIBUTES lpProcessAttributes , /新进程对象的安全属性 LPSECURITY_ATTRIBUTES lpThreadAttributes , /新进程对应线程的安全属性 BOOL bInheritHandles ,/指定父进程的对象句柄是否能被子进程继承 DWORD dwCreationFlags , /指定
4、创建进程的附加标记,即指定新创建进程的特性 LPVOID lpEnvironment ,/指定新进程使用的环境,NULL表示同父进程环境 LPCTSTR lpCurrentDirectory , /指定子进程当前路径,NULL表示与父进程路径相同 LPSTARTUPINFO lpStartupInfo , /指定新进程主窗口如何显示 LPPROCESS_INFORMATION lpProcessInformation /作为返回值使用,是一个指针 );,父进程创建子进程 用CreateProcess函数来创建一个新进程,当调用CreateProcess时,系统所做的工作:1、首先,系统创建一个
5、进程内核对象,其初始使用计数为1。(进程内核对象并不代表进程本身,而是操作系统用来管理这个进程的一个数据结构。),2、然后,系统为新进程创建一个虚拟地址空间,并将可执行文件(和所有必要的DLL)的代码及数据加载到进程的地址空间。3、最后,系统为新进程创建一个主线程内核对象(使用计数为1)(线程内核对象是操作系统用来管理线程的数据结构。),系统做完这些工作以后,新进程的主线程就开始执行运行时的启动代码,在启动代码中会调用应用程序的main函数,这样,新进程就从main函数开始执行。如果调用CreateProcess函数后,系统成功创建了新进程并且创建了其主线程,则系统返回TRUE,否则返回FAL
6、SE。,讨论CreateProcess的参数。 LPCTSTR lpApplicationName lpApplicationName参数指定新进程要使用的可执行文件的名称。可以是完整路径和文件名,也可以是部分名称。注意一定要加上扩展名“.exe”。在本次实验中,使用全路径和文件名。CreateProcess(“G:Projects操作系统编程实验创建进程ChildDebugChild.exe“,NULL,)(该参数可以为NULL,这时文件名必须是参数lpCommandLine 指向的字符串中第一个空格界定的标记。),LPTSTR lpCommandLine lpCommandLine参数用来
7、指定传递给新进程的命令行字符串。在本次实验中,不需要这个参数,可以设为NULL。(在很多时候,我们将可执行文件名和命令行参数都传给lpCommandLine参数,CreateProcess函数分析lpCommandLine参数时,会把该字符串中第一个空格分隔的标记作为可执行文件名,如果是可执行文件名是部分路径,则函数会在系统目录中按从下到上的顺序搜索可执行文件。),LPSECURITY_ATTRIBUTES lpProcessAttributes 和 LPSECURITY_ATTRIBUTES lpThreadAttributes lpProcessAttributes和lpThreadAtt
8、ributes 都是指向指向LPSECURITY_ATTRIBUTES 结构体的指针。前面介绍过,当调用CreateProcess函数创建新进程时,系统将为新进程创建一个进程内核对象和一个主线程内核对象。lpProcessAttributes和lpThreadAttributes 参数就是分别用来设置新进程内核对象和主线程内核对象的安全属性。在本次实验中为这两参数传递NULL,让系统为这两个对象赋予默认的安全描述符。 CreateProcess(“.exe”,NULL,NULL,NULL),BOOL bInheritHandlesbInheritHandles用来指定父进程随后创建的子进程是否
9、能够继承父进程的对象句柄。如果该参数为TRUE,那么父进程的每个可继承打开句柄都能被子进程继承。在本次实验中,把这个参数设置为FALSE,因为子进程不需要继承父进程的可继承句柄。CreateProcess(“.exe”,NULL,NULL,NULL,FALSE,),DWORD dwCreationFlags dwCreationFlags指定进程创建的附加标记,也可以用于控制新进程的优先级。 如果只为了启动子进程,不需要设置创建标记,则直接设置为0.如果不需要为应用程序创建控制台窗口,则可以设置该参数为CREATE_NO_WINDOW.如果需要创建新控制台窗口,而不是继承父进程的控制台窗口,则
10、设置为CREATE_NEW_CONSOLE.本次实验中设置为该标记。 CreateProcess(“.exe”, CREATE_NEW_CONSOLE, )该参数可以取得创建标记很多,也可以用于设置新进程的优先级。更多的设置可以参看MSDN。,LPVOID lpEnvironment lpEnvironment是一个指向环境块的指针,如果此参数是NULL,那么新进程使用调用进程的环境。通常都为此参数传递NULL。LPTSTR lpCurrentDirectory lpCurrentDirectory参数是一个指向空终止的字符串,用来指定子进程当前的路径,这个字符串必须是一个完整的路径名,包括驱
11、动器的标识符,如果此参数为NULL,那么新的子进程将于调用进程(父进程)用有相同的驱动器和目录。CreateProcess(“.exe”, CREATE_NO_WINDOW,NULL,NULL,),LPSTARTUPINFO lpStartupInfo lpStartupInfo是一个指向STARTUPINFO结构体的指针,用来指定新进程的主窗口将如何显示。 typedef struct _STARTUPINFO DWORD cb; LPTSTR lpReserved; LPTSTR lpDesktop; LPTSTR lpTitle; HANDLE hStdInput; HANDLE hSt
12、dOutput; HANDLE hStdError; STARTUPINFO, *LPSTARTUPINFO;,STARTUPINFO结构体成员比较多,并不需要为其所有成员都赋值。其中cb表示该结构体本身的大小,以字节为单位,通常都要为其cb成员赋值,否则函数调用会失败。在创建进程之前的准备工作就包括给该结构体变量赋值。在本次实验中,不需要设置其它启动信息,所以直接给cb参数赋值就可以。 STARTUPINFO sui; ZeroMemory(,LPPROCESS_INFORMATION lpProcessInformationlpProcessInformation参数作为返回值使用,是一个
13、指向PROCESS_INFORMATION结构体的指针,用来接收关于新进程的标志信息。PROCESS_INFORMATION结构体定义如下所示:typedef struct _PROCESS_INFORMATION HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; PROCESS_INFORMATION;,hProcess和hThread分别是标识新创建的进程句柄和新创建进程的主线程句柄;dwProcessId和dwThreadId分别是全局进程标识符和全局线程标识符,前者用来标识一个进程,后者用来标识一
14、个线程。 (当启动一个进程时,系统会为此进程分配一个标识符,同时这个进程中的线程也会被分配一个标识符,在一个进程运行时,该进程的标识符和线程的标识符是唯一的,停止后,这些标识符可能会被系统分配给其它进程和线程。),在创建进程之前的另一项准备工作就是要定义PROCESS_INFORMATION 结构体变量,准备用于接收创建进程后返回的信息。PROCESS_INFORMATION pi; CreateProcess(,,CreateProcess函数的返回值 创建进程成功后,该函数返回TRUE,否则返回FALSE. 在程序中需要判断进程创建是否成功 If(!CreateProcess(,&si,&
15、pi)/创建不成功 Return; Else /创建成功/父进程继续执行,在父进程创建子进程后,子进程就开始运行;同时父进程计算1100的和。因为计算太快,为了方便观察父进程和子进程系统工作的过程,可以在计算过程使用Sleep 函数,强制让父进程的主线程休眠。Sleep函数定义如下: void Sleep( DWORD dwMilliseconds);/休眠一段时间,以ms为单位 Sleep(1000);/表示休眠1秒,计算完之后等待子进程完成它的工作。等待子进程完成可以用WaitForSingleObject函数实现等待。该函数定义如下: DWORD WaitForSingleObject(
16、 HANDLE hHandle, DWORD dwMilliseconds );,hHandle参数指定需要等待的对象句柄。dwMilliseconds 参数指定需要等待的时间,以ms为单位。0表示立即返回不等待。INFINITE表示等待直到对象句柄可用。WaitForSingleObject( pi.hProcess,INFINITE);,父进程等待子进程完成工作后,父进程需要读取文件内容并输出,是文件的一种操作。这在下面在子进程中介绍文件操作时一起系统介绍。,子进程的工作主要涉及的是文件操作。创建文件可以用fopen函数 FILE *fopen( /返回一个指向文件结构体的指针 const
17、 char *filename, /打开或创建的文件名 const char *mode /打开或创建方式,即设定读写权限 ); Fopen函数既可以创建文件也可以打开已存在的文件 FILE *pFile=fopen(“1.txt“,“w“);,子进程,文件写操作可以用fwrite函数 size_t fwrite( const void *buffer, /指向要写入内容的文件指针 size_t size, /每次写入大小,字节为单位 size_t count, /写入次数 FILE *stream /一个指向文件结构体的指针,表示将要写操作的文件 );fwrite(“Xidian Unive
18、rsity“,1,strlen(“Xidian University“),pFile);,读文件操作用fread函数 size_t fread( const void *buffer, /指向要读取内容的文件指针 size_t size, /每次读取大小,字节为单位 size_t count, /读取次数 FILE *stream /一个指向文件结构体的指针,表示将要写读取操作的文件 );,在每次写入文件盒读取文件操作后,关闭文件,使用fclose函数int fclose( FILE *stream );/关闭文件,参数是指向操作文件的文件结构体的指针,实验结果展示 父进程,父进程创建子进程等待子进程运行,子进程结束后父进程继续执行,