fork、vfork函数
每个进程都有一个非负整数表示的唯一ID。所以进程ID可以用来作为其他标志符的一部分来保证其唯一性。
当进程终止后,其ID可以重用。大多UNIX系统实现延迟算法,使赋予新建进程的ID不同于最近终止进程的ID。
系统中有一些专用进程。如ID 为0的进程通常是调度进程,常常称为__交换进程(swapper)__。交换进程是内核的一部分,它并不执行任何磁盘上的程序,所以也称为系统进程。
ID为1的通常是init进程,在自举过程结束时由内核调用。init进程负责在自举结束后启动一个UNIX系统。init进程不会被终止。它是一个普通用户进程,但是以超级用户权限运行。
下面的这些函数可以返回进程的信息:
#include <unistd.h>
pid_t getpid(void); //返回调用进程的进程ID
pid_t getppid(void); //返回调用进程的父进程ID
uid_t getuid(void); //返回调用进程的实际用户ID
uid_t geteuid(void); //返回调用进程的有效用户ID
gid_t getgid(void); //返回调用进程的实际组ID
gid_t getegid(void); //返回调用进程的有效组ID
**fork函数**
fork函数可以创建一个子进程
#include <unistd.h>
pid_t fork(void);
fork函数会返回两次,一次是在子进程中,返回值为0; 另一次在父进程中,返回值为fork创建的子进程ID。如过失败则返回-1
子进程和父进程继续执行fork调用后的指令。子进程是父进程的副本,它们共享程序的正文段。
一般fork后子进程常调用exec函数族中函数。所以很多实现并不完全将父进程的的数据段、栈、堆复制到子进程,而是使用__写时复制(Copy On Write)__技术。这些区域会由父进程和子进程共享,内核将它们改变为只读访问权限。当父子进程中任意一个试图修改这些区域,则内核只为修改区域的内存制作一个副本。
fork调用后父子进程的执行顺序是不固定的。如果要求父子进程相互同步,则要求某种形式的进程间通信。
文件共享:
如果fork之前对父进程进行IO重定向,子进程的IO也同样进行了重定向。父进程的所有打开文件描述符会被负责到子进程中。
父子进程的不同:
1. fork返回值
2. 进程ID
3. 各自的父进程ID
4. 子进程的tms_utime、tms_stime、tms_cutime和tms_ustime均设置为0.
5. 父进程设置的文件锁不会被子进程继承
6. 子进程的未处理的闹钟(alarm)被清除
7. 子进程的未处理信号集被设为空集
fork失败主要原因有:系统中进程太多、或实际用户ID的进程总数超过系统限制(CHILD_MAX规定了每个实际用户ID在任一时刻可具有的最大进程数)。
fork的用法:
1. 一个父进程希望复制自己,使父子进程同时执行不同代码段。
2. 一个进程要执行一个不同的程序。这种情况下,子进程在fork后立即调用exec。
有的系统实现了将fork之后执行exec的操作组合成了一个,并称为spawn。
vfork函数:
vfork函数与与fork相似,但有两者语义不同。
vfork用于创建一个新进程,而该新进程的目的是exec一个新进程。vfork在子进程中调用exec或exit之前,他在父进程空间中运行,不同与fork的COW。
vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#define err_sys(info) \
({ \
fprintf(stderr, "%s:%d\n", info, strerror(errno)); \
exit(EXIT_FAILURE); \
})
int main(void)
{
int var = 6;
pid_t pid;
printf("before vfork\n");
if((pid = vfork()) < 0)
err_sys("vfork error");
else if(pid == 0)
{
var++; //modify parent's variable
exit(0);
}
printf("var = %d\n", var); //output var = 7
exit(0);
}