最近因为一个偶然的原因要在linux平台上做一个模拟实验,其中要涉及到多进程的编程,所以特此写一系列的博客来总结一下多进程的编程问题。这一系列文章只适合了解linux,并且在Linux下使用C语言写过程序的人。
首先进程的概念就不多说了,所有大凡上过操作系统课的人一定对那一堆堆的图示永生难忘。
使用进程前,了解一个简单的linux命令“ps”命令,它用来显示当前系统进程:
ps效果:
此外还有ps -l,nice,renice命令及参数,具体的使用用man查询就可以了
一.启动一个新进程:
#inlcude<stdlib.h> system(char *);这个函数可以执行一个新的shell命令,只是启动的新进程将会取代当前进程,也即如果system启动成功,则其之后的代码将不会被运行。下面是一个system的例子:
#include<stdlib.h> #include<stdio.h> void main(){ int result_code; printf("Running ps with system\n"); result_code = system("ps ax &"); if(result_code == 127){printf("can't start a shell");} if(result_code == -1){printf("start error");} else{printf("running success");} }
二.exec系列函数
#include<unistd.h>
char **environ;
int execl(const char *path,const char *arg0,...,(char *)0);
int execlp(const char *file,const char *arg0,...,(char *)0);
int execle(const char *path,const char *arg0,...,(char *)0,char *const envp[]);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]);
int execve(const char *path,char *const argv[],char *const envp[]);
exec系列函数的作用是替换进程的映像,并且其会接受当前进程的所有资源,包括已经打开的文件句柄等等。这一系列函数可以分为两大类,包括以l系列,其接受一个指令路径或者目录,以可变参数,或者一个环境变量参数变,但所有参数均需以空指针结尾;第二个系列是v系列,与l系列类似,只是其将可变参数变以数组的形式传递给函数
一个exec的示例:
#include<unistd.h> #include<stdlib.h> #include<stdio.h> void main(){ printf("exec series functions test\n"); printf("execve\n"); char *const ps_argv[] = {"ps","ax",0}; char *const ps_envp[] = {"PATH=/bin:/usr/bin","TERM=console",0}; execv("/bin/ps",ps_argv); printf("Done.\n"); }
输入输出重定向:因为exec执行后会保留之前进程的已打开文件句柄,利用这个特性,我们可以写一个小的程序,这种思想是你可以包装一个你根本就不知道源代码,只知道功能的程序。比如下面这个upper.c将文件改为大写输出,useupper.c包装它,并为它提供一个文件名参数
#include<stdio.h> #include<ctype.h> #include<stdlib.h> int main(){ int ch; while((ch = getchar())!=EOF){ putchar(toupper(ch)); } exit(0); }
#include<stdio.h> #include<stdlib.h> #include<unistd.h> int main(int argc,char *argv[]){ char *filename; if(argc != 2){ perror("arguments number less than 2\n"); exit(1); } filename = argv[1]; if(!freopen(filename,"r",stdin)){ perror("file reopen failed \n"); exit(2); } execl("./upper","upper",0); perror("program failed\n"); exit(3); }
三.复制进程映像,fork()
接下来这个方法就是大名鼎鼎的fork()函数,相信所有使用过,甚至看过linux系列书籍的人都知道这个函数。它会将当前进程分裂成两个完全一样,但是完全独立的进程。有些书上会说用它来创立了新进程,但是我觉得用分裂来得更贴切,就像生物学中无丝分裂一样,它生成的两个进程除了资源和ID不同外,其他都一样
#include<sys/types.h>#include<unistd.h> pid_t fork(void);错误时返回-1;在父进程中返回子进程ID;子进程中返回0,可以依次来区分当前在哪个进程中.
下面是一个使用fork打印父子进程的示例
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> int main(){ pid_t pid; char *message; int n; printf("fork \n"); pid = fork(); switch(pid){ case -1:perror("fork error"); break; case 0:message="this is child"; n=8; break; default:message="this is parent"; n=3; break; } for(;n>0;n--){ puts(message); sleep(1); } exit(0); }
分裂后父子进程是彼此相互独立运行的,所以要想让父进程等待子进程,只需使用wait()系统调用:
#include<sys/types.h>#include<sys/wait.h> pid_t wait(int *stat_val);有关stat_val的详细值建议去百度官网看api文档最好
一个简单的示例如下:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<sys/types.h> #include<sys/wait.h> int main(){ pid_t pid; char *message; int n; int exit_code; printf("wait test\n"); pid = fork(); switch(pid){ case -1:perror("fork failed"); exit(1); case 0:message = "this is child"; n = 8; exit_code = 37; break; default:message = "this is parent"; n= 5; exit_code = 0; break; } for(;n>0;n--){ puts(message); sleep(1); } if(pid != 0){ int stat_val; pid_t child_pid; child_pid = wait(&stat_val); printf("waiting for child to finish\n"); if(WIFEXITED(stat_val)){ printf("child exit code is %d\n",WEXITSTATUS(stat_val)); }else{ printf("child exit failed\n"); } } exit(exit_code); }
四.僵尸进程
所谓僵尸进程,是指使用fork后,子进程先于父进程结束,但是因为父子进程间依然有关系,那么子进程实际上不会真正意义上终结,如果查看当前进程表,会发现该进程依然存在,且会被标记为<defunct>/<zombies>。人为产生僵尸进程也并不那么轻松,但是在上面那个等待例子中,如果让子进程先于父进程退出,在父进程结束前调用ps -al命令后就会发现有这么一个僵尸进程,实现方法如下:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<sys/types.h> #include<sys/wait.h> int main(){ pid_t pid; char *message; int n; int exit_code; printf("wait test\n"); pid = fork(); switch(pid){ case -1:perror("fork failed"); exit(1); case 0:message = "this is child"; n = 3; exit_code = 37; break; default:message = "this is parent"; n= 12; exit_code = 0; break; } for(;n>0;n--){ puts(message); if(n == 1 ){printf("child finished\n");system("ps -al");} sleep(1); } if(pid != 0){ int stat_val; pid_t child_pid; child_pid = wait(&stat_val); printf("waiting for child to finish\n"); if(WIFEXITED(stat_val)){ printf("child exit code is %d\n",WEXITSTATUS(stat_val)); }else{ printf("child exit failed\n"); } } exit(exit_code); }
效果如图: