目录
进程是什么
进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。在操作系统原理使用这样的术语来描述的:正在运行的程序及其占用的资源(CPU、内存、系统资源等)叫做进程。比如你正在登录的QQ,比如你正在看这篇文章的网页,都是一个个进程。
任务管理器中也能看到你后台运行的各个进程。
fork()函数
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
在我们编程的过程中,一个函数调用只有一次返回(return),但由于fork()系统调用会创建一个新的进程,这时它会有两次返回。一次返回是给父进程,其返回值是子进程的PID(Process ID),第二次返回是给子进程,其返回值为0。所以我们在调用fork()后,需要通过其返回值来判断当前的代码是在父进程还是子进程运行,如果返回值是0说明现在是子进程在运行,如果返回值>0说明是父进程在运行,而如果返回值<0的话,说明fork()系统调用出错。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
int b=100;
int main(int argc,char **argv)
{
int a=10;
pid_t pid,pr;
printf("进程开始!\n");
if((pid = fork())<0)
{
printf("创建子进程失败:%s\n",strerror(errno));
return -2;
}
else if(pid == 0)
{
printf("子进程[%d] 开始运行...\n",getpid());
a++;
b++;
}
else
{
printf("父进程[%d]开始运行...\n",getpid());
//让子进程先运行
sleep(1);
/* pr=wait(&pid);
if(pr<0)
{
printf("wait failure:%s\n",strerror(errno));
return -3;
}
printf("I catched a child process with pid of %d\n",pr);
*/
}
if(pid)//pid大于0是父进程,等于0是子进程
printf("父进程pid:[%d],a:[%d],b:[%d]\n",getpid(),a,b);
else
printf("子进程pid:[%d],a:[%d],b:[%d]\n",getpid(),a,b);
return 0;
}
运行结果如下:
将父进程的sleep加上注释后:
可见最后结束运行的进程不同了。
因为进程创建之后究竟是父进程还是子进程先运行没有规定,所以父进程调用了sleep(1)的目的是希望让子进程先运行,但这个机制是不能100%确定能让子进程先执行,如果系统负载较大时1秒的时间内操作系统可能还没调度到子进程运行,所以sleep()这个机制并不可靠。
子进程继承了一份父进程的“财产”——a和b,且互不影响。在子进程中a和b都自加了1,但是最后父进程输出的是没有加的结果。这就好比儿子长大了自己住了,但是怀恋原来家的环境,所以父亲给他做了一个一模一样的房子,儿子和父亲房子虽长得一样,但是各自在各自的家中,所以互不影响。
wait()
在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项可使调用者不用阻塞。waitpid并不等待在其调用的之后的第一个终止进程,他有若干个选项,可以控制他所等待的进程。 如果一个已经终止、但其父进程尚未对其调用wait进行善后处理(获取终止子进程的有关信息如CPU时间片、释放它锁占用的资源如文件描述符等)的进程被称僵死进程(zombie),ps命令将僵死
进程的状态打印为Z。如果子进程已经终止,并且是一个僵死进程,则wait立即返回该子进程的状态。所以,我们在编写多进程程序时,最好调用wait()或waitpid()来解决僵尸进程的问题。
在上面代码块的父进程中可以看到有几行注释掉的代码,就是wait()函数,将注释解开后,sleep注释掉,因为wait()已经是阻塞的了,让其等待子进程运行结束后收回子进程。如下图:
虽然最后打印的是子进程,那是因为wait之后pid被置为了0,再经过判断以后,就打印了子进程的printf,但是可以看到进程号,依然是父进程号。
exec*()
当然,进程肯定不止是为了复刻一个父进程。在上面的例子中,我们创建了一个子进程都是让子进程继续执行父进程的文本段,但更多的情况下是让该进程去执行另外一个程序。这时我们会在fork()之后紧接着调用exec*()系列的函数来让子进程去执行另外一个程序。
定义函数:int execl(const char * path, const char * arg, …);
函数说明:execl()用来执行参数path 字符串所代表的文件路径, 接下来的参数代表执行该文件时传递过去的argv(0), argv[1], …, 最后一个参数必须用空指针(NULL)作结束.
返回值:如果执行成功则函数不会返回, 执行失败则直接返回-1, 失败原因存于errno 中.
例如,用execl让子进程去执行ls -la :
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
int main(int argc,char **argv)
{
pid_t pid,pr;
printf("进程开始!\n");
if((pid = fork())<0)
{
printf("创建子进程失败:%s\n",strerror(errno));
return -2;
}
else if(pid == 0)
{
printf("子进程[%d] 开始运行...\n",getpid());
execl("/bin/ls", "ls", "-al",NULL);
}
else
{
printf("父进程[%d]开始运行...\n",getpid());
pr=wait(&pid);
if(pr<0)
{
printf("wait failure:%s\n",strerror(errno));
return -3;
}
printf("I catched a child process with pid of %d\n",pr);
}
printf("pid:[%d]\n",getpid());
return 0;
}
这样fork()+exec*的搭配使用就能够创建一个进程去干别的事了。
system()与popen()函数
如果我们在程序中,想执行另外一个Linux命令时,可以调用fork()然后再exec执行相应的命令即可,但这样相对比较麻烦。Linux系统提供了一个system()库函数,该库函数可以快速创建一个进程来执行相应的命令。
int system(const char *command);
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
int main(int argc,char **argv)
{
int rv=0;
char cmd_buf[64];
memset(cmd_buf,0,sizeof(cmd_buf));
snprintf(cmd_buf,sizeof(cmd_buf),"ls -la");
rv=system(cmd_buf);
if(rv<0)
{
printf("system() failure%s:\n",strerror(errno));
}
return 0;
}
运行结果:
其实也有另外一个函数popen()可以执行一条命令,并返回一个基于管道(pipe)的文件流,这样我们可以从该文件流中一行样解析了。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
int main(int argc,char **argv)
{
FILE *fd;
int rv=0;
char cmd_buf[64];
char buf[1024];
memset(cmd_buf,0,sizeof(cmd_buf));
snprintf(cmd_buf,sizeof(cmd_buf),"ls -la");
fd=popen(cmd_buf,"r");
memset(buf,0,sizeof(buf));
rv=fread(buf,sizeof(char),sizeof(buf),fd);
if(rv<0)
{
printf("fread is failure:%s\n",strerror(errno));
}
printf("%s\n",buf);
return 0;
}
运行结果如下: