进程创建
在linux下,创建进程可以使用两个glibc函数,分别是 fork, vfork
fork
fork函数用来创建一个子进程,声明如下:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
fork函数,一次调用,两次返回。在父进程,返回值是子进程的进程ID,在子进程,返回的是0, 可以使用getppid函数获取父进程的进程号。
fork内部调用clone系统调用。创建的子进程虽然和父进程运行在不同的内存空间,但是子进程完全复制父进程的代码和数据,刚创建成功时,两个内存空间的内容是完全一样的。这个复制使用的是写时复制。
子进程复制了父进程的数据段、BSS、代码段、堆、栈、文件描述符,父子进程不共享这些存储。两进程会共享正文段、文件表项
两个进程也有很多不同:
- 进程号
- 父进程设置的锁
- 子进程的进程资源和CPU使用时间重置为0
- 子进程未决信号集为空
如果fork返回-1,表示创建进程失败,errno 被设置
fork失败的原因有:
- 系统创建的进程达到最大进程数目限制。这时候,最好检查一下僵尸进程
- 系统内存使用完了
子进程完全复制父进程代码
#include <sys/types.h>
#include <sys/unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
pid_t id = -1;
for (int i = 0; i < 2; i++)
{
id = fork();
if (id > 0)
{
printf("parent pid: %d\n", getpid());
}
else if (id == 0)
{
printf("child pid: %d\n", getpid());
}
else
{
perror("fork");
}
}
return 0;
}
这段代码执行结果是:
parent pid: 18992
parent pid: 18992
child pid: 18993
child pid: 18994
parent pid: 18993
child pid: 18995
我们看到,for循环执行了两次,但是产生了4个不同的进程。循环n次,就创建 2^n 个进程
两进程不共享数据段、堆栈
#include <sys/types.h>
#include <sys/unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int global_a = 20;
int main()
{
int local_b = 30;
int *p = new int(10);
pid_t id = fork();
if (id > 0)
{
global_a++;
local_b++;
(*p)++;
printf("in parent pro, global_a = %d, local_b = %d, *p = %d\n", global_a, local_b, *p);
}
else if (id == 0)
{
printf("in child pro, global_a = %d, local_b = %d, *p = %d\n", global_a, local_b, *p);
}
else
{
fprintf(stderr, "error");
}
delete p;
return 0;
}
输出:
in parent pro, global_a = 21, local_b = 31, *p = 11
in child pro, global_a = 20, local_b = 30, *p = 10
父子进程的执行顺序是不确定的
如果想让两个进程保持顺序,需要用到同步的方法了
子进程继承文件描述符、共享文件表项
#include <sys/types.h>
#include <sys/unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE *pf = fopen("fil.txt", "w");
pid_t id = -1;
id = fork();
if (id > 0)
{
fprintf(pf, "parent pro")
}
else if (id == 0)
{
fprintf(pf, "parent pro")
}
fclose(pf);
return 0;
}
父子进程都写了内容到文件中,没有产生覆盖,说明父子进程共享文件偏移,就是文件表项。
子进程继承信号处理
#include <sys/types.h>
#include <sys/unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
void func(int sig)
{
printf("%d\n", sig);
}
int main()
{
signal(SIGALRM, func);
pid_t id = -1;
id = fork();
if (id > 0)
{
printf("in parent pro\n");
}
else if (id == 0)
{
raise(SIGALRM);
}
return 0;
}
这段代码在子进程可以触发 SIGALRM 信号处理函数
vfork
和fork比较,
vfork不会复制父进程的地址空间,共享父进程代码段、数据段等。在子进程调用exec,exit之前,父子进程共享数据段
父进程会阻塞,直到子进程调用exec或者退出
vfork适合在子进程直接调用exec族函数。如果调用了其它函数,因为栈共享的原因,会导致父进程段错误
#include <sys/types.h>
#include <sys/unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
pid_t id = -1;
id = vfork();
if (id > 0)
{
printf("parent process...\n");
}
else if (id == 0)
{
sleep(1);
execl("/bin/ls", "ls", nullptr);
}
return 0;
}
父子进程共享数据段、堆栈:
#include <sys/types.h>
#include <sys/unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <stdlib.h>
int global_a = 20;
int main()
{
int local_b = 30;
pid_t id = vfork();
if (id > 0)
{
printf("in parent pro, global_a = %d, local_b = %d\n", global_a, local_b);
}
else if (id == 0)
{
global_a++;
local_b++;
printf("in child pro, global_a = %d, local_b = %d\n", global_a, local_b);
_exit(0);
}
else
{
perror("vfork");
}
return 0;
}
输出:
in child pro, global_a = 21, local_b = 31
in parent pro, global_a = 21, local_b = 31
两个输出结果相同,说明,数据段和栈是共享的