1. 进程
Linux下一个进程在内存里有三部份的数据,就是“数据段”,“堆栈段”和“代码段”,其实学过汇编语言的人一定知道,一般的CPU象I386,都有上述三种段寄存器,以方便操作系统的运行。“代码段”,顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段。 堆栈段存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。这其中有许多细节问题,这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。
1.1 一个fork例子
/*************************************************************************
> File Name: test_fork.c
> Author: basilguo@163.com
> Created Time: Thu 03 Mar 2022 12:34:19 AM PST
> Description:
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t fpid;
int count = 0;
fpid = fork();
while (1)
{
if (fpid < 0)
fprintf(stderr, "Error in fork!");
else if (fpid == 0)
{
printf("I am the child process, fpid is %d, my process id is %d."
"my parent's pid is %d.\n",
fpid, getpid(), getppid());
count ++;
} else
{
printf("I am the parent process, fpid is %d, my process id is %d,"
"my parent's pid is %d.\n",
fpid, getpid(), getppid());
count ++;
}
sleep(3);
printf("count is %d.\n", count);
}
return 0;
}
$ ./test_fork
I am the parent process, fpid is 49367, my process id is 49366,my parent's pid is 47463.
I am the child process, fpid is 0, my process id is 49367.my parent's pid is 49366.
count is 1.
I am the child process, fpid is 0, my process id is 49367.my parent's pid is 49366.
count is 1.
I am the parent process, fpid is 49367, my process id is 49366,my parent's pid is 47463.
count is 2.
I am the child process, fpid is 0, my process id is 49367.my parent's pid is 49366.
count is 2.
I am the parent process, fpid is 49367, my process id is 49366,my parent's pid is 47463.
count is 3.
I am the parent process, fpid is 49367, my process id is 49366,my parent's pid is 47463.
count is 3.
I am the child process, fpid is 0, my process id is 49367.my parent's pid is 49366.
count is 4.
I am the parent process, fpid is 49367, my process id is 49366,my parent's pid is 47463.
count is 4.
I am the child process, fpid is 0, my process id is 49367.my parent's pid is 49366.
count is 5.
I am the parent process, fpid is 49367, my process id is 49366,my parent's pid is 47463.
count is 5.
I am the child process, fpid is 0, my process id is 49367.my parent's pid is 49366.
count is 6.
I am the parent process, fpid is 49367, my process id is 49366,my parent's pid is 47463.
count is 6.
...
1.2 解释
这个程序并不直观,让我们直接看进程吧。
$ ps -aux | grep test_fork
savax 49366 0.0 0.0 4516 800 pts/7 S+ 00:54 0:00 ./test_fork
savax 49367 0.0 0.0 4516 68 pts/7 S+ 00:54 0:00 ./test_fork
$ ps -aux | grep 47463
savax 47463 0.0 0.2 23328 5848 pts/7 Ss Mar02 0:00 -bash
可以看到确实是创建了两个进程,然后两个进程交替执行,但是并不是严格同步的,可以看到并不是严格的交替。哪个进程先执行要看系统的进程调度策略。其中的count变量不是两个进程共用的,而是各用各的。
- 在父进程中,
fork返回新创建子进程的进程ID; - 在子进程中,
fork返回0; - 如果出现错误,
fork返回一个负值。fork出错可能有两种原因:- 当前的进程数已经达到了系统规定的上限,这时
errno的值被设置为EAGAIN。 - 系统内存不足,这时
errno的值被设置为ENOMEM。
- 当前的进程数已经达到了系统规定的上限,这时
getpid()可以获取当前进程号,而getppid()可以获取父进程号,所谓的47463编号其实是一个bash进程,这个bash进程就是我在执行test_fork这个程序的进程。
使用fork出来的子进程,继承了父进程的地址空间,打开的文件描述符等。如果想要fork 创建的子进程不继承父进程打开的文件描述符,可以在打开文件的时候,设置 FD_CLOSEXEC标志位,如果文件描述符中这个标志位置位,那么调用 exec 时会自动关闭对应的文件。
2. 一个进程如何来启动另一个程序的执行
在Linux中要使用exec类的函数,exec类的函数不止一个,但大致相同,在Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp为例,其它函数究竟与execlp有何区别,请通过man exec命令来了解它们的具体情况。
一个进程一旦调用exec类函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继承环境变量之类的信息。)
那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话,怎么办呢?那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序:
/*************************************************************************
> File Name: test_exec_fork.c
> Author: basilguo@163.com
> Created Time: Thu 03 Mar 2022 01:07:24 AM PST
> Description:
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(void)
{
char command[256];
int ret;
while (1)
{
printf(">>>");
fgets(command, 256, stdin);
command[strlen(command)-1] = '\0';
if (fork() == 0)
{
execlp(command, command); // run by sub-process
perror(command);
exit(errno);
}
else
{
// wait for sub-process
wait(&ret);
printf("child process returns %d.\n", ret);
}
}
return 0;
}
此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。
另外,有一个更简单的执行其它程序的函数system,它是一个较高层的函数,实际上相当于在SHELL环境下执行一条命令,而exec类函数则是低层的系统调用。