网络编程:fork、vfort、exec、wait等函数

1.fork()函数与vfork()函数

1.1 fork()函数

使用fork()函数得到的子进程是父进程的复制品,子进程完全复制了父进程的资源,包括进程上下文、代码区、数据区、堆区、栈区、内存信息、打开文件的文件描述符、信号处理函数、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等信息,而子进程与父进程的区别有进程号、资源使用情况和计时器等

在使用fork()函数创建子进程的时候,我们的头脑内始终要有一个概念:在调用fork()函数前是一个进程在执行这段代码,而调用fork()函数后就变成了两个进程在执行这段代码。两个进程所执行的代码完全相同,都会执行接下来的if-else判断语句块。
当子进程从父进程内复制后,父进程与子进程内都有一个"pid"变量:

  • 在父进程中,fork()函数会将子进程的PID返回给父进程,即父进程的pid变量内存储的是一个大于0的整数
  • 在子进程中,fork()函数会返回0,即子进程的pid变量内存储的是0;如果创建进程出现错误,则会返回-1,不会创建子进程。

fork()函数一般不会返回错误,若fork()函数返回错误,则可能是当前系统内进程已经达到上限,或者内存不足
父子进程的运行先后顺序是完全随机的(取决于系统的调度);

1.2 vfork()函数

vfork函数:内核不再给子进程创建虚拟空间,直接让子进程共享父进程的虚拟空间。当父子进程中有更改相应段的行为发生时,再为子进程相应的段创建虚拟空间并分配物理空间。在vfork()函数创建子进程后父进程会阻塞,保证子进程先行运行

vfork()函数创建的子进程会与父进程(在调用exec函数族函数或exit()函数前)共用地址空间,此时子进程如果使用变量则会直接修改父进程的变量值。因此,vfork()函数创建的子进程可能会对父进程产生干扰。另外,如果子进程未调用exec函数族函数或exit()函数,则父子进程会出现死锁现象

举个例子,vfork()函数创建了一个“儿子”暂时“霸占”“老爹”的房产,此时需要委屈老爹一下,让老爹歇息(阻塞)。当儿子买房了(执行exec函数族函数)或者儿子死了(执行exit()退出),就相当于分家了,此时老爹得到自己的房产。

2.exec函数族

如果我们使用fork()函数创建一个子进程,则该子进程几乎复制了父进程的全部内容,也就是说,子进程与父进程在执行同一个可执行程序。那么我们能否让子进程不执行父进程正在执行的程序呢?
exec函数族内的函数可以根据指定的文件名或目录名找到可执行程序,并加载新的可执行程序,替换掉旧的代码区、数据区、堆区、栈区与其他系统资源。这里的可执行程序既可以是二进制文件,也可以是脚本文件。在执行exec函数族函数后,除了该进程的进程号PID,其他内容都被替换了。execl(完整的路径名,列表……);

        int execl(const char *path, const char *arg,)
        int execlp(const char *file, const char *arg,)
        int execle(const char *path, const char *arg,, 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[])
		execl(完整的路径名,列表……)execlp(文件名,列表……)execle(完整的路径,列表……,环境变量的向量表)
        execv(完整的路径名,向量表)execvp(文件名,向量表)execve(完整的路径,向量表,环境变量的向量表)    
函数参数:
    path:文件路径,使用该参数需要提供完整的文件路径
    file:文件名,使用该参数无需提供完整的文件路径,终端会自动根据$PATH的值查找文件路径
    arg:以逐个列举方式传递参数
    argv:以指针数组方式传递参数
    envp:环境变量数组
返回值:-1(通常情况下无返回值,当函数调用出错才有返回值-1)

3.exit()函数与_exit()函数

我们需要结束一个进程的时候,我们可以使用exit()函数或_exit()函数来终止该进程。当程序运行到exit()函数或_exit()函数时,进程会无条件停止剩下的所有操作,并进行清理工作,最终将进程停止。
函数原型:
void exit(int status)
函数参数:
status 表示让进程结束时的状态(会由主进程的wait();负责接收这个返回值【也可以不接收】–>类似函数的返回值),默认使用0表示正常结束

_exit()函数直接使进程停止运行,当调用_exit()函数时,内核会清除该进程的内存空间,并清除其在内核中的各种数据。

exit() 函数则在_exit()函数的基础上进行了升级,在退出进程之间增加了若干工序。exit()函数在终止进程之前会检测进程打开了哪些文件,并将缓冲区内容写回文件

4.wait()函数与waitpid()函数

函数wait原型:
        pid_t wait(int *status)    

函数参数:
status 保存子进程结束时的状态(由exit();返回的值)。使用地址传递,父进程获得该变量。若无需获得状态,则参数设置为NULL

pid = wait(NULL);//等待子进程结束

函数waitpid():

函数参数:

pid_t waitpid(pid_t pid, int *status, int options)

pid pid是一个整数,具体的数值含义为:
pid>0 回收PID等于参数pid的子进程
pid==-1 回收任何一个子进程。此时同wait()
pid==0 回收其组ID等于调用进程的组ID的任一子进程
pid<-1 回收其组ID等于pid的绝对值的任一子进程
status 同wait()
options
0:同wait(),此时父进程会阻塞等待子进程退出
WNOHANG:若指定的进程未结束,则立即返回0(不会等待子进程结束)

返回值

0 已经结束运行的子进程号
0 使用WNOHANG选项且子进程未退出
-1 错误

//循环判断;
ret = waitpid(pid,NULL,WNOHANG);//回收子进程,使用WNOHANG选项参数

以上内容来源

如有侵权,请留言,我将删除,感谢上面博客的笔记;

5.父子进程和fork的理解

#include <unistd.h> 
#include <sys/types.h> 
main () 
{ 
        pid_t pid; 
        pid=fork(); 
        if (pid < 0) 
                printf("error in fork!"); 
        else if (pid == 0) 
                printf("i am the child process, my process id is %d\n",getpid()); 
        else 
                printf("i am the parent process, my process id is %d\n",getpid()); 
}
  1. 应用fork()函数为什么if和else都执行了?

这里的if和else不是以前理解的选择分支。fork后产生的子进程和父进程并行运行的.这种理解是不正确的。if 和 else 还是选择分支。 主要的原因是,fork() 函数调用一次,返回两次。两次返回的区别是:子进程的返回值是0,父进程返回值为新子进程的进程ID。

顿悟:
当调用了fork函数,它会返回两个值,并不是执行了fork()那句后,一下返回两个值,而是分别返回,所以如果先返回>0,就先执行父进程,在父进程执行过程中,子进程在某个时候被调用,再次回到fork函数那里,fork函数再返回一个pid,其为0,即执行子进程;执行子进程后在进入执行父进程之后的部分,有点绕;
如下:

#include <unistd.h> 
#include<stdio.h>
#include <sys/types.h> 
main () 
{ 
        pid_t pid; 
        pid=fork(); 
	printf("pid:%u\n",pid);
   
        if (pid == 0) 
                printf("i am the child process, my process id is %d\n",getpid()); 
        else 
                {printf("i am the parent process, my process id is %d\n",getpid()); 
                //子进程被调用;跳到fork函数那里,执行完上面的if后再执行后面的;
                printf("after runing child!\n");
                }
}
  • 输出:
pid:28931
i am the parent process, my process id is 28930
pid:0
i am the child process, my process id is 28931

  1. 但是只有一个pid=fork(); 呀,fork()返回的第二次值在什么时候赋给pid呢
    pid这个变量是有两个的, 父进程一个, 子进程一个;
    pid=fork();
    操 作系统创建一个新的进程(子进程),并且在进程表中相应为它建立一个新的表项。新进程和原有进程的可执行程序是同一个程序;上下文和数据,绝大部分就是原 进程(父进程)的拷贝,但它们是两个相互独立的进程!此时程序寄存器pc,在父、子进程的上下文中都声称,这个进程目前执行到fork调用即将返回(此时 子进程不占有CPU,子进程的pc不是真正保存在寄存器中,而是作为进程上下文保存在进程表中的对应表项内)。问题是怎么返回,在父子进程中就分道扬镳。

父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(一个正整数),所以下面的if语句中pid<0, pid==0的两个分支都不会执行。所以输出i am the parent process…

子 进程在之后的某个时候得到调度,它的上下文被换入,占据 CPU,操作系统对fork的实现,使得子进程中fork调用返回0。所以在这个进程(注意这不是父进程了哦,虽然是同一个程序,但是这是同一个程序的另 外一次执行,在操作系统中这次执行是由另外一个进程表示的,从执行的角度说和父进程相互独立)中pid=0。这个进程继续执行的过程中,if语句中 pid<0不满足,但是pid==0是true。所以输出i am the child process…

内容来自

猜你喜欢

转载自blog.csdn.net/qq_42698422/article/details/106874760
今日推荐