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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297 | // |===================================|
// | Lesson |
// |===================================|
//
// This example demonstrates how to have two processes 'talk' to each
// other.
//
// The goal of the end of this lesson is to understand how to pipe the
// output of a child process through the parent. (So it will look
// something like this: 'child | parent')
//
// After understanding the fundamentals here however, you should
// be able to pipe input/output between any process you like.
//
// |===================================|
// | INTERPROCESS COMMUNICATION (IPC) |
// |===================================|
//
// In Unix, the file is a useful abstraction! This allows us to write
// very simple programs that do one thing very well, and then send the
// result of that program through another.
//
// FILE DESCRIPTORS
//
// A file descriptor (usually abbreviated 'fd') is a non-negative
// integer number. A file descriptor is a 'handle' or index used to a
// file to perform various input/output commands.
//
// Each Unix has some default file descriptors for which we
// can stream in and out data.
//
// |-----------------------------------------------------------------|
// |Int Value | Name | symbolic constant | file stream |
// |-----------------------------------------------------------------|
// | 0 | Standard Input | STDIN_FILENO | stdin |
// | 1 | Standard Output| STDOUT_FILENO | stdout |
// | 2 | Standard Error | STDERR_FILENO | stderr |
// |-----------------------------------------------------------------|
//
// We can add more file descriptors to this table however. In fact
// when we open a new file (using 'open' or 'fopen') we are creating a
// new handle to some resource (i.e. a file) that we may want to read
// or write new data to.
//
// Okay, so with this knowledge, file descriptors are our 'window' or
// 'channel' for which two processes could potentially talk to each
// other or share data. We just have to know how to connect them
// together.
//
// This example will show how to make two processes talk to each
// other.
//
// The commands used are:
//
// - fork()
// - waitpid(childpid, status, options)
// - man waitpid
// - pipe(int pipefd[2])
// - man 2 pipe
// - man 7 pipe
// - dup2(int oldfd, int newfd)
// - man dup
// - close(fd);
// Start of our Program
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
int main(int argc, char **argv) {
// First, lets see the actual file descriptors and their values.
// It can be helpful to access them by name instead of memorizing
// the values 0, 1, 2.
//
// Let's confirm the values of the default file descriptors of our
// input, output, and error.
printf("STDIN_FILENO = %d\n", STDIN_FILENO);
printf("STDOUT_FILENO = %d\n", STDOUT_FILENO);
printf("STDERR_FILENO = %d\n\n", STDERR_FILENO);
// Now our goal is to create two processes that can communicate
// between each other. In order to do this, we will create a
// 'pipe' between them. We can literally think of this as a 'pipe'
// connecting together, and creating a channel where information
// can be passed.
//
// -----------| |----------|
// | Here is | | Another |
// | a -|--->------>----->------->------>----->| Process |
// | process | Here is a pipe connecting 2 processes| being |
// | -|-->------->----->------->------>----->| connected|
// |----------| |----------|
// (Notice the flow of information
// from left to right in the pipe)
//
// The direction need not always be unidirectional (i.e. a one way
// channel) But that is what I am going to demonstrate.
//
//
// First, let's have some storage for file descriptors for which
// our pipes 'read' end and 'write' end will be.
// Thus, we need an array of two integers to hold our file descriptors.
int fd[2];
// We will use the pipe command (pipe2 also exists if we need more
// control, but for now let's use pipe).
// pipe() creates two file descriptors for which we can pass
// information along.
// fd[0] is the 'read' end
// fd[1] is the 'write' end.
if (pipe(fd)) {
perror("pipe() failed");
exit(EXIT_FAILURE);
}
// When the above statement is executed, two new file descriptors
// are created as the next available integers. We now have handles
// to the read and write end of the pipe.
// Let's print the file descriptors out to confirm.
// You should just see two integer values that are different if
// everything worked correctly. You will also notice that they are
// not a value of 0, 1, or 2, because those are already taken.
printf("pipe fd[0] (for reading) = %d\n", fd[0]);
printf("pipe fd[1] (for writing) = %d\n\n", fd[1]);
// Now our goal is to do something more interesting and have two
// separate processes talk to each other.
// So that means we need to launch another process so we can
// connect our pipe between the two processes.
//
// Our mechanism to do so is the 'fork()' command. Lets go ahead
// and fork() to create a new child process.
// Let's store the child process id somewhere.
pid_t childProcessID;
// Lets store the status somewhere which will tell us when the child
// process has completed executing.
int child_status;
// Execute our fork() and duplicate our parent.
// Check that a child was successfully created.
if ((childProcessID=fork()) == -1) {
perror("fork failed!");
exit(EXIT_FAILURE);
}
// We want to execute the child code first. Whatever happens in
// the child, we will output that into our pipe and then our
// parent will print out the resulting output. Our pipe can hold
// quite a bit of bytes as the result of whatever our child
// outputs--for now we will do a small example.
//
// Okay, so at this point, lets execute a separate child and parent.
// Remember, the pid that is returned is '0', then
// we are in the child process.
if (childProcessID == 0) {
// Child will execute here -- and it will do so first because
// we have some synchronization (i.e. a wait) in our parent.
//
// Remember, our child inherits (almost) everything from the
// parent, includng the file descriptors. Let's print the
// child file descriptors just to see.
printf("child copy of pipe fd[0] = %d\n", fd[0]);
printf("child copy of pipe fd[1] = %d\n\n", fd[1]);
// Let's do something with our child process, such as execute
// a command like 'echo' that will output a message.
// In order to do this, we will use the 'exec' command with
// the provided arguments.
//
// NOTE: execvpe is a one of many different exec commands.
// execvpe and execle have an 'e' at the end that allow
// us to specify additional environment variables
// 'exec' commands that contain a 'p' search for commands
// in the PATH.
// You can try on your shell to 'echo $PATH' to see the
// default search paths where programs will be found.
char* myargv[3];
myargv[0] = "echo";
myargv[1] = "hello from child from exec\n";
myargv[2] = NULL;
// Okay, now we are going to execute this command in our child
// process.
// You will notice in our parent code (the else block) however
// that the parent will always wait for the child. So in this
// sample, we want our child to execute, and then whatever the
// output is, we are going to pipe that to our parent process.
// Our parent process will then exec using the child's output
// as its input data, reading from the read end of our
// pipe.
//
// Let's setup the communication through our pipe.
// (1) First thing is-- we don't want our child to output
// as soon as it executes to the terminal.
if (close(STDOUT_FILENO) == -1) {
perror("Error closing STDOUT_FILENO");
exit(1);
}
// (2) Okay, now we do want to capture the output somewhere however!
// The 'dup2' command duplicates the file descriptor
// fd[1] into STDOUT_FILENO.
// Note: Printing out their values would still be unique, but
// they are both writing to the same locations.
if (dup2(fd[1], STDOUT_FILENO) == -1) {
perror("Error dup fd[1] to STDOUT_FILENO ");
exit(1);
}
// So this means we can now 'write' to our pipe either explicitly
// through fd[1] or STDOUT.
//
// Let's go ahead and write some data into our pipe now.
// It won't be printed until later on however.
if (dprintf(fd[1], "hello msg from child sent and buffered in pipe\n") < 0) {
perror("Error dprintf");
exit(1);
}
// So when we are done with a file descriptor (just like a file)
// we always close it (and now you know when we open a file up, it
// is just opening up a handle to read and/or write to some file using
// a file handle or a file descriptor)
close(fd[1]); // We are done with fd[1].
close(fd[0]); // We also do not need stdin.
// Now that everything is setup, we can execute our child.
// We will then use the output from this command, as the input
// into our parent.
if (execvp(myargv[0], myargv) == -1) {
perror("Error - execvp");
exit(1);
}
}
else {
// Our Parent process will execute this section of code.
// In our initial goal, remember we want the child to execute first
// and then the parent will use the childs output as its input.
//
// We specifically want to wait on the child that we created
// for it to complete its work.
// The 'waitpid' command allows us to wait on a specific child process
// id. And we have this childProcessID stored, so we use that.
waitpid(childProcessID, &child_status, 0);
// Okay, now lets finish off process communication.
// close stdin, because that is going to come from
// our child process.
if (close(STDIN_FILENO) == -1) {
perror("Error closing STDIN_FILENO");
exit(1);
}
// Our 'new' stdin is going to come from the
// read end of the pipe.
if (dup2(fd[0], 0) == -1) {
perror("Error dup fd[0] to 0");
exit(1);
}
// We can also close the 'write' file desc. because from our
// parent we can simply write out to STDOUT_FILENO by default.
if (close(fd[1]) == -1) {
perror("Error closing fd[1])");
exit(1);
}
// Now we can write out the data that is in our pipe.
// The data has been sitting in a buffer in our pipe, and is
// ready to be 'flushed' out and written through STDOUT.
// We are going to do this one character at a time.
printf("======= In parent process =====\n");
char c;
while(read(STDIN_FILENO, &c, 1) > 0) {
write(STDOUT_FILENO, &c, 1);
}
// And at this point, we are done!
}
return 0;
}
|