2
votes

The following code never ends. Why is that?

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#define SIZE 5
int nums[SIZE] = {0, 1, 2, 3, 4};
int main()
{
  int i;
  pid_t pid;
  pid = vfork();
  if(pid == 0){  /* Child process */
    for(i = 0; i < SIZE; i++){
      nums[i] *= -i;
      printf(”CHILD: %d “, nums[i]);    /* LINE X */
    }
  }
  else if (pid > 0){  /* Parent process */
    wait(NULL);
    for(i = 0; i < SIZE; i++)
      printf(”PARENT: %d “, nums[i]);   /* LINE Y */
  }
  return 0;
}

Update:

This code is just to illustrate some of the confusions I have regarding to vfork(). It seems like when I use vfork(), the child process doesn't copy the address space of the parent. Instead, it shares the address space. In that case, I would expect the nums array get updated by both of the processes, my question is in what order? How the OS synchronizes between the two?

As for why the code never ends, it is probably because I don't have any _exit() or exec() statement explicitly for exit. Am I right?

UPDATE2:
I just read: 56. Difference between the fork() and vfork() system call? and I think this article helps me with my first confusion.

The child process from vfork() system call executes in the parent’s address space (this can overwrite the parent’s data and stack ) which suspends the parent process until the child process exits.

5
int main( void ) The 80's were a long time ago.William Pursell
I think I tend to agree with the Linux man page on vfork: "It is rather unfortunate that Linux revived this spectre from the past". Given that fork is (in any version of Linux you're likely to be using) a lightweight operation, I'd stop worrying about what vfork might or might not do.Philip Kendall
There's one error in your assumptions. "Instead, it shares the address space." This is not correct. It can share the address space, but it doesn't have to. Some operating systems only implement the vfork semantics of suspending the parent but still do a copy of the address space like normal fork. The reason for that is that there were many bugs caused by sharing the address space (I seem to remember execvp on some system calling malloc and leaking memory in the parent).Art
@Art gives an reference if you think "Instead, it shares the address space." is not correct.. I like to read more :) In my knowledge vfork(), creates new process that shares the same address as calling processGrijesh Chauhan
@GrijeshChauhan Read openbsd.org/cgi-bin/cvsweb/src/sys/kern/… function sys_vfork. If it was sharing the address space the call to fork1 would have the flag FORK_SHAREVM, it doesn't. I know that other operating systems made the same choice, can't recall which ones at this moment. Last time I researched this was 10 years ago.Art

5 Answers

8
votes

To quote from the vfork(2) man page:

The vfork() function has the same effect as fork(), except that the behaviour is undefined if the process created by vfork() either modifies any data other than a variable of type pid_t used to store the return value from vfork(), or returns from the function in which vfork() was called, or calls any other function before successfully calling _exit() or one of the exec family of functions.

You're doing a whole bunch of those things, so you shouldn't expect it to work. I think the real question here is: why you're using vfork() rather than fork()?

3
votes

Don't use vfork. That's the simplest advice you can get. The only thing that vfork gives you is suspending the parent until the child either calls exec* or _exit. The part about sharing the address space is incorrect, some operating systems do it, other choose not to because it's very unsafe and has caused serious bugs.

Last time I looked at how applications use vfork in reality the absolute majority did it wrong. It was so bad that I threw away the 6 character change that enabled address space sharing on the operating system I was working on at that time. Almost everyone who uses vfork at least leaks memory if not worse.

If you really want to use vfork, don't do anything other than immediately call _exit or execve after it returns in the child process. Anything else and you're entering undefined territory. And I really mean "anything". You start parsing your strings to make arguments for your exec call and you're pretty much guaranteed that something will touch something it's not supposed to touch. And I also mean execve, not some other function from the exec family. Many libc out there do things in execvp, execl, execle, etc. that are unsafe in a vfork context.

What is specifically happening in your example:

If your operating system shares address space the child returning from main means that your environment cleans things up (flush stdout since you called printf, free memory that was allocated by printf and such things). This means that there are other functions called that will overwrite the stack frame the parent was stuck in. vfork returning in the parent returns to a stack frame that has been overwritten and anything can happen, it might not even have a return address on the stack to return to anymore. You first entered undefined behavior country by calling printf, then the return from main brought you into undefined behavior continent and the cleanup run after the return from main made you travel to undefined behavior planet.

1
votes

From the official specification:

the behavior is undefined if the process created by vfork() either modifies any data other than a variable of type pid_t used to store the return value from vfork(),

In your program you modify data other than the pid variable, meaning the behavior is undefined.

You also have to call _exit to end the process, or call one of the exec family of functions.

0
votes

The child must _exit rather than returning from main. If the child returns from main, then the stack frame does not exist for the parent when it returns from vfork.

0
votes

just call the _exit instead of calling return or insert _exit(0) to the last line in "child process". return 0 calls exit(0) while close the stdout, so when another printf follows, the program crashes.