C语言的多进程fork()、函数exec*()、system()与popen()函数

  • Post author:
  • Post category:其他



目录


进程是什么


fork()函数


wait()


exec*()


system()与popen()函数


进程是什么

进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。在操作系统原理使用这样的术语来描述的:正在运行的程序及其占用的资源(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;
}

运行结果如下:



版权声明:本文为DRAXY原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。