(1)fork和vfork
之前用过fork来创建子进程,知道可以根据返回值来区分父子进程,对于父进程来说,fork的返回值是子进程ID,对于子进程来说返回PID为0.关于这一点,vfork函数与fork函数是相同的。而fork与vfork的区别在于在哪呢?
*********************************************************************************
用fork创建的子进程会复制父进程的资源,而用vfork创建的子进程会与父进程共享地址空间。
*********************************************************************************
 
下面用函数的执行结果说明一下:
 
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int g_var = 0;
int main (void)
{
 int var = 1;
 pid_t pid;
 printf("Process ID: %ld\n",(long)getpid());
 printf("Before execute fork system call, g_val = %d,var = %d.\n",g_var,var);
 if((pid = fork())<0)
 {
  printf("Creat a new process failed.\n");
  return 1;
 }
 else if(pid==0)
 {
  g_var++;
  var++;
  printf("Process ID: %ld,g_var = %d,var =%d.\n",(long)getpid(),g_var,var);
  _exit(0);
 }
 printf("Process ID: %ld,g_var = %d,var =%d.\n",(long)getpid(),g_var,var);
 return 0;
}
修改以上代码,分别使用fork和vfork,执行结果如下:
--->使用fork
gaolu@gaolu-desktop:~$ gcc -o syst systemcall.c
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ ./syst
Process ID: 6006
Before execute fork system call, g_val = 0,var = 1.
Process ID: 6007,g_var = 1,var =2.
Process ID: 6006,g_var = 0,var =1.
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
--->使用vfork
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ gcc -o syst systemcall.c
gaolu@gaolu-desktop:~$ ./syst
Process ID: 5990
Before execute vfork system call, g_val = 0,var = 1.
Process ID: 5991,g_var = 1,var =2.
Process ID: 5990,g_var = 1,var =2.
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
以上结果可以证明:用fork函数创建子进程,子进程中修改变量不会影响父进程中变量的取值;用vfork函数创建子进程,子进程修改变量会导致父进程中对应变量值的改变。
*********************************************************************************
使用fork创建子进程往往不是为了获得完全一样的进程,而是通过exec来执行其他可执行程序,所以完全的复制有些多余,存在一定程度的资源消耗,vfork的出现促进了fork机制的改变,目前linux对fork系统调用的实现采用了写时复制(copy-on-write COW)的方法,只有子进程对内存数据进行写操作时,才会进行资源的复制。COW机制使vfork几乎没有了价值,而且子进程对内存的修改会影响父进程,需要特别注意。
**********************************************************************************
 
(2)extc函数族
在子进程中用exec执行其他的可执行程序,可以对进程中的代码段、数据段、堆栈段进行替换。主要几个函数有execl,execlp,execle,execv,execve,execvp(函数定义略)等。
 
exec后面为l,表示该函数可以使用可变参数(个数可变);包含v字符,表示支持使用参数列表;包含p,表示如果给出的程序没有给出所在路径,系统会自动搜索PATH路径(不包含p的必须给出文件所在完整路径)。
 
下面函数示范了execlp和execvp的使用,后者明显具备更大的灵活性。
#include <stdio.h>
#include <unistd.h>
int main (int argc,char* argv[])
{
 
 if(argc <2)
 {
  printf("Usage: %s path.\n",argv[0]);
  return 1;
 }
 execlp("/bin/ls","ls",argv[1],(char*)NULL); //支持变长参数,以NULL结尾
 return 0;
}
执行结果:
--->execlp()
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ gcc -o syst2 systemcall2.c
gaolu@gaolu-desktop:~$ ./syst2
Usage: ./syst2 path.
gaolu@gaolu-desktop:~$ ./syst2 /home/gaolu
commen files        music
data.dat~        picture
desktop         process.c~
document        program~
etcnetworkinterface       syst
Examples        syst2
fcitx-3.4.2        systemcall2.c
fcitx-install        systemcall2.c~
file.c~         systemcall.c~
fileopera~        test.c~
file_operate.c ~       test.dat~
fork_file.c~        test_folder
gao.lu.c~        test_program
ls~         test_program (copy)
LumaQQ         uid.c~
lumaqq_2005-linux_gtk2_x86_with_jre.tar.gz  uidprint.c~
mnt         video
module
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
--->execvp()
#include <stdio.h>
#include <unistd.h>
int main (int argc,char* argv[])
{
 
 if(argc <2)
 {
  printf("Usage: %s arg list 1 2 3...\n",argv[0]);
  return 1;
 }
 
 execvp(argv[1],&argv[1]);
 return 0;
}
执行结果:
gaolu@gaolu-desktop:~$ gcc -o syst3 systemcall2.c
gaolu@gaolu-desktop:~$ ./syst3
Usage: ./syst3 arg list 1 2 3...
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ ./syst3 ls /home/gaolu
commen files        music
data.dat~        picture
desktop         process.c~
document        program~
etcnetworkinterface       syst
Examples        syst2
fcitx-3.4.2        syst3
fcitx-install        systemcall2.c
file.c~         systemcall2.c~
fileopera~        systemcall.c~
file_operate.c ~       test.c~
fork_file.c~        test.dat~
gao.lu.c~        test_folder
ls~         test_program
LumaQQ         test_program (copy)
lumaqq_2005-linux_gtk2_x86_with_jre.tar.gz  uid.c~
mnt         uidprint.c~
module         video
gaolu@gaolu-desktop:~$
 
(3)exit和_exit
可以将两者用在main函数中替代return,区别是exit和_exit不考虑返回值类型。
exit和_exit之间的区别在于:
*****************************************************************************************
[1] exit是在ANSIC中说明,_exit是在POSIX中说明。
[2] exit终止调用进程,退出之前关闭所有文件,清空标准输入输出缓冲区,并执行在atexit中注册的回调函数;_exit终止调用进程,但是不关闭文件,不清除标准输入输出缓冲区,也不调用回调函数。
因此vfork()创建的子进程可以调用_exit退出,但是不要调用exit()退出。
*****************************************************************************************
举例如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void do_at_exit(void)
{
 printf("You can see the output when the program teminates.\n");
}
int main ()
{
 int flag = 0;
 
 flag = atexit(do_at_exit);
 if(flag != 0)
 {
  printf("Can't set exit function.\n");
  return EXIT_FAILURE;
 }
 
 exit(EXIT_SUCCESS);
}
执行结果:
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ gcc -o syst4 systemcall2.c
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ ./syst4
You can see the output when the program teminates.   //执行了注册的回调函数
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
将函数修改为_exit()以后执行结果如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void do_at_exit(void)
{
 printf("You can see the output when the program teminates.\n");
}
int main ()
{
 int flag = 0;
 
 flag = atexit(do_at_exit);
 if(flag != 0)
 {
  printf("Can't set exit function.\n");
  return EXIT_FAILURE;
 }
 
 _exit(EXIT_SUCCESS);
}
gaolu@gaolu-desktop:~$ gcc -o syst5 systemcall2.c
gaolu@gaolu-desktop:~$ ./syst5      //返回shell之前不会执行do_at_exit()
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$