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的id1.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
相关文章推荐
- OS_exp3 生产者消费者问题(Linux环境)
- 【linux草鞋应用编程系列】_2_ 环境变量和进程控制
- Linux环境下的进程控制
- Linux 进程(一):环境及其控制
- linux进程控制---wait()使用方法
- Linux环境下Oracle exp/imp导出导入工具的使用
- Linux下的C编程实战(三)――进程控制与进程通信编程
- Linux:进程控制
- Linux - 进程控制 代码(C)
- linux进程控制命令
- linux--进程管理与控制
- Git 命令参数及用法详解 & Linux编程环境下版本控制 Git使用
- Linux之进程控制
- linux环境下导出导入oracle的dmp文件。(exp,imp)
- Linux系统进程控制编程---popen函数调用
- 分离出原环境控制台下分析数据,保证进程数据同步
- Linux笔记(管道符/作业控制,shell变量,环境变量配置文件,常见问题)
- LINUX进程控制
- Linux环境下的C/C+基础调试技术2——程序控制
- linux学习笔记-读《Linux编程技术详解》-进程与进程环境