设计并实现Unix的”time“命令,该命令通过命令行参数接受要运行的程序,创建一个独立的进程来运行该程序,并记录程序运行时间。
(1)使用CreateProcess()
来创建进程运行指定可执行文件
(2)在父进程中使用WaitForSingleObject()
在命令中和新创建的进程之间同步
(3)调用GetSystemTime()
获取子进程运行时间
(4)将获得的运行时间格式化输出
(1)使用fork()
创建子进程
(2)使用execv()
指定子进程执行的程序
(3)父进程使用wait()
等待子进程结束
(4)调用gettimeofday()
获取子进程运行时间
(5)将获取的运行时间格式化输出
本实验基于本机macOS系统和Windows虚拟机完成,具体实验环境如下:
Linux环境配置如下:
Windows虚拟机环境配置如下:
本实验使用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; //登记毫秒差
本实验程序主要代码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
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
运行系统应用程序火狐浏览器,运行结果如下:
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
本实验使用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; //计算微秒数
本实验程序主代码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 #要运行的程序名称
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微秒
通过本实验,我学会了使用Linux和Windows系统下的进程控制有关API,也学习了shell和cmd的使用方式。
不同的操作系统有不同的进程控制的API,各有各的优点和缺点。
Linux系统下的API具有的优点是简洁,每个API的参数都十分简单易懂,参数也不过几个。同时Linux系统下对进程的控制理解较为容易使用pid
作为进程标识,使用fork()
创建进程,先克隆父进程的拷贝,再选择要执行的程序。但是其缺点也很明显,Linux下的API的命名并没有像Windows那样好看易懂,反而是各种缩写或奇怪的名字。
Windows系统下的API具有的优点是明了,所有的API或者参数的名字都是有意义的名字,几乎可以做到望文生义。但是其缺点也很突出,那就是参数很多,例如CreateProcess()
足足有10个参数,虽然大部分在本实验中没有用到,但是仍然需要去了解,否则可能会出现错误,这就给学习者造成较大的困扰。