/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2017. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation, either version 3 or (at your option) any *
* later version. This program is distributed without any warranty. See *
* the file COPYING.gpl-v3 for details. *
\*************************************************************************/
/* Listing 24-1 */
#include "tlpi_hdr.h"
static int idata = 111; /* Allocated in data segment */
int
main(int argc, char *argv[])
{
int istack = 222; /* Allocated in stack segment */
pid_t childPid;
switch (childPid = fork()) {
case -1:
errExit("fork");
case 0:
idata *= 3;
istack *= 3;
break;
default:
sleep(3); /* Give child a chance to execute */
break;
}
/* Both parent and child come here */
printf("PID=%ld %s idata=%d istack=%d\n", (long) getpid(),
(childPid == 0) ? "(child) " : "(parent)", idata, istack);
exit(EXIT_SUCCESS);
}
程序执行结果:
PID=21541 (child) idata=333 istack=666
PID=21540 (parent) idata=111 istack=222
子进程在fork()时拥有了自己的栈和数据段拷贝,且其对这些段中变量的修改不会影响父进程,代码中添加sleep的原因是为了让子进程先获得CPU调度,以便在父进程继续执行之前完成自身任务并退出。
/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2017. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation, either version 3 or (at your option) any *
* later version. This program is distributed without any warranty. See *
* the file COPYING.gpl-v3 for details. *
\*************************************************************************/
/* Listing 24-2 */
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{
int fd, flags;
char template[] = "/tmp/testXXXXXX";
setbuf(stdout, NULL); /* Disable buffering of stdout */
fd = mkstemp(template);
if (fd == -1)
errExit("mkstemp");
printf("File offset before fork(): %lld\n",
(long long) lseek(fd, 0, SEEK_CUR));
flags = fcntl(fd, F_GETFL);
if (flags == -1)
errExit("fcntl - F_GETFL");
printf("O_APPEND flag before fork() is: %s\n",
(flags & O_APPEND) ? "on" : "off");
switch (fork()) {
case -1:
errExit("fork");
case 0: /* Child: change file offset and status flags */
if (lseek(fd, 1000, SEEK_SET) == -1)
errExit("lseek");
flags = fcntl(fd, F_GETFL); /* Fetch current flags */
if (flags == -1)
errExit("fcntl - F_GETFL");
flags |= O_APPEND; /* Turn O_APPEND on */
if (fcntl(fd, F_SETFL, flags) == -1)
errExit("fcntl - F_SETFL");
_exit(EXIT_SUCCESS);
default: /* Parent: can see file changes made by child */
if (wait(NULL) == -1)
errExit("wait"); /* Wait for child exit */
printf("Child has exited\n");
printf("File offset in parent: %lld\n",
(long long) lseek(fd, 0, SEEK_CUR));
flags = fcntl(fd, F_GETFL);
if (flags == -1)
errExit("fcntl - F_GETFL");
printf("O_APPEND flag in parent is: %s\n",
(flags & O_APPEND) ? "on" : "off");
exit(EXIT_SUCCESS);
}
}
执行结果:
File offset before fork(): 0
O_APPEND flag before fork() is: off
Child has exited
File offset in parent: 1000
O_APPEND flag in parent is: on
分析:
for()之后父子进程间文件共享包括文件偏移量以及文件状态标志,系统调用wait()等待调用进程的任一子进程终止。
/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2017. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation, either version 3 or (at your option) any *
* later version. This program is distributed without any warranty. See *
* the file COPYING.gpl-v3 for details. *
\*************************************************************************/
/* Listing 24-3 */
#define _BSD_SOURCE /* To get sbrk() declaration from <unistd.h> in case
_XOPEN_SOURCE >= 600; defining _SVID_SOURCE or
_GNU_SOURCE also suffices */
#include <sys/wait.h>
#include "tlpi_hdr.h"
static int
func(int arg)
{
int j;
for (j = 0; j < 0x100; j++)
if (malloc(0x8000) == NULL)
errExit("malloc");
printf("Program break in child: %10p\n", sbrk(0));
return arg;
}
int
main(int argc, char *argv[])
{
int arg = (argc > 1) ? getInt(argv[1], 0, "arg") : 0;
pid_t childPid;
int status;
setbuf(stdout, NULL); /* Disable buffering of stdout */
printf("Program break in parent: %10p\n", sbrk(0));
childPid = fork();
if (childPid == -1)
errExit("fork");
if (childPid == 0) /* Child calls func() and */
exit(func(arg)); /* uses return value as exit status */
/* Parent waits for child to terminate. It can determine the
result of func() by inspecting 'status' */
if (wait(&status) == -1)
errExit("wait");
printf("Program break in parent: %10p\n", sbrk(0));
printf("Status = %d %d\n", status, WEXITSTATUS(status));
exit(EXIT_SUCCESS);
}
func函数只在子进程中调用,因此父进内存使用量将保持不变。
/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2017. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation, either version 3 or (at your option) any *
* later version. This program is distributed without any warranty. See *
* the file COPYING.gpl-v3 for details. *
\*************************************************************************/
/* Listing 24-4 */
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{
int istack = 222;
switch (vfork()) {
case -1:
errExit("vfork");
case 0: /* Child executes first, in parent's memory space */
sleep(3); /* Even if we sleep for a while,
parent still is not scheduled */
write(STDOUT_FILENO, "Child executing\n", 16);
istack *= 3; /* This change will be seen by parent */
_exit(EXIT_SUCCESS);
default: /* Parent is blocked until child exits */
write(STDOUT_FILENO, "Parent executing\n", 17);
printf("istack=%d\n", istack);
exit(EXIT_SUCCESS);
}
}
vfork()与fork()不同的是:子进程使用父进程的内存,因此子进程对数据段、堆或栈的任何改变在父进程恢复执行时为父进程所见。vfork()的语义在于执行该调用后,系统将保证子进程先于父进程获得调度以使用CPU。
上面程序的运行结果:
Child executing
Parent executing
istack=666
/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2017. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation, either version 3 or (at your option) any *
* later version. This program is distributed without any warranty. See *
* the file COPYING.gpl-v3 for details. *
\*************************************************************************/
/* Listing 24-5 */
#include <sys/wait.h>
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{
int numChildren, j;
pid_t childPid;
if (argc > 1 && strcmp(argv[1], "--help") == 0)
usageErr("%s [num-children]\n", argv[0]);
numChildren = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-children") : 1;
setbuf(stdout, NULL); /* Make stdout unbuffered */
for (j = 0; j < numChildren; j++) {
switch (childPid = fork()) {
case -1:
errExit("fork");
case 0:
printf("%d child\n", j);
_exit(EXIT_SUCCESS);
default:
printf("%d parent\n", j);
wait(NULL); /* Wait for child to terminate */
break;
}
}
exit(EXIT_SUCCESS);
}
输入./fork_whos_on_first 100,会生成100个子进程,每次打印出父子进程执行的顺序,switch语句default中的wait(NULL)是等待子进程执行完,否则容易出现在父进程退出时子进程还未执行。
可以用如awk统计生成100000个进程后子进程先执行的有多少次。
#!/usr/bin/awk -f
#
# fork_whos_on_first.count.awk
#
# Copyright, Michael Kerrisk, 2002
#
# Read (on stdin) the results of running the fork_whos_on_first.c
# program to assess the percentage iof cases in which parent or child
# was first to print a post-fork() message
#
BEGIN {
last = -1;
}
{
# match pairs of lines by field 1 (loop counter)
if ($1 != last) {
cat[$2]++;
tot++;
}
last = $1;
}
# Our input file may be long - periodically print a progress
# message with statistics so far
NR % 200000 == 0 {
print "Num children = ", NR / 2;
for (k in cat)
printf "%-6s %6d %6.2f%%\n", k, cat[k], cat[k] / tot * 100;
}
END {
print "All done";
for (k in cat)
printf "%-6s %6d %6.2f%%\n", k, cat[k], cat[k] / tot * 100;
}
先将执行结果重定向到pairs.txt中,然后利用awk程序分析执行结果,可以看到在fork10万个子进程只有264个子进程先于父进程得到调度。
从Linux 2.6.32开始父进程默认优先调度,可以通过文件/proc/sys/kernel/sched_child_runs_first设为非0值可以改变该默认设置。