Linux下C语言开发(进程控制编程——fork()、exec函数族)

进程编程基础

fork()函数

在Linux中创建一个新进程的唯一方法是使用fork()函数。fork()函数是Linux中一个非常重要的函数,和以往遇到的函数有一些区别,因为fork()函数看起来执行一次却返回两个值。

1)fork()函数说明

fork()函数用于从已存在的进程中创建一个新进程。新进程称为子进程,而园进程称为父进程。使用fork()函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括进程的上下文、代码段、进程堆栈、内存信息、打开的文件描述符、符号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等。

因为子进程几乎是父进程的完全复制,所以父子两进程会运行同一个程序。这就需要用一种方式来区分它们,并使它们照此运行,否则,这两个进程不可能做不同的事。实际上是在父进程中执行fork()函数时,父进程会复制一个子进程,而且父子进程的代码从fork()函数的返回开始分别在两个地址空间中同时运行,从而使两个进程分别获得所属fork()函数的返回值,其中在父进程中的返回值是子进程的进程号,而在子进程中返回0。因此,可以通过返回值来判断该进程的父进程还是子进程。

同时可以看出,使用fork()函数的代价是很大的,它复制了父进程中的代码段、数据段和堆栈段里的大部分内容,使得fork()函数的系统开销比较大,而且执行速度也不是很快。

2)fork()函数语法


fork()函数出错可能有两种原因:

1、当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN

2、系统内存不足,这时errno的值被设置为ENOMEM


测试程序:文件名为progress.c


保存后,输入gcc progrss.c -o progress编译成二进制代码,然后输入./progress运行此程序,运行结果如下:


从这里我们可以知道父进程先运行,子进程后运行(当然不一定父进程在前,子进程在后,而是要看系统进程调度策略)。从这里也能说明我们有时候在有fork()函数的文件中添加一条打印信息,结果却能看到多条信息,那时因为一旦调用了fork()函数,则此应用将多出一个子进程在执行。上面说到,使用fork()函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括进程的上下文、代码段、进程堆栈、内存信息、打开的文件描述符、符号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等。那我们就来看看这父进程和子进程的一些系统信息:

1)输入命令ps -aux查看进程信息,从上面运行progress程序,可知父进程PID为2546,子进程PID为2547


从这里可以看出父进程和子进程的命令行(cmdline)都是./progress,即通过fork()函数创建的子进程的cmdline与父进程一样。

2)分别查看父进程与子进程的内存映射:输入vim /proc/2546/maps查看父进程内存映射      输入 vim /proc/2547/maps查看子进程内存映射


从这里可以看出父进程与子进程内存映射各段都一样。

我们必须知道,当父进程调用了fork()函数,则此时父进程和子进程分别在两个地址空间中同时运行,且同时运行与fork()后面的代码。


由于fork()完整地复制了父进程的整个地址空间(个人理解:子进程开始运行地址是紧接着fork()函数后一个地址),因此执行速度是比较慢的。为了加快fork()的执行速度,很多UNIX系统设计者创建了vfork()。vfork()也能创建新的进程,但它不产生父进程的副本。它是通过允许父子进程可访问相同物理内存,从而伪装了对进程地址空间的真实复制,当子进程需要改变内存中的数据时才复制父进程。这既是著名的“写操作时复制”(copy-on-write)技术。现在大部分嵌入式Linux系统的fork()函数调用已经采用vfork()函数的实现方式,例如uCLinux所有的多进程管理都通过vfork()来实现。

exec函数族

1)exec函数族说明

fork()函数用于创建一个子进程,该子进程几乎复制了父进程的全部内容,但是,这个新创建的进程如何执行呢?exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,执行完之后,原调用进程的内容除了进程另外,其他全部被新的进程替换了。另外,这里的可执行文件即可以是二进制文件,也可以是Linux下任何可执行的脚本文件。

在Linux中使用exec函数族组要有两种情况:

  • 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用exec函数族中的任意一个函数让自己重生。
  • 如果一个进程想执行另一个程序,那么它就可以调用fork()函数新建一个进程,然后调用exec函数族中的任意一个函数,这样看起来就像通过执行应用程序而产生了一个新进程(这种情况非常普遍)。

2)exec函数族语言

实际上,在Linux中并没有exec函数,而是有6个以exec开头的函数,它们之间的语法有细微差别


这6个函数在函数名和使用语法的规则上都有细微的区别,下面就从可执行文件查找方式,参数传递方式及环境变量这几个方法进行比较。

  • 查找方式。前面4个函数的查找方式都是完整的文件目录路径,而最后两个函数(也就是以p结尾的两个函数)可以只给出文件名,系统就会自动安装环境变量"$PATH"所指定的路径进行查找
  • 参数传递方式。exec函数族的参数传递有两种方式:一种是逐个列举方式,而另一种则是将所有参数整体构造指针数组传递。在这里是以函数名的第5个字母来区别的,字母为"l"(list)的表示逐个列举参数的方式,其语法为const char *arg;字母为“v”(vetor)的表示将所有参数整体构造指针数组传递,其语法为char *const argv[]。
参数实际上就是用户在使用这个可执行文件时所需要的全部命令选项字符串(包括该可执行程序命令本身)。 要注意的是,这些参数必须以NULL结束。
  • 环境变量。exec函数族可以默认系统的环境变量,也可以传入指定的环境变量。这里以“e”(environment)结尾的两个函数execle()和execve()就可以在envp[]中指定当前进程所使用的环境变量
事实上,这6个函数中真正的系统调用只有execve(),其他5个都是库函数,它们最终都会调用execve()这个系统调用。在使用exec函数族是,一定要加上错误判断语句。exec很容易执行失败,其中最常见的原因有:
  • 找不到文件或路径,此时errno被设置为ENOENT。
  • 数组argv和envp忘记用NULL结束,此时errno被设置为EFAUL。
  • 没有对应可执行文件的运行权限,此时errno被设置为EACCES。

3)exec使用实例

用例代码如下,文件名为execlp.c

保存后,输入gcc execlp.c -o execlp编译生成二进制代码,再次输入./execlp运行此程序,运行结果如下:

下面截图为打印信息后面截图,从这里我们也能看出程序中执行execlp("ps","ps","-ef",NULL);语句,其实就是执行ps -ef命令。同时我们也能看出新进程取代了原有进程,新进程的进程PID为原进程的进程PID:2431。同样我们不能看到printf(“child process pid is %d,ret = %d\n”,getpid(),ret);的打印信息,因为原进程的内容被新进程完全去掉了,即不往下继续执行了。这里execlp()函数参数列表中为什么最后一个要为NULL,是因为从第二个ps开始表示命令参数,而这些参数最后需要用NULL作为结尾标识符。

那么我们该如何让原进程继续往下执行呢?此时我们就需要调用fork()函数创建一个子进程,在子进程中代用execlp()函数来执行其他程序。修改execlp.c文件,修改后内容如下:


输入./execlp运行程序结果如下:



从这里可以看出原进程(父进程)没有被”ps -ef“进程取代,而是子进程被去掉了(0 == fork(),返回值为0表示子进程)。同样这里可以看出父进程先执行完。

当使用文件名进行查找(exec函数族,以p结尾的两个函数)时,系统就会自动按照环境变量"$PATH"所指定的路径进行查找可执行文件,接下来就来验证一下:

1)输入 echo $PATH 单独查PATH环境变量,或输入 env 查看环境变量,或输入 export 查看PATH值


2)查看ps命令的路径,最终找到ps命令在/bin目录下

3)输入./ps -ef 



接下来的实例使用完整的文件目录来查找对应的可执行文件,不过要注意,目录必须以"/"开头,否是将其视为文件名。

程序代码如下:文件名为execl.c


保存后,输入gcc execl.c -o execl编译生成二进制代码,再次输入./execl运行此程序,运行结果和上面例程运行结果一样。

下面的示例利用execle()函数将环境变量添加到新建的子进程中,这里的“env”是查看当前进程环境变量的命令,代码如下:文件名为execle.c


运行结果如下:


最后一个示例使用execve()函数,通过构造指针数组的方式来传递参数,注意参数列表同样要以NULL作为结尾标识符,其代码如下:文件名为为execve.c


运行结果如下:






  • 5
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值