Porting to OS/2: Case #7 fork()

fork() is used to create a copy of a parent process. kLIBC also supports it. Unfortunately, however, its implementation is imperfect more or less although it works well in general. In addition, it is not efficiency because OS/2 kernel does not support fork() directly unlike Unix-like systems. So kLIBC had to implement fork() by itself.

As I said above, fork() works well in general. So if you have no problems, it's ok to use fork(). But if you have a problem or want more efficiency, you should use another methods.

fork() alone


When Unix-like systems did not support threads, fork() was a way to implement threads. So you can replace fork() alone with a thread.

The following is an example replacing fork() with a thread in stream/cache2.c of MPlayer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
 * \return 1 on success, 0 if the function was interrupted and -1 on error
 */

int stream_enable_cache(stream_t *stream,int64_t size,int64_t min,int64_t seek_limit){
  int ss = stream->sector_size ? stream->sector_size : STREAM_BUFFER_SIZE;
  int res = -1;
  cache_vars_t* s;

  if (stream->flags & STREAM_NON_CACHEABLE) {
    mp_msg(MSGT_CACHE,MSGL_STATUS,"\rThis stream is non-cacheable\n");
    return 1;
  }
  if (size > SIZE_MAX) {
    mp_msg(MSGT_CACHE, MSGL_FATAL, "Cache size larger than max. allocation size\n");
    return -1;
  }

  s=cache_init(size,ss);
  if(s == NULL) return -1;
  stream->cache_data=s;
  s->stream=stream; // callback
  s->seek_limit=seek_limit;


  //make sure that we won't wait from cache_fill
  //more data than it is allowed to fill
  if (s->seek_limit > s->buffer_size - s->fill_limit ){
     s->seek_limit = s->buffer_size - s->fill_limit;
  }
  if (min > s->buffer_size - s->fill_limit) {
     min = s->buffer_size - s->fill_limit;
  }
  // to make sure we wait for the cache process/thread to be active
  // before continuing
  if (min <= 0)
    min = 1;

#if FORKED_CACHE
  if((stream->cache_pid=fork())){
    if ((pid_t)stream->cache_pid == -1)
      stream->cache_pid = 0;
#else
  {
    stream_t* stream2=malloc(sizeof(stream_t));
    memcpy(stream2,s->stream,sizeof(stream_t));
    s->stream=stream2;
#if defined(__MINGW32__)
    stream->cache_pid = _beginthread( ThreadProc, 0, s );
#elif defined(__OS2__)
    stream->cache_pid = _beginthread( ThreadProc, NULL, 256 * 1024, s );
#else
    {
    pthread_t tid;
    pthread_create(&tid, NULL, ThreadProc, s);
    stream->cache_pid = 1;
    }
#endif
#endif
    if (!stream->cache_pid) {
        mp_msg(MSGT_CACHE, MSGL_ERR,
               "Starting cache process/thread failed: %s.\n", strerror(errno));
        goto err_out;
    }
    // wait until cache is filled at least prefill_init %
    mp_msg(MSGT_CACHE,MSGL_V,"CACHE_PRE_INIT: %"PRId64" [%"PRId64"] %"PRId64"  pre:%"PRId64"  eof:%d  \n",
    s->min_filepos,s->read_filepos,s->max_filepos,min,s->eof);
    while(s->read_filepos<s->min_filepos || s->max_filepos-s->read_filepos<min){
    mp_msg(MSGT_CACHE,MSGL_STATUS,MSGTR_CacheFill,
        100.0*(float)(s->max_filepos-s->read_filepos)/(float)(s->buffer_size),
        s->max_filepos-s->read_filepos
    );
    if(s->eof) break// file is smaller than prefill size
    if(stream_check_interrupt(PREFILL_SLEEP_TIME)) {
      res = 0;
      goto err_out;
        }
    }
    mp_msg(MSGT_CACHE,MSGL_STATUS,"\n");
    return 1; // parent exits

err_out:
    cache_uninit(stream);
    return res;
  }

#if FORKED_CACHE
  signal(SIGTERM,exit_sighandler); // kill
  cache_mainloop(s);
  // make sure forked code never leaves this function
  exit(0);
#endif
}

#if !FORKED_CACHE
#if defined(__MINGW32__) || defined(__OS2__)
static void ThreadProc( void *s ){
  cache_mainloop(s);
  _endthread();
}
#else
static void *ThreadProc( void *s ){
  cache_mainloop(s);
  return NULL;
}
#endif
#endif


Line 38 to line 58 is the part replacing fork() with a thread. Of course, notice that a thread function replacing a child process part from line 86 to line 106.

fork() + exec()


fork() creates a copy of a parent process and exec() replaces a current process with a new process. After all, fork() + exec() executes a new process like spawn(). As a result, fork() + exec() can be replaced with spawn().

The following is an example replacing fork() + exec() with spawn() in tests/genfile.c of GNU tar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  pipe (fd);

  SET_BINARY_MODE (fd[0]);
  SET_BINARY_MODE (fd[1]);

#ifndef __OS2__
  pid = fork ();
  if (pid == -1)
    error (EXIT_FAILURE, errno, "fork");

  if (pid == 0)
    {
      /* Child */

      /* Pipe stderr */
      if (fd[1] != 2)
    dup2 (fd[1], 2);
      close (fd[0]);

      /* Make sure POSIX locale is used */
      setenv ("LC_ALL""POSIX", 1);

      execvp (exec_argv[0], exec_argv);
      error (EXIT_FAILURE, errno, "execvp %s", exec_argv[0]);
    }
#else
  {
    int saved_stderr;

    saved_stderr = dup (2);

    /* Pipe stderr */
    dup2 (fd[1], 2);

    /* Make sure POSIX locale is used */
    setenv ("LC_ALL""POSIX", 1);

    pid = spawnvp (P_NOWAIT, exec_argv[0], exec_argv);

    dup2 (saved_stderr, 2);
    close (saved_stderr);
  }
#endif

  /* Master */
  close (fd[1]);


When replacing with spawn(), you should consider the context. Because the context after fork() is splitted into a new child process and a current parent process. In the context of a child process, it has no effect on a parent process even though varialbes and environments are changed. However, in case of spawn(), the context is only the same process. So, if you change some variables and environments, then you have to restore them. In the above example, LC_ALL env var as well should be restored. But this process does not care about LC_ALL in later part, and quits itself soon. Due to this, there was no need to restore it.


Conclusion


If you have no problem with fork(), then just use it. Otherwise, replace it with a thread or spawn().

Tip


If you want for fork() to fail every time, then use -Zno-fork linker flag. Then fork() always return -1 which means failure.

댓글

이 블로그의 인기 게시물

토렌트: < 왕좌의 게임 > 시즌 1 ~ 시즌 8 완결편 마그넷

토렌트: NGC < 코스모스 > 우리말 더빙 전편(1편~13편) 마그넷

토렌트: < 스타워즈 > Ep.1 ~ Ep.6 마그넷