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.
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.
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() 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.
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.
If you have no problem with fork(), then just use it. Otherwise, replace it with a thread or spawn().
If you want for fork() to fail every time, then use -Zno-fork linker flag. Then fork() always return -1 which means failure.
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.
댓글
댓글 쓰기