Linux进程的基本操作:fork vfork exec 偏执的太偏执、 2022-05-18 05:44 164阅读 0赞 ## 进程创建 ## 进入进程的运行状态时,需要首先创建一个新的进程。在Linux系统中,提供了几个关于创建新进程的操作函数,如fork()函数、vfork()函数和exec()函数族等。 ### 1.fork()函数 ### fork()函数的功能是创建一个新的进程,新进程为当前进程的子进程,那么当前的进程就被称为父进程。在一个函数中,可以通过fork()函数的返回值判断进程是在子进程中还是父进程中。fork()函数的调用形式为: pid_t fork(void); 使用fork()函数需要引用 sys/types.h 和 unistd.h头文件,该函数的返回值类型为pid\_t,表示一个非负整数。若程序运行在父进程中,函数返回的PID为子进程的进程号;若运行在子进程中,返回的PID为0. 如若调用fork()函数创建子进程失败,那么就会返回-1,并且提示错误信息。错误信息有以下两种形式: EAGAIN:表示fork()函数没有足够的内存用于复制父进程的分页表和进程结构数据。 ENOMEM:表示fork()函数分配必要的内核数据结构时内存不足。 #include<sys/types.h> #include<stdio.h> #include<stdlib.h> #include<unistd.h> int main(void) { pid_t pid; if((pid = fork()) < 0) { printf("fork error!\n"); exit(1); } else if(pid == 0) { printf("in the child process!\n"); } else { printf("in the parrent process!\n"); } exit(0); } 在实例中,通过fork()函数的返回值确定程序是运行在父进程中还是子进程中。运行结果如下 gcc -g -o fork1 fork1.c ./fork1 in the parrent process! in the child process! 由程序的结果可以发现fork()函数的一个特点,那就是“调用一次,返回两次”,这样的特点是如何实现的呢?通过下图可以分析其原因。 ![这里写图片描述][70] 从上图可以看出,在一个程序中,调用到fork()函数后,就出现了分叉。在子进程中,fork()函数返回0;在父进程中,fork()函数返回子进程的ID。因此,fork()函数返回值后,开发人员可以根据返回值的不同,对父进程和子进程执行不同的代码,这样就使得fork()函数具有“调用一次,返回两次”的特点。但是,父进程与子进程的返回顺序并不是固定的,由于fork()函数是系统调用函数,因此取决于系统中其他进程的运行情况和内核的调度算法。 ### 2.vfork()函数 ### vfork()函数与fork()函数相同,都是系统调用函数,两者的区别是在创建子进程时fork()函数会复制所有父进程的资源,包括进程环境、内存资源等,而vfork()函数在创建子进程时不会复制父进程的所有资源,父子进程共享地址空间。这样,在子进程中对虚拟内存空间中变量的修改,实际上是在修改父进程虚拟内存空间中的值。 #include<sys/types.h> #include<stdio.h> #include<stdlib.h> #include<unistd.h> int gvar = 2; int main(void) { pid_t pid; int var = 5; printf("process id:%ld\n",(long)getpid()); printf("gvar = %d var = %d\n",gvar,var); if((pid = vfork()) < 0) { printf("error!\n"); return 1; } else if(pid == 0) { gvar--; var++; printf("the child process id:%ld\ngvar = %d var = %d\n",(long)getpid(),gvar,var); _exit(0); } else { printf("the parrent process id:%ld\ngvar = %d var = %d\n",(long)getpid(),gvar,var); return 0; } } 运行结果如下 gcc -g -o vfork2 vfork2.c ./vfork2 process id:10239 gvar = 2 var = 5 the child process id:10240 gvar = 1 var = 6 the parrent process id:10239 gvar = 1 var = 6 ### 3.exec()函数族 ### 通过调用fork()函数和vfork()函数创建子进程,子进程和父进程执行的代码是相同的。但是,通常创建了一个新进程也就是子进程后,目的时要执行与父进程不同的操作,实现不同的功能。因此,Linux系统提供了一个exec()函数族,用于创建和修改子进程。调用exec()函数时,子进程中的代码段、数据段和堆栈段都将被替换。由于调用exec()函数并没有创建新进程,因此修改后的子进程的ID并没有改变。 exec()函数族由6种以exec开头的函数组成,定义形式分别如下: 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,const char* argv[]); int execve(const char* path,const char* argv[],char* const envp[]); int execvp(const char* file,const char* argv[]); 这些函数都定义在系统函数库中,在使用前需要引用头文件sys/types.h 和 unistd.h,并且必须在预定义时定义一个外部的全局变量,例如 extern char** environ; 上面定义的变量是一个指向Linux系统全局变量的指针。定义了这个变量后,就可以在当前工作目录中执行系统程序,如同在shell中不输入路径直接运行vim和Emacs等程序一样。 exec()函数族中的函数都实现了对子进程中的数据段、代码段和堆栈段进行替换的功能,如果调用成功,则加载新的程序,没有返回值。如果调用出错,则返回值为-1。 /*******execve.c文件**********/ #include<sys/types.h> #include<unistd.h> #include<stdio.h> extern char** environ; int main(int argc,char* argv[]) { execve("new",argv,environ); puts("this information will not be output in normal situation!"); } /*************new2.c文件*************/ #include<sys/types.h> #include<unistd.h> #include<stdio.h> int main() { puts("welcome to linux!"); return 0; } 运行结果如下: gcc -o execve execve.c gcc -o new new2.c ./execve welcome to linux! 在这个运行结果中,只输出了“welcome to linux!”,说明在execve.c这个程序中执行了new2.c这个程序中的代码,那么execve.c程序中的“this information will not be output in normal situation!”这句话为什么没有正常输出呢?原因就是调用了execve()函数,调用了这个函数后,将进程中的代码段、数据段和堆栈段都进行了修改,使得这个新创建的子进程只执行了新加载的这个程序的代码,此时父进程与子进程的代码不再有任何关系。执行了execve()函数后,原来存在的代码都被释放了,即execve.c这个文件中的puts(“this information will not be output in normal situation!”)代码得不到执行,因此无法输出此信息。 [70]: /images/20220518/7f4251d5a86c42dbab39c8feb2f42b35.png
还没有评论,来说两句吧...