您的位置:首页 > 运维架构 > Linux

OS_exp2 进程控制(Linux环境)

2018-03-09 17:10 627 查看

【实验二】实验要求

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

在Windows下实现:

使用CreateProcess()来创建进程
使用WaitForSingleObject()在“mytime”命令和新创建的进程之间同步
调用GetSystemTime()来获取时间


在Linux下实现:

使用fork()/vfork /exec()来创建进程运行程序
使用wait()等待新创建的进程结束
调用gettimeofday()来获取时间


mytime的用法:

$ mytime.exe program1

要求输出程序program1运行的时间

$ mytime.exe program2 t

t为时间参数,为program2的输入参数,控制program2的运行时间。最后输出program2的运行时间,应和t基本接近。

显示结果: ××小时××分××秒××毫秒××微秒

1.1~1.8为函数介绍和踩坑分析等废话,想看代码和运行结果请直接划到文末1.9查看。

一、Linux环境下

1.0 程序设计思路

(1)创建子进程之前,先创建time_start和time_end结构体,

struct timeval time_start;

struct timeval time_end;

(2)用fork()函数创建进程,通过返回值来判断是子进程还是父进程。

if (fork() == 0) //子进程

else if (fork() >= 0) //父进程

(3)① 如果第(2)步中是子进程运行,记录子进程开始运行的时间,并在子进程中调用execv()函数,execv()函数从终端传入的参数argv[1]中得知可执行文件的文件名并执行:execv(argv[1],&argv[1])

② 如果第(2)步是父进程在运行,记录(fork后)父进程开始运行的时间gettimeofday(&time_start,NULL);。

如果在终端中是不带时间参数的mytime命令调用,即argc == 2,则先调用wait()等待子进程运行结束,然后再获取进程终止的时间gettimeofday(&time_end,NULL);;如果在终端中是带时间参数的mytime命令调用,即argc == 3,则利用atof()函数将argv[2]中的char*型数据转换为double型数据,父进程调用usleep(sleep_sec * 1000000)函数等待子进程运行sleep_sec秒(即sleep_sec * 1000000微秒)后kill掉子进程,并获取程序终止的时间gettimeofday(&time_end,NULL);

(4)计算程序运行的时间(微秒)并将其转换成××小时××分××秒××毫秒××微秒的格式:

time_use = (time_end.tv_sec - time_start.tv_sec) * 1000000 + (time_end.tv_usec - time_start.tv_usec);

time_hour = time_use / (60 * 60 * 1000 * 1000);

time_left = time_use % (60 * 60 * 1000 * 1000);

time_min = time_left / (60 * 1000 * 1000);

time_left %= (60 * 1000 * 1000);

time_sec = time_left / (1000 * 1000);

time_left %= (1000 * 1000);

time_ms = time_left / 1000;

time_left %= 1000;

time_us = time_left;

(5)输出程序运行的时间:

printf(“此程序运行的时间为:%ld 小时, %ld 分钟, %ld 秒, %ld 毫秒, %ld 微秒\n”,time_hour, time_min, time_sec, time_ms, time_us);

(6)将c文件编译链接为可执行文件:

gcc exp2_linux.c –o exp2_linux.o

(7)修改.bashrc文件,将exp2_linux.o封装成mytime命令,并用source .bashrc命令使配置生效。

(8)在终端执行mytime program1统计program1的运行时间并输出或者执行mytime program2 t规定program2的执行时间大约为t,运行program2并输出与规定的t差不多的运行时间。

1.1 getpid()和getppid()

getpid()得到本身进程id,getppid()得到父进程进程id,如果已经是父进程,得到系统进程id,如bash环境运行得到bash的id

1.2 fork()

fork()为创建子进程,有三种不同的返回值:

1、在父进程中,fork返回新创建的子进程的PID

2.、在子进程中,fork返回0

3、如果出现错误,fork返回一个负值

另外在子进程被创建之前,只有一个进程在运行,但在fork创建子进程后,两进程同时运行

程序示例:

int main()
{
pid_t pid;
printf("Before fork ...\n");

switch(pid = fork())
{
case -1:
printf("Fork call fail\n");
exit(1);

case 0:
printf("The pid of child is: %d\n", getpid());
printf("The pid of child's parent is: %d\n", getppid());
printf("Child exiting...\n");
exit(0);

default:
printf("The pid of parent is: %d\n", getpid());
printf("the pid of parent's child is: %d\n", pid);
}
printf("After fork, program exiting...\n");
exit(0);
}


输出:

/home/sunnie/CLionProjects/OS_exp2/cmake-build-debug/OS_exp2
Before fork ...
The pid of parent is: 11731
the pid of parent's child is: 11732
After fork, program exiting...
The pid of child is: 11732
The pid of child's parent is: 1
Child exiting...

Process finished with exit code 0


1.3 execv()

1、函数原型

int execv(const char *progname, char *const argv[]);   // #include <unistd.h>


2、用法介绍

execv会停止执行当前的进程,并且以progname应用进程替换被停止执行的进程,进程ID没有改变。

progname: 被执行的应用程序。

argv: 传递给应用程序的参数列表, 注意,这个数组的第一个参数应该是应用程序名字本身,并且最后一个参数应该为NULL,不参将多个参数合并为一个参数放入数组。

3、返回值

如果应用程序正常执行完毕,那么execv是永远不会返回的;当execv在调用进程中返回时,那么这个应用程序应该出错了(可能是程序本身没找到,权限不够…), 此时它的返回值应该是-1,具体的错误代码可以通过全局变量errno查看,还可以通过stderr得到具体的错误描述字符串:

4、 示例

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main()
{
pid_t pid = fork();

if( pid == 0 ) // this is the child process
{
printf("Before execv ...\n");
printf("The pid of child is: %d\n", getpid());
printf("The pid of child's parent is: %d\n", getppid());

execv("/bin/ls", NULL);

printf("After execv ...\n");
printf("The pid of child is: %d\n", getpid());
printf("The pid of child's parent is: %d\n", getppid());
printf("Child exiting...\n");

// the program should not reach here, or it means error occurs during execute the ls command.
printf("command ls is not found, error code: %d(%s)", errno, strerror(errno));
}
}


输出:

/home/sunnie/CLionProjects/OS_exp2/cmake-build-debug/OS_exp2
Before execv ...
The pid of child is: 9181
The pid of child's parent is: 1

Process finished with exit code 0


1.4 wait()

进程一旦调用了 wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait 就会收集这个子进程的信息, 并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

定义函数 pid_t wait (int * status);

函数说明:

wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数status 可以设成NULL。子进程的结束状态值请参考waitpid()。

返回值:

如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno 中。

程序:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <zconf.h>
#include <cstring>

int main(int arg, char *args[])
{
pid_t pid = 0;
int i = 0, ret = 0;
for (i = 0; i < 3; i++)
{
pid = fork();
if (pid == -1)
{
printf("fork() failed ! error message:%s\n", strerror(errno));
return -1;
}
if (pid == 0)
{
printf("child is running!\n");
printf("The pid of child is: %d\n", getpid());
printf("The pid of child's parent is: %d\n", getppid());
exit(0);
}
}
while (1)
{
//wait()函数的返回值是子进程的pid
ret = wait(NULL);
printf("wait()返回的子进程 pid = %d\n", ret);
if (ret == -1)
{
//父进程wait()函数阻塞过程中,有可能被别的信号中断,需要做异常处理
if (errno == EINTR)
{
continue;
}
break;
}
}
printf("game is over!\n");
return 0;
}


输出:

/home/sunnie/CLionProjects/OS_exp2/cmake-build-debug/OS_exp2
child is running!
The pid of child is: 17761
The pid of child's parent is: 17760
child is running!
The pid of child is: 17762
child is running!
The pid of child's parent is: 17760
The pid of child is: 17763
The pid of child's parent is: 17760
wait()返回的子进程 pid = 17761
wait()返回的子进程 pid = 17762
wait()返回的子进程 pid = 17763
wait()返回的子进程 pid = -1
game is over!

Process finished with exit code 0


1.5 gettimeofday()

1.简介:

在C语言中可以使用函数gettimeofday()函数来得到时间。它的精度可以达到微秒

2.函数原型:

#include<sys/time.h>

int gettimeofday(struct  timeval*tv,struct  timezone *tz )


3.说明:

gettimeofday()会把目前的时间用tv 结构体返回,当地时区的信息则放到tz所指的结构中

4.结构体:

(1)timeval

struct  timeval {
long  tv_sec;/*秒*/
long  tv_usec;/*微秒*/
};


(2)timezone 结构定义为:

struct  timezone {
int tz_minuteswest;/*和greenwich 时间差了多少分钟*/
int tz_dsttime;/*type of DST correction*/
}


(3)在gettimeofday()函数中tv或者tz都可以为空。如果为空则就不返回其对应的结构体。

(4)函数执行成功后返回0,失败后返回-1,错误代码存于errno中。

(5)程序示例:

#include<stdio.h>
#include<sys/time.h>

int delay(int time)
{
int i,j;

for(i =0;i<time;i++)
for(j=0;j<5000;j++)
;
}

int main()
{
struct  timeval start;
struct  timeval end;
unsigned long diff;

gettimeofday(&start,NULL);
delay(10);
gettimeofday(&end,NULL);

diff = 1000000 * (end.tv_sec-start.tv_sec)+ end.tv_usec-start.tv_usec;
printf("the difference is %ld μs\n",diff);

return 0;
}


1.6 有关argv[]

1.6.1 argv[]的含义

int main(int argc,char *argv[]) 是 UNIX 和 Linux 中的标准写法,而 int main() 只是 UNIX 及 Linux 默许的用法..

那究竟 argc,argv[] 有何之用呢?下面看个例子 edit.c 就会明白它们的用法了:

#include<unistd.h>
#include<stdio.h>

int main(int argc,char *argv[])
{
if(argc==1 || argc>2) {
printf("请输入想要编辑的文件名如:./edit fillen");
}
if(argc==2) {
printf("编辑 %sn",argv[1]);
}
exit(0)
}


编译该程序:

gcc -o edit edit.c


运行:

./edit


结果:

请输入想要编辑的文件名如:./edit fille
运行:./edit edit.txt
结果:编辑 edit.txt


argc 是外部命令参数的个数,argv[] 存放各参数的内容。

如上例:执行 ./edit 时,argc 为1,

argv[0] 为 ./edit 。而执行 ./edit edit.txt 时,argc 的值为 2,

argv[0] 为 ./edit,argv[1] 为 edit.txt 。

1.6.2 如何转换argv[2]为int类型

argv[1] is a pointer to a string.

You can print it using: printf(“%s\n”, argv[1]);

To get an integer from a string you have first to convert it. Use strtol to convert a string to an int.

#include <errno.h>   // for errno
#include <limits.h>  // for INT_MAX
#include <stdlib.h>  // for strtol

char *p;
int num;

errno = 0;
long conv = strtol(argv[1], &p, 10);

// Check for errors: e.g., the string does not represent an integer
// or the integer is larger than int
if (errno != 0 || *p != '\0' || conv > INT_MAX) {
// Put here the handling of the error, like exiting the program with
// an error message
} else {
// No error
num = conv;
printf("%d\n", num);
}


argv[1]转int的demo:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include <errno.h>   // for errno
#include <limits.h>  // for INT_MAX
#include <stdlib.h>  // for strtol

int main(int argc, char* argv[])
{
char *p;
errno = 0;
long sleep_sec = strtol(argv[1], &p, 10);// base=10为十进制转换
printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
pid_t pid = getpid();

printf("这里是程序运行的输出!\n");
printf("pid = %d\n",pid);

// Check for errors: e.g., the string does not represent an integer
// or the integer is larger than int
if (errno != 0 || *p != '\0' || sleep_sec > INT_MAX) {
// Put here the handling of the error, like exiting the program with
// an error message
printf("转换出错!\n");
exit(1);
} else {
// No error
printf("argv[0] = %s\n",argv[0]);
printf("argv[1] = %d\n", argv[1]);
}

printf("即将sleep %d 秒\n",sleep_sec);
sleep(sleep_sec);
printf("I will kill process %d\n",pid);
kill(pid,SIGKILL);
printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
return 0;
}


输出:

sunnie@sunnie-Lenovo-Rescuer-15ISK:~/sunnie/OS_exp2$ ./test2.o 4
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这里是程序运行的输出!
pid = 27009
argv[0] = ./test2.o
argv[1] = 1374925442
即将sleep 4 秒
I will kill process 27009
Killed


1.6.3 argv[2]转double

atof(),是C 语言标准库中的一个字符串处理函数,功能是把字符串转换成浮点数,所使用的头文件为

#include<stdlib.h>

int main(int argc, char *argv[]) {
double ddd;
ddd=atof(argv[1]);
printf("%f\n", ddd);
return 0;
}


argv[2]转double的demo:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include <stdlib.h>  // for atof

int main(int argc, char* argv[])
{
printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
pid_t pid = getpid();

printf("这里是程序运行的输出!\n");
printf("pid = %d\n",pid);

double sleep_sec = atof(argv[1]);

printf("argv[0] = %s\n",argv[0]);
printf("argv[1] = %f\n", sleep_sec);

printf("即将sleep %f 秒\n",sleep_sec);
usleep(sleep_sec * 1000000); //sec to usec
printf("I will kill process %d\n",pid);
kill(pid,SIGKILL);
printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
return 0;
}


输出:

sunnie@sunnie-Lenovo-Rescuer-15ISK:~/sunnie/OS_exp2$ ./test2.o 4.789876
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这里是程序运行的输出!
pid = 28356
argv[0] = ./test2.o
argv[1] = 4.789876
即将sleep 4.789876 秒
I will kill process 28356
Killed


1.7 fork与vfork的区别

1.vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

2.fork要拷贝父进程的进程环境;而vfork则不需要完全拷贝父进程的进程环境,在子进程没有调用exec和exit之前,子进程与父进程共享进程环境,相当于线程的概念,此时父进程阻塞等待。

为什么会有vfork呢?

因为以前的fork当它创建一个子进程时,将会创建一个新的地址空间,并且拷贝父进程的资源,然后将会有两种行为:

1.执行从父进程那里拷贝过来的代码段

2.调用一个exec执行一个新的代码段

当进程调用exec函数时,一个新程序替换了当前进程的正文,数据,堆和栈段。这样,前面的拷贝工作就是白费力气了,这种情况下,聪明的人就想出了vfork。vfork并不复制父进程的进程环境,子进程在父进程的地址空间中运行,所以子进程不能进行写操作,并且在儿子“霸占”着老子的房子时候,要委屈老子一下了,让他在外面歇着(阻塞),一旦儿子执行了exec或者exit后,相当于儿子买了自己的房子了,这时候就相当于分家了。

因此,如果创建子进程是为了调用exec执行一个新的程序的时候,就应该使用vfork

1.8 测试文件

测试文件

(1)test.c文件(能运行完自行结束程序,用来测试第一种调用mytime program1):

#include<stdio.h>
int main()
{
printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
printf("这里是程序运行的输出!\n");
printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
return 0;
}


(2)test1.c文件(需要从键盘等待输入,阻塞程序运行用来测试第二种调用mytime program2 t):

#include<stdio.h>
int main(void)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
printf("%d,%d,%d/n", a, b, c);
return 0;
}


1.9 linux环境下的代码

(1)只能实现第一种调用的源码

mytime的用法一:
$ mytime program1

要求输出程序program1运行的时间

显示结果: ××小时××分××秒××毫秒××微秒


#include<math.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<unistd.h>
#include<wait.h>

int main(int argc, char* argv[])
{
//调用系统时间
struct timeval time_start;
struct timeval time_end;
//用以记录进程运行的时间
int time_use = 0; // us
int time_left = 0; // us
int time_hour = 0, time_min = 0, time_sec = 0, time_ms = 0, time_us = 0;
pid_t pid;
pid = fork();
if (pid < 0) //如果出错
{
printf("创建子进程失败!");
exit(1);
}
else if (pid == 0)  //如果是子进程
{
printf("---------------------------------------\n");
printf("Child Process is running!\n");
gettimeofday(&time_start,NULL);
printf("子进程开始运行的系统秒数: %d s\n",time_start.tv_sec);
printf("子进程开始运行的系统微秒数: %d μs\n",time_start.tv_usec);
printf("---------------------------------------\n");
//在子进程中调用execv函数在命令行中来运行一个程序
execv(argv[1],NULL);
}
else
{
printf("=================================================\n");
printf("Parent Process is running!\n");
gettimeofday(&time_start,NULL);
printf("父进程开始运行的系统秒数: %d s\n",time_start.tv_sec);
printf("父进程开始运行的系统微秒数: %d μs\n",time_start.tv_usec);
wait(NULL); //等待子进程结束
gettimeofday(&time_end,NULL);
printf("进程结束的系统秒数:%d s\n",time_end.tv_sec);
printf("进程结束的系统微秒数:%d μs\n",time_end.tv_usec);
printf("=================================================\n");
time_use = (time_end.tv_sec - time_start.tv_sec)*1000000 + (time_end.tv_usec - time_start.tv_usec);
time_hour = time_use / (60 * 60 * (int)pow(10,6));
time_left = time_use % (60 * 60 * (int)pow(10,6));
time_min = time_left / (60 * (int)pow(10,6));
time_left %= (60 * (int)pow(10,6));
time_sec = time_left / ((int)pow(10,6));
time_left %= ((int)pow(10,6));
time_ms = time_left / 1000;
time_left %= 1000;
time_us = time_left;
printf("此程序运行的时间为:%d 小时, %d 分钟, %d 秒, %d 毫秒, %d 微秒\n",time_hour,time_min,time_sec,time_ms,time_us);
}
return 0;
}




(2)两种调用方式都能实现的源码

mytime的用法一:
$ mytime program1

要求输出程序program1运行的时间

$ mytime program2 t

t为时间参数,为program2的输入参数,控制program2的运行时间。最后输出program2的运行时间,应和t基本接近。

显示结果: ××小时××分××秒××毫秒××微秒


#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<unistd.h>
#include<wait.h>

int main(int argc, char* argv[])
{
//调用系统时间
struct timeval time_start;
struct timeval time_end;
//用以记录进程运行的时间
long time_use = 0; // us
long time_left = 0; // us
long time_hour = 0, time_min = 0, time_sec = 0, time_ms = 0, time_us = 0;
long kill_ret_val = 0;
double sleep_sec = 0;

pid_t pid = fork(); // return 0 to child process, return pid of child to parent process, return -1 while failure

if (pid < 0) //如果出错
{
printf("创建子进程失败!");
exit(1);
}
else if (pid == 0)  //如果是子进程
{
printf("---------------------------------------\n");
printf("Child Process is running!\n");
gettimeofday(&time_start,NULL);
printf("子进程开始运行的系统秒数: %ld s\n",time_start.tv_sec);
printf("子进程开始运行的系统微秒数: %ld μs\n",time_start.tv_usec);
printf("---------------------------------------\n");
//在子进程中调用execv函数在命令行中来运行一个程序
execv(argv[1], &argv[1]);
}
else
{
printf("=================================================\n");
printf("parent pid = %d\n",getpid());
printf("child pid = %d\n",pid);
printf("Parent Process is running!\n");
gettimeofday(&time_start,NULL);
printf("父进程开始运行的系统秒数: %ld s\n",time_start.tv_sec);
printf("父进程开始运行的系统微秒数: %ld μs\n",time_start.tv_usec);
if (argc == 2) {
wait(NULL); //等待子进程结束
}
if (argc == 3) {
sleep_sec = atof(argv[2]);
printf("argv[0] = %s\n",argv[0]);
printf("argv[1] = %s\n",argv[1]);
printf("argv[2] = %f\n", sleep_sec);

printf("begin to sleep for %f seconds.\n",sleep_sec);
usleep(sleep_sec * 1000000); // sec to usec

printf("The parent process will kill the child process, its pid is %d.\n",pid);
kill_ret_val = kill(pid,SIGKILL);
if (kill_ret_val == -1) // return -1, fail
{
printf("kill failed.\n");
perror("kill");
}
else if (kill_ret_val == 0) // return 0, success
{ printf("process %d has been killed\n", pid); }
}
gettimeofday(&time_end,NULL);
printf("进程结束的系统秒数:%ld s\n",time_end.tv_sec);
printf("进程结束的系统微秒数:%ld μs\n",time_end.tv_usec);
printf("=================================================\n");
time_use = (time_end.tv_sec - time_start.tv_sec)*1000000 + (time_end.tv_usec - time_start.tv_usec);
time_hour = time_use / (60 * 60 * 1000 * 1000);
time_left = time_use % (60 * 60 * 1000 * 1000);
time_min = time_left / (60 * 1000 * 1000);
time_left %= (60 * 1000 * 1000);
time_sec = time_left / (1000 * 1000);
time_left %= (1000 * 1000);
time_ms = time_left / 1000;
time_left %= 1000;
time_us = time_left;
printf("此程序运行的时间为:%ld 小时, %ld 分钟, %ld 秒, %ld 毫秒, %ld 微秒\n",time_hour,time_min,time_sec,time_ms,time_us);
}
return 0;
}


(3)修改.bashrc文件

在home目录下进入.bashrc文件:

gedit .bashrc


在.bashrc中加入下面这句:

alias mytime='~/sunnie/OS_exp2/exp2_linux.o'




使配置生效:

source .bashrc


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: