About fork and printf/write [duplicate]

2019-08-19 04:47发布

问题:

Possible Duplicate:
fork() and output

by running:

#include<stdio.h>
int main()
{
    fork();
    printf("b");
    if (fork() == 0) {
        write(1, "a", 1);
    }else{
        write(1, "c", 1);
    }
    return 0;
}

I got cbcabbab, could someone explain the output to me? And if possible, is there a tool to see the running procedure step by step?

回答1:

Try running it again, you'll probably get a different output.

As for a tool to see the procedure step by step, I think strace -f might help a bit:

$ strace -f ./weirdfork
execve("./weirdfork", ["./weirdfork"], [/* 35 vars */]) = 0
... uninteresting boiler plate removed ...
clone(Process 8581 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8581
[pid  8580] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
[pid  8581] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
[pid  8580] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...>
[pid  8581] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...>
[pid  8580] <... mmap resumed> )        = 0x7fe1c7d22000
[pid  8581] <... mmap resumed> )        = 0x7fe1c7d22000
[pid  8581] clone( <unfinished ...>
[pid  8580] clone(Process 8582 attached
 <unfinished ...>
[pid  8581] <... clone resumed> child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8582
Process 8583 attached
[pid  8580] <... clone resumed> child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8583
[pid  8580] write(1, "c", 1 <unfinished ...>
[pid  8581] write(1, "c", 1cc)            = 1
[pid  8580] <... write resumed> )       = 1
[pid  8581] write(1, "b", 1b <unfinished ...>
[pid  8580] write(1, "b", 1 <unfinished ...>
[pid  8581] <... write resumed> )       = 1
b[pid  8581] exit_group(0)               = ?
Process 8581 detached
[pid  8580] <... write resumed> )       = 1
[pid  8580] exit_group(0)               = ?
[pid  8583] write(1, "a", 1 <unfinished ...>
[pid  8582] write(1, "a", 1a)            = 1
a[pid  8582] write(1, "b", 1 <unfinished ...>
[pid  8583] <... write resumed> )       = 1
[pid  8583] write(1, "b", 1b)            = 1
[pid  8583] exit_group(0)               = ?
Process 8583 detached
b<... write resumed> )                   = 1
exit_group(0)                           = ?
Process 8582 detached


回答2:

Short answer: don't mix buffered and unbuffered code.

Long answer: let's test variants of your source code using the following commands:

$ rm dump
$ for X in 0 1 2 3 4 5 6 7 8 9; do for Y in 0 1 2 3 4 5 6 7 8 9; do for Z in 0 1 2 3 4 5 6 7 8 9; do echo `./program` >> dump; done; done; done
$ sort -u dump

This executes program a thousand times, and lists all unique outputs it returned.

Buffered version: replace write by fwrite (or printf)

#include <unistd.h>
#include <stdio.h>

int main()
{
    fork();
    printf("b");
    if (fork() == 0) {
        fwrite("a", 1, 1, stdout);
    }else{
        fwrite("c", 1, 1, stdout);
    }
    return 0;
}

This gives a very regular pattern of output. In fact, only six outputs are possible:

bababcbc
babcbabc
babcbcba
bcbababc
bcbabcba
bcbcbaba

What is going on?

  1. After the first fork, there are two processes, W and Y.
  2. Both processes write a letter "b" to the stdout stream. The stream is buffered by default, so .
  3. After second fork, there are four processes: W and X, Y and Z. The stdout streams of W and X have the same state, so also the same buffer containing just "b". The same holds for Y and Z.
  4. All four processes write another letter to the stdout stream.
  5. After main returns, the C runtime takes over. Every process flushes their buffers, including the buffers of stdout.

Unbuffered version: replace printf by write

#include <unistd.h>

int main()
{
    fork();
    write(1, "b", 1);
    if (fork() == 0) {
        write(1, "a", 1);
    }else{
        write(1, "c", 1);
    }
    return 0;
}

The possible output is now a little more varied, but it's still pretty understandable, given concurrency:

bbacca
bbcaac
bbcaca
bbccaa
bcabca
bcbaca

This is probably the output you expected.

Mixed version (yours)

Your code gives many more results than the previous two variants:

cabbacbb
cabbcabb
cabbcbab
cabcabbb
cabcbabb
cabcbbab
... etc ...

This is because the write call will produce output immediately, but the buffered "b" will only be printed when each process terminates, which is after the write call, of course. Just like in the fully buffered version, every process will have that "b" in the stdout buffer, so you'll end up seeing four of them.



回答3:

Unless you specifically add code to synchronize your forked processes, they will run completely independently, and thus the order of output is completely "random". The process scheduling will decide who gets to run next, which in turn depends on how many processor cores there are in the system, what else is running, and how long each process that is currently running has been running for.

As explained in the link, you also get output from the internal buffers of printf, since the output has not yet been written to the actual file representing the stdout - you can "fix" that by adding a fflush(stdout); after printf.



标签: c buffer fork