进程控制实验

进程控制实验

1. 实验目的

设计并实现Unix的”time“命令,该命令通过命令行参数接受要运行的程序,创建一个独立的进程来运行该程序,并记录程序运行时间。

2. 实验内容

2.1 Windows实现

(1)使用CreateProcess()来创建进程运行指定可执行文件

(2)在父进程中使用WaitForSingleObject()在命令中和新创建的进程之间同步

(3)调用GetSystemTime()获取子进程运行时间

(4)将获得的运行时间格式化输出

2.2 macOS实现

(1)使用fork()创建子进程

(2)使用execv()指定子进程执行的程序

(3)父进程使用wait()等待子进程结束

(4)调用gettimeofday()获取子进程运行时间

(5)将获取的运行时间格式化输出

3. 实验环境

本实验基于本机macOS系统和Windows虚拟机完成,具体实验环境如下:

3.1 Linux环境

Linux环境配置如下:

  • 操作系统:macOS
  • 内存容量:8GB
  • 处理器:2.9GHz Intel Core i5
  • 硬盘容量:500GB

3.2 虚拟机环境

Windows虚拟机环境配置如下:

  • 虚拟机软件:VMware Fusion 11
  • 虚拟机操作系统:Windows 7 旗舰版
  • 虚拟机内存:4GB
  • 虚拟机硬盘容量:60GB

4. 程序设计和实现

4.1 Windows实现

4.1.1 API介绍

进程创建函数

本实验使用CreateProcess()函数创建进程并为进程指定运行程序。其语句调用如下:

#include
BOOL CreateProcess(LPCTSTR lpApplicationName, LPTSTR lpCommandLine,
                  LPSECURITY_ATTRIBUTES lpProcessAttributes,
                  LPSECURITY_ATTRIBUTES lpThreadAttributes,
                  BOOL bInheritHandles, DWORD dwCreationFlag,
                  LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory,
                  LPSTARTUPINFO lpStartupInfo,
                  LPPROCESS_INFORMTION lpProcessInformation);

该函数参数过多,本实验中多没用到,仅用到了部分参数,以下介绍几个有用到的参数。

lpApplication:该参数指定新进程将使用的可执行文件

lpCommandLine:该参数指定里传递给新进程的命令行字符串,该函数将按照一定的顺序搜索该可执行文件位置,并执行

lpProcessInformation:该参数是只想包含返回的进程和线程的句柄、进程和线程标识符的指针。在等待同步函数中需要从该结构中调取句柄信息

进程等待同步函数

本实验使用WaitForSingleObject()函数使父进程等待子进程,函数描述如下:

DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);

其中,参数hHandle为等待对象的句柄,实验中通过lpProcessInformation.hProcess给出,dwMillisenconds是以毫秒为单位的等待时间,由于本实验等待子进程完全结束,于是给定值为无限,即INFINITE

计时函数

本实验使用GetSystemTime()进行计时,函数描述如下:

void GetSystemTime(
  LPSYSTEMTIME lpSystemTime
);

其参数是一个指向SYSTEMTIME结构体的指针,该函数使用的是一个UTC时间,并非本地时间。

使用该函数需要定义SYSTEMTIME结构体提供记录,在输出时需要对时间进行格式化处理,具体如下:

SYSTEMTIME start,end;
GetSystemTime(&start);			//记录开始时间
/* Running code */
GetSystemTime(&end);				//记录结束时间
int hours=end.wHour-start.wHour;						//登记小时差
int minutes=end.wMinute-start.wMinute;			//登记分钟差
int seconds=end.wSecond-start.wSecond;			//登记秒差
int milliseconds=end.wMillisecond-start.wMillisecond;		//登记毫秒差

4.1.2 程序代码

本实验程序主要代码mytime.cpp,程序源代码如下:

#include 
#include 
#include 
using namespace std;
int main(int argc, char const *argv[])
{
    SYSTEMTIME start,end;
    STARTUPINFO si;
    ZeroMemory(reinterpret_cast<void*>(&si),sizeof(si));//初始化lpStartUpInfo参数
    si.cb=sizeof(si);
    PROCESS_INFORMATION pi;			//初始化lpProcessInformation参数
    TCHAR szFilename[MAX_PATH];
    sprintf(szFilename,"%s",argv[1]);	//初始化lpApplicationName参数
    TCHAR szCmdLine[MAX_PATH];
    sprintf(szCmdLine,"\"%s\" %s",argv[1],argv[2]);//初始化lpCommandLine参数
  	//创建进程
    if(!CreateProcess(szFilename,szCmdLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)){
      cout<<"Create Failed!"<<endl;
      exit(1);
    }
    GetSystemTime(&start);
    cout<<"Create Succeed!"<<endl;
    WaitForSingleObject(pi.hProcess,INFINITE);
    GetSystemTime(&end);
    int msec=end.wMilliseconds-start.wMilliseconds;
    int sec=end.wSecond-start.wSecond;
    int minute=end.wMinute-start.wMinute;
    int hour=end.wHour-start.wHour;
    cout<<"Running:"<<argv[1]<<endl;
    cout<<"Using Time: "<<hour<<" hours "<<minute<<" minutes "<<sec<<" seconds "<<msec<<" milliseconds "<<endl;
    return 0;
}

上述程序运行方式如下:

> g++ mytime.cpp -o mytime.exe
> ./mytime.exe ProgramName

4.1.3 运行结果

运行自制程序

Program1为自己写的程序,一个仅输出“In Program1”的简单程序,如下:

#include 
int main(int argc,char* argv[]){
    printf("In Program1\n");
    return 0;
}

运行结果如下:

mytime.exe program1.exe
Create Succeed!
In Program1
Running: program1.exe
Using Time: 0hours 0 minutes 0 seconds 15 milliseconds

进程控制实验_第1张图片

运行系统应用程序

运行系统应用程序火狐浏览器,运行结果如下:

mytime.exe firefox.exe
Create Succeed!
Running: firefox.exe
Using Time: 0hours 0 minutes 21 seconds 111 milliseconds
运行带参数程序

运行程序Program2,该程序是自己编写的简单程序,接受一个参数并挂起该参数对应的秒数,如下

#include 
#include 
int main(int argc,char* argv[]){
    printf("In Program2\n");
    int time=atoi(argv[1]);
    Sleep(1000*time);
    return 0;
}

运行结果如下:

mytime.exe program2.exe 1
Create Succeed!
In Program2
Running: program2.exe
Using Time: 0 hours 0 minutes 1 seconds 2 milliseconds

4.2 macOS实现

4.2.1 API介绍

进程创建函数

本实验使用fork()作为进程创建函数,其语句调用如下:

#include
#include
pid_t fork(void)

正确完成时,函数返回给父进程的是被创建子进程的标识,返回给子进程的为0;若创建失败,则返回父进程的为-1;

通过返回值,可以判断子进程是否创建成功,以及进程是子进程还是父进程(由于fork()函数调用后,子进程为父进程的一个拷贝),具体方法如下:

pid_t pid;
if((pid=fork())<0){		//此时pid为-1,则创建失败
	/* Error code */
}
if(pid==0){		//此时pid为0,则位于子进程中
    /* Child Process code */
}
else{		//此时,进程为父进程
    /* Parent Process code */
}
进程指定运行程序函数

本实验使用execv()为子进程指定运行程序,其函数调用如下:

#include
int execv(const char *pathname,char* const arg[]);

其中,参数pathname为要运行程序的源代码文件路径,arg[]为参数数组;该函数返回值指明程序是否装入成功,若返回值小于0则说明装入失败。具体方法如下:

if(execv(pathname,argv)<0){
    /* Error code */
}
计时函数

本实验使用gettimeofday()进行计时,该函数获得从1970年1月1日到现在的时间,其函数调用如下:

#include
int gettimeofday(struct timeval *tv, struct timezone *tz);

其中,参数tv是保存获取时间结果的结构体,其数据类型为struct timeval,参数tz用于保存时区结果。结构体timeval定义如下:

struct timeval{
    long int tv_sec;	//记录秒数
    long int tv_usec;	//记录微秒数
}

在本实验中,只使用参数tv记录时间,不实用tz参数,以NULL替代。具体实现方法如下:

#include
struct timeval start,end;
gettimeofday(&start,NULL);
/* Running cod */
gettimeofday(&end,NULL);

由于本实验需要格式化输出,而结构体只保存秒数和微秒数,需要格式化输出成以*小时*分*秒*毫秒*微秒的形式,具体实现如下:

int sec=end.tv_sec-start.tv_sec;	//登记秒数
int usec=end.tv_sec-start.tv_sec;	//登记微秒数
if(usec<0){							//如果微秒数小于0,则从秒借位
    usec+=1000000;
    sec-=1;
}
int hours=sec/3600;					//计算小时数
int minutes=(sec/60)%60;			//计算分钟数
int seconds=sec%60;					//计算秒数
int milliseconds=usec/1000;			//计算毫秒数
int microseconds=usec%1000;			//计算微秒数

4.2.2 程序代码

本实验程序主代码mytime.c,程序源代码如下:

#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    struct timeval start, end;
    pid_t pid;
    // 创建子进程
    if ((pid = fork()) < 0)
    {
        printf("fork error\n");
    }
    gettimeofday(&start, NULL);
    if (pid == 0)
    {
        //在子进程中将程序装入
        if (execv(argv[1], argv) < 0)
        {
            printf("execle error\n");
            _Exit(-1);
        }
    }
    else
    {
        //父进程等待子进程完成
        wait(NULL);
        gettimeofday(&end, NULL);
        int sec=end.tv_sec-start.tv_sec;
		int usec=end.tv_sec-start.tv_sec;
		if(usec<0){
		    usec+=1000000;
		    sec-=1;
		}
		int hours=sec/3600;					
		int minutes=(sec/60)%60;			
		int seconds=sec%60;					
		int milliseconds=usec/1000;			
		int microseconds=usec%1000;	
        //格式化输出时间
        printf("运行程序:%s\n", argv[1]);
        printf("使用时间: %d小时%d分%d秒%d毫秒%d微秒\n", hour, minute, sec, msec, usec);
    }
    return 0;
}

上述程序运行方式如下:

$ gcc mytime.c -o mytime
$ ./mytime ProgramName #要运行的程序名称

4.2.3 运行结果

运行自制程序

Program1是实验则自己写的程序,如下

#include 
int main(int argc,char* argv[]){
    printf("In Program1\n");
    return 0;
}

结果如下

$ ./mytime program1
In Program1
运行程序:program1
使用时间: 0小时0分0秒4毫秒982微秒
运行系统应用程序

本实验选取计算器Calculator作为应用程序,计算其时间,结果如下:

$ ./mytime Calculator
运行程序: Calculator
使用时间: 0小时0分10秒793毫秒798微秒
运行带参数程序

带参数程序为Program2,该程序可以接收参数t,并挂起t秒时间,如下:

#include 
#include 
int main(int argc, char *argv[])
{
    printf("In Program2\n");
    int time=atoi(argv[2]);
    sleep(time);
    return 0;
}

运行结果如下:

$ ./mytime program2 2
In Program2
运行程序:program2
使用时间: 0小时0分2秒9毫秒443微秒

5. 实验收获与体会

​ 通过本实验,我学会了使用Linux和Windows系统下的进程控制有关API,也学习了shell和cmd的使用方式。

​ 不同的操作系统有不同的进程控制的API,各有各的优点和缺点。

​ Linux系统下的API具有的优点是简洁,每个API的参数都十分简单易懂,参数也不过几个。同时Linux系统下对进程的控制理解较为容易使用pid作为进程标识,使用fork()创建进程,先克隆父进程的拷贝,再选择要执行的程序。但是其缺点也很明显,Linux下的API的命名并没有像Windows那样好看易懂,反而是各种缩写或奇怪的名字。

​ Windows系统下的API具有的优点是明了,所有的API或者参数的名字都是有意义的名字,几乎可以做到望文生义。但是其缺点也很突出,那就是参数很多,例如CreateProcess()足足有10个参数,虽然大部分在本实验中没有用到,但是仍然需要去了解,否则可能会出现错误,这就给学习者造成较大的困扰。

你可能感兴趣的:(BIT)