fork函数的应用与理解

一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <unistd.h>
#include <stdio.h>

int main ()
{
pid_t fpid; //fpid表示fork函数返回的值
int count = 0;
fpid = fork();
if (fpid < 0)
printf("error in fork!\n");
else if (fpid == 0)
{
printf("i am the child process, my process id is %d\n", getpid());
printf("我是爹的儿子\n");
count++;
}
else
{
printf("i am the parent process, my process id is %d\n", getpid());
printf("我是孩子他爹\n");
count++;
}
printf("统计结果是: %d\n", count);
return 0;
}

执行结果:

image-20220414141057062

在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)
为什么两个进程的fpid不同呢,这与fork函数的特性有关。

fork函数特性

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

  1. 在父进程中,fork返回新创建子进程的进程ID;
  2. 在子进程中,fork返回0;
  3. 如果出现错误,fork返回一个负值;

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

fork出错可能有两种原因

  1. 当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
  2. 系统内存不足,这时errno的值被设置为ENOMEM。

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。

fork只拷贝下一个要执行的代码到新的进程。

例子二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <unistd.h>
#include <stdio.h>

int main(void)
{
int i = 0;
printf("i son/pa ppid pid fpid\n");
//ppid指当前进程的父进程pid
//pid指当前进程的pid,
//fpid指fork返回给当前进程的值
for(i = 0; i < 2; i++)
{
pid_t fpid = fork();
if(fpid == 0)
printf("%d child %4d %4d %4d\n", i, getppid(), getpid(), fpid);
else
printf("%d parent %4d %4d %4d\n", i, getppid(), getpid(), fpid);
}
return 0;
}

执行结果:

i son/pa ppid pid fpid
0 parent 2043 3224 3225
0 child 3224 3225 0
1 parent 2043 3224 3226
1 parent 3224 3225 3227
1 child 1 3227 0
1 child 1 3226 0

image-20220414142146362

exec函数组

exec函数组包含六个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
  #include <unistd.h>

  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, char *const argv[]);

  int execvp(const char *file, char *const argv[]);

  int execve(const char *path, char *const argv[], char *const envp[]);

命名规则:

  • e后续, 参数必须带环境变量部分, 环境变零部分参数会成为执行exec函数期间的环境变量, 比较少用。

  • l 后续, 命令参数部分必须以”,” 相隔, 最后1个命令参数必须是NULL。

  • v 后续, 命令参数部分必须是1个以NULL结尾的字符串指针数组的头部指针. 例如char pstr就是1个字符串的指针, char pstr[] 就是数组了, 分别指向各个字符串。
  • p后续, 执行文件部分可以不带路径, exec函数会在$PATH中找。

==exec函数会取代执行它的进程, 也就是说, 一旦exec函数执行成功, 它就不会返回了, 进程结束. 但是如果exec函数执行失败, 它会返回失败的信息, 而且进程继续执行后面的代码!==

exec函数里的参数分成3个部分:

  1. 执行文件部分
  2. 命令参数部分
  3. 环境变量部分
  • 例如我要执行1个命令 ls -l /home/gateman。执行文件部分就是 “/usr/bin/ls”
  • 命令参赛部分就是 “ls”,”-l”,”/home/gateman”,NULL 。见到是以ls开头 每1个空格都必须分开成2个部分, 而且以NULL结尾的啊。
  • 环境变量部分, 这是1个数组,最后的元素必须是NULL 。例如 char * env[] = {“PATH=/home/gateman”, “USER=lei”, “STATUS=testing”, NULL};

execv函数

1
2
3
4
5
6
7
8
9
10
11
12
if (fork() == 0){
//child process
char * execv_str[] = {"echo", "executed by execv",NULL};
if (execv("/usr/bin/echo",execv_str) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execv done\n\n");
}

execvp 函数

1
2
3
4
5
6
7
8
9
10
11
12
if (fork() == 0){
//child process
char * execvp_str[] = {"echo", "executed by execvp",">>", "~/abc.txt",NULL};
if (execvp("echo",execvp_str) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execvp done\n\n");
}

execve 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
if (fork() == 0){
//child process
char * execve_str[] = {"env",NULL};
char * env[] = {"PATH=/tmp", "USER=lei", "STATUS=testing", NULL};
if (execve("/usr/bin/env",execve_str,env) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execve done\n\n");
}

execl 函数

1
2
3
4
5
6
7
8
9
10
11
if (fork() == 0){
//child process
if (execl("/usr/bin/echo","echo","executed by execl" ,NULL) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execv done\n\n");
}

execlp 函数

1
2
3
4
5
6
7
8
9
10
11
if (fork() == 0){
//child process
if (execlp("echo","echo","executed by execlp" ,NULL) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execlp done\n\n");
}

execle 函数

1
2
3
4
5
6
7
8
9
10
11
12
if (fork() == 0){
//child process
char * env[] = {"PATH=/home/gateman", "USER=lei", "STATUS=testing", NULL};
if (execle("/usr/bin/env","env",NULL,env) <0){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execle done\n\n");
}