[1 Since most shells offer pipes that connect only two processes applications requiring pipes used by more than two processes must be coded in a programming language such as C

Many Unix systems provide, besides the pipe( ) system call, two wrapper functions named popen( ) and pclose( ) that handle all the dirty work usually done when using pipes. Once a pipe has been created by means of the popen( ) function, it can be used with the high-level I/O functions included in the C library (fprintf( ), fscanf( ), and so on).

In Linux, popen( ) and pclose( ) are included in the C library. The popen( ) function receives two parameters: the filename pathname of an executable file and a type string specifying the direction of the data transfer. It returns the pointer to a file data structure. The popen( ) function essentially performs the following operations:

1. Creates a new pipe by using the pipe( ) system call

2. Forks a new process, which in turn executes the following operations:

a. If type is r, duplicates the file descriptor associated with the pipe's write channel as file descriptor 1 (standard output); otherwise, if type is w, duplicates the file descriptor associated with the pipe's read channel as file descriptor 0 (standard input)

b. Closes the file descriptors returned by pipe( )

c. Invokes the execve( ) system call to execute the program specified by filename

3. If type is r, closes the file descriptor associated with the pipe's write channel; otherwise, if type is w, closes the file descriptor associated with the pipe's read channel

4. Returns the address of the file file pointer that refers to whichever file descriptor for the pipe is still open

After the popen( ) invocation, parent and child can exchange information through the pipe: the parent can read (if type is r) or write (if type is w) data by using the file pointer returned by the function. The data is written to the standard output or read from the standard input, respectively, by the program executed by the child process.

The pclose( ) function (which receives the file pointer returned by popen( ) as its parameter) simply invokes the wait4( ) system call and waits for the termination of the process created by popen( ) .

19.1.2 Pipe Data Structures

We now have to start thinking again on the system call level. Once a pipe is created, a process uses the read( ) and write( ) VFS system calls to access it. Therefore, for each pipe, the kernel creates an inode object plus two file objects—one for reading and the other for writing. When a process wants to read from or write to the pipe, it must use the proper file descriptor.

When the inode object refers to a pipe, its i_pipe field points to a pipe_inode_info structure shown in Table 19-1.

Table 19-1. The pipe_inode_info structure

Type

Field

Description

struct wait_queue *

wait

Pipe/FIFO wait queue

char *

base

Address of kernel buffer

unsigned int

len

Number of bytes written into the buffer and yet to be read

unsigned int

start

Read position in kernel buffer

unsigned int

readers

Flag for (or number of) reading processes

unsigned int

writers

Flag for (or number of) writing processes

unsigned int

waiting_readers

Number of reading processes sleeping in the wait queue

unsigned int

waiting writers

Number of writing processes sleeping in the wait queue

unsigned int

r counter

Like readers, but used when waiting for a process that reads from the FIFO

unsigned int

w_counter

Like writers, but used when waiting for a process that writes into the FIFO

Besides one inode and two file objects, each pipe has its own pipe buffer—a single page frame containing the data written into the pipe and yet to be read. The address of this page frame is stored in the base field of the pipe_inode_info structure. The len field of the structure stores the number of bytes written into the pipe buffer that are yet to be read; in the following, we call that number the current pipe size.

The pipe buffer is circular and it is accessed both by reading and writing processes, so the kernel must keep track of two current positions in the buffer:

• The offset of the next byte to be read, which is stored in the start field of the pipe_inode_info structure

• The offset of the next byte to be written, which is derived from start and the pipe size (the len field of the structure)

To avoid race conditions on the pipe's data structures, the kernel prevents concurrent accesses to the pipe buffer through the use of the i_sem semaphore included in the inode object.

A pipe is implemented as a set of VFS objects, which have no corresponding disk image. In Linux 2.4, these VFS objects are organized into the pipefs special filesystem to expedite their handling (see Section 12.3.1). Since this filesystem has no mount point in the system directory tree, users never see it. However, thanks to pipefs, the pipes are fully integrated in the VFS layer, and the kernel can handle them in the same way as named pipes or FIFOs, which truly exist as files recognizable to end users (see the later section Section 19.2).

The init_pipe_fs( ) function, typically executed during kernel initialization, registers the pipefs filesystem and mounts it (refer to the discussion in Section 12.4.1):

struct file_system_type pipe_fs_type; root fs type.name = "pipefs";

root_fs_type.read_super = pipefs_read_super; root_fs_type.fs_flags = FS_NOMOUNT; register_filesystem(&pipe_fs_type);

pipe mnt = do kern mount("pipefs", 0, "pipefs", NULL);

The mounted filesystem object that represents the root directory of pipefs is stored in the pipe_mnt variable.

Was this article helpful?

0 0

Post a comment