Linux下Fork与Exec使用
Linux下进程的结构
Linux下一个进程在内存里有三部分的数据,就是"代码段"、"堆栈段"和"数据段"。其实学过汇编语言的人一定知道,一般的CPU都有上述三种段寄存器,以方便操作系统的运行。这三个部分也是构成一个完整的执行序列的必要的部分。
"代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。
"堆栈段"存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。
而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。
Linux下的进程控制
在传统的Unix环境下,有两个基本的操作用于创建和修改进程:函数fork( )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec( )用来启动另外的进程以取代当前运行的进程。
fork()
fork在英文中是"分叉"的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就"分叉"了,所以这个名字取得很形象。
调用这个fork函数时发生了什么呢?fork函数启动一个新的进程,这个进程几乎是当前进程的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。个人理解,还拷贝了环境变量。这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。它们再要交互信息时,只有通过进程间通信来实现。
也就是说,fork出的子进程的一切都来自父进程,包括代码、数据、堆栈、打开的文件等,就连代码的执行位置(状态)都是一样的。
exec( )函数族
一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,并重新加载配置文件。唯一留下的,就是进程号,个人理解,还留下了拷贝自父进程环境变量和重新加载的配置文件中的内容(环境变量,函数等等)
我们平时使用shell命令时,产生的子进程方式大体有以下两种
第一种只使用 fork() 函数,子进程几乎是当前进程的一个拷贝,父进程中的函数、全局变量、别名以及环境变量在子进程中仍然有效。不会重新加载配置文件
注意:子进程只是父进程的拷贝,两者并不共享,所以在子进程中改变了某个全局变量或者环境变量,不会反应到父进程中,父进程中该变量的值保持原样。同样,在子进程被创建后,在改变父进程中变量的值,也不会反应到子进程中。
第二种使用 fork() 后,又使用了 exec() 函数。这样新建子进程只留下了进程ID和环境变量,及重新加载的配置文件。其余的全局变量,别名等就不存在了。相当于exce对子进程进行了格式化一样
以新进程的方式运行脚本文件,比如bash ./test.sh
、chmod +x ./test.sh; ./test.sh
,或者在当前 Shell 中使用 bash 命令启动新的 Shell,它们都属于第二种创建子进程的方式,所以子进程除了能继承父进程的环境变量外,基本上也不能使用父进程的什么东西了,比如,父进程的全局变量、局部变量、文件描述符、别名等在子进程中都无效。
但是,组命令、命令替换、管道这几种语法都使用第一种方式创建进程,所以子进程可以使用父进程的一切,包括全局变量、局部变量、别名等。
为了方便两者的区别,我们称第一种为子shell,第二种为子进程。