alp-apB-low-level-io(1).pdf

(246 KB) Pobierz
../alp/advanced-linux-programming.pdf (14)
B
Low-Level I/O
C PROGRAMMERS ON GNU/L INUX HAVE TWO SETS OF INPUT / OUTPUT functions at
their disposal.The standard C library provides I/O functions: printf , fopen , and so
on. 1 The Linux kernel itself provides another set of I/O operations that operate at a
lower level than the C library functions.
Because this book is for people who already know the C language, we’ll assume
that you have encountered and know how to use the C library I/O functions.
Often there are good reasons to use Linux’s low-level I/O functions. Many of these
are kernel system calls 2 and provide the most direct access to underlying system capa-
bilities that is available to application programs. In fact, the standard C library I/O
routines are implemented on top of the Linux low-level I/O system calls. Using the
latter is usually the most efficient way to perform input and output operations—and is
sometimes more convenient, too.
1.The C++ standard library provides iostreams with similar functionality.The standard C
library is also available in the C++ language.
2. See Chapter 8, “Linux System Calls,” for an explanation of the difference between a system
call and an ordinary function call.
3444799.003.png 3444799.004.png
282 Appendix B Low-Level I/O
Throughout this book, we assume that you’re familiar with the calls described in this
appendix.You may already be familiar with them because they’re nearly the same as
those provided on other UNIX and UNIX-like operating systems (and on the Win32
platform as well). If you’re not familiar with them, however, read on; you’ll find the
rest of the book much easier to understand if you familiarize yourself with this
material first.
B.1 Reading and Writing Data
The first I/O function you likely encountered when you first learned the C language
was printf .This formats a text string and then prints it to standard output.The gener-
alized version, fprintf , can print the text to a stream other than standard output. A
stream is represented by a FILE* pointer.You obtain a FILE* pointer by opening a file
with fopen. When you’re done, you can close it with fclose . In addition to fprintf ,
you can use such functions as fputc , fputs , and fwrite to write data to the stream, or
fscanf , fgetc , fgets , and fread to read data.
With the Linux low-level I/O operations, you use a handle called a file descriptor
instead of a FILE* pointer. A file descriptor is an integer value that refers to a particu-
lar instance of an open file in a single process. It can be open for reading, for writing,
or for both reading and writing. A file descriptor doesn’t have to refer to an open file;
it can represent a connection with another system component that is capable of send-
ing or receiving data. For example, a connection to a hardware device is represented
by a file descriptor (see Chapter 6, “Devices”), as is an open socket (see Chapter 5,
“Interprocess Communication,” Section 5.5, “Sockets”) or one end of a pipe (see
Section 5.4, “Pipes”).
Include the header files <fcntl.h> , <sys/types.h> , <sys/stat.h> , and <unistd.h>
if you use any of the low-level I/O functions described here.
B.1.1 Opening a File
To open a file and produce a file descriptor that can access that file, use the open call.
It takes as arguments the path name of the file to open, as a character string, and flags
specifying how to open it.You can use open to create a new file; if you do, pass a third
argument that specifies the access permissions to set for the new file.
If the second argument is O_RDONLY , the file is opened for reading only; an error
will result if you subsequently try to write to the resulting file descriptor. Similarly,
O_WRONLY causes the file descriptor to be write-only. Specifying O_RDWR produces a file
descriptor that can be used both for reading and for writing. Note that not all files
may be opened in all three modes. For instance, the permissions on a file might forbid
a particular process from opening it for reading or for writing; a file on a read-only
device such as a CD-ROM drive may not be opened for writing.
3444799.005.png
B.1 Reading and Writing Data
283
You can specify additional options by using the bitwise or of this value with one or
more flags.These are the most commonly used values:
n Specify O_TRUNC to truncate the opened file, if it previously existed. Data written
to the file descriptor will replace previous contents of the file.
n Specify O_APPEND to append to an existing file. Data written to the file descriptor
will be added to the end of the file.
n Specify O_CREAT to create a new file. If the filename that you provide to open
does not exist, a new file will be created, provided that the directory containing
it exists and that the process has permission to create files in that directory. If the
file already exists, it is opened instead.
n Specify O_EXCL with O_CREAT to force creation of a new file. If the file already
exists, the open call will fail.
If you call open with O_CREAT , provide an additional third argument specifying the per-
missions for the new file. See Chapter 10, “Security,” Section 10.3, “File System
Permissions,” for a description of permission bits and how to use them.
For example, the program in Listing B.1 creates a new file with the filename speci-
fied on the command line. It uses the O_EXCL flag with open , so if the file already
exists, an error occurs.The new file is given read and write permissions for the owner
and owning group, and read permissions only for others. (If your umask is set to a
nonzero value, the actual permissions may be more restrictive.)
Umasks
When you create a new file with open , some permission bits that you specify may be turned off. This is
because your umask is set to a nonzero value. A process’s umask specifies bits that are masked out of all
newly created files’ permissions. The actual permissions used are the bitwise and of the permissions you
specify to open and the bitwise complement of the umask.
To change your umask from the shell, use the umask command, and specify the numerical value of the
mask, in octal notation. To change the umask for a running process, use the umask call, passing it the
desired mask value to use for subsequent open calls.
For example, calling this line
umask (S_IRWXO | S_IWGRP);
in a program, or invoking this command
% umask 027
specifies that write permissions for group members and read, write, and execute permissions for others
will always be masked out of a new file’s permissions.
3444799.006.png
284 Appendix B Low-Level I/O
Listing B.1 ( create-file.c ) Create a New File
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main (int argc, char* argv[])
{
/* The path at which to create the new file. */
char* path = argv[1];
/* The permissions for the new file. */
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
/* Create the file. */
int fd = open (path, O_WRONLY | O_EXCL | O_CREAT, mode);
if (fd == -1) {
/* An error occurred. Print an error message and bail. */
perror (“open”);
return 1;
}
return 0;
}
Here’s the program in action:
% ./create-file testfile
% ls -l testfile
-rw-rw-r-- 1 samuel users 0 Feb 1 22:47 testfile
% ./create-file testfile
open: File exists
Note that the length of the new file is 0 because the program didn’t write any data to it.
B.1.2 Closing File Descriptors
When you’re done with a file descriptor, close it with close . In some cases, such as the
program in Listing B.1, it’s not necessary to call close explicitly because Linux closes
all open file descriptors when a process terminates (that is, when the program ends).
Of course, once you close a file descriptor, you should no longer use it.
Closing a file descriptor may cause Linux to take a particular action, depending on
the nature of the file descriptor. For example, when you close a file descriptor for a
network socket, Linux closes the network connection between the two computers
communicating through the socket.
Linux limits the number of open file descriptors that a process may have open at a
time. Open file descriptors use kernel resources, so it’s good to close file descriptors
when you’re done with them. A typical limit is 1,024 file descriptors per process.You
can adjust this limit with the setrlimit system call; see Section 8.5, “ getrlimit and
setrlimit : Resource Limits,” for more information.
3444799.001.png
B.1 Reading and Writing Data
285
B.1.3 Writing Data
Write data to a file descriptor using the write call. Provide the file descriptor, a
pointer to a buffer of data, and the number of bytes to write.The file descriptor must
be open for writing.The data written to the file need not be a character string; write
copies arbitrary bytes from the buffer to the file descriptor.
The program in Listing B.2 appends the current time to the file specified on the
command line. If the file doesn’t exist, it is created.This program also uses the time ,
localtime , and asctime functions to obtain and format the current time; see their
respective man pages for more information.
Listing B.2 ( timestamp.c ) Append a Timestamp to a File
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
/* Return a character string representing the current date and time. */
char* get_timestamp ()
{
time_t now = time (NULL);
return asctime (localtime (&now));
}
int main (int argc, char* argv[])
{
/* The file to which to append the timestamp. */
char* filename = argv[1];
/* Get the current timestamp. */
char* timestamp = get_timestamp ();
/* Open the file for writing. If it exists, append to it;
otherwise, create a new file. */
int fd = open (filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
/* Compute the length of the timestamp string. */
size_t length = strlen (timestamp);
/* Write the timestamp to the file. */
write (fd, timestamp, length);
/* All done. */
close (fd);
return 0;
}
3444799.002.png
Zgłoś jeśli naruszono regulamin