1.fork函数:Linux环境下,创建进程的主要方法是调用fork函数。Linux下所有的进程都是由init(PID为1)直接或间接创建。
pid_t fork(void); pid_t类型其实就是一个整型
返回值:如果执行成功,在父进程中返回子进程(新创建的进程)的PID,子进程将返回0;如果执行失败,则在子进程中返回-1,错误原因存储在errno中。
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
int n = 0;
int main()
{
pid_t pid = fork();
if (pid < 0) {
printf("create fail\n");
return 0;
}
else if (pid == 0) {
n++;
printf("child %d\n", n);
}
else {
n++;
printf("parent %d\n", n);
}
return 0;
}
打印结果:
father 1
child 1
可以看出:fork函数后的代码在子进程中也被执行。实际上,其他代码也在子进程的代码段,只是子进程执行的位置为fork返回位置,之前的代码无法执行。而从上面的n的返回值可以看出,父子进程各有一个自己的pcb。
1.1 fork的子进程对父进程打开的文件描述符的处理
fork函数创建子进程后,子进程将复制父进程的数据段、BSS段、代码段、堆空间、栈空间和文件描述符,而对于文件描述符关联的内核文件表项(struct file 结构),则是采用共享的方式。
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdio.h>
int main()
{
pid_t pid;
int fd;
int status;
const char* ch1 = "hello";
const char* ch2 = "worid";
const char* ch3 = "IN\n";
if ((fd = open("text01.txt", O_RDWR | O_CREAT, 0644)) == -1) {
perror("parent open");
}
if (write(fd, ch1, strlen(ch1)) == -1) { //父进程向文件中写入数据
perror("parent write");
}
if ((pid = fork()) == -1) { //创建新进程
perror("fork");
}
else if (pid == 0) {
if (write(fd, ch2, strlen(ch2)) == -1){ //子进程向文件写入
perror("child write");
}
}
else {
sleep(1); //等待子进程先执行
if (write(fd, ch3, strlen(ch3)) == -1) { //父进程向文件写入
perror("parent write");
}
wait(&status);
}
return 0;
}
打印文件内容:helloworidIN
可以看出:父进程首先创建并打开文件,然后向文件写入ch1的内容,父进程等待一秒(为了让子进程先执行),接着子进程向文件写入ch2的内容,最后父进程再写入ch3的内容。且写入数据不交叉覆盖,说明父子进程共享文件偏移,因此共享文件表项。
2.vfork函数:vfork函数创建新进程时并不复制子进程的地址空间,而是在必要的时候才重新申请新的存储空间。如果子进程执行exec()函数,则使用fork()从父进程复制到子进程的数据空间将不被使用。这样效率非常低,从而使得vfork非常有用,vfork()有时比fork()可以很大程度上提高性能。
pid_t vfork(void);
vfork在子进程环境中返回0,在父进程环境中返回子进程的进程号。
在执行过程中,fork()函数是复制一个父进程的副本,从而拥有自己独立的代码段,数据段以及堆栈空间,即成为一个独立的实体。而vfork是共享父进程的代码以及数据段。
将上面的程序稍微修改fork–>vfork
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
int main()
{
int n = 0;
pid_t pid = vfork();
if (pid < 0) {
printf("create fail\n");
return 0;
}
else if (pid == 0) {
n++;
printf("child %d\n", n);
_exit(0); //使用_exit退出
}
else {
n++;
printf("parent %d\n", n);
}
return 0;
}
打印结果:
child 1
parent 2
如果将子进程处的_exit函数拿掉,会发生未知错误。
child 1
parent 32564
child 1
Segmentation fault
注意: 由于vfork后父子进程共用同一块空间,通常情况下,操作系统会优先执行子进程,如果让子进程先执行然后return掉,那么它会释放栈空间,从而导致父进程执行错误,所以需要exit或_exit函数退出。