用fork()+exec()运行子进程

诀窍是在子进程中调用exec()函数,这样原来的父进程就能继续运行了。我们一步一步来看。

用fork()+exec()运行子进程 - 图1

  • 复制进程

第一步用fork()系统调用复制当前进程。

进程需要以某种方式区分自己是父进程还是子进程,为此fork()函数向子进程返回0,向父进程返回非零值。

  • 如果是子进程,就调用exec()

这一刻,你有两个完全相同的进程在运行,它们使用相同的代码,但子进程(从fork()接收到0的那个)现在需要调用exec()运行程序替换自己:

用fork()+exec()运行子进程 - 图2

现在你有两个独立的进程:子进程在运行rssgossip.py脚本,而原来的父进程可以继续做其他事,完全不受干扰。

用fork()+exec()运行子进程 - 图3代码冰箱贴

下面就来修改newshound程序。代码需要在独立进程中为每条RSS源运行rssgossip.py脚本。我们缩减了代码,你只需关注主循环即可。记得检查错误,千万别把父进程和子进程搞混了!

用fork()+exec()运行子进程 - 图4

 

用fork()+exec()运行子进程 - 图5叉子函数

你可以像这样调用fork()

  1. pid_t pid = fork();

fork()会返回一个整型值:为子进程返回0,为父进程返回一个正数。父进程将接收到子进程的进程标识符。

什么是pid_t?不同操作系统用不同的整数类型保存进程ID,有的用short,有的用int,操作系统使用哪种类型,pid_t就设为哪个。

用fork()+exec()运行子进程 - 图6

用fork()+exec()运行子进程 - 图7代码冰箱贴解答

下面就来修改newshound程序。代码需要在独立进程中为每条RSS源运行rssgossip.py脚本。我们缩减了代码,你只需关注主循环即可。记得检查错误,千万别把父进程和子进程搞混了!

用fork()+exec()运行子进程 - 图8

用fork()+exec()运行子进程 - 图9试驾

现在编译并运行代码,将看到:

用fork()+exec()运行子进程 - 图10

通过克隆自己,然后在独立进程中运行Python脚本,newshound成功地为每个RSS源运行了独立的进程,而且最妙的是这些进程将同时运行。

用fork()+exec()运行子进程 - 图11

这要比逐条读取新闻源快多了。通过学习用fork()exec()创建并运行独立进程,不但能更好地利用现有软件,而且还能提高程序的性能。

这里没有蠢问题

问:system()能在独立进程中运行程序吗?

:可以,但使用system()使你没办法控制程序的运行方式。

问:克隆进程岂不是很慢?我的意思是在用exec()替换子进程前我们还要等fork()复制完整个进程。

:为了让fork进程变快,操作系统使用了很多技巧。比如操作系统不会真的复制父进程的数据,而是让父子进程共享数据。

问:这样一来,如果子进程修改了存储器中的数据,岂不是会把事情搞砸?

:不会,如果操作系统发现子进程要修改存储器,就会为它复制一份。

问:这技术听起来真酷,有名字吗?

:有,叫“写时复制”(copy-on-write)。

问:pid_t就是int吗?

:这取决于平台,你唯一知道的就是它是整型。

问:我在int里保存fork()调用的结果,程序还是能运行。

:最好还是用pid_t来保存进程ID,否则当把代码拿到其他机器上编译时可能会出错。

问:为什么Windows不支持fork()系统调用?

:Windows管理进程的方式和其他操作系统完全不同,那些用来提高fork()效率的方法在Windows上很难实现,这可能就是为什么Windows没有内置的fork()

问:但Cygwin能让我在Windows中调用fork(),对吗?

:是的,为了让Windows的进程看起来和Linux、Unix和Mac的一样,写Cygwin的专家做了很多工作。但由于他们还是需要依靠Windows来创建底层进程,所以Cygwin上的fork()要比其他平台上的fork()慢一些。

问:如果我想让代码能在Windows上运行,有其他替代品吗?

:嗯,有一个名叫CreateProcess()的函数,它是一个加强版的system()。如果你想了解更多信息,可以到http://msdn.microsoft.com搜索CreateProcess。

问:多个新闻源的输出为什么不会混在一起?

:操作系统会确保每个字符串都完整地打印出来。

 

用fork()+exec()运行子进程 - 图12要点

  • 系统调用是内核中的函数。

  • exec()函数比system()提供了更多控制权。

  • exec()函数替换当前进程。

  • fork()函数复制当前进程。

  • 系统调用在失败时通常返回-1。

  • 系统调用失败以后会把errno变量设为错误码。