Porting to OS/2: Case #6 pipe()

Pipes are used for IPC(inter-process-communication). OS/2 kLIBC also supports them. However, there are some differences.

First, kLIBC's anonymous pipe which is provided by pipe() does not support non-blocking mode. Second, it does not work with select().

To overcome these, there are two ways. One is to use a name pipe and the other one is to use socketpair().

Named pipe


Named pipe is a IPC method based on server and client communication. Server side creates a name pipe, client side opens it, then communicate each other. Although this is IPC method, it is possible to use a named pipe in one process. We can create non-blocking pipes with this.

However, this has a problem. First, select() does not work as well, because a named pipe is not a socket. Instead, you can use native APIs such as DosWaitNPipe(). Second, some kLIBC io functions such as fcntl() do not work. In this case, you can use DosSetNPHState() or DosSetFHState() APIs.

socketpair()


socketpair() is a IPC method to overcome pipe's one-way problem. That is, socketpair() enable to communicate each other in two ways.

socketpair() has all the features which we want to have to replace pipe(). select() works well because it uses a socket. And any other kLIBC functions work well, too. This is what we want.

However, socketpair() also has shortages. Because it uses socket APIs, the result executables depend on socket dlls such as TCPIP32.DLL no matter which is a real socket program. And socketpair() is a kLIBC specific function. So handles created by socketpair() cannot be inherited by non-kLIBC child processes such as CMD.EXE.

Whereas, socketpair() is perfect if it is used only in a process or it is used with kLIBC processes only.

Conclustion


// ----- 2014/09/25
If you want to use pipes between kLIBC and non-kLIBC processes, then use a named pipe, else use socketpair().

If non-blocking is not needed and select() is not used on pipes, then use just pipe().
If non-blocking is needed, select() is not used on pipes, and pipes are used between kLIBC processes and non-kLIBC processes, then use a named pipe.
If non-blocking is needed, select() is used on pipes, and pipes are used between kLIBC processes only, then socketpair().
Otherwise, mix these three ways well. ^^
// -----

Example


The following is a simple example to implement pipe() with a named pipe and socketpair().

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
#define INCL_DOS
#include <os2.h>

#include <io.h>             /* for _imphandle() */
#include <sys/socket.h>     /* for socketpair() */

#define PIPE_NAME "\\PIPE\\MY_PIPE"

static int named_pipe( int *ph )
{
    HPIPE hpipe;
    HFILE hpipeWrite;
    ULONG ulAction;

    /* NP_NOWAIT should be specified, otherwise DosConnectNPipe() blocks.
     * If you want to change pipes to blocking-mode, then use DosSetNPHState()
     * after DosConnectNPipe()
     */

    DosCreateNPipe( PIPE_NAME,
                    &hpipe,
                    NP_ACCESS_DUPLEX,
                    NP_NOWAIT | NP_TYPE_BYTE | NP_READMODE_BYTE | 1,
                    32768, 32768, 0 );

    DosConnectNPipe( hpipe );

    DosOpen( PIPE_NAME, &hpipeWrite, &ulAction, 0, FILE_NORMAL,
             OPEN_ACTION_OPEN_IF_EXISTS,
             OPEN_SHARE_DENYREADWRITE | OPEN_ACCESS_WRITEONLY,
             NULL );

    /* _imphandle() is not required specifically, because kLIBC imports
     * native handles automatically if needed. But here use _imphandle()
     * specifically.
     */

    ph[ 0 ] = _imphandle( hpipe );
    ph[ 1 ] = _imphandle( hpipeWrite );

    return 0;

}

static int sock_pipe( int *sv )
{
    return socketpair( AF_LOCAL, SOCK_STREAM, 0, sv );
}

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#ifndef STDOUT_FILENO
#define STDOUT_FILENO  1
#endif

int main( void )
{
    int ph[ 2 ];
    int saved_stdout;
    char buf[ 512 ];
    int byte_read;

    /* If you change named_pipe() to sock_pipe(), then this program will quits
     * without printing any entries of a current directory at all.
     */

    named_pipe( ph );

    saved_stdout = dup( STDOUT_FILENO );
    dup2( ph[ 1 ], STDOUT_FILENO );

    system("cmd /c dir");

    dup2( saved_stdout, STDOUT_FILENO );
    close( saved_stdout);

    /* This has no effect on named_pipe() */
    fcntl( ph[ 0 ], F_SETFL, O_NONBLOCK );

    while(( byte_read = read( ph[ 0 ], buf, sizeof( buf ))) != -1 )
        write( STDOUT_FILENO, buf, byte_read );

    close( ph[ 0 ]);
    close( ph[ 1 ]);

    return 0;
}


* Note : when using named_pipe() in practice, you have to use different PIPE_NAME whenever named_pipe() is called. Otherwise, any other calls than first call will fail because a pipe with the same name already exists. You have to process errors by APIs and functions properly.

댓글

이 블로그의 인기 게시물

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

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

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