fork与exec
在Linux中,都是通过fork
与vfork
系统调用来创建子进程,并且在fork完之后,通常会调用exec
命令簇来替换代码段,执行不同的任务。而在创建子进程的时候,同时通过COW
的方式创建的。
COW
,即Copy On Write。当fork
出子进程时,父进程与子进程是共用同一块内存空间存放数据、打开的文件、线程信息等等,其目的是为了让子进程可以更快的创建,并且减少内存分配以及各种数据结构的创建,共享父进程的大部分信息。只有当子进程的内存产生写操作的时候,才会Copy一份新的内存给子进程。
fork与vfork的区别
fork
所创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容。
而vfork
的区别,仅仅在于vfork
创建的子进程会先于父进程执行。
当结果返回小于0时,则代表创建子进程失败
当结果为0时,则代表子进程开始执行
当结果大于0时,返回值则代表子进程的pid,父进程继续执行
wait与waitpid
wait
允许父进程获取子进程结束时的状态,并且将子进程的task_struct从内核中清除。在调用wait
时,父进程会被阻塞等待返回。
wait
的函数原型如下:
#include <sys/types.h>
#include <wait.h>
int wait(int *status)
如果父进程没有调用
wait
获取子进程状态时,子进程会销毁,但是子进程的task_struct仍然会存在内核数据结构中,并且称之为Zombie进程,如果Zombie进程过多的话,会导致新进程无法创建,因为内核创建的进程数有限。
如果在意子进程的结果的话,可以通过status
的值来知道进程终止的原因。该原因都在sys/wait.h
头文件中定义:
宏 | 说明 |
---|---|
WIFEXITED | 如果子进程正常结束,它就返回真;否则返回假。 |
WEXITSTATUS | 如果WIFEXITED(status)为真,则可以用该宏取得子进程exit()返回的结束代码。 |
WIFSIGNALED | 如果子进程因为一个未捕获的信号而终止,它就返回真;否则返回假。 |
WTERMSIG | 如果WIFSIGNALED为真,则可以用该宏获得导致子进程终止的信号代码。 |
WIFSTOPPED | 如果当前子进程被暂停了,则返回真;否则返回假。 |
WSTOPSIG | 如果WIFSTOPPED为真,则可以使用该宏获得导致子进程暂停的信号代码。 |
而调用wait
等待的是任一子进程,如果父进程fork
了很多个子进程的话,则任一子进程返回都会触发该函数,也就无法知道获取的是哪个子进程的任务返回了。于是,就需要waitpid
函数了。
waitpid
函数原型如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
从参数中可以看到,可以指定wait
操作的pid,以及options
。而options
有两个选项:
WNOHANG
:如果pid指定的子进程没有结束,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待,不影响父进程的继续执行;如果结束了,则返回该子进程的进程号。
WUNTRACED
:如果子进程进入暂停状态,则马上返回。
如果对于子进程的状态改变不是很关心的话,也可以注册SIGCHILD
信号,当子进程状态改变时候,内核会发送该信号给父进程,让父进程接收子进程状态,可以在该信号处理函数中调用wait()
来接收子进程状态,并且让子进程可以安心改变状态。
void wait4children(int signo) {
int status;
wait(&status);
}
int main() {
...
pid_t pid;
signal(SIGCHLD, wait4children);
...
}
SIGCHILD信号代表子进程状态变更了,例如停止、继续、退出等,内核会发送这个信号通知父进程。而父进程就能通过 wait/waitpid 来获悉这些状态了。只是通常都用来关心子进程是否被销毁。
等待子进程都结束
如果需要等待子进程都结束,则需要在fork完子进程后,为每个创建的子进程调用waitpid
来等待所有子进程都结束
Android中的fork与wait
Android中Runtime.getRuntime().exec(cmd)
也会通过fork和exec来创建子进程执行cmd命令。
private void test() {
new Thread(new Runnable() {
@Override public void run() {
try {
// 打印5次top命令的输入输出
Process dumpDirectory = Runtime.getRuntime().exec("top -n 15");
// ping www.baidu.com
Process dumpTop = Runtime.getRuntime().exec("ping -c 30 www.baidu.com");
printLog("DumpDirectory", dumpDirectory.getInputStream());
printLog("DumpTop", dumpTop.getInputStream());
Log.e("Process","Process Started");
dumpDirectory.waitFor();
dumpTop.waitFor();
Log.e("Process","Process End");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
void printLog(final String tag, final InputStream input) {
new Thread(new Runnable() {
@Override public void run() {
Reader reader = new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while ((line = bf.readLine()) != null) {
Log.e("ReadBuffer", "Tag:" + tag + " Line:" + line);
}
} catch (Exception e) {
}
}
}).start();
}
在上述代码中:
- 创建了线程,在线程中创建子进程并且阻塞等待两个子进程的结果,这样不会阻塞UI线程
- 创建完Process之后,可以通过
getInputStream
获取输入流,该输入流是通过Pipe传递过来的,可以将命令执行的结果进行输出 - 而后调用
waitFor
等待两个进程的执行结束