fork
fork()
被用于创建一个子进程。这个函数返回0代表创建成功,返回负数代表创建失败。
举例:
1 | #include <unistd.h> |
注:在 C 语言中,
pid_t
是一种数据类型,它用来表示进程 ID(Process ID),在头文件<sys/types.h>
中定义。pid_t
不是int
,尽管在许多系统上pid_t
被定义为int
,但这并不是必然的,因为它可以是不同平台上不同的类型。
当我们创建一个子进程的时候,这段代码,包括它当前的所有参数都被复制给了子进程。这样一来,子进程的操作无论怎么更改都是在子进程里生效。见下图
输出:
1 | There are 5 apples |
这些参数里面,唯一不同的是pid
,子进程的pid
是0,而父进程的pid
没有赋值。我们不能说,子进程的作用域只在那个if语句中,在if外面就不是子进程了。其实不然,我们可以试一下下面的代码来证明子进程和父进程都拥有苹果,而且互不干扰。
1 | #include <unistd.h> |
输出
1 | There are 5 apples |
前一个是父进程的苹果,没有被吃掉,后一个是子进程的苹果,吃掉了四个。即使是在if语句外面也是可以被执行的。if仅仅只是用于辅助判断哪些代码可以在子进程执行,哪些代码可以在父进程执行而已
wait
搭配fork()
使用。本质上就是为了回收已经执行完毕的子进程,释放子进程的资源
没有用wait()主进程不会受到影响,但是子进程即使终止了资源也不会被回收,从而会变成僵尸进程
进程一旦调用了 wait,就 立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait 就会收集这个子进程的信息, 并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
wait()
的返回值是子进程的ID。
如果你对第一次写 hello world
有印象,你一定记得代码最后的return 0;
。课本上只会跟你说:“return
0代表这个函数的返回值是0”,但是你无法理解为什么要返回0,返回给谁。现在明白了父进程和子进程的概念,你就知道这个0是返回给父进程的,
是告诉父进程子进程的退出状态。
要接住这个返回值,我们要定义一个整型status
。后面通过这个变量来确定子进程退出的状态。
1 | #include <unistd.h> |
输出
1 | 0 |
WEXITSTATUS
是预设的函数,用来查看函数的返回值的。还有很多其他的函数可供使用,
详情点这里。
这里有一个很大的问题,一定不要弄混淆了
wait()
的返回值不是子进程的返回值, 而是子进程的id。status
不是子进程的返回值,而是进程的状态。所以子进程无论return什么,只要正常返回,status都是0
另外需要说明的是wait()
函数并不仅仅是在等到子进程结束,而是在子进程状态转移的时候返回
exec
exec()
是一系列函数的总称,主要的作用是执行某个文件。它们分别是execl()
,
execlp()
, execle()
, execv()
,
execvp()
,
execvpe()
。我后面会补充一期文件系统的复习。现在只要假设exec()
可以用来跑一个程序就好。
这么多函数,我应该用哪一个?别慌。以exec
作为前缀,我们可以这样确定要用哪一个函数:
l
代表输入的是参数,而v
代表输入的是数组。- 带
e
表示需要设置环境,不带e
表示不需要。 - 带
p
表示输入的是可执行文件的名字,不带表示输入的是可执行文件的路径(函数会根据$PATH
的路径来判断可执行文件的位置)。 下面这段代码介绍了execlp()
和execvp()
的区别。 程序会输出两段一样的结果,分别是子进程和父进程的运行结果。不同的是,父进程用execvp()
, 根据上面的判断这是一个接受参数数组和可执行文件名的函数。你可以试着自己换不同的函数来尝试他们的效果。 NULL
在函数中的作用是告诉函数参数到此为止。
1 | #include <unistd.h> |
总结一下:
- 要创建子进程我们要用
fork()
,这个函数的返回值告诉我们子进程是否创建成功。 - 要在子进程中运行可执行文件要用
exec()
,这个函数有多个变种,用哪一个根据手头有的变量确定。 wait()
被父进程用于等待子进程的状态。